Backpropagation es la regla de la cadena aplicada al grafo computacional. Una vez que entiendes la regla de la cadena, backprop no tiene secretos.
Imagina una fila de dominos. Si el último cae, puedes preguntarte: ¿cuál fue el primero que cayó? ¿Cuánto "contribuyó" cada domino intermedio al efecto final? Backpropagation es exactamente esa investigación, pero matemática.
Si tienes una cadena de funciones: L = f(g(x)), la derivada de L respecto a x es el producto de las derivadas en cada paso:
Ejemplo concreto:
x = 2
g(x) = x² = 4 → dg/dx = 2x = 4
f(g) = g + 10 = 14 → df/dg = 1
L = f(g(x)) = 14
¿Cuánto cambia L si x sube 1?
La regla de la cadena dice:
dL/dx = (dL/df) × (df/dg) × (dg/dx)
= 1 × 1 × 4
= 4
Verificación directa:
x=3: g=9, f=9+10=19 → L subió de 14 a 19 = 5
Hmm, no es exactamente 4... eso es porque la derivada es exacta
solo para cambios infinitesimalmente pequeños. Para x=2.001:
g = 2.001² = 4.004001 → L = 14.004001 → cambio ≈ 0.004001
dividido por 0.001 ≈ 4.0 ✅ (la derivada es la pendiente en ese punto)
En nuestro modelo, el grafo tiene miles de nodos (todos los pesos multiplicados por sus inputs). Backpropagation hace exactamente lo mismo que el ejemplo anterior, pero para todos esos nodos a la vez, en orden inverso.
Las flechas rosas van hacia atrás (backward). En cada paso multiplican el gradiente del nodo siguiente por el gradiente local de la operación. El nodo 'a' acumula dos contribuciones: una de L (×1) y otra de c a través de × (×3 = b).
Para propagar correctamente el gradiente de un nodo, necesitamos tener el gradiente de todos sus "descendientes" (los nodos que dependen de él). El orden topológico garantiza que siempre procesamos los nodos en el orden correcto: de L hacia los inputs.
El algoritmo de orden topológico recorre el grafo
de "hojas" (L, el output) hacia las "raíces" (a, b, inputs).
Grafo:
a → [×] → c → [+] → L
b → [×] ↑
a ────────────────┘
Orden topológico correcto para backward:
1. L (el output — siempre primero)
2. c (depende de a y b)
3. a, b (inputs — siempre al final)
El backward procesa en este orden.
Si procesáramos 'a' antes que 'c', no sabríamos aún que c
también contribuye al gradiente de 'a'.
En código, esto se implementa con DFS (búsqueda en profundidad):
- Visita recursivamente los _children de cada nodo
- Agrega el nodo a la lista DESPUÉS de visitar sus hijos
- Invierte la lista → orden topológico
Aquí puedes ver cómo el gradiente se usa para actualizar un parámetro. Tenemos L = (w × x) + w con x=3 fijo. El objetivo es llegar a L=0 ajustando w.
w -= lr × w.grad. Así el modelo mejora.