如何在大型阵列上进行内存高效的2D卷积

时间:2014-10-16 16:47:32

标签: scipy numerical-methods convolution

我有一个问题,我需要将一个非常大的2D阵列(磁盘上的文件)与一个适合内存的较小阵列进行卷积。当数组适合内存时,scipy.signal.fftconvolve很好,但当数组不适合时,则无效。除了循环遍历每个数组中的所有点以手动计算卷积之外,还有其他合理的方法吗?我对数学不满意,但我想知道fftconvolve是否可以分成几部分并重新组合一点点重叠?还有别的吗?

3 个答案:

答案 0 :(得分:1)

我可以建议你采用两种不同的方法(虽然我不会冒险提供一些示例代码,但希望你不会介意把它搞清楚):

1)使用numpy.memmap; " 内存映射文件用于访问磁盘上的大段文件,而无需将整个文件读入内存。 (...)memmap对象可以在接受ndarray的任何地方使用。"

2)将大数组拆分为切片,使用mode='full'执行卷积,并叠加结果。对于每个瓷砖,您将获得一个"边界"在瓷砖周围,与卷积内核的宽度相同。

可以组合两种方法(例如,从memmapped文件中读取切片,并将结果叠加到另一个memmapped文件中,这是结果)。

答案 1 :(得分:1)

迫切需要heltonbiker的回应,快速完成它将非常重要。正如您的“瓷砖”的重新组装一样。如果您无法将大数组加载到内存中,则需要将其作为memmapped文件加载...但要将其作为memmapped文件加载,您需要先创建一个。

这是一个粗略的伪代码。

#you need to know how big your data matrix is

#todo: assign nrows and ncols based on your data.

fp = np.memmap('your_new_memmap_filename.dat', dtype='float32', mode='w+', shape=(nrows, ncols))#create file with appropriate dimensions

data = open('yourdatafile.txt', 'r')
i = 0
for line in data:
    arr = map(float, line.strip().split(','))   #comma delimited? header row?
    fp[i, :] = arr
    i += 1

del fp  #see documentation...del will flush the output and close the file.

现在要处理..可以继续或新脚本

convolve_matrix = somenumpyarray
fp_result = np.memmap('your_results_memmap_filename.dat', dtype='float32', mode='w+', shape=(nrows, ncols))

#open big file read only
fp = np.memmap('your_new_memmap_filename.dat', dtype='float32', mode='r', shape=(nrows, ncols))

chunksize = 10000 #?
for i in range(int(nrows/chunksize) - 1):  #don't forget the remainder at the end
    chunk = fp[i * chunksize: (i + 1) * chunksize, :]
    res = fftconvolve(chunk, convolve_matrix)
    fp_result[i * chunksize: (i + 1) * chunksize, :] = res

#deal with remainder

del fp_result
del fp

请注意,此伪代码不会重叠,您需要填补一些空白。此外,一旦您完成拼贴工作,请确保使用Joblib并并行处理拼贴。 https://pythonhosted.org/joblib/parallel.html 对不起,我不能提供更多代码,我有一个2-d tiler / reassembler我为gis制作但它不在这台电脑上。它可能甚至没有多大帮助,因为你的砖瓦不会返回实际的瓷砖,而是返回切片列表,可能是几个列表,它们在哪里抓取切片(在大数组上),在结果中放置它的位置(大结果数组)以及切片的位置切片的结果(从大数组中抓取的切片的结果)迭代切片列表并且处理将很容易。但是制作切片功能会很棘手。

for source_slice, result_slice, mini_slice in zip(source_slice, result_slice, mini_slice):
    matrix2convolve = big_fp[source_slice[0]:source_slice[1], :]
    convolve_result = fftconvolve(matrix2convolve, convolve_matrix)
    big_result_fp[result_slice[0]:result_slice[1], :] = convolve_result[mini_slice[0]:mini_slice[1], :]

答案 2 :(得分:-1)

这听起来很幼稚,但如果你移动结果像素,它就是100%真实。

通常做的卷积是错误的。实际操作根本不需要内存。您只需读取文件,执行卷积,然后将其写回到同一位置。

执行算法时的缺陷是它要求我们有一个中心像素。如果您宁愿将结果像素放在左上角,请执行卷积。您可以简单地将卷积作为扫描线操作进行读取。由于此操作没有更改任何向下或向右的像素,因此结果是正确的。

一旦对先前像素的完全任意和毫无意义的依赖性被打破,你就可以做一些很酷的事情,比如组合卷积内核,并在当前正在读取的相同内存占用(或文件)内执行操作。 http://godsnotwheregodsnot.blogspot.com/2015/02/combining-convolution-kernels.html

请在此处查看来源http://pastebin.com/bk0A2Z5D

将像素置于中心的原因是它们不会移动。但是,它不值得。如果您在结果周围有相同的垃圾线,那么你真的可以将它们移回到同一个内存中的正确位置。实际上,如果你在右下角执行下一个卷积并在数组上向后迭代,那么你最终将结果返回到或多或少开始的地方,或者使用内核:

0,0
0,1

简而言之,问题的唯一原因是前段时间某人决定应该有一个中心像素。当您放弃这个愚蠢的想法时,您总是知道结果像素的位置,您可以进行有趣的操作,例如预编译卷积矩阵,从而使用组合内核立即执行所有卷积,并且,对您来说,纯粹通过从磁盘读取来执行操作。试想一下,你可能是第一个拥有美国宇航局仙女座星系图像模糊副本的人。

如果有人知道谁先决定要制作一个中心像素的历史,我有点想知道。因为没有它,卷积只是逐像素扫描线操作。