Home Chinland InfoTech Academy
Ready
All Courses

Deep Learning — Interactive Concept Lab

The deep-learning companion lab — training mechanics, CNNs, sequence models, transformers, modern LLMs, generative models, and deep RL. Click, drag, and animate each idea to see how it works. Foundations (neurons, activations, backprop, convolution, attention, embeddings, RNNs) live in the ML Concept Lab. Press Esc for this menu.

74 interactive concepts· 9 clusters
Deep dives — guided, end-to-end
No concepts match — try another term.
Why depth is hard

Vanishing & exploding gradients

Backprop multiplies gradients layer by layer on the way back. If each factor is a little below 1, the product shrinks toward zero over many layers — early layers stop learning. A little above 1 and it explodes. This single fact is why naive deep nets refused to train for decades.

Per-layer factor

weight scale

Sigmoid / tanh path

The product

∂L/∂x₁ ∝ f(depth)f = w · φ′
factor f
verdict

The fixes

This is why we use ReLU (derivative 1 when active, not 0.25 like sigmoid), careful initialization, residual/skip connections, and normalization — each keeps that per-layer factor near 1 so the signal survives the trip back.

Forward & reverse

Computational graph & autodiff

Every model is a graph of simple operations. The forward pass flows values up to the loss; reverse-mode autodiff (backprop) flows gradients back down, applying the chain rule at each node — computing every parameter's gradient in a single sweep. This is the engine inside every framework.

Inputs

a
b
c

Forward pass

The graph

L = (a·b + c)²
forward: compute m=a·b, s=m+c, L=s²
backward: ∂L/∂s=2s, then chain to a, b, c

Why reverse mode

One backward sweep yields the gradient of one output with respect to all inputs — exactly what training needs (one loss, millions of parameters). Forward-mode would cost one sweep per parameter.

Breaking symmetry

Weight initialization

Where you start matters. Initialize every weight to the same value and all neurons in a layer learn identically forever — symmetry never breaks. Initialize too large or too small and activations explode or vanish through the layers. Xavier and He initialization pick the variance that keeps the signal stable.

Initialization scheme

He / Xavier

The variance rule

Var(W) = 2nin (He, for ReLU)
activation std at layer 10

What the bars show

Each bar is the spread (std) of activations at that layer. Flat across depth = healthy signal. Collapsing to zero = vanishing; growing without bound = exploding/saturating. Zeros init makes every neuron identical — the network can never differentiate them.

ReLU, GELU & friends

Activation zoo

Non-linearities are what let a deep net bend into complex shapes — without them, stacked layers collapse into one linear map. But each activation has a personality: sigmoid/tanh saturate (flat tails → tiny gradients), ReLU is cheap but can "die," and smooth variants like GELU and Swish power modern nets. Compare curves and their derivatives.

Activation

ReLU

Reading the plot

Solid = the activation φ(x). Dashed = its derivative φ′(x) — the multiplier backprop sees. Where the derivative is ~0 (flat tails, ReLU's left half), gradient stops flowing.

gradient at x=−3

Picking one

ReLU is the cheap default but "dies" when stuck negative (zero gradient). LeakyReLU keeps a small slope. GELU/Swish are smooth and tend to train a touch better in transformers and deep CNNs. Sigmoid/tanh now mostly live in gates and outputs, not hidden layers.

Warmup & decay

Learning-rate schedules

The learning rate is the single most important knob — and the best value changes over training. Big steps early to make fast progress, small steps late to settle into a minimum. Schedules automate this: step decay, exponential, cosine, and the warmup that stabilizes the volatile first steps of large models.

Schedule

Cosine annealing

Why it helps

Too-high a constant rate bounces around the minimum forever; too-low crawls. Decaying lets you have both. Warmup — ramping up over the first few hundred steps — prevents the huge, destabilizing updates that big batches and transformers suffer at initialization.

Past the threshold

Double descent

Classical statistics says test error follows a U: too simple underfits, too complex overfits. Modern over-parameterized nets break this — push capacity past the point of exactly fitting the data (the interpolation threshold) and test error, after spiking, falls a second time. It's why enormous models generalize at all.

Model capacity

Under-parameterized

Readings

train error
test error

The three zones

Under-parameterized: the classical U — test error dips then rises. Interpolation threshold: the model just barely fits every point; test error peaks here. Over-parameterized: among the many zero-train-error solutions, gradient descent prefers smooth ones, so test error descends again.

Verify backprop

Gradient checking

Backprop is easy to get subtly wrong. Gradient checking is the safety net: compare your analytic gradient against a brute-force numerical one from finite differences. If they agree to ~7 decimal places, your code is right; if not, you've caught a bug. Shrink ε and watch the secant collapse onto the true tangent.

Controls

step size ε = 1e-3

Correct backprop

The check

gnumf(w+ε) − f(w−ε)
numerical
analytic
relative error
verdict

The ε sweet spot

Drag ε very small and the relative error rises again — floating-point cancellation swamps the answer. Too large and the finite-difference approximation is inaccurate. Around 1e-5 to 1e-7 is the safe zone.

Tame the cliff

Gradient clipping

Some loss landscapes have cliffs — regions where the gradient is enormous. One such step can hurl your parameters far away and blow up training. Clipping caps the gradient's norm: keep the direction, bound the length. Watch the ball ride the descent, then toggle clipping off and see it get flung off the cliff.

Controls

threshold c = 2.0

Clipping ON

Clip by norm

if ‖g‖ > c:  g ← c · g‖g‖
raw ‖g‖
applied ‖g‖
Noise vs speed

Mini-batch size

Each step estimates the gradient from a batch of examples. A tiny batch is cheap but noisy — the path jitters. A huge batch is smooth and accurate but expensive, with fewer updates per epoch. The noise isn't all bad: it helps escape sharp minima. Slide the batch size and watch the descent path change character.

Batch size

batch = 8 examples

Mini-batch

The trade-off

gradient noise ∝ 1 / √(batch)
noise level
updates / epoch
Bend into any shape

Universal approximation

A neural network with a single hidden layer can approximate any continuous function — given enough neurons. Each ReLU unit contributes one kink; sum enough of them and you can trace any curve. Add hidden neurons and watch the network's piecewise approximation close in on the target.

Hidden neurons

N = 3 units → 4 linear pieces

Too few neurons

The theorem

For any continuous f on a bounded domain and any ε, there exists a one-hidden-layer net whose output stays within ε of f everywhere.

max error
How a net computes

The forward pass

A neural network is a chain of simple steps: each layer multiplies its inputs by a weight matrix, adds a bias, and applies a non-linearity. Data flows left to right, layer by layer, until it becomes a prediction. Step through a 3→4→4→2 network and watch real numbers propagate through every unit.

Walk the layers

layer 0 / 3

Input layer

This layer computes

General form: a(l) = φ(W(l)a(l−1) + b(l)), where φ is the activation.

Why stacking works

A single linear layer can only draw a straight boundary; the non-linearity between layers is what lets composition build curves, corners, and complex shapes. Remove the activations and the whole stack collapses back into one linear map — depth would buy nothing.

How a net learns

Backpropagation

Backprop is how a network assigns blame. After the forward pass computes the loss, it sweeps backward, using the chain rule to find exactly how much each weight contributed to the error — every gradient in a single pass. Step through a tiny 2→2→1 sigmoid network with real numbers and watch the error flow back.

Step through it

step 0 / 10

Start — the input

The four equations

BP1  δL = (a − y) ⊙ σ′(z)
BP2  δ(l) = (W(l+1)T δ(l+1)) ⊙ σ′(z)
BP3  ∂C/∂b = δ
BP4  ∂C/∂W = δ · a(l−1)T

It is just the chain rule

For a first-layer weight, ∂L/∂W¹ = ∂L/∂a² · ∂a²/∂z² · ∂z²/∂a¹ · ∂a¹/∂z¹ · ∂z¹/∂W¹. Every factor is local and cheap. Backprop’s trick is to compute the shared prefix once (the δ terms) and reuse it for every weight — turning an exponential blow-up into one linear sweep.

Step downhill

Gradient descent

Learning is gradient descent: compute the slope of the loss, then take a step in the opposite (downhill) direction. The gradient points toward steepest ascent, so we subtract it. The learning rate sets the step size — too small and you crawl, too large and you bounce out. Watch the trajectory on an elliptical bowl.

Controls

learning rate η = 0.18

Mini-batch gradient descent

The update rule

w ← w − η · ∇L(w)
learning rate η0.18
behaviour

Stability needs η < 2/λmax; below 1/λmax the descent is monotone.

SGD · Momentum · Adam

Optimizers

Plain gradient descent struggles in a ravine — a valley that's steep across and shallow along. It zig-zags down the walls and crawls along the floor. Optimizers fix this: momentum builds speed, RMSProp adapts each parameter's step, and Adam combines both. Switch optimizer and watch the path; the faint trails are the others, for comparison.

Optimizer

Adam

Update rule

g = ∇L · μ,ρ,β are decay rates · ε guards the divide

Why it matters

Real loss surfaces are badly conditioned — some directions far steeper than others. Adapting the step per-direction (RMSProp/Adam) or carrying momentum turns a slow zig-zag into a fast glide, which is why Adam is the default starting point for most deep nets.

Drop to generalize

Dropout

During training, dropout randomly switches off a fraction of neurons on every forward pass. Each pass trains a different thinned network, so no neuron can rely on any specific other — they can't co-adapt. It's like training a huge ensemble of sub-networks that share weights. At test time all neurons are on, scaled to compensate. Watch the active sub-network change each step.

Controls

dropout rate p = 0.40

Training

The rule

a ← (m ⊙ a) / (1 − p),  m ~ Bernoulli(1−p)
active neurons

Why it generalizes

Forcing the network to work with random subsets stops neurons from forming fragile, co-dependent detectors. The result behaves like averaging exponentially many sub-networks — a powerful, almost free regularizer. Inverted dropout divides by (1−p) during training so the test-time pass needs no rescaling.

Normalize the batch

Batch normalization

Batch norm steadies training by normalizing each layer's pre-activations across the mini-batch — subtract the batch mean, divide by the batch std — then a learnable scale (γ) and shift (β) let the network undo or reshape that normalization if it helps. Step through the transformation and watch the distribution snap to zero-mean, unit-variance, then move where γ and β want it.

Stage

γ (scale) = 1.0
β (shift) = 0.0

Raw activations

The transform

n = (x − μB) / √(σ²B + ε)  ·  y = γn + β
batch mean μ
batch std σ
Stop before overfit

Early stopping

Train long enough and training loss keeps falling — but validation loss bottoms out and then climbs as the model starts memorizing noise. Early stopping watches the validation curve, keeps the best checkpoint, and halts once it hasn't improved for patience epochs. Press play and watch the curves diverge.

Controls

patience = 8 epochs

How it works

Checkpoints

current epoch0
best (val min)
stop at
Sliding filters

The convolution operation

A convolution slides a small kernel across the image; at each position it computes a dot product between the kernel and the patch beneath it, producing one number in the output feature map. The same kernel is reused everywhere — so the layer learns a feature once and detects it anywhere. Pick a kernel and watch it sweep.

Kernel (3×3)

Vertical-edge detector

One output value

(out)ij = Σ (patch ⊙ kernel)

Teal = positive response, pink = negative. The running Σ on the canvas is the value being written into the highlighted output cell.

Why convolution, not dense?

A dense layer would need a separate weight for every pixel-to-pixel link — millions of them. Convolution shares one tiny kernel across all positions: far fewer parameters, and translation-equivariant (a feature is detected wherever it appears). This weight-sharing is the core idea that makes vision nets feasible.

Output size

Padding & stride

Two knobs control how a convolution moves and how big its output is. Stride is how far the kernel jumps each step; padding adds a border of zeros so the kernel can reach the edges. Together they set the output dimensions — and whether the map keeps its size or shrinks.

Stride

Padding

Valid padding

Output size

output map

N = input, K = kernel = 3, P = padding, S = stride.

Stride as downsampling

Stride 1 visits every location; stride 2 skips every other one, roughly halving each output dimension — a cheap way to downsample inside the convolution instead of adding a separate pooling layer (common in modern architectures).

Downsampling

Pooling

Pooling shrinks a feature map by summarizing each small window with a single number — usually the maximum. It cuts computation, enlarges the effective receptive field of later layers, and grants a little translation invariance: a feature shifting by one pixel often lands in the same pooled cell. Watch a 2×2 window stride across a 6×6 map.

Pooling type

Max pooling

This layer

2×2 window · stride 2 · 6×6 → 3×3

In max pooling the brightest cell in each window (highlighted) is the one that survives.

The trade-off

Pooling makes the network cheaper and more robust to small shifts, but it is deliberately lossy — it discards where within the window the feature was. Some modern nets drop pooling entirely and downsample with strided convolutions instead.

What each neuron sees

Receptive field

A single neuron deep in a CNN doesn't see the whole image — only a patch of it, its receptive field. Each stacked conv layer widens that patch, so deep units integrate information from a large region while early ones stay local. Add layers and watch the cone of dependence grow back to the input.

Depth & kernel

conv layers L = 3

3×3 kernels

Receptive field

RF = 1 + L · (K − 1)  (stride 1)
field at the top unit

Growing it faster

Stride and pooling multiply the field instead of adding to it, and dilated convolutions skip pixels to expand it without extra parameters. That's how a network reaches a field covering the whole image within a manageable number of layers.

Train very deep nets

Residual & skip connections

Stacking dozens of plain layers paradoxically makes a net harder to train — gradients vanish on the long trip back. A residual block adds a shortcut: out = F(x) + x. The layers only learn the residual, and the identity skip gives gradients a clear path home. This one idea (ResNet) unlocked networks hundreds of layers deep.

Skip connection

With skip connection

The block

out = F(x) + x

F(x) is the conv→ReLU→conv path; the +x is the identity skip. If F learns nothing useful, the block falls back to passing x through unchanged — so adding depth can never hurt.

Edges → textures → objects

What filters learn

Train a CNN and a hierarchy emerges on its own. Early layers learn generic edge and color detectors; middle layers combine them into textures and motifs; deeper layers assemble parts; the last layers respond to whole objects. Step through the depths to see the abstraction climb (patterns shown are illustrative).

Layer depth

Layer 1 — edges & colors

Why this matters

Because early features are generic, a network trained on millions of images can be reused for a new task — keep the early layers, retrain only the last few. That's transfer learning, and it's why you rarely train a vision model from scratch.

LeNet → ResNet

Architecture timeline

CNNs didn't arrive fully formed — they evolved. From LeNet reading digits in 1998, to AlexNet shocking ImageNet in 2012, through VGG's depth, Inception's efficiency, and ResNet's skip connections, each milestone added one key idea and drove the error rate down. Tap a model to see what it contributed.

Milestone

ResNet (2015)

At a glance

depth
ImageNet result
Learnable upsampling

Transposed convolution

To go the other way — make a feature map bigger (for segmentation masks, image generation, decoders) — we use transposed convolution. Instead of collapsing a patch to one value, each input pixel stamps a learned kernel into a region of the output; overlapping stamps add up. Watch the small grid expand into a large one.

Stride

Stride 2

Output size

out = (N − 1) · S + K
3×3 input →
Bigger field, same cost

Dilated convolution

Want a wider view without more weights or losing resolution? Dilated (atrous) convolution spreads the kernel's taps apart, leaving gaps. The same nine weights now cover a much larger region — the receptive field grows exponentially with stacked dilation while the parameter count stays fixed. Change the dilation rate and watch the field expand.

Dilation rate

Dilation 2

Effective field

K + (K − 1)(d − 1),  weights = K² = 9
field covered
Cheap convolutions

Depthwise separable convolution

A standard convolution mixes space and channels in one expensive step. Depthwise separable convolution factorizes it: first filter each channel spatially (depthwise), then mix channels with a 1×1 convolution (pointwise). The result is the engine of mobile-friendly nets like MobileNet — often ~8–9× cheaper for nearly the same accuracy.

Convolution type

Depthwise separable

Cost (multiply-adds / pixel)

this layer
vs standard
Free training data

Image augmentation

The cheapest regularizer in vision: transform each training image in label-preserving ways so the model sees endless variety and stops memorizing. Flips, crops, rotations, color jitter, and cutout all teach invariances — a flipped cat is still a cat. Pick a transform and watch it animate.

Augmentation

Horizontal flip

Why it works

Each transform encodes an invariance you want the model to have. It effectively multiplies your dataset for free and is one of the most reliable ways to cut overfitting — but every transform must keep the label correct, which is domain-specific.

State through time

RNN unrolling & BPTT

A recurrent network reads a sequence one step at a time, carrying a hidden state forward as memory. "Unrolling" draws the same cell repeated across time — the same weights at every step. Training runs backprop through that unrolled chain (BPTT), and that's where the trouble starts: gradients from distant steps fade away. Toggle the forward and backward passes.

Pass

Forward pass

The recurrence

hₜ = tanh(Wₓ xₜ + Wₕ hₜ₋₁ + b)

Wₓ and Wₕ are the same at every step. The output is yₜ = Wᵧ hₜ. One fixed-size state must summarize the entire past.

Gated memory

LSTM & GRU gates

Plain RNNs forget quickly because their gradient decays. Gated cells fix this with a nearly-linear memory path that gates only nudge: the LSTM has forget, input, and output gates plus a cell-state highway; the GRU is a leaner two-gate version. Slide the gate value to see how long a memory survives.

Cell type

forget gate value = 0.85

LSTM

Gates are sigmoids

gate = σ(W·[hₜ₋₁, xₜ] + b) ∈ (0,1)

Each gate outputs values between 0 (block) and 1 (pass). Multiplying the memory by a gate near 1 preserves it; near 0 erases it.

Learning embeddings

Word2Vec (skip-gram)

Before transformers, this is how machines learned word meaning: predict a word's neighbours from the word itself. Words appearing in similar contexts drift to similar vectors, and relationships become directions you can do arithmetic on — king − man + woman ≈ queen. Switch views to see training, the resulting space, and analogies.

View

Skip-gram training

Why it mattered

Dense embeddings replaced sparse one-hot vectors and gave models a notion of similarity for free. The same idea — learn vectors so context is predictable — underlies the token embeddings at the input of every modern language model.

Past + future context

Bidirectional RNN

A plain RNN only knows the past. But meaning often depends on what comes after — "the bank was steep" only resolves once you read "steep". A bidirectional RNN runs two passes, left-to-right and right-to-left, and concatenates them, so every word is encoded with full context. Toggle between forward-only and bidirectional.

Direction

Bidirectional

Combined state

hₜ = [ h⃗ₜ ; h⃖ₜ ]

The forward and backward hidden states are concatenated, doubling the representation size and giving each position both directions of context.

Encoder → decoder

Sequence-to-sequence

Translation and summarization map one sequence to another of different length. The classic design: an encoder RNN reads the whole input into a single fixed context vector, and a decoder RNN generates the output from it, one token at a time. It works — until the input gets long and that one vector can't hold everything. Try a long sentence.

Input length

Short input

The bottleneck

c = encoder(x₁…xₙ),  yₜ = decoder(c, y<ₜ)

Every output token is generated from the same fixed-size c. The longer the input, the more information that single vector must throw away.

Fix the bottleneck

Attention (Bahdanau)

Attention removes the single-vector bottleneck: instead of compressing everything into one context, the decoder looks back at all encoder states and takes a weighted average, with weights it computes fresh at each step. It learns to align each output word with the input words that matter — and this mechanism is the seed of the entire transformer.

View

Live attention weights

Weighted context

αᵢ = softmax(score),  c = Σ αᵢ hᵢ

The weights αᵢ sum to 1 and depend on the current decoder state, so the context vector changes for every output token.

Better than greedy

Beam search

When a model generates text it must choose tokens one by one. Greedy decoding always grabs the single most likely next token — but the best sequence may start with a less likely word. Beam search hedges: it keeps the top-k partial sequences alive at each step, scoring them by total probability. Widen the beam and watch it find a better path.

Beam width

Beam width 2

Scoring

score = Σ log P(tokenₜ)
best sequence
its probability
greedy would pick
Scoring translation

BLEU score

How do you grade a machine translation automatically? BLEU compares the candidate to a reference by counting overlapping n-grams (1- to 4-word chunks), with two safeguards: clipping stops repetition gaming, and a brevity penalty punishes too-short output. Pick a candidate to see how each design choice rewards or destroys the score.

Candidate

Good translation

Components

1·2·3·4-gram precision
brevity penalty
BLEU =
Attention is all you need

Self-attention (Q · K · V)

This is the engine of the transformer. Every token looks at every other token and rewrites itself as a relevance-weighted blend of them. It does this with three learned projections — Query, Key, Value — a dot-product score, a scale, a softmax, and a weighted sum. Step through the whole machine with real numbers, and switch which token is doing the attending.

Attending from

Step 0 / 8

The input sequence

The whole thing in one line

Attention(Q,K,V) = softmax( Q·KT / √dk ) · V

Q = E·WQ, K = E·WK, V = E·WV — three learned projections of the same input E.

Why it changed everything

Unlike an RNN, every position is computed in parallel and any token can reach any other in one step — no vanishing gradient over distance. Watch out: comparing every token to every other is O(n²) in sequence length, which is why long-context efficiency is a major research area.

Many views at once

Multi-head attention

One attention pattern is limiting — a word may relate to others in several ways at once (its subject, its article, the verb it belongs to). Multi-head attention runs several attention computations in parallel, each with its own Q/K/V projections, so each head can specialize. Their outputs are concatenated and mixed by a final projection. Tap a head to see what it focuses on.

Head

Head 1 — previous-token

Combining heads

headᵢ = Attention(Q Wᵢ, K Wᵢ, V Wᵢ)
MultiHead = Concat(head₁…headₕ) Wᴼ

Each head works in a smaller subspace (dmodel/h), so total compute matches one full-size attention.

Injecting order

Positional encoding

Attention treats its input as a set — shuffle the tokens and the output just shuffles too. But "dog bites man" ≠ "man bites dog", so order must be injected. The original transformer adds sinusoidal encodings: each position gets a vector of sines and cosines at geometrically-spaced frequencies, a unique fingerprint the model can read. Explore the pattern.

View

highlight dimension = 4

Heatmap of encodings

The formula

PE(pos,2i) = sin(pos / 100002i/d)
PE(pos,2i+1) = cos(pos / 100002i/d)
Attention + FFN + norm

The transformer block

Stack the pieces and you get the unit that repeats N times to form a transformer. An encoder block is two sub-layers — multi-head self-attention and a feed-forward network — each wrapped in a residual connection and layer norm. A decoder block adds masked self-attention and cross-attention. Watch a representation flow through, residuals and all.

Block type

Encoder block

Each sub-layer

out = LayerNorm( x + Sublayer(x) )

The x + is the residual skip; the feed-forward is two linear layers with an activation between. This wrapper is what makes deep stacks trainable.

Normalize per token

Layer normalization

Transformers normalize differently from CNNs. Layer norm rescales each token's feature vector on its own — across features, not across the batch. That makes it independent of batch size and indifferent to sequence length, which is exactly what attention needs. Compare the two normalization axes side by side.

Normalize over

Layer norm (per token)

Per group

y = γ · (x − μ) / √(σ² + ε) + β

Same formula as batch norm — only the axis the statistics are computed over changes. Layer norm: each row (token). Batch norm: each column (feature).

No peeking ahead

Causal masking

A model that generates text left-to-right must never see the future — when predicting word 3 it can only use words 1–2. Causal masking enforces this inside attention: before the softmax, every score where a token would attend to a later token is set to −∞, so its weight becomes exactly zero. Toggle the mask and watch the attention matrix change shape.

Masking

Masked (causal)

How it's done

scoreᵢⱼ ← −∞ if j > i, then softmax

softmax(−∞) = 0, so future positions get zero attention weight. GPT-style decoders use this; BERT-style encoders don't.

Text → tokens

Tokenization (BPE)

A model doesn't see characters or words — it sees tokens. Byte-pair encoding starts from characters and repeatedly merges the most frequent adjacent pair, so common words become single tokens while rare ones split into reusable subwords. This covers any text with a fixed vocabulary and never hits an unknown word. See how different inputs break apart.

Example

Common words → whole tokens

Token count

tokens produced

Cost and context limits are measured in tokens, not words — and the split follows merge frequency, not grammar.

Watch a model write

Generating text, end-to-end

Here is the whole transformer, working on real text. A prompt goes in, and the model predicts the next token by running every stage you've seen — tokenize, embed, add position, masked attention through N layers, project to logits, softmax, sample — then appends the token and does it all again. Press Write and watch it generate one token at a time, or step through a single prediction in detail.

Run the model

Stage 0 / 9
temperature T = 0.80

Input context

Next-token probability

P(next | context) = softmax( logits / T )

Low T → near-greedy and repetitive; high T → more varied but riskier. This single loop, repeated, is all an LLM does.

Translate with cross-attention

Encoder–decoder, end-to-end

The original transformer translates. Its two halves work together: the encoder reads the whole source sentence at once and builds a context-rich representation; the decoder then generates the translation word by word, and at each step cross-attention lets it look back at the encoded source — aligning each output word with the input words it comes from. Run it on "le chat noir" and watch the alignment, including the adjective reorder.

Run the translation

Encoder · stage 1/4

Source input

Cross-attention

context = Σ α(target, source) · encoderₛ

The decoder's query comes from the target; the keys and values come from the encoder. That's the only place the two halves meet.

Why two halves

The encoder is bidirectional (it may see the whole source), while the decoder is masked (it can't see future target words). Cross-attention is the bridge — and dropping the encoder entirely gives you a decoder-only model like GPT.

Every equation, every shape

Transformer, the mathematics

The visual demos show what flows where; this one shows the linear algebra. We carry a tiny but real example — 3 tokens, d=4, 2 heads — through every stage of the forward pass, tracking the exact matrix operation, the resulting shape, and the derivation behind each design choice (why √dₖ, why residuals, how the loss factorizes). Step through encoding, then decoding, to output.

Walk the forward pass

Stage 1 / 11 —

Equations

Shapes

The chain rule, derived

Backpropagation, the mathematics

Backprop is just the chain rule applied with bookkeeping. On a small network — 2 inputs → 3 hidden → 1 output, with a squared-error loss — we do one forward pass, then derive the four equations that carry the error backwards and turn it into a gradient for every weight. Watch the signal go forward, then the error come back.

Walk the algorithm

Step 1 / 7 —

Equations

Shapes

Update rules, derived

Optimizers, the mathematics

Every optimizer is a rule for turning a gradient into a parameter step. Starting from plain gradient descent, we build up through SGD, momentum, RMSProp, and Adam — deriving each addition (a velocity, a per-parameter scale, a bias correction) and what problem it solves. The contour shows the step each rule would take.

Build up the optimizer

Step 1 / 7 —

Update rule

What it adds

Arithmetic & backprop

Convolution, the mathematics

A convolution layer is a small, shared linear map slid across a grid. Here we derive the operation itself, the output-size formula, the parameter count (and why sharing makes it tiny), the equivalent matrix multiply, how the receptive field grows, and the surprisingly elegant fact that its backward pass is also a convolution. Step through with a 5×5 input and a 3×3 kernel.

Walk the math

Step 1 / 7 —

Equations

Numbers

Greedy · top-k · top-p

Sampling strategies

Once the model gives next-token probabilities, something has to pick a token — and that choice shapes the whole output's character. Greedy is safe but dull; temperature dials randomness; top-k and top-p (nucleus) trim the unreliable tail in different ways. Here is one fixed distribution; switch strategies and see which tokens survive and get sampled.

Strategy

— (deterministic)

Greedy

Pick from the distribution

token ~ P, P = softmax(logits / T)

Top-k keeps the k highest; top-p keeps the smallest set summing to ≥ p; both renormalize before sampling.

Fast generation

KV cache

Generating each new token requires attending to every previous token — which needs their keys and values. But those never change once computed. The KV cache stores them, so each step recomputes K and V for only the one new token instead of the whole sequence. Toggle the cache and watch the wasted work appear or vanish as the text grows.

Cache

With KV cache

Cost & memory

cache = 2 · nlayers · n · d · bytes

Per-step compute: O(n) with the cache vs O(n²) without. The cache's size grows linearly with the sequence — eventually the memory bottleneck.

How much it can see

Context window

A model can only attend to a fixed maximum number of tokens — its context window. Feed it more and the earliest tokens fall outside and are simply not seen. And because attention compares every token to every other, the cost grows with the square of the window, which is why long context is expensive. Drag the window size to see both effects.

View

window size = 8 tokens

What fits

Attention cost

cost ∝ window² (every token attends to every token)
Bigger, predictably better

Scaling laws

One of the most striking facts in deep learning: test loss falls as a smooth power law in model size, dataset size, and compute — a straight line over many orders of magnitude on a log-log plot. That predictability is why labs invest in scale. But returns diminish, there's an irreducible floor, and for a fixed compute budget, size and data must be balanced.

Scale by

drag along the curve

Scaling parameters

The power law

L(X) ≈ E + (Xc / X)α

E is the irreducible loss; α the scaling exponent. Compute-optimal: C ≈ 6·N·D, balance N and D.

Learn from the prompt

In-context learning

A large model can pick up a brand-new task from a few examples placed in its prompt — with no weight updates, no training, nothing saved. It infers the pattern on the fly. Here the rule is "map a word to its letter count," which the model has to discover from the examples alone. Add shots and watch it go from guessing to confident.

Examples in the prompt

2 shot(s)

Few-shot (in-context)

No weights change

answer = model( examples ⊕ query )

The pattern is inferred from the prompt at inference time; the weights are frozen. This ability emerges mainly in large models.

Aligning with humans

RLHF

A pretrained model predicts likely text, not helpful text. Reinforcement Learning from Human Feedback aligns it in three stages: supervised fine-tuning on demonstrations, a reward model trained from human preference comparisons, then RL that optimizes the policy against that reward while a KL penalty keeps it close to where it started. Step through the pipeline.

Stage

1 · Supervised fine-tuning

RL objective

maximize E[ R(x,y) ] − β · KL(π ‖ πSFT)

Push toward high reward R, but the KL term keeps the policy π near the SFT model so it stays fluent and doesn't hack the reward.

Sparse, scalable capacity

Mixture-of-experts

Instead of one big feed-forward network, a mixture-of-experts layer has many expert networks and a router that sends each token to only a few of them — say the top 2 of 8. The model holds an enormous number of parameters but activates only a small fraction per token, buying capacity without paying full compute. The catch is keeping the experts evenly used.

View

Top-k routing

Sparse mixture

y = Σe ∈ top-k ge · Experte(x)

Active per token: 2 of 6 experts. Total parameters scale with the expert count, but compute scales with k only.

Fewer bits, smaller model

Quantization

Weights are usually stored as 32- or 16-bit floats, but you can map them to a small set of low-precision integer levels — INT8 or INT4 — to shrink memory and speed up inference. Fewer bits means a coarser grid and more rounding error. Switch precision and watch the quantization levels spread apart and the reconstruction error grow.

Precision

INT8 — 8-bit integers

Quantize → dequantize

q = round((x − z) / s), x̂ = s·q + z

s is the scale, z the zero-point. Levels = 2bits; coarser grid → larger error.

Reuse learned features

Transfer learning

Rarely do you train from scratch. A model pretrained on a huge dataset has already learned general features — edges, textures, shapes — in its early layers, and only its later layers are task-specific. Transfer learning reuses that backbone and adapts the top to a new task with far less data. Choose how much to freeze versus fine-tune.

Approach

trainable layers (from top) = 1

Feature extraction

What changes

freeze early layers · train top layers + head

Early layers are general and transfer across tasks; later layers are specific and need adapting.

Low-rank fine-tuning

LoRA

Fine-tuning every weight of a large model is expensive to train and store. LoRA's insight: the change a task induces is mostly low-rank, so freeze the original weight W and learn only a thin update ΔW = B·A through a rank-r bottleneck. You train a tiny fraction of the parameters, and can merge or swap the adapter freely. Drag the rank to see the savings.

View

rank r = 2

Low-rank update

The decomposition

W′ = W + B·A, A ∈ ℝr×d, B ∈ ℝd×r
trained params
Small model, big teacher

Knowledge distillation

To get a small, fast model that still works well, train it to mimic a large "teacher." The key is what it mimics: not just the hard correct label, but the teacher's full softened probability distribution — which encodes "dark knowledge" about how classes relate. Compare hard labels with soft targets, and soften with temperature.

Student learns from

temperature T = 2.0

Soft targets (distillation)

Loss

L = (1−α)·CE(y, student) + α·T²·KL(teacherT ‖ studentT)
Learning B erases A

Catastrophic forgetting

Train a network on task A, then move it to task B, and something alarming happens: as it learns B, its accuracy on A collapses. Gradient descent freely overwrites the weights A relied on, because nothing is testing A anymore. This is the central obstacle to continual learning — and the reason mitigations like rehearsal and EWC exist. Watch A's accuracy after the task switch.

Strategy

Naive sequential (forgets)

Protecting old knowledge

EWC: L = LB + λ Σi Fii − θ*A,i

Fi measures how important weight i was for A; the penalty resists changing it.

One model, many tasks

Multi-task learning

Instead of one model per task, train a single shared backbone with task-specific heads on several tasks at once. Each task becomes extra signal and regularization for the others, improving data efficiency. The risk is the flip side: when tasks pull the shared weights in opposing directions, they conflict and one can get worse.

View

Shared backbone

Combined loss

L = Σi wi · Li (shared θ, per-task heads)
Easy to hard

Curriculum learning

Like a school syllabus, present training examples in a meaningful order — easy first, then progressively harder — rather than all shuffled together. Clean early gradients build good representations that make the hard examples learnable faster and to a better optimum. Compare a curriculum against a random shuffle on the same task.

Ordering

Curriculum (easy → hard)

The idea

order examples by increasing difficulty

Helps most on hard or noisy tasks; needs a sensible difficulty measure.

Compress & reconstruct

Autoencoder

An autoencoder learns to copy its input through a narrow bottleneck: an encoder squeezes the input down to a small latent code, and a decoder rebuilds it. Forced through the bottleneck, the network must keep only what matters — a learned, nonlinear compression. Shrink the bottleneck and watch the reconstruction degrade; corrupt the input and watch it learn to clean.

Mode

latent size = 3

Compression (bottleneck)

Objective

minimize ‖x − decode(encode(x))‖²

A smaller latent = more compression = higher reconstruction error.

A smooth latent space

Variational autoencoder

A plain autoencoder's latent space is a bag of unrelated points — you can't sample from it. A VAE fixes this by encoding to a distribution (a mean and spread), sampling a code from it, and regularizing those distributions toward a standard normal prior. The result is a smooth, continuous latent space you can sample and interpolate — a true generative model.

View

Encode to a distribution

Reparameterize + regularize

z = μ + σ ⊙ ε, ε ~ N(0, 1)
L = ‖x − x̂‖² + KL( q(z|x) ‖ N(0,I) )
Generator vs discriminator

GAN

A generative adversarial network pits two networks against each other: a generator turns noise into fakes, and a discriminator tries to tell fakes from real data. Training is a game — the generator gets better at fooling, the discriminator at detecting — until the fakes are indistinguishable and the discriminator is reduced to a coin flip. Watch the two distributions converge.

View

The two players

The minimax game

minG maxD E[log D(x)] + E[log(1 − D(G(z)))]
Denoise into data

Diffusion models

Diffusion turns generation into denoising. A fixed forward process slowly adds Gaussian noise to a real sample until it's pure static; a learned reverse process starts from static and removes noise step by step until a coherent sample emerges. Train the network to predict the noise at each step, and you can generate from nothing. This is how modern image generators work.

Process

Forward (add noise)

Noising & denoising

xt = √ᾱt · x0 + √(1−ᾱt) · ε

The reverse model predicts ε at each step and subtracts a little of it.

Pull together, push apart

Contrastive learning

How do you learn good representations without labels? Make two random augmentations of the same image — a positive pair — and train the encoder so their embeddings land close, while embeddings of different images are pushed apart. The model has to capture what's essential and invariant. This self-supervised recipe underpins much of modern representation learning.

Force

Positive pair (pull together)

InfoNCE

L = −log [ esim(a,p)/τ / Σ esim(a,·)/τ ]

Pull the positive in, push all negatives out — no labels required.

Morph through latent space

Latent interpolation

A good generative model's latent space is smooth and meaningful, not a lookup table. Walk a straight line between two latent codes, decode each step, and the output morphs continuously through plausible in-betweens — proof the space is continuous. Directions can even carry meaning, so you can do arithmetic on concepts. Drag through the morph.

View

α = 0.50

Interpolate (morph)

Walking the latent

z = (1−α)·z₁ + α·z₂
Value of a state

Bellman & value functions

Reinforcement learning rests on one recursive idea: the value of a state is the best reward you can get now plus the discounted value of wherever you land next. That's the Bellman equation. Apply it repeatedly — value iteration — and value flows outward from the goal across a gridworld. The discount γ controls how far the reward's influence reaches.

View

discount γ = 0.90

Value iteration

The Bellman equation

V(s) = maxa [ R(s,a) + γ Σ P(s′|s,a) V(s′) ]
Explore vs exploit

Exploration (ε-greedy)

An agent that always picks the best-known option may never discover a better one; an agent that only explores never cashes in. ε-greedy is the simplest balance: with probability ε take a random action, otherwise take the best you know. Watch a multi-armed bandit's value estimates form, and how the exploration rate changes which arm it settles on.

Regime

ε = 0.10

Balanced

The rule

a = argmaxa Q(a) w.p. 1−ε, else random
Q-learning at scale

Deep Q-networks

Tabular value methods break when states are images or huge. A DQN approximates Q(s,a) with a neural net — but naive training is unstable, so two tricks make it work: experience replay (store transitions, train on random minibatches to break correlation) and a target network (a frozen copy that provides stable learning targets). Together they turned Q-learning into something that plays Atari.

Trick

Experience replay

TD loss

L = ( r + γ maxa′ Q⁻(s′,a′) − Q(s,a) )²

Q⁻ is the frozen target network; the squared TD error is minimized.

Optimize the policy directly

Policy gradient

Value methods learn what states are worth and act greedily. Policy gradients skip that and adjust the policy itself: push up the probability of actions that led to high return, push down the rest. It optimizes exactly what you care about and handles continuous actions — but raw returns make the gradient noisy, so a baseline is used to calm it. Watch the policy concentrate on the best action.

Method

With a baseline

The gradient

∇J(θ) = E[ ∇log π(a|s) · (R − b) ]

Scale each action's log-prob gradient by how much better than baseline b its return was.

Policy + value together

Actor-critic

Actor-critic fuses the two families. An actor (the policy) chooses actions; a critic (a value function) judges them. The critic's estimate replaces the noisy Monte-Carlo return that makes plain policy gradients shaky, so the actor learns from a low-variance advantage signal and can update every single step. It's the backbone of modern RL algorithms like A2C, PPO, and SAC.

View

Actor & critic

Advantage

A = r + γV(s′) − V(s)

Actor pushes by ∇log π · A; critic trains V toward r + γV(s′).

Deep dive · with real numbers

Neural Networks, from scratch

Most explanations stop at the diagram. Here we build a tiny but complete network, feed it a real dataset, and turn every crank by hand — the forward pass, the loss, the gradients, the weight update — with the actual numbers shown at each step. Then we train it live and watch it solve a problem no straight line can. By the end you'll have seen not just what a neural network is, but exactly what happens inside one.

1 · The idea

What a neural network actually is

Strip away the mystique and a neural network is just a function with adjustable knobs. You feed in numbers, it multiplies them by weights, bends the result through a nonlinear squashing function, and passes that to the next layer. "Learning" means nudging the weights so the output gets closer to the answer you wanted.

Neuron

Takes a weighted sum of its inputs, adds a bias, then applies a nonlinearity: a = σ(w·x + b).

Layer

Many neurons in parallel, each with its own weights — together they transform one vector into another.

Depth

Stacking layers lets the network compose simple features into complex ones. The nonlinearity is what makes depth worth having.

Learning

Measure error with a loss, compute how each weight affected it (gradient), and step every weight downhill.

2 · The essential math

Five expressions are the whole story

Everything below is built from these. A neuron computes a weighted sum, squashes it with an activation, the network chains layers into one function, a loss scores the output, and gradient descent improves the weights.

z = w·x + b
a = σ(z),   σ(z) = 1 / (1 + e−z)
ŷ = σ( W₂ · tanh(W₁x + b₁) + b₂ )
L = ½ (ŷ − y)²      w ← w − η · ∂L/∂w

The squashing function is the key trick: without it, stacking layers would just give another linear function. The sigmoid σ maps any number into (0, 1) — handy for a probability — while its cousin tanh maps into (−1, 1) and is common in hidden layers. Both are smooth, so we can take derivatives and run gradient descent.

3 · The dataset

A real problem: XOR

We'll teach the network exclusive-or: output 1 when exactly one input is on. It's tiny — four examples — but famous, because no single straight line can separate the 1s from the 0s. That's exactly why we need a hidden layer.

x₁x₂target y
000
011
101
110
4 · The architecture

2 inputs → 2 hidden → 1 output

The smallest network that can solve XOR: two inputs, a hidden layer of two tanh neurons, and one sigmoid output that reads as the probability of class 1. That's nine numbers to learn — six weights and three biases.

5 · The forward pass

Compute one prediction, by hand

Let's run the input x = [1, 0] (whose target is 1) through a network with some example weights, doing the actual arithmetic. Step through it:

6 · How it learns

Backpropagation, by hand

The prediction was 0.80 but should be 1. Backprop answers one question for every weight: which way, and how much, should I move you to reduce the loss? It applies the chain rule from the output backwards. Same example, real numbers:

7 · Gradient descent

Stepping downhill

Backprop hands us the gradient — the direction in which the loss increases. Gradient descent does the obvious thing: take a step the opposite way, sized by the learning rate η, and repeat. Here's that idea on a single weight — the curve is the loss, the dot is the weight, the amber arrow is the downhill direction. Roll it, and watch η decide everything.

η = 0.40
Too small an η and it crawls; too large and it overshoots the minimum, bouncing or flying off entirely; just right and it settles in a few steps. Notice it tends to fall into the nearer valley, not necessarily the deepest one — the very same local-minimum trap the live network hits below, and why momentum and a good starting point matter.
8 · Train it live

Watch the boundary form

Now the payoff. Hit Train and the network runs that exact loop — forward, loss, backprop, update — over all four points, thousands of times. The background colour is the network's output across the whole input plane; watch it bend until it carves the XOR pattern, and the loss curve fall. Reset re-randomises the weights so you can see it solve it again from a fresh start.

η = 0.6 speed = 35
epoch 0loss solved 0/4
predictions: 
When "solved" reads 4/4, every point sits in the correctly-coloured region — the two hidden neurons have each learned a line, and the output neuron combines them into the bent boundary XOR needs. That combination of simple pieces into something nonlinear is, in miniature, what every deep network does.
9 · Reading the output & scaling up

What the number means, and where this goes

The output 0.80 isn't arbitrary — because of the sigmoid it's a probability: the network's confidence that the answer is class 1. Threshold at 0.5 to decide. Everything you just saw scales directly: real networks use the same weighted-sum-then-nonlinearity neuron, the same loss-and-gradient-descent loop, just with millions of weights, many layers, and specialised structure.

Vision

Swap dense layers for convolutions and the same machinery recognises objects in images.

Language

Stack attention layers and it predicts the next token — the basis of modern LLMs.

Control

Let the output choose actions and train on reward, and it learns to play games or steer robots.

The constant

Weighted sums, nonlinearities, a loss, and gradient descent — the four ideas never change.

10 · Recap

What you saw

A neuron is a weighted sum plus a squash. A network chains them into a function with adjustable weights. A dataset and a loss define "right." The forward pass produces a prediction from real numbers; backprop assigns blame to each weight via the chain rule; gradient descent nudges them all downhill. Repeat, and a network with just two hidden neurons learns a pattern no line can split.

Want to go deeper on any single step? The concept cards break out each idea — the forward pass, backpropagation, gradient descent, activation functions, weight initialization — with their own focused animations.
Deep dive · with real numbers

Transformers, from scratch

The transformer powers every modern large language model, and its one essential idea — attention — sounds abstract until you watch the arithmetic. So we'll take the three-word sequence "the cat sat", give each word a tiny vector, and turn the full self-attention crank by hand: project to queries/keys/values, score every pair, softmax into attention weights, and blend the values — with every number on screen. Then you'll explore attention interactively and watch the model predict the next word.

1 · The idea

Every word looks at every other word

Older sequence models read words one at a time, passing a memory along. Transformers throw that out: every token looks at all the others at once and decides which are relevant. That mechanism is self-attention. It's parallel (fast to train), it handles long-range links directly, and stacked up it becomes GPT.

Token

Each word becomes a vector (an embedding) plus a marker of its position.

Query · Key · Value

Each token emits a query ("what I'm looking for"), a key ("what I offer"), and a value ("what I'll pass on").

Attention

Match queries to keys → weights; blend everyone's values by those weights. That's it.

Depth

Stack many attention+MLP blocks and the representations get richer at every layer.

2 · The data

A three-word sequence

Our input is "the cat sat" — three tokens at positions 0, 1, 2. We'll use an embedding dimension of just 4, and a single attention head that projects down to 2 dimensions, so every vector and dot-product is small enough to read.

positiontokenid
0the0
1cat1
2sat2
3 · Embeddings

Words become vectors

A lookup table maps each token id to a learned vector. Similar words get similar vectors; here each word is a point in 4-dimensional space. These are the raw material everything else operates on.

4 · Positional encoding

Telling the model the order

Attention sees the tokens as a set — it has no built-in sense of order. So we add a positional encoding: a fixed pattern of sines and cosines, different for each position, that stamps "where" onto each vector. The input to attention is x = embedding + position.

5 · Query, Key, Value

Three projections of each token

Three learned matrices turn every token vector x into a query q = x·WQ, a key k = x·WK, and a value v = x·WV. Here they map the 4-dim vectors down to 2-dim. Done for all tokens at once, that's just three matrix multiplies.

6 · Self-attention

Compute one token's context, by hand

Let's compute attention for the query token "cat" (position 1): how much should it draw from "the", "cat", and "sat", and what's the result? Every number is real — step through it.

7 · The attention matrix

Explore who attends to whom

Every token runs that same computation, giving a full attention matrix: row = the querying token, column = how much weight it puts on each other token (each row sums to 1). Click a row (or pick a query) to see its weights and the blended output. Toggle the causal mask to forbid looking at future tokens — the rule language models use when predicting left-to-right.

query:
The diagonal-plus-context pattern is the whole game: "cat" can pull information from "the" and "sat" in proportion to how well its query matches their keys. Stack and repeat, and the model routes meaning across a whole paragraph.
8 · Multi-head attention

Several attentions at once

One head captures one kind of relationship. Real transformers run several heads in parallel — each with its own WQ, WK, WV projecting into a small subspace — then concatenate the results and mix them with one more matrix. Different heads learn different patterns (one tracks syntax, another nearby words, and so on).

Let's actually do it with two heads for the last token "sat" — the one we'll predict from — combining them with the output matrix WO:

9 · The full block

Attention is half of it

A transformer block wraps attention with two more ideas: a residual connection (add the input back, so information and gradients flow freely) and layer normalization (keep activations well-scaled), then a small per-token feed-forward network (MLP) with its own residual + norm. That whole block is what gets stacked.

Now the arithmetic, continuing with "sat" — every step of the block with real numbers. (LayerNorm here uses scale 1, shift 0 for clarity; real ones learn those two parameters.)

10 · Predicting the next word

From the last token to a word

To predict what follows "the cat sat", take the processed vector at the last position, map it to one score (logit) per vocabulary word, and softmax into probabilities — using the block's actual output vector for "sat" that we computed just above. The temperature controls how sharp the choice is.

temperature = 1.0
Lower the temperature and the model commits to its top guess; raise it and the distribution flattens toward random. Sampling from this distribution, appending the word, and running the whole pass again — that loop is exactly how an LLM writes text.
11 · The math, all in one place

Every formula you just used

Now that you've seen each step with numbers, here are the equations behind them — essentially the whole transformer in nine lines.

xᵢ = E[tokenᵢ] + PE(i)
PE(p, 2k) = sin( p / 10000^(2k/d) ),   PE(p, 2k+1) = cos( p / 10000^(2k/d) )
Q = X·W_Q,   K = X·W_K,   V = X·W_V
Attention(Q, K, V) = softmax( Q·Kᵀ / √dₖ ) · V
MultiHead(X) = Concat(head₁, …, head_h) · W_O
LayerNorm(z) = (z − μ) / √(σ² + ε) ⊙ γ + β
FFN(h) = max(0, h·W₁ + b₁) · W₂ + b₂
h = LayerNorm( x + MultiHead(x) ),   block(x) = LayerNorm( h + FFN(h) )
logits = out · W_U,   p = softmax(logits)

And the shape of the data at every stage — the journey of "the cat sat" from words to a probability over the next word:

stageshapewhat it is
tokens3the, cat, sat
embeddings3 × 4a vector per word
+ positional encoding3 × 4order stamped in
Q, K, V3 × 2 eachquery / key / value
attention weights3 × 3who attends to whom
head output3 × 2context per token
multi-head output3 × 4heads combined by W_O
block output3 × 4after residual + norm + FFN
last token vector4"sat", processed
logits → probabilities6→ "mat"
12 · How it learns

The loss, the gradient, and training

So far every weight was fixed. Training adjusts them. We score the prediction with cross-entropy loss — how surprised the model is by the correct next word — then nudge the weights to reduce it, exactly the gradient-descent loop from the neural-network dive.

L = −log p(target)

The elegant part is the gradient at the output. For softmax followed by cross-entropy it collapses to one of the simplest expressions in all of deep learning:

∂L / ∂logitᵢ = pᵢ − targetᵢ   (predicted − actual)

That gradient flows backward through WU, the block, attention, and the embeddings by the chain rule (backpropagation), and every matrix takes a small step downhill: W ← W − η·∂L/∂W. Below we train just the final output layer on this one example — pick a target word and watch the model learn to predict it.

target word:
η = 0.5
target dogp(target) loss step 0
Watch the bar for your target word climb toward 100% while the loss falls — that's all training is: the predicted-minus-target gradient, applied a little at a time, over and over. A real model does this for every weight, across billions of word-predictions.
13 · Stacking & scaling

From this to GPT

Everything you just traced — embed, add position, attend, MLP — is one block. Stack dozens to a hundred of them, widen the vectors from 4 to thousands, grow the vocabulary, and train on a large corpus, and the same machinery becomes a large language model.

Wider

dmodel grows from 4 to thousands; many heads instead of one.

Deeper

Dozens–hundreds of identical blocks stacked.

Bigger vocab

Tens of thousands of subword tokens, not three words.

Same idea

Query·key attention, softmax, value blend, residual+norm+MLP — unchanged.

14 · Recap

What you saw

Words became vectors; positions were stamped in; each token produced a query, key, and value. Scores compared queries to keys, softmax turned them into attention weights, and each token's output was a weighted blend of values — its context. Multi-head, residual, norm, and an MLP complete the block; the last token's vector becomes a probability over the next word. That is a transformer.

Want the focused versions? The concept cards break out self-attention, multi-head attention, positional encoding, the transformer block, causal masking, and the full transformer mathematics with their own animations.
Deep dive · with real numbers

Convolutional networks, from scratch

A dense network treats an image as a flat list of pixels and forgets that nearby pixels belong together. A convolutional network keeps the 2-D layout and slides a tiny filter across it, looking for the same local pattern everywhere. We'll take a small image, convolve a 3×3 filter over it by hand — every multiply-and-add on screen — see edges light up, add ReLU and pooling, classify the image, and finally train a small CNN live and watch its filters grow into edge detectors.

1 · The idea

Slide a small filter everywhere

Instead of one giant weight per pixel, a CNN learns a small filter (here 3×3) and applies the same filter at every location. That's two big wins: far fewer weights, and a pattern learned in one corner is recognized everywhere else.

Filter (kernel)

A small grid of weights that detects one local pattern — an edge, a corner, a texture.

Convolution

Slide the filter over the image; at each spot, multiply overlapping values and sum → one output number.

Feature map

All those outputs form a new image showing where the pattern was found.

Hierarchy

Stack layers: edges → textures → parts → objects, each built from the last.

2 · The data

A tiny image

Our image is 6×6 grayscale — dark on the left, bright on the right, so there's a single vertical edge down the middle. Pixel values are 0 (dark) to 1 (bright). The job of a good filter is to find that edge.

3 · Convolution

One output pixel, by hand

Place the 3×3 filter over a patch of the image, multiply each filter weight by the pixel beneath it, and add it all up — that single number is one pixel of the feature map. Step through it at the edge:

Now watch the filter slide across the whole image, filling in the feature map one pixel at a time:

4 · Filters detect features

Different filters, different patterns

The filter's weights decide what it finds. Swap them and the same image reveals different things — a vertical-edge detector lights up the middle seam; a horizontal-edge detector sees nothing here; a blur smooths; a sharpen exaggerates. Pick one and watch the feature map change.

filter:
A filter is just a pattern-matcher: it gives a big response where the image looks like the filter, and ~0 elsewhere. A CNN learns these weights rather than hand-designing them — which we'll do at the end.
5 · Channels & depth

Colour images and stacks of filters

Real images aren't one grid — a colour image has three channels (red, green, blue). So a filter isn't 3×3, it's 3×3×3: one slice per input channel. You convolve each slice with its channel and add the three results into a single feature-map pixel. And a layer has many filters, each making its own map — so the output is itself a stack whose depth equals the number of filters, ready for the next layer to convolve again.

Rule of thumb: a filter's depth matches its input's depth, and the number of filters sets the output's depth. 3 channels in (RGB), K feature maps out — and those K maps become the next layer's "channels".
6 · Stride, padding & size

How big is the output?

A 3×3 filter on a 6×6 image, moved one pixel at a time (stride 1) with no border added (valid padding), can sit in 4×4 positions — so the feature map is 4×4. Larger stride skips positions and shrinks the output; adding a border (same padding) keeps it the same size.

out = ⌊ (in − filter + 2·padding) / stride ⌋ + 1

For us: (6 − 3 + 0)/1 + 1 = 4. Each output pixel summarizes a 3×3 patch — its receptive field. Stack layers and that field grows, so deep neurons "see" more of the image.

7 · ReLU & pooling

Keep the strong signals, shrink the rest

Two cheap steps follow convolution. ReLU zeroes negative responses (keep evidence "for" a pattern, drop evidence "against"). Max pooling then slides a 2×2 window and keeps only the largest value in each — shrinking the map, keeping the strongest activations, and making the result robust to small shifts.

8 · Stacking layers

Edges → corners → objects

One layer finds edges. Stack a second whose filters convolve the feature maps of the first, and it can combine a vertical and a horizontal edge that meet into a corner detector; a third assembles corners into shapes, and so on. Each deeper layer also sees a wider patch of the original image — a growing receptive field — so early layers handle fine detail while deep layers reason about whole objects. This edges→parts→objects hierarchy is learned, not designed.

9 · The full CNN

From pixels to a label

Put it together: convolve with two filters (a vertical- and a horizontal-edge detector), ReLU, pool each map down to a single strongest response, and feed those two numbers through a softmax to choose a class — "vertical" vs "horizontal". For our image the vertical detector fires hard and the horizontal one barely, so the answer is clear.

10 · How it learns

Watch the filters become edge detectors

We hand-picked those filters. A real CNN learns them. Starting from random 3×3 weights, we train a tiny CNN (two filters → ReLU → pool → softmax) to tell vertical-edge images from horizontal ones, using the same cross-entropy loss and gradient descent as the other dives. Backprop sends the error to whichever pixel patch won the max-pool. Hit Train and watch the random filters sharpen into edge detectors while accuracy climbs.

First, the gradient for one image — worked by hand. It's the same predicted-minus-target rule, sent backward through the softmax, the max-pool, and the convolution:

Now let the network run that loop automatically over all the images:

η = 0.5
epoch 0loss accuracy
No one told the network what an edge looks like — the predicted-minus-target gradient, pushed into whichever patch maximised each filter, is enough to grow edge detectors on its own. That is the whole magic of CNNs: useful visual features, learned from labels alone.
11 · The math

Every formula in one place

Convolution is one weighted sum repeated across positions; everything else you've met before.

(I ∗ K)[i,j] = Σ_u Σ_v  I[i+u, j+v] · K[u,v]
out = ⌊ (in − k + 2p) / s ⌋ + 1
ReLU(z) = max(0, z)
pool[i,j] = max over the 2×2 window
p = softmax(W · pooled + b),   L = −log p(target)
stageshapewhat it is
image6 × 6pixels
conv (2 filters, 3×3)2 × 4 × 4feature maps
ReLU2 × 4 × 4negatives → 0
max pool (global)2strongest response each
softmax2→ vertical / horizontal
12 · Recap

What you saw

A filter is a small grid of weights; convolution slides it everywhere and records where the pattern matched; ReLU keeps the positive evidence; pooling shrinks and stabilises; a final softmax turns the strongest responses into a label. Train with the same loss-and-gradient loop and the filters discover edges on their own. Stack many such layers and a CNN builds from edges up to whole objects.

For the focused versions, the concept cards cover the convolution operation, padding & stride, pooling, receptive fields, what filters learn, and the convolution mathematics with their own animations.
Deep dive · with real numbers

Sequence models, from scratch

Images have a fixed shape; language, audio and time-series don't — they arrive one step at a time and the meaning depends on order and on what came before. A recurrent neural network handles this with a trick: it processes one element at a time while keeping a hidden state — a small memory vector — that it carries forward and updates at every step. We'll unroll one through time, watch the memory evolve as you change the input, do the forward pass by hand, and train it by backprop-through-time to remember.

1 · The idea

A network with a memory

A feed-forward net maps a fixed input to an output and forgets everything between calls. An RNN adds a loop: at each step it combines the new input with its previous hidden state to produce a new hidden state. The same weights are reused at every step, so it can handle sequences of any length and a pattern learned at one position works at any other.

Hidden state

A small vector of numbers — the network's running memory of the sequence so far.

Recurrence

hₜ depends on the input xₜ and the previous state hₜ₋₁ — a loop through time.

Weight sharing

One set of weights is applied at every timestep, so length doesn't matter.

Unrolling

Lay the loop out across time and it becomes a deep net — one layer per step.

2 · The data

A task that needs memory

To prove the memory does something, we pick a task it can't fake: read a sequence of bits, and at the very end output the first bit you saw. The answer is decided at step 1 but only asked for at the end, so the network has no choice but to carry it the whole way in its hidden state.

Examples: 1011 → 1 · 0010 → 0 · 1000 → 1. The last three bits are distractions; only the first matters.

A net with no memory could only ever guess from the most recent input — it would score ~50%. Getting this right requires remembering across time.
3 · The cell, unrolled

Watch the memory evolve

Here's a small RNN (2-dimensional hidden state) unrolled across a 5-step sequence. Each cell takes the input bit and the previous state, and produces a new state (shown as two bars, teal positive / purple negative). Toggle the bits and watch how the state — and the final read-out — change. Notice the first bit's long reach.

input sequence:
4 · The math

One step, reused forever

The entire recurrent layer is a single equation applied at each step, plus a read-out at the end.

hₜ = tanh( Wₓ·xₜ + Wₕ·hₜ₋₁ + b )
ŷ = σ( Wₒ·h_T + bₒ )
L = −[ y·log ŷ + (1−y)·log(1−ŷ) ]  (binary cross-entropy)

Wₓ mixes in the new input, Wₕ carries the old state forward, tanh keeps the state bounded in (−1, 1). The same Wₓ, Wₕ, b act at every timestep — that's weight sharing. The final state h_T is squeezed through one more layer into a probability.

5 · Forward by hand

Unroll a real sequence

Take the sequence [1, 0, 1] with fixed weights and step through it — each hidden state feeds the next, and the last one decides the answer.

6 · Training (backprop through time)

Teaching it to remember

Unrolled, the RNN is just a deep network that happens to reuse its weights — so we train it the same way: forward pass, cross-entropy loss, then backpropagate the error backwards through every timestep (this is backprop-through-time) and step the shared weights downhill. We train on all 16 four-bit sequences, each labelled by its first bit. Hit Train and watch accuracy climb from a coin-flip to perfect recall.

First, the gradient by hand. Backprop-through-time pushes the error from the loss back through every step — and you can watch the signal shrink on the way, the vanishing gradient in real numbers:

Now let the network run that update automatically over all 16 sequences:

η = 0.3
epoch 0loss accuracy
The only way to score 16/16 is to copy the first bit into the hidden state and protect it across three more steps. When training succeeds, that's exactly what the network has taught itself to do.
7 · The vanishing gradient

Why long memories are hard

Backprop-through-time multiplies a gradient by roughly the same factor at every step it travels back — the recurrent weight times tanh's slope. If that factor is below 1, the gradient shrinks geometrically: after many steps it's effectively zero, so early inputs get almost no learning signal. (Above 1 it explodes instead.) This is why a plain RNN struggles to connect events far apart in time. Drag the per-step factor and watch the signal that survives back to the first step.

per-step gain: 0.70
tanh's slope is at most 1 and usually less, so the default regime is vanishing. The fix isn't a bigger network — it's a different cell that keeps a protected path for the memory and its gradient. That's the LSTM.
8 · The LSTM

A protected path for memory

The Long Short-Term Memory cell adds a second state — the cell state C, a conveyor belt running straight through time. Three gates (each a little sigmoid that outputs values in 0–1) decide what happens to it: the forget gate erases part of the old memory, the input gate writes new candidate information, and the output gate chooses how much of the memory to reveal as this step's hidden state.

fₜ = σ( W_f·[hₜ₋₁, xₜ] + b_f )  (forget)
iₜ = σ( W_i·[…] + b_i ),   C̃ₜ = tanh( W_c·[…] + b_c )  (input + candidate)
Cₜ = fₜ ⊙ Cₜ₋₁ + iₜ ⊙ C̃ₜ  (update the conveyor belt)
oₜ = σ( W_o·[…] + b_o ),   hₜ = oₜ ⊙ tanh( Cₜ )  (output)

Step through one update with real numbers:

The cell update is additive: Cₜ = fₜ·Cₜ₋₁ + …. When the forget gate stays near 1, the gradient flows back through C almost unchanged — no shrinking factor at each step. That "constant error carousel" is what lets LSTMs remember across hundreds of steps. (The GRU is a lighter cousin that merges this down to two gates.)

9 · Recap

What you saw

A recurrent net carries a hidden-state memory and updates it with one shared set of weights at every step; unrolled through time it's a deep network trained by backprop-through-time. That memory lets it recall the first bit long after seeing it — but pushing a gradient back through many steps makes it shrink toward zero, the vanishing-gradient problem, so plain RNNs struggle with long range. The LSTM fixes this with a protected, additive cell state and three gates that learn what to keep, write, and reveal — remembering across hundreds of steps.

Related concept cards cover recurrent cells, backprop through time, LSTM & GRU gates, and sequence-to-sequence models with their own animations.
Deep dive · with real numbers

Generative models, from scratch

The networks so far recognise — image to label, sequence to answer. A generative model instead learns the shape of the data well enough to make new examples. The simplest way in is the autoencoder: squeeze each data point through a narrow bottleneck into a tiny latent code, then expand it back. To reconstruct well through such a tight pipe, the network is forced to discover the data's underlying structure. Decode a latent you choose and you've generated something new. We'll do the squeeze by hand, train one live and watch its decoded shape mould itself to the data, then upgrade it to a variational autoencoder you can sample from.

1 · The idea

Compress, then recreate

An autoencoder is two networks back to back. The encoder maps a data point down to a few latent numbers; the decoder maps those numbers back up to a full data point. Train it to make the output match the input, and the latent code becomes a compact summary — a coordinate in a learned "map" of the data. New points on that map decode to new, plausible data.

Bottleneck

A layer far narrower than the input — it can't copy, so it must compress.

Latent code

The few numbers in the bottleneck: the data point's coordinates in the learned map.

Reconstruction

The decoder's attempt to rebuild the input from the code; the loss is how far off it is.

Generation

Pick any latent code, decode it, and out comes a brand-new sample.

2 · The data

Two dimensions, one hidden factor

Our dataset is a cloud of 2-D points lying along a gentle arc. Even though each point has an x and a y, they really vary along a single direction — position on the arc. That hidden 1-D factor is exactly what a good autoencoder with a one-number latent should discover.

3 · The autoencoder

Down to one number, back to two

Here the encoder takes the 2-D point through a small hidden layer to a single latent number z; the decoder takes z through another hidden layer back to a 2-D reconstruction. The whole thing is trained to minimise the squared distance between input and output.

z = encoder(x),   x̂ = decoder(z)
L = ‖ x − x̂ ‖²  (reconstruction loss)
4 · Forward by hand

One point through the bottleneck

Take the point x = [0.80, 0.60] and push it through a tiny autoencoder (two hidden units, one latent). Watch two numbers collapse to one and expand back to two.

5 · Train live

Watch the shape fit the data

Now train the full autoencoder by gradient descent on the reconstruction loss. The line drawn here is the decoded manifold — everything the decoder produces as the latent z sweeps across its range. Start random and it's a tangled stub; as training proceeds it stretches and bends until it threads right through the data cloud. That curve is the 1-D structure the network has discovered.

First, the gradient by hand — continuing the point from chapter 4. The reconstruction error flows back through the decoder, funnels into the single latent, and on into the encoder:

Now let it run that update over all 30 points and watch the manifold settle onto the data:

η = 0.30
epoch 0reconstruction loss
6 · The latent space

Generate by choosing a code

Once trained, the decoder is a generator: hand it a latent number and it produces a data point. Drag the slider to move along the latent axis and watch the generated point glide along the learned arc — including spots between the original samples. That's the model inventing plausible new data.

latent z: 0.00
Train the autoencoder first (chapter 5), then come back here — the better the fit, the cleaner the generated points. A plain autoencoder, though, leaves gaps: pick a latent far from any training point and you may decode nonsense. Fixing that is the VAE's job.
7 · From autoencoder to VAE

A latent you can sample

To generate from scratch we need to know which latents are valid. A variational autoencoder arranges for the latents to fill a standard bell curve. Its encoder outputs not one z but a mean μ and spread σ; we sample z from that little Gaussian, and a KL term in the loss gently pulls every (μ, σ) toward (0, 1). Sampling is made differentiable by the reparameterization trick.

Why bother? A plain autoencoder scatters its codes wherever is convenient, leaving stretches of the latent axis that decode to nothing — so you can't just pick a random z. Here are the trained autoencoder's actual codes versus the bell the VAE packs them into (train in chapter 5 first to see them spread):

z = μ + σ · ε,   ε ~ 𝒩(0, 1)  (reparameterization)
L = ‖ x − x̂ ‖²  +  KL,   KL = −½ Σ ( 1 + log σ² − μ² − σ² )

That KL term isn't arbitrary — it's the exact closed form of the gap between the encoder's Gaussian 𝒩(μ, σ²) and the prior 𝒩(0, 1). Here is the integral behind it:

And the reparameterization that lets us sample z while keeping the gradient flowing:

The reparameterization trick makes sampling differentiable, and the KL term packs the codes into a known bell curve. Time to actually train one and watch it happen.
8 · The VAE, trained

Watch the codes fill the bell

Now train a real VAE. Same decoder as before, but the encoder outputs a mean μ and spread σ for every point; we sample z = μ + σ·ε, reconstruct, and add the KL term to the loss. Hit Train and watch two things at once: the blue manifold settles onto the data (reconstruction), while below, the codes slide inward to fill the standard bell (the KL term doing its job). The amber points are decoded from z drawn out of pure 𝒩(0,1) noise — once the bell is filled, they land cleanly on the data.

First, the two gradients that make a VAE different from a plain autoencoder — the path through the reparameterized sample and the KL term — worked by hand (continuing μ = 0.30, log σ² = −1.20, ε = 0.70 from the previous chapter):

Those are exactly the updates the trainer applies every step. Run it and watch them accumulate — reconstruction sharpening the fit, KL shaping the codes:

η = 0.20
epoch 0reconstruction KL

And the codes themselves — each point's encoded mean μ — drifting from wherever they started into the shape of the target bell:

Those amber points are generated from nothing but Gaussian noise — no input, no nearest neighbour, just sample and decode. That's the whole promise of a generative model, and the KL term is what made the latent space safe to sample. Diffusion models and GANs chase the same goal by other routes.
9 · Recap

What you saw

An autoencoder squeezes data through a bottleneck and learns to rebuild it, and in doing so discovers the data's hidden structure — the decoded manifold that threads through the cloud. Decoding a chosen latent generates new data; the VAE makes that latent space samplable with a probabilistic encoder, the reparameterization trick, and a KL term. From recognition to creation, it's the same machinery — forward pass, a loss, gradient descent — pointed at a new goal.

Related concept cards cover autoencoders, variational autoencoders, latent spaces, GANs, and diffusion models with their own animations.
Deep dive · with real numbers

Reinforcement learning, from scratch

Every other dive learned from labelled targets — here is the input, here is the right answer. Reinforcement learning has no answer key. An agent moves through an environment and all it ever gets back is a number: good, bad, or nothing. No one says which action was correct, and the reward for a good move may arrive only many steps later. We'll teach an agent to cross a small gridworld with Q-learning: write the update by hand, then watch a map of values and a set of policy arrows form on the grid as it works out the route entirely on its own.

1 · The idea

Learning from reward, not answers

The loop is simple: the agent sees a state, picks an action, and the environment hands back a reward and the next state. Repeat. The agent's job is to choose actions that maximise total reward over time — which forces it to think ahead, since a small reward now can be worth less than reaching a big one later.

State & action

Where the agent is, and the moves available from there.

Reward

A single number after each move. The only feedback there is.

Policy π

The agent's rule for choosing an action in each state.

Return

Total future reward — the thing the agent actually wants to maximise.

2 · The gridworld

A goal, a pit, and two walls

The environment is a 5×5 grid. The agent starts bottom-left (S) and wants the goal top-right (reward +1). One cell is a pit (reward −1) that ends the episode badly; two cells are walls it can't enter. Every ordinary step costs a small −0.02, so wandering is mildly punished. From any cell it can move up, right, down or left; bumping a wall or the edge just leaves it in place. An episode ends at the goal or the pit.

3 · Return, value, policy

Valuing the future

To choose well the agent must value the future, not just the next reward. The return from a moment is the sum of all rewards after it, with later ones discounted by γ (here 0.9) so nearer reward counts for more:

G = r₀ + γ r₁ + γ² r₂ + ⋯ = Σₜ γᵗ rₜ

Worth doing once with numbers — it's exactly why the agent ends up preferring short routes:

It can't know the return ahead of time, so it estimates it. The action-value Q(s, a) is the return the agent expects if it takes action a in state s and then keeps acting well. Given Q, the policy is just: in each state, take the highest-valued action.

π(s) = argmaxₐ Q(s, a)
4 · The Q-learning update

One estimate built on the next

Q-learning learns those values straight from experience, one step at a time. After taking action a in state s, landing in s′ with reward r, it nudges Q(s, a) toward a fresher estimate — the reward just seen plus the discounted value of the best action available next:

Q(s, a) ← Q(s, a) + α [ r + γ maxₐ′ Q(s′, a′) − Q(s, a) ]

The bracket is the temporal-difference error δ: the gap between the old estimate and the new one. α (here 0.5) sets how big a correction each experience makes. The trick is that Q(s, a) is improved using Q(s′, ·) — an estimate leaning on another estimate. That bootstrapping is exactly what lets value flow backward from the goal.

δ = r + γ maxₐ′ Q(s′, a′) − Q(s, a)
5 · One update by hand

Value seeps back from the goal

Two updates with real numbers (γ = 0.9, α = 0.5) show how a cell with no value at all becomes valuable — purely because of what it leads to.

6 · Train live

Watch the map and the arrows form

Now let it learn by trial and error. Each cell is tinted by its value (green good, red bad), the arrow is the current best action there, and the amber dot is the agent. Hit Train: value spreads outward from the goal, the arrows organise into a route that skirts the pit, and the return-per-episode curve climbs from negative (falling in the pit, wandering) toward the reward.

ε α
episode 0ε 0.20α 0.50avg return
7 · Explore vs exploit

Why the agent rolls the dice

If the agent always took its current best guess it would exploit what it knows but never explore — and might lock onto a mediocre route, never discovering a better one. ε-greedy fixes this: with probability ε it ignores its values and tries a random move. Below are the four action-values for one state; greedy always picks the tallest bar, but ε of the time the agent gambles instead.

uses the ε from the trainer above
Too little ε and the agent freezes into whatever it learned first; too much and it wanders without ever committing. A common recipe starts ε high and decays it, exploring early then settling. Slide ε to 0 in the trainer and watch the policy stop improving; raise it and it keeps probing for something better.
8 · Recap

What you saw

With no labels and only a reward signal, the agent learned to cross the grid. The return discounts future reward by γ; the action-value Q(s, a) estimates that return; the Q-learning update drags each value toward the reward plus the best next value, so value propagates backward from the goal one step at a time; and ε-greedy keeps the agent exploring instead of settling early. The greedy policy — follow the arrows — is what falls out at the end.

Related concept cards cover Q-learning, policy gradients, value functions, exploration, and reward shaping with their own animations.
Deep dive · with real numbers

Optimization & training, from scratch

Every dive so far ended with "...and then gradient descent." This one opens that box. Training is a walk downhill on a loss surface, but the way you walk decides everything: too big a step explodes, too small crawls, and a naive step zig-zags helplessly down a narrow valley. We'll take a step by hand, see the learning rate make or break it, schedule it over training, race three optimizers down an awkward landscape, escape the saddle points and plateaus that fill real loss surfaces, keep deep signals alive with good initialization and normalization, and then meet the other half of training — making the model generalise instead of just memorise, with early stopping as the simplest tool of all.

1 · The idea

Walking downhill, carefully

Backprop gives the gradient — the uphill direction of the loss. Training just steps the opposite way, over and over. Simple in principle, but four choices decide whether it works at all: which update rule, how big a step, how much data per step, and how to stop the model overfitting.

The optimizer

The rule that turns a gradient into a parameter update — SGD, momentum, Adam.

Learning rate

How far to step each time. The single most important knob in all of training.

Batch size

How many examples each step is based on — trading gradient noise for speed.

Generalisation

Fitting the signal, not the noise, so the model works on data it never saw.

2 · The loss landscape

The gradient points the wrong way

Picture the loss as a surface over the parameters; the rings below are its contours, the centre is the minimum. At any point the gradient ∇L points uphill — the steepest way to increase loss. So we step along −∇L, scaled by the learning rate η. That's the whole of gradient descent.

θ ← θ − η ∇L(θ)

Concretely, this bowl is L = ½(2x² + y²), so ∇L = (2x, y). At the marked point (1.7, 1.25) that’s (3.4, 1.25) — the red arrow — and the orange step heads exactly opposite.

3 · A step by hand · the learning rate

How far is just right?

Take the simplest loss, f(x) = x², and walk it down with real numbers:

The step size η is everything. Drag it below: too small and the ball barely moves; around the sweet spot it drops straight in; push it past the edge and the steps overshoot harder each time — the loss explodes.

η = 0.10
4 · Momentum

Remember where you were going

Plain descent treats every step independently, so in a narrow valley it bounces between the walls and barely crawls along the floor. Momentum fixes this by accumulating a velocity — a running average of past gradients. Consistent directions build up speed; alternating ones cancel out. It's a ball with inertia rolling downhill instead of a dot teleporting.

v ← μ v + ∇L,   θ ← θ − η v  (μ ≈ 0.9)
5 · Adam

A learning rate per parameter

One global η is a compromise: directions that need big steps and ones that need small steps get the same. Adam gives each parameter its own effective rate. It keeps a running mean of the gradient (m, like momentum) and a running mean of its square (v, the typical magnitude), then steps by the mean divided by the root of the square — so steep directions are damped and flat ones amplified.

m ← β₁ m + (1−β₁) g,   v ← β₂ v + (1−β₂) g²
θ ← θ − η · m̂ ∕ (√v̂ + ε)  (m̂, v̂ bias-corrected)
6 · The optimizer race

Three optimizers, one awkward valley

Here is the classic test: a loss that's steep in one direction and shallow in the other (the contours are stretched ellipses; here L = ½(18x² + y²), eighteen times more curved across the valley than along it, so no single step size suits both). All three start at the same corner. Watch SGD zig-zag and crawl, momentum build speed along the valley floor, and Adam rescale each axis and drive almost straight to the minimum.

η = 0.03
7 · Learning-rate schedules

Change the step as you go

η doesn't have to be a fixed number. Early in training you want big steps to cover ground; later you want small ones to settle into the minimum instead of rattling around it. A schedule lowers η over training — most commonly a cosine decay, often with a short warmup at the start so the first steps don't explode while Adam's running averages are still cold.

cosine:  η_t = ½ η₀ (1 + cos(π t ∕ T))  ·  step:  η₀ → η₀∕3 → η₀∕9  ·  warmup:  η₀ · t∕t_w, then cosine

Why a constant rate leaves a floor: near the minimum a noisy step behaves like x ← (1 − 2η)x − η·ξ, where ξ is the gradient noise (variance s²). The variance settles where it stops shrinking — Var(x) = η²s² ∕ (1 − (1−2η)²) ≈ η s² ∕ 4 for small η — so the expected loss E[x²] is proportional to η. Halve the rate and you roughly halve the floor; drive η → 0 on a schedule and the floor follows it down. With s = 0.7 and η = 0.34 here, the constant floor sits near 0.34·0.49 ∕ 4 ≈ 0.042, while the cosine tail drives η toward 0 and keeps dropping past it.

The bottom panel shows the payoff (log scale): a high constant rate (dashed) drops the loss fast but then bounces on a noise floor it can't get under — each step's randomness is proportional to η. Decaying η lowers that floor as you go, so the loss keeps falling. Pick a schedule:

8 · Saddle points & plateaus

The real obstacle isn't local minima

The bowl in chapter 2 was a convenient lie. Real loss surfaces are non-convex and high-dimensional, and there the trouble is rarely a bad local minimum — it's saddle points and flat plateaus, regions where the gradient nearly vanishes and plain gradient descent stalls. Below, three optimizers start on the flat shoulder near x = 0 (slope ≈ 0) of a double-well. Watch plain GD inch along the plateau while momentum (carrying velocity) and SGD noise break free and drop into a minimum far sooner.

f(x) = ¼x⁴ − x²  →   f′(x) = x³ − 2x  →   f′(x) = 0 at x = 0, ±√2

Which critical point is the trap? The second derivative f″(x) = 3x² − 2 is −2 at x = 0 (a maximum along this slice — the saddle/plateau) and +4 at x = ±√2 ≈ ±1.414 (the two real minima). The hazard isn't the minima; it's the flat top. A gradient step there is f′(0.02) = 0.02³ − 2·0.02 = −0.040, so with η = 0.03 plain GD moves just −η·f′ = +0.0012 per step — a crawl. Down on the wall at x = 1, f′ = 1 − 2 = −1, a step 25× larger. Momentum accumulates those tiny pushes into real velocity; noise jolts x off the ridge directly — both clear the plateau while GD inches.

This is why momentum and stochastic noise matter so much: in a million-dimensional space almost every critical point that isn't the minimum is a saddle, and the very noise that makes SGD's loss curve fuzzy is what knocks it off plateaus that would trap exact gradient descent.
9 · Batch size & noise

Why the loss curve is fuzzy

A real gradient is averaged over a batch of examples. The full dataset gives the exact gradient but is slow; a single example (pure stochastic) is fast but noisy, since one example is a rough estimate of the whole. Noise scales as 1∕√batch. Slide the batch size and watch the training curve go from jagged to smooth — small batches are noisier per step, yet cheaper and their noise can even help escape sharp, brittle minima.

Why the square root? The batch gradient is an average of b per-example gradients, and averaging b independent estimates divides their variance by b (a mean of b draws has variance σ²∕b). The standard deviation — the wobble you actually see — is the square root of that, so it falls as 1∕√b. Going from batch 1 to 100 cuts the noise only 10×, which is why ever-larger batches give diminishing returns.

batch size = 16
10 · Initialization & gradient clipping

Starting the weights at the right scale

Before training even begins, the initial weight scale decides whether a signal survives a deep stack. Each layer multiplies the typical activation (and gradient) magnitude by roughly a constant factor. Below 1, the signal vanishes layer by layer; above 1 it explodes; right at 1 it holds steady. Slide the per-layer gain and watch the magnitude across 24 layers (log scale):

magnitude after L layers ≈ g^L  ·  He init: Var(W) = 2 ∕ fan-in  ⇒  g ≈ 1

Each layer scales the typical magnitude by a factor g (set by the weight variance and the fan-in), so down a stack of L the magnitude is the product g·g·…·g = g^L — exponential in depth. That's unforgiving: at L = 24, g = 1.40 gives 1.40²⁴ ≈ 3.2 × 10³ (exploding), g = 0.60 gives 0.60²⁴ ≈ 4.7 × 10⁻⁶ (vanished), and only g = 1.00 holds at exactly 1. He initialization sets Var(W) = 2∕fan-in precisely so each layer's output variance matches its input — pinning g at ≈ 1 so the signal neither dies nor blows up.

per-layer gain g = 1.00

This is exactly what Xavier and He initialization solve: they set the weight variance from the layer's fan-in (He uses 2∕fan-in for ReLU) so the gain lands at ≈ 1 and signals propagate intact. And for spikes that still slip through — one huge gradient that would blow up a step — gradient clipping rescales it to a ceiling: g ← g · min(1, c ∕ ‖g‖), capping the norm at c without changing its direction.

11 · Normalization

Keeping each layer's inputs well-behaved

Even with good initialization, the distribution of each layer's inputs drifts and stretches as the layers below it update — so every layer is forever chasing a moving target. Normalization (batch norm, layer norm) fixes this by standardizing each layer's pre-activations back to zero mean and unit variance, then letting the model rescale them with two learnable parameters γ and β. Toggle it and watch the per-layer distributions:

x̂ = (x − μ) ∕ √(σ² + ε),   y = γ x̂ + β

Worked example: a unit emits activations [2, 4, 6, 8] across a batch. Mean μ = 5; variance σ² = ((−3)² + (−1)² + 1² + 3²) ∕ 4 = 5. Normalize: x̂ = (x − 5) ∕ √5 ≈ [−1.34, −0.45, 0.45, 1.34] — now zero mean, unit variance. Then the learnable γ, β rescale: with γ = 2, β = 1 the output is y = 2x̂ + 1 ≈ [−1.68, 0.11, 1.89, 3.68]. The model can still represent any mean and spread it wants — it just no longer has to chase a drifting input to do it.

Off, the bells march sideways and spread layer by layer; on, every layer sees a clean, standardized input regardless of what's happening below it. That stability lets you train deeper networks at higher learning rates — it both speeds convergence and smooths the loss landscape.

12 · Overfitting & regularization

Fitting the signal, not the noise

A model with enough capacity can drive training error to zero by memorising every point — including the noise — and then fails on anything new. Below, a polynomial is fit to noisy samples of a smooth curve. Raise the degree and the fit wiggles through every training point while the held-out (validation) error blows up: overfitting. Then raise weight decay (λ, an L2 penalty on the coefficients) and watch the curve smooth back out — generalisation restored.

L = L_data + λ ‖θ‖²  →   θ ← (1 − 2ηλ) θ − η ∇L_data

That shrink factor isn’t arbitrary: differentiating the penalty gives ∂(λ‖θ‖²)∕∂θ = 2λθ, which adds −η·2λθ to every update. So each step first pulls the weights a little toward zero — by the factor (1 − 2ηλ) — then applies the data gradient. Big weights are taxed, so the fit can’t lean too hard on any one coefficient.

degree = 3 λ = 0.00
Dropout (randomly zeroing units during training), early stopping (quit when validation loss turns up), more data, and weight decay are all ways to say the same thing: don't let the model trust any one feature too much. The gap between training and validation loss is the number that matters.
13 · Early stopping

Quit while you're ahead

The simplest regularizer of all is to stop at the right moment. Training loss falls forever — the model keeps fitting the training set harder — but validation loss traces a U: it improves while the model learns real structure, bottoms out, then climbs as the model starts memorising noise. The best model is the one at that bottom. Drag the stopping epoch:

stop when  val(e) > mine′ ≤ e val(e′)  for patience epochs  →  keep the best checkpoint

There's nothing to differentiate here — it's a rule, not a formula — but the bookkeeping matters. You track the best validation loss seen so far and the epoch it occurred; each epoch that fails to beat it increments a counter, and when the counter reaches the patience (say 10 epochs) you halt and roll back to that checkpoint. Here validation bottoms out at epoch 22 (val ≈ 0.256); by epoch 48 it has climbed to ≈ 0.564 while training loss kept falling — that widening gap is the overfitting, and the epoch-22 weights are what you actually ship.

stop at epoch 22
In practice you keep a checkpoint of the best validation score and stop when it hasn't improved for a set number of epochs (the "patience"). It costs nothing, needs no tuning, and stacks with weight decay and dropout rather than replacing them.
14 · Recap

What you saw

Training is gradient descent plus a stack of choices. The learning rate sets the step — too large explodes, too small crawls — and a schedule shrinks it over time to settle cleanly. Momentum carries velocity through narrow valleys and off plateaus; Adam gives every parameter its own rate; and on an ill-conditioned surface both leave plain SGD far behind. Batch size trades gradient noise for speed, and that same noise helps escape the saddle points that fill high-dimensional landscapes. Good initialization and normalization keep signals from vanishing or exploding through deep stacks. And capacity without restraint memorises noise — so weight decay, dropout, and early stopping keep the model general. The same machinery as every other dive — a loss and its gradient — but tuned to actually converge.

Related concept cards cover SGD, momentum, Adam, learning-rate schedules, batch normalization, dropout, and regularization with their own animations.
Deep dive · with real numbers

Diffusion models, from scratch

Every image, video and audio generator you have heard of runs on the same surprising idea: learn to destroy data with noise in a fixed, known way, then train a network to run that destruction backwards. Start from pure static and denoise, step by step, until structure appears. We will take a ring of 2-D points, melt it into a Gaussian blob, derive the one identity that makes training cheap, see why the network only ever has to predict the noise it was given, and then watch a cloud of random points reorganise itself back into the ring — by hand and live.

1 · The idea

Learn to undo noise

There are two processes. The forward process is fixed and dumb: it just adds a little Gaussian noise, over and over, until the data is indistinguishable from static. The reverse process is the model: a network that, given a noisy point and a noise level, predicts what noise was added — which is enough to take one step back toward clean data. Run the reverse step from fresh noise enough times and you have generated a brand-new sample.

Forward q

Add noise on a fixed schedule. No parameters, nothing to learn.

Reverse pθ

The network. Predicts the noise, so we can step back toward data.

Objective

Just predict the added noise ε. A plain regression loss.

Sampling

Start from N(0, 1), denoise T times, watch a sample form.

2 · The data

A ring we can watch

To see the process we use a 2-D toy: 90 points arranged in a ring. It is clearly not a Gaussian — there is a hole in the middle and mass at a fixed radius — so when noise destroys that structure, and when the model rebuilds it, both are obvious to the eye. Everything below is the exact algorithm a real image model uses; only the dimensionality is tiny.

3 · The forward process

Melting the data into noise

One forward step scales the point down a touch and adds a little Gaussian noise, with the amount set by a schedule βt:

q(xt | xt−1) = N( √(1−βt) · xt−1 ,  βt I )

The √(1−βt) shrink keeps the variance from growing without bound; the +βt injects fresh noise. Drag t from 0 to the end and watch the ring dissolve into an isotropic blob — the same blob no matter what the data was:

noise level  t = 0 / 39
4 · The schedule & the closed form

Jump to any noise level in one shot

Composing many small Gaussian steps is itself one big Gaussian step. Define the surviving signal after t steps as the running product ᾱt = ∏s≤t(1−βs). Then you can skip the whole loop and sample xt directly from x0:

q(xt | x0) = N( √ᾱt · x0 ,  (1−ᾱt) I )  ⇒   xt = √ᾱt · x0 + √(1−ᾱt) · ε

Why it collapses into one step: a single step is xt = √αt·xt−1 + √(1−αt)·ε. Substitute xt−1 = √αt−1·xt−2 + √(1−αt−1)·ε′ and the two independent noise terms add:

√(αt(1−αt−1))·ε′ + √(1−αt)·ε  ⇒  variance = αt(1−αt−1) + (1−αt) = 1 − αtαt−1

Independent zero-mean Gaussians merge in quadrature — variances add — so two steps become one: xt = √(αtαt−1)·xt−2 + √(1 − αtαt−1)·ε̃. Induct down to x₀ and the surviving-signal coefficient is just the running product ᾱt = ∏ αs — the closed form above. With α₁ = 0.9, α₂ = 0.8 that gives signal √(0.9·0.8) = √0.72 = 0.849 and noise √(1−0.72) = √0.28 = 0.529 — the very numbers from the by-hand steps below.

That single identity is the engine of the whole method: training never runs the chain, it just picks a random t, draws one ε, and jumps. Below, the green curve √ᾱt is how much signal survives and the violet √(1−ᾱt) is how much noise has taken over — they cross when the data is half gone.

5 · Forward, by hand

Two steps, then the shortcut

Worth doing once with real numbers on a single 1-D point, so the closed form stops looking like magic:

6 · What the network learns

Just predict the noise

Here is the part that makes diffusion practical. Because xt = √ᾱt x0 + √(1−ᾱt) ε, the clean point and the noise are interchangeable — recover either and you have the other. So instead of predicting the clean image, the network εθ is trained to predict the noise that was added, with a plain mean-squared error:

L = Ex₀, ε, t ‖ ε − εθ( √ᾱt x0 + √(1−ᾱt) ε , t ) ‖²

This is not an ad-hoc trick: the full variational bound on −log p(x₀) unrolls into one denoising term per step, and after the Gaussian algebra each term reduces to exactly this — predict the ε that produced xt. Dropping the per-step weights (which barely matters in practice) leaves the clean MSE above.

It also has a beautiful interpretation. The score of the noisy distribution — the direction in which data becomes more likely — is ∇x log q(xt) = − ε ∕ √(1−ᾱt). So predicting the noise is, up to that scale factor, the same as learning the score: the network learns which way to push a point to make it look more like real data. That is literally the arrows in the next panel.

7 · The denoiser as a vector field

Which way is “more like data”

At a fixed noise level, evaluate the (perfectly trained) denoiser everywhere on the plane and draw −εθ, the direction it would nudge each point. Every arrow points back toward the ring: outside it pushes in, inside it pushes out, and on the ring the push vanishes. That field is the learned score. Slide the noise level — at high noise the field is broad and vague, at low noise it sharpens onto the exact ring:

noise level  t = 18 / 39
8 · Training the denoiser, live

Watch the field be learned

The arrows above came from the perfect denoiser. A real network has to learn that field from data, using exactly the loss from chapter 6: draw a point, noise it to a random level t, and nudge the weights so the predicted ε matches the true ε. Here a small time-conditioned model does precisely that. It starts predicting zero — so its loss is just the variance of the noise, ≈ 2 — and as it trains the arrows swing from random to pointing home. Choose a noise level to inspect and press Train:

inspect t = 10

The loss falls fast then flattens on a floor well above zero — that floor is real, not a bug. Many different (x₀, ε) pairs collide on the same noisy xt, so the noise is only partly recoverable; the best possible prediction is the average ε consistent with xt. That average is exactly the score direction, which is why the learned arrows line up with the ideal field from the previous panel even though the loss never reaches 0.

9 · Reverse, by hand

One step back toward the data

Where does that formula come from? Reverse the chain. The reverse of a single step is intractable in general, but if you also condition on the clean point x₀ it becomes a tractable Gaussian — Bayes on the forward step times the two closed forms:

q(xt−1 | xt, x0) = N( μ̃t , β̃t I ),   μ̃t = (√αt(1−ᾱt−1) ∕ (1−ᾱt))·xt + (√ᾱt−1·βt ∕ (1−ᾱt))·x0,   β̃t = ((1−ᾱt−1) ∕ (1−ᾱt))·βt

At sampling time we don’t know x₀ — but εθ hands us an estimate of it: x̂0 = (xt − √(1−ᾱt)·εθ) ∕ √ᾱt. Substitute that x̂0 into μ̃t and the coefficients collapse into the compact update below; the σt z term (with σt² = β̃t) puts back exactly the randomness the posterior says belongs there. So the sampling rule isn’t a heuristic — it’s the posterior mean of the true reverse step with x₀ replaced by the model’s guess:

xt−1 = (1∕√αt) · ( xt − (βt ∕ √(1−ᾱt)) · εθ ) + σt z
10 · Sampling: noise → data

Watch a sample form

Now the payoff. Seventy points start as pure Gaussian noise (t = 39). Each frame applies the reverse step above — predict the noise with the optimal denoiser, nudge toward the clean estimate, add a shrinking dash of randomness — and the cloud walks itself down to t = 0, settling onto the ring it has never been shown directly. This is generation: structure out of static, one denoising step at a time.

The faint violet dots are the real data, for reference. The green points are samples — they are generated, not copied, so they land on the ring without sitting exactly on any training point. Run it again and a different noise seed gives a different but equally valid ring.
11 · In practice

From a ring to an image model

Scale this up and almost nothing changes conceptually. The 2-D point becomes a full image (or a compressed latent of one, as in Stable Diffusion, which is cheaper); the toy denoiser becomes a large U-Net or transformer; and the schedule runs over hundreds or thousands of steps (with faster samplers like DDIM cutting that to a few dozen). To make it follow a prompt, the network is also fed a text embedding — conditioning — and classifier-free guidance sharpens prompt adherence by extrapolating between the conditioned and unconditioned predictions.

classifier-free guidance:  ε̃ = εθ(xt, ∅) + w·( εθ(xt, c) − εθ(xt, ∅) )

The network is trained on both the prompt c and an empty prompt ∅ (the conditioning is randomly dropped during training). At sampling you push past the conditioned prediction, away from the unconditioned one; the guidance weight w (typically 5–15) trades diversity for prompt fidelity. And to cut hundreds of steps to a few dozen, deterministic samplers like DDIM drop the random σt z and take larger exact steps along the same path — xt−1 = √ᾱt−1·x̂0 + √(1−ᾱt−1)·εθ, reusing the same clean-point estimate — same trained model, far fewer evaluations.

The reason diffusion overtook GANs: training is a stable regression instead of a fragile two-player game, it covers the full data distribution instead of collapsing onto a few modes, and the iterative sampler trades compute for quality on demand. The cost is exactly that — many network evaluations per sample — which is what the fast-sampler research keeps chipping away at.

12 · Recap

What you saw

A diffusion model is a fixed forward process that drowns data in Gaussian noise, plus a network trained to undo one step of it. The closed form xt = √ᾱt x₀ + √(1−ᾱt) ε lets training jump to any noise level in one shot; the network just predicts that ε, which doubles as learning the score — the direction toward more-likely data. You trained a small denoiser from scratch and watched its vector field sharpen toward that score; the reverse step you stepped by hand is the exact posterior mean with x₀ swapped for the model’s guess. Sampling reverses the chain from pure noise, and you watched 70 points do exactly that, reforming a ring they were never directly shown. Same loss-and-gradient machinery as every other dive, pointed at a strange and powerful target.

Related concept cards cover diffusion models, the forward/reverse process, score matching, latent diffusion, and classifier-free guidance with their own animations.
Deep dive · with real numbers

LLM training & alignment, from scratch

The Transformers dive built the engine. This one drives it through the whole factory: from raw text to a model that predicts the next token, to one that follows instructions, to one that has been shaped by human preferences into something helpful — and finally to the sampling knobs that decide what actually comes out. Every stage is the same loss-and-gradient machinery you have seen, pointed at a new target. We will tokenise by hand, train a real next-token network live until it speaks in grammatical sentences, and derive the reward model and the DPO loss line by line.

1 · The idea

Four stages, one model

A modern assistant is built in stages. First pretraining: predict the next token on a vast pile of text until the model has absorbed grammar, facts and reasoning patterns. That gives a base model — a brilliant autocomplete that does not yet answer questions. Then fine-tuning and alignment turn that autocomplete into something that follows instructions and prefers helpful, honest answers. Finally decoding chooses, token by token, what to actually emit.

1. Pretrain

Next-token prediction on huge text. Learns language itself.

2. SFT

Imitate curated instruction → answer demonstrations.

3. Align

Reshape with human preferences: reward model, RLHF, DPO.

4. Decode

Sample the next token — temperature, top-k, top-p.

2 · Tokenization

Text becomes a sequence of integers

A model never sees letters — it sees token IDs. Splitting on whole words makes the vocabulary explode and chokes on anything unseen; splitting on single characters makes sequences painfully long. Byte-pair encoding (BPE) finds the middle: start from characters and repeatedly merge the most frequent adjacent pair into a new token, so common chunks like "ing" or "the" become single tokens while rare words still decompose into pieces. Build a tiny BPE vocabulary by hand:

3 · The pretraining objective

Predict the next token

Pretraining is one task repeated trillions of times: given the tokens so far, put a probability on every token in the vocabulary for what comes next, via a softmax over the model's output logits. Training maximises the probability of the token that actually came next — equivalently, it minimises the cross-entropy, the negative log-probability assigned to the truth:

p(token) = softmax(z),   L = − log p(true next token),   perplexity = eL

What does one training example look like? A single sequence is shredded into many next-token problems at once. The tokens "the cat sat ." become four overlapping predictions — given "the" predict "cat", given "the cat" predict "sat", given "the cat sat" predict "." — all scored in a single forward pass. The model's input is the token sequence; its target is the very same sequence shifted left by one position. (This trick, teacher forcing, is what lets a whole document be trained on in parallel rather than one token at a time.)

Perplexity is the friendlier number: it is roughly "how many tokens the model is effectively choosing between." A model that has learned nothing is as confused as the vocabulary is big; a perfect model on a deterministic next token has perplexity 1. Watch the arithmetic:

And the gradient that does the work is famously clean. Differentiating the softmax cross-entropy with respect to the logits gives, for every token i,

∂L ∕ ∂zi = pi − yi   (yi = 1 for the true token, 0 otherwise)

— the predicted probability minus the one-hot truth. In the example above (p = [0.50, 0.20, 0.15, 0.10, 0.05], true = "sat"), the gradient on the logits is [0.50−1, 0.20, 0.15, 0.10, 0.05] = [−0.50, 0.20, 0.15, 0.10, 0.05]: one descent step lowers every wrong token's logit and raises "sat"'s, in a single stroke. (At initialisation the model is uniform, p = 1∕V, so L = log V and perplexity = V — exactly the vocabulary size, which is where the live demo below starts.) This is the precise update the trainer runs.

4 · Pretraining, live

Watch a model learn to talk

Here is a real next-token network — a small two-layer model that sees the previous two tokens and predicts the next, trained by exactly the cross-entropy above on a stream of simple sentences (det → noun → verb → optional adverb → period). It starts with random weights, so its perplexity is near the vocabulary size and its samples are noise. Press Train and watch the perplexity fall and the generated sentences snap into grammar:

This is the whole of pretraining in miniature: no grammar rules were programmed in: the model induced "a determiner is followed by a noun, a noun by a verb" purely from being scored on next-token prediction. Real LLMs do the same thing with a vocabulary of ~100,000 tokens and context windows of thousands, over trillions of tokens — but the objective on screen is identical.

5 · Scaling laws

Why bigger and more is reliably better

One of the most consequential empirical findings in the field: test loss falls as a smooth power law in compute, model size and data over many orders of magnitude. Plotted on log axes it is nearly a straight line, bending toward an irreducible floor (the entropy of language itself). This predictability is why labs invest in scale — the return is forecastable. Slide the compute budget:

L(C) ≈ L + (C0 ∕ C)α
compute budget  C = 106.0

The exponent α is small, so each step down in loss costs roughly an order of magnitude more compute — but it keeps paying out. The Chinchilla result refined this: for a fixed compute budget, model size and training tokens should grow together (roughly 20 tokens per parameter); most early large models were badly under-trained on too little data for their size.

6 · Supervised fine-tuning

From autocomplete to assistant

A base model continues text; it does not answer questions, because the internet rarely follows a question immediately with a clean answer. Supervised fine-tuning (SFT) fixes this with the same next-token objective on a curated dataset of instruction → response demonstrations. The one twist: the loss is computed only on the response tokens — the prompt is masked out, so the model learns to produce good answers, not to predict the user's questions.

LSFT = − Σt ∈ response log pθ(yt | y<t, prompt)

A few tens of thousands of high-quality demonstrations are enough to convert a base model into a usable instruction-follower — the knowledge is already in there from pretraining; SFT just teaches it the format of being helpful. Quality matters far more than quantity here: a small clean dataset beats a large noisy one, because the model is imitating every example faithfully, mistakes included.

7 · The reward model

Learning what people prefer

SFT can only imitate demonstrations, and writing the perfect answer is hard — but comparing two answers is easy. So we collect preferences: show people two responses to a prompt and record which they prefer. The Bradley–Terry model turns those comparisons into a numeric reward by assuming the probability of preferring one response rises smoothly with its reward advantage:

P(yw ≻ yl) = σ( r(yw) − r(yl) ),   LRM = − log σ( r(yw) − r(yl) )

A network rφ is trained to output a scalar reward per response so that preferred answers score higher. Work one example:

8 · RLHF

Optimise reward — but stay on a leash

With a reward model in hand, alignment becomes an optimisation: adjust the policy (the LLM) to produce higher-reward responses. But maximising a learned reward without limit invites reward hacking — the model finds degenerate text that fools the imperfect reward model. The fix is a KL penalty that keeps the policy close to the SFT reference it started from:

maxπ  Ey∼π[ r(y) ]  −  β · KL( π ‖ πref )

The leash is the KL divergence, a measure of how far the policy has moved from the reference: KL(π ‖ πref) = Σy π(y)·log[π(y) ∕ πref(y)] — zero when the two match, growing as they diverge. For a number: moving from a reference [0.40, 0.35, 0.25] to a policy [0.70, 0.20, 0.10] costs 0.70·log(0.70∕0.40) + 0.20·log(0.20∕0.35) + 0.10·log(0.10∕0.25) ≈ 0.39 − 0.11 − 0.09 = 0.19 nats. That is exactly the quantity the alignment panel two chapters down reports as you slide β.

This objective can be solved exactly, which is the key to everything that follows. Maximise Σ π(y)r(y) − β Σ π(y)·log[π(y)∕πref(y)] subject to Σ π(y) = 1; setting the derivative of the Lagrangian to zero gives r(y) − β(log[π(y)∕πref(y)] + 1) + λ = 0, so log[π(y)∕πref(y)] = r(y)∕β + const, i.e.

π*(y) = (1 ∕ Z) · πref(y) · exp( r(y) ∕ β )

— the reference distribution tilted toward reward (a Gibbs/Boltzmann form), with Z renormalising. This is the policy chapter 10 plots, and the exact identity DPO exploits next: we already know what the optimum looks like, so there is no need to actually run the RL loop to reach it.

Classic RLHF optimises this with a policy-gradient method (PPO): sample responses, score them with the reward model, and nudge the policy toward the high-reward ones while the KL term pulls back any token whose probability has drifted too far from the reference. β sets the leash length: small β chases reward hard (and risks hacking and gibberish), large β barely moves from the SFT model. It works well but is finicky — four models in memory, an unstable RL loop, careful tuning.

9 · DPO

Skip the RL — the policy is its own reward model

Direct Preference Optimization makes a sharp observation: the RLHF objective above has a known closed-form solution, and you can rearrange it to train directly on preferences with a plain classification loss — no reward model, no sampling, no RL loop. Walk the derivation:

LDPO = − log σ( β log [πθ(yw) ∕ πref(yw)] − β log [πθ(yl) ∕ πref(yl)] )

The bracketed log-ratio is the implicit reward: the policy scores its own responses. DPO is stable, needs only the policy and a frozen reference, and has become the default for open models. RLHF can still edge ahead when the reward model generalises well beyond the preference data, but DPO gets most of the benefit for a fraction of the complexity.

10 · Alignment, live

The reward–vs–reference tradeoff

Both RLHF and DPO are solving the same problem from chapter 8, and it has an exact optimal policy: tilt the reference distribution toward reward, tempered by β.

π*(y) ∝ πref(y) · exp( r(y) ∕ β )

Below, an SFT reference assigns fairly flat probabilities to three candidate answers, and the reward model scores the helpful one highest. Slide β to see alignment's central knob: small β concentrates all mass on the top-reward answer (aggressive, risks over-optimising); large β leaves the policy almost unchanged from the reference. The readouts track how far the aligned policy has drifted (KL) and its expected reward:

β = 1.5
11 · Decoding & inference

Turning probabilities into words

Training is over; now the model emits text one token at a time, and how you sample from its distribution changes everything. Temperature T rescales the logits before the softmax — T < 1 sharpens toward the top token (focused, repetitive), T > 1 flattens (diverse, riskier). Top-k keeps only the k most likely tokens; top-p (nucleus) keeps the smallest set whose probabilities sum to p, then renormalises. Reshape a real next-token distribution:

pi = softmax(zi ∕ T)  ·  nucleus: keep top tokens until Σ p ≥ top-p, renormalise

Concretely, take three tokens with logits [2.0, 1.0, 0.0]. At T = 1 the softmax is [0.67, 0.24, 0.09]. Cool to T = 0.5 and the logit gaps double → [0.87, 0.12, 0.02], nearly greedy. Heat to T = 2 and they halve → [0.51, 0.31, 0.19], nearly uniform. Temperature is a single dial running from "always the top token" (T → 0) to "ignore the model entirely" (T → ∞).

T = 0.80  top-p = 0.90
12 · In practice

How it really gets built

The frontier recipe is pretrain → SFT → preference-tune, with endless refinement at each stage. Preference data is expensive, so RLAIF and Constitutional AI use a strong model (guided by a written set of principles) to generate the comparisons instead of humans. Alignment is increasingly done with DPO or its variants for stability, sometimes followed by a round of RLHF for the last increment of quality. And none of it replaces pretraining — alignment reshapes how the model uses what it knows, but the knowledge, the reasoning, and the ceiling all come from the next-token objective at scale.

A useful mental model: pretraining grows the capability, SFT teaches the format, preference-tuning sets the manners, and decoding picks the words. Every one of those is the same gradient on the same kind of loss — the surprise of the whole field is how far that single idea, scaled, actually goes.

13 · Recap

What you saw

An assistant is a next-token predictor that has been progressively shaped. Pretraining minimises cross-entropy on raw text — you watched a small one learn grammar from nothing and its perplexity collapse — and scaling laws say that loss keeps falling predictably with size and data. SFT re-runs the same objective on instruction demonstrations, masked to the response. Then preferences take over: a Bradley–Terry reward model turns comparisons into scores, RLHF maximises that reward under a KL leash, and DPO — derived straight from that objective — drops the RL entirely by letting the policy be its own reward model. Finally, temperature and nucleus sampling decide what the trained distribution actually says. Same loss, same gradient, four times over.

Related concept cards cover tokenization, pretraining, RLHF, DPO, reward models, scaling laws, and decoding strategies with their own animations.
Deep dive · with real numbers

Self-supervised learning, from scratch

Labels are expensive; raw data is everywhere. Self-supervised learning turns that around — it manufactures its own supervision out of the structure already present in unlabelled data, learning representations so good that a tiny labelled dataset (or none at all) suffices downstream. Two big ideas do most of the work: hide part of the input and predict it, or pull together two views of the same thing while pushing apart everything else. We will derive the contrastive loss line by line, watch clusters self-organise from nothing but "these two came from the same source," and then prove the representations are useful with a linear probe.

1 · The idea

Supervision from the data itself

A supervised model needs a human-labelled target for every example. A self-supervised model invents the target from the input: delete a word and the word itself is the label; take two random crops of one photo and "they match" is the label. No annotation required — so you can train on the entire internet. The goal is not the pretext task itself but the representation it forces the network to build: a feature space where similar things land nearby, which then transfers to real tasks.

Masked prediction

Hide part of the input, predict it. BERT, MAE.

Contrastive

Same source → close; different → far. SimCLR, CLIP.

Representation

The real prize: a transferable feature space.

Transfer

Freeze features, train a tiny head — or go zero-shot.

2 · Learning without labels

The pretrain-then-probe bet

The wager of self-supervision: if a network must solve a hard enough pretext task on huge unlabelled data, it has to discover the underlying structure of the data to succeed — and that structure is exactly what downstream tasks need. So you pretrain once, expensively, on a pretext task, then freeze the features and attach a small task-specific head trained on whatever little labelled data you have. The representation does the heavy lifting; the head just reads it out.

This is the engine behind modern foundation models: BERT and GPT pretrain on text, MAE and DINO on images, CLIP on image–text pairs, wav2vec on audio. In every case the labelled fine-tuning is a thin layer on top of representations learned without labels.

3 · Masked prediction

Hide it, then predict it

The simplest pretext task: corrupt the input and reconstruct it. BERT masks ~15% of the tokens in a sentence and predicts each from the words on both sides; MAE masks ~75% of the patches in an image and reconstructs them. The loss is the same cross-entropy from the LLM dive — but applied only at the masked positions, and the context is bidirectional, which makes these models superb at understanding (not generation).

There is a subtlety in the data: of the 15% of tokens BERT selects, it replaces 80% with [MASK], 10% with a random token, and leaves 10% unchanged. The reason is that [MASK] never appears at fine-tuning or inference time, so a model trained to expect it everywhere would be mismatched; the random and unchanged cases force it to build a useful representation of every token, not just the masked slots.

LMLM = − Σt ∈ masked log pθ(xt | xcontext, both sides)
4 · The contrastive loss

Pull together, push apart

Contrastive learning needs no reconstruction at all. Make two augmented views of one example (crop, recolour, blur) — they form a positive pair that should map to nearby points. Every other example in the batch is a negative that should be pushed away. Measure closeness by cosine similarity — the dot product of the unit-normalised embeddings, sim(a, b) = (a·b) ∕ (‖a‖‖b‖) = â·b̂ ∈ [−1, 1] — and the InfoNCE loss is just a softmax cross-entropy whose "correct class" is the positive:

LInfoNCE = − log  exp(sim(a, a+) ∕ τ)  ∕  Σk exp(sim(a, k) ∕ τ)

Minimising it means making the anchor far more similar to its positive than to any negative. Work the arithmetic:

And the gradient shows why it works. Writing mk = softmaxk(sim(a,k)∕τ) for the weight the loss puts on each candidate, the update on the anchor is

a ← a + (η ∕ τ)·[ a+ − Σk mk·k ]

— move toward the positive a+, and away from the m-weighted average of all candidates. Because mk is largest for whichever negatives are already closest, the push lands hardest on the "hard negatives." In the worked example, m = [0.965, 0.029, 0.007] is dominated by the positive itself, so a+ − Σ mk·k ≈ 0: an already-aligned anchor barely moves. In the misaligned case (m on the positive only 0.33) that term is large, so the pull toward a+ is strong — the force self-limits exactly as alignment improves.

The name is literal: InfoNCE is a lower bound on the mutual information between the two views, and the bound is capped by log(number of candidates). More negatives ⇒ a tighter bound ⇒ better representations — which is precisely why SimCLR trains with very large batches and MoCo keeps a running queue of thousands of negatives.

5 · Contrastive training, live

Clusters from nothing but "same source"

Below are 30 samples — six augmented views of each of five source items — each with a 2-D embedding on the unit circle (a real encoder would compute these from raw input; here they are free parameters so we can watch the forces directly). The only signal is which views came from the same source; no labels, no class names. Press Train and watch InfoNCE pull each source's six views together and shove the five groups apart until clean, evenly-spaced clusters appear:

Nobody told the model there were five groups. From a purely local instruction — "these two crops are the same photo" — a global structure emerged: five tight clusters spread evenly around the sphere. The two forces have names: alignment (positives coincide) and uniformity (everything else spreads to fill the space). That is the whole of contrastive representation learning.

6 · Temperature & collapse

Two knobs, one failure mode

The temperature τ controls how harshly negatives are penalised. Small τ sharpens the softmax so only the very hardest negatives matter (sharper clusters, but touchy training); large τ treats all negatives gently. With τ = 0.2, similarities [0.9, 0.2, −0.1] become logits [4.5, 1.0, −0.5] and a near-one-hot weighting; at τ = 1 the same similarities barely separate.

The two forces from the last chapter can be written down exactly. Asymptotically the contrastive loss decomposes into an alignment term (positives should coincide) and a uniformity term (features should spread evenly over the sphere):

Lalign = E(x,x⁺) ‖ f(x) − f(x⁺) ‖²   ·    Lunif = log Ex,y e−2‖f(x)−f(y)‖²

InfoNCE optimises both at once. The lurking danger is collapse: if the encoder maps everything to the same point, alignment is trivially perfect and the loss looks great — but uniformity is destroyed and the representation is useless.

Negatives are what prevent collapse — pushing different examples apart makes the all-same-point solution costly. Methods without explicit negatives (BYOL, DINO) avoid collapse by other means: a momentum "teacher" network, a stop-gradient, or centering — so the student can't just copy a constant.

7 · CLIP

Contrastive across two modalities

The same loss links different kinds of data. CLIP takes a batch of N (image, caption) pairs, embeds images with one encoder and captions with another, and builds the N×N matrix of image–text similarities. The matched pairs sit on the diagonal; the contrastive objective pushes the diagonal up and everything off-diagonal down — InfoNCE applied symmetrically, once across rows (each image finds its caption) and once down columns (each caption finds its image):

LCLIP = ½ ( CErows(S ∕ τ) + CEcols(S ∕ τ) ),   Sij = cos(imagei, textj)

Toggle between an untrained and a trained encoder and watch the diagonal light up:

Once trained, CLIP classifies zero-shot: to label a new image, embed it, embed candidate captions like "a photo of a cat / dog / car," and pick the highest similarity — no task-specific training at all. The supervision was never labels; it was 400 million image–caption pairs scraped from the web, each its own positive.

8 · The payoff: linear probe

Proving the features are useful

How do we know the representation is any good? The standard test is a linear probe: freeze the encoder, train only a single linear classifier on top with a little labelled data, and measure accuracy. If a mere linear boundary now separates the classes, the encoder has already done the hard nonlinear work. Below, two classes form concentric rings in the raw input — no straight line can split them — but in the learned representation they become trivially separable:

Same points, same labels — only the representation changed. A linear probe on the raw coordinates is stuck at chance because the classes are concentric; on the learned features it is near-perfect. That gap is the entire value proposition of self-supervised pretraining: it reshapes the data so that the easy models downstream actually work.

9 · In practice

The quiet engine of foundation models

Self-supervision is how nearly every large model is born. Text models pretrain by next-token or masked prediction; vision models use masked autoencoding (MAE) or contrastive/self-distillation (SimCLR, MoCo, BYOL, DINO); CLIP and its successors bridge vision and language; speech uses masked prediction on audio. The labelled stage that follows — fine-tuning, instruction tuning, alignment — is comparatively tiny, because the representation already encodes the structure of the world the data came from.

The throughline across this whole lab: the loss keeps changing — squared error, cross-entropy, InfoNCE, preference loss — but the machinery never does. Define a differentiable objective that only a good representation can minimise, then follow the gradient. Self-supervision's trick is simply finding such objectives that need no human labels at all.

10 · Recap

What you saw

Self-supervised learning builds targets out of unlabelled data. Masked prediction hides part of the input and reconstructs it with the familiar cross-entropy. Contrastive learning needs no reconstruction: the InfoNCE loss — a softmax cross-entropy whose correct class is a positive pair — pulls two views of one source together and pushes all others apart, and you watched five clean clusters self-organise from that signal alone. Temperature tunes how hard negatives bite, and negatives (or clever substitutes) prevent the everything-collapses-to-a-point failure. CLIP runs the same loss across images and text for zero-shot classification, and a frozen-feature linear probe proves the representations actually transfer.

Related concept cards cover self-supervised learning, contrastive learning, masked autoencoders, CLIP, and representation learning with their own animations.
Deep dive · with real numbers

Graph neural networks, from scratch

Images are grids and text is a sequence, but molecules, social networks, road maps, knowledge graphs and recommendation systems are graphs — nodes joined by edges, with no fixed order and no fixed shape. A graph network learns on them with one deceptively simple operation repeated in every layer: each node looks at its neighbours, aggregates their messages, and updates itself. We will run that update by hand with real numbers, watch information spread hop by hop, label a whole network from four seed nodes, and see why stacking too many layers quietly destroys everything.

1 · The idea

Learning on nodes and edges

A graph is a set of nodes (each with a feature vector) connected by edges. The catch that breaks ordinary networks: there is no canonical ordering of nodes and every node can have a different number of neighbours, so you cannot just flatten it into a fixed-size vector. The fix is message passing — a local rule, applied identically at every node, that is invariant to how you happen to number things.

Nodes & edges

Entities and their relationships. Features live on the nodes.

Message passing

Each node aggregates its neighbours, then updates itself.

Permutation invariance

Re-label the nodes and nothing changes. Aggregation must not care about order.

Three tasks

Classify a node, predict an edge, or label a whole graph.

2 · Graphs as data

An adjacency matrix and a feature matrix

Two arrays describe everything. The feature matrix X stacks one feature vector per node. The adjacency matrix A records the edges: Aij = 1 if node i connects to node j. Here is a small graph and its adjacency — symmetric, because these edges are undirected:

3 · Message passing

The one operation everything is built from

Every graph-network layer is the same two-step local rule, run at every node simultaneously: aggregate the neighbours' current vectors with some order-independent function (sum, mean or max), then update the node by combining that aggregate with its own vector through a small learned transform:

hv(l+1) = UPDATE( hv(l),  AGGREGATE{ hu(l) : u ∈ N(v) } )

The aggregation must be permutation-invariant — a sum does not care what order the neighbours arrive in, and that is exactly what a graph needs. The learned transform (a weight matrix W and bias, shared across all nodes) is the only thing with parameters, which is why one trained network handles graphs of any size or shape.

Formally this makes the layer permutation-equivariant: relabel the nodes by any permutation P and the output is just the same vectors relabelled — GNN(PX, PAP) = P·GNN(X, A). Nothing depends on the arbitrary numbering, which is exactly the property a graph demands.

4 · One layer, by hand

Averaging a neighbourhood

Take the graph above with a single number on each node and update one of them, so the aggregate-then-transform stops being abstract:

5 · The GCN layer

Doing it for all nodes at once

Written as matrices, a whole layer is one line. The graph convolutional network (GCN) uses a particular aggregation: add self-loops (so a node keeps its own features) and normalise by degree symmetrically, so high-degree hubs do not drown everything out:

H(l+1) = σ( Â H(l) W(l) ),   Â = D̃−1/2 (A + I) D̃−1/2

Here A + I adds the self-loop, D̃ is the degree matrix of A + I, and the D̃−1/2(·)D̃−1/2 sandwich weights the edge between nodes i and j by 1∕√(di dj) — gentle on hub nodes. (The by-hand step above used a plain mean for clarity; the symmetric normalisation is the same idea with degree-aware weights.) Multiplying by  mixes each node with its neighbours; multiplying by W transforms the features; σ is a nonlinearity like ReLU.

A concrete entry: suppose node i has 3 neighbours and node j has 2. With self-loops their degrees are d̃i = 4 and d̃j = 3, so the weight Âij = 1∕√(d̃i·d̃j) = 1∕√12 = 0.289, while node i keeps 1∕d̃i = 0.25 of itself. A degree-50 hub contributes only 1∕√(51·d̃j) to each neighbour — automatically tiny — which is the whole point of the symmetric normalisation: popular nodes do not shout over everyone.

6 · Depth = reach

Each layer is one hop further

One layer lets a node see its immediate neighbours. Stack a second and those neighbours have already absorbed their neighbours — so two layers reach two hops out, three layers reach three, and so on. The number of layers is literally the radius of the receptive field. Pick how many layers and watch the reach of the highlighted node grow:

layers (hops) = 1
7 · Node classification, live

Labelling a whole graph from a few seeds

The classic graph task is semi-supervised node classification: you know the labels of only a handful of nodes and must infer the rest from the graph structure. Below, a network of two loosely-connected communities has just four labelled seeds (two per colour, ringed). A two-layer GCN is trained on those four alone — every other node starts unlabelled (grey). Press Train and watch labels propagate along the edges until the whole graph is correctly split:

L = − Σv ∈ labelled seeds log pθ(yv | graph)  (unlabelled nodes contribute no loss term)

Only the four seeds appear in the loss — yet the gradient trains a classifier for all fourteen. The reason is the two  multiplications: a seed's prediction depends on its 2-hop neighbourhood and on the shared W, so backprop pushes signal into W from the seeds' surroundings, and because W is reused at every node, what it learns there applies everywhere. This is transductive learning: the unlabelled nodes' features and edges are used in the forward pass (they shape the seeds' predictions); only their labels are withheld. The gradient at the output is the familiar p − y, non-zero only on seed rows, then flowed back through  and W.

Four labels coloured fourteen nodes. The GCN never saw a node's community directly — it inferred it from who connects to whom, because message passing makes densely-connected nodes converge to similar representations. This is how graph nets power fraud detection, recommendations and protein function prediction: labels are scarce, but the structure carries the signal.
8 · Graph attention

Not all neighbours matter equally

A GCN weights every neighbour by a fixed function of degree. The graph attention network (GAT) learns the weights instead: it scores each neighbour, softmaxes the scores into attention weights over the neighbourhood, and aggregates accordingly — the same attention idea as transformers, restricted to the graph's edges.

αij = softmaxj( LeakyReLU( aᵀ [ W hi ‖ W hj ] ) ),   hi′ = σ( Σj ∈ N(i) αij W hj )

The score concatenates the transformed anchor and neighbour, dots them with a learned vector a, and passes through a LeakyReLU; softmax over the neighbourhood normalises the scores to weights that sum to 1. In practice GAT runs several independent attention heads in parallel and concatenates their outputs — exactly the multi-head trick from transformers — so the node can attend to neighbours in several different ways at once.

9 · Over-smoothing

Why you cannot just stack more layers

If depth grows the receptive field, why not use fifty layers? Because each layer also averages neighbours, and repeated averaging is diffusion: push it far enough and every node's features blur into the same value, losing all the distinctions the network needed. This is over-smoothing. Slide the depth and watch six initially-distinct node features collapse together:

message-passing rounds = 1

Why a line and not random noise? Â is an averaging operator whose largest eigenvalue is 1, with eigenvector proportional to √(degree). Repeated multiplication ÂL shrinks every other eigen-component toward zero, so ÂLX converges onto that one direction: every node's feature becomes the same vector, scaled only by √(dv). Whatever distinguished the nodes is gone — all that survives is a degree pattern.

This is the central tension in graph nets: you need enough layers to reach the relevant neighbourhood, but too many wash out the signal. Most GCNs use just 2–3 layers. Remedies — residual/skip connections, jumping-knowledge, normalisation, or attention that can learn to ignore neighbours — let a few architectures go deeper, but shallow remains the norm.

10 · In practice

Three tasks, many flavours

Message passing supports all three graph tasks. Node-level: classify each node (the demo above). Edge-level: predict whether two nodes should be linked — the engine of recommendation and knowledge-graph completion. Graph-level: pool all node vectors into one — a permutation-invariant readout, hG = Σv hv (or mean/max) — and classify the whole graph, which is how models predict a molecule's properties from its atoms and bonds. (The sum is what keeps the graph label independent of node ordering.) The popular variants differ mainly in the aggregator: GCN (degree-normalised mean), GraphSAGE (sample-and-aggregate for huge graphs), GAT (attention), and GIN (a sum-based form proven as expressive as the classical graph-isomorphism test).

The throughline of this lab holds here too: the loss and gradient are unchanged from every other dive. What is special to graphs is only the layer — an aggregation that respects the irregular, orderless structure of the data — and from that single idea comes drug discovery, traffic forecasting, fraud rings, and recommendation at billion-edge scale.

11 · Recap

What you saw

A graph network learns by message passing: every node aggregates its neighbours with a permutation-invariant function and updates through a shared learned transform. You did one GCN layer by hand (aggregate the neighbourhood, transform, activate), saw the whole-graph matrix form  H W, and watched depth extend the receptive field one hop per layer. A two-layer GCN coloured an entire network from four seeds; graph attention learned which neighbours matter; and over-smoothing showed why too much depth collapses every node into the same blur. Same loss, same gradient — a new kind of layer for a new kind of data.

Related concept cards cover graph neural networks, message passing, graph convolutions, graph attention, and node embeddings with their own animations.
Deep dive · with real numbers

GANs, from scratch

Most networks learn by being told the right answer. A generative adversarial network learns by being caught. One network, the generator, turns random noise into fake samples; a second, the discriminator, tries to tell those fakes from real data. Each makes the other better: the forger improves until its counterfeits pass, the detective sharpens until it can spot them — a game that, at equilibrium, forces the generator to reproduce the data distribution exactly. We will write the game down, derive its optimum, and watch a real generator paint a target shape out of pure noise.

1 · The idea

A forger and a detective

Picture a counterfeiter and a bank inspector locked in escalation. The counterfeiter (generator) prints fake notes from random ink; the inspector (discriminator) learns the tells and rejects them; the counterfeiter adapts, the inspector re-learns, and round after round the fakes get better. When the inspector can do no better than a coin flip, the counterfeits are indistinguishable from real money — and that is exactly the generator we wanted.

Generator

Noise z → fake sample. Never sees real data directly.

Discriminator

Sample → probability it is real. A learned critic.

Adversarial

One's gain is the other's loss. A minimax game.

Equilibrium

Fakes match data; the critic is reduced to guessing.

2 · The minimax game

One value function, two opposite goals

The discriminator D outputs a probability that its input is real. The generator G maps noise z to a sample G(z). They share a single value function — the discriminator tries to maximise it, the generator to minimise it:

minG maxD  V(D,G) = Ex∼data[ log D(x) ] + Ez∼noise[ log(1 − D(G(z))) ]

The first term rewards D for scoring real data near 1; the second rewards it for scoring fakes near 0. G only touches the second term — and wants the opposite, to make D(G(z)) near 1. Put numbers on it:

3 · The optimal discriminator

The best critic, in closed form

Freeze the generator. What is the best possible discriminator? Because the value function is an average over x, we can maximise it pointwise — and the answer is beautifully simple: at each x, D should report the fraction of probability mass there that is real.

Below, the real data is a fixed bell curve and the generator's distribution is another you can slide. The white curve is the optimal discriminator D* = pdata∕(pdata+pg). Drag the generator onto the data and watch D* flatten to 0.5 everywhere — the critic can no longer tell them apart:

generator mean = 1.5
4 · What the generator really minimises

The game is a divergence

Substitute that optimal D* back into the value function and the algebra collapses into something profound — the generator is minimising the Jensen–Shannon divergence between the fake and real distributions:

V(D*, G) = − log 4 + 2 · JSD( pdata ‖ pg )

JSD is a symmetric, always-finite cousin of the KL divergence: JSD(p ‖ q) = ½ KL(p ‖ m) + ½ KL(q ‖ m) where m = (p + q)∕2 is their average. It is zero only when the two distributions are identical and positive otherwise. So the generator's objective is to drive pg all the way onto pdata. At that global optimum pg = pdata, the divergence is zero and D*(x) = ½ everywhere — the detective is reduced to a coin flip.

Why exactly −log4 at the bottom? With D* = ½ every term inside V becomes log ½, so V = Edata[log ½] + Efake[log ½] = 2·log ½ = −log 4 ≈ −1.386. That is the theoretical promise of a GAN: the unique equilibrium of the game is the true data distribution.

5 · A GAN, live

Painting a distribution from noise

Here are two real networks playing the game. The generator (a small MLP) maps 2-D Gaussian noise to points on the plane; the discriminator (another MLP) learns to separate them from the target — a ring. The shaded background is the discriminator's current verdict (green = "looks real," dark = "looks fake"). Press Train and watch the fake cloud, scattered at first, migrate until it covers the ring and the background washes to grey:

No target image was ever shown to the generator — it never sees a real point. Its only signal is the discriminator's gradient: "move your samples in the direction that looks more real to me." From that alone it reconstructs the entire ring. The background settling toward an even grey is the equilibrium D* = ½: the critic genuinely can no longer tell the two apart.
6 · Why GANs are hard

The saturating gradient, and the fix

The theory is clean; the training is treacherous. Look at the generator's original term, log(1 − D(G(z))). Early on the generator is terrible, the discriminator rejects its fakes with confidence (D ≈ 0), and exactly there the gradient of log(1 − D) is nearly flat — the generator gets almost no signal precisely when it needs it most. This is the saturating gradient.

saturating: min log(1 − D(G(z)))  →   non-saturating: max log D(G(z))

The standard fix flips the objective: instead of minimising "the chance D catches me," the generator maximises "the chance D is fooled," log D(G(z)). The optimum is identical, but the gradient is strong exactly when D is winning — so the generator keeps learning even while being beaten. (The live demo uses this non-saturating loss.) Even so, the two-player dynamics can oscillate, stall, or diverge rather than settle; balancing the pair is the central art of training GANs.

Underneath, it is all the familiar gradient. The discriminator minimises a binary cross-entropy, whose gradient on its output logit is the same D − label that trained every classifier in this lab: D − 1 on a real sample (push the score up), D on a fake (push it down). The generator has no labels and never touches a real point — it learns purely by backpropagating through the frozen discriminator: ∂loss∕∂(G's weights) = ∂loss∕∂D · ∂D∕∂(G output) · ∂(G output)∕∂(weights). "Move your samples wherever D thinks looks more real."

The saturating problem lives in that first factor. Under the original loss log(1 − D), the gradient on a fake's logit works out to −D — proportional to D itself, so when the discriminator confidently rejects (D ≈ 0) the signal nearly vanishes. The non-saturating loss −log D instead gives D − 1, proportional to (1 − D), which is largest exactly when D ≈ 0:

saturating grad ∝ D  |   non-saturating grad ∝ (1 − D)

Numerically, when the generator is losing badly at D = 0.1: the saturating push is just 0.1 (feeble, right when G most needs to learn), while the non-saturating push is 0.9 (strong). At D = 0.9 they swap — but by then the generator is already winning. Same optimum, far healthier gradient when it counts.

7 · Mode collapse

The failure that haunts every GAN

The most infamous failure: the generator discovers one output that reliably fools the discriminator and produces only that, ignoring the rest of the data. Fakes look fine individually but have no variety — a face generator that draws the same face, a digit model stuck on a single "7." Here the target has eight separate clusters; toggle between a healthy generator that covers them all and a collapsed one that has piled all its mass onto a few:

Collapse happens because the generator is only ever asked to fool the discriminator on the current batch, not to match the full diversity of the data — covering one mode perfectly can be a stable trap. Remedies include showing the discriminator several samples at once (minibatch discrimination), unrolling its updates, or changing the distance the game minimises altogether.

8 · Better games

Wasserstein and beyond

Jensen–Shannon divergence has a flaw: when the fake and real distributions barely overlap — common early in training — it saturates and its gradient vanishes, starving the generator. The Wasserstein GAN replaces it with the earth-mover (Wasserstein) distance, the minimal "cost" to transport one distribution onto the other, which gives a smooth, informative gradient even when the distributions are far apart:

W(pdata, pg) = inf E[ ‖x − y‖ ]  (cheapest transport plan from pg to pdata)

The discriminator becomes a "critic" that scores realness on an unbounded scale rather than outputting a probability, kept well-behaved by constraining it to be 1-Lipschitz (its output cannot change faster than its input) — the condition that the Kantorovich–Rubinstein duality needs to turn the Wasserstein distance into a maximisation over critics. Weight clipping (as in the live demo above) and the gradient penalty are two ways to enforce that bound. WGANs train far more stably and rarely mode-collapse, which is why the technique became standard.

9 · In practice

What GANs built, and what came after

GANs powered a decade of generative leaps: DCGAN made them work with convolutions, StyleGAN produced photorealistic faces with controllable style, conditional and image-to-image variants (pix2pix, CycleGAN) enabled sketch-to-photo and style transfer. Their signature strengths are sharp samples and fast, single-pass generation. The trade-off is the training difficulty you have just seen — instability and mode collapse — which is precisely why diffusion models (their own dive in this lab) have overtaken GANs for many image tasks: they are slower to sample but far more stable to train and cover the full data distribution by construction.

The throughline of the lab holds once more: a differentiable loss and a gradient. What is singular about the GAN is that the loss is not fixed — it is another network, learned in lockstep, so the target moves as the generator chases it. That moving target is both the source of their remarkable sample quality and the reason they are so hard to train.

10 · Recap

What you saw

A GAN is a two-player game: a generator turning noise into samples and a discriminator judging them, sharing one value function that one maximises and the other minimises. You scored the game by hand, derived the optimal discriminator D* = pdata∕(pdata+pg) and the result that the generator is minimising Jensen–Shannon divergence — zero only when the fakes match the data and the critic is reduced to a coin flip. You watched a real generator reconstruct a ring from noise using nothing but the discriminator's gradient, met the saturating-gradient trap and its non-saturating fix, saw mode collapse, and met the Wasserstein distance that tames it. A loss that is itself a learned, adversarial network — same gradient, a moving target.

Related concept cards cover GANs, the generator & discriminator, mode collapse, Wasserstein GANs, and adversarial training with their own animations.
Menu ← → move · Esc menu · 1–9 jump