BRAGA

02 Writing02-02 Notes02-02-223

Momentum (SGD with momentum)

Published 26 Oct 20268 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
vt+1=β vt+∇f(θt),θt+1=θt−η vt+1\mathbf{v}_{t+1} = \beta \, \mathbf{v}_t + \nabla f(\boldsymbol{\theta}_t), \qquad \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta \, \mathbf{v}_{t+1}
(1)

, where v0=0\mathbf{v}_0 = \mathbf{0}, η\eta is the learning rate and β∈[0,1)\beta \in [0, 1) is the momentum coefficient (typically 0.90.9)

Deep Breakdown

What it tells me (Situation Awareness): Momentum replaces "step along the current gradient" with "step along a running sum of recent gradients". The velocity v\mathbf{v} is an exponentially weighted accumulation: unrolled, vt+1=∑k=0tβk∇f(θt−k)\mathbf{v}_{t+1} = \sum_{k=0}^{t} \beta^k \nabla f(\boldsymbol{\theta}_{t-k}). Gradient components that point the same way step after step add up (up to a factor 1/(1−β)1/(1-\beta), i.e. 10×10\times for β=0.9\beta = 0.9), while components that flip sign — the zig-zag across a narrow valley — cancel out. The result is faster travel along gentle, consistent directions and damped oscillation along steep ones, which is exactly the failure mode of plain gradient descent on ill-conditioned problems. In stochastic training the averaging also smooths mini-batch noise, and the accumulated velocity helps roll through plateaus and away from saddle points. The physical picture is a heavy ball rolling downhill with friction 1−β1-\beta. In DA/DS: the default optimizer for training CNNs (SGD + momentum 0.9 + a learning-rate schedule), a building block of Adam (its first moment is a momentum term), training linear/logistic models with SGD on large data, matrix factorisation for recommenders, and fine-tuning when Adam generalises worse.

Flowchart

Mini-batchgradient g_tDecay oldvelocity: beta ·v_tAdd newgradient: v =beta · v + gUpdate: theta =theta - eta · vConsistentdirections:speeds upOscillatingdirections:cancel outNext mini-batch

Real-world use

Where it is used

Image models (ResNets trained with SGD, momentum 0.9, weight decay, cosine or step schedule); Adam/AdamW (first-moment estimate mtm_t is momentum with 1−β11-\beta_1 scaling); LARS/LAMB large-batch optimizers; online learning for ad click models; any SGD-based pipeline in scikit-learn (SGDClassifier uses plain SGD, while MLPClassifier(solver='sgd', momentum=0.9, nesterovs_momentum=True) exposes momentum directly).

Problem it solves

Plain gradient descent on an ill-conditioned loss zig-zags: the learning rate must be small enough for the steepest direction (η<2/L\eta \lt 2/L), so progress along the flattest direction is painfully slow — the number of steps grows like the condition number κ=L/μ\kappa = L/\mu. Stochastic gradients add noise that makes the path even more erratic.

How it solves the problem

The velocity is a low-pass filter on the gradient sequence. Along a consistent direction the effective step grows towards η/(1−β)\eta/(1-\beta); across an oscillating direction successive gradients have opposite signs and largely cancel. On quadratics, heavy-ball momentum with tuned β\beta and η\eta cuts the iteration count from order κ\kappa to order κ\sqrt{\kappa}.

Example in a business

A marketing team fits a conversion model (logistic regression, 10 unevenly scaled features, 5,000 sessions) with mini-batch SGD. With the same learning rate 0.020.02 and 5 epochs, plain SGD reaches log-loss 0.32940.3294, momentum 0.50.5 reaches 0.31210.3121, and momentum 0.90.9 reaches 0.30610.3061 (see the professional example). On a toy quadratic with condition number 25, plain GD needs 259 iterations, momentum 0.9 needs 145, Nesterov momentum 45, and tuned heavy-ball momentum only 29.

The formula, piece by piece

  • θt\boldsymbol{\theta}_t: parameters at step tt
  • ∇f(θt)\nabla f(\boldsymbol{\theta}_t): gradient (in practice a mini-batch estimate gt\mathbf{g}_t)
  • vt\mathbf{v}_t: velocity, the accumulated gradient history; starts at 0\mathbf{0}
  • β\beta: momentum coefficient — the fraction of the old velocity kept each step; β=0\beta = 0 recovers plain SGD
  • βvt\beta \mathbf{v}_t: decayed memory; a gradient from kk steps ago carries weight βk\beta^k (0.910≈0.350.9^{10} \approx 0.35, 0.950≈0.0050.9^{50} \approx 0.005)
  • 1/(1−β)1/(1-\beta): the effective memory length and maximum amplification (10 for β=0.9\beta = 0.9, 100 for 0.990.99)
  • η\eta: learning rate applied to the velocity, not to the raw gradient
  • Alternative (dampened) form used by some libraries and by Adam: vt+1=βvt+(1−β)gt\mathbf{v}_{t+1} = \beta \mathbf{v}_t + (1-\beta)\mathbf{g}_t, which is the same update with η\eta rescaled by 1/(1−β)1/(1-\beta)

The formula in words

"Keep a running velocity that remembers most of its previous value and adds the new gradient, then move the parameters along that velocity; directions the gradient keeps agreeing on build up speed, and directions it keeps flipping on cancel out." Tiny example: on f(x)=x2f(x) = x^2 with η=0.1\eta = 0.1, β=0.9\beta = 0.9 from x0=1x_0 = 1, momentum gives x=0.8,0.46,0.062x = 0.8, 0.46, 0.062 after three steps while plain GD gives 0.8,0.64,0.5120.8, 0.64, 0.512.

Critical Concepts

  • Nesterov accelerated gradient (NAG): evaluate the gradient at the look-ahead point: vt+1=βvt+∇f(θt−ηβvt)\mathbf{v}_{t+1} = \beta \mathbf{v}_t + \nabla f(\boldsymbol{\theta}_t - \eta\beta\mathbf{v}_t), θt+1=θt−ηvt+1\boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta\mathbf{v}_{t+1}. It "corrects before overshooting", damps oscillations better, and has optimal O(κlog⁡(1/ε))O(\sqrt{\kappa}\log(1/\varepsilon)) guarantees for smooth convex functions. On the condition-25 quadratic below it needs 45 iterations versus 145 for heavy ball at the same β\beta and η\eta.
  • Overshoot: momentum carries the iterate past the minimum (x4=−0.3086x_4 = -0.3086 in the tiny example) and the path spirals in. Too high a β\beta with too large an η\eta oscillates or diverges; the stability region on a quadratic is 0<ηλ<2(1+β)0 \lt \eta\lambda \lt 2(1+\beta).
  • Tuned heavy ball on quadratics: β∗=(L−μL+μ)2\beta^* = \left(\frac{\sqrt{L}-\sqrt{\mu}}{\sqrt{L}+\sqrt{\mu}}\right)^2, η∗=4(L+μ)2\eta^* = \frac{4}{(\sqrt{L}+\sqrt{\mu})^2}; convergence rate κ−1κ+1\frac{\sqrt{\kappa}-1}{\sqrt{\kappa}+1} per step versus κ−1κ+1\frac{\kappa-1}{\kappa+1} for GD.
  • Coupling with learning rate: effective step ≈η/(1−β)\approx \eta/(1-\beta); when raising β\beta from 0.9 to 0.99, lower η\eta roughly tenfold to keep the same scale.
  • Noise averaging: with mini-batches, the velocity averages about 1/(1−β)1/(1-\beta) gradients, reducing variance — similar in spirit to a larger batch.
  • Saddles and plateaus: accumulated velocity keeps moving through flat regions where the instantaneous gradient is tiny.
  • Momentum inside Adam: Adam's mtm_t is a dampened momentum; Adam then divides by vt\sqrt{v_t} (per-parameter scaling) — momentum handles direction smoothing, the second moment handles scale.

Step-by-step calculation (tiny example)

Minimise f(x)=x2f(x) = x^2 (gradient 2x2x) from x0=1x_0 = 1 with η=0.1\eta = 0.1, β=0.9\beta = 0.9, v0=0v_0 = 0.

Step 1 v1=0.9×0+2(1)=2v_1 = 0.9 \times 0 + 2(1) = 2; x1=1−0.1×2=0.8x_1 = 1 - 0.1 \times 2 = 0.8. (Plain GD: x1=0.8x_1 = 0.8.)

Step 2 v2=0.9×2+2(0.8)=1.8+1.6=3.4v_2 = 0.9 \times 2 + 2(0.8) = 1.8 + 1.6 = 3.4; x2=0.8−0.34=0.46x_2 = 0.8 - 0.34 = 0.46. (Plain GD: 0.640.64.)

Step 3 v3=0.9×3.4+2(0.46)=3.06+0.92=3.98v_3 = 0.9 \times 3.4 + 2(0.46) = 3.06 + 0.92 = 3.98; x3=0.46−0.398=0.062x_3 = 0.46 - 0.398 = 0.062. (Plain GD: 0.5120.512.)

Step 4: overshoot v4=0.9×3.98+2(0.062)=3.582+0.124=3.706v_4 = 0.9 \times 3.98 + 2(0.062) = 3.582 + 0.124 = 3.706; x4=0.062−0.3706=−0.3086x_4 = 0.062 - 0.3706 = -0.3086. The ball rolls past the minimum and will spiral back; the accumulated speed is both the strength and the risk.

Code examples

Beginner example (Python)

python
# f(x) = x^2, gradient 2x; compare plain GD with momentum
eta, beta = 0.1, 0.9
x_gd, x_m, v = 1.0, 1.0, 0.0
for t in range(1, 5):
    x_gd = x_gd - eta * 2 * x_gd
    v = beta * v + 2 * x_m
    x_m = x_m - eta * v
    print(f"step {t}: plain GD x = {x_gd:.4f} | momentum v = {v:.4f}, x = {x_m:.4f}")
# step 1: 0.8000 | v = 2.0000, x = 0.8000
# step 2: 0.6400 | v = 3.4000, x = 0.4600
# step 3: 0.5120 | v = 3.9800, x = 0.0620
# step 4: 0.4096 | v = 3.7060, x = -0.3086 (overshoot)

Beginner example (R)

r
eta <- 0.1; beta <- 0.9
x_gd <- 1; x_m <- 1; v <- 0
for (t in 1:4) {
  x_gd <- x_gd - eta * 2 * x_gd
  v <- beta * v + 2 * x_m
  x_m <- x_m - eta * v
  cat(sprintf("step %d: GD x = %.4f | momentum v = %.4f, x = %.4f\n", t, x_gd, v, x_m))
}

Professional example (Python)

python
import numpy as np

# Part 1: ill-conditioned quadratic f = 0.5 * (x^2 + 25 y^2), curvatures mu = 1, L = 25
A = np.diag([1.0, 25.0])


def iterations_to_converge(beta, eta, nesterov=False, tol=1e-3, max_iter=5000):
    theta = np.array([10.0, 1.0])
    v = np.zeros(2)
    for k in range(1, max_iter + 1):
        look = theta - eta * beta * v if nesterov else theta   # Nesterov: gradient at look-ahead
        g = A @ look
        v = beta * v + g
        theta = theta - eta * v
        if np.linalg.norm(theta) < tol:
            return k
    return max_iter


L, mu = 25.0, 1.0
beta_opt = ((np.sqrt(L) - np.sqrt(mu)) / (np.sqrt(L) + np.sqrt(mu))) ** 2
eta_opt = 4 / (np.sqrt(L) + np.sqrt(mu)) ** 2
print("iterations until ||theta|| < 1e-3 (condition number 25):")
print(f"  plain GD, eta = 0.035          : {iterations_to_converge(0.0, 0.035)}")
print(f"  momentum beta = 0.9, eta = 0.035: {iterations_to_converge(0.9, 0.035)}")
print(f"  Nesterov beta = 0.9, eta = 0.035: {iterations_to_converge(0.9, 0.035, nesterov=True)}")
print(f"  best plain GD, eta = 2/(L+mu)   : {iterations_to_converge(0.0, 2 / (L + mu))}")
print(f"  tuned heavy ball (beta = {beta_opt:.3f}, eta = {eta_opt:.3f}): "
      f"{iterations_to_converge(beta_opt, eta_opt)}")

# Part 2: mini-batch SGD with momentum on a logistic-regression conversion model
rng = np.random.default_rng(1)
n, p = 5000, 10
X = rng.normal(size=(n, p)) * np.linspace(0.5, 3.0, p)    # unevenly scaled features
w_true = rng.normal(size=p)
y = (rng.random(n) < 1 / (1 + np.exp(-X @ w_true))).astype(float)


def loss(w):
    z = X @ w
    return float(np.mean(np.logaddexp(0.0, z) - y * z))


def train(beta, eta=0.02, epochs=5, batch=64, seed=0):
    r = np.random.default_rng(seed)
    w, v = np.zeros(p), np.zeros(p)
    for _ in range(epochs):
        idx = r.permutation(n)
        for start in range(0, n, batch):
            b = idx[start:start + batch]
            prob = 1 / (1 + np.exp(-(X[b] @ w)))
            g = X[b].T @ (prob - y[b]) / len(b)
            v = beta * v + g
            w = w - eta * v
    return loss(w)


print("\nlogistic regression, 5 epochs of mini-batch SGD (eta = 0.02):")
for beta in (0.0, 0.5, 0.9):
    print(f"  beta = {beta}: final log-loss = {train(beta):.4f}")

Source: domain knowledge

#calculus#continuous-optimisation#advanced-optimisers-stochastic-and-adaptive

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close