C#的张量。矩阵,向量,自定义类型,并且相对较快

你好!



我不知何故需要张量(矩阵扩展)进行投影。用Google搜索后,发现了各种各样的库,遍地都是,您需要什么-不。我必须执行为期五天的计划,并执行所需的计划。关于使用张量和优化技巧的简短说明。







那我们需要什么?



  1. N维矩阵(张量)
  2. 实现将张量用作数据结构的一组基本方法的实现
  3. 实现一组基本的数学函数(用于矩阵和向量)
  4. 泛型类型,即任何类型。和自定义运算符


在我们面前已经写了什么?
, Towel , :



  1. ,
  2. Transpose — «» , O(V), V — «».
  3. , . , , , . , ( , )


System.Numerics.Tensor . , , , . , , .



MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .



元素存储,换位,张量



这些项目将存储在一维数组中。为了从一组索引中获取元素,我们将索引乘以某些系数并相加。即,假设我们有一个张量[3 x 4 x 5]。然后,我们需要形成一个由三个元素组成的数组-(他本人想出了这个名字)。然后最后一个元素为1,倒数第二个为5,第一个元素为5 * 4 =20。即,块= [20,5,1]。例如,当按索引[1,0,4]访问时,数组中的索引看起来像20 * 1 + 5 * 0 + 4 * 1 = 24。



转置



...即更改轴的顺序。愚蠢而又简单的方法是创建一个新数组并将元素按新顺序放置在其中。但是通常方便的是进行转置,使用所需的轴顺序,然后再将轴顺序更改回去。当然,在这种情况下,您不能更改线性数组(LM)本身,并且在引用某些索引时,我们将仅更改顺序。



考虑以下功能:



private int GetFlattenedIndexSilent(int[] indices)
{
    var res = 0;
    for (int i = 0; i < indices.Length; i++)
        res += indices[i] * blocks[i];
    return res + LinOffset;
}


如您所见,如果交换这些,则将创建交换轴的可见性。因此我们将这样写:



public void Transpose(int axis1, int axis2)
{
    (blocks[axis1], blocks[axis2]) = (blocks[axis2], blocks[axis1]);
    (Shape[axis1], Shape[axis2]) = (Shape[axis2], Shape[axis1]);
}


我们只是在适当的地方更改轴的数量和长度。



子张量



N维张量的次张量是M维张量(M <N),是原始张量的一部分。例如,形状张量[2 x 3 x 4]的零元素是形状张量[3 x 4]。我们只是通过转移获得它。



假设我们在索引n处得到一个次张量然后,第一元件是所述N *块[0] + 0 *块[1] + 0 *块[2] + ...也就是说,移位为n *个块[0]因此,在不复制原始张量的情况下,我们记住了shift,创建了一个新的张量,该张量带有指向我们数据的链接,但带有一个偏移。而且,您还需要从块中抛出元素,即元素块[0],因为这是第一个轴,所以不会调用它。

其他作曲操作



所有其他人已经从这些中得到了跟随。



  1. SetSubtensor将元素转发到所需的子张量
  2. Concat创建一个新的张量,并在那里转发两个元素(而第一个轴的长度是两个张量的轴长度的总和)
  3. 堆栈将多个张量与一个附加轴组合为一个(例如,堆栈([3 x 4],[3 x 4])-> [2 x 3 x 4])
  4. 切片从某些降压器返回堆栈


我定义的所有合成操作都可以在这里找到



数学运算



这里的一切已经很简单了。



1)逐点运算(即,对于两个张量,对一对对应的元素(即,具有相同的坐标)执行运算)。这里是实现(下面解释了为什么这样丑陋的代码)。



2)在矩阵上进行运算。在我看来,乘积,求反和其他简单操作不需要解释。尽管有一个故事可以讲述行列式。



行列式的故事
, . — , , O(N!). 3x3 ( ).



, . -: float , int .



, , . , .



, . , , , , . . SafeDivisionWrapper .



: . , . SafeDivision ( , ).



3)对退伍军人的操作(点和叉积)。



优化



模板?


C#中没有模板。因此,您必须使用拐杖。有些人想出了将动态编译成委托的方法,例如,它实现了两种类型的和。



但是,我想要一个自定义,所以我启动了一个界面,用户需要从该界面继承该结构。在这种情况下,图元存储在线性数组本身中,而加,差和其他函数称为



var c = default(TWrapper).Addition(a, b);


内联在您的方法之前。这种结构的实现示例



索引编制


此外,尽管在索引器中使用参数似乎是合乎逻辑的,但实际上是这样的:



public T this[params int[] indices]


实际上,每个调用都会创建一个数组,因此您必须创建很多重载与索引一起使用的其他函数也会发生同样的情况。



例外情况


我还把所有异常都放入了#if ALLOW_EXCEPTIONS块中,以防万一您确实很快需要它,并且索引绝对没有问题。性能略有提高。



实际上,这不仅是微优化,而且在安全性方面会花费很多。例如,一个查询被发送到您的张量,您已经出于自己的原因在其中检查了数据的正确性。那为什么还需要另一张支票?而且它们不是免费的,特别是如果我们使用整数保存甚至不必要的算术运算。



多线程


谢谢Billy,事实证明它非常简单,可以通过Parallel.For实现。



尽管多线程不是万能药,但您必须仔细启用它。我为在i7-7700HQ上逐点添加张量运行了一个基准:







其中Y轴显示使用一定大小(X轴大小)的两个整数张量执行一次操作的时间(以微秒为单位)。



也就是说,存在一定的阈值,多线程才有意义。为了不必思考,我制作了Threading.Auto标志并愚蠢地对常量进行硬编码,从等于您可以启用多线程的卷开始(是否有更聪明的自动方法?)。



同时,该库仍不比游戏矩阵快,甚至比在CUDA上计算的更快。做什么的?这些已经写好了,但是我们的主要目的是自定义类型。



像这样



这是简短的笔记,感谢您的阅读。指向项目github的链接在这里它的主要用户是AngouriMath符号代数



关于我们在Angouri中的张量的一些信息
, AM- Entity, . ,



var t = MathS.Matrices.Matrix(3, 3,
              "A", "B", "C",   //      ,     "A * 3",  "sqrt(sin(x) + 5)"
              "D", "E", "F",
              "G", "H", "J");
Console.WriteLine(t.Determinant().Simplify());






A * (E * J - F * H) + C * (D * H - E * G) - B * (D * J - F * G)








All Articles