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
y B
, y desea hacer una multiplicación de matrices. Tu también:
np.einsum("ij, jk -> ik", A, B)
Aquí la cadena de subíndice ij
corresponde a la matriz, A
mientras que la cadena de subíndice jk
corresponde a la matriz B
. 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 la ein
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 matriz A
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ácter j
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
# a vector
In [197]: vec
Out[197]: array([0, 1, 2, 3])
# an array
In [198]: A
Out[198]:
array([[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34],
[41, 42, 43, 44]])
# another array
In [199]: B
Out[199]:
array([[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[4, 4, 4, 4]])
1) Multiplicación matricial (similar a np.matmul(arr1, arr2)
)
In [200]: np.einsum("ij, jk -> ik", A, B)
Out[200]:
array([[130, 130, 130, 130],
[230, 230, 230, 230],
[330, 330, 330, 330],
[430, 430, 430, 430]])
2) Extraer elementos a lo largo de la diagonal principal (similar a np.diag(arr)
)
In [202]: np.einsum("ii -> i", A)
Out[202]: array([11, 22, 33, 44])
3) Producto Hadamard (es decir, producto basado en elementos de dos matrices) (similar a arr1 * arr2
)
In [203]: np.einsum("ij, ij -> ij", A, B)
Out[203]:
array([[ 11, 12, 13, 14],
[ 42, 44, 46, 48],
[ 93, 96, 99, 102],
[164, 168, 172, 176]])
4) Cuadratura por elementos (similar a np.square(arr)
o arr ** 2
)
In [210]: np.einsum("ij, ij -> ij", B, B)
Out[210]:
array([[ 1, 1, 1, 1],
[ 4, 4, 4, 4],
[ 9, 9, 9, 9],
[16, 16, 16, 16]])
5) Trace (es decir, suma de elementos principal-diagonales) (similar a np.trace(arr)
)
In [217]: np.einsum("ii -> ", A)
Out[217]: 110
6) Transposición de matriz (similar a np.transpose(arr)
)
In [221]: np.einsum("ij -> ji", A)
Out[221]:
array([[11, 21, 31, 41],
[12, 22, 32, 42],
[13, 23, 33, 43],
[14, 24, 34, 44]])
7) Producto externo (de vectores) (similar a np.outer(vec1, vec2)
)
In [255]: np.einsum("i, j -> ij", vec, vec)
Out[255]:
array([[0, 0, 0, 0],
[0, 1, 2, 3],
[0, 2, 4, 6],
[0, 3, 6, 9]])
8) Producto interno (de vectores) (similar a np.inner(vec1, vec2)
)
In [256]: np.einsum("i, i -> ", vec, vec)
Out[256]: 14
9) Suma a lo largo del eje 0 (similar a np.sum(arr, axis=0)
)
In [260]: np.einsum("ij -> j", B)
Out[260]: array([10, 10, 10, 10])
10) Suma a lo largo del eje 1 (similar a np.sum(arr, axis=1)
)
In [261]: np.einsum("ij -> i", B)
Out[261]: array([ 4, 8, 12, 16])
11) Multiplicación de matriz de lotes
In [287]: BM = np.stack((A, B), axis=0)
In [288]: BM
Out[288]:
array([[[11, 12, 13, 14],
[21, 22, 23, 24],
[31, 32, 33, 34],
[41, 42, 43, 44]],
[[ 1, 1, 1, 1],
[ 2, 2, 2, 2],
[ 3, 3, 3, 3],
[ 4, 4, 4, 4]]])
In [289]: BM.shape
Out[289]: (2, 4, 4)
# batch matrix multiply using einsum
In [292]: BMM = np.einsum("bij, bjk -> bik", BM, BM)
In [293]: BMM
Out[293]:
array([[[1350, 1400, 1450, 1500],
[2390, 2480, 2570, 2660],
[3430, 3560, 3690, 3820],
[4470, 4640, 4810, 4980]],
[[ 10, 10, 10, 10],
[ 20, 20, 20, 20],
[ 30, 30, 30, 30],
[ 40, 40, 40, 40]]])
In [294]: BMM.shape
Out[294]: (2, 4, 4)
12) Suma a lo largo del eje 2 (similar a np.sum(arr, axis=2)
)
In [330]: np.einsum("ijk -> ij", BM)
Out[330]:
array([[ 50, 90, 130, 170],
[ 4, 8, 12, 16]])
13) Suma todos los elementos en la matriz (similar a np.sum(arr)
)
In [335]: np.einsum("ijk -> ", BM)
Out[335]: 480
14) Suma sobre múltiples ejes (es decir, marginación)
(similar a np.sum(arr, axis=(axis0, axis1, axis2, axis3, axis4, axis6, axis7))
)
# 8D array
In [354]: R = np.random.standard_normal((3,5,4,6,8,2,7,9))
# marginalize out axis 5 (i.e. "n" here)
In [363]: esum = np.einsum("ijklmnop -> n", R)
# marginalize out axis 5 (i.e. sum over rest of the axes)
In [364]: nsum = np.sum(R, axis=(0,1,2,3,4,6,7))
In [365]: np.allclose(esum, nsum)
Out[365]: True
15) de doble punto los productos (similar a np.sum (Hadamard-producto) cf. 3 )
In [772]: A
Out[772]:
array([[1, 2, 3],
[4, 2, 2],
[2, 3, 4]])
In [773]: B
Out[773]:
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
In [774]: np.einsum("ij, ij -> ", A, B)
Out[774]: 124
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.
# inputs
In [115]: A = np.random.rand(3,3)
In [116]: b = np.random.rand(3, 4, 5)
# solve for x
In [117]: x = np.linalg.solve(A, b.reshape(b.shape[0], -1)).reshape(b.shape)
# 2D and 3D array multiplication :)
In [118]: Ax = np.einsum('ij, jkl', A, x)
# indeed the same!
In [119]: np.allclose(Ax, b)
Out[119]: True
Por el contrario, si uno tiene que usar np.matmul()
para esta verificación, tenemos que hacer un par de reshape
operaciones para lograr el mismo resultado como:
# reshape 3D array `x` to 2D, perform matmul
# then reshape the resultant array to 3D
In [123]: Ax_matmul = np.matmul(A, x.reshape(x.shape[0], -1)).reshape(x.shape)
# indeed correct!
In [124]: np.allclose(Ax, Ax_matmul)
Out[124]: True
Bono : Lea más matemáticas aquí: Einstein-Summation y definitivamente aquí: Tensor-Notation
(A * B)^T
, o de manera equivalenteB^T * A^T
.