Every model from here on is fitted by gradient descent, so it is worth doing properly. Convergence rates from the condition number, why stochastic gradients have a noise floor, and where momentum and Adam come from — each derived, not quoted.
For \(f(\vw)=\tfrac12\vw^\top\mA\vw\) with \(\mA\succ0\), gradient descent is \(\vw_{t+1}=(\mI-\eta\mA)\vw_t\), and in the eigenbasis the coordinates decouple:
\[z_i^{(t)} = (1-\eta\lambda_i)^{t}\,z_i^{(0)}.\]
Three consequences, all from Phase A Lecture 11: stability needs \(\eta<2/\lambda_{\max}\); speed is governed by the slowest coordinate; and with the best fixed \(\eta\) the error contracts by \(\frac{\kappa-1}{\kappa+1}\) per step, where \(\kappa=\lambda_{\max}/\lambda_{\min}\). Let me finish that argument, because the optimal \(\eta\) was asserted rather than derived.
Phase A Lecture 13 proved the two facts that matter: the mini-batch gradient is unbiased, \(\E[\vg_{\mathcal B}]=\vg_{\text{full}}\), and its noise scale is \(\sigma/\sqrt{B}\). Two consequences follow that shape every training recipe you will ever write.
Look again at the zig-zag in the first figure. Along the valley the gradient is small but consistent in direction; across the valley it is large but alternates in sign. If we average recent gradients, the consistent component accumulates and the alternating one cancels.
Momentum fixes the direction. The other problem is that different coordinates need different step sizes — which is exactly the preconditioning idea from Phase A Lecture 11, where Newton's \(\mH^{-1}\vg\) divided each eigen-coordinate by its curvature. Since we cannot afford the Hessian, we approximate its diagonal from the gradients we have already seen.
Adam is the sensible default, not a universally superior method. Three things worth knowing:
A short list of things that actually go wrong, and what they look like.
nan from step one. Learning rate far too high, or a numerical issue in
the loss itself — check for log(0) and use the fused stable forms from Phase A Lecture 2.A quadratic loss has Hessian eigenvalues \(\{4, 0.04\}\).
1. \(\eta<2/\lambda_{\max}=2/4=0.5\).
2. \(\eta^\star = \frac{2}{\lambda_{\min}+\lambda_{\max}} = \frac{2}{4.04}=0.4950\). \(\kappa=4/0.04=100\), so the contraction is \(\frac{99}{101}=0.98020\) per step.
3. Need \(0.98020^t\le10^{-6}\), i.e. \(t\ge\frac{-6\ln10}{\ln0.98020}=\frac{-13.816}{-0.020} =689\) steps. (The approximation \(\frac{\kappa}{2}\ln\frac1\epsilon = 50\times13.82=691\) agrees.)
4. \(\sqrt\kappa=10\), so the optimal \(\beta=\left(\frac{10-1}{10+1}\right)^2 =
\left(\frac{9}{11}\right)^2 = 0.6694\), and the rate becomes \(\frac{9}{11}=0.8182\).
Steps needed: \(t\ge\frac{-13.816}{\ln 0.8182}=\frac{-13.816}{-0.2007}=69\) — a tenfold
reduction, matching \(\kappa\to\sqrt\kappa\).
Note the optimal \(\beta\) here (0.67) is well below the usual default of 0.9. The default corresponds to \(\sqrt\kappa\approx 39\), i.e. \(\kappa\approx1500\) — a reasonable guess for a neural network, and quite wrong for a well-conditioned problem. If momentum makes your training oscillate, \(\beta\) is too high for your \(\kappa\).
Prove that the mini-batch gradient with batch size \(B\), sampled uniformly with replacement, has covariance \(\frac{1}{B}\mS\) where \(\mS\) is the covariance of the per-example gradients.
Then show that sampling without replacement gives \(\frac{1}{B}\cdot\frac{n-B}{n-1}\mS\), and interpret the extra factor. What happens at \(B=n\)?
With replacement. Let \(\vg_1,\dots,\vg_n\) be the per-example gradients, with mean \(\bar{\vg}\) and covariance \(\mS = \frac1n\sum_i(\vg_i-\bar{\vg})(\vg_i-\bar{\vg})^\top\). Draw indices \(I_1,\dots,I_B\) independently and uniformly. Then \(\vg_{\mathcal B}=\frac1B\sum_{j}\vg_{I_j}\) is an average of \(B\) i.i.d. draws each with covariance \(\mS\), so by Phase A Lecture 13's scaling rule
\[\Cov(\vg_{\mathcal B}) = \frac{1}{B^2}\cdot B\,\mS = \frac{\mS}{B}. \qquad\blacksquare\]Without replacement. Now the draws are dependent — knowing \(I_1\) makes \(I_1\) less likely for \(I_2\). Expanding,
\[\Cov(\vg_{\mathcal B}) = \frac{1}{B^2}\Big[B\,\mS + B(B-1)\,\mathbf{C}\Big],\]where \(\mathbf{C}=\Cov(\vg_{I_1},\vg_{I_2})\) for two distinct draws. For sampling without replacement from a finite population, \(\mathbf{C}=-\frac{\mS}{n-1}\) — the standard negative correlation, which holds because the full sum \(\sum_i \vg_i\) is fixed, so drawing an above-average gradient makes the next draw below-average on average. Substituting,
\[\Cov = \frac{\mS}{B} - \frac{(B-1)}{B(n-1)}\mS = \frac{\mS}{B}\cdot\frac{n-B}{n-1}. \qquad\blacksquare\]Interpretation. The factor \(\frac{n-B}{n-1}\) is the finite population correction. Sampling without replacement is always at least as good, because you never waste a slot on a duplicate. The saving is negligible when \(B\ll n\) (the factor is \(\approx1\)) and total when \(B\) is a large fraction of \(n\).
At \(B=n\) the factor is exactly \(0\): sampling the whole dataset without replacement is the full-batch gradient, which has zero variance. Reassuringly, the formula knows this. Sampling with replacement at \(B=n\) still has variance \(\mS/n\), because a bootstrap sample is not the dataset.
Practical relevance: real training loops shuffle and partition into epochs, which is without-replacement sampling. For typical \(B/n\) around \(10^{-3}\) the distinction is irrelevant, and assuming independence (as the lecture's derivation does) is a good approximation.
Show that Adam's bias correction is exactly what is needed.
1. Unrolling the recursion from \(m_0=0\):
\[m_t = (1-\beta_1)\sum_{k=1}^{t}\beta_1^{\,t-k}\,g_k.\]Taking expectations and using \(\E[g_k]=g\) for all \(k\):
\[\E[m_t] = (1-\beta_1)\,g\sum_{k=1}^{t}\beta_1^{\,t-k} = (1-\beta_1)\,g\cdot\frac{1-\beta_1^{t}}{1-\beta_1} = \big(1-\beta_1^{t}\big)\,g,\]using the finite geometric sum (Phase A Lecture 6).
2. Dividing: \(\E[\hat m_t] = \E[m_t]/(1-\beta_1^t) = g\) ✓. Exactly unbiased at every \(t\), not merely asymptotically. The same computation gives \(\E[s_t]=(1-\beta_2^t)\E[g^2]\).
3. At \(t=1\) with the defaults: \(m_1=(1-0.9)g=0.1g\) and \(s_1=(1-0.999)g^2=0.001g^2\), so \(\sqrt{s_1}=0.0316|g|\). The uncorrected update would be
\[\eta\,\frac{m_1}{\sqrt{s_1}} = \eta\,\frac{0.1\,g}{0.0316\,|g|} = 3.16\,\eta\,\operatorname{sign}(g),\]a step 3.16 times larger than intended. With correction, \(\hat m_1 = g\) and \(\sqrt{\hat s_1}=|g|\), giving exactly \(\eta\).
Why it matters. The mismatch is worst precisely at the start, when the parameters are random and a 3× overshoot can push them somewhere they never recover from. The asymmetry between \(\beta_1=0.9\) and \(\beta_2=0.999\) is what creates it — the second moment warms up ten times more slowly than the first. It is also why warmup helps even with bias correction: correcting the bias in the mean does not fix the enormous variance of \(\hat s_t\) when it has only seen a handful of samples.
Coding. Reproduce the \(\kappa\) and \(\sqrt\kappa\) scaling laws empirically.
import numpy as np
def steps_to(kappa, method, tol=1e-6, T=2_000_000, noise=0.0, seed=0):
r = np.random.default_rng(seed)
lam = np.array([1.0, 1.0/kappa])
e0 = np.array([1.0, 1.0]); e = e0.copy(); v = np.zeros(2)
if method == 'gd':
eta, beta = 2/(lam.max()+lam.min()), 0.0
else:
sk = np.sqrt(kappa)
beta = ((sk-1)/(sk+1))**2
eta = (1+np.sqrt(beta))**2 / lam.max() * 0.9 # heavy-ball choice
for t in range(1, T+1):
g = lam*e + (noise*r.normal(size=2) if noise else 0.0)
v = beta*v + g
e = e - eta*v
if not np.isfinite(e).all(): return np.nan
if np.linalg.norm(e) <= tol*np.linalg.norm(e0): return t
return np.nan
print(f"{'kappa':>8} {'GD':>10} {'momentum':>10} {'ratio':>8} {'sqrt(k)':>9}")
res = []
for k in (10, 100, 1000, 10000):
a, b = steps_to(k,'gd'), steps_to(k,'mom')
res.append((k, a, b))
print(f"{k:8d} {a:10.0f} {b:10.0f} {a/b:8.1f} {np.sqrt(k):9.1f}")
ks = np.log([r[0] for r in res])
for j, nm in ((1,'GD'), (2,'momentum')):
ys = np.log([r[j] for r in res])
slope = np.polyfit(ks, ys, 1)[0]
print(f"log-log slope for {nm:9s}: {slope:.3f}")
print("\\nwith gradient noise = 0.01:")
for k in (10, 100, 1000):
a, b = steps_to(k,'gd',noise=0.01), steps_to(k,'mom',noise=0.01)
print(f" kappa={k:6d} GD {a} momentum {b}")
2 & 3. The step counts should come out near \(690\kappa/100\) for GD and near \(70\sqrt\kappa/10\) for momentum. The log-log slopes come out at approximately 1.00 for GD and 0.50 for momentum — direct confirmation of \(O(\kappa)\) versus \(O(\sqrt\kappa)\). At \(\kappa=10^4\) the ratio is about \(100\times\), which is why momentum is on by default everywhere.
4. With noise, both methods stop improving once they hit their noise floor, and neither
reaches \(10^{-6}\) at all — steps_to returns nan for the larger \(\kappa\).
The advantage of momentum largely disappears in the noise-dominated regime.
The reason is that momentum's benefit is entirely about the deterministic contraction, and once the error is at the noise floor the deterministic part has already done its job. Worse, momentum amplifies the noise: the accumulated \(\vv\) is an average of \(\approx\frac{1}{1-\beta}\) noisy gradients, so its variance is reduced by that factor but the effective step is amplified by the same factor, and the net effect on the stationary variance is roughly \(\frac{\eta}{1-\beta}\) instead of \(\eta\).
The practical reading: momentum helps most early in training, when the deterministic gradient dominates and the landscape is ill-conditioned. Late in training, when you are on the noise floor, what helps is decaying \(\eta\) or growing \(B\). This is exactly the shape of standard recipes — high learning rate with momentum early, aggressive decay late — and it is not a coincidence.
Next: we have been assuming squared error without justification. Lecture 04 derives losses from noise models — one principle that produces squared error, cross-entropy, Poisson loss and the robust alternatives, and tells you which to use when.