Python 在np.einsum上循环多次。。。有没有更快的办法?

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")

我有一个似然函数,我正试图用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")

输出
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循环。