English · Español
Lab 03 — Gradiente que se desvanece, empíricamente¶
Objetivo: medir la decaída del gradiente a través del tiempo en una RNN vainilla. Confirmar el archivo de teoría 03 con números obtenidos en tu propia máquina.
Tiempo estimado: 60–90 minutos.
Prerrequisito: lab 02 (forward de la RNN) comprometido.
Lo que produces¶
Un directorio experiments/14-vanishing-grad/ que contiene:
bptt_norm.py— calcula la norma del gradiente en cada paso temporal (time step) vía BPTT para una secuencia sintética de longitud 50.decay.json— norma del gradiente vs paso temporal para la RNN vainilla y la GRU.decay.png— gráfica con eje y logarítmico de la norma del gradiente vs tiempo.manifest.json.README.md(2–3 párrafos).
El planteamiento¶
Tarea sintética: una secuencia de tokens de longitud 50. El modelo recibe la secuencia, ejecuta el forward, calcula una pérdida solo en el paso final (p. ej., cross-entropy contra un token objetivo), y luego se hace BPTT hacia atrás. En cada paso \(t\), registras:
La gráfica de \(g_t\) vs \(t\) en eje y logarítmico es el entregable. Forma esperada: \(g_t\) decae desde \(g_{50}\) (grande) hasta \(g_0\) (minúsculo). La pendiente en ejes logarítmicos es el exponente de decaída por paso.
La secuencia sintética¶
Puedes usar o:
- Opción A (más limpia): una cadena larga pronombre-verbo-separador alargada, p. ej.,
I work , you work , I work , you work , ...repetido durante 50 tokens. Objetivo en la posición 50: la forma verbal con la que el primer token (elIoriginal) debería concordar. La dependencia es a largo alcance por construcción. - Opción B (más simple): IDs de tokens aleatorios. Objetivo = un token fijo. La dependencia es artificial, pero el flujo del gradiente sigue siendo significativo.
Ambas valen para medir el desvanecimiento. La Opción A está más alineada con el tema de la gramática verbal; la Opción B es más rápida de montar.
TODOs¶
Bloque A — implementar BPTT para la RNN vainilla¶
Necesitas el gradiente de \(L_{50}\) respecto a cada \(h_t\). La recurrencia:
for t in range(1, T+1):
z_t = W_hh @ h[t-1] + W_xh @ x[t] + b_h
h[t] = np.tanh(z_t)
logits = W_ho @ h[T] + b_o
loss = cross_entropy(logits, target_id)
Backward:
dh[T] = (derivada de cross_entropy respecto a h[T])
for t in range(T, 0, -1):
# Retropropagar a través de tanh
dz_t = dh[t] * (1 - h[t]**2)
# Retropropagar a través de la recurrencia
dh[t-1] = W_hh.T @ dz_t
g[t-1] = np.linalg.norm(dh[t-1])
- Implementa esto en
bptt_norm.py. - Usa los mismos parámetros del modelo y la misma semilla que en el lab 02 para reproducibilidad.
- Ejecuta para \(T = 50\).
- Registra \(g[0], g[1], \ldots, g[49]\) en
decay.json.
Bloque B — repetir para la GRU¶
Calcula las mismas normas de gradiente para una GRU. El backward es más liado por las puertas. Pista de implementación: la mayor parte del gradiente fluye por la vía aditiva \((1 - z_t) \odot h_{t-1}\). El jacobiano \(\partial h_t / \partial h_{t-1}\) para la GRU es:
Si \(z_t\) es pequeño, esto se aproxima a la identidad — los gradientes fluyen sin contracción. Esta es la observación empírica clave del lab.
- Implementa el backward de la GRU en
bptt_norm.py(o en un archivo aparte). - Misma semilla, misma secuencia.
- Registra \(g[0], \ldots, g[49]\).
Bloque C — gráfica¶
decay.png:
- eje x: paso temporal \(t\) de 0 a 49.
- eje y: \(g_t\) en escala logarítmica.
- Dos líneas: RNN vainilla (rojo) y GRU (azul).
- Anota: "el gradiente de la RNN vainilla decae N órdenes de magnitud en 50 pasos; la GRU decae M".
Calcula N y M a partir de tus números; compromételos en README.md.
Bloque D — interpretar¶
En README.md, responde:
- Tasa de decaída de la RNN vainilla. ¿Cuántos órdenes de magnitud por cada 10 pasos? Compara con la predicción teórica de
theory/03-vanishing-gradient.md: con \(W_{hh}\) escalado en torno a 0.1, el radio espectral es pequeño, así que la decaída es rápida. Cita tu estimación del radio espectral. - Tasa de decaída de la GRU. ¿Cuántos órdenes por cada 10 pasos? Debería ser sustancialmente menor que la de la RNN vainilla — es la vía aditiva haciendo su trabajo.
- Implicación práctica. Si el gradiente del modelo en \(t=0\) es, digamos, \(10^{-15}\), ¿puede el modelo aprender algo de los primeros tokens? (No — está por debajo de la precisión FP32 e indistinguible de una actualización cero.) Enúncialo como el hecho práctico: las RNN vainilla no pueden aprender dependencias a largo alcance; no es un problema de hiperparámetros.
- Por qué esto motiva la attention. El gradiente de la attention desde una pérdida en la posición 50 hasta el embedding de entrada en la posición 0 fluye a través de un peso softmax, no de 50 multiplicaciones matriciales. La señal del gradiente se conserva en un paso. La Fase 15 lo deriva.
Bloque E — manifest¶
{
"experiment": "14-vanishing-grad",
"date": "YYYY-MM-DD",
"seed": 42,
"config": {
"sequence_length": 50,
"d_hidden": 32,
"init_scale_W_hh": 0.1,
"synthetic_target": "Option A"
},
"results_summary": {
"rnn_orders_of_decay_per_10steps": null,
"gru_orders_of_decay_per_10steps": null,
"rnn_spectral_radius_W_hh": null
},
"versions": {"python": "3.11.x", "numpy": "X.Y.Z"}
}
Restricciones¶
- Sin entrenamiento. Modelos con inicialización aleatoria. Estamos midiendo el flujo crudo del gradiente, no lo que haría el entrenamiento.
- Sin PyTorch. Implementa BPTT a mano. La parte más dura del lab — pero mecánicamente esclarecedora.
- Misma semilla para RNN y GRU. Si no, la comparación es ruido.
Condiciones de parada¶
Hecho cuando:
decay.pngmuestra dos curvas claramente separadas (la RNN más empinada que la GRU).README.mdresponde a las cuatro preguntas de interpretación.- Los números de órdenes-de-decaída están comprometidos en
manifest.json. - Puedes señalar la gráfica y explicar por qué la línea de la RNN es más empinada.
Trampas¶
- La GRU decae igual de rápido que la RNN. Probablemente \(z_t\) está cerca de 1 en todas partes — la GRU está actuando como una RNN vainilla. Comprueba: imprime media y desviación típica de los valores de \(z_t\) durante el forward. Si \(z_t > 0.9\) siempre, tu inicialización empujó las puertas a saturarse. Reduce la escala de inicialización.
- Todas las normas de gradiente son cero. Probablemente tanh saturó y mató todo. Reduce la escala de inicialización o revisa el forward por overflow.
- Todas las normas de gradiente son enormes. Probablemente \(W_{hh}\) tiene radio espectral > 1. Usa inicialización ortogonal con escala 0.9 para un resultado más limpio.
- Underflow numérico. \(g_0\) puede ser más pequeño que el mínimo FP32 (\(\sim 10^{-38}\)). Si ocurre, usa FP64 (
dtype=np.float64) y sustituye cualquier cero pornp.finfo(float).tinypara la gráfica logarítmica.
Cuándo consultar solutions/¶
Tras comprometer todos los archivos. La solución en solutions/03-vanishing-empirical-ref.md discute qué esperar con diferentes escalas de inicialización y muestra una gráfica de referencia.
El trabajo de labs de la Fase 14 está completo. Siguiente: /quiz 14, luego PHASE_14_REPORT.md, luego reflexión, luego proceed a la Fase 15.