EVERYTHING AIAI engineering, made visual
0/13 complete
LESSON 07 · DEEP LEARNING × AI · BUILD

99% on the training set.
60% on the world.

That gap is not bad luck — it is memorisation, and a big enough network will happily do it on data with no pattern at all. This lesson is the tax office: weight decay, dropout, normalization, augmentation and early stopping, each one a different way to buy generalization by giving up a little training accuracy.

75 MIN · 8 CHAPTERSPREREQ · PHASE 3 · LESSON 06
FIG. 07 / ONE DIAL · THREE REGIMES
training points held out fit
LESSON 07TYPE · BUILD~75 MINPREREQ · PHASE 3 · LESSON 06ORIGINAL LESSON ↗
THE 60-SECOND VERSIONThen meet the gap ↓
01 / THE GAP IS THE SYMPTOM

Training accuracy is a promise. Held-out accuracy is the evidence.

A big enough network can drive training loss to zero on completely random labels — Zhang et al. showed it on ImageNet in 2017. So a perfect training score proves nothing except that the model had the capacity to memorise. The number that matters is the gap: 99.9% train against 65% test is a 34.9-point alarm.

99.9 − 65 = a 34.9-point overfitting gap
02 / FIVE WAYS TO TAX COMPLEXITY

Each regularizer constrains a different freedom.

Weight decay keeps any single weight from growing large. Dropout stops units from co-adapting. Normalization makes the loss landscape smoother so the optimizer finds flatter minima. Augmentation makes memorising one view useless. Early stopping ships the checkpoint at the validation minimum instead of the last one.

magnitude · redundancy · landscape · views · time
03 / MEASURE, THEN DOSE

Regularization is a dial, not a switch.

Every technique trades capacity for generalisation: push it too far and the model underfits, which is just as wrong as overfitting. The source's rule of thumb — gap over 10% heavy, 5–10% moderate, under 5% leave it alone — is a starting dose. Then you test one change at a time against the held-out score, because that is the only referee that is not fooled.

gap > 10% · 5–10% · < 5%
MENTAL MODEL IN ONE SENTENCE

Regularization is a tax on complexity: you surrender a little training accuracy so the model stops paying attention to noise — and the held-out score, never the training score, is the ledger you balance.

By the end you will be able to read a train–validation gap and say which side of the bias–variance dial you are on; compute one weight-decay step and the half-life of a shrinking weight; explain why L1 deletes weights while L2 only shrinks them; run dropout with inverted scaling and prove its expected value is unchanged; compare BatchNorm, LayerNorm and RMSNorm with actual numbers; pick an early-stopping patience and count the epochs it saves; and choose a defensible regularization budget instead of sprinkling techniques you cannot justify.

THE GAP THAT GIVES YOU AWAY

Two numbers.
One verdict.

A model that scores 99% on the data it trained on has told you almost nothing. The number that matters is the other one — the score on data it has never seen — and the distance between them is the overfitting gap that every technique in this lesson exists to close.

Every model sits somewhere on a spectrum. Too simple and it cannot capture the pattern at all; that is underfitting, and its signature is a small gap with a low score on both sides. Too complex and it starts fitting the particular noise of the training set; that is overfitting, and its signature is a big gap. The sweet spot is in between, and regularization is how you push a model toward it from the overfit side.

the source lesson's spectrum, in train / held-out accuracy: underfit train 60.0% held-out 58.0% gap 2.0 too simple good fit train 95.0% held-out 92.0% gap 3.0 generalizes overfit train 99.9% held-out 65.0% gap 34.9 memorized noise the gap is the quantity to attack — not the training score.

Read the middle row again: the good model is worse on the training set than the overfit one, and that is the point. Training accuracy is a promise the model makes to data it has already seen. Held-out accuracy is the evidence about data it has not. Optimizing the promise instead of the evidence is the single most common way a beginner ships a model that fails in production.

The overfitting spectrum, on one axis

Slide the capacity dial right and watch the two curves split. Then raise λ — the tax on complexity — and watch the validation curve flatten while the training curve pays a little. The random-label switch is the Zhang et al. experiment in miniature: capacity still drives training loss down, and validation never learns anything.

capacity 6.0 of 10 train loss 0.0474 validation loss 0.4174 gap 0.3700 best capacity 3.22 val loss there 0.3470 zone overfit no regularization: the only defense is stopping at the right capacity.

Curves are a simplified teaching model, not a trained network — the shape is what matters: validation loss is U-shaped, training loss is not. Regularization raises the left end of the U and lowers the right end, which is why it works best when you are on the overfit side.

The extreme case is not a thought experiment. Zhang et al. (2017) took standard image classifiers, replaced every label in ImageNet with a random one, and trained. The networks drove training loss to near zero — they memorized a million random input-output pairs — while held-out accuracy never rose above chance (0.1% for 1,000 classes). There is no pattern in random labels, so the only way to fit them is to memorize. With enough parameters, that is exactly what a network does.

This is why “my training accuracy is great” is not evidence of anything except capacity. GPT-3 has 175 billion parameters trained on roughly 500 billion tokens of text, which is plenty of capacity to regurgitate passages verbatim if nothing stops it. Scale makes the gap problem more pressing, not less: the bigger the model, the more it can memorize, and the more deliberately you have to make memorization unprofitable.

Worked check — three models, three gap ledgers

Suppose validation night gives you these three runs and nothing else. The gap column is the diagnosis; the held-out column is the verdict.

model A train 60.2% held-out 58.1% gap 2.1 → underfit model B train 95.1% held-out 92.3% gap 2.8 → good fit model C train 99.9% held-out 65.0% gap 34.9 → overfit ship B. A's gap is tiny, but the model has no capacity to spend — regularizing it further would only lower it. C needs regularization; B needs nothing and no longer trains. wrong move 1: add dropout to A — the gap is small, not the problem. wrong move 2: ship C because its training accuracy is highest.

Model C is the trap. Its 99.9% training score is the highest of the three and the least useful: 34.9 points of that skill exist only on training data. The diagnosis is mechanical — a gap over 10% means heavy regularization, 5–10% moderate, under 5% leave the model alone — and the practice is a loop: measure the gap, change one thing, measure again.

Quick check

A run reports 99.4% training accuracy and 61.2% validation accuracy. What is the most accurate diagnosis?

The takeaway: the gap is the symptom, and it can be large in either direction of the complexity dial. Everything from here on is a different way to tax complexity — and each one pays for generalisation with a little training accuracy.

THE COMPLEXITY TAX

Every weight
pays rent.

Weight decay adds the squared size of every weight to the loss. A weight that is not earning its keep now costs something, every step, and the optimizer will shrink it toward zero. That is the whole mechanism — and it is the scalar one-step arithmetic you can do in your head.

The source’s two functions are the entire idea: add a penalty equal to half the squared magnitude of all the weights, and take its gradient. The task loss still does its job; the penalty adds a second force that pulls every weight toward zero, proportional to how large that weight currently is.

total_loss = task_loss + (λ / 2) · Σ wᵢ² d(penalty)/dwᵢ = λ · wᵢ ← the gradient is just λ times the weight so the update becomes: wᵢ ← wᵢ − lr · (task_gradᵢ + λ · wᵢ) = wᵢ · (1 − lr·λ) − lr · task_gradᵢ

The second line is the one to internalise: with no task gradient at all, weight decay multiplies every weight by the same factor (1 − lr·λ) each step. A weight of 4.0 with lr = 0.1 and λ = 0.01 becomes 4.0 × 0.999 = 3.996 after one step. Nothing dramatic happens in one step — which is exactly why it works: a small, relentless pull toward small weights, applied thousands of times.

Why would small weights generalise better? An overfit model is one that has amplified directions in the data that happen to separate the training examples — including the noise. Large weights are the amplifier: a small change in an input produces a big swing in the output. Keeping weights modest limits the model’s effective capacity and forces it to rely on robust features that move the output together, instead of memorized quirks that each need one big weight to fire.

Polynomial fit playground: degree vs λ

Ten noisy samples, six held out, one smooth curve underneath. Turn the degree up and the fit chases noise — training error falls toward zero while held-out error climbs. Then raise λ and watch the side panel bend back: the same model, told to keep its weights small.

degree 9 · λ 0.10000 train MSE 0.1863 (10 points) validation MSE 0.2251 (6 points) gap 0.0388 largest |w| 0.358 best λ for degree 9: 0.00109 train 0.0369 · validation 0.0327 → past the sweet spot: the penalty is now the problem.

Same sixteen points every run (fixed seed). The side panel is the whole argument for weight decay in one picture: for a fixed degree, held-out error is U-shaped in λ, and the minimum moves as you change capacity. The data and curve are a simplified teaching model.

Worked check — the penalty ledger and one decay step

First the ledger, on the source’s five weights with λ = 0.01. The penalty is (λ/2)·Σw², and each weight’s gradient contribution is λ·w:

weights 0.50 −1.20 3.00 0.10 −2.50 squares 0.25 1.44 9.00 0.01 6.25 Σ = 16.95 penalty (0.01 / 2) × 16.95 = 0.08475 gradients 0.005 −0.012 0.030 0.001 −0.025 the 3.00 weight is the largest, and it receives the largest decay gradient (0.030 = λ × 3.00).

Now one real training step, on a single weight w = 4.0 with a task gradient of 0.50, learning rate 0.1, λ = 0.01:

task gradient 0.50 decay gradient λ·w = 0.01 × 4.0 = 0.040 total gradient 0.54 update w ← 4.0 − 0.1 × 0.54 = 3.946 without decay w ← 4.0 − 0.1 × 0.50 = 3.950 one-step difference 0.004 pure decay (no task gradient), repeated: step 1 4.000 × 0.999 = 3.996 step 100 4.000 × 0.999¹⁰⁰ = 3.6191 step 1000 4.000 × 0.999¹⁰⁰⁰ = 1.4706 half-life ln(2) / 0.001 ≈ 693 steps

The last two lines are the intuition for λ. The decay removes a fixed fraction of each weight per step, so a weight left to the penalty alone halves every 693 steps — but 1,000 steps of pure decay has only brought 4.0 down to 1.47, because 0.1% per step compounds slowly. Real training runs have thousands to millions of steps, which is when that gentle pull becomes decisive. Raise λ and the half-life shortens in proportion.

Typical weight-decay strengths from the source lesson. Match the value to the optimizer, not just the model.
SettingTypical λWhy
AdamW · transformers0.01decoupled decay; the modern LLM default
SGD · CNNs1e-4smaller, because SGD has no adaptive rescaling
Heavily overfit runs0.1a big tax for a big gap — watch for underfitting
Quick check

A weight has no task gradient at all on this step. With w = 2.0, λ = 0.01 and lr = 0.1, what does one step of weight decay do to it?

The takeaway: weight decay is a contract — every weight pays lr·λ·w per step for the right to exist, large weights pay more, and the model ends up with only the weights it actually needs. The polynomial playground above is the same trade in one picture: crank λ and the wild fit straightens out.

L1: THE PENALTY THAT DELETES

Two penalties.
One of them deletes.

Weight decay shrinks weights smoothly and never quite reaches zero. Swap the square for an absolute value and the picture changes: the penalty becomes a flat tax, and small weights run out of money. This is the difference between shrinking a model and pruning it.

L1 regularization adds λ · Σ|wᵢ| to the loss instead of (λ/2) · Σwᵢ². The derivative is not λw this time — it is λ · sign(w), which is constant in magnitude: every nonzero weight is pushed toward zero by the same amount, lr·λ per step, no matter how large or small it is. Large weights barely notice; small weights get wiped out. That is why L1 produces exact zeros while L2 only approaches them.

L2 penalty (λ/2)·Σwᵢ² gradient λ·wᵢ shrink ∝ size L1 penalty λ·Σ|wᵢ| gradient λ·sign(wᵢ) shrink = constant one weight, w = 0.3, λ = 0.01, lr = 0.1, no task gradient: L2 step k: w = 0.3 × 0.999ᵏ after 100 steps 0.2714 L1 step k: w = 0.3 − 0.001·k after 100 steps 0.2000 after 300 steps 0.0000 ← exact L2 removes a shrinking fraction; it never arrives at 0. L1 removes a fixed amount; it arrives in finite steps.

In two dimensions the geometry makes it obvious. The set of weights with a fixed L2 budget is a circle; with a fixed L1 budget it is a diamond with corners sitting on the axes. The regularized solution is where the smallest task-loss contour touches the budget shape — and the diamond’s corners are always waiting there. Touch a corner and a whole weight is zero. The circle has no corners, so the contact point almost never lands on an axis; L2 shrinks, L1 selects.

L1 vs L2: which one deletes a weight?

The ellipses are the task loss; the dashed shape is the penalty budget. The solution is where the smallest untouchable ellipse touches the budget. Squares have corners; circles do not — that single geometric fact is the difference between shrinking and deleting.

solution w [1.5158, 1.0447] task loss 0.7041 penalty 1.6945 total 2.3987 zero coordinates 0 ‖w‖₁ = |w₁|+|w₂| 2.5605 ½‖w‖₂² 1.6945 → L2 has shrunk both coordinates, and neither is exactly 0.

A simplified teaching loss (a tilted quadratic) and a two-weight model, chosen so the geometry is visible. The trajectories start at (0, 0): L1 uses the exact soft-threshold step, L2 folds the shrinkage into the gradient. The source’s exercise connects this picture to feature deletion — L1 is why Lasso selects.

Worked check — where the gradient stops being proportional

The constant step is not an approximation. Differentiate the penalty term directly and plug in two weights of different sizes:

w = +0.30 L2 gradient λw = 0.003 L1 gradient λ·sign(w) = 0.010 w = +0.03 L2 gradient λw = 0.0003 L1 gradient λ·sign(w) = 0.010 at the same lr = 0.1: L2 step sizes 0.0003 vs 0.00003 proportional — small stays small L1 step sizes 0.001 vs 0.001 identical — both march to zero once w = 0 exactly, the soft-threshold update keeps it there, so L1 lands on zero in finite steps. L2 subtracts a fraction of whatever is left, so it approaches zero asymptotically and never quite arrives.

The practical reading: L1 is a sparsity regularizer, useful when you want the model to declare that a feature or a unit is unnecessary. L2 is a smoothness regularizer, useful when you want every weight a little smaller but none gone. (At w = 0 the L2 update is exactly 0 too, so a zero would stay zero — but in real training a task gradient is always pulling, and L2 only approaches the origin asymptotically while L1 lands on it.) In practice most deep networks use L2/weight decay, because a zero inside a layer can be undone by the next layer’s weights — but L1 is the reason Lasso can delete features, and the pairing of the two (elastic net) is a standard toolkit entry.

The takeaway: the shape of the penalty is the shape of the solution. Squares give you smooth shrinkage; absolute values give you corners, and corners give you zeros. You met this geometry first as Lasso feature selection (Phase 2, Lesson 18) — inside a deep network, it is the same picture with millions of weights.

DROPOUT

Train with holes.
Test whole.

On every training pass, delete each unit with probability p — then multiply the survivors by 1/(1 − p). That second half is what makes the trick work: the network trains under random damage, and inference needs no change at all.

The source’s Dropout class does exactly three things. In training mode it rolls a Bernoulli mask: each output survives with probability 1 − p, dies with probability p. Dead units become 0.0. Living units are divided by 1 − p. In eval mode it returns the inputs untouched. The backward pass applies the same mask, scaled the same way, so a dropped unit receives exactly zero gradient for that step.

during training output = activation(z) · mask / (1 − p) mask[i] ~ Bernoulli(1 − p) (1 with prob 1−p, else 0) during testing output = activation(z) no mask, no scaling expected value E[output] = (1 − p) · (a / (1 − p)) = a the scaling cancels the masking exactly.

Why random deletion regularizes anything? Because it makes co-adaptation unprofitable. A unit that learns to fire only when a specific partner unit is present is betting on a partner that disappears on half the passes; the loss punishes that bet. The network is pushed toward redundant representations — several units that can each do the job — which is exactly the kind of robustness that transfers to unseen data.

There is a bigger way to see it. A layer with N units has 2^N possible on/off patterns, so dropout is training a different subnetwork on every pass, all sharing the same weights. 10 units give 2¹⁰ = 1,024 subnetworks; 16 units give 65,536; 100 units give about 1.27 × 10³⁰. At test time you use every unit, and the scaling makes the full network’s output approximately the average of all those subnetworks — a gigantic ensemble for the price of one model.

Dropout: train with holes, test whole

Every training pass rolls a new mask: each unit is zeroed with probability p, and the survivors are multiplied by 1/(1 − p). The dashed lines are the expected values — and they land exactly on the eval bars. That is the whole trick: the network sees a different subnetwork each step, and inference needs no change at all.

p 0.30 scale 1/(1 − p) 1.4286 expected active units 8.4 of 12 a unit whose eval value is 3.00: kept in training 3.00 × 1.4286 = 4.2857 dropped in training 0.0000 expected (p = 0.30) 3.0000 ← equals eval subnetworks implied by 12 units: 2¹² = 4096 → light masking: the usual transformer-to-CNN range.

The mask is re-rolled every 1.25 s from a deterministic sequence, so every reader sees the same passes. In the source’s Python, the backward pass multiplies by the same mask and the same 1/(1 − p) — dropped units receive exactly zero gradient for that step.

Worked check — why 1/(1 − p) is the exact right factor

Take one unit whose no-dropout activation at test time is 3.0. Under a mask, it either survives (and gets scaled) or dies (and outputs 0). Watch the expected value:

p = 0.5 scale 1/(1−0.5) = 2.000 kept: 3.0 × 2.000 = 6.000 dropped: 0.000 E[out] 0.5 × 6.000 + 0.5 × 0.000 = 3.000 = the eval value p = 0.2 scale 1/0.8 = 1.250 kept: 3.0 × 1.250 = 3.750 dropped: 0.000 E[out] 0.8 × 3.750 + 0.2 × 0.000 = 3.000 = the eval value p = 0.1 scale 1/0.9 = 1.111… kept: 3.0 × 1.1111 = 3.3333 dropped: 0.000 E[out] 0.9 × 3.3333 + 0.1 × 0.000 = 3.000 = the eval value without inverted scaling (classic dropout), the same unit: kept: 3.0, dropped: 0 → E[out] = (1−p)·3.0 test time then has to multiply by (1−p) to match. inverted dropout moves that correction into training, so inference code never needs to know dropout exists.

The same algebra runs backward. The gradient arriving at the unit is multiplied by mask / (1 − p): a surviving unit contributes its usual gradient amplified by 1/(1 − p), a dead unit contributes 0. Averaged over masks, the expected gradient matches the no-dropout gradient — so the updates, like the outputs, are unbiased.

Quick check

A unit's activation at test time should be 4.0. During one training pass with p = 0.25 the unit survives the mask. What does inverted dropout output, and why?

The takeaway: dropout is noise that cannot be memorized. The mask changes every step, the expectation is preserved exactly, and the network ends up redundant on purpose — a hallmark of models that generalize.

NORMALIZATION LAYERS

Same trick,
different axis.

BatchNorm, LayerNorm and RMSNorm all rescale activations to a predictable size — they just disagree about which numbers count as “the group”. That disagreement decides which one can run at batch size 1, which one transformers use, and which one is 10% cheaper.

Batch normalization looks down each feature column of a mini-batch: it computes the mean and variance of that feature across the batch, normalizes every value with them, then applies two learnable parameters — a scale γ and a shift β — so the layer can undo the normalization if that is what the network wants. During training those statistics come from the current batch; during inference they come from running averages accumulated with momentum 0.1 (90% old value, 10% new batch), because inference batches may not resemble training batches at all.

BatchNorm, per feature j across the batch: μ = (1/B) · Σ xᵢ σ² = (1/B) · Σ (xᵢ − μ)² x̂ = (xᵢ − μ) / √(σ² + ε) y = γ · x̂ + β γ, β are learned running stats (inference): running = 0.9 · running + 0.1 · batch_stat

The original paper credited “internal covariate shift” — the idea that each layer keeps chasing a moving input distribution. Santurkar et al. (2018) showed that explanation is wrong: the benefit is that the loss landscape gets smoother. Gradients become more predictive and the optimizer can take larger steps safely, which is why BatchNorm lets you raise the learning rate instead of babysitting it. The cost is the batch dependency: with batch size 1 the mean is the sample itself and the variance is zero, so x̂ = (x − x)/σ = 0 and the layer can only output β. Small batches (< 32) give noisy, unhelpful statistics.

Layer normalization fixes that by switching the axis. It normalizes across the features of one sample: mean and variance come from that sample alone, no other row is involved. The computation is now identical in training and inference, independent of batch size, and safe for variable-length sequences — which is precisely why transformers use it. RMSNorm takes the next step and removes the mean subtraction entirely: divide by the root mean square, multiply by γ, no β. The centering step turns out to contribute very little to accuracy while costing computation, so RMSNorm matches LayerNorm quality with roughly 10% less overhead — enough, at LLM scale, that LLaMA, LLaMA 2/3 and Mistral all use it.

Three normalizations, one batch, side by side

Edit sample 1 and watch which axis each method normalizes. BatchNorm reads down the column and depends on the batch; LayerNorm reads across the row and does not. RMSNorm is LayerNorm without the centering — its output has rms 1 but not mean 0. Switch the batch to one sample and BatchNorm has nothing left to measure.

RAW BATCH · rows = samples, columns = features

f1f2f3f4
s11.0003.0005.0007.000
s23.000-1.0000.0002.000
s35.0000.0004.0001.000
s47.0003.0002.0003.000

BATCHNORM OUT · γ 1, β 0

f1f2f3f4
s1-1.3420.9801.1721.646
s2-0.447-1.260-1.432-0.549
s30.447-0.7000.651-0.988
s41.3420.980-0.391-0.110

column stats f1 · μ 4.000 · σ² 5.000

LAYERNORM OUT · sample 1 · γ 1, β 0

f1f2f3f4
s1-1.342-0.4470.4471.342

row stats · μ 4.000 · σ² 5.000 · mean(out) 0.000

RMSNORM OUT · sample 1 · γ 1

f1f2f3f4
s10.2180.6551.0911.528

rms(in) 4.583 · mean(out) 0.873 · rms(out) 1.000

sample 1 [1.00, 3.00, 5.00, 7.00] row mean 4.000 row variance 5.000 (σ 2.236) BatchNorm column f1 μ 4.0000 · σ² 5.0000 LayerNorm row μ 4.0000 · σ² 5.0000 RMSNorm row rms 4.5826 output, sample 1: BatchNorm [-1.342, 0.980, 1.172, 1.646] LayerNorm [-1.342, -0.447, 0.447, 1.342] RMSNorm [0.218, 0.655, 1.091, 1.528] mean of LayerNorm out 0.0000 (centered) mean of RMSNorm out 0.8729 (not centered) rms of RMSNorm out 1.0000 (scale only) running mean after k batches of the same mean, momentum 0.1: k = 1 10.0% of the way there k = 3 27.1% k = 10 65.1% k = 30 95.8%

BatchNorm is the odd one out: its numbers change when you change the other samples, and with a batch of one it can only output β. That is why transformers — which often run with batch size 1 during generation and variable-length sequences — normalize across features instead, and why modern LLMs drop LayerNorm’s mean subtraction for RMSNorm’s ~10% savings.

Worked check — one row, three normalizations

Take the row [1, 3, 5, 7] and run it through all three. The numbers are small enough to check by hand; they are also exactly what the lab above displays.

mean μ = (1+3+5+7)/4 = 4 variance σ² = (9+1+1+9)/4 = 5 σ = √5 = 2.2361 BatchNorm column stats (same row, as one column of a batch): x̂ = [(1−4), (3−4), (5−4), (7−4)] / 2.2361 = [−1.3416, −0.4472, +0.4472, +1.3416] with γ = 2, β = 0.5: y = [−2.1833, −0.3944, +1.3944, +3.1833] LayerNorm on the same row: identical arithmetic, applied per sample: x̂ has mean 0 and rms 1 before γ and β. RMSNorm (γ = 1): rms = √((1+9+25+49)/4) = √21 = 4.5826 y = x / 4.5826 = [0.2182, 0.6547, 1.0911, 1.5275] mean(y) = 0.8729 ← not centered rms(y) = 1.0000 ← scale normalized batch size 1, BatchNorm: x̂ = (x − x)/√(0 + ε) = 0 output = γ·0 + β = 0 — the signal is erased, only β survives. running mean with momentum 0.1 (starting from 0), batch means 4.0, 4.5, 5.0: after batch 1 0.9×0 + 0.1×4.0 = 0.400 after batch 2 0.9×0.4 + 0.1×4.5 = 0.810 after batch 3 0.9×0.81 + 0.1×5.0 = 1.229 after k batches of a constant mean μ: 1 − 0.9ᵏ → 65% at k = 10. the running average lags on purpose; that lag is the stability.

Two details worth noticing. LayerNorm output really is zero-mean unit-variance before γ and β — the lab’s readout shows mean 0.0000 and rms 1.0000 for it. RMSNorm is not zero-mean: on this row the output mean is 0.8729. What it keeps is the scale guarantee, and the source’s measurements found that is most of the benefit. The running-statistics trace is the other half of the design: inference does not use today’s batch at all, which is why forgetting model.eval() makes a BatchNorm model misbehave.

The normalization family, side by side. The axis is the choice that matters; everything else follows from it.
MethodNormalizes acrossBatch size 1?Train vs evalTypical home
BatchNormthe batch, per featuremeaningless — output is βdifferent (batch vs running stats)CNNs, large batches
LayerNormthe features, per samplefineidenticaltransformers
RMSNormthe features, per sample, scale onlyfineidenticalmodern LLMs (LLaMA, Mistral)

The takeaway: normalization regularizes by making the landscape smoother and the activations predictable, not by shrinking anything. Choose the axis that survives your deployment conditions: batch for CNNs with big batches, features for sequences, and features without the mean for the largest models.

EARLY STOPPING

The cheapest
regularizer there is.

Training loss falls forever. Validation loss falls, bottoms out, and climbs. Early stopping just refuses to keep training past the bottom — and ships the checkpoint from the minimum instead of the end.

The procedure is four lines of bookkeeping. After every epoch, evaluate on the validation set. If the validation loss is the best so far, save a copy of the weights and reset a counter. If it is not, increment the counter. When the counter reaches the patience — typically 5 to 20 epochs — stop training and load the saved copy. The model you ship is the one from the bottom of the validation curve, not the one from the end.

for epoch in range(1000): train_one_epoch() val_loss = evaluate(validation_set) if val_loss < best_val_loss: best_val_loss = val_loss best_weights = copy(weights) # the checkpoint epochs_without_improvement = 0 else: epochs_without_improvement += 1 if epochs_without_improvement >= patience: break # stop load(best_weights) # ship the minimum

It is “free” in the sense that it adds no hyperparameter to the loss and no change to the model — but it is not free of judgment. Patience is the one dial: too small and noise in the validation curve stops you before a real improvement; too large and you pay for epochs you know are useless. And the validation set must stay honest: stopping on the test set turns the test set into a training signal, and your final number becomes fiction.

Early stopping: the free regularizer

Training loss only ever falls. Validation loss falls, bottoms out, then climbs as the model starts memorising noise. Pick the patience — how many epochs you are willing to wait for a new best — and watch the green zone: epochs you never have to run, with the best checkpoint already saved from the bottom of the curve.

patience 12 epochs best epoch 49 of 120 best validation 0.3247 validation at stop 0.3475 stop epoch 61 epochs saved 59 of 120 (49%) final val (if run to 120) 0.4483 overfit tax avoided 0.1236 loss units attempted budget 1000 epochs → stop at 61, saving 939 epochs at 42 s/epoch: 11.0 hours

The curve is generated from a fixed formula with per-run noise (seeded), so every reader gets the same five runs. Switch seeds to see the honest part: the best epoch is never known in advance — 48, 48, 57, 57, 55 for seeds 1–5 with patience 20. Early stopping ships the checkpoint, not the last state.

Worked check — the checkpoint ledger

A synthetic run, built to show the arithmetic (not a measured log): the budget was 1,000 epochs, patience 20, and the validation curve looked like this at a few checkpoints.

epoch train loss val loss note 100 0.0410 0.4120 200 0.0185 0.3315 300 0.0094 0.2902 340 0.0071 0.2841 ← best; checkpoint saved here 360 0.0059 0.2981 ← 20 epochs without improvement → stop 500 (never run) — 1000 (never run) — epochs actually used 360 of 1000 → 640 saved (64%) overfit tax avoided 0.5210 (val at epoch 1000) − 0.2841 = 0.2369 at 42 s per epoch 640 × 42 s ≈ 26,880 s ≈ 7.5 hours saved shipped weights the epoch-340 checkpoint, not epoch 360

Two things to notice. First, the saved compute is not the point — the epoch-340 weights score better on held-out data than any later checkpoint, and shipping the epoch-360 weights would hand over 0.0140 more validation loss for nothing. Second, patience costs epochs on purpose: training continues for 20 epochs after the best one precisely so that a later, genuine improvement is not missed. That is the entire meaning of the dial.

Quick check

Validation loss is lowest at epoch 12 (0.284). Training continues to epoch 20, where patience 8 triggers a stop and validation loss is 0.351. The team runs to epoch 40 anyway and finishes at 0.402. Which weights do you ship?

The takeaway: the best epoch is unknowable in advance, which is why you save the checkpoint and let patience decide. It pairs with every other technique in this lesson and belongs in every training script by default.

AUGMENTATION & THE BUDGET

More data,
from the data you have.

Data augmentation is regularization without touching the model: transform the inputs while preserving the label, and memorising any single view stops paying. It is the largest free lunch in vision — and it comes with the same tax as everything else.

The source’s lists are the standard catalogue. For images: random crop, flip, rotation, colour jitter, cutout. For text: synonym replacement, back-translation, random deletion. For audio: time stretch, pitch shift, added noise. The mechanism is identical in each case — produce a new input that still deserves the same label, so the model is forced to learn the structure that survives the transform instead of the particular pixels, words or samples in front of it.

A model that sees each image once can memorize it. A model that sees fifty augmented versions of each image cannot — the only thing common to all fifty is the invariant content, and that is what gets learned. The effect is exactly a regularization effect: the effective size of the training set grows without collecting a single new example, which is why augmentation is the first thing to reach for on a vision task with a large gap. The subtlety is that the transform must preserve the label. Flipping a 6 to make a 9, or rotating a digit past legibility, teaches the model an invariance that is not true; that is worse than no augmentation at all.

The augmentation gallery

Data augmentation is regularization without touching the model: every epoch shows a different view of the same image, so memorising the original is useless. Each transform teaches one invariance — flip, tilt, position, noise, scale. None of them changes the label.

strength 50% transforms on 6 of 6 effective dataset ×6 per image per epoch ✓ original the one view the model memorises ✓ flip ↔ left–right is not the label ✓ rotate tilt is not the label ✓ shift + crop position is not the label ✓ noise sensor noise is not the label ✓ zoom scale is not the label → heavy augmentation: strong regularization, slower fitting.

The digit is a stylised illustration drawn in code, not a real dataset image, and the transforms are the source lesson’s list applied to it. The same idea covers text (synonym replacement, back-translation) and audio (time stretch, pitch shift) — the mechanism is always “same label, different input”.

Worked check — label smoothing by the numbers

Label smoothing is the fourth kind of augmentation: instead of transforming inputs, it transforms targets. A one-hot label claims 100% certainty; soften it with ε = 0.1 across 10 classes and the target becomes 0.9 for the correct class and 0.1/9 = 0.0111 for each of the other nine. What that does to the loss is measurable:

soft target t = [0.9, 0.0111 × 9] hard target h = [1.0, 0 × 9] the smoothed loss is H(t) + KL(t ‖ p), so its floor is H(t): H(t) = −(0.9·ln 0.9 + 9 × 0.01111·ln 0.01111) = 0.0948 + 0.4500 = 0.5448 minimum loss at the minimum, the model outputs exactly t — confident, not certain. an overconfident model, p(correct) = 0.999 and the rest spread: CE = 0.0009 + 0.9105 = 0.9114 higher than the floor so pushing p(correct) above 0.9 now makes the loss worse. the one-hot version of that row gives CE = −ln 0.999 = 0.0010 — a model trained to drive it to zero is being told to push logits to infinity.

That inversion is the entire regularizing effect: with hard targets, more confidence is always rewarded, so logits can sprawl; with smoothed targets, confidence beyond the target is penalized, so the model settles at a calibrated 0.9. GPT and most modern models use label smoothing or an equivalent (Phase 3, Lesson 05), and it is the natural companion to dropout — dropout fights variance in the features, smoothing fights overconfidence in the output.

None of these tools should be used blindly. The source’s prescription flow keys the dose to the gap you measured, and every row is a budget: you are trading some training accuracy for held-out accuracy, and past a point the trade turns negative (underfitting).

The source lesson’s prescription table: measure the train–validation gap, then match the dose.
Measured gapLevelBudget
over 10%Heavydropout p 0.3–0.5 · weight decay 0.01–0.1 · aggressive augmentation · early stopping
5–10%Moderatedropout p 0.1–0.2 · weight decay 0.001–0.01 · BatchNorm or LayerNorm
under 5%Lightdropout p 0.05–0.1 · weight decay 1e-4 · early stopping only if the curve turns

The regularization budget board

The source lesson’s prescription flow, turned into a board. Dial in the train–validation gap you actually measured, and read the budget: what to switch on, at what strength, and what each technique costs you if you overshoot. The gap decides the dose.

HEAVYgap 12.0% → regularization budget (over 10%)
dropoutp 0.3–0.5
weight decayλ 0.01–0.1
augmentationaggressive transforms
normalizationLayerNorm everywhere; RMSNorm in LLMs
label smoothingε 0.1
early stoppingalways: patience 5–20
What each technique constrains, and the price of overshooting it. Settings from the source lesson’s defaults.
TechniqueConstrainsMain risk
Dropoutco-adaptation: no unit can rely on anothertoo high and the model underfits; transformers use p 0.1, MLPs p 0.5, CNNs p 0.2–0.3
Weight decayweight magnitude: no single weight dominatesdecays biases and norm gains if applied blindly; use AdamW for Adam runs
Augmentationeffective dataset size: views, not memorisationa transform that changes the label teaches the wrong invariance
Normalizationactivation scale: smoother loss landscapeBatchNorm breaks at batch size 1 and with variable-length sequences
Label smoothingconfidence: targets stop being 100% certaintoo much smoothing caps confidence the model legitimately needs
Early stoppingtraining time: ship the best checkpoint, not the lastfree; the only cost is keeping a copy of the best weights
level HEAVY measured gap 12.0% rule gap > 10% heavy · 5–10% moderate · < 5% light for a tabular MLP: Small data overfits fast: dropout 0.1–0.5 on hidden layers, weight decay 0.001–0.01, early stopping with patience 5–20, and label smoothing if the model is overconfident. every technique trades capacity for generalisation. measure the gap first; a 2% gap needs none of this.

The board is a teaching summary of the source’s “when to apply what” flow — the numbers are typical ranges, not laws. The only reliable loop is: measure the gap, add one technique, measure the gap again.

The takeaway: augmentation and label smoothing both change the data rather than the model, which is why they can be added on top of weight decay and dropout without fighting them. In practice they are the first two levers a vision team touches, and the budget board above is how the source decides when that team should reach further.

CHECK YOURSELF

Five questions.
Then the terms worth keeping.

Answer before you look. The dropout-scaling question and the LayerNorm-versus-BatchNorm question are the two that separate “I read about regularization” from “I can debug a training run”.

0 / 5 answered · 0 correct

01What is overfitting in neural networks?

02How does dropout regularize a neural network?

03Why do transformers use LayerNorm instead of BatchNorm?

04What is the key difference between RMSNorm and LayerNorm?

05Why is it critical to call model.eval() before running inference in PyTorch?

Key terms, demystified

Click a card to swap the lazy description for what it actually means.

Exercises from the lesson

Four problems with exact numbers — spatial dropout arithmetic, a label-smoothing loss comparison, an early-stopping checkpoint patrol, and LayerNorm versus RMSNorm by hand. Try first; a worked answer is one click away.

  1. Implement spatial dropout for 2D data: instead of dropping individual neurons, drop entire feature channels. For a layer with 2 channels of 4 units (8 units total) at p = 0.5, compute the expected number of active units and the probability that a whole channel survives under both standard dropout and spatial dropout, then explain how the gradient pressure changes.
    Show one worked answer

    Expected active units are the same either way: 8 × (1 − 0.5) = 4.0. The structure is what differs. Standard dropout gives each channel the probability 0.5⁴ = 0.0625 that all four of its units survive (and 0.5⁴ = 6.25% that all four are zeroed), so channels are chopped into random fragments — the network is pushed to make every individual unit redundant. Spatial dropout rolls one mask per channel: each channel survives intact with probability 0.5, and when it survives all four units pass (scaled by 1/(1 − 0.5) = 2), which is 8× more likely to keep a channel whole. The redundancy pressure moves from units to channels: the network must not rely on any single feature map, but inside a live map the spatial structure (edges, textures) stays coherent. That is why spatial dropout is the standard choice in convolutional networks — unit-level masking destroys the spatial correlations convolutions exist to exploit. In the backward pass, spatial dropout multiplies by the shared per-channel mask (and the same factor 2), so whole channels receive zero gradient together.

  2. Label smoothing (ε = 0.1) on a 3-class problem and dropout (p = 0.5) both regularize. Take logits z = [4.0, 2.0, 0.5]. Compute the softmax probabilities, the hard-target cross-entropy for the correct class, and the smoothed cross-entropy. Which loss produces the larger gradient signal at this point, and why does that not mean it trains faster?
    Show one worked answer

    Softmax: e⁴ = 54.598, e² = 7.389, e⁰·⁵ = 1.6487, total 63.636 → p = [0.8580, 0.1161, 0.0259]. Hard target [1, 0, 0]: loss = −ln 0.8580 = 0.1532. Smoothed target [0.9, 0.05, 0.05]: loss = −(0.9·ln 0.8580 + 0.05·ln 0.1161 + 0.05·ln 0.0259) = 0.9(0.1532)+0.05(2.1531)+0.05(3.6533) = 0.1378+0.1077+0.1827 = 0.4282. The smoothed loss is 2.8× larger here, so the gradient step on these logits is larger — but it is pushing toward a different goal. The hard target's minimum is p = [1, 0, 0], which needs logits pushed to infinity; the smoothed target's minimum is p = [0.9, 0.05, 0.05], a finite target (its loss floor is H(t) = 0.9·ln(1/0.9) + 0.1·ln(1/0.05) = 0.0948 + 0.2996 = 0.3944). Train longer under smoothing and the model converges to calibrated-but-not-certain logits rather than racing away; combine it with dropout, which is what suppresses the variance that made the model overconfident in the first place.

  3. You log validation loss every epoch on a 40-epoch budget: 0.92, 0.74, 0.61, 0.52, 0.47, 0.44, 0.436, 0.441, 0.439, 0.447, and the curve keeps drifting upward after that. With patience 3, which epoch's weights do you ship, at which epoch does training stop, and how much computation did early stopping save? Repeat for patience 2.
    Show one worked answer

    The minimum is epoch 7 (1-based) at 0.436 — epochs 8 and 9 both fail to beat it (0.441, 0.439 are higher), and epoch 6's 0.44 is also higher. Patience 3: the window expires after three consecutive non-improving epochs, so training stops at epoch 10 (0.447) and you ship the epoch-7 checkpoint; the run consumed 10 of 40 epochs, saving 30 epochs, 75% of the budget. Patience 2: stop at epoch 9 instead, saving 31 epochs (77.5%) but accepting a stop that lands closer to the minimum — with noisier curves a short patience risks stopping before a later improvement, which is exactly the trade the patience dial sets. Note both answers ship epoch 7, not the last epoch: the final checkpoint had already overfit by 0.011 loss units, and on a real curve that drift is the thing that costs test accuracy.

  4. Normalize x = [2, −1, 4, 3] three ways by hand: LayerNorm (γ = 1, β = 0), RMSNorm (γ = 1), and the scale-only check. Report each output vector, its mean, and its rms, and say what RMSNorm keeps from LayerNorm and what it drops.
    Show one worked answer

    LayerNorm: mean = (2−1+4+3)/4 = 2; variance = ((0)² + (−3)² + (2)² + (1)²)/4 = (0+9+4+1)/4 = 3.5, σ = 1.8708. x̂ = [(2−2)/1.8708, (−1−2)/1.8708, (4−2)/1.8708, (3−2)/1.8708] = [0.0000, −1.6036, 1.0690, 0.5345]; mean 0.0000, rms 1.0000 (the output is exactly zero-mean unit-variance before γ and β act). RMSNorm: rms = √((4+1+16+9)/4) = √7.5 = 2.7386, output = x/2.7386 = [0.7303, −0.3651, 1.4606, 1.0954]; mean 0.7303, rms 1.0000 (check: (0.7303² + 0.3651² + 1.4606² + 1.0954²)/4 = (0.5333+0.1333+2.1333+1.2000)/4 = 1.0). So RMSNorm keeps the scale normalization — the output has rms exactly 1 whatever the input scale — and drops the centering: a positive input vector stays positive, mean 0.7303 instead of 0. This is the change Zhang & Sennrich measured as ~10% faster with equal accuracy, and why LLaMA and Mistral can drop both the mean subtraction and the β parameter.

Terms this lesson borrows from later lessons (or outside)

You do not need to master these here. Each one gets a proper treatment in its own lesson; the one-line meaning is enough to keep reading. Orange dotted underlines in the prose point back to this list.

  • bias–variance tradeoffSimple models underfit (bias), flexible models chase noise (variance). Regularization deliberately adds a little bias to remove a lot of variance — every technique in this lesson is that trade with a different knob. (Phase 2, Lesson 10)
  • L1 / Lasso regularizationΣ|wᵢ| added to a loss. Its diamond-shaped constraint region has corners on the axes, so weights land on exactly zero and delete their features — the feature-selection lesson's whole engine. (Phase 2, Lesson 18)
  • backpropagationThe reverse sweep that turns a loss into dL/dw for every weight. Weight decay adds λw to that gradient before the update, so the penalty flows through exactly the machinery built in the backprop lesson. (Phase 3, Lesson 03)
  • cross-entropy & label smoothingThe classification loss whose one-hot targets claim 100% certainty. Softening them to 0.9/0.1 keeps logits from racing to infinity — a regularizer folded into the loss function. (Phase 3, Lesson 05)
  • AdamW & decoupled weight decayIn Adam, adding λw to the gradient is rescaled by the adaptive denominators, so it is not the same as true weight decay. AdamW subtracts lr·λ·w directly from the weights — which is why every modern transformer trains with AdamW. (Phase 3, Lesson 06)
KEEP GOING

A picture is a start.
Practice is the rest.

This lesson is a port of an open course. Everything here traces back to it — and the next step is running the code yourself.

Lesson text adapted from AI Engineering from Scratch (Phase 03, Lesson 07) and the Math Foundations Notebook reference build. The eight labs (gap-spectrum explorer with a random-label control, polynomial fit playground with a live validation-versus-λ panel, L1/L2 penalty-contour lab, animated dropout mask with train/eval scaling, three-normalization numeric board, early-stopping patience explorer, augmentation gallery, and the regularization budget board), the three-model gap ledger, the weight-decay ledger with the pure-decay half-life, the L1-versus-L2 one-step race, the inverted-dropout expectation check at p = 0.5/0.2/0.1, the [1, 3, 5, 7] normalization row with the running-mean EMA trace, the synthetic early-stopping checkpoint ledger, the label-smoothing loss floor, and the L1/L2 memory hook are original to this page. Every analytic curve is labelled a simplified teaching model; every number shown is computed live by the labs or verified by hand in the prose.