如何避免使用numpy for-loops?

时间:2013-05-17 11:22:52

标签: python numpy

我已经编写了以下代码,它完全符合我的要求,但速度太慢了。我确信有一种方法可以让它更快,但我似乎无法找到它应该如何完成。代码的第一部分只是为了显示哪种形状。

两张测量图像(VV1HH1
预先计算的值,VV模拟值和HH模拟值,均取决于3个参数(预计算为(101, 31, 11)值)
索引2只是将VVHH图像放在同一个ndarray中,而不是制作两个3darrays

VV1 = numpy.ndarray((54, 43)).flatten()
HH1 = numpy.ndarray((54, 43)).flatten()
precomp = numpy.ndarray((101, 31, 11, 2))

我们让三个参数中的两个变化

comp = numpy.zeros((len(parameter1), len(parameter2)))

for i,(vv,hh) in enumerate(zip(VV1,HH1)):
    comp0 = numpy.zeros((len(parameter1),len(parameter2)))
    for j in range(len(parameter1)):
        for jj in range(len(parameter2)):
            comp0[j,jj] = numpy.min((vv-precomp[j,jj,:,0])**2+(hh-precomp[j,jj,:,1])**2)
    comp+=comp0

我知道我应该做的显而易见的事情就是摆脱尽可能多的for循环,但我不知道如何在处理更多维度时使numpy.min行为正常。

第二件事(不太重要,如果它可以得到矢量化,但仍然很有趣)我注意到它需要大部分CPU时间,而不是RAM,但我已经搜索了很长时间,但我找不到写东西的方法像matlab中的“parfor”而不是“for”,(如果我只是将for循环放在一个单独的方法中,是否可以制作一个@parallel装饰器?)

编辑:回复Janne Karila:是的,这肯定会改善它,

for (vv,hh) in zip(VV1,HH1):
    comp+= numpy.min((vv-precomp[...,0])**2+(hh-precomp[...,1])**2, axis=2)

肯定要快得多,但有没有可能删除外部for循环呢?有没有办法使用@parallel或其他东西进行for循环并行?

3 个答案:

答案 0 :(得分:5)

这可以替换内部循环,jjj

comp0 = numpy.min((vv-precomp[...,0])**2+(hh-precomp[...,1])**2, axis=2)

这可能是整个循环的替代品,尽管所有这些索引都让我有点兴奋。 (这会创建一个大的中间数组)

comp = numpy.sum(
        numpy.min((VV1.reshape(-1,1,1,1) - precomp[numpy.newaxis,...,0])**2
                 +(HH1.reshape(-1,1,1,1) - precomp[numpy.newaxis,...,1])**2, 
                 axis=2),
        axis=0)

答案 1 :(得分:0)

在计算机科学中,有Big O notation的概念,用于获得做某事需要做多少工作的近似值。为了使程序快速,尽量少做。

这就是为什么Janne的答案要快得多,你的计算量也会减少。更进一步,我们可以应用memoization的概念,因为你是CPU绑定而不是RAM绑定。如果需要比以下示例更复杂,可以使用memory library

class AutoVivification(dict):
        """Implementation of perl's autovivification feature."""
        def __getitem__(self, item):
            try:
                return dict.__getitem__(self, item)
            except KeyError:
                value = self[item] = type(self)()
                return value

memo = AutoVivification()

def memoize(n, arr, end):
    if not memo[n][arr][end]:
        memo[n][arr][end] = (n-arr[...,end])**2        
    return memo[n][arr][end]


for (vv,hh) in zip(VV1,HH1):
    first = memoize(vv, precomp, 0)
    second = memoize(hh, precomp, 1)
    comp+= numpy.min(first+second, axis=2)

任何已经计算过的内容都会保存到字典中的内存中,我们可以稍后查找,而不是重新计算它。您甚至可以将完成的数学分解为更小的步骤,如果需要,每个步骤都会被记忆。

AutoVivification字典只是为了更容易将结果保存在memoize中,因为我很懒。同样,你可以记住你做的任何数学,所以如果numpy.min很慢,也要记住它。

答案 2 :(得分:0)

并行化循环的一种方法是以使用map的方式构造它。在这种情况下,您可以使用multiprocessing.Pool来使用并行映射。

我会改变这个:

for (vv,hh) in zip(VV1,HH1):
    comp+= numpy.min((vv-precomp[...,0])**2+(hh-precomp[...,1])**2, axis=2)

对于这样的事情:

def buildcomp(vvhh):
    vv, hh = vvhh
    return numpy.min((vv-precomp[...,0])**2+(hh-precomp[...,1])**2, axis=2)

if __name__=='__main__':
    from multiprocessing import Pool
    nthreads = 2
    p = Pool(nthreads)
    complist = p.map(buildcomp, np.column_stack((VV1,HH1)))
    comp = np.dstack(complist).sum(-1)

请注意,dstack假定每个comp.ndim都是2,因为它会添加第三个轴,并沿着它加总。这会减慢它的速度,因为你必须构建列表,堆叠它,然后求它,但这些都是并行操作或者是numpy操作。

我还将zip更改为numpy操作np.column_stack,因为对于长数组,zip 更慢,假设它们已经是1d数组(他们在你的例子中。)

我无法轻易测试,所以如果有问题,请随时告诉我。