Skip to content

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:

\[ g_t = \left\| \frac{\partial L_{50}}{\partial h_t} \right\|_2 \]

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 (el I original) 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:

\[ \frac{\partial h_t}{\partial h_{t-1}} \approx \text{diag}(1 - z_t) + (\text{términos pequeños de } \tilde h_t) \]

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. decay.png muestra dos curvas claramente separadas (la RNN más empinada que la GRU).
  2. README.md responde a las cuatro preguntas de interpretación.
  3. Los números de órdenes-de-decaída están comprometidos en manifest.json.
  4. 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 por np.finfo(float).tiny para 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.