如何使用张量或 einsum 避免使用 for 循环?
How to avoid using a for loop using either tensors or einsum?
我手头有以下问题。 F
是维度 2 X 100 X 65
的 NumPy 数组。我想生成另一个数组 V
,其维度为 2 X 2 X 65
。此数组 V
必须按以下方式计算:
对于每个 t
,V[:, :, t] = F[:, :, t] @ F[:, :, t].T
其中 .T
表示矩阵转置,@
表示通常的矩阵乘法。现在,我正在使用以下方法:
aux_matrix = np.matmul(np.transpose(F, (2, 0 ,1)), np.transpose(F, (2, 1, 0)))
V = np.transpose(aux_matrix, (1, 2, 0))
我知道 np.tensordot
和 np.einsum
可以帮助解决这种情况,使事情变得更快、更优雅。但是,我是张量的新手,不习惯爱因斯坦符号。有人可以提供一些关于如何执行此计算的信息,也许 link 可以作为指导初学者的参考吗?谢谢!
正如评论中的解决方案所说,解决方案的 einsum
等价物是,
np.einsum("ijk,njk->ink", F, F)
按照einsum的规则,两个数组的axis=1
和axis=0
(标签j
和k
对应的坐标轴)都将得到元素-明智地成倍增加。其中,最终输出中缺少 j
而 k
存在。这意味着对应于 j
的轴将在最终解决方案中相加,而 k
将停止在逐元素乘法处。
一般来说,如果标签在符号中重复,它们将逐元素相乘,如果最终输出中缺少标签,则在逐元素加法之上进行加法。
这正是这里发生的事情。 F
的形状为 (2, 100, 65)
。 "ijk,njk->ink"
将在这种情况下执行以下操作 -
- 第一个数组
axis=0
的元素将与第二个数组 axis=0
的每个元素一起操作。这就是字符串中的 i,n->in
所代表的内容。这里,i=2
和 n=2
以及最终矩阵的前两个维度由 in
给出,因此最终数组的前两个维度的形状为 (2, 2)
。
- 对应于
j
的轴在两个数组中重复。因此,它们将按元素相乘。但是,最终输出中缺少这一点,因此该结果将沿该方向求和。即最终输出会少掉100对应的维度。
- 对应于
k
的标签也会发生类似的事情,但它仍然存在于最终输出中,因此不会发生减和。因此最终输出的轴对应于 65.
如果您检查最终输出的形状,它是 (2, 2, 65)
,正如预期的那样。
我希望这能消除 OP 在评论中表达的疑问。
但是,就性能而言,假设这会自动优于 matmul
编队是不正确的。就可读性而言,也许。但实际性能可能取决于数组的大小和相对维度,以及许多其他因素。
如果将 optimize=True
键添加到 einsum
中,则值得检查性能是否会发生变化,因为我已经看到这在某些情况下会产生巨大的性能差异。然而,对于这个数组的大小,这似乎让事情变得更糟(这可能是因为 einsum 需要花费时间来找出优化数组的好方法,考虑到相对较小的大小,这可能是不合理的数组)。
我的经验是,如果您能够在不使用任何额外的 for
循环的情况下使用 matmul
找到解决方案,那么如果您最关心的是性能,请坚持使用它。另一方面,如果您的程序有一堆 for
循环,请尝试 einsum
,有或没有 optimize=True
。然而,即使在那种情况下, 具有本机 for
循环的解决方案优于 einsum
,具体取决于数组的相对维度。
我手头有以下问题。 F
是维度 2 X 100 X 65
的 NumPy 数组。我想生成另一个数组 V
,其维度为 2 X 2 X 65
。此数组 V
必须按以下方式计算:
对于每个 t
,V[:, :, t] = F[:, :, t] @ F[:, :, t].T
其中 .T
表示矩阵转置,@
表示通常的矩阵乘法。现在,我正在使用以下方法:
aux_matrix = np.matmul(np.transpose(F, (2, 0 ,1)), np.transpose(F, (2, 1, 0)))
V = np.transpose(aux_matrix, (1, 2, 0))
我知道 np.tensordot
和 np.einsum
可以帮助解决这种情况,使事情变得更快、更优雅。但是,我是张量的新手,不习惯爱因斯坦符号。有人可以提供一些关于如何执行此计算的信息,也许 link 可以作为指导初学者的参考吗?谢谢!
正如评论中的解决方案所说,解决方案的 einsum
等价物是,
np.einsum("ijk,njk->ink", F, F)
按照einsum的规则,两个数组的axis=1
和axis=0
(标签j
和k
对应的坐标轴)都将得到元素-明智地成倍增加。其中,最终输出中缺少 j
而 k
存在。这意味着对应于 j
的轴将在最终解决方案中相加,而 k
将停止在逐元素乘法处。
一般来说,如果标签在符号中重复,它们将逐元素相乘,如果最终输出中缺少标签,则在逐元素加法之上进行加法。
这正是这里发生的事情。 F
的形状为 (2, 100, 65)
。 "ijk,njk->ink"
将在这种情况下执行以下操作 -
- 第一个数组
axis=0
的元素将与第二个数组axis=0
的每个元素一起操作。这就是字符串中的i,n->in
所代表的内容。这里,i=2
和n=2
以及最终矩阵的前两个维度由in
给出,因此最终数组的前两个维度的形状为(2, 2)
。 - 对应于
j
的轴在两个数组中重复。因此,它们将按元素相乘。但是,最终输出中缺少这一点,因此该结果将沿该方向求和。即最终输出会少掉100对应的维度。 - 对应于
k
的标签也会发生类似的事情,但它仍然存在于最终输出中,因此不会发生减和。因此最终输出的轴对应于 65.
如果您检查最终输出的形状,它是 (2, 2, 65)
,正如预期的那样。
我希望这能消除 OP 在评论中表达的疑问。
但是,就性能而言,假设这会自动优于 matmul
编队是不正确的。就可读性而言,也许。但实际性能可能取决于数组的大小和相对维度,以及许多其他因素。
如果将 optimize=True
键添加到 einsum
中,则值得检查性能是否会发生变化,因为我已经看到这在某些情况下会产生巨大的性能差异。然而,对于这个数组的大小,这似乎让事情变得更糟(这可能是因为 einsum 需要花费时间来找出优化数组的好方法,考虑到相对较小的大小,这可能是不合理的数组)。
我的经验是,如果您能够在不使用任何额外的 for
循环的情况下使用 matmul
找到解决方案,那么如果您最关心的是性能,请坚持使用它。另一方面,如果您的程序有一堆 for
循环,请尝试 einsum
,有或没有 optimize=True
。然而,即使在那种情况下,for
循环的解决方案优于 einsum
,具体取决于数组的相对维度。