【问题标题】:Numpy: Faster computation of triple nested loops involving sum reductionsNumpy:更快地计算涉及总和减少的三重嵌套循环
【发布时间】:2018-05-17 04:21:41
【问题描述】:

我的目标是有效地计算以下嵌套循环,

Ab = np.random.randn(1000, 100)    
Tb = np.zeros((100, 100, 100))

for i in range(d):
    for j in range(d):
        for k in range(d):
            Tb[i, j, k] = np.sum(Ab[:, i] * Ab[:, j] * Ab[:, k])

我发现了一种通过仅循环组合来更快地执行嵌套循环的方法:

for i,j,k in itertools.combinations_with_replacement(np.arange(100), 3):
    Abijk = np.sum(Ab[:, i] * Ab[:, j] * Ab[:, k])

    Tb[i, j, k] = Abijk
    Tb[i, k, j] = Abijk

    Tb[j, i, k] = Abijk
    Tb[j, k, i] = Abijk

    Tb[k, j, i] = Abijk
    Tb[k, i, j] = Abijk

有没有更有效的方法来做到这一点?

我希望有一种方法可以利用 Numpy 的 Blas、Numba 的 JIT 或 Pytorch GPU 实现。

【问题讨论】:

    标签: python numpy parallel-processing gpu jit


    【解决方案1】:

    方法#1

    我们可以直接将迭代器作为einsum string notation 使用NumPy 的内置np.einsum。因此,解决方案是使用单个 einsum 调用 -

    Tb = np.einsum('ai,aj,ak->ijk',Ab,Ab,Ab)
    

    方法#2

    我们可以使用broadcasted elementwise-multiplication 的组合,然后是np.tensordotnp.matmul 的所有sum-reductions

    因此,再次使用 einsum 或显式维度扩展和 broadcasting 获取广播的元素乘法 -

    parte1 = np.einsum('ai,aj->aij',Ab,Ab)
    parte1 = (Ab[:,None,:]*Ab[:,:,None]
    

    那么,tensordotnp.matmul -

    Tb = np.tensordot(parte1,Ab,axes=((0),(0)))
    Tb = np.matmul(parte1.T, Ab) # Or parte1.T @ Ab on Python 3.x
    

    因此,第二种方法总共有四种可能的变体。

    运行时测试

    In [140]: d = 100
         ...: m = 1000
         ...: Ab = np.random.randn(m,d)
    
    In [148]: %%timeit  # original faster method
         ...: d = 100
         ...: Tb = np.zeros((d,d,d))
         ...: for i,j,k in itertools.combinations_with_replacement(np.arange(100), 3):
         ...:     Abijk = np.sum(Ab[:, i] * Ab[:, j] * Ab[:, k])
         ...: 
         ...:     Tb[i, j, k] = Abijk
         ...:     Tb[i, k, j] = Abijk
         ...: 
         ...:     Tb[j, i, k] = Abijk
         ...:     Tb[j, k, i] = Abijk
         ...: 
         ...:     Tb[k, j, i] = Abijk
         ...:     Tb[k, i, j] = Abijk
    1 loop, best of 3: 2.08 s per loop
    
    In [141]: %timeit np.einsum('ai,aj,ak->ijk',Ab,Ab,Ab)
    1 loop, best of 3: 3.08 s per loop
    
    In [142]: %timeit np.tensordot(np.einsum('ai,aj->aij',Ab,Ab),Ab,axes=((0),(0)))
         ...: %timeit np.tensordot(Ab[:,None,:]*Ab[:,:,None],Ab,axes=((0),(0)))
         ...: %timeit np.matmul(np.einsum('ai,aj->ija',Ab,Ab), Ab)
         ...: %timeit np.matmul(Ab.T[None,:,:]*Ab.T[:,None,:], Ab)
    
    
    10 loops, best of 3:  56.8 ms per loop
    10 loops, best of 3:  59.2 ms per loop
     1 loop,  best of 3: 673   ms per loop
     1 loop,  best of 3: 670   ms per loop
    

    最快的似乎是基于tensordot 的。因此,通过更快的单循环 itertools 方法获得 35x+ 加速。

    【讨论】:

    • tensordoteinsum 有了惊人的改进,感谢这些出色的算法!
    • @Curious 我无法在最后运行您的基于 itertools 的工具。如果它对你来说不是太多的工作,你能告诉我它的时间数字以及它与基于 tensordot 的比较吗?我总是对性能数字很好奇 :)
    • @Divaker 当您使用itertools 运行该方法时,您可能收到错误TypeError: 'int' object is not iterable 我修复了上面的代码,它应该是itertools.combinations_with_replacement(np.arange(100), 3) 而不是itertools.combinations_with_replacement(100, 3) :)
    • @Curious 是的,就是这样!也为所有的一个地方比较添加了时间。
    • @Curious Well einsum 使用纯 C 代码,而 tensordot 利用更快的 BLAS。 einsum 在需要处理超过 2-dim 的数据时显然很慢(根据我使用它的经验),在这种情况下它会变成 4-dim(如果您在求和之前考虑整个扩展),我我猜只是所涉及的内存拥塞。
    猜你喜欢
    • 2015-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多