copiar matriz 2D en 3ra dimensión, N veces (Python)

106

Me gustaría copiar una matriz 2D numpy en una tercera dimensión. Por ejemplo, dada la matriz numpy (2D):

import numpy as np
arr = np.array([[1,2],[1,2]])
# arr.shape = (2, 2)

conviértalo en una matriz 3D con N copias de este tipo en una nueva dimensión. Actuando arrcon N = 3, la salida debería ser:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)
anon01
fuente

Respuestas:

146

Probablemente la forma más limpia es usar np.repeat:

a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2,  2)

# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)

print(b.shape)
# (2, 2, 3)

print(b[:, :, 0])
# [[1 2]
#  [1 2]]

print(b[:, :, 1])
# [[1 2]
#  [1 2]]

print(b[:, :, 2])
# [[1 2]
#  [1 2]]

Habiendo dicho eso, a menudo puede evitar repetir sus matrices por completo utilizando la transmisión . Por ejemplo, digamos que quería agregar un (3,)vector:

c = np.array([1, 2, 3])

a a. Podría copiar el contenido de a3 veces en la tercera dimensión, luego copiar el contenido de cdos veces tanto en la primera como en la segunda dimensión, de modo que ambas matrices fueran (2, 2, 3), luego calcular su suma. Sin embargo, es mucho más sencillo y rápido hacer esto:

d = a[..., None] + c[None, None, :]

Aquí, a[..., None]tiene forma (2, 2, 1)y c[None, None, :]tiene forma (1, 1, 3)*. Cuando calculo la suma, el resultado se 'difunde' a lo largo de las dimensiones del tamaño 1, lo que me da un resultado de forma (2, 2, 3):

print(d.shape)
# (2,  2, 3)

print(d[..., 0])    # a + c[0]
# [[2 3]
#  [2 3]]

print(d[..., 1])    # a + c[1]
# [[3 4]
#  [3 4]]

print(d[..., 2])    # a + c[2]
# [[4 5]
#  [4 5]]

La transmisión es una técnica muy poderosa porque evita la sobrecarga adicional involucrada en la creación de copias repetidas de sus matrices de entrada en la memoria.


* Aunque los incluí para mayor claridad, los Noneíndices en cno son realmente necesarios; también puede hacerlo a[..., None] + c, es decir, transmitir una (2, 2, 1)matriz contra una (3,)matriz. Esto se debe a que si una de las matrices tiene menos dimensiones que la otra, solo las dimensiones finales de las dos matrices deben ser compatibles. Para dar un ejemplo más complicado:

a = np.ones((6, 1, 4, 3, 1))  # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2))     #     5 x 1 x 3 x 2
result = a + b                # 6 x 5 x 4 x 3 x 2
ali_m
fuente
Para comprobar que este hecho da el resultado correcto, también se puede imprimir b[:,:,0], b[:,:,1]y b[:,:,2]. Cada segmento de la tercera dimensión es una copia de la matriz 2D original. Esto no es tan obvio con solo mirarlo print(b).
Ely
¿Cuál es la diferencia entre None y np.newaxis? Cuando lo probé, dio el mismo resultado.
monolito
1
@wedran Son exactamente iguales - np.newaxises solo un alias deNone
ali_m
27

Otra forma es utilizar numpy.dstack. Suponiendo que desea repetir los a num_repeatstiempos de la matriz :

import numpy as np
b = np.dstack([a]*num_repeats)

El truco consiste en envolver la matriz aen una lista de un solo elemento, luego usar el *operador para duplicar los elementos en esta lista num_repeatsveces.

Por ejemplo, si:

a = np.array([[1, 2], [1, 2]])
num_repeats = 5

Esto repite la matriz de [1 2; 1 2]5 veces en la tercera dimensión. Para verificar (en IPython):

In [110]: import numpy as np

In [111]: num_repeats = 5

In [112]: a = np.array([[1, 2], [1, 2]])

In [113]: b = np.dstack([a]*num_repeats)

In [114]: b[:,:,0]
Out[114]: 
array([[1, 2],
       [1, 2]])

In [115]: b[:,:,1]
Out[115]: 
array([[1, 2],
       [1, 2]])

In [116]: b[:,:,2]
Out[116]: 
array([[1, 2],
       [1, 2]])

In [117]: b[:,:,3]
Out[117]: 
array([[1, 2],
       [1, 2]])

In [118]: b[:,:,4]
Out[118]: 
array([[1, 2],
       [1, 2]])

In [119]: b.shape
Out[119]: (2, 2, 5)

Al final podemos ver que la forma de la matriz es 2 x 2, con 5 cortes en la tercera dimensión.

rayryeng
fuente
¿Cómo se compara esto con reshape? ¿Más rápido? da la misma estructura? Definitivamente es más ordenado.
Ander Biguri
@AnderBiguri Nunca he evaluado ... Puse esto aquí principalmente para que esté completo. Será interesante medir el tiempo y ver las diferencias.
rayryeng
1
¡Acabo de hacer img = np.dstack ([arr] * 3) y funcionó bien! Gracias
thanos
1
Pensé que podría proponer un resultado visualizado por eficiencia. Al ser una publicación antigua, la gente podría haberse perdido eso. Se agregó una solución en esta sesión de preguntas y respuestas.
Divakar
1
En mi humilde opinión, la solución más legible, pero sería genial compararla con otros métodos de comparación.
mrgloom
16

¡Utilice una vista y obtenga tiempo de ejecución gratis! Ampliar genéricon-dim matrices an+1-dim

Introducido en NumPy1.10.0 , podemos aprovechar numpy.broadcast_topara generar simplemente una 3Dvista en la 2Dmatriz de entrada. El beneficio sería la ausencia de sobrecarga de memoria adicional y el tiempo de ejecución prácticamente gratuito. Esto sería esencial en los casos en que las matrices son grandes y podemos trabajar con vistas. Además, esto funcionaría con genéricon-dim casos .

Usaría la palabra stacken lugar decopy , ya que los lectores podrían confundirla con la copia de matrices que crea copias de memoria.

Apilar a lo largo del primer eje

Si queremos apilar la entrada a lo arrlargo del primer eje, la solución np.broadcast_topara crear la 3Dvista sería:

np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here

Apilar a lo largo del tercer / último eje

Para apilar la entrada a lo arrlargo del tercer eje, la solución para crear una 3Dvista sería:

np.broadcast_to(arr[...,None],arr.shape+(3,))

Si realmente necesitamos una copia de memoria, siempre podemos agregarla .copy(). Por lo tanto, las soluciones serían:

np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()

Así es como funciona el apilamiento para los dos casos, que se muestra con su información de forma para un caso de muestra:

# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)

# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)

# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)

Las mismas soluciones funcionarían para extender una n-dimentrada an+1-dim ver la salida a lo largo del primer y último eje. Exploremos algunos casos de mayor atenuación:

Caso de entrada 3D:

In [58]: arr = np.random.rand(4,5,6)

# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)

# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)

Caso de entrada 4D:

In [61]: arr = np.random.rand(4,5,6,7)

# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)

# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)

y así.

Tiempos

Usemos un 2Dcaso de muestra grande y obtengamos los tiempos y verifiquemos que la salida sea un view.

# Sample input array
In [19]: arr = np.random.rand(1000,1000)

Demostremos que la solución propuesta es realmente una vista. Usaremos apilamiento a lo largo del primer eje (los resultados serían muy similares para apilar a lo largo del tercer eje) -

In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True

Consigamos los tiempos para mostrar que es prácticamente gratis.

In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop

In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop

Al ser una vista, el aumento Nde 3a 3000no cambió nada en los tiempos y ambos son insignificantes en las unidades de tiempo. Por lo tanto, eficiente tanto en memoria como en rendimiento.

Divakar
fuente
3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Edite @ Mr.F, para preservar el orden de las dimensiones:

B=B.T
yevgeniy
fuente
Esto da como resultado una matriz N x 2 x 2 para mí, por ejemplo, se B.shapeimprime (N, 2, 2)para cualquier valor de N. Si transpone Bcon B.T, coincide con la salida esperada.
ely
@ Mr.F - Tienes razón. Esta será transmitido a lo largo de la primera dimensión, y al hacerlo B[0], B[1],...le dará la rebanada de la derecha, que voy a argumentar y decir que es más fácil de escribir en lugar de utilizar B[:,:,0], B[:,:,1], etc.
rayryeng
Puede ser más fácil de escribir, pero, por ejemplo, si está haciendo esto con datos de imagen, sería en gran medida incorrecto, ya que casi todos los algoritmos esperan que se utilicen las convenciones del álgebra lineal para los cortes 2D de canales de píxeles. Es difícil imaginar una aplicación en la que comience con una matriz 2D, tratando filas y columnas con una cierta convención, y luego desee múltiples copias de esa misma cosa extendiéndose a un nuevo eje, pero de repente desea que el primer eje cambie de significado a ser el nuevo eje ...
ely
@ Mr.F - Oh, ciertamente. No puedo adivinar qué aplicaciones querrá usar la matriz 3D en el futuro. Dicho esto, todo depende de la aplicación. FWIW, prefiero el B[:,:,i]y eso es a lo que estoy acostumbrado.
rayryeng
2

Aquí hay un ejemplo de transmisión que hace exactamente lo que se solicitó.

a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]

Entonces b*aes el resultado deseado y (b*a)[:,:,0]produce array([[1, 2],[1, 2]]), que es el original a, como lo hace (b*a)[:,:,1], etc.

Mike O'Connor
fuente
2

Esto ahora también se puede lograr usando np.tile de la siguiente manera:

import numpy as np

a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))

b.shape
(3,2,2)

b
array([[[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]]])
FBruzzesi
fuente