我可以通过numpy避免动态编程的Python循环开销吗?

时间:2011-11-22 07:32:01

标签: numpy dynamic-programming memoization

我需要帮助解决以下问题的Pythonic循环开销:我正在编写一个计算像素流算法的函数,这是2D Numpy数组上的经典动态编程算法。它要求:

1)至少访问一次数组的所有元素:

for x in range(xsize):
    for y in range(ysize):
         updateDistance(x,y)

2)潜在地遵循基于元素的邻居的值的元素路径,该元素看起来像这样

while len(workingList) > 0:
   x,y = workingList.pop()
   #if any neighbors of x,y need calculation, push x,y and neighbors on workingList 
   #else, calculate flow on pixels as a sum of flow on neighboring pixels

不幸的是,即使对updateDistance的调用是pass,我似乎也会在#1上获得大量的Pythonic循环开销。我认为这是一个经典的算法,在Python中必须有一个很好的方法,可以避免一些循环开销。我也担心,如果我能解决#1,我将会在#2的循环中被淘汰。

有关快速循环2D numpy数组中的元素并可能更新该数组中元素链的任何建议吗?

编辑:清除#2

的更多细节

似乎我可以对第一个循环进行矢量化,也许可以通过矢量化np.meshgrid调用。

部分循环有点复杂,但这是一个简化版本。我关心循环和索引到相邻元素:

#A is a 2d cost matrix
workingList = [(x,y)]
while len(workingList) > 0:
   x,y = workingList.pop()
   neighborsToCalculate = []
   for n in neighborsThatNeedCalculation(x,y): #indexes A to check neighbors of (x,y)
      neighborsToCalculate.append(n)
   if len(neighborstToCalculate) != 0:
       workingList.append((x,y))
       workingList.extend(neighborsToCalculate)
   else:
       for xn,yn in neighbors(x,y):
          A[x,y] += 1+A[xn,yn]

这是一个经典的广度优先搜索问题。如果可以并行化将会很棒。它可能不能以当前形式出现,因为它遵循一条路径,但我很乐意接受建议。

3 个答案:

答案 0 :(得分:3)

对于第一部分,您可以使用numpy.vectorize,但是如果无法使用数组操作来实现updateDistance的功能,则应该这样做。这是一个例子:

import numpy as np    
updateDistance = np.vectorize(lambda x: x + 1) # my updateDistance increments

实际上,如果这是您要执行的操作,请执行a + 1因此,如果我们采用一组数组并应用updateDistance

>>> a = np.ones((3,3))
>>> updateDistance(a)
array([[ 2.,  2.,  2.],
       [ 2.,  2.,  2.],
       [ 2.,  2.,  2.]])

至于第二部分,我认为我不能很好地理解细节,以提出更好的选择。听起来你需要反复查看最近的邻居,所以我怀疑你可以改善if-else中的事情,至少。


更新:第一部分的计时。

注意:这些时间是在我的机器上完成的,没有尝试规范化环境。

循环时间由:

生成
python -mtimeit 'import numpy as np' 'n = 100' 'a = np.ones((n, n))' 'b = np.zeros((n, n))' 'for x in range(n): ' '    for y in range(n):' '        b[x,y] = a[x,y] + 1'

生成np.vectorize次时使用:

python -mtimeit 'import numpy as np' 'n = 100' 'a = np.ones((n, n))' 'updateDistance = np.vectorize(lambda x: x + 1)' 'b = updateDistance(a)'

在这两种情况下,n = 100都会导致100 x 100阵列。根据需要替换100

Array size    Loop version    np.vectorize version    np.vectorize speed up
100 x 100     20.2 msec        2.6 msec               7.77x
200 x 200     81.8 msec       10.4 msec               7.87x
400 x 400     325 msec        42.6 msec               7.63x

最后,要将np.vectorize示例与简单地使用数组操作进行比较,您可以执行以下操作:

python -mtimeit 'import numpy as np' 'n = 100' 'a = np.ones((n, n))' 'a += 1'

在我的机器上,这产生了以下结果。

Array size    Array operation version    Speed up over np.vectorize version
100 x 100     23.6 usec                  110.2x
200 x 200     79.7 usec                  130.5x
400 x 400     286 usec                   149.0x

总之,使用np.vectorize而不是循环有一个优势,但如果可能的话,使用数组操作实现updateDistance的功能有更大的动力。

答案 1 :(得分:3)

如果你在算法中使用python循环,你将不会从numpy获得任何速度提升。您需要并行化您的问题。

在图像处理中,并行化意味着对所有像素使用相同的功能,即使用内核。在numpy,而不是做:

for x in range(xsize):
    for y in range(ysize):
         img1[y, x] = img2[y, x] + img3[y, x]
你这样做:

img1 = img2 + img3 # add 2 images pixelwise

以便循环发生在c中。事实上,如果每个像素都有一个未知长度的邻居列表,那么就很难以这种方式并行化问题。你应该重新修改你的问题(你可以对你的算法更具体一点吗?),或者使用其他语言,比如cython。

修改

如果不更改算法,您将无法从Numpy中获益。 Numpy允许您执行线性代数运算。执行任意操作时,无法避免使用此库循环开销。

要优化此功能,您可以考虑:

  • 切换到另一种语言,如cython(专门用于python扩展),以摆脱循环成本

  • 优化算法:如果只使用线性代数运算得到相同的结果(这取决于neighborsThatNeedCalculation函数),你可以使用numpy,但是你需要设计一个新的架构

  • 使用MapReduce等并行化技术。使用python你可以使用一个工作池(在多处理模块中可用),如果切换到另一种语言,你将获得更多的速度提升,因为python将有其他瓶颈。

如果你想要一些易于设置和集成的东西,并且你只需要有类似c的表演,我强烈建议如果你不能重做你的算法的cython。

答案 2 :(得分:1)

你应该考虑使用C-extension / Cython。 如果你继续使用Python,可以通过替换:

来实现一项重大改进
for xn,yn in neighbors(x,y):
      A[x,y] += 1+A[xn,yn]

使用:

n = neighbors(x,y)
A[x,y] += len(n)+sum(A[n])

neighbors应该返回索引,而不是下标。