En este tutorial, veremos varias formas de realizar la multiplicación de matrices usando  matrices NumPy . aprenderemos a multiplicar matrices de diferentes tamaños juntas.

También. Aprenderemos cómo acelerar el proceso de multiplicación usando GPU y otros temas candentes, ¡así que comencemos!

Antes de seguir adelante, es mejor revisar algunas terminologías básicas de Álgebra de matrices.

Terminologías básicas

Vector:  Algebraicamente, un vector es una colección de coordenadas de un punto en el espacio.
Por tanto, un vector con 2 valores representa un punto en un espacio bidimensional. En Ciencias de la Computación, un vector es una disposición de números a lo largo de una sola dimensión. También se conoce comúnmente como matriz, lista o tupla.
P.ej. [1,2,3,4]

Matriz:  Una matriz (matrices plurales) es una disposición bidimensional de números o una colección de vectores.
Ex:

1
2
3
[[1,2,3],
[4,5,6],
[7,8,9]]

Producto escalar:  Un producto escalar es una operación matemática entre  2 vectores de igual longitud .
Es igual a la suma de los productos de los elementos correspondientes de los vectores.

Con una comprensión clara de estas terminologías, estamos listos para comenzar.

Multiplicación de matrices con un vector

Comencemos con una forma simple de multiplicación de matrices: entre una matriz y un vector.

Antes de continuar, primero entendamos cómo se representa una matriz usando NumPy.

El  método array () de NumPy  se utiliza para representar vectores, matrices y tensores de dimensiones superiores Definamos un vector de 5 dimensiones y una matriz de 3 × 3 usando NumPy.

01
02
03
04
05
06
07
08
09
10
11
12
13
import numpy as np
 
a = np.array([1, 3, 5, 7, 9])
 
b = np.array([[1, 2, 3],
             [4, 5, 6],
             [7, 8, 9]])
 
print("Vector a:\n", a)
 
print()
 
print("Matrix b:\n", b)

Salida:

Veamos ahora cómo se produce la multiplicación entre una matriz y un vector.

Deben tenerse en cuenta los siguientes puntos para una multiplicación matriz-vector:

  1. El resultado de una multiplicación matriz-vector es un vector.
  2. Cada elemento de este vector se obtiene realizando un producto escalar entre cada fila de la matriz y el vector que se está multiplicando.
  3. El número de columnas de la matriz debe ser igual al número de elementos del vector.

Usaremos el  método matmul () de NumPy  para la mayoría de nuestras operaciones de multiplicación de matrices.
Definamos una matriz de 3 × 3 y multipliquemos con un vector de longitud 3.

01
02
03
04
05
06
07
08
09
10
11
12
import numpy as np
 
a = np.array([[1, 2, 3],
             [4, 5, 6],
             [7, 8, 9]])
b= np.array([10, 20, 30])
 
print("A =", a)
 
print("b =", b)
 
print("Ab =",np.matmul(a,b))

Salida:

Observe cómo el resultado es un vector de longitud igual a las filas de la matriz multiplicadora.

Multiplicación con otra matriz

Ahora que entendemos la multiplicación de una matriz con un vector, sería fácil averiguar la multiplicación de dos matrices.
Pero, antes de eso, repasemos las reglas más importantes de la multiplicación de matrices:

  1. El número de columnas de la primera matriz debe ser igual al número de filas de la segunda matriz.
  2. Si multiplicamos una matriz de dimensiones mxn con otra matriz de dimensiones nxp, entonces el producto resultante será una matriz de dimensiones mx p.

Consideremos la multiplicación de una matriz A de mxn con una matriz B de nxp: 

El producto de las dos matrices C = AB tendrá m fila y p columnas.
Cada elemento de la matriz de productos C resulta de un producto escalar entre un vector de fila en A y un vector de columna en B.

Hagamos ahora una multiplicación de matrices de 2 matrices en Python, usando NumPy.
Generaremos aleatoriamente 2 matrices de dimensiones 3 x 2 y 2 x 4.
Usaremos el   método np.random.randint () para generar los números.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
17
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 15, size=(3,2))
 
B = np.random.randint(0, 15, size =(2,4))
 
print("Matrix A:\n", A)
 
print("shape of A =", A.shape)
 
print()
 
print("Matrix B:\n", B)
 
print("shape of B =", B.shape)

Salida:

Nota: estamos configurando una semilla aleatoria usando 'np.random.seed ()' para hacer que el generador de números aleatorios sea determinista.
Esto generará los mismos números aleatorios cada vez que ejecute este fragmento de código. Este paso es esencial si desea reproducir su resultado en un momento posterior.

Puede establecer cualquier otro entero como semilla, pero sugiero establecerlo en 42 para este tutorial para que su salida coincida con las que se muestran en las capturas de pantalla de salida.

Ahora multipliquemos las dos matrices usando el  método np.matmul ()  . La matriz resultante debe tener la forma de 3 x 4.

1
2
3
4
5
C = np.matmul(A, B)
 
print("product of A and B:\n", C)
 
print("shape of product =", C.shape)

Salida:

Multiplicación entre 3 matrices

La multiplicación de las 3 matrices estará compuesta por dos operaciones de multiplicación de 2 matrices y cada una de las dos operaciones seguirá las mismas reglas que se discutieron en la sección anterior.

Digamos que estamos multiplicando 3 matrices A, B y C; y el producto es D = ABC.
Aquí, el número de columnas en A debe ser igual al número de filas en B y el número de filas en C debe ser igual al número de columnas en B.

La matriz resultante tendrá filas iguales al número de filas en A y columnas iguales al número de columnas en C.

Una propiedad importante de la operación de multiplicación de matrices es que  es asociativa .
Con la multiplicación de múltiples matrices, el orden de las operaciones de multiplicación individuales no importa y, por lo tanto, no produce resultados diferentes.

Por ejemplo, en nuestro ejemplo de multiplicación de 3 matrices D = ABC, no importa si realizamos AB primero o BC primero.

Ambos ordenamientos arrojarían el mismo resultado. Hagamos un ejemplo en Python.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 10, size=(2,2))
 
B = np.random.randint(0, 10, size=(2,3))
 
C = np.random.randint(0, 10, size=(3,3))
 
print("Matrix A:\n{}, shape={}\n".format(A, A.shape))
 
print("Matrix B:\n{}, shape={}\n".format(B, B.shape))
 
print("Matrix C:\n{}, shape={}\n".format(C, C.shape))

Salida:

Con base en las reglas que discutimos anteriormente, la multiplicación de estas 3 matrices debería producir una matriz resultante de forma (2, 3).
Tenga en cuenta que el método  np.matmul ()  acepta solo 2 matrices como entrada para la multiplicación, por lo que llamaremos al método dos veces en el orden que deseamos multiplicar, y pasaremos el resultado de la primera llamada como parámetro a la segunda.
(Encontraremos una mejor manera de lidiar con este problema en una sección posterior cuando introduzcamos el operador '@')

Hagamos la multiplicación en ambos órdenes y validemos la propiedad de asociatividad.

1
2
3
4
5
6
7
D = np.matmul(np.matmul(A,B), C)
 
print("Result of multiplication in the order (AB)C:\n\n{},shape={}\n".format(D, D.shape))
 
D = np.matmul(A, np.matmul(B,C))
 
print("Result of multiplication in the order A(BC):\n\n{},shape={}".format(D, D.shape))

Salida:

Como podemos ver, el resultado de la multiplicación de las 3 matrices sigue siendo el mismo si multiplicamos A y B primero, o B y C primero.
Por tanto, la propiedad de asociatividad queda validada.
Además, la forma de la matriz resultante es (2, 3) que está en las líneas esperadas.

Multiplicación de matrices NumPy 3D

Una matriz 3D no es más que una colección (o una pila) de muchas matrices 2D, al igual que una matriz 2D es una colección / pila de muchos vectores 1D.

Entonces, la multiplicación matricial de matrices 3D implica múltiples multiplicaciones de matrices 2D, que eventualmente se reduce a un producto escalar entre sus vectores de fila / columna.

Consideremos un ejemplo de matriz A de forma (3,3,2) multiplicada por otra matriz B 3D de forma (3,2,4).

1
2
3
4
5
6
7
8
9
import numpy as np
 
np.random.seed(42)
 
A  = np.random.randint(0, 10, size=(3,3,2))
 
B  = np.random.randint(0, 10, size=(3,2,4))
 
print("A:\n{}, shape={}\nB:\n{}, shape={}".format(A, A.shape,B, B.shape))

Salida:

La primera matriz es una pila de tres matrices 2D, cada una con forma (3,2) y la segunda matriz es una pila de 3 matrices 2D, cada una con forma (2,4).

La multiplicación de matrices entre estos dos implicará 3 multiplicaciones entre las matrices 2D correspondientes de A y B que tienen formas (3,2) y (2,4) respectivamente.

Específicamente, la primera multiplicación será entre A [0] y B [0], la segunda multiplicación será entre A [1] y B [1] y finalmente, la tercera multiplicación será entre A [2] y B [2 ].

El resultado de cada multiplicación individual de matrices 2D tendrá la forma (3,4). Por lo tanto, el producto final de las dos matrices 3D será una matriz de forma (3, 3, 4).

Démonos cuenta de esto usando código.

1
2
3
C = np.matmul(A,B)
 
print("Product C:\n{}, shape={}".format(C, C.shape))

Salida:

Alternativas a np.matmul ()

Además de 'np.matmul ()', hay otras dos formas de realizar la multiplicación de matrices: el   método np.dot () y el  operador '@' , cada uno de los cuales ofrece algunas diferencias / flexibilidad en las operaciones de multiplicación de matrices.

El método 'np.dot ()'

Este método se usa principalmente para encontrar el producto escalar de los vectores, pero si pasamos dos matrices 2-D, entonces se comportará de manera similar al método 'np.matmul ()' y devolverá el resultado de la multiplicación matricial de las dos matrices.

Veamos un ejemplo:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import numpy as np
 
# a 3x2 matrix
A = np.array([[8, 2, 2],
             [1, 0, 3]])
 
# a 2x3 matrix
B = np.array([[1, 3],
             [5, 0],
             [9, 6]])
 
# dot product should return a 2x2 product
C = np.dot(A, B)
 
print("product of A and B:\n{} shape={}".format(C, C.shape))

Salida:

Aquí, definimos una matriz de 3 × 2 y una matriz de 2 × 3 y su producto escalar arroja un resultado de 2 × 2 que es la multiplicación matricial de las dos matrices,
lo mismo que habría devuelto 'np.matmul ()'.

La  diferencia entre np.dot () y np.matmul ()  está en su funcionamiento en matrices 3D.
Mientras que 'np.matmul ()' opera en dos matrices 3D calculando la multiplicación de matrices de los pares correspondientes de matrices 2D (como se discutió en la última sección), np.dot () por otro lado calcula los productos punto de varios pares de filas vectores y vectores de columna de la primera y segunda matriz respectivamente.

np.dot () en dos matrices 3D A y B devuelve un producto de suma sobre el último eje de  A  y el penúltimo eje de B.
Esto no es intuitivo y no es fácilmente comprensible.

Entonces, si A tiene forma (a, b, c) y B tiene forma (d, c, e), entonces el resultado de np.dot (A, B) será de forma (a, d, b, e) cuyo elemento individual en una posición (i, j, k, m) está dado por:

1
dot(A, B)[i,j,k,m] = sum(A[i,j,:] * B[k,:,m])

Veamos un ejemplo:

1
2
3
4
5
6
7
8
9
import numpy as np
 
np.random.seed(42)
 
A  = np.random.randint(0, 10, size=(2,3,2))
 
B  = np.random.randint(0, 10, size=(3,2,4))
 
print("A:\n{}, shape={}\nB:\n{}, shape={}".format(A, A.shape,B, B.shape))

Salida:

Si ahora pasamos estas matrices al método 'np.dot ()', devolverá una matriz de forma (2,3,3,4) cuyos elementos individuales se calculan utilizando la fórmula dada anteriormente.

1
2
3
C = np.dot(A,B)
 
print("np.dot(A,B) =\n{}, shape={}".format(C, C.shape))

Salida:

Otra diferencia importante entre 'np.matmul ()' y 'np.dot ()' es que 'np.matmul ()' no permite la multiplicación con un escalar (se discutirá en la siguiente sección), mientras que 'np. dot () 'lo permite.

El operador

El operador @ introducido en Python 3.5, realiza la misma operación que 'np.matmul ()'.

Repasemos un ejemplo anterior de 'np.matmul ()' usando el operador @, y veremos el mismo resultado que se devolvió anteriormente:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 15, size=(3,2))
 
B = np.random.randint(0, 15, size =(2,4))
 
print("Matrix A:\n{}, shape={}".format(A, A.shape))
 
print("Matrix B:\n{}, shape={}".format(B, B.shape))
 
C = A @ B
 
print("product of A and B:\n{}, shape={}".format(C, C.shape))

Salida:

El operador '@' se vuelve útil cuando estamos realizando una multiplicación de matrices de más de 2 matrices.

Anteriormente, teníamos que llamar a 'np.matmul ()' varias veces y pasar sus resultados como parámetro a la siguiente llamada.
Ahora, podemos realizar la misma operación de una manera más simple (e intuitiva):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
17
18
19
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 10, size=(2,2))
 
B = np.random.randint(0, 10, size=(2,3))
 
C = np.random.randint(0, 10, size=(3,3))
 
print("Matrix A:\n{}, shape={}\n".format(A, A.shape))
 
print("Matrix B:\n{}, shape={}\n".format(B, B.shape))
 
print("Matrix C:\n{}, shape={}\n".format(C, C.shape))
 
D = A @ B @ C # earlier np.matmul(np.matmul(A,B),C)
 
print("Product ABC:\n\n{}, shape={}\n".format(D, D.shape))

Salida:

Multiplicación con un escalar (valor único)

Hasta ahora hemos realizado la multiplicación de una matriz con un vector u otra matriz. Pero, ¿qué sucede cuando realizamos una multiplicación de matrices con un valor escalar o numérico único?

El resultado de tal operación se obtiene multiplicando cada elemento de la matriz por el valor escalar. Por tanto, la matriz de salida tiene la misma dimensión que la matriz de entrada.

Tenga en cuenta que 'np.matmul ()' no permite la multiplicación de una matriz con un escalar. Esto se puede lograr usando el   método np.dot () o usando el  operador '*'.

Veamos esto en un ejemplo de código.

01
02
03
04
05
06
07
08
09
10
11
import numpy as np
 
A = np.array([[1,2,3],
             [4,5, 6],
             [7, 8, 9]])
 
B = A * 10
 
print("Matrix A:\n{}, shape={}\n".format(A, A.shape))
 
print("Multiplication of A with 10:\n{}, shape={}".format(B, B.shape))

Salida:

Multiplicación de matrices por elementos

A veces queremos multiplicar los elementos correspondientes de dos matrices que tienen la misma forma.

Esta operación también se denomina  Producto Hadamard. Acepta dos matrices de las mismas dimensiones y produce una tercera matriz de la misma dimensión.

Se puede lograr en Python llamando a la  función multiply () de NumPy  o usando el  operador '*' .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 10, size=(3,3))
 
B = np.random.randint(0, 10, size=(3,3))
 
print("Matrix A:\n{}\n".format(A))
 
print("Matrix B:\n{}\n".format(B))
 
C = np.multiply(A,B) # or A * B
 
print("Element-wise multiplication of A and B:\n{}".format(C))

Salida:

La única regla a tener en cuenta para la multiplicación por elementos es que  las dos matrices deben tener la misma forma .
Sin embargo, si falta una dimensión de una matriz, NumPy la  transmitirá  para que coincida con la forma de la otra matriz.

De hecho, la multiplicación de matrices con un escalar también implica la transmisión del valor escalar a una matriz de forma igual al operando de la matriz en la multiplicación.

Eso significa que cuando estamos multiplicando una matriz de forma (3,3) con un valor escalar 10, NumPy crearía otra matriz de forma (3,3) con valores constantes de 10 en todas las posiciones de la matriz y realizaría una multiplicación por elementos entre las dos matrices.

Entendamos esto con un ejemplo:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import numpy as np
 
np.random.seed(42)
 
A = np.random.randint(0, 10, size=(3,4))
 
B = np.array([[1,2,3,4]])
 
print("Matrix A:\n{}, shape={}\n".format(A, A.shape))
 
print(