Python 提高移动窗口计算的内存消耗和速度

Python 提高移动窗口计算的内存消耗和速度,python,performance,numpy,Python,Performance,Numpy,在这个移动窗口计算中是否有可能获得更好的性能(内存消耗和速度)?我有一个1000x1000numpy数组,在整个数组中使用16x16窗口,最后对每个窗口应用一些函数(在本例中,是离散余弦变换) 此代码占用了太多内存(在上面的示例中,内存消耗不是很糟糕-windows使用1Gb,而dcts也需要1Gb),需要25秒才能完成。我有点不确定我做错了什么,因为这应该是一个简单的计算(例如过滤图像)。有没有更好的方法来实现这一点 更新: 我最初担心Kington的解决方案和我最初的方法产生的阵列非常不同,

在这个移动窗口计算中是否有可能获得更好的性能(内存消耗和速度)?我有一个
1000x1000
numpy数组,在整个数组中使用
16x16
窗口,最后对每个窗口应用一些函数(在本例中,是离散余弦变换)

此代码占用了太多内存(在上面的示例中,内存消耗不是很糟糕-
windows
使用1Gb,而
dcts
也需要1Gb),需要25秒才能完成。我有点不确定我做错了什么,因为这应该是一个简单的计算(例如过滤图像)。有没有更好的方法来实现这一点

更新:

我最初担心Kington的解决方案和我最初的方法产生的阵列非常不同,但差异仅限于边界,因此不太可能对大多数应用程序造成严重问题。剩下的唯一问题是,这两种解决方案都非常缓慢。目前,第一种溶液需要1分钟10秒,第二种溶液需要59秒

更新2:

我注意到最大的罪魁祸首是
dct
np.mean
。即使是
generic_filter
也可以使用带有瓶颈的“cythonized”版本的
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
。如果有人知道怎么做,我将非常感谢您的帮助。

skimage.util.view\u因为windows正在使用跨步技巧来创建一个重叠的“窗口”数组,而不使用任何额外的内存

但是,当您为形状创建一个新数组时,它将需要约32倍(16 x 16)的内存,即原始
x
数组或
windows
数组使用的内存

根据您的评论,您的最终结果是执行
dct.重塑(windows.shape).平均值(axis=2).平均值(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\u过滤器
。它不会大大提高性能(瓶颈是内部循环中的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],))

由于
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

您真的需要存储完整的、重叠的中间结果吗?(
dct
)通常,在过滤图像(例如
scipy.ndimage.generic_filter
)时,您只对每个“窗口”的单个输出值感兴趣。这是一个很好的观点。现在我用的是DCT的平均值。类似这样:
dcts.reforme(windows.shape).mean(axis=2).mean(axis=2)
我将看一看
generic\u filter
如果您只存储平均值,它应该会加快速度并显著减少内存使用
windows
实际上只使用原始
X
数组的内存。但是,您正在使用
dct
创建一个巨大的数组。正确。这就是问题所在。我想看看泛型过滤器是如何工作的,以便尝试一下。结果实际上是一样的。但是,原始代码只是忽略边界并丢弃边界附近的数据。
generic_filter
的第一个结果位于输入数组的[0,0],而原始解决方案的第一个结果位于[8,8]。谢谢。我正在努力弄清楚为什么我用这个方法不能得到同样的结果。也许,mode参数与此有关?@RobertSmith-是的,边界条件(
mode
kwarg)不同,返回数组(1000x1000)的形状也不同。您使用的方法不会对整个图像进行计算,因此没有边界
generic_filter
对整个图像进行计算,因此它必须以某种方式处理边界条件。默认设置是用零填充<代码>模式class='reflect'np.allclose(result1, result2, atol=1e-6) # False np.allclose(result1, result2, atol=1e-5) # True