Por qué el descenso del gradiente zigzaguea y cómo lo soluciona Momentum
PLOT_STEPS = 55 x_ = np.linspace(-5, 5, 500) y_ = np.linspace(-2.2, 2.2, 500) X, Y = np.meshgrid(x_, y_) Z = pérdida(X, Y) fig = plt.figure(figsize=(16, 10), facecolor=”#FAFAF8″) gs = GridSpec(2, 3, figura=fig, hspace=0.45, wspace=0.38, izquierda=0.07, derecha=0.97, arriba=0.88, abajo=0.08) COLORS = { “gd”: “#E05C4B”, “mom_good”: “#3A7CA5”, “mom_large”: “#F4A536”, “contour”: “#D4C9B8”, “minima”: “#2A9D5C”, “inicio”: “#444444”, } PANEL_TITLES = [
“Vanilla Gradient Descent\nOscillates, slow (185 steps to converge)”,
“Momentum β = 0.90\nSmooth, fast (159 steps to converge)”,
“Momentum β = 0.99 (too large)\nOvershoots — never converges”,
]

trazado_rutas = [
path_gd[:PLOT_STEPS+1]ruta_mom_good[:PLOT_STEPS+1]ruta_mamá_grande[:PLOT_STEPS+1]]colores = [COLORS[“gd”]COLORES[“mom_good”]COLORES[“mom_large”]]# fila superior: paneles de trayectoria para col, (ruta, color, título) en enumerate(zip(paths_plot, colores, PANEL_TITLES)): ax = fig.add_subplot(gs[0, col]) ax.set_facecolor(“#F5F3EE”) niveles = np.geomspace(0.005, 3.5, 28) ax.contour(X, Y, Z, niveles=niveles, colores=COLORES[“contour”]anchos de línea = 0,7, alfa = 0,9) ax.plot (ruta[:, 0]camino[:, 1]color=color, lw=1.8, alfa=0.85, zorder=3) ax.scatter(ruta[:, 0]camino[:, 1]color=color, s=18, zorder=4, alfa=0.6) ax.scatter(*ruta[0]marcador=”o”, s=90, color=COLORES[“start”]zorder=5, etiqueta=”inicio”) ax.scatter(*ruta[-1]marcador=”*”, s=120, color=COLORES[“minima”]zorder=5, etiqueta=”fin”) ax.scatter(0, 0, marcador=”+”, s=200, color=COLORES[“minima”]anchos de línea=2.5, zorder=6) ax.set_xlim(-5, 5) ax.set_ylim(-2.2, 2.2) ax.set_title(title, fontsize=9.5, fontweight=”bold”, color=”#222″, pad=7, loc=”left”) ax.set_xlabel(“θ₁ (dirección lenta)”, fontsize=8, color=”#666″) ax.set_ylabel(“θ₂ (dirección rápida)”, fontsize=8, color=”#666″) ax.tick_params(labelsize=7, colours=”#888″) para el lomo en ax.spines.values(): spin.set_edgecolor(“#CCCCCC”) # abajo a la izquierda: curvas de pérdida (300 pasos completos) ax_loss = figura.add_subplot(gs[1, :2]) ax_loss.set_facecolor(“#F5F3EE”) rutas_completas = [path_gd, path_mom_good, path_mom_large]
etiquetas_completas = [“Vanilla GD (185 steps)”, “Momentum β=0.90 (159 steps)”, “Momentum β=0.99 (diverges)”]

para ruta, color, etiqueta en zip(full_paths, colores, full_labels): pérdidas = [loss(*p) for p in path]
rango_pasos = np.arange(len(ruta)) ax_loss.plot(rango_pasos, pérdidas, color=color, lw=2, etiqueta=etiqueta, alfa=0.9) ax_loss.axhline(0.001, color=”#999″, lw=1, ls=”–“, alfa=0.6) ax_loss.text(305, 0.001, “convergence\nthreshold”, fontsize=7, color=”#888″, va=”center”) ax_loss.set_yscale(“log”) ax_loss.set_xlim(0, STEPS) ax_loss.set_title(“Pérdida frente a paso de optimización (escala logarítmica, 300 pasos)”, fontsize=10.5, fontweight=”bold”, color=”#222″, loc=”left”) ax_loss.set_xlabel(“Step”, fontsize=9, color=”#666″) ax_loss.set_ylabel(“Loss f(θ)”, fontsize=9, color=”#666″) ax_loss.legend(fontsize=8.5, framealpha=0.6) ax_loss.tick_params(labelsize=8, colores=”#888″) para el lomo ax_loss.spines.values(): spin.set_edgecolor(“#CCCCCC”) # abajo a la derecha: panel de anotaciones ax_ann = fig.add_subplot(gs[1, 2]) ax_ann.set_facecolor(“#F5F3EE”) ax_ann.axis(“off”) annotation = ( “Reglas de actualización\n\n” “Vanilla GD\n” ” θ ← θ − α·∇L(θ)\n\n” “Momentum GD\n” ” v ← β·v + (1−β)·∇L(θ)\n” ” θ ← θ − α·v\n\n” “Intuición clave\n” ” v acumula gradientes pasados.\n” ” Las oscilaciones verticales se cancelan.\n” ” Pasos horizontales compuestos.\n\n” “Hiperparámetro β\n” ” β → 0: se comporta como GD\n” ” β = 0,9: dulce típico spot\n” ” β → 1 : se sobrepasa / diverge” ) ax_ann.text(0.05, 0.97, annotation, transform=ax_ann.transAxes, fontsize=8.8, va=”top”, ha=”left”, fontfamily=”monospace”, color=”#333″, linespaceing=1.7) fig.suptitle(“Impulso en descenso de gradiente”, fontsize=16, fontweight=”bold”, color=”#111″, y=0.95) plt.savefig(“momentum_explainer.png”, dpi=150, bbox_inches=”tight”, facecolor=fig.get_facecolor()) plt.show()