在此移动窗口计算中是否有可能获得更好的性能(内存消耗和速度)?我有一个1000x1000
numpy数组,我在整个数组中取16x16
个窗口,最后将一些函数应用到每个窗口(在本例中为离散余弦变换。)
import numpy as np
from scipy.fftpack import dct
from skimage.util import view_as_windows
X = np.arange(1000*1000, dtype=np.float32).reshape(1000,1000)
window_size = 16
windows = view_as_windows(X, (window_size,window_size))
dcts = np.zeros(windows.reshape(-1,window_size, window_size).shape, dtype=np.float32)
for idx, window in enumerate(windows.reshape(-1,window_size, window_size)):
dcts[idx, :, :] = dct(window)
dcts = dcts.reshape(windows.shape)
此代码占用太多内存(在上面的示例中,内存消耗不是很严重 - windows
使用1Gb而dcts
也需要1Gb)并且需要25秒才能完成。我有点不确定我做错了什么,因为这应该是一个简单的计算(例如过滤图像。)有没有更好的方法来实现这个目标?
更新
我最初担心Kington的解决方案和我的初始方法生成的数组非常不同,但差异仅限于边界,因此不太可能对大多数应用程序造成严重问题。唯一剩下的问题是两种解决方案都很慢。目前,第一种解决方案需要1分10秒,第二种解决方案需要59秒。
更新2:
我注意到目前为止最大的罪魁祸首是dct
和np.mean
。即使generic_filter
使用“{cymonized”版本的mean
并且瓶颈也能正常执行(8.6秒):
import bottleneck as bp
def func(window, shape):
window = window.reshape(shape)
#return np.abs(dct(dct(window, axis=1), axis=0)).mean()
return bp.nanmean(dct(window))
result = scipy.ndimage.generic_filter(X, func, (16, 16),
extra_arguments=([16, 16],))
我目前正在阅读如何使用numpy包装C代码以替换scipy.fftpack.dct
。如果有人知道该怎么做,我将不胜感激。
答案 0 :(得分:3)
skimage.util.view_as_windows
使用跨步技巧制作一系列重叠的“窗口”,不使用任何额外的内存。
但是,当您创建一个形状形状的新数组时,它将需要原始X
数组或windows
数组使用的内存大约32倍(16 x 16)。
根据您的评论,您的最终结果是dcts.reshape(windows.shape).mean(axis=2).mean(axis=2)
- 取每个窗口dct
的平均值。
因此,在循环中取平均值并且不存储巨大的中间窗口数组会更有内存效率(尽管性能相似):
import numpy as np
from scipy.fftpack import dct
from skimage.util import view_as_windows
X = np.arange(1000*1000, dtype=np.float32).reshape(1000,1000)
window_size = 16
windows = view_as_windows(X, (window_size, window_size))
dcts = np.zeros(windows.shape[:2], dtype=np.float32).ravel()
for idx, window in enumerate(windows.reshape(-1, window_size, window_size)):
dcts[idx] = dct(window).mean()
dcts = dcts.reshape(windows.shape[:2])
另一个选项是scipy.ndimage.generic_filter
。它不会提高性能(瓶颈是内循环中的python函数调用),但是你将拥有更多的边界条件选项,并且它将具有相当的内存效率:
import numpy as np
from scipy.fftpack import dct
import scipy.ndimage
X = np.arange(1000*1000, dtype=np.float32).reshape(1000,1000)
def func(window, shape):
window = window.reshape(shape)
return dct(window).mean()
result = scipy.ndimage.generic_filter(X, func, (16, 16),
extra_arguments=([16, 16],))
答案 1 :(得分:3)
由于scipy.fftpack.dct
计算沿输入数组最后一个轴的单独变换,您可以用以下代码替换循环:
windows = view_as_windows(X, (window_size,window_size))
dcts = dct(windows)
result1 = dcts.mean(axis=(2,3))
现在只有dcts
数组需要大量内存,windows
仅仅是X
的视图。而且因为DCT是用单个函数调用计算的,所以它也快得多。但是,由于窗口重叠,因此需要进行大量重复计算。这可以通过仅计算每个子行的DCT一次,然后是窗口均值来克服:
ws = window_size
row_dcts = dct(view_as_windows(X, (1, ws)))
cs = row_dcts.squeeze().sum(axis=-1).cumsum(axis=0)
result2 = np.vstack((cs[ws-1], cs[ws:]-cs[:-ws])) / ws**2
虽然在代码清晰度方面似乎失去了效率所获得的......但基本上这里的方法是首先计算DCT,然后通过在2D窗口上求和然后除以得到窗口平均值窗口中的元素数量。 DCT已经在行方向移动窗口上计算,因此我们对这些窗口进行定期求和。但是,我们需要对列进行移动窗口总和,以得到正确的2D窗口总和。为了有效地做到这一点,我们使用cumsum
技巧,其中:
sum(A[p:q]) # q-p == window_size
相当于:
cs = cumsum(A)
cs[q-1] - cs[p-1]
这避免了必须一遍又一遍地对完全相同的数字求和。不幸的是,它不适用于第一个窗口(当p == 0
时),因此我们只需要cs[q-1]
并将其与其他窗口总和一起堆叠。最后,我们除以元素的数量,得到2D平均窗口。
如果你喜欢2D DCT,那么第二种方法就不那么有趣了,因为你最终需要完整的985 x 985 x 16 x 16
数组才能获得平均值。
上述两种方法都应该是等效的,但使用64位浮点数执行算法可能是个好主意:
np.allclose(result1, result2, atol=1e-6)
# False
np.allclose(result1, result2, atol=1e-5)
# True