Derivatives told you how a function changes locally. Partial Derivatives extended that to functions of many variables. The Chain Rule explained how gradients flow through composed functions—the backbone of backpropagation. Gradient assembled those partial derivatives into a single vector: the direction of steepest ascent.
Gradient descent is where calculus becomes an algorithm. It takes that gradient and walks downhill on a loss surface, updating model parameters until predictions improve. Every neural network you train—from a small logistic regressor to a billion-parameter language model—relies on some variant of this loop.
If the gradient is the compass, gradient descent is the journey.
Learning Objectives
By the end of this lesson, students should be able to:
- State the gradient descent update rule and explain each symbol: weights, learning rate, and loss gradient.
- Describe why we subtract the gradient (not add it) when minimizing loss.
- Explain the role of the learning rate α and diagnose symptoms of rates that are too large or too small.
- Distinguish batch gradient descent, mini-batch gradient descent, and stochastic gradient descent (SGD).
- Recognize local minima, saddle points, and plateaus as landscape challenges—and why deep networks often still train successfully.
- Outline a standard ML training loop: forward pass, loss, backward pass, optimizer step.
- Describe momentum at a high level as a technique that smooths and accelerates updates.
- Read a high-level PyTorch training script and map each line to the mathematical update rule.
Introduction: From Gradient to Algorithm
In supervised learning, a model has parameters (weights) w that map inputs to predictions. Training means finding w that minimizes a loss function L(w)—a scalar measuring how wrong the model is on data. You cannot try every possible w in a high-dimensional space; you need an iterative procedure.
Gradient descent is that procedure. At each step, compute the gradient ∇wL—the vector of partial derivatives of the loss with respect to every parameter. The gradient points uphill (toward higher loss). To reduce loss, move in the opposite direction.
This lecture turns the gradient from a mathematical object into a training algorithm—the engine behind modern deep learning.
The Update Rule
Given current parameters w, loss L, and learning rate α > 0, one step of gradient descent is:
w ← w − α ∇wL
Read aloud: “w gets updated to w minus alpha times the gradient of L with respect to w.” The assignment arrow (←) emphasizes that we overwrite the old weights with new ones.
Unpacking the symbols:
| Symbol | Name | Role in the Update |
|---|---|---|
| w | Parameters (weights) | The vector of all trainable values—weights and biases in a neural network. May live in millions of dimensions. |
| L | Loss function | A scalar objective to minimize (e.g., cross-entropy, mean squared error). Must be differentiable for gradient descent. |
| ∇wL | Gradient of L w.r.t. w | Vector of partial derivatives. Points in the direction of steepest increase in L. Same dimension as w. |
| α | Learning rate | Positive scalar controlling step size. Also written η (eta) in many textbooks—same role, different symbol. |
| − | Descent direction | Subtracting the gradient moves against the uphill direction—hence “descent.” |
Component-wise view: If w = (w1, w2, …, wn), then each parameter updates independently:
wi ← wi − α · (∂L / ∂wi)
The gradient packages all these partial derivatives into one vector so we can write a single, compact update.
Suppose L(w) = (w − 3)2. Then dL/dw = 2(w − 3). Start at w = 0 with α = 0.1:
- Step 1: w ← 0 − 0.1 × 2(0 − 3) = 0 + 0.6 = 0.6
- Step 2: w ← 0.6 − 0.1 × 2(0.6 − 3) = 0.6 + 0.48 = 1.08
- Step 3: w ← 1.08 − 0.1 × 2(1.08 − 3) = 1.08 + 0.384 = 1.464
Each step moves w toward the minimum at w = 3. The loss decreases monotonically for this simple convex function.
Why Subtract the Gradient?
The gradient ∇L points in the direction of steepest ascent—the direction that increases L fastest. Minimization requires the opposite direction: −∇L.
Geometric intuition: imagine standing on a hillside where height represents loss. The gradient points uphill. To reach the valley, walk downhill—opposite to the gradient. The learning rate α determines how far you stride in that downhill direction each step.
If you accidentally add the gradient (w ← w + α∇L), you climb uphill and loss increases. This is gradient ascent—useful for maximization problems, but wrong for training classifiers and regressors.
The Learning Rate α
The learning rate is the single most important hyperparameter in gradient descent. It scales every component of the gradient equally.
α Too Large
Updates overshoot the minimum. Loss oscillates, spikes, or diverges to NaN.
Symptom: Training loss jumps wildly or explodes within the first few epochs.
α Too Small
Updates are timid. Convergence is correct but painfully slow; training may stall on flat regions.
Symptom: Loss decreases glacially; many epochs needed for acceptable accuracy.
α Well Tuned
Loss decreases smoothly and reaches a good minimum in reasonable time.
Practice: Start with a standard value (e.g., 1e-3 for Adam, 0.01–0.1 for SGD), then tune on a validation set.
Unlike parameters w, the learning rate is typically not learned by gradient descent itself (though learning rate schedules and adaptive optimizers like Adam adjust effective step sizes per parameter). Engineers set α or choose a schedule: constant, step decay, cosine annealing, warmup-then-decay.
There is no universal learning rate. It depends on model architecture, loss scale, batch size, and optimizer. A rate that works for a 2-layer MLP on MNIST may destroy training for a transformer on web text. Always monitor loss curves during the first epoch when trying a new α.
Estimating the Gradient: Batch, Mini-Batch, and SGD
The update rule assumes we can compute ∇L. But in ML, L is almost always an average loss over training data:
L(w) = (1/N) ∑i=1N Li(w)
where Li is the loss on the i-th training example. The true gradient is the average of per-example gradients. How we approximate that average defines three variants:
| Variant | Gradient Computed Over | Update Frequency | Trade-offs |
|---|---|---|---|
| Batch GD | Entire dataset (all N examples) | One update per epoch | Accurate gradient direction; expensive for large N; slow updates |
| Mini-Batch GD | A subset of B examples (batch size) | One update per mini-batch | Industry standard; balances noise and speed; GPU-friendly parallelism |
| SGD | One example (B = 1) | One update per example | Noisy but fast per step; rarely used alone at B = 1 today; “SGD” often means mini-batch in practice |
Mini-batch gradient descent is the workhorse of deep learning. A typical batch size ranges from 32 to 4096 depending on GPU memory and model size. The gradient estimate is:
∇L ≈ (1/B) ∑i ∈ batch ∇Li
This is an unbiased estimate of the true gradient when batches are sampled uniformly. The noise introduced by mini-batching can actually help escape poor regions—similar to shaking a ball out of a shallow dip.
The Loss Landscape: Local Minima and Beyond
Gradient descent follows local slope information. It has no global map of the loss surface. That creates well-known challenges:
A point w* is a local minimum if L(w*) ≤ L(w) for all w in some neighborhood around w*. The gradient is zero (or near zero) at such a point, so gradient descent stops updating.
- Local minima — Valleys that are low locally but not globally. Gradient descent can get stuck, returning a suboptimal solution.
- Saddle points — Points where the gradient is zero but the point is neither a minimum nor a maximum (like a mountain pass). Common in high dimensions; can slow training dramatically.
- Plateaus — Regions where the gradient is very small everywhere nearby. Progress stalls even though you are not at a minimum.
For simple convex problems (linear regression with MSE, logistic regression), the loss has a single global minimum—gradient descent is guaranteed to find it (with an appropriate α). Neural networks are non-convex: millions of local minima and saddle points exist.
Empirically, large neural networks often reach solutions that generalize well, even though we do not find the global minimum. Reasons include: (1) many local minima have similar loss in high dimensions; (2) mini-batch noise helps escape saddles; (3) overparameterized networks have wide, flat basins that generalize better than sharp minima; (4) early stopping prevents overfitting before wandering too far. Local minima are a concern in theory but less catastrophic in practice than early textbooks suggested.
Momentum: A Brief Preview
Plain gradient descent can zigzag in narrow valleys and crawl across flat regions. Momentum addresses this by accumulating a velocity vector that smooths updates over time.
v ← βv − α∇L, w ← w + v
Here v is a velocity buffer and β ∈ [0, 1) is a momentum coefficient (often 0.9). The update remembers past gradient directions—like a ball rolling downhill, gaining speed in consistent directions and dampening oscillations across ravines.
Momentum is one of many extensions to vanilla gradient descent. The next lecture, Optimization, covers Adam, RMSprop, learning rate schedules, and other optimizers used in production training.
The Training Loop
Gradient descent is not a single formula—it is a loop embedded in a larger training procedure. Every PyTorch, TensorFlow, or JAX training script follows this skeleton:
Key engineering details often omitted from the formula but critical in practice:
- Zeroing gradients — Frameworks accumulate gradients by default. Call
optimizer.zero_grad()before each backward pass or gradients from prior batches add up incorrectly. - Evaluation mode — Disable dropout and batch-norm training behavior during validation (
model.eval()in PyTorch). - Gradient clipping — Cap gradient magnitude to prevent explosion in recurrent and transformer models (covered in later volumes).
- Early stopping — Halt training when validation loss stops improving to avoid overfitting.
PyTorch: Gradient Descent in Code
PyTorch hides the calculus behind two mechanisms: loss.backward() computes gradients via autograd, and optimizer.step() applies the update rule. Below is a high-level training loop for a simple classifier—not production code, but the structural template every engineer recognizes:
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
# Model, loss, and optimizer (w is model.parameters(), alpha is lr)
model = nn.Linear(784, 10) # w: weights and biases
criterion = nn.CrossEntropyLoss() # L: scalar loss
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # alpha = 0.01
train_loader = DataLoader(dataset, batch_size=64, shuffle=True)
for epoch in range(num_epochs):
model.train()
for inputs, labels in train_loader: # mini-batch loop
# Forward pass
outputs = model(inputs) # y_hat = f(w; x)
loss = criterion(outputs, labels) # L(w)
# Backward pass: compute nabla_w L
optimizer.zero_grad() # clear old gradients
loss.backward() # autograd: chain rule
# Update rule: w <- w - alpha * nabla_w L
optimizer.step()
# Validation (no gradient updates)
model.eval()
with torch.no_grad():
val_loss = evaluate(model, val_loader)
Mapping code to math:
| PyTorch | Mathematical Meaning |
|---|---|
model.parameters() |
The parameter vector w (tensors distributed across layers) |
loss = criterion(outputs, labels) |
Compute scalar L(w) |
loss.backward() |
Compute ∇wL via backpropagation |
optimizer.step() |
Apply w ← w − α∇L (SGD) or extended rule (Adam, etc.) |
lr=0.01 in optimizer constructor |
Learning rate α |
batch_size=64 in DataLoader |
Mini-batch size B for gradient estimation |
Switching from SGD to momentum is a one-line change: torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9). Switching to Adam: torch.optim.Adam(model.parameters(), lr=1e-3). The training loop structure stays identical—only the optimizer’s internal update rule changes.
Convergence and Stopping Criteria
When do you stop iterating? Common criteria:
- Fixed epochs — Train for a predetermined number of passes (e.g., 10, 50, 100). Simple but arbitrary.
- Loss threshold — Stop when L drops below a target. Rarely used alone because the target is unknown beforehand.
- Early stopping — Monitor validation loss; stop when it rises for several consecutive checks. Prevents overfitting.
- Gradient norm — Stop when ‘∇L’ is near zero, indicating a stationary point. Uncommon as a sole criterion because near-zero gradients also occur at saddles and plateaus.
In practice, engineers combine fixed epochs with early stopping and checkpointing: save the best validation model, then restore it after training ends.
Common Misconceptions
Why people believe it: The algorithm “minimizes” the loss, so it sounds like it finds the best possible solution.
Reality: Gradient descent finds a local minimum (or stationary point) starting from initialization. For non-convex losses like neural networks, different random seeds can land in different basins with different test accuracy.
Why people believe it: The name “stochastic” suggests one random example at a time.
Reality: Historically, SGD used single examples. Today, when practitioners say “SGD,” they often mean mini-batch SGD with the torch.optim.SGD optimizer—batch sizes of 32, 64, or larger. True single-example SGD is rare in deep learning due to GPU inefficiency.
Why people believe it: Bigger steps cover more ground per iteration.
Reality: Too large an α causes overshooting and divergence. There is a sweet spot—and adaptive optimizers adjust per-parameter step sizes because different weights need different effective rates.
zero_grad() to save time.”
Why people believe it: It seems like an optional cleanup step.
Reality: PyTorch accumulates gradients across calls to backward(). Without zeroing, gradients from multiple batches sum together and corrupt the update. This is one of the most common bugs in beginner training scripts.
Quick Knowledge Check
- Short Answer: Write the gradient descent update rule. Answer: w ← w − α∇wL.
- True/False: The gradient points in the direction of steepest loss decrease. Answer: False — it points in the direction of steepest increase; we subtract it to descend.
- Multiple Choice: Which variant uses the entire dataset for one gradient computation? (a) SGD, (b) Mini-batch GD, (c) Batch GD, (d) Momentum. Answer: (c) Batch GD.
- Short Answer: What happens if the learning rate is too large? Answer: Updates overshoot; loss may oscillate or diverge.
- Short Answer: What is a local minimum? Answer: A point where loss is lower than all nearby points but not necessarily the global lowest loss.
- True/False: In PyTorch,
loss.backward()updates the model weights. Answer: False — it computes gradients;optimizer.step()updates weights. - Short Answer: What does momentum add to plain gradient descent? Answer: A velocity term that accumulates past gradients, smoothing updates and accelerating consistent directions.
- Multiple Choice: Why do we call
optimizer.zero_grad()beforebackward()? (a) To reset the model, (b) To clear accumulated gradients, (c) To set learning rate to zero, (d) To enable evaluation mode. Answer: (b). - Short Answer: Name the four steps inside one mini-batch iteration. Answer: Forward pass, compute loss, backward pass, optimizer step (accept: zero grad before backward).
- True/False: Mini-batch noise is always harmful to training. Answer: False — noise can help escape saddle points and poor local regions.
Key Takeaways
- Gradient descent iteratively minimizes loss: w ← w − α∇wL.
- The gradient points uphill; subtracting it moves parameters downhill on the loss surface.
- The learning rate α controls step size—the most critical hyperparameter to tune.
- Batch GD (full data), mini-batch GD (subset), and SGD (one example) trade gradient accuracy for speed and noise.
- Mini-batch training is the standard in deep learning; batch size affects memory, speed, and gradient noise.
- Local minima, saddles, and plateaus complicate non-convex optimization, but large networks still train effectively in practice.
- Momentum smooths updates by accumulating velocity—a preview of richer optimizers in the next lecture.
- The training loop: forward → loss → backward → step; PyTorch’s autograd and optimizer handle the calculus.
- Always zero gradients between batches; monitor loss curves when tuning α.
Further Reading & References
Books
- Deep Learning — Goodfellow, Bengio, and Courville (2016). Chapter 8: Optimization for Training Deep Models. The definitive treatment of gradient descent variants.
- Pattern Recognition and Machine Learning — Christopher Bishop. Section 5.2: Gradient descent and error surfaces.
Video & Visual
- Gradient Descent, How Neural Networks Learn — 3Blue1Brown (YouTube). Visual intuition for the update rule on a 2D loss surface.
- Google Machine Learning Crash Course — Gradient descent module with interactive learning rate demonstrations.
Official Documentation
- PyTorch Optimizers — SGD, Adam, and all built-in update rules
- PyTorch Neural Networks Tutorial — Training loop walkthrough
- PyTorch Autograd — How
backward()computes gradients
Teaching strategy: Draw a bowl-shaped curve on the board. Mark a starting point, draw the tangent (gradient), and show that moving opposite to the tangent descends. Vary step size visually—small steps creep; large steps bounce out of the bowl.
Hands-on idea: Implement the scalar example L(w) = (w − 3)2 in a Jupyter notebook. Loop 20 steps with α = 0.1 and plot w and L over iterations. Then try α = 1.5 and show divergence.
PyTorch demo: Train nn.Linear(1, 1) on synthetic data (y = 2x + 1). Print loss.item() each epoch so students see the scalar decrease. Intentionally omit zero_grad() once and show broken training.
Discussion prompt: If two different random initializations reach different final losses on the same network, does that contradict gradient descent? What does it tell us about the loss landscape?
Bridge to next lecture: Preview that Adam and learning rate schedules are engineering upgrades to the same loop—the update rule changes, the training skeleton does not.