Optimization · foundations Section 1.5 · item 4 of 25

Gradient
descent

Stand somewhere on a surface you cannot see. Feel which way is steepest downhill. Take a step. Repeat. You never know where the bottom is and you never need to — you only ever need the slope where you're standing.

Every neural network ever trained was trained by this. The elaborations — momentum, Adam, schedules — are all corrections to the same three-line loop, and they exist because the surface is rarely as friendly as it sounds.

The update

θθαf(θ)

Parameters minus learning rate times gradient. The minus sign is the whole algorithm: the gradient points uphill, so you go the other way.

Drop a ball and see what happens

Click anywhere on the surface to place the starting point, then step. Four landscapes, one algorithm, one number to tune. Three of the four are specifically the cases that make optimization hard.

path minimum
0.180
Iteration0
Loss
‖gradient‖
Click the surface to place a start point.

On the ravine, try 0.09 and then 0.10. The difference between a working model and a diverged one is often that small.

01The idea

Why the gradient, and why negative

Zoom in far enough on any smooth function and it looks like a plane. The gradient is that plane's slope — a vector whose direction is steepest ascent and whose length is how steep. Reverse it and you have the best local guess at "downhill".

The derivation, briefly

f(θ + d) ≈ f(θ) + ∇f(θ) · d

To decrease f as much as possible with a small step d, make the dot product as negative as possible. A dot product is minimized when the two vectors point in exactly opposite directions — so d ∝ −∇f.

Note the "locally". Steepest descent is optimal for an infinitesimal step and has no claim on anything beyond that. The ravine in the hero is precisely a case where the locally steepest direction points almost perpendicular to where you actually need to go.

What each piece contributes

∇f(θ)
Direction and magnitude of steepest increase. Computed by backpropagation in a network, by hand or autodiff elsewhere.
Turn around. This is the entire distinction between gradient descent and gradient ascent.
α
Learning rate — how far to trust the local approximation. The single most important hyperparameter in machine learning.
Overwrite and repeat. Nothing from the previous step is retained by plain gradient descent, which is exactly what momentum changes.
The gradient shrinks near a minimum, which gives you automatic braking. Steps are large where the surface is steep and small where it flattens out, so the algorithm decelerates as it arrives without being told to. That's also why a plateau is so damaging — a flat region produces tiny gradients and therefore tiny steps, and progress stalls without anything appearing to be wrong.
02The loop

Three lines, and the decisions around them

# the entire algorithm
theta = initialize()
for step in range(max_steps):
    g = grad(loss, theta)          # the only expensive part
    theta = theta - lr * g
    if norm(g) < tol:
        break

Initialization

Zeros work for linear and logistic regression and fail completely for neural networks — every neuron in a layer would compute the same thing forever. Networks need random initialization scaled to the layer width: He for ReLU, Xavier for tanh.

Stopping

Small gradient norm, small change in loss, a step budget, or — in practice, almost always — validation performance ceasing to improve. Training loss alone is the wrong stopping signal for anything you intend to deploy.

Cost per step

One gradient evaluation. Backpropagation computes it for roughly twice the cost of a forward pass regardless of parameter count, which is the fact that makes training large networks possible at all.

Notice what the algorithm never has. It has no map of the surface, no knowledge of where the minimum is, and no memory of anywhere it has been. All it gets is the slope underfoot. That the method works as well as it does on functions with billions of parameters is genuinely surprising, and section 10 is about why.
03Learning rate

The one number that decides everything

Too small and you'll still be training next week. Too large and the step overshoots the valley, lands higher than it started, and each iteration makes things worse. The gap between those is often less than a factor of two.

Same surface, same start, four learning rates · log scale · computed live

Reading a loss curve

slow decay
Learning rate too small. Increase by 3× and try again.
fast then flat
Roughly right, possibly needs decay to squeeze out the last of it.
bouncing
Too large. It's making progress but overshooting each valley.
rising / NaN
Far too large. Divergence. Cut by 10× immediately.
flat from step 0
Not a learning rate problem. Check the gradient is reaching the parameters at all.

The theory, for a quadratic

With Hessian eigenvalues between λmin and λmax, gradient descent converges if and only if:

α < 2 ⁄ λmax

The largest curvature sets the speed limit, but the smallest sets how fast you actually make progress along the shallow direction. When those two differ by a factor of a hundred, you are forced to crawl along the direction that matters in order to stay stable along the one that doesn't. That is the ravine, stated in eigenvalues.

The learning rate finder is worth the two minutes. Train for a few hundred steps while increasing the learning rate exponentially, and plot loss against it. You get a curve that descends, bottoms out, and then explodes. Pick a value roughly one order of magnitude below where it explodes. This is far more reliable than guessing and much cheaper than a grid search.
04How much data

Batch, stochastic, mini-batch

The gradient of the loss over a dataset is the average of the per-example gradients. You are free to estimate that average from a subset — and doing so turns out to be better, not merely cheaper.

VariantExamples per stepCharacter
Batch GDAll of themExact gradient, smooth monotone descent, one update per epoch. Unusable past moderate data sizes
Stochastic GDOneVery noisy, very fast per step, needs a decaying learning rate to settle
Mini-batch32–512, typicallyThe universal default. Enough averaging to be stable, small enough to be fast, and sized to fill the GPU
The noise is a feature. A stochastic gradient is an unbiased but wrong estimate of the true one, and that error acts like a random kick. It shakes the iterate out of sharp narrow minima and off saddle points, and the minima it settles into tend to be flatter — which correlates with better generalization. Batch gradient descent on a neural network would be both slower and worse.
Batch size and learning rate move together. Larger batches give less noisy gradients, so you can take larger steps — the common heuristics are to scale the learning rate linearly or with the square root of the batch size. Change one without the other and a configuration that worked will stop working, which is a common surprise when moving to more GPUs.

Vocabulary that trips people up: an iteration is one parameter update, an epoch is one full pass through the training data. With 50,000 examples and a batch size of 100, one epoch is 500 iterations. Papers report both, and they are not interchangeable.

05Bad geometry

Why it's harder than the picture suggests

Ill-conditioning

The Hessian's condition number κ = λ_max / λ_min measures how elongated the bowl is. Plain gradient descent contracts the error by roughly (κ−1)/(κ+1) per step, so a condition number of 1,000 means you make about a tenth of a percent of progress per iteration. This is the dominant practical obstacle, and the hero's ravine is a mild version.

Saddle points

Points where the gradient vanishes but which are minima in some directions and maxima in others. In high dimensions they vastly outnumber local minima — a critical point needs every eigenvalue positive to be a minimum, and that gets exponentially unlikely as dimensions grow. Gradients near a saddle are tiny, so progress crawls.

Plateaus and cliffs

Large flat regions produce almost no gradient and stall progress. Sharp cliffs produce enormous ones and throw the parameters into nonsense in a single step. Recurrent networks are especially prone to both, which is why gradient clipping originated there.

Local minima

The classic worry, and mostly not the real problem. In large overparameterized networks most local minima found in practice have similar loss values, so which one you land in matters far less than the textbook picture implies. Saddle points and conditioning cost you much more.

Try the saddle preset in the hero and start near the vertical axis. The gradient is nearly zero, so nothing appears to happen for a long stretch — and then the run accelerates away once it drifts far enough off the ridge. A loss curve that is flat for a thousand steps and then suddenly drops is usually this, not a bug.
06Better steps

Momentum, adaptivity, and Adam

Every improvement over plain gradient descent does one of two things: remember previous gradients, or give each parameter its own step size. Watch three optimizers run the same ravine from the same start.

Plain GD
Momentum
Adam
Identical start, identical surface · loss shown after each run
MethodIdeaNotes
MomentumAccumulate a velocity: v ← βv − αg; θ ← θ + vOscillations cancel across steps, consistent directions build up. β = 0.9 is near-universal
NesterovEvaluate the gradient at where momentum is about to take youA look-ahead correction. Slightly better, rarely decisive
AdaGradDivide by the accumulated sum of squared gradientsPer-parameter rates. Excellent for sparse features, but the rate decays to zero and stops
RMSPropSame, with an exponential moving average insteadFixes AdaGrad's terminal decay. Long the default for RNNs
AdamMomentum and RMSProp, with bias correctionThe default for almost everything. β₁=0.9, β₂=0.999, ε=1e-8
AdamWDecouples weight decay from the adaptive scalingAdam's L2 penalty interacts badly with the per-parameter rates. AdamW is the fix, and is what you should use
Why momentum fixes the ravine. Across the narrow direction, successive gradients point opposite ways and cancel in the running average. Along the shallow direction they consistently agree and accumulate. The velocity vector therefore ends up pointing down the valley rather than across it — exactly the direction plain gradient descent keeps failing to find.
Adam is not strictly better. Well-tuned SGD with momentum frequently generalizes better than Adam on vision tasks, and remains the standard there. Adam's advantage is that it works acceptably almost immediately with almost no tuning, which for most projects is the more valuable property.
07Schedules

Changing the step size as you go

A rate large enough to make early progress is too large to settle precisely at the end. Nearly every serious training run therefore varies it over time.

Learning rate against training progress
ScheduleShapeWhen
ConstantFlatFine with Adam on short runs. The honest baseline
Step decayDrop by 10× at set epochsClassic vision recipes. Simple and effective
ExponentialSmooth continuous decayWhen you want no discontinuities
Cosine annealingSmooth decay to near zeroThe modern default for deep learning. Often with warm restarts
WarmupRamp up over the first few hundred stepsEssential for transformers and large batches. Adam's variance estimates are unreliable early on
One-cycleUp then down, with inverse momentumFast convergence on a fixed budget
ReduceLROnPlateauCut when validation stops improvingReactive rather than scheduled. Useful when run length is unknown
Warmup then cosine decay is the recipe to start from for anything transformer-shaped. Ramp linearly over the first few percent of steps, then anneal smoothly to near zero. It is close to universal in current practice, and the warmup portion is not optional at scale — skipping it is a reliable way to produce a loss spike in the first thousand steps.
08Scaling

The cheapest fix available

Feature scaling is usually presented as tidying-up. It isn't. It directly reshapes the loss surface, and it is the highest-return intervention available to anyone using a gradient method.

Unscaled features · after standardization

What's actually happening

If one feature ranges over thousands and another over fractions, the loss changes enormously along one parameter axis and barely along the other. The contours become a long thin ellipse — a ravine, manufactured entirely by your choice of units.

Standardizing makes the two axes comparable, the contours round out, and the condition number collapses. Same problem, same optimum, dramatically easier descent.

The rule to internalize: anything solved by an iterative gradient method needs scaled inputs. Anything solved in closed form does not. This is why linear regression via the normal equation is indifferent to feature scale while the same model trained by gradient descent is not, and why the answer to "does scaling matter for this model" is really a question about how it's fitted.
Fit the scaler on training data only, then apply it unchanged to validation and test. Computing means and standard deviations over the full dataset before splitting leaks information, and the leak is small enough to be invisible and large enough to inflate your reported score.

Inside a network, normalization layers do the same job repeatedly. Batch norm, layer norm and their relatives keep activations well-scaled at every depth, which is why they let you train deeper networks with larger learning rates. Their original explanation has been disputed, but the effect on the optimization landscape is not in question.

09Gradient trouble

When the signal doesn't arrive

Vanishing gradients

Backpropagation multiplies derivatives layer by layer. If each is below one, the product shrinks geometrically and early layers receive almost nothing — they simply stop learning while later layers train normally.

Fixes: ReLU-family activations instead of saturating ones, residual connections that give gradients a direct path, careful initialization, normalization layers, and gated architectures for sequences.

Exploding gradients

The same product running the other way. One enormous step and the parameters land somewhere that produces NaN, from which nothing recovers.

Fixes: gradient clipping by global norm — rescale the whole gradient if its norm exceeds a threshold, preserving direction while capping magnitude. One line, and it's standard practice for RNNs and transformers.

# the standard PyTorch step, with the guards in place
optimizer.zero_grad(set_to_none=True)   # gradients accumulate by default!
loss = criterion(model(x), y)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
scheduler.step()
Forgetting zero_grad is the most common bug in PyTorch. Gradients accumulate across backward passes by design, so omitting it means every step uses the sum of all gradients so far. Training doesn't crash — it just quietly gets worse, which makes it far harder to spot than a crash would be.
10Why it works

Guarantees, and their absence

Convex problems: solid ground

  • Any local minimum is global. There is nowhere bad to end up.
  • For an L-smooth function, α ≤ 1/L guarantees the loss decreases every step.
  • Convergence rate O(1/t), improving to O(1/t²) with Nesterov acceleration.
  • Strongly convex problems converge geometrically, at a rate set by the condition number.

Linear regression, logistic regression and SVMs all live here, which is why they train reliably and reproducibly.

Non-convex problems: almost nothing

For a neural network, theory promises convergence to a point where the gradient is small. It does not promise that point is good, or that two runs will find comparable ones.

And yet it works. The current understanding is that heavy overparameterization makes the landscape unexpectedly benign — most minima reachable by SGD have similar loss, and the noise in stochastic gradients biases the search toward flatter regions that generalize better. Gradient descent on neural networks is a method that works far better than anyone can currently prove it should.
11In practice

Defaults, and what to check when it fails

Starting points that usually work

optimizer
AdamW. Switch to SGD+momentum only if you're going to tune it properly.
learning rate
3e-4 for Adam on a new problem. 0.1 for SGD+momentum on vision with a schedule.
momentum
0.9. Almost never worth tuning.
batch size
The largest that fits, then adjust the learning rate to match.
schedule
Linear warmup then cosine decay.
clipping
Global norm 1.0 for anything recurrent or transformer-shaped.

Loss won't go down — in order

  1. Overfit a single batch. If the model can't drive the loss to near zero on ten examples, the bug is in the model or the data pipeline, not the optimizer.
  2. Check the learning rate across three orders of magnitude before changing anything else.
  3. Verify gradients are non-zero and reaching every layer. Print gradient norms per layer.
  4. Confirm inputs are scaled and contain no NaNs.
  5. Check labels line up with inputs. A shuffling bug looks exactly like an optimization failure.
  6. Confirm zero_grad is being called and the loss is what you think it is.
Overfitting one batch is the single most valuable debugging habit in deep learning. It takes thirty seconds and cleanly separates "the optimizer isn't working" from "the model cannot express this" and from "the data is wrong". Most people learn it far later than they should.
12Second order

Using curvature, and why we mostly don't

Gradient descent knows the slope. Second-order methods also know how the slope is changing, which lets them step directly toward the bottom of a local quadratic approximation instead of feeling their way there.

MethodStepCost and verdict
Newtonθ ← θ − H⁻¹gQuadratic convergence near the optimum. O(p³) per step and needs the Hessian — impossible for large models
Quasi-Newton (BFGS)Builds an approximate inverse Hessian from gradientsO(p²) memory. Fine for thousands of parameters, not millions
L-BFGSKeeps only the last few update pairsExcellent for smooth full-batch problems. Degrades badly with mini-batch noise, which rules it out for deep learning
Natural gradientUses the Fisher information as the metricPrincipled and expensive. K-FAC and similar make it approximately practical
AdamPer-parameter scaling from squared gradientsA crude diagonal curvature estimate. Nearly free, which is why it won
The reason first-order methods dominate is arithmetic. A model with 10⁸ parameters has a Hessian with 10¹⁶ entries. Storing it is out of the question, never mind inverting it. Adam's diagonal approximation captures just enough curvature information to help while costing one extra vector per parameter — and that trade has held up across every scale so far.
13Practice

Drills

#Do thisYou'll know it when
1Minimize f(x) = x² by hand for five steps at three learning ratesYou can predict divergence from α and the curvature
2Implement gradient descent for linear regression in NumPy; match the normal equationBoth give the same β
3Plot loss vs iteration for α across four orders of magnitudeYou recognize each failure shape instantly
4Run the same problem with unscaled then standardized featuresThe iteration count difference is startling
5Add momentum to your implementation and re-run the ravineYou can explain the cancellation argument without notes
6Implement Adam from the paper's pseudocode, bias correction includedIt matches the framework version step for step
7Compare batch, mini-batch and single-example descent on one datasetYou've seen the noise-versus-speed trade directly
8Write a learning rate finder and run it on a real modelYou stop guessing the learning rate
9Deliberately break a training run six ways and diagnose each from the loss curve aloneDebugging becomes reading rather than guessing
10Overfit a single batch on every new model you build, before anything elseIt becomes automatic

The one-paragraph summary

Gradient descent minimizes a function by repeatedly stepping against its gradient, scaled by a learning rate — an algorithm with no map, no memory and no knowledge of where the minimum lies, only the local slope. The learning rate is the decisive hyperparameter: too small and progress crawls, too large and the iterate overshoots and diverges, with the stability limit set by the largest curvature. Estimating the gradient from mini-batches rather than the full dataset is both faster and better, because the resulting noise helps escape saddle points and biases the search toward flatter minima. The dominant practical obstacle is ill-conditioning — elongated loss surfaces where the locally steepest direction is nearly perpendicular to the direction of real progress — which momentum addresses by accumulating consistent directions and cancelling oscillating ones, and which feature scaling addresses by reshaping the surface at source. Adam combines momentum with per-parameter step sizes and is the reasonable default; AdamW is its corrected form. Almost everything else — warmup, cosine decay, gradient clipping, normalization layers, careful initialization — exists to keep that three-line loop stable on surfaces it has no right to succeed on.