(注:这个答案是基于一个简短的博客文章 about einsum
我不久前写过。)
什么是einsum
do?
想象一下我们有两个多维数组,A
and B
。现在假设我们想要...
-
multiply
A
with B
以特定的方式创造新的产品系列;然后也许
-
sum这个沿着特定轴的新数组;然后也许
-
转置新数组的轴按特定顺序排列。
有一个很好的机会einsum
与 NumPy 函数的组合(如multiply
, sum
and transpose
会允许。
如何einsum
work?
这是一个简单(但并非完全微不足道)的示例。取以下两个数组:
A = np.array([0, 1, 2])
B = np.array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
我们将倍增A
and B
按元素排序,然后沿新数组的行求和。在“正常”NumPy 中我们会这样写:
>>> (A[:, np.newaxis] * B).sum(axis=1)
array([ 0, 22, 76])
所以这里的索引操作A
将两个数组的第一个轴对齐,以便可以广播乘法。然后对产品数组的行进行求和以返回答案。
现在如果我们想使用einsum
相反,我们可以写:
>>> np.einsum('i,ij->i', A, B)
array([ 0, 22, 76])
The 签名 string 'i,ij->i'
是这里的关键,需要一些解释。你可以把它想象成两半。在左侧(->
)我们已经标记了两个输入数组。在 - 的右边->
,我们已经标记了我们想要最终得到的数组。
接下来会发生以下情况:
-
A
有一个轴;我们已经给它贴上了标签i
. And B
有两个轴;我们将轴 0 标记为i
轴 1 为j
.
-
By 重复标签i
在两个输入数组中,我们都告诉einsum
这两个轴应该是乘以一起。换句话说,我们将数组相乘A
数组的每一列B
, 就像A[:, np.newaxis] * B
does.
-
请注意j
没有作为标签出现在我们想要的输出中;我们刚刚使用过i
(我们希望最终得到一个一维数组)。经过omitting标签,我们告诉einsum
to sum沿着这个轴。换句话说,我们对产品的行进行求和,就像.sum(axis=1)
does.
这基本上就是您使用时需要知道的全部内容einsum
。玩一点是有帮助的;如果我们在输出中保留两个标签,'i,ij->ij'
,我们得到一个二维产品数组(与A[:, np.newaxis] * B
)。如果我们说没有输出标签,'i,ij->
,我们返回一个数字(与执行相同(A[:, np.newaxis] * B).sum()
).
伟大的事情是einsum
然而,它并没有首先构建临时的产品系列;它只是将结果相加。这可以大大节省内存使用。
一个稍微大一点的例子
为了解释点积,这里有两个新数组:
A = array([[1, 1, 1],
[2, 2, 2],
[5, 5, 5]])
B = array([[0, 1, 0],
[1, 1, 0],
[1, 1, 1]])
我们将使用以下方法计算点积np.einsum('ij,jk->ik', A, B)
。这是一张显示标签的图片A
and B
以及我们从函数中获得的输出数组:
你可以看到这个标签j
被重复 - 这意味着我们将行相乘A
与列B
。此外,标签j
不包含在输出中 - 我们正在对这些乘积求和。标签i
and k
保留用于输出,因此我们返回一个二维数组。
将此结果与标签所在的数组进行比较可能会更清楚j
is not总结。下面,在左侧您可以看到由写入产生的 3D 数组np.einsum('ij,jk->ijk', A, B)
(即我们保留了标签j
):
求和轴j
给出预期的点积,如右图所示。
一些练习
为了更多地感受einsum
,使用下标表示法实现熟悉的 NumPy 数组操作非常有用。任何涉及乘法轴和求和轴组合的东西都可以使用以下方式编写einsum
.
设 A 和 B 是两个长度相同的一维数组。例如,A = np.arange(10)
and B = np.arange(5, 15)
.
-
总数是A
可以写成:
np.einsum('i->', A)
-
逐元素乘法,A * B
,可以写成:
np.einsum('i,i->i', A, B)
-
内积或点积,np.inner(A, B)
or np.dot(A, B)
,可以写成:
np.einsum('i,i->', A, B) # or just use 'i,i'
-
外层产品,np.outer(A, B)
,可以写成:
np.einsum('i,j->ij', A, B)
对于二维数组,C
and D
,前提是轴的长度兼容(两者长度相同或其中之一的长度为 1),以下是一些示例:
-
的踪迹C
(主对角线之和),np.trace(C)
,可以写成:
np.einsum('ii', C)
-
逐元素乘法C
和转置D
, C * D.T
,可以写成:
np.einsum('ij,ji->ij', C, D)
-
将每个元素相乘C
通过数组D
(制作 4D 数组),C[:, :, None, None] * D
,可以写成:
np.einsum('ij,kl->ijkl', C, D)