37 · Ops elemento a elemento, broadcasting, reducciones
Vectoriza aritmética sobre arrays enteros sin un solo bucle Python — luego domina las reglas de broadcasting que convierten el centrado de media por columna, la normalización z-score y el softmax en una sola línea.
**Vectorización** significa expresar un bucle sobre elementos de array como una única operación de array — `a * 2` en lugar de `for x in a: x * 2`. **Broadcasting** es la regla de NumPy para expandir automáticamente arrays de shapes más pequeñas para que puedan combinarse con los más grandes: un vector de shape `(3,)` puede sumarse a una matriz `(100, 3)` porque NumPy 'emite' el vector sobre las 100 filas. Juntas, estas dos ideas te permiten escribir un paso completo de entrenamiento de ML — forward pass, loss, gradiente — en un puñado de líneas.
Sin esto:
Sin broadcasting, centrar la media de un dataset de shape `(1000, 20)` requeriría embaldosar explícitamente el vector de media en una matriz `(1000, 20)` antes de restar — seis líneas en lugar de una. Cada normalización, cálculo de puntuación de atención y actualización de batch-norm depende de que el broadcasting sea implícito y correcto.
Cuando escribes a + b con dos arrays NumPy del mismo shape, NumPy suma el elemento a[i] al elemento b[i] para cada i — en C, sin un bucle Python. Esto es vectorización: el bucle existe a nivel C donde corre a la velocidad del ancho de banda de memoria.
Lo mismo aplica a las operaciones unarias: np.exp(a), np.log(a), np.sqrt(a) aplican cada función a cada elemento de a y devuelven un nuevo array del mismo shape. Estas se llaman ufuncs (funciones universales) en NumPy.
Las reducciones colapsan uno o más ejes: a.sum() devuelve un escalar; a.sum(axis=0) colapsa el eje de filas y devuelve un valor por columna; a.mean(axis=1) colapsa el eje de columnas y devuelve un valor por fila. El parámetro axis es la dimensión que desaparece después de la reducción.
Para productos matriciales, usa np.dot(A, B) o el operador @ (A @ B). El operador @ fue añadido a Python 3.5 específicamente para la multiplicación matricial — llama a __matmul__ bajo el capó.
Python (in browser)
`argmax()` devuelve el índice del elemento más grande — aplicado a lo largo de `axis=1` te da la clase predicha para cada muestra en un batch de clasificación (la columna con la puntuación de logit más alta). Esta es una de las funciones NumPy más llamadas en los bucles de evaluación de ML.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
El broadcasting alinea los shapes **desde la derecha**: `(3, 4)` menos `(4,)` — los shapes coinciden en el eje final (ambos de tamaño 4) por lo que NumPy estira el vector `(4,)` sobre las 3 filas. Centrar la media de un dataset de esta manera es el primer paso de PCA y la normalización z-score — una resta, sin bucles, sin embaldosado.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
El operador `@` es la forma más limpia de expresar una transformación lineal. Cada capa completamente conectada en una red neuronal es `salida = W @ entrada + sesgo`. Para entradas en batch, transpón la matriz de pesos para que las dimensiones se alineen: `(batch, in_features) @ (in_features, out_features) → (batch, out_features)`.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Python (in browser)
Restar `x.max()` antes de `np.exp` es esencial — sin eso, `exp(1000)` desborda a `inf` y la división produce `nan`. El desplazamiento no cambia la salida (se cancela en numerador y denominador) pero mantiene cada exponente en un rango seguro. Este softmax de dos líneas — broadcasting de `x.max()` vía resta escalar, luego reducción con `.sum()` — está implementado de forma idéntica dentro de `F.softmax` de PyTorch.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Imprime `.shape` en cada array intermedio mientras aprendes broadcasting — las reglas son mecánicas pero los errores silenciosos (resultado incorrecto, sin excepción) son desagradables. Una vez que tengas un modelo mental de la alineación, el broadcasting se convierte en tu herramienta NumPy más poderosa.
¿Qué shape produce `np.array([1, 2, 3]) + np.array([[10], [20]])`?
La lección 3 de MML cubre las matrices como transformaciones lineales. El operador `@` implementa exactamente esto: `W @ x` aplica la transformación codificada en `W` al vector `x`. El broadcasting generaliza esto: `X @ W.T` aplica la misma transformación a cada fila de `X` simultáneamente — la misma matemática, procesada en batch sobre muestras.
- Las operaciones elemento a elemento (`+`, `*`, `np.exp`, `np.log`, ...) se aplican en C sobre todo el array sin bucle Python — esto es vectorización.
- El broadcasting alinea los shapes desde la derecha; las dimensiones de tamaño 1 se estiran para coincidir. El shape resultante es el máximo elemento a elemento de los shapes alineados.
- Las reducciones colapsan ejes: `a.sum(axis=0)` elimina el eje 0 (filas) dejando totales por columna. El argumento `axis` es el eje que desaparece.
- `A @ B` es la multiplicación matricial (`__matmul__`). Para capas de redes neuronales: `output = W @ input` para un vector único, o `output = X @ W.T` para un batch.
- El softmax numéricamente estable es `exp(x - x.max()) / exp(x - x.max()).sum()` — el desplazamiento se cancela algebraicamente pero previene el desbordamiento de `exp` en punto flotante.
Cada forward pass en una red neuronal es una cadena de `@` (multiplicaciones matriciales), activaciones elemento a elemento (`np.maximum(0, x)` para ReLU, `softmax` para la capa de salida) y reducciones (`.mean()` para la loss). El broadcasting maneja la dimensión de batch de forma transparente: `(batch, features) @ (features, hidden)` da `(batch, hidden)` con el mismo one-liner que el caso de muestra única.
Si lo quitas: Sin operaciones vectorizadas y broadcasting, cada paso de entrenamiento requeriría cuatro bucles Python anidados (batch × capa × fila × columna) — haciendo la experimentación prácticamente imposible a escala. La vectorización de NumPy es la razón por la que puedes iterar en un bucle de entrenamiento en segundos en un portátil en lugar de horas.