08

Retropropagación

¿Cómo se reparte la culpa del error entre miles de pesos?

9 min

Retropropagación

Con voz. Puedes pausar y saltar a cualquier paso.

Lección completa

Subtítulos

Ya sabemos corregir un peso restándole su pendiente. Pero una red tiene miles de pesos, en capas, y casi ninguno toca la salida. ¿Cómo sabe cada uno cuánto error le toca?

Voz

Sin voz: la lección avanza sola con subtítulos.

La voz de esta lección está en preparación: por ahora avanza sola con subtítulos.

0:00 / 0:00

Qué lograrás

  • Calcular, paso a paso y con números reales, el pase hacia delante, la pérdida y las culpas (gradientes) de una red pequeña.
  • Explicar la regla de la cadena como multiplicar la influencia de cada eslabón, y por qué la culpa de una capa se reutiliza hacia atrás.
  • Distinguir lo que calcula la retropropagación (el gradiente) de lo que hace el descenso del gradiente (mover los pesos).

Ideas clave

  1. El error final de una red se reparte entre miles de pesos escondidos en capas. Saber cuánta culpa tiene cada uno es el problema de la asignación de crédito.
  2. La regla de la cadena dice que la influencia total de un peso sobre el error es el producto de la influencia de cada eslabón del camino.
  3. La retropropagación recorre la red de la salida hacia la entrada, calcula la culpa de cada neurona y la reutiliza para la capa anterior: obtiene todo el gradiente en un solo viaje.
  4. La retropropagación solo calcula el gradiente. Quien corrige los pesos es el descenso del gradiente: w ← w − η · culpa.
  5. Es barata (en redes grandes, del orden de un par de pases hacia delante), pero exige guardar los valores del pase hacia delante, y la culpa puede atenuarse al cruzar muchas capas.

Lo que mucha gente cree

Creencia común La retropropagación es el mecanismo con el que la red corrige sus pesos.

Lo correcto En el uso actual, solo calcula cuánta culpa tiene cada peso (el gradiente), con un viaje hacia atrás. Mover los pesos es trabajo del descenso del gradiente (u otro optimizador), que usa esos números.

Compruébalo

Cuatro preguntas. Sin nota ni cuenta: sirven para ver si la idea quedó.

ConceptoUn compañero dice: «la retropropagación corrige los pesos de la red». ¿Qué es más exacto?
Concepto¿Por qué la retropropagación es mucho más barata que tantear los pesos uno por uno?
CálculoLa culpa de la neurona de salida es −0.2. Un peso de 0.5 une una neurona oculta con la salida, y la pendiente de la sigmoide de esa neurona oculta vale 0.25. ¿Cuál es la culpa de la neurona oculta?
AplicaciónUn equipo nota que entrenar un modelo ocupa mucha más memoria de GPU que usarlo, con el mismo tamaño. ¿Qué parte se debe a la retropropagación?

Por dentroPara quien quiere las fórmulas y el código. No hace falta para seguir el curso.

Las ecuaciones de la red

La red de la lección tiene entradas x1,x2x_1, x_2, dos neuronas ocultas, una de salida, activación sigmoide σ(z)=1/(1+e−z)\sigma(z)=1/(1+e^{-z}) y no usa sesgos:

z1=w1x1+w2x2,z2=w3x1+w4x2,aj=σ(zj)z_1 = w_1x_1 + w_2x_2,\quad z_2 = w_3x_1 + w_4x_2,\quad a_j=\sigma(z_j)
z=w5a1+w6a2,y^=σ(z),L=12(y^−t)2z = w_5a_1 + w_6a_2,\quad \hat y=\sigma(z),\quad L=\tfrac12(\hat y-t)^2

Con x=(1, 0.5)\mathbf x=(1,\,0.5), t=1t=1 y w=(0.6, −0.3, 0.7, 0.5, 0.8, −0.9)\mathbf w=(0.6,\,-0.3,\,0.7,\,0.5,\,0.8,\,-0.9) resulta y^≈0.460\hat y\approx 0.460 y L≈0.146L\approx 0.146. Los pesos de partida se eligieron de modo que cada cuenta mostrada con tres decimales se pueda repetir a mano, con las cifras ya redondeadas, sin discrepancias (el script verify-data.ts de la lección lo comprueba, y compara el gradiente con diferencias finitas).

La regla de la cadena, paso a paso

Si LL depende de y^\hat y, este de zz y este de w5w_5, la derivada de LL respecto de w5w_5 es el producto de las derivadas de cada eslabón:

∂L∂w5=∂L∂y^⋅∂y^∂z⋅∂z∂w5=(y^−t)⋅y^(1−y^)⋅a1\frac{\partial L}{\partial w_5}=\frac{\partial L}{\partial \hat y}\cdot\frac{\partial \hat y}{\partial z}\cdot\frac{\partial z}{\partial w_5}=(\hat y-t)\cdot\hat y(1-\hat y)\cdot a_1

La derivada de la sigmoide es σ′(z)=σ(z)(1−σ(z))\sigma'(z)=\sigma(z)(1-\sigma(z)), así que se calcula con la activación que ya se guardó. Los dos primeros factores forman la culpa de la neurona δ=∂L/∂z\delta=\partial L/\partial z. Hacia atrás, capa por capa:

δy=(y^−t) y^(1−y^),∂L∂w4+j=δy aj\delta_y=(\hat y-t)\,\hat y(1-\hat y),\qquad \frac{\partial L}{\partial w_{4+j}}=\delta_y\,a_j
δj=δy w4+j aj(1−aj),∂L∂w=δj xi\delta_j=\delta_y\,w_{4+j}\,a_j(1-a_j),\qquad \frac{\partial L}{\partial w}=\delta_j\,x_i

Con los números de la lección: δy=−0.540⋅0.248≈−0.134\delta_y=-0.540\cdot0.248\approx-0.134; ∂L/∂w5=−0.134⋅0.611≈−0.082\partial L/\partial w_5=-0.134\cdot0.611\approx-0.082; δ1=−0.134⋅0.8⋅0.238≈−0.026\delta_1=-0.134\cdot0.8\cdot0.238\approx-0.026 y ∂L/∂w1=δ1 x1≈−0.026\partial L/\partial w_1=\delta_1\,x_1\approx-0.026. Como w6<0w_6<0, δ2\delta_2 sale con signo contrario (+0.024+0.024). Observa que las culpas ∂L/∂w5\partial L/\partial w_5 y ∂L/∂w6\partial L/\partial w_6 no suman δy\delta_y: cada peso recibe la culpa de la neurona multiplicada por su propia señal, no una parte de ella.

En capas y en forma de matriz

Con W(l)W^{(l)} la matriz que lleva a(l−1)\mathbf a^{(l-1)} a z(l)\mathbf z^{(l)}, las mismas ideas se escriben para cualquier número de capas:

δ(l)=(W(l+1)⊤δ(l+1))⊙σ′(z(l)),∂L∂W(l)=δ(l) a(l−1)⊤,∂L∂b(l)=δ(l)\boldsymbol\delta^{(l)}=\big(W^{(l+1)\top}\boldsymbol\delta^{(l+1)}\big)\odot\sigma'(\mathbf z^{(l)}),\qquad \frac{\partial L}{\partial W^{(l)}}=\boldsymbol\delta^{(l)}\,\mathbf a^{(l-1)\top},\qquad \frac{\partial L}{\partial \mathbf b^{(l)}}=\boldsymbol\delta^{(l)}

Cada δ(l)\boldsymbol\delta^{(l)} se calcula una vez a partir de la capa siguiente: eso es lo que hace barato el método. Con ReLU, σ′\sigma' se reemplaza por 11 si z>0z>0 y 00 en otro caso.

Por qué es barata

Para una pérdida escalar y NN pesos, el modo inverso de la diferenciación automática (las técnicas para derivar programas de forma exacta; la retropropagación es su caso particular para redes) entrega las NN derivadas en un solo recorrido hacia atrás. El modo directo, o tantear con diferencias finitas, necesita un recorrido por peso. El gradiente completo cuesta una constante pequeña por el costo de evaluar la función: Baydin et al. (2018), citando a Griewank y Walther (2008), dan c<6c<6 y típicamente entre 2 y 3, y Griewank (2012) lo llama el «principio del gradiente barato».

En un transformer grande, Kaplan et al. (2020) cuentan unos 2N2N FLOPs por token hacia delante, con NN los parámetros sin contar los de embeddings, y observan que el pase hacia atrás cuesta aproximadamente el doble: de ahí su ≈6N\approx 6N FLOPs por token de entrenamiento. A cambio, hay que guardar las activaciones del pase hacia delante hasta usarlas: el propio artículo de 1986 señala que las salidas de las capas intermedias del pase hacia delante hacen falta para el pase hacia atrás.

Un límite: la culpa puede atenuarse

Como σ′(z)≤14\sigma'(z)\le \tfrac14, cada capa de sigmoides aporta un factor de a lo sumo 14\tfrac14 a la culpa: (14)n(\tfrac14)^n tras nn capas. Los pesos también multiplican en cada capa y podrían compensarlo, pero con pesos moderados el producto se encoge de forma exponencial. Por eso en redes profundas se prefieren otras activaciones, conexiones residuales y variantes como la LSTM, temas de las lecciones 12 y 13.

De dónde viene, con cuidado

El nombre y la fama son de 1986, pero la idea es más vieja y tuvo varios descubridores:

  • Linnainmaa. Su tesis de maestría (Universidad de Helsinki, 1970, en finés; resultados en inglés en BIT, 1976) estudia cómo se acumula el error de redondeo de un algoritmo y propone lo que hoy se llama modo inverso. Según Griewank (2012), él no lo presentó como un método para obtener gradientes de forma barata.
  • Werbos. Su tesis doctoral en estadística (Harvard, agosto de 1974) desarrolla las «derivadas ordenadas» y la «retroalimentación dinámica» para modelos de predicción. Él mismo cuenta (Werbos, 2006) que de las redes neuronales solo escribió unas palabras en el capítulo 5; Goodfellow et al. (2016) fechan en 1981 su propuesta de aplicarlo a entrenar redes neuronales.
  • Rumelhart, Hinton y Williams (Nature, 1986). Usan la misma matemática de esta lección: error 12∑(y−d)2\tfrac12\sum(y-d)^2, sigmoide, un pase hacia delante y uno hacia atrás que «propaga derivadas». Reconocen que David Parker y Yann LeCun descubrieron variantes de forma independiente. En el artículo, «back-propagation» nombra todo el procedimiento de aprendizaje (con la actualización Δw=−ε ∂E/∂w\Delta w=-\varepsilon\,\partial E/\partial w y una variante con término de aceleración, lo que hoy se llama momento); la distinción de hoy, retropropagación para el gradiente y otro algoritmo para aprender con él, es la que explican Goodfellow et al. (2016). Los autores cierran diciendo que el procedimiento, «en su forma actual, no es un modelo plausible del aprendizaje en los cerebros».

En código

import math

def sigma(z):
    return 1 / (1 + math.exp(-z))

X, T = (1.0, 0.5), 1.0

def forward(w):
    a1 = sigma(w[0] * X[0] + w[1] * X[1])
    a2 = sigma(w[2] * X[0] + w[3] * X[1])
    y = sigma(w[4] * a1 + w[5] * a2)
    return a1, a2, y, 0.5 * (y - T) ** 2

def backward(w):
    a1, a2, y, loss = forward(w)
    d_y = (y - T) * y * (1 - y)            # culpa de la neurona de salida
    d_1 = d_y * w[4] * a1 * (1 - a1)       # culpa de la oculta 1
    d_2 = d_y * w[5] * a2 * (1 - a2)       # culpa de la oculta 2
    grad = [d_1 * X[0], d_1 * X[1], d_2 * X[0], d_2 * X[1], d_y * a1, d_y * a2]
    return loss, grad

w = [0.6, -0.3, 0.7, 0.5, 0.8, -0.9]
loss, grad = backward(w)
print(round(loss, 3), [round(g, 3) for g in grad])
# 0.146 [-0.026, -0.013, 0.024, 0.012, -0.082, -0.097]

# comprobación: tantear cada peso (un pase hacia delante extra por peso)
eps = 1e-6
check = []
for k in range(6):
    w2 = w.copy()
    w2[k] += eps
    check.append((forward(w2)[3] - loss) / eps)
print([round(c, 3) for c in check])
# [-0.026, -0.013, 0.024, 0.012, -0.082, -0.097]

# un paso de descenso del gradiente: lo da el descenso, no la retropropagación
eta = 1.0
w = [wk - eta * g for wk, g in zip(w, grad)]
print(round(forward(w)[3], 3))
# 0.129

Lo que viene

Con datos reales el gradiente se calcula sobre un lote de ejemplos y no sobre uno solo, y optimizadores como el momento o Adam transforman ese gradiente antes de mover los pesos. Todos reciben el gradiente de la retropropagación. Es el tema de la lección 9.

Lente de decisión

Qué significa esta idea si tu trabajo es decidir, no programar.

  • Un paso de entrenamiento cuesta del orden de tres pases hacia delante: uno hacia delante y uno hacia atrás que cuesta el doble. Pide el costo por token de entrenamiento, no solo el de uso.
  • Entrenar exige guardar los valores intermedios del pase hacia delante: la memoria de la GPU, no solo el cómputo, limita qué modelo cabe. Pregunta cuánta memoria pide entrenar frente a usar.
  • Por retropropagación solo se entrena lo que es derivable. Una regla dura, una búsqueda en una base de datos o una decisión discreta en medio del sistema cortan el flujo de la culpa: ahí hacen falta otros métodos.
  • Casi nadie programa la retropropagación a mano: PyTorch y TensorFlow la derivan automáticamente. Las preguntas útiles son qué partes se entrenan y cuáles se congelan, porque congelar capas ahorra cómputo y memoria de gradientes.

Términos de la lección

Transcripción completa
ComentariosConversación pública de esta lección: dudas, datos por revisar, ideas.

Los comentarios viven en GitHub Discussions, en el repositorio público del curso; para escribir se necesita una cuenta de GitHub. Se cargan desde giscus.app al abrir esta sección. Las personas que revisan el curso los leen, y si un cambio se acepta, se aplica en los dos idiomas. Abrir el hilo en GitHub

Fuentes