Usando la función numpy `as_strided` para crear parches, mosaicos, ventanas móviles o deslizantes de dimensión arbitraria

Pasé un tiempo esta mañana buscando una pregunta generalizada para apuntar duplicados a preguntas sobre as_strided y / o cómo hacer funciones de ventana generalizadas . Parece que hay muchas preguntas sobre cómo crear (con seguridad) parches, ventanas deslizantes, ventanas, mosaicos o vistas en una matriz para aprendizaje automático, convolución, procesamiento de imágenes y / o integración numérica.

Estoy buscando una función generalizada que pueda aceptar un parámetro de window , step y axis y devolver una vista as_strided para más de las dimensiones arbitrarias. Voy a dar mi respuesta a continuación, pero estoy interesado si alguien puede hacer un método más eficiente, ya que no estoy seguro de usar np.squeeze() es el mejor método, no estoy seguro de que mis declaraciones assert que la función sea segura suficiente para escribir en la vista resultante, y no estoy seguro de cómo manejar la caja de borde del axis no está en orden ascendente.

DEBIDA DILIGENCIA

La función más generalizada que puedo encontrar es sklearn.feature_extraction.image.extract_patches escrito por @eickenberg (así como el skimage.util.view_as_windows aparentemente equivalente), pero esos no están bien documentados en la red, y no pueden hacer windows over menos ejes que los que hay en la matriz original (por ejemplo, esta pregunta pide una ventana de un cierto tamaño sobre un solo eje). También a menudo las preguntas quieren una respuesta única numpy .

@Divakar creó una función numpy generalizada para las entradas 1-d aquí , pero las entradas de mayor dimensión requieren un poco más de cuidado. Hice una ventana 2D básica sobre el método de entrada en 3D , pero no es muy extensible.

Aquí está la receta que tengo hasta ahora:

 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 

Algunos casos de prueba:

 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