A neural network is a stack of learned feature transformations followed by a linear model. Each layer applies an affine map then a nonlinearity; composing many of them lets the network represent functions that no single linear model can. Everything you learned in Phases 0–1 carries over: dot products, gradients, cross-entropy, regularization, the bias–variance tradeoff. The new ingredient is depth, and the new skill is computing gradients through many composed functions: backpropagation.
A neural network is just a big differentiable function \(f_{\vtheta}(\vx)\) with millions of parameters \(\vtheta\). We pick a loss, compute \(\nabla_{\vtheta} L\) by the chain rule (backprop), and descend. That is the entire algorithm. Phases 3–6 only change the architecture of \(f\); the training loop stays the same.
Neural network basics
The perceptron and the MLP
A single artificial neuron computes \(a=\phi(\vw\tp\vx+b)\) — a dot product, a bias, and a nonlinearity \(\phi\). A multilayer perceptron (MLP) stacks layers of these: \[\vh^{(1)}=\phi(\mW^{(1)}\vx+\vb^{(1)}),\quad \vh^{(2)}=\phi(\mW^{(2)}\vh^{(1)}+\vb^{(2)}),\quad\dots,\quad \hat\vy=\mW^{(L)}\vh^{(L-1)}+\vb^{(L)}.\]
Without a nonlinearity, stacking layers collapses: \(\mW^{(2)}(\mW^{(1)}\vx)=(\mW^{(2)}\mW^{(1)})\vx\) is still linear. The nonlinearity is what gives depth its power. The universal approximation theorem says even one hidden layer (wide enough) can approximate any continuous function — but depth makes this efficient, reusing features hierarchically.
Activation functions
| Activation | Definition | Notes |
|---|---|---|
| Sigmoid | \(\sigma(z)=\frac{1}{1+e^{-z}}\) | saturates, vanishing gradients; use only at output |
| Tanh | \(\tanh(z)\) | zero-centered sigmoid; still saturates |
| ReLU | \(\max(0,z)\) | default; cheap, no saturation for \(z>0\); “dying ReLU” |
| Leaky ReLU | \(\max(\alpha z, z)\) | small slope \(\alpha\) for \(z<0\) fixes dying ReLU |
| GELU | \(z\,\Phi(z)\) | smooth; standard in Transformers |
| Swish/SiLU | \(z\,\sigma(z)\) | smooth, often \(\ge\) ReLU in deep nets |
Loss functions
Regression uses MSE; classification uses cross-entropy on top of a softmax. Both are negative log-likelihoods (Phase 0). For numerical stability, frameworks fuse softmax and cross-entropy into one op ( takes raw logits, not probabilities).
Backpropagation — derive it once by hand
Backprop is the chain rule applied systematically over a computational graph, reusing intermediate results so the cost of all gradients equals a constant multiple of one forward pass.
For a layer \(\vz=\mW\va+\vb\), \(\,\vh=\phi(\vz)\), given the upstream gradient \(\boldsymbol{\delta}=\partial L/\partial\vh\), backprop computes: \[\frac{\partial L}{\partial \vz}=\boldsymbol{\delta}\odot\phi'(\vz),\quad \frac{\partial L}{\partial \mW}=\frac{\partial L}{\partial \vz}\,\va\tp,\quad \frac{\partial L}{\partial \vb}=\frac{\partial L}{\partial \vz},\quad \frac{\partial L}{\partial \va}=\mW\tp\frac{\partial L}{\partial \vz},\] where \(\odot\) is elementwise product. The last term is the gradient passed to the previous layer — the recursion.
Network: \(\vz_1=\mW_1\vx+\vb_1\), \(\va_1=\relu(\vz_1)\), \(\vz_2=\mW_2\va_1+\vb_2\), \(\hat\vy=\softmax(\vz_2)\), loss \(L=-\sum_k y_k\log\hat y_k\) (one-hot \(\vy\)).
Output gradient (the famous clean result of softmax+CE): \(\dfrac{\partial L}{\partial\vz_2}=\hat\vy-\vy\).
Layer 2: \(\dfrac{\partial L}{\partial\mW_2}=(\hat\vy-\vy)\,\va_1\tp\), \(\;\dfrac{\partial L}{\partial\vb_2}=\hat\vy-\vy\).
Backprop to hidden: \(\dfrac{\partial L}{\partial\va_1}=\mW_2\tp(\hat\vy-\vy)\), then \(\dfrac{\partial L}{\partial\vz_1}=\dfrac{\partial L}{\partial\va_1}\odot\mathbb{1}[\vz_1>0]\).
Layer 1: \(\dfrac{\partial L}{\partial\mW_1}=\dfrac{\partial L}{\partial\vz_1}\,\vx\tp\), \(\;\dfrac{\partial L}{\partial\vb_1}=\dfrac{\partial L}{\partial\vz_1}\).
Do this derivation on paper at least once. The pattern — multiply by the local derivative, then push back through \(\mW\tp\) — is all of backprop.
The cleanness of \(\partial L/\partial\vz_2=\hat\vy-\vy\) is not a coincidence: it is why softmax pairs with cross-entropy and sigmoid pairs with binary cross-entropy. The gradient is simply “prediction minus target,” which is also the gradient of linear regression’s MSE — a deep unity across models.
Weight initialization
If weights are too large, activations explode; too small, they vanish. Principled schemes keep the variance of activations (and gradients) roughly constant across layers:
Xavier/Glorot (for tanh/sigmoid): \(\Var(w)=\frac{2}{n_\text{in}+n_\text{out}}\).
He (for ReLU): \(\Var(w)=\frac{2}{n_\text{in}}\) — accounts for ReLU zeroing half the inputs.
Never initialize all weights to the same constant (e.g. zero): every neuron in a layer then computes the identical gradient and stays identical forever — the symmetry-breaking failure. Random init breaks symmetry. Getting initialization wrong is a classic reason a network “won’t train” even though the code is correct.
Optimization
Gradient descent variants
Batch GD uses the whole dataset per step (accurate, slow, memory-heavy). Stochastic GD (SGD) uses one example (noisy, fast). Mini-batch (32–512 examples) is the practical sweet spot — it exploits vectorized hardware and the gradient noise actually helps generalization.
Momentum and adaptive methods
\[\begin{align*} \text{Momentum:}&\quad \vv_t=\beta\vv_{t-1}+\nabla L,\quad \vtheta_t=\vtheta_{t-1}-\eta\vv_t \\ \text{RMSProp:}&\quad s_t=\rho s_{t-1}+(1-\rho)(\nabla L)^2,\quad \vtheta_t=\vtheta_{t-1}-\eta\,\nabla L/\sqrt{s_t+\epsilon} \\ \text{Adam:}&\quad \text{momentum} + \text{RMSProp, with bias correction} \end{align*}\]
Adam (and AdamW) is the default optimizer for deep learning. Momentum accelerates along consistent directions and damps oscillation (fixing the zig-zag you saw in Phase 0); RMSProp adapts the per-parameter step size. AdamW decouples weight decay from the gradient update and is the correct choice for Transformers. Start with Adam/AdamW; reach for tuned SGD+momentum when squeezing the last accuracy on vision models.
Learning-rate scheduling
The single most important hyperparameter is the learning rate. Common schedules: step decay (cut by 10\(\times\) at milestones), cosine annealing (smooth decay to near zero), and warmup (ramp up over the first few hundred steps — essential for Transformers to avoid early divergence). A learning-rate finder (sweep LR and watch loss) quickly brackets a good value.
Vanishing/exploding gradients and clipping
In deep or recurrent nets, repeated multiplication by Jacobians can shrink gradients to zero (vanishing) or blow them up (exploding). Mitigations: ReLU-family activations, careful init, normalization layers, residual connections (Phase 3), and gradient clipping (cap the gradient norm) — standard practice when training RNNs and Transformers.
Regularization & generalization
Deep nets have enormous capacity, so controlling overfitting is central.
Dropout: randomly zero a fraction of activations during training; an implicit ensemble that prevents co-adaptation. Disabled at inference.
Weight decay (L2): penalize large weights; in AdamW this is decoupled from the adaptive scaling.
Early stopping: halt when validation loss stops improving — cheap and effective.
Data augmentation: expand the effective dataset with label-preserving transforms (crucial in vision, Phase 3).
Normalization layers
Batch normalization normalizes each feature across the mini-batch, stabilizing and accelerating training (and acting as a mild regularizer). Layer normalization normalizes across features within each example — batch-size independent and the standard in Transformers and RNNs.
BatchNorm behaves differently in train vs. eval mode (it uses running statistics at inference). Forgetting (and ) at inference is a top source of mysterious metric drops. BatchNorm also misbehaves with very small batches — prefer GroupNorm/LayerNorm there.
Frameworks
Autograd and computational graphs
Frameworks record operations into a graph and apply backprop automatically. PyTorch builds the graph dynamically (define-by-run), so models are plain Python — easy to debug and to write custom architectures. Each tensor carries ; calling populates .
PyTorch — the recommended primary framework
PyTorch is transparent, dominant in research, and the path of least resistance for custom architectures (everything in Phases 3–6). The canonical training loop is worth memorizing:
import torch, torch.nn as nn
model = nn.Sequential(nn.Linear(784, 256), nn.ReLU(),
nn.Linear(256, 10)) # logits
opt = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
loss_fn = nn.CrossEntropyLoss() # expects raw logits
for epoch in range(epochs):
model.train()
for xb, yb in train_loader:
opt.zero_grad() # 1. clear old gradients
logits = model(xb) # 2. forward pass
loss = loss_fn(logits, yb) # 3. compute loss
loss.backward() # 4. backprop (autograd)
opt.step() # 5. update parameters
model.eval()
with torch.no_grad():
... # validation, no graph builtThose five lines — zero, forward, loss, backward, step — are the heartbeat of all deep learning, from MNIST to GPT. Everything else (data loading, architectures, schedulers, mixed precision) wraps around this loop.
TensorFlow / Keras
Worth recognizing: Keras offers a concise high-level API, and TensorFlow has strong production/mobile tooling (TF Lite, TF Serving) — relevant if you ever need on-device inference. You do not need to learn it deeply now; concepts transfer directly from PyTorch.
Checkpoint project
Part A — raw NumPy, manual backprop (no autograd). Implement a 2-layer MLP for MNIST:
forward pass (linear \(\to\) ReLU \(\to\) linear \(\to\) softmax), cross-entropy loss;
manual backprop using the worked example above;
mini-batch SGD with He initialization;
a gradient check: compare your analytic gradients to numerical \(\frac{L(\theta+\epsilon)-L(\theta-\epsilon)}{2\epsilon}\) — they must agree to \(\sim\)1e-6. This single test catches almost every backprop bug.
Target \(\ge\)95% test accuracy and a smoothly decreasing loss curve.
Part B — reimplement in PyTorch. The same architecture using and . Confirm you get comparable accuracy with far less code, then experiment: swap SGD\(\to\)Adam, add dropout and weight decay, add a LR schedule, and plot how each affects the validation curve.
Deliverables: both implementations, the gradient-check output, overlaid loss/accuracy curves, and a short reflection on what autograd did for you and what each regularizer changed.
import numpy as np
def softmax(z):
z = z - z.max(1, keepdims=True) # numerical stability
e = np.exp(z); return e / e.sum(1, keepdims=True)
def forward(X, W1, b1, W2, b2):
Z1 = X @ W1 + b1; A1 = np.maximum(0, Z1)
Z2 = A1 @ W2 + b2; P = softmax(Z2)
return Z1, A1, Z2, P
def backward(X, Y, Z1, A1, P, W2): # Y is one-hot
n = X.shape[0]
dZ2 = (P - Y) / n # softmax+CE gradient
dW2 = A1.T @ dZ2; db2 = dZ2.sum(0)
dA1 = dZ2 @ W2.T
dZ1 = dA1 * (Z1 > 0) # ReLU derivative
dW1 = X.T @ dZ1; db1 = dZ1.sum(0)
return dW1, db1, dW2, db2Andrej Karpathy — Neural Networks: Zero to Hero (builds backprop and a GPT from scratch; exceptional).
Michael Nielsen — Neural Networks and Deep Learning (free online; the clearest backprop derivation).
PyTorch official tutorials (pytorch.org) — start with “Learn the Basics”.
Deep Learning (Goodfellow, Bengio, Courville) — Part II for the authoritative treatment.
Phase 2 self-check
Derive backprop for a 2-layer net on paper and pass a numerical gradient check in code.
Explain why nonlinearities and proper initialization are necessary.
Contrast SGD, momentum, RMSProp, and Adam, and say when you would use each.
Explain dropout, weight decay, batch vs. layer norm, and early stopping.
Write the 5-step PyTorch training loop from memory and explain each line.