Estoy luchando por entender exactamente cómo einsum
funciona. He visto la documentación y algunos ejemplos, pero no parece que se quede.
Aquí hay un ejemplo que analizamos en clase:
C = np.einsum("ij,jk->ki", A, B)
para dos matrices A
yB
Creo que esto tomaría A^T * B
, pero no estoy seguro (está tomando la transposición de uno de ellos, ¿verdad?). ¿Alguien puede explicarme exactamente lo que está sucediendo aquí (y en general cuando se usa einsum
)?
python
arrays
numpy
multidimensional-array
numpy-einsum
Estrecho de lanza
fuente
fuente
(A * B)^T
, o de manera equivalenteB^T * A^T
.einsum
aquí . (Me complace trasplantar los bits más relevantes a una respuesta en Stack Overflow si es útil).numpy
documentación es lamentablemente inadecuada al explicar los detalles.*
no se trata de la multiplicación matricial sino de la multiplicación por elementos. ¡Cuidado!Respuestas:
(Nota: esta respuesta se basa en una breve publicación de blog sobre lo
einsum
que escribí hace un tiempo).¿Qué
einsum
hacer?Imagine que tenemos dos matrices multidimensionales
A
yB
. Ahora supongamos que queremos ...A
conB
de una manera particular para crear nueva gama de productos; y luego tal vezHay una posibilidad buena que
einsum
nos ayudará a hacer esto más rápido y más memoria-eficiente que las combinaciones de las funciones NumPy gustamultiply
,sum
ytranspose
lo permita.Como
einsum
funcionaAquí hay un ejemplo simple (pero no completamente trivial). Tome las siguientes dos matrices:
Multiplicaremos
A
y en función de losB
elementos y luego sumaremos a lo largo de las filas de la nueva matriz. En NumPy "normal" escribiríamos:Entonces, aquí, la operación de indexación
A
alinea los primeros ejes de las dos matrices para que la multiplicación pueda transmitirse. Las filas de la matriz de productos se suman para devolver la respuesta.Ahora, si quisiéramos usarlo
einsum
, podríamos escribir:La cadena de firma
'i,ij->i'
es la clave aquí y necesita un poco de explicación. Puedes pensarlo en dos mitades. En el lado izquierdo (a la izquierda de->
) hemos etiquetado las dos matrices de entrada. A la derecha de->
, hemos etiquetado la matriz con la que queremos terminar.Esto es lo que sucede después:
A
tiene un eje; Lo hemos etiquetadoi
. YB
tiene dos ejes; hemos etiquetado el eje 0 comoi
y el eje 1 comoj
.Al repetir la etiqueta
i
en ambas matrices de entrada, estamos diciendoeinsum
que estos dos ejes deben multiplicarse juntos. En otras palabras, estamos multiplicando la matrizA
con cada columna de la matrizB
, al igual que loA[:, np.newaxis] * B
hace.Tenga en cuenta que
j
no aparece como una etiqueta en nuestra salida deseada; acabamos de usari
(queremos terminar con una matriz 1D). Al omitir la etiqueta, le estamos diciendoeinsum
que sume a lo largo de este eje. En otras palabras, estamos sumando las filas de los productos, al igual que lo.sum(axis=1)
hace.Eso es básicamente todo lo que necesitas saber para usar
einsum
. Ayuda a jugar un poco; Si dejamos ambas etiquetas en la salida,'i,ij->ij'
recuperamos una matriz 2D de productos (igual queA[:, np.newaxis] * B
). Si decimos que no hay etiquetas de salida,'i,ij->
recuperamos un solo número (lo mismo que hacer(A[:, np.newaxis] * B).sum()
).La gran cosa acerca
einsum
sin embargo, es que es no construir una matriz temporal de productos de primera; simplemente resume los productos a medida que avanza. Esto puede conducir a grandes ahorros en el uso de la memoria.Un ejemplo un poco más grande
Para explicar el producto punto, aquí hay dos nuevas matrices:
Calcularemos el producto de punto usando
np.einsum('ij,jk->ik', A, B)
. Aquí hay una foto que muestra el etiquetado de laA
eB
y la matriz de salida que obtenemos de la función:Puede ver que la etiqueta
j
se repite, esto significa que estamos multiplicando las filas deA
con las columnas deB
. Además, la etiquetaj
no está incluida en la salida; estamos sumando estos productos. Las etiquetasi
yk
se guardan para la salida, por lo que recuperamos una matriz 2D.Puede ser que sea aún más clara para comparar este resultado con la matriz donde la etiqueta
j
se no se suma. A continuación, a la izquierda, puede ver la matriz 3D que resulta de la escrituranp.einsum('ij,jk->ijk', A, B)
(es decir, hemos mantenido la etiquetaj
):El eje de suma
j
da el producto de punto esperado, que se muestra a la derecha.Algunos ejercicios
Para tener más experiencia
einsum
, puede ser útil implementar operaciones familiares de matriz NumPy utilizando la notación de subíndice. Cualquier cosa que implique combinaciones de ejes de multiplicación y suma se puede escribir usandoeinsum
.Deje que A y B sean dos matrices 1D con la misma longitud. Por ejemplo,
A = np.arange(10)
yB = np.arange(5, 15)
.La suma de
A
se puede escribir:La multiplicación por elementos
A * B
, se puede escribir:El producto interno o producto de punto,
np.inner(A, B)
onp.dot(A, B)
, se puede escribir:El producto externo
np.outer(A, B)
, se puede escribir:Para matrices 2D
C
yD
, siempre que los ejes sean longitudes compatibles (tanto la misma longitud como una de ellas tiene longitud 1), aquí hay algunos ejemplos:El rastro de
C
(suma de la diagonal principal)np.trace(C)
, se puede escribir:En cuanto al elemento de multiplicación de
C
y la transpuesta deD
,C * D.T
, se puede escribir:Multiplicando cada elemento de
C
por la matrizD
(para hacer una matriz 4D)C[:, :, None, None] * D
, se puede escribir:fuente
ij,jk
podría funcionar por sí solo (sin las flechas) para formar la multiplicación de la matriz. Pero parece que, por claridad, es mejor colocar las flechas y luego las dimensiones de salida. Está en la publicación del blog.A
es de longitud 3, lo mismo que la longitud de las columnasB
(mientras que las filasB
tienen longitud 4 y no pueden multiplicarse por elementosA
).->
afecta a la semántica: "En modo implícito, los subíndices elegidos son importantes ya que los ejes de la salida se reordenan alfabéticamente. Esto significa quenp.einsum('ij', a)
no afecta a una matriz 2D, mientrasnp.einsum('ji', a)
toma su transposición".Captar la idea
numpy.einsum()
es muy fácil si lo entiendes intuitivamente. Como ejemplo, comencemos con una descripción simple que involucra la multiplicación de matrices .Para usar
numpy.einsum()
, todo lo que tiene que hacer es pasar la llamada cadena de subíndices como argumento, seguida de sus matrices de entrada .Digamos que tiene dos matrices 2D,
A
yB
, y desea hacer una multiplicación de matrices. Tu también:Aquí la cadena de subíndice
ij
corresponde a la matriz,A
mientras que la cadena de subíndicejk
corresponde a la matrizB
. Además, lo más importante a tener en cuenta aquí es que el número de caracteres en cada cadena de subíndice debe coincidir con las dimensiones de la matriz. (es decir, dos caracteres para matrices 2D, tres caracteres para matrices 3D, etc.) Y si repite los caracteres entre cadenas de subíndice (j
en nuestro caso), eso significa que desea que laein
suma ocurra a lo largo de esas dimensiones. Por lo tanto, se reducirán la suma. (es decir, esa dimensión se habrá ido )La cadena de subíndice después de esto
->
, será nuestra matriz resultante. Si lo deja vacío, todo se sumará y se devolverá un valor escalar. De lo contrario, la matriz resultante tendrá dimensiones de acuerdo con la cadena de subíndice . En nuestro ejemplo, lo seráik
. Esto es intuitivo porque sabemos que para la multiplicación de matrices, el número de columnas en la matrizA
debe coincidir con el número de filas en la matriz,B
que es lo que está sucediendo aquí (es decir, codificamos este conocimiento repitiendo el carácterj
en la cadena del subíndice )Aquí hay algunos ejemplos más que ilustran el uso / poder de la
np.einsum()
implementación de algunas operaciones comunes de tensor o nd-array , sucintamente.Entradas
1) Multiplicación matricial (similar a
np.matmul(arr1, arr2)
)2) Extraer elementos a lo largo de la diagonal principal (similar a
np.diag(arr)
)3) Producto Hadamard (es decir, producto basado en elementos de dos matrices) (similar a
arr1 * arr2
)4) Cuadratura por elementos (similar a
np.square(arr)
oarr ** 2
)5) Trace (es decir, suma de elementos principal-diagonales) (similar a
np.trace(arr)
)6) Transposición de matriz (similar a
np.transpose(arr)
)7) Producto externo (de vectores) (similar a
np.outer(vec1, vec2)
)8) Producto interno (de vectores) (similar a
np.inner(vec1, vec2)
)9) Suma a lo largo del eje 0 (similar a
np.sum(arr, axis=0)
)10) Suma a lo largo del eje 1 (similar a
np.sum(arr, axis=1)
)11) Multiplicación de matriz de lotes
12) Suma a lo largo del eje 2 (similar a
np.sum(arr, axis=2)
)13) Suma todos los elementos en la matriz (similar a
np.sum(arr)
)14) Suma sobre múltiples ejes (es decir, marginación)
(similar a
np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7))
)15) de doble punto los productos (similar a np.sum (Hadamard-producto) cf. 3 )
16) Multiplicación de matrices 2D y 3D
Tal multiplicación podría ser muy útil al resolver un sistema lineal de ecuaciones ( Ax = b ) donde desea verificar el resultado.
Por el contrario, si uno tiene que usar
np.matmul()
para esta verificación, tenemos que hacer un par dereshape
operaciones para lograr el mismo resultado como:Bono : Lea más matemáticas aquí: Einstein-Summation y definitivamente aquí: Tensor-Notation
fuente
Hagamos 2 matrices, con dimensiones diferentes pero compatibles para resaltar su interacción
Su cálculo, toma un 'punto' (suma de productos) de un (2,3) con un (3,4) para producir una matriz (4,2).
i
es el primer dim deA
, el último deC
;k
el último deB
, primero deC
.j
es 'consumido' por la sumatoria.Esto es lo mismo que
np.dot(A,B).T
: se transpone la salida final.Para ver más de lo que sucede
j
, cambie losC
subíndices aijk
:Esto también se puede producir con:
Es decir, agregue una
k
dimensión al final deA
, y unai
al frente deB
, dando como resultado una matriz (2,3,4).0 + 4 + 16 = 20
,9 + 28 + 55 = 92
, Etc; Sumaj
y transpone para obtener el resultado anterior:fuente
Encontré NumPy: Los trucos del oficio (Parte II) instructivos
Observe que hay tres ejes, i, j, k, y que j se repite (en el lado izquierdo).
i,j
representar filas y columnas paraa
.j,k
parab
.Para calcular el producto y alinear el
j
eje, necesitamos agregarle un ejea
. (b
se transmitirá a lo largo (?) del primer eje)j
está ausente del lado derecho, por lo que sumamosj
cuál es el segundo eje de la matriz 3x3x3Finalmente, los índices se invierten (alfabéticamente) en el lado derecho, por lo que transponemos.
fuente
Al leer las ecuaciones de einsum, me pareció más útil poder reducirlas mentalmente a sus versiones imperativas.
Comencemos con la siguiente declaración (imponente):
Al trabajar primero en la puntuación, vemos que tenemos dos blobs separados por comas de 4 letras,
bhwi
ybhwj
, antes de la flecha, y un único blob de 3 letrasbij
después. Por lo tanto, la ecuación produce un resultado de tensor de rango 3 a partir de dos entradas de tensor de rango 4.Ahora, deje que cada letra de cada blob sea el nombre de una variable de rango. La posición en la que aparece la letra en el blob es el índice del eje sobre el que se extiende en ese tensor. La suma imperativa que produce cada elemento de C, por lo tanto, tiene que comenzar con tres bucles anidados, uno para cada índice de C.
Entonces, esencialmente, tiene un
for
ciclo para cada índice de salida de C. Dejaremos los rangos indeterminados por ahora.A continuación, miramos el lado izquierdo: ¿hay alguna variable de rango allí que no aparezca en el lado derecho ? En nuestro caso, sí,
h
yw
. Agregue unfor
bucle anidado interno para cada variable:Dentro del ciclo más interno ahora tenemos todos los índices definidos, por lo que podemos escribir la suma real y la traducción está completa:
Si hasta ahora ha podido seguir el código, ¡felicidades! Esto es todo lo que necesitas para poder leer las ecuaciones de einsum. Observe en particular cómo la fórmula original de einsum se asigna a la declaración de suma final en el fragmento de arriba. Los for-loops y los límites de rango son simplemente imprecisos y esa declaración final es todo lo que realmente necesita para comprender lo que está sucediendo.
En aras de la exhaustividad, veamos cómo determinar los rangos para cada variable de rango. Bueno, el rango de cada variable es simplemente la longitud de las dimensiones que indexa. Obviamente, si una variable indexa más de una dimensión en uno o más tensores, entonces las longitudes de cada una de esas dimensiones deben ser iguales. Aquí está el código anterior con los rangos completos:
fuente