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\)).

  1. Output gradient (the famous clean result of softmax+CE): \(\dfrac{\partial L}{\partial\vz_2}=\hat\vy-\vy\).

  2. Layer 2: \(\dfrac{\partial L}{\partial\mW_2}=(\hat\vy-\vy)\,\va_1\tp\), \(\;\dfrac{\partial L}{\partial\vb_2}=\hat\vy-\vy\).

  3. 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]\).

  4. 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 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 built

Those 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, db2
  • Andrej KarpathyNeural Networks: Zero to Hero (builds backprop and a GPT from scratch; exceptional).

  • Michael NielsenNeural 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.