在numpy数组上快速迭代以求平方余数

时间:2018-08-24 09:57:48

标签: python arrays performance numpy

我喜欢最小二乘匹配具有许多已知信号形状的数据(浮点数的数组)。我的代码可以运行,但是对于我计划执行的许多运行来说太慢了:

import numpy
import time

samples = 50000
width_signal = 100
data = numpy.random.normal(0, 1, samples)
signal = numpy.random.normal(0, 1, width_signal)  # Placeholder

t0 = time.clock()
for i in range(samples - width_signal):
    data_chunk = data[i:i + width_signal]
    residuals = data_chunk - signal
    squared_residuals = residuals**2
    summed_residuals = numpy.sum(squared_residuals)
t1 = time.clock()
print('Time elapsed (sec)', t1-t0)

编辑:更正了一个错误:首先对残差求平方,然后求和。

这需要大约0.2秒才能在我的计算机上运行。因为我有许多数据集和信号形状,所以这太慢了。我的特定问题不允许使用典型的MCMC方法,因为信号形状差异太大。它必须是蛮力的。

典型的数据量为50,000浮点,信号量为100。这些差异可能只有几个。

我的测试表明:

  • 数据numpy.sum(residuals)的求和占90%的时间。我尝试使用Python的sum(residuals),对于较小的数组(〜<50个元素),速度更快,而对于较大的数组,速度则较慢。我应该插入一个if条件吗?
  • 我尝试了numpy.roll()而不是直接获取数据,而.roll()的速度较慢。

问题:

  • 加速是否合乎逻辑?
  • 是否有一种更快的求和数组的方法?我不知道C,但是如果速度更快,我可以尝试一下。
  • GPU可以帮助吗?我有很多事情要做。如果是这样,我在哪里可以找到一个代码片段来做到这一点?

2 个答案:

答案 0 :(得分:6)

基于Compute mean squared, absolute deviation and custom similarity measure - Python/NumPy中提出的各种方法,我们希望在这里解决问题。

方法1

我们可以利用基于np.lib.stride_tricks.as_stridedscikit-image's view_as_windows来获取滑动窗口,从而在这里有了我们的第一个解决方案-

from skimage.util import view_as_windows

d = view_as_windows(data,(width_signal))-signal # diffs
out = np.einsum('ij,ij->i',d,d)

More info on use of as_strided based view_as_windows.

方法2

再次基于该答案文章中的矩阵乘法技巧,我们可以提高性能,就像这样-

def MSD_strided(data, signal):
    w = view_as_windows(data,(width_signal))
    return (w**2).sum(1) + (signal**2).sum(0) - 2*w.dot(signal)

方法3

我们将通过统一过滤和卷积对方法2进行改进-

from scipy.ndimage.filters import uniform_filter 

def MSD_uniffilt_conv(data, signal):
    hW = width_signal//2
    l = len(data)-len(signal)+1
    parte1 = uniform_filter(data**2,width_signal)[hW:hW+l]*width_signal
    parte3 = np.convolve(data, signal[::-1],'valid')    
    return parte1 + (signal**2).sum(0) - 2*parte3

基准化

发布样本的时间-

In [117]: %%timeit
     ...: for i in range(samples - width_signal + 1):
     ...:     data_chunk = data[i:i + width_signal]
     ...:     residuals = data_chunk - signal
     ...:     squared_residuals = residuals**2
     ...:     summed_residuals = numpy.sum(squared_residuals)
1 loop, best of 3: 239 ms per loop

In [118]: %%timeit
     ...: d = view_as_windows(data,(width_signal))-signal
     ...: np.einsum('ij,ij->i',d,d)
100 loops, best of 3: 11.1 ms per loop

In [209]: %timeit MSD_strided(data, signal)
10 loops, best of 3: 18.4 ms per loop

In [210]: %timeit MSD_uniffilt_conv(data, signal)
1000 loops, best of 3: 1.71 ms per loop

~140x 在那里有了第三个加速!

答案 1 :(得分:3)

除了Divakar提供的版本外,您还可以简单地使用Numba或Cython之类的编译器。

Exmaple

import numba as nb
@nb.njit(fastmath=True,parallel=True)
def sq_residuals(data,signal):
  summed_residuals=np.empty(data.shape[0]+1-signal.shape[0],dtype=data.dtype)
  for i in nb.prange(data.shape[0] - signal.shape[0]+1):
      sum=0.
      for j in range(signal.shape[0]):
        sum+=(data[i+j]-signal[j])**2
      summed_residuals[i]=sum
  return summed_residuals

时间

Numba 0.4dev, Python 3.6, Quadcore i7
MSD_uniffilt_conv(Divakar): 2.4ms

after the first call which invokes some compilation overhead:
sq_residuals              : 1.7ms