使用numpy`as_strided`函数创建任意维度的补丁,平铺,滚动或滑动窗口

Dan*_*l F 7 arrays numpy convolution

今天早上花了一些时间寻找一个广义的问题来指出重复问题as_strided和/或如何制作通用窗口函数.关于如何(安全地)创建补丁,滑动窗口,滚动窗口,平铺或视图到阵列以进行机器学习,卷积,图像处理和/或数值积分,似乎存在很多问题.

我在寻找一个通用功能,可以接受window,step而且axis参数和返回一个as_strided超过任意维度视图.我将在下面给出我的答案,但我很感兴趣,如果有人能够制作更有效的方法,因为我不确定使用np.squeeze()是最好的方法,我不确定我的assert语句使函数足够安全以写入结果查看,我不知道如何处理axis不按升序排列的边缘情况.

尽职调查

我能找到的最通用的函数是sklearn.feature_extraction.image.extract_patches由@eickenberg编写的(以及显然是等价的skimage.util.view_as_windows),但是那些在网上没有很好地记录,并且不能在比原始数组中更少的轴上执行窗口(例如,这个问题要求在一个轴上有一定大小的窗口).通常,问题numpy只需要答案.

@Divakar在这里numpy为1-d输入创建了一个通用函数,但是更高维度的输入需要更多的关注.我在3d输入法上制作了一个裸骨2D窗口,但它不是很可扩展.

Dan*_*l F 9

这是我到目前为止的配方:

def window_nd(a, window, steps = None, axis = None, outlist = False):
    """
    Create a windowed view over `n`-dimensional input that uses an 
    `m`-dimensional window, with `m <= n`

    Parameters
    -------------
    a : Array-like
        The array to create the view on

    window : tuple or int
        If int, the size of the window in `axis`, or in all dimensions if 
        `axis == None`

        If tuple, the shape of the desired window.  `window.size` must be:
            equal to `len(axis)` if `axis != None`, else 
            equal to `len(a.shape)`, or 
            1

    steps : tuple, int or None
        The offset between consecutive windows in desired dimension
        If None, offset is one in all dimensions
        If int, the offset for all windows over `axis`
        If tuple, the steps along each `axis`.  
            `len(steps)` must me equal to `len(axis)`

    axis : tuple, int or None
        The axes over which to apply the window
        If None, apply over all dimensions
        if tuple or int, the dimensions over which to apply the window

    outlist : boolean
        If output should be as list of windows.  
        If False, it will be an array with 
            `a.nidim + 1 <= a_view.ndim <= a.ndim *2`.  
        If True, output is a list of arrays with `a_view[0].ndim = a.ndim`
            Warning: this is a memory-intensive copy and not a view

    Returns
    -------

    a_view : ndarray
        A windowed view on the input array `a`, or copied list of windows   

    """
    ashp = np.array(a.shape)

    if axis != None:
        axs = np.array(axis, ndmin = 1)
        assert np.all(np.in1d(axs, np.arange(ashp.size))), "Axes out of range"
    else:
        axs = np.arange(ashp.size)

    window = np.array(window, ndmin = 1)
    assert (window.size == axs.size) | (window.size == 1), "Window dims and axes don't match"
    wshp = ashp.copy()
    wshp[axs] = window
    assert np.all(wshp <= ashp), "Window is bigger than input array in axes"

    stp = np.ones_like(ashp)
    if steps:
        steps = np.array(steps, ndmin = 1)
        assert np.all(steps > 0), "Only positive steps allowed"
        assert (steps.size == axs.size) | (steps.size == 1), "Steps and axes don't match"
        stp[axs] = steps

    astr = np.array(a.strides)

    shape = tuple((ashp - wshp) // stp + 1) + tuple(wshp)
    strides = tuple(astr * stp) + tuple(astr)

    as_strided = np.lib.stride_tricks.as_strided
    a_view = np.squeeze(as_strided(a, 
                                 shape = shape, 
                                 strides = strides))
    if outlist:
        return list(a_view.reshape((-1,) + tuple(wshp)))
    else:
        return a_view
Run Code Online (Sandbox Code Playgroud)

一些测试用例:

a = np.arange(1000).reshape(10,10,10)

window_nd(a, 4).shape # sliding (4x4x4) window
Out: (7, 7, 7, 4, 4, 4)

window_nd(a, 2, 2).shape # (2x2x2) blocks
Out: (5, 5, 5, 2, 2, 2)

window_nd(a, 2, 1, 0).shape # sliding window of width 2 over axis 0
Out: (9, 2, 10, 10)

window_nd(a, 2, 2, (0,1)).shape # tiled (2x2) windows over first and second axes
Out: (5, 5, 2, 2, 10)

window_nd(a,(4,3,2)).shape  # arbitrary sliding window
Out: (7, 8, 9, 4, 3, 2)

window_nd(a,(4,3,2),(1,5,2),(0,2,1)).shape #arbitrary windows, steps and axis
Out: (7, 5, 2, 4, 2, 3) # note shape[-3:] != window as axes are out of order
Run Code Online (Sandbox Code Playgroud)