Frontend Labs
⋯

21. Optimization & Backpropagation

Level: Expert You'll use this for: actually understanding how a neural network trains, instead of treating .fit()/loss.backward() as magic.

Gradient descent, from the ground up

To minimize a loss function L(w), repeatedly step in the direction opposite the gradient (the direction of steepest ascent, Ch. 13):

w_new = w_old - η · ∇L(w_old)

η (eta) is the learning rate — too large and you overshoot/diverge, too small and training crawls.

def gradient_descent(grad_fn, w0, lr=0.1, steps=50):
    w = w0
    for _ in range(steps):
        w = w - lr * grad_fn(w)
    return w

# Minimize f(w) = (w-3)^2, whose derivative is 2(w-3)
w_min = gradient_descent(grad_fn=lambda w: 2*(w-3), w0=0.0)
print(w_min)   # converges to ~3.0

Full worked backpropagation example (2 layers, by hand)

Network: x → [w1] → h → relu → [w2] → y_pred, loss L = (y_pred - target)².

# Forward pass
x, target = 2.0, 10.0
w1, w2 = 1.5, 2.0

h = w1 * x                 # h = 3.0
h_relu = max(0, h)         # h_relu = 3.0
y_pred = w2 * h_relu       # y_pred = 6.0
loss = (y_pred - target)**2   # loss = 16.0

# Backward pass (chain rule, Ch. 13, applied link by link)
dL_dy = 2 * (y_pred - target)          # dL/dy_pred = 2*(6-10) = -8.0
dy_dw2 = h_relu                          # dy_pred/dw2 = h_relu = 3.0
dL_dw2 = dL_dy * dy_dw2                  # -8.0 * 3.0 = -24.0

dy_dh_relu = w2                          # dy_pred/dh_relu = w2 = 2.0
dh_relu_dh = 1.0 if h > 0 else 0.0       # ReLU derivative = 1 (since h=3>0)
dL_dh = dL_dy * dy_dh_relu * dh_relu_dh  # -8.0 * 2.0 * 1.0 = -16.0

dh_dw1 = x                               # dh/dw1 = x = 2.0
dL_dw1 = dL_dh * dh_dw1                  # -16.0 * 2.0 = -32.0

print("dL/dw1 =", dL_dw1, " dL/dw2 =", dL_dw2)
# Update: w1_new = w1 - lr*dL_dw1, w2_new = w2 - lr*dL_dw2

This hand-computed example is literally what loss.backward() in PyTorch does automatically, for every parameter, across every layer, via a computation graph — reverse-mode automatic differentiation is just this chain-rule bookkeeping, done exactly, at scale.

Why deep networks need more than plain gradient descent

  • Vanishing/exploding gradients: chaining many derivatives through the chain rule means multiplying many terms together — if each is < 1, the product shrinks toward 0 exponentially with depth (vanishing); if each is > 1, it explodes. This motivates activation function choice (ReLU over sigmoid) and architectural fixes (residual/skip connections, normalization layers).
  • Momentum: accumulates a running average of past gradients to smooth out noisy steps and accelerate through consistent directions.
  • Adam: combines momentum with a per-parameter adaptive learning rate based on the running variance of gradients — the default optimizer for most modern deep learning.

Convexity, and why it matters

A function is convex if a straight line between any two points on it never dips below the curve — convex functions have exactly one global minimum, guaranteeing gradient descent finds the best answer. Most real deep learning loss landscapes are non-convex (many local minima and saddle points) — which is why training is empirically finicky and why techniques like momentum, learning-rate schedules, and good initialization matter so much in practice.

Lagrange multipliers (constrained optimization)

To minimize f(x) subject to a constraint g(x) = 0, form the Lagrangian L(x, λ) = f(x) - λ·g(x) and solve ∇L = 0. This is the mechanism behind Support Vector Machines (maximize margin subject to correct-classification constraints) and appears throughout constrained resource-allocation problems (e.g., "minimize latency subject to a fixed compute budget").

The learning-rate speed limit: η < 2/λ_max

Why does a learning rate that's too large diverge? Take the simplest loss, a 1-D bowl with curvature λ:

L(w) = ½ · λ · w²       L'(w) = λ·w       (minimum at w = 0)

update:  w_new = w - η·λ·w = (1 - η·λ) · w
Symbol Meaning
λ curvature, the second derivative L''. In n dimensions, an eigenvalue of the Hessian (Ch. 13)
η learning rate
1 - ηλ the factor the error is multiplied by every step

After t steps, w_t = (1 - ηλ)ᵗ · w₀. That's a geometric sequence (Ch. 6), so it converges if and only if |1 - ηλ| < 1, that is, 0 < η < 2/λ. With λ = 10:

η factor 1 - ηλ What the loss curve does
0.05 0.5 smooth, fast convergence
0.15 -0.5 converges, overshooting back and forth
0.19 -0.9 barely converges, violent oscillation
0.25 -1.5 diverges: the error grows 1.5× per step until you see NaN

In a real network every Hessian eigenvalue imposes its own limit, and the sharpest direction (λ_max) sets the speed limit for all of them. But the flattest direction (λ_min) then crawls at a factor of 1 - ηλ_min ≈ 1. The ratio κ = λ_max/λ_min, the condition number, measures how hard the problem is. Plain gradient descent needs on the order of κ steps. Feature standardization (Ch. 9 z-scores), BatchNorm/LayerNorm, momentum and Adam are all, at heart, ways to reduce κ or work around it. Researchers have observed that training tends to drive λ_max up until it hovers right at 2/η ("the edge of stability", Cohen et al., 2021), so this formula is a live dynamic in real training runs.

Momentum and Adam, every symbol explained

Momentum (the "heavy ball"):

v ← β · v + ∇L(w)
w ← w - η · v

v is a velocity that accumulates past gradients, an EWMA from Ch. 9 with β ≈ 0.9, so about 10 steps of memory. Along consistent directions, steps add up and the effective learning rate is about η/(1-β) = 10η. Across oscillating directions the opposite signs cancel. That accelerates the slow, flat directions without speeding up the sharp ones, which is exactly the fix for high κ.

Adam (Kingma & Ba, 2014), the default optimizer for transformers:

m ← β₁·m + (1-β₁)·g                 1st moment: EWMA of gradients
v ← β₂·v + (1-β₂)·g²                2nd moment: EWMA of squared gradients
m̂ = m / (1 - β₁ᵗ)                   bias correction
v̂ = v / (1 - β₂ᵗ)
w ← w - η · m̂ / (√v̂ + ε)
Symbol Typical value Meaning
g the current gradient ∇L(w)
m starts at 0 smoothed gradient: which way to go (momentum)
v starts at 0 smoothed squared gradient: how big gradients usually are for this parameter
β₁, β₂ 0.9, 0.999 memory of about 10 and about 1,000 steps
t 1, 2, 3, … step count, used only in bias correction
ε 1e-8 stops division by zero
m̂/√v̂ ≈ ±1 a unit-less step: a direction divided by its typical size

Why bias correction? m and v start at 0, so early on they're dragged toward 0. At step 1, m = 0.1·g, which is 10× too small. Dividing by 1 - 0.9¹ = 0.1 fixes it exactly.

Worked step 1, gradient g = 0.5:

m = 0.1 · 0.5   = 0.05          m̂ = 0.05    / (1 - 0.9)   = 0.5
v = 0.001 · 0.25 = 0.00025      v̂ = 0.00025 / (1 - 0.999) = 0.25
step = η · 0.5 / √0.25 = η · 1.0

Try g = 0.0005 instead and the step is still η · 1.0. Adam's step size is roughly η whatever the gradient's scale. That's why one learning rate works across embedding tables, attention weights and layer norms whose gradients differ by orders of magnitude, and why η in Adam reads directly as "the maximum change per parameter per step."

Lagrange multipliers, worked: how should flows share a network?

A problem from networking that shows what a Lagrange multiplier means. Two links, each with capacity 10 Mbit/s. Flow 0 crosses both links. Flow 1 uses only link A, and flow 2 uses only link B:

flow 0:  ──[ link A ]──[ link B ]──
flow 1:  ──[ link A ]──
flow 2:              ──[ link B ]──

Frank Kelly (1998) proposed choosing rates xᵢ to maximize total "utility" Σ log(xᵢ). The log gives diminishing returns, so starving any flow is very costly. This is proportional fairness:

maximize    log x₀ + log x₁ + log x₂
subject to  x₀ + x₁ ≤ 10      (link A, multiplier λ_A)
            x₀ + x₂ ≤ 10      (link B, multiplier λ_B)

The Lagrangian folds each constraint into the objective, weighted by a multiplier:

ℒ = log x₀ + log x₁ + log x₂ - λ_A·(x₀ + x₁ - 10) - λ_B·(x₀ + x₂ - 10)
Symbol Meaning
ℒ the Lagrangian: objective minus "price × overuse" for each constraint
λ_A, λ_B Lagrange multipliers, the price per unit of bandwidth on each link
∂ℒ/∂xᵢ = 0 at the optimum, no flow can gain by changing its rate

Set each partial derivative to zero:

∂ℒ/∂x₁ = 1/x₁ - λ_A = 0              ->  x₁ = 1/λ_A
∂ℒ/∂x₂ = 1/x₂ - λ_B = 0              ->  x₂ = 1/λ_B
∂ℒ/∂x₀ = 1/x₀ - λ_A - λ_B = 0        ->  x₀ = 1/(λ_A + λ_B)

Flow 0 pays the price of every link it crosses. By symmetry λ_A = λ_B = λ, both links are full, so x₀ + x₁ = 1/(2λ) + 1/λ = 10, giving λ = 0.15:

x₀ = 1/0.30 = 3.33 Mbit/s       (the 2-hop flow)
x₁ = x₂ = 1/0.15 = 6.67 Mbit/s  (the 1-hop flows)

Compare max-min fairness, where everyone gets 5. Proportional fairness gives the long flow less, because it uses twice the network resources. Kelly showed that TCP-style congestion control, where each flow backs off when it sees loss or delay (the "price") on its path, is a distributed algorithm solving this optimization. The multipliers are the congestion signals, and the links compute them without any central coordinator. This is also why long multi-hop TCP flows get less throughput than short ones in practice. ML uses the identical machinery: the SVM's support vectors are the training points whose Lagrange multipliers are non-zero.

Real-life engineering ties

  • AI: this chapter is the actual mechanism of model.fit() — knowing it lets you debug training instability (exploding loss, NaN gradients, stuck training) as a first-principles problem instead of randomly changing hyperparameters.
  • SWE/Network: gradient-based tuning shows up in auto-scaling controllers and adaptive rate limiters that continuously adjust a parameter to minimize an error signal (closely related to Ch. 29, control theory).
  • Security: adversarial examples (inputs crafted to fool an ML model) are literally found via gradient ascent on the model's own loss function with respect to the input instead of the weights — understanding backprop is a prerequisite for understanding adversarial ML attacks (Ch. 28 connects this to game theory).

More real-life examples:

  • The 2012 AlexNet result that kicked off the modern deep learning boom was, mathematically, nothing more than the gradient descent and backpropagation in this chapter run at then-unprecedented scale on GPUs — the math didn't change, the compute did.
  • Modern large language model training runs use carefully engineered learning-rate schedules (a warmup period followed by cosine decay) specifically to avoid the too-high-divergence and too-low-slowness failure modes described in this chapter.
  • ResNet (2015) introduced "skip connections" specifically to fix the vanishing-gradient problem in very deep networks — a direct, famous architectural response to the chain-rule multiplication problem explained above.
  • Researchers have physically demonstrated adversarial attacks by placing small stickers on a stop sign that reliably fool a vision model into classifying it as a speed-limit sign (Eykholt et al., 2018) — a real-world instance of the gradient-ascent-on-the-input attack.

Common mistake

Setting the learning rate too high "to train faster" — this is the single most common cause of a loss curve that diverges or oscillates instead of converging; always look at the loss curve shape, not just the final number.


Generated from real-life-maths-guide.md · 8 Oct 2026 16:36