Python 在np.einsum上循环多次。。。有没有更快的办法?
我有一个似然函数,我正试图用MCMC进行采样。我在日志似然性本身中使用了no for循环,但我确实调用了Python 在np.einsum上循环多次。。。有没有更快的办法?,python,performance,numpy,numpy-einsum,log-likelihood,Python,Performance,Numpy,Numpy Einsum,Log Likelihood,我有一个似然函数,我正试图用MCMC进行采样。我在日志似然性本身中使用了no for循环,但我确实调用了np.einsum()一次 下面是我当前代码的示例: 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")
np.einsum()
一次
下面是我当前代码的示例:
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")
输出
out
具有尺寸(50,601000,4)。这个计算有点太慢,无法实现高效的MCMC采样(在我的机器上大约4秒),有没有办法加快它一条有用的信息是,对于对数似然函数的每次调用,当数组A和B中的实际值发生变化时,每个数组的维数保持不变。我想这可能有助于加快速度,由于相同的元素总是相乘在一起。即使在小循环中使用tensordot
也要快10倍以上:
timeit(lambda:np.einsum('ijkl,lui->jkui', A, B, optimize="optimal"),number=5)/5
# 3.052245747600682
timeit(lambda:np.stack([np.tensordot(a,b,1) for a,b in zip(A,B.transpose(2,0,1))],-1),number=10)/10
# 0.23842503569903784
out_td = np.stack([np.tensordot(a,b,1) for a,b in zip(A,B.transpose(2,0,1))],-1)
out_es = np.einsum('ijkl,lui->jkui', A, B, optimize="optimal")
np.allclose(out_td,out_es)
# True
其中一个轴在
A
(第一个)和B
(最后一个)中保持对齐,并在输出(最后一个)中保持对齐,并且是非常小的循环数4
。所以,我们可以简单地用一个张量和约化来循环这个。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
,那么我们将看到最优
版本在计时方面的显著差异-
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:])进行数组初始化
可以一次性完成,并使用建议的循环遍历log likelion函数的每次调用,以使用tensordot
并更新输出out
非常棒,谢谢。我甚至不熟悉np.tensordot,这似乎是一个很好的函数@Jsn如果你是新来的tensordot
,这可能会很有帮助-对于一些例子来说。内存拥塞的洞察是很有启发性的。我没有意识到其影响如此之大。内存拥塞的例子更像是一个einsum vs BLAS的问题。78将重新组织张量并执行TDOT,79不可能调用TDOT,因此它使用einsum循环。