Momentum and Nesterov acceleration
Add momentum to gradient descent and see why acceleration improves the convergence rate.
Gradient descent on an ill-conditioned problem zig-zags: it bounces across the steep directions while crawling along the flat ones. Momentum averages successive gradients so the consistent flat-direction signal accumulates while the oscillating steep-direction noise cancels. Nesterov's variant tunes this into a provably faster rate.
Heavy-ball momentum
Momentum (Polyak's heavy ball) maintains a velocity vector that accumulates past gradients with a decay :
[1]The velocity is an exponentially weighted sum of gradients: . A heavy ball rolling downhill keeps moving in a consistent direction and is not deflected by every local wiggle, which is the physical picture controls.
Why it helps: the zig-zag cancels
On a quadratic with a steep direction (curvature ) and a flat direction (curvature ), plain gradient descent overshoots the steep axis and reverses sign each step, producing oscillation, while inching along the flat axis. Momentum averages those sign-flipping steep-direction gradients toward zero, while the flat-direction gradients all point the same way and build up. The effective step grows along the direction that matters and shrinks along the direction that oscillates.
Nesterov accelerated gradient
Nesterov's method evaluates the gradient at a look-ahead point, after the momentum step has been tentatively applied:
Computing the gradient where momentum is about to land, rather than where it currently sits, lets the method correct an overshoot before committing to it. This look-ahead is what turns heuristic speedup into a provable one.
The accelerated rate
For -strongly convex, -smooth with condition number , gradient descent needs iterations. Nesterov's method, with , achieves
needing only iterations. The dependence on the condition number drops from to , a large gain on ill-conditioned problems. This rate is optimal among methods that only use gradients: no first-order method can do better in the worst case[2].
Check it on arrays
import numpy as np
# Ill-conditioned quadratic: f(x) = 0.5 x^T A x, A = diag(mu, L)
mu, L = 1.0, 100.0
A = np.diag([mu, L])
grad = lambda x: A @ x
f = lambda x: 0.5 * x @ A @ x # f(x*) = 0
eta = 1.0 / L
kappa = L / mu
def gd(steps):
x = np.array([1.0, 1.0]); gaps = []
for _ in range(steps):
gaps.append(f(x)); x = x - eta * grad(x)
return np.array(gaps)
def nesterov(steps):
beta = (np.sqrt(kappa) - 1) / (np.sqrt(kappa) + 1)
x = np.array([1.0, 1.0]); v = np.zeros(2); gaps = []
for _ in range(steps):
gaps.append(f(x))
v = beta * v + grad(x - eta * beta * v)
x = x - eta * v
return np.array(gaps)
g_gd, g_nag = gd(400), nesterov(400)
# Iterations to reach gap < 1e-6
def first_below(g, tol=1e-6):
idx = np.where(g < tol)[0]
return idx[0] if len(idx) else len(g)
print("GD steps to 1e-6: ", first_below(g_gd))
print("Nesterov steps to 1e-6:", first_below(g_nag))
print("sqrt(kappa) =", round(np.sqrt(kappa), 1), " kappa =", kappa)Nesterov reaches the tolerance in far fewer steps than plain gradient descent, with the gap closing on the timescale rather than .
Exercises
Exercise 1. Show that the heavy-ball velocity unrolls into the exponentially weighted sum , starting from .
Solution
Step 1. The recurrence is with . Compute the first few terms. With :
Step 2. Then . The gradient from step has been multiplied by once per later step.
Step 3. Induction: assume . Then
The gradient at step carries weight , decaying geometrically with age.
Exercise 2. A constant gradient feeds the heavy ball every step (the consistent flat-direction signal). Show the velocity converges to , so the effective step is amplified by relative to plain gradient descent.
Solution
Step 1. With for all , the unrolled velocity from Exercise 1 is
reindexing .
Step 2. The finite geometric sum is .
Step 3. Since , as , so the velocity converges to
The update therefore moves by in the steady state, an amplification of over the plain step . With the consistent direction is effectively boosted tenfold.
Exercise 3 (code). On the ill-conditioned quadratic with , count the gradient-descent and Nesterov steps to reach a gap below , and check that Nesterov's count scales with rather than : the speedup factor should be on the order of .
Solution
import numpy as np
mu, L = 1.0, 100.0
A = np.diag([mu, L])
grad = lambda x: A @ x
f = lambda x: 0.5 * x @ A @ x
eta = 1.0 / L
kappa = L / mu
def gd(steps):
x = np.array([1.0, 1.0]); gaps = []
for _ in range(steps):
gaps.append(f(x)); x = x - eta * grad(x)
return np.array(gaps)
def nesterov(steps):
beta = (np.sqrt(kappa) - 1) / (np.sqrt(kappa) + 1)
x = np.array([1.0, 1.0]); v = np.zeros(2); gaps = []
for _ in range(steps):
gaps.append(f(x))
v = beta * v + grad(x - eta * beta * v)
x = x - eta * v
return np.array(gaps)
def first_below(g, tol=1e-6):
idx = np.where(g < tol)[0]
return int(idx[0]) if len(idx) else len(g)
n_gd = first_below(gd(2000))
n_nag = first_below(nesterov(2000))
speedup = n_gd / n_nag
assert speedup > np.sqrt(kappa) / 2 # speedup is on the sqrt(kappa) order
print("GD steps:", n_gd, " Nesterov steps:", n_nag,
" speedup:", round(speedup, 1), " sqrt(kappa):", round(np.sqrt(kappa), 1))Nesterov reaches the tolerance several times faster, with the step ratio on the order of , confirming the rate improves from to .
The next lesson makes the step size adapt per coordinate, the idea behind Adam.
References
- [1]Polyak, B. T. (1964). "Some methods of speeding up the convergence of iteration methods". USSR Computational Mathematics and Mathematical Physics, 4(5), 1-17. The heavy-ball momentum method.↩
- [2]Nesterov, Y. (2018). Lectures on Convex Optimization (2nd ed.), §2.2. Springer. The accelerated gradient method and the optimal first-order rate.↩