您对爱因斯坦求和的理解似乎不正确。您写出的下标运算具有正确的乘法,但总和在错误的轴上。
想想这意味着什么:np.einsum('i,ij->i', A, B)
。
A
有形有形。(i,)
_B
(i, j)
- 将每一列
B
乘以A
。
- 在 的第二个轴上求和
B
,即在标记为 的轴上求和j
。
这给出了 shape 的输出(i,) == (3,)
,而您的下标符号给出了 shape 的输出(j,) == (4,)
。你在错误的轴上求和。
更多详情:
请记住,乘法总是首先发生。左边的下标告诉np.einsum
函数输入数组的哪些行/列/等要相互相乘。此步骤的输出始终具有与最高维输入数组相同的形状。即,此时,假设的“中间”数组具有 shape (3, 4) == B.shape
。
乘法之后是求和。这由右侧省略哪些下标来控制。在这种情况下,j
省略,表示沿数组的第一个轴求和。(您正在对零进行求和。)
如果您改为写: ,则不np.einsum('i,ij->ij', A, B)
会有求和,因为没有省略下标。因此,您将在问题结束时获得数组。
这里有几个例子:
例 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
例 5:
请注意,求和确实发生了,因为我们重复了输入标签'i'
。如果我们对输入数组的每个轴使用不同的标签,我们可以计算类似于 Kronecker 产品的东西:
>>> np.einsum('i,jk', A, B).shape
(3, 3, 4)
编辑
Einstein sum 的 NumPy 实现与传统定义略有不同。从技术上讲,爱因斯坦和没有“输出标签”的概念。这些总是被重复的输入标签所暗示。
来自文档:"Whenever a label is repeated, it is summed."
因此,传统上,我们会编写类似np.einsum('i,ij', A, B)
. 这相当于np.einsum('i,ij->j', A, B)
。被i
重复,所以它被求和,只留下标记为 的轴j
。您可以将不指定输出标签的总和视为仅指定输入中不重复的标签的总和。也就是说,标签'i,ij'
与 相同'i,ij->j'
。
输出标签是在 NumPy 中实现的扩展或扩充,它允许调用者强制求和或在轴上强制不求和。从文档:"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."