用numpy加速循环

时间:2014-10-16 12:01:52

标签: python arrays performance optimization numpy

下一个for-loop如何通过numpy获得加速?我想这里可以使用一些花哨的索引技巧,但我不知道哪一个(可以在这里使用einsum?)。

a=0
for i in range(len(b)):
    a+=numpy.mean(C[d,e,f+b[i]])*g[i]

编辑: C是一个形状与(20, 1600, 500)相当的numpy 3D数组。 d,e,f是“有趣”的点数的索引(d,e,f的长度相同且大约为900) b和g具有相同的长度(大约50)。平均值取自C中具有索引d,e,f+b[i]

的所有点

4 个答案:

答案 0 :(得分:3)

您可以执行以下操作:

C[d, e][:, np.add.outer(f, b)].dot(g).diagonal().mean()
通过过早地采用将形成对角线的元素来提高甚至更多:

C[d, e][np.arange(len(d))[:, None], np.add.outer(f, b)].dot(g).mean()

答案 1 :(得分:1)

它非常类似于loopy版本:

np.sum(np.mean(C[d,e,f+b[:,None]], axis=1) * g)

您可以将求和乘法组合成点积:

C[d,e,f+b[:,None]].mean(1).dot(g)

但是对于似乎并不重要的时间安排;索引操作是迄今为止最耗时的操作(至少在Numpy 1.8.0上)。与此相比,原始代码中的循环开销无关紧要。

答案 2 :(得分:1)

计时

两个会话都是用

初始化的
In [1]: C = np.random.rand(20,1600,500)

In [2]: d = np.random.randint(0, 20, size=900)

In [3]: e = np.random.randint(1600, size=900)

In [4]: f = np.random.randint(400, size=900)

In [5]: b = np.random.randint(100, size=50)

In [6]: g = np.random.rand(50)

Numpy 1.9.0

In [7]: %timeit C[d,e,f + b[:,np.newaxis]].mean(axis=1).dot(g)
1000 loops, best of 3: 942 µs per loop

In [8]: %timeit C[d[:,np.newaxis],e[:, np.newaxis],f[:, np.newaxis] + b].mean(axis=0).dot(g)
1000 loops, best of 3: 762 µs per loop

In [9]: %%timeit                                               
   ...: a = 0
   ...: for i in range(len(b)):                                     
   ...:     a += np.mean(C[d, e, f + b[i]]) * g[i]
   ...: 
100 loops, best of 3: 2.25 ms per loop

In [10]: np.__version__
Out[10]: '1.9.0'

In [11]: %%timeit
(C.ravel()[np.ravel_multi_index((d[:,np.newaxis],
                                 e[:,np.newaxis],
                                 f[:,np.newaxis] + b), dims=C.shape)]
 .mean(axis=0).dot(g))
   ....: 
1000 loops, best of 3: 940 µs per loop

Numpy 1.8.2

In [7]: %timeit C[d,e,f + b[:,np.newaxis]].mean(axis=1).dot(g)
100 loops, best of 3: 2.81 ms per loop

In [8]: %timeit C[d[:,np.newaxis],e[:, np.newaxis],f[:, np.newaxis] + b].mean(axis=0).dot(g)
100 loops, best of 3: 2.7 ms per loop

In [9]: %%timeit                                               
   ...: a = 0
   ...: for i in range(len(b)):                                     
   ...:     a += np.mean(C[d, e, f + b[i]]) * g[i]
   ...: 
100 loops, best of 3: 4.12 ms per loop

In [10]: np.__version__
Out[10]: '1.8.2'

In [51]: %%timeit
(C.ravel()[np.ravel_multi_index((d[:,np.newaxis],
                                 e[:,np.newaxis],
                                 f[:,np.newaxis] + b), dims=C.shape)]
 .mean(axis=0).dot(g))
   ....: 
1000 loops, best of 3: 1.4 ms per loop

描述

您可以使用坐标广播技巧从一开始就充实您的50x900阵列:

In [158]: C[d,e,f + b[:, np.newaxis]].shape
Out[158]: (50, 900)

从那时起,meandot会将您带到目的地:

In [159]: C[d,e,f + b[:, np.newaxis]].mean(axis=1).dot(g)
Out[159]: 13.582349962518611

In [160]: 
a = 0
for i in range(len(b)):       
    a += np.mean(C[d, e, f + b[i]]) * g[i]
print(a)
   .....: 
13.5823499625

它比循环版本快3.3倍:

In [161]: %timeit C[d,e,f + b[:, np.newaxis]].mean(axis=1).dot(g)
1000 loops, best of 3: 585 µs per loop

In [162]: %%timeit                                               
a = 0
for i in range(len(b)):                                     
    a += np.mean(C[d, e, f + b[i]]) * g[i]
   .....: 
1000 loops, best of 3: 1.95 ms per loop

数组的大小很大,因此您必须考虑CPU缓存。我不能说我知道np.sum如何遍历数组,但在2d数组中总有一种更好的方法(当你选择的下一个元素是在内存方面相邻时)和稍微差一些方式(当下一个元素是跨越大步发现)。让我们看看我们是否可以通过在索引编制期间转置数组来赢得更多东西:

In [196]: C[d[:,np.newaxis], e[:,np.newaxis], f[:,np.newaxis] + b].mean(axis=0).dot(g)
Out[196]: 13.582349962518608

In [197]: %timeit C[d[:,np.newaxis], e[:,np.newaxis], f[:,np.newaxis] + b].mean(axis=0).dot(g)
1000 loops, best of 3: 461 µs per loop

这比循环快4.2倍。

答案 3 :(得分:0)

您在结构方面可能希望的唯一速度是使用以下代码:

#Initialize a 4-D array
aggregated = numpy.zeros((len(d), len(e), len(f), len(b)))
#Populate it by the shifted copies of C
for i in range(len(b)):
    aggregated[:, :, :, i] = C[d, e, f + b[i]]

#Compute the mean on the first three axes
means = numpy.mean(aggregated, axis=(0, 1, 2))
#Multiply term-by-term by g (be careful that means and g have the same size!) and sum
a = numpy.sum(means * g)

然而这并不能保证计算速度更快,甚至可能因为以下原因而变慢:

  • 填充4-D阵列的成本不可忽略,因为它复制了内存
  • b非常小,所以无论如何你都不会赢得太多。如果b更大,这可能会变得有趣,只要d,e,f也变得更小

无论如何,您应该对两种解决方案进行基准测试您也可以尝试使用像Cython这样的东西来执行for循环,但这似乎有点过头了。