Uso de la función numpy `as_strided` para crear parches, mosaicos, ventanas enrollables o deslizantes de dimensión arbitraria
Pasé un rato esta mañana buscando una pregunta generalizada para señalar duplicados en caso de preguntas sobre as_strided
y/o cómo crear funciones de ventana generalizadas . Parece haber muchas preguntas sobre cómo crear (de forma segura) parches, ventanas deslizantes, ventanas rodantes, 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 window
parámetro step
y axis
devolver una as_strided
vista para dimensiones más arbitrarias. Daré mi respuesta a continuación, pero me interesa saber si alguien puede crear un método más eficiente, ya que no estoy seguro de que usar np.squeeze()
sea el mejor método, no estoy seguro de que mis assert
declaraciones hagan que la función sea lo suficientemente segura como para escribir en el resultado. vista, y no estoy seguro de cómo manejar el caso límite de axis
no estar en orden ascendente.
DEBIDA DILIGENCIA
La función más generalizada que puedo encontrar está sklearn.feature_extraction.image.extract_patches
escrita por @eickenberg (así como la aparentemente equivalente skimage.util.view_as_windows
), pero no están bien documentadas en la red y no pueden hacer ventanas en menos ejes de los que hay en la matriz original (por ejemplo , esta pregunta solicita una ventana de cierto tamaño en un solo eje). También muchas veces las preguntas quieren una numpy
única respuesta.
@Divakar creó una función generalizada numpy
para entradas 1-d aquí , pero las entradas de mayor dimensión requieren un poco más de cuidado. He creado una ventana 2D básica sobre un método de entrada 3D , pero no es muy extensible.
EDITAR ENERO DE 2020 : Se cambió el retorno iterable de una lista a un generador para ahorrar memoria.
EDITAR OCTUBRE DE 2020 : coloque el generador en una función separada, ya que mezclar generadores y return
declaraciones no funciona de manera intuitiva.
Aquí está la receta que tengo hasta ahora:
def window_nd(a, window, steps = None, axis = None, gen_data = 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
gen_data : boolean
returns data needed for a generator
Returns
-------
a_view : ndarray
A windowed view on the input array `a`, or `a, wshp`, where `whsp` is the window shape needed for creating the generator
"""
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 gen_data :
return a_view, shape[:-wshp.size]
else:
return a_view
def window_gen(a, window, **kwargs):
#Same docstring as above, returns a generator
_ = kwargs.pop(gen_data, False)
a_view, shp = window_nd(a, window, gen_data = True, **kwargs)
for idx in np.ndindex(shp):
yield a_view[idx]
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