Skip to main content
© 2026 ePowerAI. All rights reserved.
CoursesContact
eAI
CoursesContact
Week 6: Stochastic Gradient Descent
Optimization
01Week 1: Optimization for Learning
02Week 2: Convex Sets and Functions
03Week 3: Gradient Descent Theory
04Week 4: Least Squares and Conditioning
05Week 5: Classification Losses
06Week 6: Stochastic Gradient Descent
07Week 7: Momentum and Acceleration
08Week 8: Adaptive First-Order Methods
09Week 9: Practical Training Dynamics
10Week 10: Nonsmooth and Composite Objectives
11Week 11: Constraints: Projections and Penalties
12Week 12: Duality for Practitioners
13Week 13: Nonconvex Deep Learning
14Week 14: Capstone — Theory Meets Training
Week 06· Optimization· First-order stack22 min read

Week 6: Stochastic Gradient Descent

✦Learning Outcomes
  • Define mini-batch SGDStochastic Gradient Descent and explain why it is faster per-iteration than full-batch GD
  • Describe the bias–variance tension: mini-batch gradient is unbiased but noisy
  • Choose a decreasing learning rate schedule for convergence under noise
  • Contrast epoch (pass over full dataset) vs step (single mini-batch update)
◆Prerequisites

Background: Week 1 (GD algorithm), Week 3 (convergence rates, smoothness), Week 4 (least squares as a benchmark). The finite-sum ERM objective from Week 1 is the natural setting for SGD.

Later weeks: SGD is the base layer for momentum (Week 7) and Adam (Week 8). The noise characteristics introduced here determine how those advanced methods behave.

Finite-Sum Objectives#

Nearly all supervised learning objectives have the form:

f(w)=1n∑i=1nfi(w)f(w) = \frac{1}{n} \sum_{i=1}^n f_i(w)f(w)=n1​∑i=1n​fi​(w)

where fi(w)=ℓ(fw(xi),yi)f_i(w) = \ell(f_w(x_i), y_i)fi​(w)=ℓ(fw​(xi​),yi​) is the loss on the iii-th training example. This is the ERMEmpirical Risk Minimization objective from Week 1.

The full gradient is ∇f(w)=1n∑i=1n∇fi(w)\nabla f(w) = \frac{1}{n} \sum_{i=1}^n \nabla f_i(w)∇f(w)=n1​∑i=1n​∇fi​(w), which requires scanning all nnn examples — one full pass through the dataset — for a single parameter update. When nnn is large (millions or billions), this is impossibly slow.

SGD replaces the full sum with an estimate computed from a small, randomly chosen mini-batch.

Mini-Batch SGD#

At iteration kkk, sample a mini-batch Bk⊂{1,…,n}\mathcal{B}_k \subset \{1, \ldots, n\}Bk​⊂{1,…,n} of size BBB uniformly at random (without replacement). The mini-batch gradient estimate is:

gk=1B∑i∈Bk∇fi(wk)g_k = \frac{1}{B} \sum_{i \in \mathcal{B}_k} \nabla f_i(w_k)gk​=B1​∑i∈Bk​​∇fi​(wk​)

The SGD update is then wk+1=wk−ηk gkw_{k+1} = w_k - \eta_k \, g_kwk+1​=wk​−ηk​gk​.

Two key properties of gkg_kgk​:

  1. Unbiased: E[gk]=∇f(wk)\mathbb{E}[g_k] = \nabla f(w_k)E[gk​]=∇f(wk​). On average, the mini-batch gradient points in the right direction. The expectation is over the random choice of Bk\mathcal{B}_kBk​.

  2. Noisy: Var(gk)\mathrm{Var}(g_k)Var(gk​) is proportional to 1/B1/B1/B. A smaller batch has higher variance (noisier gradient estimate) but is cheaper to compute.

Computational cost per step:

  • Full-batch GD: O(nd)\mathcal{O}(n d)O(nd) to compute ∇f\nabla f∇f, then O(d)\mathcal{O}(d)O(d) to update
  • Mini-batch SGD (size BBB): O(Bd)\mathcal{O}(B d)O(Bd) to compute gkg_kgk​, then O(d)\mathcal{O}(d)O(d) to update

SGD makes roughly n/Bn/Bn/B times as many updates in the time it takes GD to do one update.

Noise and Variance#

The variance of the mini-batch gradient estimate is:

Var(gk)≈σ2Bwhere σ2=1n∑i=1n∥∇fi(wk)−∇f(wk)∥2\mathrm{Var}(g_k) \approx \frac{\sigma^2}{B} \quad \text{where } \sigma^2 = \frac{1}{n} \sum_{i=1}^n \|\nabla f_i(w_k) - \nabla f(w_k)\|^2Var(gk​)≈Bσ2​where σ2=n1​∑i=1n​∥∇fi​(wk​)−∇f(wk​)∥2

The term σ2\sigma^2σ2 measures how different individual example gradients are from the average. When examples are "similar" (homogeneous data), σ2\sigma^2σ2 is small and SGD works well even with B=1B=1B=1. When examples are very different (heterogeneous data), you need larger batches to reduce variance.

The batch-size tradeoff:

  • Small BBB: cheap per iteration, many noisy updates per epoch → better exploration
  • Large BBB: expensive per iteration, fewer but more accurate updates → reliable descent
  • B=nB = nB=n: recovers full-batch GD (zero variance, maximum cost per step)

In practice, BBB is typically chosen as a power of 2 (32, 64, 128, 256) to fit well with GPU memory and vectorised computation.

Step Sizes Under Noise#

With a fixed step size η\etaη, SGD does not converge to the exact optimum — it oscillates in a ball around w∗w^*w∗ whose radius is proportional to ησ2\eta \sigma^2ησ2. To converge exactly, we must decay the step size:

ηk=η01+γkorηk=η0k\eta_k = \frac{\eta_0}{1 + \gamma k} \quad \text{or} \quad \eta_k = \frac{\eta_0}{\sqrt{k}}ηk​=1+γkη0​​orηk​=k​η0​​

The O(1/k)\mathcal{O}(1/\sqrt{k})O(1/k​) schedule is common in theory; in practice, step-decay (halve η\etaη every TTT epochs) and cosine annealing (Week 9) are more popular.

A useful heuristic: for SGD with batch size BBB, a rule of thumb is η∝B\eta \propto Bη∝B. Doubling the batch size approximately allows doubling the learning rate (because the gradient estimate is twice as accurate).

Epoch vs Step#

  • Step (or iteration): one mini-batch processed, one parameter update
  • Epoch: one full pass through the entire dataset (⌈n/B⌉\lceil n/B \rceil⌈n/B⌉ steps)

At the end of each epoch, the data is typically shuffled (random permutation) to avoid periodicity effects. Shuffling ensures each mini-batch is an independent random sample, which is important for the unbiasedness property.

Tracking loss per epoch is more meaningful than loss per step, because epoch-level loss estimates the true objective while step-level loss is computed on a single mini-batch and is noisier.

Exercise · Multiple choice

If the mini-batch size B is doubled, the variance of the gradient estimate is approximately:

Halved
The same
Doubled
Quadrupled
Question 1 of 4

The mini-batch gradient g_k is an unbiased estimate of the full gradient ∇f(w) because:

E[g_k] = ∇f(w)
g_k has small variance
The data is shuffled
The batch size is large

Browser lab#

Compare GD, SGD(B=1), and SGD(B=16) on a finite-sum quadratic objective. Plot loss vs epoch and observe the noise–convergence tradeoff.

python · runs in browser
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
d, n = 3, 200

w_true = np.array([1.5, -0.8, 0.6])
X = rng.normal(size=(n, d))
y = X @ w_true + rng.normal(scale=0.3, size=n)

def loss(w, X, y):
    return 0.5 * np.mean((X @ w - y)**2)

def run_method(X, y, w0, n_epochs, method, B=None, eta=None):
    w = w0.copy()
    n = len(X)
    epoch_losses = []
    for ep in range(n_epochs):
        perm = rng.permutation(n)
        for start in range(0, n, B or n):
            idx = perm[start:start + (B or n)]
            g = X[idx].T @ (X[idx] @ w - y[idx]) / len(idx)
            w -= eta * g
        epoch_losses.append(loss(w, X, y))
    return epoch_losses

w0 = np.zeros(d)
n_epochs = 40
L = np.linalg.eigvalsh(X.T @ X / n).max()
eta = 1.0 / L

losses_gd = run_method(X, y, w0, n_epochs, "GD", B=n, eta=eta)
losses_sgd1 = run_method(X, y, w0, n_epochs, "SGD1", B=1, eta=eta*0.05)
losses_sgd16 = run_method(X, y, w0, n_epochs, "SGD16", B=16, eta=eta*0.5)

plt.figure(figsize=(8, 5))
plt.plot(losses_gd, label="GD (full batch)", linewidth=2)
plt.plot(losses_sgd1, alpha=0.8, label="SGD B=1 (noisy)", linewidth=1)
plt.plot(losses_sgd16, alpha=0.8, label="SGD B=16", linewidth=1.5)
plt.yscale("log")
plt.xlabel("Epoch"); plt.ylabel("Loss")
plt.title("GD vs SGD: Loss per Epoch")
plt.legend(); plt.grid(True, alpha=0.3)
plt.show()
← Previous
Week 5: Classification Losses
Next →
Week 7: Momentum and Acceleration
On this page
  • Finite-Sum Objectives
  • Mini-Batch SGD
  • Noise and Variance
  • Step Sizes Under Noise
  • Epoch vs Step
  • Browser lab