Backpropagation & Optimizers
- Rumelhart, Hinton, and Williams (1986) popularized backpropagation for training multi-layer networks
- Based on the chain rule of calculus applied to computational graphs
- Adam optimizer (Kingma & Ba, 2014) is now the default optimizer for most deep learning tasks
Backpropagation computes gradients of the loss with respect to every weight in the network by applying the chain rule backward from the output. Optimizers (SGD, Adam, AdaGrad) use these gradients to update weights, iteratively reducing the loss.
- Chain rule is the mathematical backbone - connects to automatic differentiation in PyTorch/JAX
- Learning rate scheduling (cosine annealing, warmup) builds on the basic optimizer framework
- Vanishing/exploding gradients connect to initialization strategies, batch norm, and residual connections
- Derive reverse-mode automatic differentiation as the chain rule applied right-to-left on a DAG, with cost equal to one forward pass per output.
- State the SGD, momentum, Nesterov, Adagrad, RMSprop, Adam, and AdamW updates and explain when each is preferred.
- Prove (sketch) Adam's bias correction and explain why Adam + L2 differs from AdamW.
- Pick a learning-rate schedule (cosine annealing, warmup) and explain why warmup helps with Adam in transformers.
- Apply gradient clipping and mixed-precision training, and recognize when L-BFGS or K-FAC is the right choice.
TL;DR
Backpropagation is reverse-mode automatic differentiation specialized to feed-forward DAGs [1]. It computes every parameter gradient in time proportional to one forward pass. The optimizer family layered on top is a tower of corrections: SGD is the baseline; momentum (Polyak 1964) [9] averages gradients to dampen noise; Nesterov (1983) [8] evaluates the gradient at the look-ahead point; Adagrad [6] rescales each parameter by its accumulated squared gradient; RMSprop [10] replaces the sum with an exponential moving average; Adam [2] combines momentum with RMSprop and adds bias correction; AdamW [3] decouples weight decay from the adaptive update. Cosine annealing [4] and warmup [5] are the standard schedules; gradient clipping and mixed precision [11] are the standard production fixes.
Intuition
Training a neural network is gradient descent on a high-dimensional, non-convex loss surface. Two pieces have to work together:
- A way to compute the gradient. Backprop computes for all parameters at once in a single sweep over the network, reusing intermediate results.
- A rule for taking the next step. SGD steps directly along the negative gradient. Everything fancier (momentum, Adam, AdamW) is a different way to pick the step size and direction given a stream of stochastic gradient estimates.
The optimizers in this section all share the same skeleton: maintain some running statistics of past gradients (mean, second moment, both, or neither), use those statistics to scale the next step, and trade more state for faster convergence and more stable training.
For a scalar loss that depends on parameters through a DAG, reverse-mode autodiff (backprop) computes all in a single backward pass over the graph. The cost is at most a small constant (typically 2 to 4) times the forward pass, regardless of the number of parameters. This is what makes training networks with billions of parameters feasible.
Forward-mode autodiff has the opposite trade-off: cheap per-input partial , but you need one pass per input. For ML where there is one scalar loss and millions of parameters, reverse mode wins by orders of magnitude.
Math
Reverse-mode autodiff: chain rule on a DAG
Setup
Model the computation as a directed acyclic graph where each node stores a value computed from its parents via some elementary operation:
Designate one node as the loss and a subset of nodes as parameters . We want for every parameter.
Forward pass
Evaluate nodes in topological order. Cache every . Time and memory are both .
Backward pass
Define the adjoint . The adjoint of the loss node is . For every other node , by the chain rule applied to the children :
Process nodes in reverse topological order. For each node, fetch the adjoints of its children (already computed) and multiply by the local Jacobian. The local Jacobian is computed at the cached forward values, which is why the forward pass had to run first.
Cost
Every edge in is touched once in the forward pass and once in the backward pass. So time is for both passes, and the backward pass is at most a small constant times the forward pass (typically 2-4× because of cache pressure and slightly more arithmetic per local Jacobian).
Memory
The naive implementation stores every intermediate activation ( memory). For very deep networks this is the dominant cost; checkpointing recomputes activations in segments to trade compute for memory, taking the cost from memory and time to memory and time. [14]
Why it works for arbitrary DAGs
Backprop is just reverse-mode autodiff specialized to feed-forward networks. For arbitrary DAGs (residual connections, attention, weight tying), the same algorithm applies: process in reverse topological order and accumulate the adjoint contributions from every child. Modern frameworks (PyTorch’s autograd, JAX’s grad, TensorFlow’s GradientTape) all implement this.
From SGD to Nesterov: the first-order family
Notation: parameters , stochastic gradient at step is , learning rate , momentum coefficient .
SGD
Pros: cheap (no extra state), well-understood theory. Cons: noisy on stochastic gradients, slow on ill-conditioned problems (the step size must be at most where is the largest curvature, but the slowest direction has curvature , so convergence rate is ).
Polyak’s heavy-ball momentum
The velocity is an exponential moving average of past gradients with effective horizon . This dampens oscillations across narrow valleys (the perpendicular components average to zero) while accumulating progress along the valley floor.
Convergence rate on quadratic objectives: where is the condition number, roughly times faster than vanilla GD on ill-conditioned quadratics [9]. Typical for deep learning: 0.9 (effective horizon 10 steps).
Nesterov’s accelerated gradient
Evaluate the gradient at the look-ahead point rather than at :
The look-ahead lets the algorithm “see” the curvature ahead and correct earlier. On smooth convex problems Nesterov’s method achieves the optimal rate (vs for plain GD) [8]. In practice on deep nets the win over plain momentum is small but consistent and free [7].
Adaptive optimizers: Adagrad, RMSprop, Adam, AdamW
The first-order family scales every parameter with the same learning rate . Adaptive methods give each parameter its own learning rate, scaled by past gradient magnitude.
Adagrad
(Element-wise.) Each parameter’s effective learning rate decays like along directions that have seen large gradients. Strong on sparse gradients (NLP with bag-of-words features, recommender embeddings) where rare features get to keep a large step. Convergence guarantees on online convex problems [6]. Weakness: grows unboundedly and the effective LR eventually dies.
RMSprop
Replace the sum with an exponential moving average:
This fixes the dying-LR problem. Hinton introduced it in lecture 6.5 of his 2012 Coursera class [10]; it never had a paper but is widely used.
Adam
Combine RMSprop (second-moment EMA) with momentum (first-moment EMA):
with bias correction (see below):
and update
Defaults from Kingma & Ba [2]: , , , . Adam is the default optimizer for most deep-learning workloads outside CV.
Why bias correction
At , , biased toward zero. Computing undoes the bias. Without it, the early steps would be a factor of smaller than intended, making the network train too slowly for the first few hundred steps; over-time it self-corrects but the warm-up period is significant. The same logic applies to .
AdamW: decoupled weight decay
The original Adam paper recommended adding L2 regularization through the gradient: before the moment updates. Loshchilov & Hutter showed this is wrong: the L2 term gets divided by along with everything else, so parameters with large second moments end up with effectively less weight decay than parameters with small second moments. The fix is to decouple weight decay from the gradient:
The decay term is a uniform pull toward the origin, applied directly to without going through the adaptive scaling. AdamW [3] is the default for transformer training (GPT, BERT, ViT).
Schedules and tricks
A lot of “what makes training work” lives in the learning-rate schedule and a small set of stability tricks.
Cosine annealing with warm restarts
The cosine schedule (Loshchilov & Hutter 2016) [4] decays the learning rate from to as
The variant SGDR resets at the end of each cycle (warm restart); each restart escapes flat regions and explores new local minima. Now the de facto schedule for image classification.
Linear warmup
Start at and linearly ramp to over the first ~5% of training. Standard for transformers and large-batch CV (Goyal et al. 2017 trained ImageNet in 1 hour on 256 GPUs by carefully scheduling warmup with batch size; see “linear scaling rule”) [5]. Empirically the largest stability benefit is for Adam in transformers, where the second-moment estimate is noisy at the start and a too-large learning rate quickly diverges.
Gradient clipping
When a single batch produces a huge gradient (a step into a non-smooth region of the loss), clip its norm:
Standard for RNNs (where exploding gradients are routine) and for transformers under mixed precision. Typical : 1.0 or 5.0.
Mixed precision
Store activations and gradients in fp16 (or bf16) but keep a master copy of weights and the optimizer state in fp32. Use a dynamic loss-scaling factor to keep small gradients out of the fp16 subnormal range. The result: ~2× memory and ~1.5-2× throughput at no measurable accuracy loss [11]. Implemented in torch.cuda.amp and tf.keras.mixed_precision.
Second-order methods
Briefly: when batch size is large enough that one batch is essentially the full gradient, second-order methods can win. Two examples:
- L-BFGS [12]: maintains an implicit low-rank approximation of the inverse Hessian using the last pairs of . Memory . Used for batch optimization in scientific computing and sometimes for fine-tuning small models.
- K-FAC [13]: approximates the Fisher information matrix as a Kronecker product of layer-local matrices, then preconditions the gradient. Sometimes useful on small networks but adds significant complexity. Mostly displaced in practice by Adam + careful schedules.
For most modern training, the first-order family (Adam/AdamW with cosine + warmup) is the right default; second-order methods are a niche tool.
Optimizer comparison
| Optimizer | State per parameter | Adaptive LR? | Typical hyperparameters | When to use | Watch out for |
|---|---|---|---|---|---|
| SGD | 0 | no | lr 0.01-0.1 | CV from scratch with momentum | Slow on ill-conditioned problems. |
| SGD + momentum | 1 (velocity) | no | lr 0.01-0.1, momentum 0.9 | ResNet-style CNNs, ImageNet baselines | Tune lr per architecture. |
| Nesterov | 1 | no | lr 0.01-0.1, momentum 0.9 | Slight upgrade over momentum | Same as momentum. |
| Adagrad | 1 (G_t) | yes (per-param) | lr 0.01-0.1 | Sparse gradients (NLP bag-of-words) | LR dies as G_t grows; rarely used today. |
| RMSprop | 1 (v_t EMA) | yes | lr 1e-3, beta2 0.99 | RNNs and pre-Adam workloads | Mostly displaced by Adam. |
| Adam | 2 (m_t, v_t) | yes | lr 1e-3, beta1 0.9, beta2 0.999 | Default for transformers and most non-CV | Adam + L2 is not weight decay; use AdamW. |
| AdamW | 2 | yes | lr 1e-3 to 5e-4, weight_decay 0.01-0.1 | Default for transformers, ViT, LLM finetuning | Decay weight is decoupled, not part of the gradient. |
Implementations
NumPy implementations of SGD, momentum, and Adam side-by-side, then PyTorch’s torch.optim and JAX/Optax. Each version trains the same tiny MLP on XOR.
import numpy as np
# Each optimizer is a (state -> next state) pure function.
def sgd_step(params, grads, state, lr=0.1):
return [p - lr*g for p, g in zip(params, grads)], state
def momentum_step(params, grads, state, lr=0.1, rho=0.9):
if state is None: state = [np.zeros_like(p) for p in params]
new_v = [rho*v + g for v, g in zip(state, grads)]
new_p = [p - lr*v for p, v in zip(params, new_v)]
return new_p, new_v
def adam_step(params, grads, state, lr=1e-3, b1=0.9, b2=0.999, eps=1e-8):
if state is None:
state = ([np.zeros_like(p) for p in params],
[np.zeros_like(p) for p in params],
0)
m, v, t = state
t += 1
new_m = [b1*mi + (1-b1)*g for mi, g in zip(m, grads)]
new_v = [b2*vi + (1-b2)*g*g for vi, g in zip(v, grads)]
bc1 = 1 - b1**t
bc2 = 1 - b2**t
new_p = [p - lr * (mi/bc1) / (np.sqrt(vi/bc2) + eps)
for p, mi, vi in zip(params, new_m, new_v)]
return new_p, (new_m, new_v, t)Run a head-to-head numpy comparison of SGD, momentum, and Adam on a tiny MLP for XOR. Watch how Adam reaches a low loss in far fewer steps:
(no output yet)
Interactive demo
A two-dimensional toy: gradient descent on . Step the ball forward, change the learning rate, and watch the algorithm converge or oscillate. The same dynamics generalize to high-dimensional loss surfaces.
Real-world uses
| Domain | Optimizer | Schedule | Why |
|---|---|---|---|
| Modern image classification (ResNet, EfficientNet) | SGD + momentum 0.9 | Cosine annealing, warmup ~5% | Tradition + good generalization on CV; many papers report SGD beats Adam at final test accuracy. |
| Transformers (BERT, GPT) | AdamW | Linear warmup + linear or cosine decay | Adaptive LR is essential for the wide range of gradient scales across attention vs FFN layers; AdamW because plain Adam mishandles weight decay. |
| Vision Transformers and hybrids | AdamW | Warmup + cosine | Same reasoning as transformers; CNN tricks (SGD + momentum) do not transfer to ViTs. |
| LLM fine-tuning (full-parameter) | AdamW | Constant or short cosine | Fine-tuning is short enough that fancy schedules do not matter much; stability does. |
| LLM fine-tuning (LoRA / PEFT) | AdamW (only on adapter weights) | Constant | Most parameters are frozen; the optimizer state is small enough to ignore. |
| Reinforcement learning (policy gradient) | Adam | Constant | Noisy gradients benefit from adaptive scaling; weight decay is rarely needed when episodes provide implicit regularization. |
Pitfalls
Adding to the gradient before the moment update means the decay term gets divided by along with the rest. Parameters with large second moments end up with effectively less decay than parameters with small ones, the opposite of what L2 regularization is supposed to do. AdamW decouples the decay so it acts uniformly on . For BERT/GPT/ViT-class models the gap between Adam+L2 and AdamW is several percent on downstream tasks [3].
Too small a learning rate stalls (you watch the loss plateau, then declare the model “doesn’t work”); too large diverges (loss to infinity in a few steps). The sweet spot can span an order of magnitude or two. The cheapest way to find it: an LR-finder sweep (Smith 2017): exponentially ramp the LR from very small to very large over a single epoch, plot the loss, pick the largest stable LR.
Without warmup, the first few hundred Adam steps have a noisy second-moment estimate , and the resulting per-parameter step sizes are wildly miscalibrated. Combined with the typical large LR of for transformers, this makes early training diverge frequently. A linear warmup from 0 to peak over the first ~5% of steps fixes this [5]. CNNs trained with SGD+momentum do not need warmup nearly as urgently because momentum is much less sensitive to early-step noise.
The “linear scaling rule” (Goyal et al. 2017) [5]: when you multiply batch size by , multiply the learning rate by and re-tune the warmup. Otherwise momentum overshoots the new gradient regime. This is the trick that lets large-batch training keep pace with small-batch baselines.
The conventional order in PyTorch is: loss.backward() -> clip_grad_norm_ -> optimizer.step() -> scheduler.step(). Swapping clip_grad_norm_ with optimizer.step() clips the wrong quantity. This is a common bug in tutorial code.
Pop quiz
- 1. What is the asymptotic cost of one backward pass relative to one forward pass?
- 2. Why do we apply the bias correction m_hat = m / (1 - beta1^t) in Adam?
- 3. Why is AdamW preferred over Adam + L2 regularization?
- 4. Why does linear warmup help Adam in transformer training?
- 5. Which technique is most effective at preventing gradient explosion in RNNs?
Interview Q&A
Derive backprop on a 2-layer MLP from first principles.onsite
Forward pass. For input , weights , , and biases :
with loss .
Adjoints. Let . Then by the chain rule
For the hidden layer, , and
Why this is reverse-mode autodiff. Each adjoint is computed from the next-layer adjoint and a local Jacobian (the activation derivative and the weight matrix transpose). Processed in reverse topological order, the cost is one local Jacobian-vector product per node, equal to the forward pass cost up to a constant. [1]
Derive Adam's update rule with bias correction.onsite
Maintain two EMAs:
with .
Bias derivation. Unroll: . If is approximately constant at value , then , biased toward zero by the factor . Define to undo this; analogously .
Update.
The numerator is a momentum-style EMA of past gradients; the denominator is a per-parameter normalization (RMSprop-style). Defaults: , , , . [2]
Why does AdamW outperform Adam + L2 on transformer training?research
In Adam + L2, the regularizer is added to the gradient: . Then the moment updates and the divide by are applied. The effect of the decay on parameter is
Parameters with large (typically large outgoing weights, or attention queries/keys) see effectively smaller decay; parameters with small see effectively larger decay. The intended uniform pull toward zero is corrupted into a non-uniform pull weighted by gradient magnitudes.
AdamW decouples by writing
The decay term is the same for every parameter. Empirically this matters: Loshchilov & Hutter and many subsequent papers show 0.5-2% absolute accuracy gains on ImageNet ViTs and language model perplexity. AdamW is the default for every transformer optimizer in HuggingFace and PyTorch out of the box [3].
Sketch SGD's convergence proof on a smooth convex objective.research
Assume is convex, -smooth (), and the stochastic gradient satisfies and .
For SGD with step size :
By convexity, . Take expectations and rearrange:
Sum from to with and apply Jensen on the average iterate:
So SGD on smooth convex problems converges at rate in function value. With strong convexity the rate improves to ; deep learning has neither, but the bound’s intuition (smaller step + decreasing implies smaller variance in the limit) carries over and motivates LR decay schedules [14].
A short project that takes about an evening:
- Implement the from-scratch numpy MLP from the previous page, plus the SGD / momentum / Adam optimizers above. Train on MNIST.
- Reproduce the loss curves: SGD at vs momentum at the same vs Adam at . Plot all three on one axis.
- Add cosine annealing and a 5% warmup. Compare to constant LR.
- Add gradient clipping at and try to find an LR that diverges without it but converges with it.
- Stretch: implement L-BFGS with a memory of and run it on a small linear regression. Notice it converges in a handful of iterations, but the per-iteration cost is much higher than Adam.
References
- Rumelhart, D. E., Hinton, G. E., and Williams, R. J. (1986). Learning representations by back-propagating errors. Nature 323, 533-536. doi:10.1038/323533a0
- Kingma, D. P. and Ba, J. (2014). Adam: A Method for Stochastic Optimization. ICLR 2015 (arXiv preprint 2014). arXiv:1412.6980
- Loshchilov, I. and Hutter, F. (2017). Decoupled Weight Decay Regularization. ICLR 2019 (arXiv preprint 2017). arXiv:1711.05101
- Loshchilov, I. and Hutter, F. (2016). SGDR: Stochastic Gradient Descent with Warm Restarts. ICLR 2017 (arXiv preprint 2016). arXiv:1608.03983
- Goyal, P., Dollár, P., Girshick, R., Noordhuis, P., Wesolowski, L., Kyrola, A., Tulloch, A., Jia, Y., and He, K. (2017). Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour. arXiv preprint. arXiv:1706.02677
- Duchi, J., Hazan, E., and Singer, Y. (2011). Adaptive Subgradient Methods for Online Learning and Stochastic Optimization. Journal of Machine Learning Research 12, 2121-2159. https://jmlr.org/papers/v12/duchi11a.html
- Sutskever, I., Martens, J., Dahl, G., and Hinton, G. (2013). On the importance of initialization and momentum in deep learning. Proc. 30th International Conference on Machine Learning (ICML), 1139-1147. https://proceedings.mlr.press/v28/sutskever13.html
- Nesterov, Y. (1983). A method for solving the convex programming problem with convergence rate O(1/k^2). Doklady Akademii Nauk SSSR 269(3), 543-547. https://www.mathnet.ru/eng/dan46009
- Polyak, B. T. (1964). Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 4(5), 1-17. doi:10.1016/0041-5553(64)90137-5
- Tieleman, T. and Hinton, G. (2012). Lecture 6.5 - rmsprop: Divide the gradient by a running average of its recent magnitude. COURSERA: Neural Networks for Machine Learning. https://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf
- Micikevicius, P., Narang, S., Alben, J., Diamos, G., Elsen, E., Garcia, D., Ginsburg, B., Houston, M., Kuchaiev, O., Venkatesh, G., and Wu, H. (2017). Mixed Precision Training. ICLR 2018 (arXiv preprint 2017). arXiv:1710.03740
- Liu, D. C. and Nocedal, J. (1989). On the limited memory BFGS method for large scale optimization. Mathematical Programming 45(1), 503-528. doi:10.1007/BF01589116
- Martens, J. and Grosse, R. (2015). Optimizing Neural Networks with Kronecker-factored Approximate Curvature. ICML 2015. arXiv:1503.05671
- Goodfellow, I., Bengio, Y., and Courville, A. (2016). Deep Learning. MIT Press. https://www.deeplearningbook.org
Interactive Demo
Tip: Click Forward to watch activations propagate, then Backward to see gradients flow in reverse