如何使用张量或 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 必须按以下方式计算:

对于每个 tV[:, :, 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.tensordotnp.einsum 可以帮助解决这种情况,使事情变得更快、更优雅。但是,我是张量的新手,不习惯爱因斯坦符号。有人可以提供一些关于如何执行此计算的信息,也许 link 可以作为指导初学者的参考吗?谢谢!

正如评论中的解决方案所说,解决方案的 einsum 等价物是,

np.einsum("ijk,njk->ink", F, F)

按照einsum的规则,两个数组的axis=1axis=0(标签jk对应的坐标轴)都将得到元素-明智地成倍增加。其中,最终输出中缺少 jk 存在。这意味着对应于 j 的轴将在最终解决方案中相加,而 k 将停止在逐元素乘法处。

一般来说,如果标签在符号中重复,它们将逐元素相乘,如果最终输出中缺少标签,则在逐元素加法之上进行加法。

这正是这里发生的事情。 F 的形状为 (2, 100, 65)"ijk,njk->ink" 将在这种情况下执行以下操作 -

  • 第一个数组 axis=0 的元素将与第二个数组 axis=0 的每个元素一起操作。这就是字符串中的 i,n->in 所代表的内容。这里,i=2n=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,具体取决于数组的相对维度。