看来你对爱因斯坦求和的理解是不正确的。您已经写出的下标操作具有正确的乘法,但总和超出了错误的轴。
想想这是什么意思:np.einsum('i,ij->i', A, B)
。
A
具有形状(i,)
和B
具有形状(i, j)
。
- 将
B
的每一列乘以A
。
- 在
B
的第二轴上的和,即在标记为j
的轴上。
这给出了形状(i,) == (3,)
的输出,而您的下标符号给出形状(j,) == (4,)
的输出。你正在总结错误的轴。
更多细节:
记住乘法总是首先发生。左手下标告诉np.einsum
函数输入数组中的哪些行/列/等将相互相乘。该步骤的输出总是具有与最高维输入数组相同的形状。即,在这一点上,假设的“中间”阵列具有形状(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'
。如果我们改用不同的标签输入数组的每个轴,我们可以计算的东西类似克罗内克产品:
>>> 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
的轴。您可以考虑将输出标签指定为否的总和与仅指定输入中不重复的标签相同。也就是说,标签'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."
事实上,我有这个问题的声明“请记住,乘法总是首先发生”,这也可以在其他stackoverflow答案中找到。文档中的哪些部分正确解释了这一点? “左边的下标告诉np.einsum函数输入数组的哪些行/列/等要相互相乘”文档中解释了哪里? – FenryrMKIII
说“乘法总是先发生”并不是真的在文档中,它来自符号的定义。有关详细信息(https://en.wikipedia.org/wiki/Einstein_notation#Statement_of_convention),请参阅维基百科页面,以及我的编辑以获取有关文档如何解释输出下标的想法的更多详细信息。 – bnaecker
我了解爱因斯坦的符号。在流体力学中使用它很多。正如你在编辑中所说的那样,这里的实现似乎偏离了传统的爱因斯坦表示法。正如你所说,爱因斯坦符号将是'ij,i',然后你知道你在改变j时总结我。那么,“i,ij-> i”是指关于传统的爱因斯坦符号?它有什么作用 ?它抑制了我的总和? – FenryrMKIII