Learn ML

Chapter 17 of 25

Optimizers

Smarter ways to walk downhill

22 min read

Motivation

Plain gradient descent works, in the sense that it's guaranteed to move downhill. But "guaranteed to eventually get there" and "gets there in a reasonable amount of time" are very different promises. Real loss surfaces have narrow ravines, wildly different sensitivities in different directions, and long, nearly flat plateaus — and plain gradient descent, armed with nothing but the current gradient and a single fixed learning rate, handles all of these badly. It oscillates in ravines, crawls across plateaus, and needs its one learning rate to somehow be right for every parameter simultaneously, which is rarely possible.

Every optimizer in this chapter exists to fix one specific weakness in the one before it. Understanding that chain of fixes — not just memorizing formulas — is what makes the whole family click into place, and it's exactly why Adam, the optimizer nearly everyone reaches for by default, looks the way it does.

A Real-Life Scenario

Picture someone hiking down a steep, narrow canyon toward a river at the bottom. If they only ever step in the exact direction the ground slopes right under their feet, they'll zigzag wildly back and forth across the narrow canyon walls, making painfully slow net progress toward the river even though every single step is technically "downhill." A more experienced hiker instead keeps some momentum from their recent direction of travel, smoothing out the zigzag into a more direct path forward — and if they also start favoring paths where the ground has been gently sloped before (rather than steep and jagged), they get down even faster and with much less wasted motion.

That's the whole story of this chapter: momentum smooths the zigzag, and adaptive per-parameter learning rates favor the gentle, informative directions over the steep, jittery ones.

Building the Intuition

  • Every optimizer here builds on the same skeleton: compute a gradient, then adjust the parameters by some function of that gradient. The differences are all in what extra information gets tracked and used.
  • Momentum-based methods remember a running average of past gradients, smoothing out noisy zigzagging.
  • Adaptive methods (AdaGrad, RMSProp, Adam) give every individual parameter its own effective learning rate, based on how large that parameter's gradients have historically been.
  • Adam simply combines both ideas — momentum's smoothing and RMSProp's per-parameter adaptivity — into one optimizer, which is why it dominates as the default choice today.

The Math Behind It

Batch, Stochastic, and Mini-Batch Gradient Descent

The Three Variants

Batch Gradient Descent:

θθηθ1Ni=1N(fθ(x(i)),y(i))\theta \leftarrow \theta - \eta \nabla_\theta \frac{1}{N}\sum_{i=1}^{N} \ell\big(f_\theta(\mathbf{x}^{(i)}), y^{(i)}\big)

Stochastic Gradient Descent (SGD):

θθηθ(fθ(x(i)),y(i))for one randomly chosen i\theta \leftarrow \theta - \eta \nabla_\theta\, \ell\big(f_\theta(\mathbf{x}^{(i)}), y^{(i)}\big) \quad \text{for one randomly chosen } i

Mini-Batch Gradient Descent:

θθηθ1Bibatch(fθ(x(i)),y(i))\theta \leftarrow \theta - \eta \nabla_\theta \frac{1}{B}\sum_{i \in \text{batch}} \ell\big(f_\theta(\mathbf{x}^{(i)}), y^{(i)}\big)

Where: NN is the full dataset size and BNB \ll N is the mini-batch size (typically 32, 64, 128, or 256).

Trade-offs between gradient descent variants
VariantAdvantagesDisadvantages
Batch GDStable, accurate gradient directionExtremely slow per update on large datasets; needs the entire dataset in memory
SGDFast updates; noise can help escape shallow minima/saddlesVery noisy gradient estimates; loss curve fluctuates heavily; poor hardware utilization
Mini-Batch GDGood balance of speed and stability; efficient on vectorized/GPU hardwareRequires choosing a batch size (an extra hyperparameter)
A terminology overload worth knowing

Mini-batch gradient descent is used almost universally in practice. When practitioners casually say "SGD," they very often actually mean mini-batch gradient descent, or one of its momentum/adaptive variants below — be aware of this common overload in papers and frameworks.

Momentum

Momentum
vt=βvt1+(1β)θL(θt1),θt=θt1ηvt\mathbf{v}_t = \beta \mathbf{v}_{t-1} + (1-\beta)\nabla_\theta L(\theta_{t-1}), \qquad \theta_t = \theta_{t-1} - \eta \mathbf{v}_t

Where: vt\mathbf{v}_t is the "velocity" — an exponentially weighted moving average of past gradients — and β[0,1)\beta\in[0,1) (typically 0.9) controls how much past gradients are remembered.

Intuition: imagine a ball rolling down the loss surface. Instead of responding only to the current gradient, momentum accumulates a velocity that smooths out noisy gradient directions and helps the optimizer keep moving through flat regions or shallow local minima, much like physical momentum carries a rolling ball past small dips.

Gradient Descent Path With and Without MomentumIllustration coming soon
Momentum smooths the noisy, oscillating path of plain SGD (red) into a more direct trajectory toward the minimum (green), particularly in narrow, ravine-shaped loss surfaces.

Nesterov Accelerated Gradient (NAG)

vt=βvt1+(1β)θL(θt1ηβvt1),θt=θt1ηvt\mathbf{v}_t = \beta \mathbf{v}_{t-1} + (1-\beta)\nabla_\theta L(\theta_{t-1} - \eta\beta\mathbf{v}_{t-1}), \qquad \theta_t = \theta_{t-1}-\eta\mathbf{v}_t

Standard momentum computes the gradient at the current position, then applies the accumulated velocity. Nesterov momentum instead computes the gradient at the position the velocity is about to move to — a "look-ahead" step — giving the optimizer a chance to correct course before overshooting rather than after. This typically yields faster, more stable convergence than plain momentum.

AdaGrad

AdaGrad
Gt=Gt1+(θL(θt1))2,θt=θt1ηGt+ϵθL(θt1)G_t = G_{t-1} + \big(\nabla_\theta L(\theta_{t-1})\big)^2, \qquad \theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{G_t}+\epsilon}\odot\nabla_\theta L(\theta_{t-1})

Where: GtG_t accumulates the sum of squared gradients for each parameter individually, and ϵ\epsilon is a small constant preventing division by zero.

Intuition: AdaGrad gives each parameter its own adaptive learning rate — parameters that have historically received large gradients get their effective learning rate shrunk, while parameters that have received small or infrequent gradients keep a relatively larger effective learning rate. This is especially useful for sparse features updated only occasionally.

AdaGrad's fatal flaw

Because GtG_t only ever accumulates and never shrinks, AdaGrad's effective learning rate decreases continuously throughout training and can eventually become so small that learning effectively stops, even if the model hasn't yet converged. This directly motivates RMSProp and Adam.

RMSProp

st=βst1+(1β)(θL(θt1))2,θt=θt1ηst+ϵθL(θt1)\mathbf{s}_t = \beta \mathbf{s}_{t-1} + (1-\beta)\big(\nabla_\theta L(\theta_{t-1})\big)^2, \qquad \theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{\mathbf{s}_t}+\epsilon}\odot\nabla_\theta L(\theta_{t-1})

RMSProp fixes AdaGrad's core problem by replacing the ever-growing sum GtG_t with an exponentially decaying moving average st\mathbf{s}_t (with decay controlled by β\beta, typically 0.9). Because old gradients are gradually "forgotten," the effective learning rate no longer shrinks all the way to zero, and training can keep adapting throughout the entire run.

AdaDelta

AdaDelta extends RMSProp one step further by also maintaining a moving average of past parameter updates (not just gradients), and uses the ratio of these two moving averages to size each update. This removes the need to manually specify a global learning rate η\eta at all — a nice property in theory, though in practice Adam has become far more popular thanks to its strong empirical performance and simpler, more intuitive hyperparameters.

Adam (Adaptive Moment Estimation)

Adam combines Momentum's idea (tracking a moving average of gradients) with RMSProp's idea (tracking a moving average of squared gradients) into one optimizer — currently the default choice for training most modern neural networks, including MLPs.

Adam
mt=β1mt1+(1β1)gt,vt=β2vt1+(1β2)gt2\mathbf{m}_t = \beta_1 \mathbf{m}_{t-1} + (1-\beta_1)\mathbf{g}_t, \qquad \mathbf{v}_t = \beta_2 \mathbf{v}_{t-1} + (1-\beta_2)\mathbf{g}_t^2m^t=mt1β1t,v^t=vt1β2t(bias correction)\hat{\mathbf{m}}_t = \frac{\mathbf{m}_t}{1-\beta_1^t}, \qquad \hat{\mathbf{v}}_t = \frac{\mathbf{v}_t}{1-\beta_2^t} \qquad \text{(bias correction)}θt=θt1ηv^t+ϵm^t\theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{\hat{\mathbf{v}}_t}+\epsilon}\odot\hat{\mathbf{m}}_t

Where: gt=θL(θt1)\mathbf{g}_t = \nabla_\theta L(\theta_{t-1}); typical defaults are β1=0.9\beta_1=0.9, β2=0.999\beta_2=0.999, ϵ=108\epsilon=10^{-8}.

Why bias correction is needed: since m0=v0=0\mathbf{m}_0=\mathbf{v}_0=\mathbf{0}, the raw moving averages are strongly biased toward zero during the first few steps of training, when tt is small. Dividing by (1β1t)(1-\beta_1^t) and (1β2t)(1-\beta_2^t) exactly corrects this initialization bias, which matters most early in training.

A frequently asked interview question

"Why is Adam so popular?" Mention that it combines momentum's smoothing of noisy gradients with RMSProp's per-parameter adaptive learning rates, includes bias correction for early-training stability, and works well across a very wide range of problems with minimal hyperparameter tuning — a robust default.

AdamW

Standard Adam, when combined with L2 regularization implemented as an added penalty term inside the loss, interacts poorly with Adam's adaptive per-parameter learning rates — effectively weakening the regularization for parameters with large accumulated gradients. AdamW fixes this by applying weight decay as a separate, direct step (θtθtηλθt1\theta_t \leftarrow \theta_t - \eta\lambda\theta_{t-1}) rather than folding it into the gradient computation, decoupling it from Adam's adaptive scaling. AdamW is now the standard choice in most modern deep learning practice, particularly for Transformer-based models.

Nadam and Lion (Brief Notes)

Nadam (Nesterov-accelerated Adam) applies the same look-ahead idea used in Nesterov momentum to Adam's momentum term, aiming to combine Adam's adaptive learning rates with Nesterov's improved convergence behavior. It typically performs comparably to, or slightly better than, standard Adam.

Lion ("EvoLved Sign Momentum") is a more recent optimizer, discovered via automated search over optimizer designs, that uses only the sign of a momentum-smoothed gradient to update parameters — rather than the gradient's raw magnitude — combined with decoupled weight decay similar to AdamW. It uses noticeably less memory than Adam, since it doesn't track a second moment, and has shown strong results on some large-scale vision and language models, though it's less thoroughly battle-tested than Adam/AdamW across the full breadth of deep learning tasks.

Interactive: Watch Learning Rate Choice Play Out

The optimizers in this chapter all still depend on getting the learning rate roughly right — momentum and adaptivity smooth the path, but they don't rescue a wildly mis-scaled step size. Revisit the same bowl-shaped descent from earlier in this course and see convergence, divergence, and oscillation play out directly.

Learning Rate Explorer

Pick a learning rate and step through gradient descent on a bowl-shaped loss curve. See how the same underlying update rule behaves completely differently depending on this one number.

Step 0

Try stepping through and watch how it behaves.

Comparison Table

Optimizer comparison
OptimizerKey IdeaTypical Use Today
SGD (mini-batch)Plain gradient step per mini-batchBaseline; sometimes still used with momentum for CV tasks
MomentumExponential moving average of gradientsCommon addition to plain SGD
NesterovLook-ahead gradient before applying momentumSlight improvement over plain momentum
AdaGradPer-parameter rate from accumulated squared gradientsSparse-feature problems; rarely used for deep nets today
RMSPropExponential moving average of squared gradientsCommon in RNN training
AdaDeltaRMSProp without a manual global learning rateRarely used today
AdamMomentum + RMSProp + bias correctionDefault choice for most deep learning tasks
AdamWAdam with decoupled weight decayStandard for Transformers and most modern large models
NadamAdam + Nesterov look-aheadOccasional alternative to Adam
LionSign-based update with decoupled weight decayEmerging alternative for large-scale training

Implementing It

import numpy as np
 
def L(theta): return theta[0]**2 + 10*theta[1]**2
def grad_L(theta): return np.array([2*theta[0], 20*theta[1]])
 
def run_sgd(steps=50, lr=0.05):
    theta = np.array([5.0, 5.0])
    for _ in range(steps):
        theta -= lr * grad_L(theta)
    return theta
 
def run_momentum(steps=50, lr=0.05, beta=0.9):
    theta = np.array([5.0, 5.0]); v = np.zeros(2)
    for _ in range(steps):
        g = grad_L(theta)
        v = beta*v + (1-beta)*g
        theta -= lr * v
    return theta
 
def run_adam(steps=50, lr=0.1, b1=0.9, b2=0.999, eps=1e-8):
    theta = np.array([5.0, 5.0]); m = np.zeros(2); v = np.zeros(2)
    for t in range(1, steps+1):
        g = grad_L(theta)
        m = b1*m + (1-b1)*g
        v = b2*v + (1-b2)*g**2
        m_hat = m / (1 - b1**t)
        v_hat = v / (1 - b2**t)
        theta -= lr * m_hat / (np.sqrt(v_hat) + eps)
    return theta
 
print("Final theta (SGD):     ", run_sgd())
print("Final theta (Momentum):", run_momentum())
print("Final theta (Adam):    ", run_adam())
print("True minimum: [0. 0.]")

The toy loss L(θ)=θ02+10θ12L(\theta) = \theta_0^2 + 10\theta_1^2 is deliberately shaped like a narrow ravine — the second coordinate has 10x the curvature of the first — which is exactly the kind of loss surface where plain SGD zigzags and momentum-based or adaptive methods visibly do better. Try running all three and comparing how many steps each needs to get close to [0, 0].

A strong practical default

Use Adam or AdamW with default hyperparameters (η103\eta \approx 10^{-3}, β1=0.9\beta_1=0.9, β2=0.999\beta_2=0.999) as a strong first choice for training an MLP. Only switch to plain SGD with momentum if there's a specific reason — matching a known reference result, or observing better final generalization after careful tuning, which sometimes shows up in computer vision.

Key Takeaways

Key Takeaways
  • Mini-batch gradient descent is the practical standard, balancing gradient stability against computational efficiency.
  • Momentum smooths noisy gradients using an exponential moving average; Nesterov improves on this with a look-ahead correction.
  • AdaGrad introduces per-parameter adaptive learning rates but suffers from a monotonically shrinking effective learning rate.
  • RMSProp fixes this using an exponentially decaying average of squared gradients.
  • Adam combines momentum and RMSProp with bias correction and is the most widely used default optimizer; AdamW improves on it with decoupled weight decay.

Common Mistakes

Assuming a fancier optimizer rescues a bad learning rate

Momentum and adaptive methods smooth and rescale gradients, but they don't eliminate the need to choose a reasonable base learning rate η\eta — a wildly mis-scaled η\eta can still cause divergence or painfully slow training with any of these optimizers.

Forgetting Adam's bias correction step when implementing it from scratch

Skipping the (1β1t)(1-\beta_1^t) and (1β2t)(1-\beta_2^t) division makes early training updates artificially small, since mt\mathbf{m}_t and vt\mathbf{v}_t start at zero and take many steps to "warm up" without correction.

Interview Corner

Quick Check
1. What core weakness of AdaGrad does RMSProp fix?
2. Why does Adam need bias correction during the first few training steps?

Where This Shows Up in Practice

Nearly every deep learning framework ships Adam or AdamW as a one-line default optimizer, and for good reason — the chain of ideas in this chapter (smoothing via momentum, adaptivity via RMSProp-style scaling, stability via bias correction) addresses the real, practical failure modes of plain gradient descent on the messy, non-convex loss surfaces described earlier in this course. Choosing an optimizer is rarely the deciding factor between a good and a great model, but choosing a badly mismatched one can easily prevent training from working at all.