CALCULUS / 9. OPTIMIZATION

Optimization — Gradient Descent & Beyond

Finding minima — where calculus meets machine learning


EXPLANATION

Optimization is the goal of training: find the parameters θ that minimize the loss function L(θ).

Critical points where ∇L = 0:
• Local minimum: ∇L=0, Hessian positive definite
• Local maximum: ∇L=0, Hessian negative definite
• Saddle point: ∇L=0, Hessian indefinite (some eigenvalues +, some -)

Deep neural networks have mostly saddle points, not local minima. This is actually OK — saddle points in high dimensions are usually easy to escape because there's always a descent direction.

Gradient Descent variants:
• Batch GD: use all data → exact gradient, slow
• SGD: use one sample → noisy but fast
• Mini-batch: use batch of 32-256 → best of both

Learning rate is the most important hyperparameter:
• Too large → overshoots, diverges
• Too small → too slow, gets stuck
• Adaptive methods (Adam) → adjust lr per parameter automatically

Adam = Momentum + RMSProp:
m = β₁m + (1-β₁)∇L         (first moment — momentum)
v = β₂v + (1-β₂)(∇L)²      (second moment — variance)
θ = θ - lr · m̂/√(v̂+ε)     (adaptive update)

DIAGRAM

Loss surface — 2D visualization:

  high loss
      ╲  saddle   ╱
       ╲    ×    ╱
        ╲       ╱
     min ×     × min
         └─────┘
  low loss (valley)

  Gradient descent paths:
  Large lr: zig-zags → overshoots valley
  Small lr: slow descent → takes forever
  Adam: adapts per-dimension → follows valley efficiently

  Vanishing vs exploding gradients:
  ||∇L|| → 0  (too small → no learning)
  ||∇L|| → ∞  (too large → diverges)
  Gradient clipping: clip ||∇L|| to max_norm

CODE

PYTHON
1import numpy as np
2import matplotlib.pyplot as plt
3
4np.random.seed(42)
5
6# ── Vanilla gradient descent ──────────────────────────────────────
7def rosenbrock(x, y, a=1, b=100):
8 """Classic optimization test banana-shaped valley."""
9 return (a - x)**2 + b*(y - x**2)**2
10
11def rosenbrock_grad(x, y, a=1, b=100):
12 df_dx = -2*(a-x) - 4*b*x*(y-x**2)
13 df_dy = 2*b*(y-x**2)
14 return np.array([df_dx, df_dy])
15
16# Gradient descent
17def gradient_descent(lr=0.001, steps=5000):
18 theta = np.array([-1.5, 1.5])
19 history = [theta.copy()]
20 for _ in range(steps):
21 g = rosenbrock_grad(*theta)
22 theta = theta - lr * g
23 history.append(theta.copy())
24 return np.array(history)
25
26# ── Adam optimizer from scratch ───────────────────────────────────
27def adam(lr=0.01, steps=2000, b1=0.9, b2=0.999, eps=1e-8):
28 theta = np.array([-1.5, 1.5])
29 m = np.zeros(2) # first moment
30 v = np.zeros(2) # second moment
31 history = [theta.copy()]
32
33 for t in range(1, steps+1):
34 g = rosenbrock_grad(*theta)
35 m = b1*m + (1-b1)*g
36 v = b2*v + (1-b2)*g**2
37 m_hat = m / (1-b1**t) # bias correction
38 v_hat = v / (1-b2**t)
39 theta = theta - lr * m_hat / (np.sqrt(v_hat) + eps)
40 history.append(theta.copy())
41
42 return np.array(history)
43
44hist_gd = gradient_descent(lr=0.001, steps=5000)
45hist_adam = adam(lr=0.01, steps=2000)
46
47print("Optimization results (target: [1.0, 1.0]):")
48print(f" GD final: {hist_gd[-1]}, loss={rosenbrock(*hist_gd[-1]):.6f}")
49print(f" Adam final: {hist_adam[-1]}, loss={rosenbrock(*hist_adam[-1]):.6f}")
50
51# ── Learning rate sensitivity ─────────────────────────────────────
52def simple_loss(w): return (w - 3)**2
53def simple_grad(w): return 2*(w - 3)
54
55print("
56Learning rate comparison (minimizing (w-3)²):")
57for lr in [0.01, 0.1, 0.5, 0.9, 1.1]:
58 w = 0.0
59 for _ in range(100):
60 w -= lr * simple_grad(w)
61 status = "converged" if abs(w-3) < 0.01 else "DIVERGED"
62 print(f" lr={lr}: w={w:.4f} [{status}]")
← PREV8. Integration