其中一个轴在A(第一个)和B(最后一个)中保持对齐,并且也保持在输出中(最后一个),并且是4 的一个非常小的循环数。所以,我们可以简单地用 np.tensordot 循环那个,以减少张量。在处理如此大的数据集时,4x 较少的内存拥塞的好处可能会克服 4x 循环,因为每次迭代的计算量也较少 4x。
因此,tensordot 的解决方案将是 -
def func1(A, B):
out = np.empty(A.shape[1:3] + B.shape[1:])
for i in range(len(A)):
out[...,i] = np.tensordot(A[i], B[...,i],axes=(-1,0))
return out
时间安排 -
In [70]: A = np.random.rand(4,50,60,200) # Random NDarray
...: B = np.random.rand(200,1000,4) # Random NDarray
...: out = np.einsum('ijkl,lui->jkui', A, B, optimize="optimal")
# Einsum solution without optimize
In [71]: %timeit np.einsum('ijkl,lui->jkui', A, B)
2.89 s ± 109 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# Einsum solution with optimize
In [72]: %timeit np.einsum('ijkl,lui->jkui', A, B, optimize="optimal")
2.79 s ± 9.31 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# @Paul Panzer's soln
In [74]: %timeit np.stack([np.tensordot(a,b,1) for a,b in zip(A,B.transpose(2,0,1))],-1)
183 ms ± 6.08 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [73]: %timeit func1(A,B)
158 ms ± 3.35 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
只是为了重申内存拥塞和计算需求的重要性,假设我们也想对长度的最后一个轴进行求和减少4,那么我们将看到optimal 的时序有明显差异版本-
In [78]: %timeit np.einsum('ijkl,lui->jkui', A, B, optimize="optimal")
2.76 s ± 9.36 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [79]: %timeit np.einsum('ijkl,lui->jku', A, B, optimize="optimal")
93.8 ms ± 3.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
所以,在这种情况下,最好选择einsum。
特定于给定问题
鉴于A 和B 的维度保持不变,使用out = np.empty(A.shape[1:3] + B.shape[1:]) 的数组初始化可以一次性完成,并使用建议的循环遍历对数似然函数的每次调用过度使用tensordot 并更新输出out。