Optimisers Mathematical Deep Dive
P6.dl-architectures.04 · Audience: it-ml · Prerequisites: Training Loop and Optimisers
The SGD derivation from the stochastic objective, momentum as an exponential moving average with its heavy-ball origin, Adam with the full bias-correction proof, and learning-rate schedule formulas — mathematical details for IT/ML practitioners, with pencil-and-paper exercises, NumPy programming tasks, and a 5-question self-test.
Prerequisite: Training Loop and Optimisers. This page assumes familiarity with expectations, the chain rule, and geometric series. All derivations are self-contained.
Stochastic Gradient Descent minimises an objective by iteratively taking steps in the direction of the negative gradient of a stochastic estimate of the loss.
Problem: minimise the expected loss over the data distribution:
Because we cannot compute the expectation over the full dataset at each step, we approximate it with a mini-batch ℬ of size B:
SGD update rule:
Why this works (gradient descent intuition). If the loss surface were quadratic, the exact gradient is κθ and the update converges geometrically to 0 as long as η < 2/κ:
Convergence conditions:
| Condition | Result | Notes |
|---|---|---|
| η < 2/κ (convex quadratic) | Exact convergence to minimum | κ = Lipschitz constant of the gradient (curvature) |
| η → 0 but Σ ηt = ∞, Σ ηt² < ∞ | Convergence in expectation (stochastic setting) | Robbins–Monro conditions — satisfied by 1/t schedule |
| η fixed | Converges to noise ball around minimum; size ∝ η·σ² | Most common in practice; σ² = variance of stochastic gradient |
Worked SGD trace on L(w) = w² (gradient 2w, minimum at 0) with the defaults w₀ = 3.0 and η = 0.1. Each step multiplies w by (1 − 2η) = 0.8, so the loss contracts by 0.8² = 0.64 per step:
| Step | w | L(w) = w² |
|---|---|---|
| 0 | 3.00000 | 9.00000 |
| 1 | 2.40000 | 5.76000 |
| 2 | 1.92000 | 3.68640 |
| 3 | 1.53600 | 2.35930 |
| 4 | 1.22880 | 1.50995 |
| 5 | 0.98304 | 0.96637 |
After 20 steps: w = 3.0 × 0.8²⁰ ≈ 0.0346, loss ≈ 0.0012. Convergence speed is controlled by η·κ where κ = 2 (curvature of w²). Current product: 0.200 (< 1: converges; ≥ 1: may diverge).
Exercises
Work these on paper or in the scratch console at the bottom of the page. Each exercise carries its worked solution behind a reveal, so check yourself as you go. (Hands-on PyTorch training rungs arrive with the VN-056 training clinic — coming soon.)
Maths exercises (pencil and paper)
M1 — One Adam step. Given , , , , , , , : compute the Adam weight update at for a single parameter .
Recall: , , then bias-correct and update.
Show answer — M1
(bias-corrected back to )
(bias-corrected back to )
M2 — Step-decay LR at t = 100. Compute for , , .
Show answer — M2
(three full drops have occurred)
M3 — Bias-correction proof: show that when . Starting from , after one step: . Show that .
Show answer — M3
✓
The bias-correction exactly cancels the factor that underweights the first gradient, recovering the unbiased estimate.
Programming exercises (NumPy)
Try each exercise in your own Python environment (or the scratch console), then reveal the reference solution.
P1 — Implement one Adam step in NumPy.
import numpy as np
def adam_step(theta, g, m, v, t, alpha=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
"""Perform one Adam update.
Parameters
----------
theta : np.ndarray Current parameters.
g : np.ndarray Gradient at current step.
m : np.ndarray First moment (EMA of gradients).
v : np.ndarray Second moment (EMA of squared gradients).
t : int Current step (1-based).
Returns
-------
tuple[np.ndarray, np.ndarray, np.ndarray]
(theta_new, m_new, v_new)
"""
# TODO: implement Adam
pass
theta = np.array([0.5, -0.3])
g = np.array([0.1, -0.2])
m = np.zeros(2)
v = np.zeros(2)
print(adam_step(theta, g, m, v, t=1))
Show solution — P1
def adam_step(theta, g, m, v, t, alpha=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
m_new = beta1 * m + (1 - beta1) * g
v_new = beta2 * v + (1 - beta2) * g**2
m_hat = m_new / (1 - beta1**t)
v_hat = v_new / (1 - beta2**t)
theta_new = theta - alpha / (np.sqrt(v_hat) + eps) * m_hat
return theta_new, m_new, v_new
theta_new, m_new, v_new = adam_step(theta, g, m, v, t=1)
print(theta_new) # [0.499 -0.299 ] — very small step at t=1
P2 — Implement a step-decay learning rate schedule.
import numpy as np
def step_decay_schedule(t_max, alpha0=0.1, step_size=30, gamma=0.5):
"""Return LR values for steps 0..t_max.
Parameters
----------
t_max : int Total number of steps.
alpha0 : float Initial learning rate.
step_size : int Number of steps between decays.
gamma : float Decay factor per drop.
Returns
-------
np.ndarray LR at each step.
"""
# TODO: compute alpha(t) = alpha0 * gamma^floor(t/step_size) for t in range(t_max+1)
pass
lrs = step_decay_schedule(150)
print(lrs[:5], "...", lrs[30], "...", lrs[90])
Show solution — P2
def step_decay_schedule(t_max, alpha0=0.1, step_size=30, gamma=0.5):
t = np.arange(t_max + 1)
return alpha0 * gamma ** np.floor(t / step_size)
lrs = step_decay_schedule(150)
print(lrs[:5]) # [0.1 0.1 0.1 0.1 0.1]
print(lrs[30]) # 0.05 (first drop at t=30)
print(lrs[90]) # 0.0125 (three drops: 0.1 × 0.5³)
P3 — Implement a cosine annealing schedule.
import numpy as np
def cosine_annealing(t_max, alpha_max=0.1, alpha_min=0.0):
"""Return cosine-annealed LR values for steps 0..t_max.
Parameters
----------
t_max : int Total number of steps (one cycle).
alpha_max : float Peak learning rate.
alpha_min : float Minimum learning rate.
Returns
-------
np.ndarray LR at each step.
"""
# TODO: alpha(t) = alpha_min + 0.5*(alpha_max - alpha_min)*(1 + cos(pi*t/t_max))
pass
lrs = cosine_annealing(150)
print(lrs[0], lrs[75], lrs[150]) # should be ~alpha_max, ~midpoint, ~alpha_min
Show solution — P3
def cosine_annealing(t_max, alpha_max=0.1, alpha_min=0.0):
t = np.arange(t_max + 1)
return alpha_min + 0.5 * (alpha_max - alpha_min) * (1 + np.cos(np.pi * t / t_max))
lrs = cosine_annealing(150)
print(lrs[0]) # 0.1 (= alpha_max)
print(lrs[75]) # 0.05 (= midpoint)
print(lrs[150]) # 0.0 (= alpha_min)
Self-Test — 5 Questions
Write your answer before revealing.
Q1 — What does the second moment v_t in Adam track?
Q2 — At step t = 1, what is the bias-correction factor for m when β₁ = 0.9?
Q3 — What is the effective learning rate in Adam for a parameter whose gradient has been consistently ≈ 0.3 for many steps? (α = 0.001)
Q4 — In step decay, what happens to the LR at t = 2s?
Q5 — Why does cosine annealing outperform step decay in practice?
Cross-references: Training Loop and Optimisers (base module) · Regularisation and Normalisation — dropout and LayerNorm, the two regularisation techniques built into every Transformer encoder layer. Hands-on PyTorch training rungs arrive with the VN-056 training clinic (coming soon).
Ask the mentor about this module
Ask a question about this content. The mentor explains and grounds its answer in what you are studying; asking is recorded as a learning signal, not a grade.
Try it yourself
A scratch console for this page's ideas — ungraded, nothing you run here is recorded.
Scratch console
A scratch console with the scientific stack (pandas, numpy, scikit-learn). Runs on the server — no network, resource-limited and measured.
Output appears here.
Where next?
Later in Deep-Learning Architectures