Skip to content

English · Español

Lab 03 — Implementar y graficar cinco programaciones de tasa de aprendizaje

🇪🇸 Cinco curvas en los mismos ejes: constante, escalonada, coseno, warmup→coseno, WSD. Las dibujas, las anotas, y eliges una para Fase 18.

Objetivo

Implementa cinco funciones de programación de LR en src/minigrad/schedules.py y grafícalas en un único chart. A ojo, identifica la que usarás en el bucle de entrenamiento de la Fase 18 y justifícala en 3-5 frases.

Planteamiento

Tareas

Parte A — Implementa las programaciones

import numpy as np

def constant(t, eta_0=1.0):
    return eta_0

def step_decay(t, eta_0=1.0, step=1000, gamma=0.1):
    return eta_0 * (gamma ** (t // step))

def cosine(t, eta_0=1.0, eta_min=0.0, T_total=10000):
    if t >= T_total:
        return eta_min
    return eta_min + 0.5 * (eta_0 - eta_min) * (1 + np.cos(np.pi * t / T_total))

def warmup_cosine(t, eta_0=1.0, eta_min=0.0, T_warm=500, T_total=10000):
    if t < T_warm:
        return eta_0 * (t / T_warm)
    return cosine(t - T_warm, eta_0, eta_min, T_total - T_warm)

def wsd(t, eta_0=1.0, eta_min=0.0, T_warm=500, T_stable=7000, T_total=10000):
    if t < T_warm:
        return eta_0 * (t / T_warm)
    if t < T_stable:
        return eta_0
    # Decaimiento lineal de eta_0 a eta_min sobre [T_stable, T_total]
    if t >= T_total:
        return eta_min
    frac = (t - T_stable) / (T_total - T_stable)
    return eta_0 * (1 - frac) + eta_min * frac

Todas toman t y devuelven η_t. Funciones puras — sin estado interno.

Parte B — Graficar

import matplotlib.pyplot as plt

T_total = 10000
ts = np.arange(T_total + 1)

schedules = {
    "constant": [constant(t) for t in ts],
    "step (γ=0.1 @ T=1000)": [step_decay(t) for t in ts],
    "cosine": [cosine(t, T_total=T_total) for t in ts],
    "warmup→cosine (T_warm=500)": [warmup_cosine(t, T_warm=500, T_total=T_total) for t in ts],
    "WSD (T_warm=500, T_stable=7000)": [wsd(t, T_warm=500, T_stable=7000, T_total=T_total) for t in ts],
}

plt.figure(figsize=(10, 6))
for name, vals in schedules.items():
    plt.plot(ts, vals, label=name, linewidth=2)
plt.xlabel("Step")
plt.ylabel("Learning rate (relative to η_0)")
plt.legend(loc="lower left")
plt.title("LR schedules (η_0 = 1.0, T_total = 10,000 steps)")
plt.grid(True, alpha=0.3)
plt.savefig("lr_schedules.png", dpi=150)

Parte C — Anotar

Para cada programación, añade 2-3 frases a experiments/04-lr-schedules/INTERPRETATION.md:

  • Constante: Cuándo la usarías. (Mayormente: como baseline.)
  • Decaimiento escalonado: Por qué aparecen "escalones" en la curva de pérdida. Cuándo la programación manual gana a la automática.
  • Coseno: Suave y sin parámetros (dado T_total). El default para entrenamiento sin warmup.
  • Warmup→Coseno: El default de los transformers. Argumento de condicionamiento de la Hessiana (según teoría 04).
  • WSD: Cuándo quieres publicar un checkpoint sin comprometerte con T_total por adelantado.

Parte D — Elige una para la Fase 18

En 3-5 frases, declara qué programación usarás en la Fase 18 (el bucle de entrenamiento de Mini-GPT). Justifica por: - El optimizador que estás usando (Adam — así que el warmup ayuda por la razón de la estimación de varianza). - El tamaño del modelo (pequeño — pero el argumento sigue aplicando). - La duración del entrenamiento (~10K pasos — suficiente para que importe el decaimiento en coseno).

La respuesta esperada: warmup→coseno con T_warm = 500. Confirma o argumenta lo contrario.

Parte E — Verificar propiedades

Añade unit tests en tests/test_schedules.py:

def test_constant_is_constant():
    assert constant(0) == constant(5000) == constant(99999)

def test_warmup_starts_at_zero():
    assert warmup_cosine(0, T_warm=500) == 0.0

def test_warmup_ends_at_peak():
    # En t = T_warm, la fase de warmup termina; cosine empieza en su pico
    assert abs(warmup_cosine(500, T_warm=500, T_total=10000) - 1.0) < 1e-9

def test_cosine_ends_at_eta_min():
    assert abs(cosine(10000, T_total=10000) - 0.0) < 1e-9

def test_step_decay_drops():
    assert step_decay(999) == 1.0
    assert step_decay(1000) == 0.1
    assert step_decay(1999) == 0.1
    assert step_decay(2000) == 0.01

def test_wsd_three_phases():
    s = wsd(t=100, T_warm=500, T_stable=7000)   # warmup
    assert 0 < s < 1
    assert wsd(3000, T_warm=500, T_stable=7000) == 1.0   # estable
    assert wsd(10000, T_warm=500, T_stable=7000, T_total=10000) == 0.0  # decaído

Entregable

  • src/minigrad/schedules.py — las cinco funciones.
  • tests/test_schedules.py — los unit tests de arriba (extendidos según sea necesario).
  • experiments/04-lr-schedules/lr_schedules.png — el chart.
  • experiments/04-lr-schedules/INTERPRETATION.md — anotaciones y la elección para la Fase-18.
  • manifest.json.

Aceptación

  • La gráfica se renderiza limpiamente con las cinco curvas en los mismos ejes.
  • Cada curva es visualmente distinta.
  • Las curvas warmup→cosine y WSD suben visiblemente desde 0 durante T_warm pasos.
  • Todos los unit tests pasan.
  • La "elección para la Fase 18" está justificada, no solo enunciada.

Escollos

  • T_total en cosine vs warmup→cosine. En warmup_cosine, el cosine corre sobre T_total - T_warm pasos (ya que los primeros T_warm pasos son warmup). Si usas T_total directamente en el cosine interno, la curva termina antes del paso T_total.
  • División entera. t // step es lo que quiere el step-decay. t / step daría un decaimiento continuo — no la forma de escalera.
  • Off-by-one en t = T_warm. Que el borde pertenezca a "warmup" o a "cosine" es una elección de convención. Sé consistente.
  • Eje log-LR al graficar. Algunas referencias muestran la LR en eje y logarítmico. Para una comparación cualitativa lado a lado, lineal es más claro.
  • Olvidar las tres fases de WSD. WSD tiene tres: warmup, estable, decaimiento. Saltarse la fase estable lo hace idéntico a warmup→cosine.

Ampliación

  • Añade una programación one-cycle (Smith): sube en rampa, alcanza pico en algún punto del medio, baja en rampa. Grafica.
  • Añade decaimiento-lineal-con-warmup (usado por algunos entrenamientos estilo BERT). Grafica.
  • Demuestra el efecto: entrena un modelo minúsculo (o usa una función 2D sintética) con warmup→cosine vs constant. Muestra cómo divergen las curvas de pérdida.

Fin de los labs de la Fase 4. Tiempo de escribir PHASE_04_REPORT.md y prepararse para la Fase 5.

Siguiente: Fase 05 — Probabilidad y teoría de la información.