English · Español
Break — Quita el término de momentum de SGD¶
🇪🇸 Quita el momentum (β = 0) de un optimizador SGD que estaba convergiendo en un valle estrecho. Observa cómo la pérdida zig-zaguea y converge más lento. La gráfica de la pérdida es el mejor profesor.
Objetivo: cualquier implementación de SGD-con-momentum. Usamos el lab del optimizador de Rosenbrock (lab/02-optimizers-on-rosenbrock.md) como laboratorio porque su pérdida anisotrópica hace el efecto dramático.
Hipótesis¶
El learner predice: "Poner β = 0 (sin momentum) en SGD sobre la función de Rosenbrock (a) requerirá muchos más pasos para alcanzar la misma pérdida, y (b) trazará una trayectoria visiblemente zigzagueante a través del valle curvo." La curva de pérdida seguirá decreciendo — no es catastrófico — pero la convergencia será 5-20× más lenta.
El break¶
En tu optimizador SGD-con-momentum:
class SGDMomentum:
- def __init__(self, params, lr=0.001, beta=0.9):
+ def __init__(self, params, lr=0.001, beta=0.0): # /break: momentum off
self.params = params
self.lr = lr
self.beta = beta
self.v = [np.zeros_like(p) for p in params]
def step(self, grads):
for i, (p, g) in enumerate(zip(self.params, grads)):
self.v[i] = self.beta * self.v[i] + g
p -= self.lr * self.v[i]
(Con beta = 0, la velocidad colapsa a v = g — es decir, SGD puro.)
Procedimiento de ejecución¶
uv run python -c "
import numpy as np
def rosenbrock(x):
return (1 - x[0])**2 + 100*(x[1] - x[0]**2)**2
def grad_rosenbrock(x):
dx = -2*(1 - x[0]) - 400*x[0]*(x[1] - x[0]**2)
dy = 200*(x[1] - x[0]**2)
return np.array([dx, dy])
for beta in (0.0, 0.9):
x = np.array([-1.5, 1.5])
lr = 1e-3
v = np.zeros_like(x)
for step in range(5000):
g = grad_rosenbrock(x)
v = beta * v + g
x = x - lr * v
if step in (0, 100, 1000, 4999):
print(f'beta={beta:.1f} step={step:5d} x={x} loss={rosenbrock(x):.4f}')
print()
"
Modo de fallo esperado¶
Con β = 0.9 (correcto):
beta=0.9 step= 0 x=[-1.4948 1.5006] loss= 4.2
beta=0.9 step= 100 x=[ 0.34 0.10 ] loss= 0.44
beta=0.9 step= 1000 x=[ 0.91 0.83 ] loss= 0.008
beta=0.9 step= 4999 x=[ 0.999 0.999 ] loss= 1e-6
Con β = 0 (roto):
beta=0.0 step= 0 x=[-1.4955 1.4994] loss= 4.6
beta=0.0 step= 100 x=[-0.78 0.69 ] loss= 3.2 <-- apenas se movió
beta=0.0 step= 1000 x=[ 0.13 0.02 ] loss= 0.76 <-- 100× más lento
beta=0.0 step= 4999 x=[ 0.65 0.42 ] loss= 0.12 <-- aún no converge
Firma cuantitativa: en el paso 1000, β=0.9 alcanza pérdida < 0.01; β=0 sigue por encima de 0.5. 50× diferencia en velocidad de convergencia.
Diagnóstico¶
Solo desde los logs:
- Grafica
x[0]a lo largo del tiempo para ambas ejecuciones. β=0 traza un patrón en sierra (zig-zag a través del valle parabólico); β=0.9 traza una curva suave. Si ves oscilación en coordenadas de pasos sucesivos, no tienes momentum (o tienes muy poco). - Grafica la norma del gradiente. Con β=0 el gradiente por paso es grande pero se cancela con el siguiente paso. Con momentum, la velocidad acumulada sigue moviéndose en la misma dirección.
Lección¶
En una superficie de pérdida anisotrópica, la dirección del gradiente alterna entre "a través del valle" (ruidosa, signo alternante) y "a lo largo del valle" (silenciosa, signo consistente). El SGD puro trata ambas igual y zigzaguea. Momentum promedia la componente alternante a cero y refuerza la componente consistente.
El arreglo es una línea: v = β v + g, con β = 0.9 como default canónico. Esta es la mejora individual más barata en la jerarquía de optimizadores y explica por qué cada optimizador moderno (Adam, AdamW, Lion) mantiene un término de velocidad de alguna forma.
Referencias¶
- Polyak, Some methods of speeding up the convergence of iteration methods, USSR Computational Mathematics, 1964 (el método heavy-ball original).
- Sutskever et al., On the importance of initialization and momentum in deep learning, ICML 2013 — confirmación empírica en redes profundas.