Chapter 93 — Deep Learning & Training Dynamics
📖 Definition
A neural network is a stack of layers; each layer does a linear transform (W·x + b) followed by a non-linear activation. Training repeats four steps:
- Forward pass — compute predictions.
- Loss — measure the error (cross-entropy for classification/next-token, MSE for regression).
- Backward pass (backpropagation) — use the chain rule to compute the gradient of the loss for every parameter.
- Optimizer step — move parameters a little against the gradient.
🔍 Backpropagation in One Paragraph
The loss depends on the output, which depends on the last layer, which depends on the previous layer, and so on. The chain rule lets us compute ∂loss/∂weight for every weight by passing gradients backwards layer by layer, reusing intermediate results from the forward pass. That's why training needs memory for activations — they are stored during the forward pass for use in the backward pass.
💻 Code Example — A Training Loop in PyTorch
import torch
from torch import nn
model = nn.Sequential(
nn.Linear(784, 256), nn.GELU(), nn.Dropout(0.1),
nn.Linear(256, 10),
)
loss_fn = nn.CrossEntropyLoss(label_smoothing=0.1)
opt = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.01)
sched = torch.optim.lr_scheduler.OneCycleLR(opt, max_lr=3e-4, total_steps=10_000)
for step, (x, y) in enumerate(train_loader):
model.train()
logits = model(x) # 1. forward
loss = loss_fn(logits, y) # 2. loss
opt.zero_grad(set_to_none=True)
loss.backward() # 3. backward
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # stability
opt.step() # 4. update
sched.step()
if step % 500 == 0:
model.eval()
with torch.no_grad():
val_loss = evaluate(model, val_loader)model.train() / model.eval() matter: dropout and batch norm behave differently in each mode.
📊 Activation Functions
| Activation | Formula idea | Notes |
|---|---|---|
| Sigmoid | squashes to (0, 1) | Saturates → vanishing gradients; used for binary outputs |
| Tanh | squashes to (−1, 1) | Also saturates |
| ReLU | max(0, x) |
Simple, fast; "dead ReLU" if always negative |
| GELU | smooth ReLU | Used in BERT/GPT-2 |
| SwiGLU | gated (Swish × linear) | Used in LLaMA-style LLMs; better quality per parameter |
| Softmax | turns scores into probabilities summing to 1 | Output layer for classes / next token |
📊 Optimizers
| Optimizer | Idea | Use |
|---|---|---|
| SGD | step = −lr · gradient | Simple; with momentum still strong for vision |
| Momentum | keeps a running average of gradients → smoother, faster | |
| RMSProp | per-parameter step size from recent gradient magnitude | |
| Adam | momentum + per-parameter scaling | Default for most deep learning |
| AdamW | Adam with decoupled weight decay | Standard for transformers/LLMs |
| Adafactor / Lion / 8-bit Adam | lower memory | Large models |
Adam vs AdamW (common senior question): in Adam, L2 regularisation added to the loss gets rescaled by the adaptive learning rate, so it doesn't behave like real weight decay. AdamW applies weight decay directly to the weights, separately from the gradient step — better generalisation.
Adam keeps two extra values per parameter (first and second moment), which is why optimizer memory is large for LLMs.
🔍 Learning Rate & Schedules
The learning rate is the most important hyperparameter.
- Too high → loss explodes or oscillates. Too low → slow, stuck.
- Warmup: start small and ramp up over the first ~1–5% of steps (Adam's estimates are noisy at the start).
- Cosine decay (or linear decay) down to ~10% of peak afterwards.
- Bigger batch → can usually use a bigger learning rate.
- Fine-tuning uses a much smaller LR than pretraining (e.g. 1e-5 to 2e-4).
🔍 Normalisation
| Type | Normalises over | Used in |
|---|---|---|
| Batch norm | The batch, per feature | CNNs; depends on batch size |
| Layer norm | Features of each single example | Transformers (works for any batch size, sequences) |
| RMSNorm | Like layer norm without mean-centering | LLaMA-style LLMs (cheaper) |
Pre-LN vs Post-LN: modern transformers put normalisation before attention/FFN (pre-LN), which makes deep models train stably without careful warmup.
🔍 Vanishing & Exploding Gradients
| Problem | Cause | Fixes |
|---|---|---|
| Vanishing | Many layers of small derivatives multiply → ~0 | ReLU-family activations, residual connections, normalisation, good init |
| Exploding | Large derivatives multiply → huge | Gradient clipping, lower LR, normalisation, good init |
Residual connections (x + layer(x)) give gradients a "highway" straight back through the network — the reason 100-layer transformers train at all.
Initialisation: Xavier/Glorot (tanh/sigmoid), He/Kaiming (ReLU) keep activation variance stable across layers.
🔍 Mixed Precision Training
| Format | Bits | Notes |
|---|---|---|
| FP32 | 32 | Full precision; master weights |
| FP16 | 16 | Fast, small range → needs loss scaling to avoid underflow |
| BF16 | 16 | Same range as FP32, less precision → no loss scaling; standard for LLMs |
| FP8 | 8 | Newest GPUs (H100+); used for training/inference with care |
scaler = torch.amp.GradScaler() # only needed for fp16, not bf16
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
loss = loss_fn(model(x), y)
loss.backward()🔍 Memory Tricks for Big Models
| Trick | Saves | Cost |
|---|---|---|
| Gradient accumulation | Simulate a big batch with small micro-batches | More steps per update |
| Gradient (activation) checkpointing | Activation memory — recompute in backward | ~20–30% more compute |
| Mixed precision | Half the memory for activations/weights | Need care with stability |
| 8-bit optimizers | Optimizer state memory | Small quality risk |
| Sharding (FSDP / ZeRO) | Splits weights/grads/optimizer across GPUs | Communication |
🔍 Reading Loss Curves
| What you see | Likely cause | Action |
|---|---|---|
| Loss is NaN | LR too high, fp16 overflow, bad data (div by 0) | Lower LR, bf16, clip gradients, check data |
| Loss spikes then recovers | Bad batches, LR too high | Clip, skip bad batches, lower LR |
| Train ↓, val ↑ | Overfitting | Regularise, more data, early stop |
| Both flat and high | Underfitting or LR too low / bug | Overfit a tiny batch first to test the pipeline |
| Val better than train | Dropout/augmentation only in train | Usually fine |
Debugging tip: "Can the model overfit 10 examples to ~0 loss?" If not, there's a bug in data, labels or code — not a modelling problem.
🔍 CNNs and RNNs (Short, for Context)
- CNN: shares small filters across an image → translation-invariant features; still used for vision (ResNet) though Vision Transformers compete at scale.
- RNN / LSTM / GRU: process sequences step by step with a hidden state; hard to parallelise and weak on long-range dependencies → replaced by transformers for language.
🧠 What a Senior Answer Adds
- Explains why an optimizer or norm is chosen (stability, memory), not only what it is.
- Knows memory budgets: weights + gradients + optimizer states + activations.
- Debugs systematically: overfit a small batch, check data, then tune.
🎯 Likely Interview Questions
- Explain backpropagation. — Chain rule applied backwards through layers to get each parameter's gradient, reusing forward activations.
- Why do we need non-linear activations? — Without them, stacked linear layers collapse into one linear function.
- Adam vs AdamW? — AdamW decouples weight decay from the adaptive gradient update — correct regularisation, standard for transformers.
- Why use learning-rate warmup? — Early Adam statistics are noisy and big early updates destabilise training.
- Batch norm vs layer norm? — Across the batch vs across features of one example; transformers use layer norm/RMSNorm.
- What causes vanishing gradients and how do you fix them? — Repeated small derivatives; ReLU-family, residuals, normalisation, init.
- What does gradient clipping do? — Caps the gradient norm to prevent exploding updates.
- BF16 vs FP16? — Same 16 bits; BF16 keeps FP32's exponent range so no loss scaling is needed.
- What is gradient checkpointing? — Don't store all activations; recompute them in backward — trades compute for memory.
- How does dropout work at inference? — It's turned off; activations are scaled during training so expected values match.
- Your loss became NaN — what do you check? — LR, precision overflow, bad inputs/labels, division by zero, clip gradients.
- Why residual connections? — Gradient highway; lets very deep networks train.
← ML Fundamentals | Index | Next: Transformer Architecture →