Module A · Lecture 07

Gradient Descent & Stochastic Gradient Descent

If backpropagation (next lecture) tells us which way every weight should move, gradient descent is the rule that decides how far — and how often — to move it.

⏱ ~60 min 🧩 Builds on: Lectures 4–6 🎯 CO1
🧭 Why we're learning this now

Lecture 6 gave us a loss function, a single number that tells us how wrong the network currently is. Knowing you're wrong isn't the same as knowing how to become less wrong — this lecture is the general-purpose algorithm for turning "here's a number I want smaller" into "here's exactly how to change every parameter."

  • State the gradient descent update rule and interpret it geometrically as a step opposite the steepest-ascent direction.
  • Hand-compute successive gradient descent iterates for a simple quadratic loss and recognize geometric convergence.
  • Differentiate (Batch) Gradient Descent, Stochastic Gradient Descent, and Mini-Batch Gradient Descent using the instructor's comparison criteria.
  • Explain why mini-batch gradient descent (batch sizes 32/64/128/256…) is the practical default for training real networks.
  • Diagnose the effect of the learning rate: too small stalls training, too large causes oscillation or divergence.
  • Preview how Lecture 8 computes \(\nabla_w L\) efficiently and how Lecture 9's optimizers make smarter use of it.

1. Why Gradient Descent?

Lecture 6 gave us a loss function \(L\) that scores how wrong the network's predictions are — cross-entropy for classification, squared error for regression. Training a network means searching for the parameters (weights and biases, collectively \(w\)) that make \(L\) as small as possible. For any network with more than a handful of weights, the loss surface \(L(w)\) lives in an enormous, high-dimensional space that cannot be searched exhaustively.

Gradient descent is the algorithm that makes this search tractable. Instead of evaluating every possible setting of \(w\), it starts from some initial guess and repeatedly takes small steps that are guaranteed — locally — to reduce the loss. It is, without exaggeration, the single algorithm that makes deep learning possible: every network you will meet in this course, however large, is trained by some variant of it.

2. The Update Rule & Geometric Intuition

The gradient descent update rule

$$w \leftarrow w - \eta\, \nabla_w L$$ where \(\nabla_w L\) is the gradient of the loss with respect to \(w\) (computed by backpropagation, Lecture 8), and \(\eta > 0\) is the learning rate, a small positive scalar controlling step size.

The gradient \(\nabla_w L\) points in the direction of steepest ascent of the loss — the direction in which \(L\) increases fastest from the current point. If we want to decrease the loss, the best local direction to move is exactly opposite the gradient: \(-\nabla_w L\). That is the entire idea behind the minus sign in the update rule. Each step nudges \(w\) a little way downhill on the loss surface, and repeating the step traces a path toward a minimum.

The loss curve \(L(w)=(w-3)^2\) with five successive gradient descent iterates plotted on it (\(w_0=0\), \(\eta=0.1\)). Each step lands closer to the minimum at \(w=3\), and the step size itself shrinks as the curve flattens near the bottom.

3. Worked Example: Watching Convergence

Take the simplest possible loss surface, a 1-D quadratic bowl \(L(w) = (w-3)^2\), whose minimum is obviously at \(w=3\). Its gradient is \(\nabla_w L = 2(w-3)\) — every value used in the six iterations below is just this formula evaluated at the current \(w\). If you're already comfortable differentiating this by hand, skip straight to the iterations below. If you'd like the mechanical derivation spelled out, expand it here.

New to this? Expand: deriving ∇w L = 2(w−3) via the chain rule
Where does ∇w L = 2(w−3) actually come from?

Rather than take this gradient on faith, differentiate it directly. Write \(L\) as a composition \(L=u^2\) where \(u=w-3\), and apply the single-variable chain rule \(\frac{dL}{dw}=\frac{dL}{du}\cdot\frac{du}{dw}\):

$$\frac{dL}{dw}=\underbrace{2u}_{d(u^2)/du}\ \cdot\ \underbrace{1}_{d(w-3)/dw} = 2u = \mathbf{2(w-3)}$$

So \(\nabla_w L = 2(w-3)\) — every gradient value used in the six iterations below is just this formula evaluated at the current \(w\).

Now start from \(w_0=0\) with learning rate \(\eta=0.1\) and apply the update rule \(w\leftarrow w-\eta\nabla_wL\) repeatedly. Every arithmetic step is written out below, in order — nothing is left for you to fill in.

🔢 Iteration 1 — w₀ = 0

Gradient: \(\nabla L(0)=2(0-3)=\mathbf{-6}\).   Update: \(w_1 = 0 - 0.1\times(-6) = 0+0.6=\mathbf{0.6}\).

Iteration 2 — w₁ = 0.6

Gradient: \(\nabla L(0.6)=2(0.6-3)=2(-2.4)=\mathbf{-4.8}\).   Update: \(w_2 = 0.6 - 0.1\times(-4.8) = 0.6+0.48=\mathbf{1.08}\).

Iteration 3 — w₂ = 1.08

Gradient: \(\nabla L(1.08)=2(1.08-3)=2(-1.92)=\mathbf{-3.84}\).   Update: \(w_3 = 1.08 - 0.1\times(-3.84) = 1.08+0.384=\mathbf{1.464}\).

Iteration 4 — w₃ = 1.464

Gradient: \(\nabla L(1.464)=2(1.464-3)=2(-1.536)=\mathbf{-3.072}\).   Update: \(w_4 = 1.464 - 0.1\times(-3.072) = 1.464+0.3072=\mathbf{1.7712}\).

Iteration 5 — w₄ = 1.7712

Gradient: \(\nabla L(1.7712)=2(1.7712-3)=2(-1.2288)=\mathbf{-2.4576}\).   Update: \(w_5 = 1.7712 - 0.1\times(-2.4576) = 1.7712+0.24576=\mathbf{2.01696}\).

Iteration 6 — w₅ = 2.01696

Gradient: \(\nabla L(2.01696)=2(2.01696-3)=2(-0.98304)=\mathbf{-1.96608}\).   Update: \(w_6 = 2.01696 - 0.1\times(-1.96608) = 2.01696+0.196608\approx\mathbf{2.2136}\).

✅ Why it converges geometrically

Substituting the gradient into the update rule gives \(w_{t+1} = w_t - \eta\cdot 2(w_t-3) = w_t(1-2\eta) + 2\eta\cdot 3\) — a linear recurrence in \(w_t\). With \(\eta=0.1\), this is \(w_{t+1} = 0.8\,w_t + 0.6\), whose fixed point solves \(w^\*=0.8w^\*+0.6 \Rightarrow w^\*=3\) — the true minimum. Because the multiplier \(|1-2\eta|=0.8 < 1\), the distance to the minimum shrinks by a constant factor (80%) every step: convergence is geometric, exactly as the six hand-computed iterations above show — each \(w_t\) closes 80% of the remaining gap to 3.

You can replay the same six iterations interactively below — useful for testing yourself before moving on:

4. Batch, Stochastic & Mini-Batch Gradient Descent

The update rule \(w \leftarrow w - \eta\nabla_w L\) says nothing about how much data we use to compute \(\nabla_w L\) on each step — and that choice defines three distinct variants of gradient descent.

  • (Batch) Gradient Descent computes the gradient using the entire training set before taking a single step. The gradient estimate is exact and the descent path is smooth, but one step requires a full pass over potentially millions of examples.
  • Stochastic Gradient Descent (SGD) computes the gradient from a single randomly chosen training example (or a very small handful) and updates immediately. Each step is cheap and fast, but the gradient estimate is noisy, so the descent path zig-zags rather than gliding smoothly downhill.
FeatureGradient DescentStochastic Gradient Descent
Data used per updateEntire datasetOne (or few) samples
SpeedSlow (needs full pass)Fast per step
ConvergenceStable, smoothNoisy, but often faster
Suitable forSmall datasetsLarge datasets, online learning

5. Mini-Batch Gradient Descent: The Practical Default

Mini-batch gradient descent is the middle ground that virtually all real training uses: compute the gradient over a small, fixed-size batch of examples (commonly 32, 64, 128, or 256), average it, and take one step. This keeps the noise of pure SGD low enough for stable progress while keeping each update far cheaper than a full-dataset pass — and, crucially, batched matrix multiplications map efficiently onto GPU/TPU hardware, which is designed for exactly this kind of parallel arithmetic.

Rule of thumb

"Gradient descent" in a modern deep learning paper almost always means mini-batch gradient descent. Pure full-batch GD is rare outside small-data settings; pure single-example SGD is rare outside streaming/online learning. Batch size itself is a hyperparameter you tune, typically a power of two chosen to fit comfortably in GPU memory.

6. Learning Rate: The Critical Hyperparameter

The learning rate \(\eta\) is arguably the single most important hyperparameter in training a neural network:

  • Too small — steps are tiny, so the loss decreases correctly but painfully slowly, and training may not converge within any reasonable number of epochs.
  • Too large — steps overshoot the minimum. Instead of settling down, the iterate can oscillate around the minimum or, worse, diverge to increasingly extreme values.

We can see divergence directly by re-running the same quadratic \(L(w)=(w-3)^2\) with an aggressive learning rate \(\eta = 1.1\), starting again from \(w_0=0\):

StepComputationw
0initial0
1\(0 - 1.1\times2(0-3)\)6.6
2\(6.6 - 1.1\times2(6.6-3)\)−1.32
3\(-1.32 - 1.1\times2(-1.32-3)\)8.184
4\(8.184 - 1.1\times2(8.184-3)\)−3.221
\(w_t\) plotted against iteration \(t\) for \(\eta=1.1\) on \(L(w)=(w-3)^2\). The recurrence here is \(w_{t+1}=-1.2\,w_t+6.6\): because the multiplier \(|1-2\eta|=1.2 > 1\), each error overshoots further than the last and the sign flips every step — growing oscillation, i.e. divergence.
⚠ Reading the divergence recurrence

The same linear-recurrence trick from Section 3 explains this instantly: \(w_{t+1}=w_t(1-2\eta)+2\eta\cdot3\). Convergence requires \(|1-2\eta|<1\), i.e. \(0<\eta<1\) for this particular curve's curvature. At \(\eta=1.1\), \(|1-2\eta|=1.2>1\), so the distance from the minimum grows by 20% every step, with alternating sign — exactly the oscillating blow-up in the chart above. In practice, this is why training loss that suddenly turns to NaN is the classic symptom of a learning rate set too high.

7. Common Pitfalls

⚠ Things to watch for
  • One learning rate for the whole surface. Real loss surfaces are not simple bowls — they can be steep in one direction and nearly flat in another. A single global \(\eta\) that works for one axis may be wrong for another (motivates Lecture 9's per-parameter optimizers).
  • Confusing batch size with learning rate. They interact — larger batches give lower-variance gradient estimates, which often tolerate a larger \(\eta\) — but they are independent hyperparameters that must both be tuned.
  • Assuming SGD's noise is purely bad. The noise in stochastic and mini-batch updates can actually help escape shallow local minima and saddle points that full-batch GD would settle into — Lecture 9 revisits this.
  • Forgetting to shuffle data between epochs when using SGD/mini-batch — without shuffling, batches are not representative samples and training can develop systematic biases.

8. Summary

Key takeaways
  • Gradient descent updates weights via \(w \leftarrow w - \eta\nabla_w L\), stepping opposite the direction of steepest ascent.
  • On a quadratic loss, gradient descent is a linear recurrence — it converges geometrically when \(|1-2\eta|<1\) and diverges (oscillating, growing) when that condition fails.
  • Batch GD (whole dataset), SGD (one/few samples), and mini-batch GD (small fixed batches, e.g. 32–256) trade off gradient accuracy against speed per step; mini-batch is the practical default.
  • The learning rate is a critical hyperparameter: too small stalls training, too large causes oscillation or divergence.
  • Coming next: Lecture 8 (Backpropagation) shows how to compute \(\nabla_w L\) efficiently for deep networks with many layers. Lecture 9 (Advanced Optimizers) shows smarter ways to use that gradient once we have it.

9. Code: Gradient Descent From Scratch

The script below reproduces both the convergent (\(\eta=0.1\)) and divergent (\(\eta=1.1\)) numeric examples above, plus a small mini-batch gradient descent demo on a toy linear-regression dataset.

lecture-07-gradient-descent.py
import numpy as np

def L(w):      return (w - 3) ** 2
def grad_L(w): return 2 * (w - 3)

def gradient_descent(w0, lr, steps):
    w = w0
    history = [w]
    for _ in range(steps):
        w = w - lr * grad_L(w)
        history.append(w)
    return history

# ---- convergent run: matches the lecture's worked example ----
conv = gradient_descent(w0=0.0, lr=0.1, steps=6)
print("Convergent (eta=0.1):", [round(w, 4) for w in conv])

# ---- divergent run: learning rate too large ----
div = gradient_descent(w0=0.0, lr=1.1, steps=4)
print("Divergent  (eta=1.1):", [round(w, 4) for w in div])

# ---- mini-batch gradient descent on a toy linear regression ----
rng = np.random.default_rng(0)
X = rng.uniform(-1, 1, size=(200, 1))
true_w, true_b = 2.5, -0.7
y = true_w * X[:, 0] + true_b + rng.normal(0, 0.05, size=200)

w, b, lr, batch_size, epochs = 0.0, 0.0, 0.1, 32, 100
n = len(X)
for epoch in range(epochs):
    idx = rng.permutation(n)
    for start in range(0, n, batch_size):
        batch = idx[start:start + batch_size]
        xb, yb = X[batch, 0], y[batch]
        pred = w * xb + b
        err = pred - yb
        dw = 2 * np.mean(err * xb)
        db = 2 * np.mean(err)
        w -= lr * dw
        b -= lr * db
    if epoch % 20 == 0:
        mse = np.mean((w * X[:, 0] + b - y) ** 2)
        print(f"  epoch {epoch:3d}: w={w:.3f} b={b:.3f} mse={mse:.4f}")

print(f"Learned w={w:.3f}, b={b:.3f}  (true w={true_w}, b={true_b})")

⬇ Download lecture-07-gradient-descent.py   More resources for this lecture →