Python大型二维数组中的高效求和

时间:2018-03-27 12:57:32

标签: python numpy convolution

我的任务很简单:我有一个大的二维矩阵,只包含零和一个。对于此矩阵中的每个位置,我想要围绕此位置的窗口中的所有像素求和。问题是矩阵的形状(166667,17668)和窗口大小范围从(333,333)到(5333,5333)。到目前为止,我只尝试了一部分数据。我到达的代码:

out_arr = np.array( in_arr.shape )
in_arr = np.pad(in_arr, windowsize//2, mode='reflect')
for y in range(out_arr.shape[0]):
    for x in range(out_arr.shape[1]):
        out_arr[y, x] = np.sum(in_arr[y:y+windowsize, x:x+windowsize])

显然,这需要很长时间。但就我而言,它比使用numpy.stride_tricks.as_strided的滚动窗口方法更快,如here所述。我尝试使用cython编译它,没有效果。

  1. 除了并行化之外,你有什么建议来加快速度?
  2. 我手头有一个Nvidia Titan X.有没有办法从中受益? (例如使用cupy)

2 个答案:

答案 0 :(得分:2)

对于窗口求和,卷积实际上是过度的,因为存在一个简单的O(n)解:

import numpy as np
from scipy.signal import convolve

def winsum(in_arr, windowsize):
    in_arr = np.pad(in_arr, windowsize//2+1, mode='reflect')[:-1, :-1]
    in_arr[0] = 0
    in_arr[:, 0] = 0
    ps = in_arr.cumsum(0).cumsum(1)
    return ps[windowsize:, windowsize:] + ps[:-windowsize, :-windowsize] \
           -  ps[windowsize:, :-windowsize] - ps[:-windowsize, windowsize:]

这已经很快了,但你可以节省更多,因为对于所有较小的窗口大小,可以为最大窗口大小计算一次ps

然而,存在一个潜在的缺点,即通过总结这样的一切可能产生的非常大的数字。一个数字更多的声音版本通过首先采取差异消除了这个问题。缺点:通过共享ps节省的额外费用已不再可用。

def winsum_safe(in_arr, windowsize):
    in_arr = np.pad(in_arr, windowsize//2, mode='reflect')
    in_arr[windowsize:] -= in_arr[:-windowsize]
    in_arr[:, windowsize:] -= in_arr[:, :-windowsize]
    return in_arr.cumsum(0)[windowsize-1:].cumsum(1)[:, windowsize-1:]

作为参考,这里是最接近的竞争者,它是基于fft的卷积。您需要一个最新版本的scipy才能有效地工作。在旧版本中,使用fftconvolve代替convolve

def winsumc(in_arr, windowsize):
    in_arr = np.pad(in_arr, windowsize//2, mode='reflect')
    kernel = np.ones((windowsize, windowsize), in_arr.dtype)
    return convolve(in_arr, kernel, 'valid')

下一个是模拟scipy的旧行为和极其缓慢的行为。

def winsum_nofft(in_arr, windowsize):
    in_arr = np.pad(in_arr, windowsize//2, mode='reflect')
    kernel = np.ones((windowsize, windowsize), in_arr.dtype)
    return convolve(in_arr, kernel, 'valid', method='direct')

测试和基准测试:

data = np.random.random((1000, 1000))

assert np.allclose(winsum(data, 333), winsumc(data, 333))
assert np.allclose(winsum(data, 333), winsum_safe(data, 333))

kwds = dict(globals=globals(), number=10)

from timeit import timeit
from time import perf_counter

print('data 100x1000, window 333x333')
print('cumsum:      ', timeit('winsum(data, 333)', **kwds)*100, 'ms')
print('cumsum safe: ', timeit('winsum_safe(data, 333)', **kwds)*100, 'ms')
print('fftconv:     ', timeit('winsumc(data, 333)', **kwds)*100, 'ms')


t = perf_counter()
res = winsum_nofft(data, 99) # 333 just takes too long
t = perf_counter() - t

assert np.allclose(winsum(data, 99), res)

print('data 100x1000, window 99x99')
print('conv:        ', t*1000, 'ms')

示例输出:

data 100x1000, window 333x333
cumsum:       70.33260859316215 ms
cumsum safe:  59.98647050000727 ms
fftconv:      298.60571819590405 ms
data 100x1000, window 99x99
conv:         135224.8261970235 ms

答案 1 :(得分:1)

@Divakar在评论中指出你可以使用conv2d而且他是对的。这是一个例子:

import numpy as np
from scipy import signal

data = np.random.rand(5,5) # you original data that you want to sum
kernel = np.ones((2,2)) # square matrix of your dimensions, filled with ones
output = signal.convolve2d(data,kernel,mode='same') # the convolution