关于 numpy.einsum() 的附加信息
Additional information on numpy.einsum()
我正在尝试理解 numpy.einsum() 函数,但是来自 Whosebug 的文档以及 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
这个函数实际上是如何控制的?我觉得这是由文档中提到的输出标签控制的:
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
所以我以某种方式假设放置 ->i
会禁用内部总和的求和。但这究竟是如何工作的呢?这对我来说不清楚。放置 ->j
可提供预期的实际爱因斯坦总和。
看来你对爱因斯坦求和的理解不正确。你写的下标运算乘法正确,但求和在错误的轴上。
想想这是什么意思:np.einsum('i,ij->i', A, B)
.
A
的形状为 (i,)
,B
的形状为 (i, j)
。
- 将
B
的每一列乘以 A
。
- 在
B
的第二个轴上求和,即在标记为 j
的轴上求和。
这给出了形状 (i,) == (3,)
的输出,而你的下标符号给出了形状 (j,) == (4,)
的输出。你在错误的轴上求和。
更多详情:
记住乘法总是先发生。左边的下标告诉 np.einsum
函数,输入数组的 rows/columns/etc 将与另一个相乘。此步骤的输出始终与最高维输入数组具有相同的形状。也就是说,此时,假设的 "intermediate" 数组的形状为 (3, 4) == B.shape
.
乘法之后还有求和。这是由从右侧 省略 的下标控制的。在这种情况下,省略了 j
,这意味着沿数组的第一个轴求和。 (您正在对第零求和。)
如果您改写: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
示例 5:
请注意,求和确实发生了,因为我们重复了输入标签 'i'
。如果我们为输入数组的每个轴使用不同的标签,我们可以计算类似于 Kronecker 产品的东西:
>>> np.einsum('i,jk', A, B).shape
(3, 3, 4)
编辑
爱因斯坦求和的 NumPy 实现与传统定义略有不同。从技术上讲,爱因斯坦求和没有 "output labels" 的概念。这些总是由重复的输入标签暗示。
来自文档:"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."
我正在尝试理解 numpy.einsum() 函数,但是来自 Whosebug 的文档以及 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
这个函数实际上是如何控制的?我觉得这是由文档中提到的输出标签控制的:
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
所以我以某种方式假设放置 ->i
会禁用内部总和的求和。但这究竟是如何工作的呢?这对我来说不清楚。放置 ->j
可提供预期的实际爱因斯坦总和。
看来你对爱因斯坦求和的理解不正确。你写的下标运算乘法正确,但求和在错误的轴上。
想想这是什么意思:np.einsum('i,ij->i', A, B)
.
A
的形状为(i,)
,B
的形状为(i, j)
。- 将
B
的每一列乘以A
。 - 在
B
的第二个轴上求和,即在标记为j
的轴上求和。
这给出了形状 (i,) == (3,)
的输出,而你的下标符号给出了形状 (j,) == (4,)
的输出。你在错误的轴上求和。
更多详情:
记住乘法总是先发生。左边的下标告诉 np.einsum
函数,输入数组的 rows/columns/etc 将与另一个相乘。此步骤的输出始终与最高维输入数组具有相同的形状。也就是说,此时,假设的 "intermediate" 数组的形状为 (3, 4) == B.shape
.
乘法之后还有求和。这是由从右侧 省略 的下标控制的。在这种情况下,省略了 j
,这意味着沿数组的第一个轴求和。 (您正在对第零求和。)
如果您改写: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
示例 5:
请注意,求和确实发生了,因为我们重复了输入标签 'i'
。如果我们为输入数组的每个轴使用不同的标签,我们可以计算类似于 Kronecker 产品的东西:
>>> np.einsum('i,jk', A, B).shape
(3, 3, 4)
编辑
爱因斯坦求和的 NumPy 实现与传统定义略有不同。从技术上讲,爱因斯坦求和没有 "output labels" 的概念。这些总是由重复的输入标签暗示。
来自文档:"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."