我需要帮助解决以下问题的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]
这是一个经典的广度优先搜索问题。如果可以并行化将会很棒。它可能不能以当前形式出现,因为它遵循一条路径,但我很乐意接受建议。
答案 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)
在图像处理中,并行化意味着对所有像素使用相同的功能,即使用内核。在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
应该返回索引,而不是下标。