¿Cómo entender los grandes pasos para el laico?

Actualmente estoy pasando por un número y hay un tema en numpy llamado “zancadas”. Entiendo lo que es. pero como funciona? No encontré ninguna información útil en línea. ¿Alguien puede dejarme entender en términos de un laico?

Los datos reales de una matriz numpy se almacenan en un bloque de memoria homogéneo y contiguo llamado buffer de datos. Para obtener más información, consulte NumPy internals . Usando el orden de fila mayor (predeterminado), una matriz 2D se ve así:

introduzca la descripción de la imagen aquí

Para asignar los índices i, j, k, … de una matriz multidimensional a las posiciones en el búfer de datos (el desplazamiento, en bytes), NumPy usa la noción de pasos . Las zancadas son el número de bytes a saltar en la memoria para pasar de un elemento al siguiente a lo largo de cada dirección / dimensión de la matriz. En otras palabras, es la separación de bytes entre elementos consecutivos para cada dimensión.

Por ejemplo:

>>> a = np.arange(1,10).reshape(3,3) >>> a array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 

Esta matriz 2D tiene dos direcciones, ejes 0 (que se ejecutan verticalmente hacia abajo a través de filas) y eje 1 (que se ejecuta horizontalmente a través de columnas), con cada elemento que tiene tamaño:

 >>> a.itemsize # in bytes 4 

Así que para pasar de a[0, 0] -> a[0, 1] (moviéndose horizontalmente a lo largo de la fila 0, de la columna 0 a la 1ª columna) el paso de bytes en el búfer de datos es 4. Lo mismo para a[0, 1] -> a[0, 2] , a[1, 0] -> a[1, 1] etc. Esto significa que el número de pasos para la dirección horizontal (eje-1) es de 4 bytes.

Sin embargo, para pasar de a[0, 0] -> a[1, 0] (moviéndose verticalmente a lo largo de la columna 0, de la fila 0 a la 1ª fila), primero debe recorrer todos los elementos restantes en la fila 0 para llegar a la primera fila, y luego moverse a través de la primera fila para obtener el elemento a[1, 0] , es decir, a[0, 0] -> a[0, 1] -> a[0, 2] -> a[1, 0] . Por lo tanto, el número de pasos para la dirección vertical (eje-0) es 3 * 4 = 12 bytes. Tenga en cuenta que al pasar de a[0, 2] -> a[1, 0] , y en general desde el último elemento de la i-th fila hasta el primer elemento de la (i + 1) -th row, también es 4 bytes porque la matriz a se almacena en el orden de fila mayor.

Es por eso

 >>> a.strides # (strides[0], strides[1]) (12, 4) 

Este es otro ejemplo que muestra que las zancadas en la dirección horizontal (eje-1), strides[1] , de una matriz 2D no son necesarias igual al tamaño del elemento (por ejemplo, una matriz con orden mayor de columnas):

 >>> b = np.array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]).T >>> b array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> b.strides (4, 12) 

Aquí strides[1] es un múltiplo del tamaño del elemento. Aunque la matriz b parece idéntica a la matriz a , es una matriz diferente: internamente b se almacena como |1|4|7|2|5|8|3|6|9| (porque la transposición no afecta al búfer de datos, sino que solo intercambia los pasos y la forma), mientras que a |1|2|3|4|5|6|7|8|9| . Lo que los hace parecerse son los diferentes pasos. Es decir, el paso de bytes para b[0, 0] -> b[0, 1] es 3 * 4 = 12 bytes y para b[0, 0] -> b[1, 0] es 4 bytes, mientras que para a[0, 0] -> a[0, 1] es de 4 bytes y para a[0, 0] -> a[1, 0] es de 12 bytes.

Por último, pero no menos importante, NumPy permite crear vistas de matrices existentes con la opción de modificar las zancadas y la forma, ver trucos de zancada . Por ejemplo:

 >>> np.lib.stride_tricks.as_strided(a, shape=a.shape[::-1], strides=a.strides[::-1]) array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) 

que es equivalente a transponer la matriz a .

Permítanme agregar, pero sin entrar en muchos detalles, que incluso se pueden definir pasos que no sean múltiplos del tamaño del elemento. Aquí hay un ejemplo:

 >>> a = np.lib.stride_tricks.as_strided(np.array([1, 512, 0, 3], dtype=np.int16), shape=(3,), strides=(3,)) >>> a array([1, 2, 3], dtype=int16) >>> a.strides[0] 3 >>> a.itemsize 2 

Solo para agregar a la gran respuesta de @AndyK, aprendí sobre los grandes avances de Numpy MedKit . Allí muestran el uso con un problema de la siguiente manera:

Entrada dada :

 x = np.arange(20).reshape([4, 5]) >>> x array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) 

Salida esperada :

 array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9]], [[ 5, 6, 7, 8, 9], [ 10, 11, 12, 13, 14]], [[ 10, 11, 12, 13, 14], [ 15, 16, 17, 18, 19]]]) 

Para hacer esto, necesitamos conocer los siguientes términos:

forma – Las dimensiones de la matriz a lo largo de cada eje.

strides : el número de bytes de memoria que se deben omitir para avanzar al siguiente elemento a lo largo de una determinada dimensión.

 >>> x.strides (20, 4) >>> np.int32().itemsize 4 

Ahora, si nos fijamos en el resultado esperado :

 array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9]], [[ 5, 6, 7, 8, 9], [ 10, 11, 12, 13, 14]], [[ 10, 11, 12, 13, 14], [ 15, 16, 17, 18, 19]]]) 

Necesitamos manipular la forma de la matriz y los pasos. La forma de salida debe ser (3, 2, 5), es decir, 3 elementos, cada uno con dos filas (m == 2) y cada fila con 5 elementos.

Los pasos deben cambiar de (20, 4) a (20, 20, 4). Cada elemento de la nueva matriz de salida comienza en una nueva fila, cada fila consta de 20 bytes (5 elementos de 4 bytes cada uno) y cada elemento ocupa 4 bytes (int32).

Asi que:

 >>> from numpy.lib import stride_tricks >>> stride_tricks.as_strided(x, shape=(3, 2, 5), strides=(20, 20, 4)) ... array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9]], [[ 5, 6, 7, 8, 9], [ 10, 11, 12, 13, 14]], [[ 10, 11, 12, 13, 14], [ 15, 16, 17, 18, 19]]]) 

Una alternativa sería:

 >>> d = dict(x.__array_interface__) >>> d['shape'] = (3, 2, 5) >>> s['strides'] = (20, 20, 4) >>> class Arr: ... __array_interface__ = d ... base = x >>> np.array(Arr()) array([[[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9]], [[ 5, 6, 7, 8, 9], [ 10, 11, 12, 13, 14]], [[ 10, 11, 12, 13, 14], [ 15, 16, 17, 18, 19]]]) 

Utilizo este método muy a menudo en lugar de numpy.hstack o numpy.vstack y créeme, computacionalmente es mucho más rápido.

Nota:

Cuando se usan matrices muy grandes con este truco, calcular los pasos exactos no es tan trivial. Por lo general, hago una matriz numpy.zeroes de la forma deseada y obtengo las zancadas utilizando array.strides y lo uso en la función stride_tricks.as_strided .

¡Espero eso ayude!