有关numpy.einsum()

时间:2017-11-18 13:48:56

标签: python numpy-einsum

我正在尝试理解numpy.einsum()函数,但来自stackoverflow的文档和this answer仍然给我留下了一些问题。

让我们考虑爱因斯坦和答案中定义的矩阵。

A = np.array([0, 1, 2])

B = np.array([[ 0,  1,  2,  3],
                  [ 4,  5,  6,  7],
                  [ 8,  9, 10, 11]])

np.einsum('i,ij->i', A, B)

所以,基于我对爱因斯坦和的理解,我会将这个函数翻译成等价的符号(A_i * B_ij),所以我会得到:

j = 1:A_1 * B_11 + A_2 * B_21 + A_3 * B_31

j = 2:A_1 * B_12 + A_2 * B_22 + A_3 * B_32

依此类推,直到j = 4.这给出了

j = 1:0 + 4 + 16

j = 2:0 + 5 + 18

根据我的理解,这将是爱因斯坦的总和。相反,该函数不执行总和,而是将单独的项存储在矩阵中,我们可以在其中发现(A_i * B_ij)的结果

0   0   0   0
4   5   6   7
16  18  20  22

这是如何实际控制的?我觉得这是由文档中提到的输出标签控制的:

  

可以通过将输出下标标签指定为来控制输出   好。这指定了标签顺序,并允许求和   在需要时被禁止或强迫

所以不知怎的,我假设把->i禁用内部总和的求和。但这是如何工作的呢?这对我来说并不清楚。放->j提供了预期的实际爱因斯坦和。

1 个答案:

答案 0 :(得分:2)

看来你对爱因斯坦求和的理解是不正确的。你写出的下标操作的乘法是正确的,但总和是在错误的轴上。

想想这意味着什么:np.einsum('i,ij->i', A, B)

  1. A的形状为(i,)B的形状为(i, j)
  2. B的每一列乘以A
  3. B的第二个轴上求和,即在标有j的轴上求和。
  4. 这会给出形状(i,) == (3,)的输出,而下标符号会给出形状(j,) == (4,)的输出。你在错误的轴上进行求和。

    更详细信息:

    请记住,乘法总是先发生。左侧下标告诉np.einsum函数输入数组的哪些行/列/等要相互相乘。此步骤的输出始终与最高维输入数组具有相同的形状。即,在这一点上,一个假设的"中间体"数组的形状为(3, 4) == B.shape

    乘法后,有求和。这是由右侧的省略的下标控制的。在这种情况下,省略j,这意味着沿阵列的第一轴求和。 (你在第0次总结。)

    如果您改为编写:np.einsum('i,ij->ij', A, B),则会有 no 求和,因为没有省略下标。因此,您可以获得在问题结束时获得的数组。

    以下是几个例子:

    例1:

    没有省略下标,所以没有总结。只需将B的列乘以A即可。这是你写完的最后一个数组。

    >>> (np.einsum('i,ij->ij', A, B) == (A[:, None] * B)).all()
    True
    

    例2:

    与示例相同。将列相乘,然后对输出列进行求和。

    >>> (np.einsum('i,ij->i', A, B) == (A[:, None] * B).sum(axis=-1)).all()
    True
    

    例3:

    您在上面写的总和。将列相乘,然后对输出的求和。

    >>> (np.einsum('i,ij->j', A, B) == (A[:, None] * B).sum(axis=0)).all()
    True
    

    例4:

    请注意,我们可以在末尾省略所有轴,以获得整个数组的总和。

    >>> np.einsum('i,ij->', A, B)
    98
    

    Ex 5:

    请注意,总和之所以发生,是因为我们重复了输入标签'i'。如果我们为输入数组的每个轴使用不同的标签,我们可以计算类似于Kronecker产品的东西:

    >>> np.einsum('i,jk', A, B).shape
    (3, 3, 4)
    

    修改

    爱因斯坦和的NumPy实现与传统定义略有不同。从技术上讲,爱因斯坦总和并没有“输出标签”的概念。重复的输入标签总是暗示这些。

    来自文档:"Whenever a label is repeated, it is summed."因此,传统上,我们会编写类似np.einsum('i,ij', A, B)的内容。这相当于np.einsum('i,ij->j', A, B)。重复i,因此将其相加,只留下标记为j的轴。您可以考虑将 no 输出标签指定为与仅指定输入中未重复的标签相同的总和。也就是说,标签'i,ij''i,ij->j'相同。

    输出标签是在NumPy中实现的扩展或扩充,允许调用者强制求和或在轴上强制执行 no 求和。来自文档:"The output can be controlled by specifying output subscript labels as well. This specifies the label order, and allows summing to be disallowed or forced when desired."