Mariano Rivera
version: enero 2018
Broadcasting es una estrategia de numpy para operar con arreglos mutidimensionles de diferentes dimensiones.
Por ejemplo, suponga que desamos multiplicar en escalar por vector :
Esto equivale, matemáticamente, a:
Es decir cada elemento del vector es multiplicado por el escalar . Es como si, en términos computacionales, se creara un vector virtual con réplicas del escalar:
donde denota el producto de Hadamard (elemento a elemento), no confundir con el producto punto (dot) o producto Tensorial
Recordemos que producto de Hadamard es el default en numpy. Es decir
El producto de Hadamard entre dos vectores se calcula mediante:
import numpy as np
# arreglo de 3 números enteros aleatorios distribucion uniforme [low, high)
x = np.random.randint(low=1,high=10, size=3)
y = np.random.randint(low=1,high=10, size=3)
print(x)
print(y)
print(x*y) # producto de Hadamard
[2 7 7]
[2 7 8]
[ 4 49 56]
Producto punto:
Esto es:
print(x.dot(y))
print(sum(x*y))
61
61
Regresemos al ejemplo que nos motivó, el producto de un escalar por un vector que es un ejemplo de broadcasting. Antes de explicar este proceso, explicaremos la convención usada por PYTHON y extendida en cómputo Tensorial que es ampliamente usado en implementaciones de Redes Neuronales de la modalidad de Aprendizaje Profundo (Deep Learning):
a) Una arreglo multidimensiosional tienen ejes (axes). Es decir, un vector tienen 1 eje, una matriz 2 ejes, y asi.
b) El tamaño de los ejes son la forma (shape). Asi una Matriz de renglones por columnas es, en la convención de Python, un Tensor de 2 ejes (tensor de orden 2) con forma de (m,n).
Ahora si, el broadcasting es un proceso automático implementado en las operaciones de numpy para eficientar los cálculos con arreglos multidimensionales donde, siempre que no haya ambiguedad, se procede como sigue:
De ser necesario, toma el arreglo de menor forma (shape) y agrega 1’s por la izquierda a la forma para hacerlos compatibles.
De ser necesario, replica el arreglo en los ejes (axes) con forma igual a 1, hasta que alcance la forma del arreglo de mayor forma.
Por ejemplo sumar X+Y
Asumamos X tiene shape ( 32, 256, 256) y Y tiene shape ( 30, 2 , 32, 256, 256)
Extiende los axes de X a (1,1, 32, 256, 256)
Replica X hasta alcanzar la shape de Y: ( 30, 2 , 32, 256, 256)
Suma elemento a elemento X+Y y el resultado tienen shape ( 30, 2 , 32, 256, 256)
El broadcasting se realiza en forma virtual, está implementado en memoria, por lo que es muy eficiente.
import numpy as np
import pprint
# arreglo de 3 números enteros aleatorios distribucion uniforme [low, high)
X = np.random.randint(low=1,high=10, size=(3,3))
Y = np.random.randint(low=1,high=10, size=(3,1))
Z = np.random.randint(low=1,high=10, size=(3))
print('X.shape =', X.shape)
print('Y.shape =', Y.shape)
print('Z.shape =', Z.shape)
pp.pprint('matriz de 3x3, X=')
pp.pprint(X)
pp.pprint('vector columna de tamaño 3, Y= ')
pp.pprint(Y)
pp.pprint('vector columna de tamaño 3, Z= ')
pp.pprint(Z)
X.shape = (3, 3)
Y.shape = (3, 1)
Z.shape = (3,)
'matriz de 3x3, X='
array([[4, 1, 2],
[8, 8, 5],
[3, 7, 6]])
'vector columna de tamaño 3, Y= '
array([[7],
[2],
[1]])
'vector columna de tamaño 3, Z= '
array([7, 2, 3])
Calcular
X es de (3,3), Y es (3,1) por lo que Y se replicará por columnas y se sumará:
print('X+Y=')
pp.pprint(X+Y) # suma elemento a elemento con Broadcasting
X+Y=
array([[11, 8, 9],
[10, 10, 7],
[ 4, 8, 7]])
Al vector Y no le es necesario añadir axes por la derecha (de hecho, numpy no lo hubiera hecho) pues su shape es (3,1).
Calcular
X es de (3,3) y Z es (3). Luego Z se hará de (1,3), se replicará por renglones y se sumará:
print('X+Z=')
pp.pprint(X+Z) # suma elemento a elemento con Broadcasting
X+Z=
array([[11, 3, 5],
[15, 10, 8],
[10, 9, 9]])
Y = np.array(range(0,4))
Y = Y.reshape((Y.shape[0],1))
Zt = np.array(range(0,4))
print('Y=\n', Y)
print('Zt =\n', Z)
Y=
[[0]
[1]
[2]
[3]]
Zt =
[0 1 2 3]
Uno es renglón y el otro columna, asi que funciona el bradcasting (esto es correcto, matemáticamente)
Y*Zt
array([[0, 0, 0, 0],
[0, 1, 2, 3],
[0, 2, 4, 6],
[0, 3, 6, 9]])
Expliquemos que se hizo, paso a paso:
Y.shape = (4,1)
Z.shape = (4)
luego agrega ejes necesarios
Z.shape = (1,4)
y replica para poder sumar
que son arreglos de
Ahora intentemos calcular el producto punto punto invirtiendo los factores :
Zt*Y
array([[0, 0, 0, 0],
[0, 1, 2, 3],
[0, 2, 4, 6],
[0, 3, 6, 9]])
¿Pero que es esto? es broadcasting, no es algebra lineal!
Debemos ser cuidadosos. Los pasos son muy similares al caso anterior, solo que ahora:
y el orden de los factores no altera el producto, al menos en este nivel: elemento a elemento.