← All posts

Foundations

Post 4 of 15

Overfitting and regularisation: dropout, weight decay and early stopping

Posts in this series · 15

In the first Foundations post we built a tiny network and trained it on one example. In the second we gave it a loss and walked downhill, and in the third we learned to walk downhill faster. All three posts quietly assumed something: that a smaller training loss is always better.

It is not. A student who memorises last year’s exam answers can score 100% on last year’s exam and fail this year’s. A network can do the same. This post is about telling memory apart from understanding, and about the handful of tricks, together called regularisation, that push a network towards understanding.

Why we hold data back

In one sentence: we hide some data from the network during training so we can later check whether it learned the pattern or just the examples.

We split our labelled data into three piles:

Everyday picture: the training set is the homework, the validation set is the practice exam you take to decide how to study, and the test set is the real exam. If you peek at the real exam while studying, your score stops meaning anything.

Why three piles and not two? Because every time we use the validation set to make a choice, we leak a little information about it into the model. After choosing among twenty settings by validation score, the best of the twenty looks slightly better on validation than it really is. The test set, untouched, gives the honest number. A common split is roughly 80% / 10% / 10%, but with very large datasets the validation and test piles can be a much smaller fraction.

One more rule: the split must be random (or, for speech and similar data, split by speaker or recording, so the same voice does not appear on both sides). If the validation set looks different from real use, its score answers the wrong question.

Twenty examples shuffled into training, validation and test piles
Split
Figure 1. Three piles. Training data changes the weights, validation data guides our choices, test data is opened once at the end.

Our toy: eight noisy points

To see overfitting happen we need a problem small enough to look at whole. Take the curve y=sin⁡(2πx)y = \sin(2\pi x) for xx between 0 and 1: one smooth wave, up then down. We pretend we do not know this curve. We only get 8 training points, evenly spaced at x=0,17,27,…,1x = 0, \tfrac17, \tfrac27, \ldots, 1, and each yy has a little random noise added, the way real measurements always do.

xx00.1430.2860.4290.5710.7140.8571
yy (train)−0.130.751.300.56−0.76−0.97−0.900.03

We also get 7 validation points halfway between, at x=114,314,…,1314x = \tfrac{1}{14}, \tfrac{3}{14}, \ldots, \tfrac{13}{14}, with their own noise:

xx0.0710.2140.3570.50.6430.7860.929
yy (validation)0.111.020.830.32−0.72−0.87−0.73

Our “model” is a polynomial: a sum of powers of xx, each with its own weight.

f(x)=w0+w1x+w2x2+⋯+wdxdf(x) = w_0 + w_1 x + w_2 x^2 + \cdots + w_d x^d

A polynomial is not a neural network, but it misbehaves in exactly the same way, and with only d+1d + 1 weights we can see every one of them. We score each fit with mean squared error (MSE, from post 2): the average of (prediction minus target) squared.

Overfitting and underfitting

Fit each degree to the 8 training points (by finding the weights with the lowest training MSE), then measure MSE on both sets:

Degree ddWeightsTraining MSEValidation MSE
1 (a line)20.4410.246
3 (a cubic)40.0360.058
780.0000.090

Read the table row by row.

Look at the weights the degree 7 fit needs: w3≈1166w_3 \approx 1166, w4≈−4077w_4 \approx -4077, w5≈6863w_5 \approx 6863, w6≈−5479w_6 \approx -5479. Huge numbers of opposite signs, cancelling each other almost perfectly at the training points. The cubic’s largest weight is about −38-38. Keep this in mind: overfit models tend to have huge weights. That observation is the whole idea behind weight decay.

What would change? With more training points (say 100 instead of 8), the degree 7 curve could no longer pass through all of them, so it would be forced to follow the average trend and its validation error would drop towards the cubic’s. More data is the best regulariser of all; the rest of this post is about what to do when you cannot get more.

Polynomial fits of rising degree to eight points, with training and validation error
Figure 2. Bendier is not always better. Slide the degree from 0 to 7. Training error only ever falls, reaching 0 at degree 7. Validation error is lowest at degree 3 (0.058) and rises again to 0.090.

Bias and variance in plain words

There are two different ways to be wrong, and the table shows both.

Simple models have high bias and low variance; flexible models have low bias and high variance. Underfitting is a bias problem; overfitting is a variance problem. Every method in the rest of this post reduces variance and accepts a little extra bias in exchange.

(Very large modern networks complicate this picture: past a certain size, validation error can fall again, a pattern called “double descent”. The plain trade-off above is still the right first intuition, and regularisation is still used in those large models.)

Model capacity

Capacity means how many different shapes a model can take. A degree 7 polynomial has more capacity than a cubic; a network with more neurons, layers or weights has more capacity than a smaller one.

Two ways to control it:

  1. Change the model: fewer weights, fewer layers, lower degree. Blunt, and you have to retrain for each choice.
  2. Keep the big model but restrict how it uses its capacity. This is regularisation. The degree 7 polynomial can still make a wave, but we discourage it from making wild swings.

Modern practice nearly always chooses the second: build a model with plenty of capacity, then regularise it. The next three sections are the three most common ways.

Weight decay: shrink every weight a little

In one sentence: add a cost for large weights to the loss, so the network only keeps a weight large if it really pays for itself.

Ltotal(w)=Ldata(w)+λ2∑jwj2L_{\text{total}}(\mathbf{w}) = L_{\text{data}}(\mathbf{w}) + \frac{\lambda}{2} \sum_{j} w_j^2

By convention we usually leave biases out of the penalty; a bias only shifts the output and does not make the curve wilder.

Everyday picture: a rubber band ties every weight to zero. The data pulls weights away from zero to fit the examples; the band pulls them back. A weight ends up large only if the data pulls hard enough to stretch the band. Noise pulls weakly and inconsistently, so it loses.

Its gradient

The slope of λ2w2\tfrac{\lambda}{2} w^2 with respect to ww is λw\lambda w. So the gradient of the total loss is the data gradient plus λw\lambda w, and one gradient descent step (learning rate η\eta, as in post 2) becomes:

w←w−η(∂Ldata∂w+λw)w \leftarrow w - \eta \left( \frac{\partial L_{\text{data}}}{\partial w} + \lambda w \right)

Multiply out the bracket and collect the ww terms:

w←(1−ηλ) w−η ∂Ldata∂ww \leftarrow (1 - \eta\lambda)\, w - \eta \, \frac{\partial L_{\text{data}}}{\partial w}

This is why the method is called weight decay: each step, every weight decays a little towards zero, then the data pushes it back where it is needed.

Worked by hand

Take one weight w=2w = 2, data gradient 0.50.5, learning rate η=0.1\eta = 0.1 and strength λ=0.1\lambda = 0.1.

  1. Shrink factor: 1−ηλ=1−0.1×0.1=0.991 - \eta\lambda = 1 - 0.1 \times 0.1 = 0.99.
  2. Shrink part: 0.99×2=1.980.99 \times 2 = 1.98. The weight lost 0.02 just for being large.
  3. Gradient part: η×0.5=0.1×0.5=0.05\eta \times 0.5 = 0.1 \times 0.5 = 0.05.
  4. New weight: 1.98−0.05=1.98 - 0.05 = 1.93.

Without weight decay the step would give 2−0.05=1.952 - 0.05 = 1.95. The difference, 0.020.02, is exactly ηλw=0.1×0.1×2\eta \lambda w = 0.1 \times 0.1 \times 2. Notice it is proportional to ww: a weight of 20 would lose 0.2, a weight of 0.2 only 0.002. Big weights are pulled hardest.

What would change? If the data gradient were 0 (the weight is useless for the data), only the shrink would act: after 10 steps w=2×0.9910≈w = 2 \times 0.99^{10} \approx 1.809, after 100 steps ≈\approx 0.732, heading steadily to zero. A useless weight fades away. If λ\lambda were 10 times larger (1.0), the shrink factor would be 0.9 and the weight would lose 0.2 per step instead of 0.02.

A note for the optimiser fans: with plain gradient descent, “add λw\lambda w to the gradient” and “multiply the weight by 1−ηλ1 - \eta\lambda” are the same thing. With Adam (from post 3) they are not, because Adam rescales the gradient. Loshchilov and Hutter (2019) showed the shrink form works better with Adam and called it AdamW; it is the default for training transformers today.

The idea has two histories. In statistics and inverse problems it is Tikhonov regularisation (Tikhonov, 1963) and, for linear regression, ridge regression (Hoerl and Kennard, 1970). For neural networks, Krogh and Hertz (1992) analysed why “a simple weight decay can improve generalization”.

A weight on a number line: first shrunk towards zero, then moved by its gradient
Repeat with the same gradient
Figure 3. One weight-decay step, split in two. Defaults w = 2, gradient 0.5, η = 0.1, λ = 0.1: shrink to 1.98, step to 1.93, against 1.95 without decay.

Weight decay on our polynomial

Now apply the L2 penalty to the degree 7 polynomial. (Here we can solve for the best weights directly instead of stepping, but the result is the same place gradient descent with weight decay would settle.) With LdataL_{\text{data}} the training MSE:

λ\lambdaTraining MSEValidation MSE
0 (no penalty)0.0000.090
0.000010.0230.065
0.00010.0390.044
0.00020.0570.041
0.0010.1120.058
0.010.2260.134
10.5130.374

The same U shape as the degree table, but now controlled by one smooth dial instead of by deleting weights. A tiny λ\lambda already tames the wild swings; around λ=0.0002\lambda = 0.0002 the degree 7 polynomial beats the plain cubic on validation (0.041 against 0.058). Too much λ\lambda crushes every weight towards zero and the curve flattens into an underfit line near the average of the data.

Notice that training error only ever rises as λ\lambda grows. That is the trade in one column: we give up training accuracy on purpose.

A degree 7 fit smoothed by a weight penalty, with its weights shrinking
Figure 4. One dial from overfit to underfit. At λ = 0 the curve threads every training point (validation 0.090). Near λ = 0.0002 it is a smooth wave (validation 0.041). At λ = 1 it is almost flat (validation 0.374).

L1: pushing weights to exactly zero

In one sentence: penalise the absolute size of each weight instead of its square, and many weights end up exactly zero.

Ltotal(w)=Ldata(w)+λ∑j∣wj∣L_{\text{total}}(\mathbf{w}) = L_{\text{data}}(\mathbf{w}) + \lambda \sum_j |w_j|

Why exact zeros? Compare the slopes of the two penalties. The L2 penalty λ2w2\tfrac{\lambda}{2} w^2 has slope λw\lambda w, which fades as ww gets small: near zero the pull is almost nothing, so a weight drifts towards zero but never quite arrives. The L1 penalty λ∣w∣\lambda |w| has slope ±λ\pm\lambda, a constant pull however small the weight is. A weight whose data gradient is weaker than λ\lambda is dragged all the way to zero and kept there.

Everyday picture: L2 is a tax that is a percentage of your wealth, so small fortunes pay tiny amounts and never quite reach zero. L1 is a flat fee per day: if your income does not cover it, you go to zero.

Worked by hand. Suppose two weights each want to sit at a target, c1=1c_1 = 1 and c2=0.3c_2 = 0.3, with data loss (w1−1)2+(w2−0.3)2(w_1 - 1)^2 + (w_2 - 0.3)^2. Use λ=0.8\lambda = 0.8.

What would change? With λ=0.5\lambda = 0.5, L1 subtracts 0.25: w1=0.75w_1 = 0.75, w2=0.05w_2 = 0.05, both survive. The rule is: a weight survives only if its target is bigger than λ/2\lambda / 2.

L1 on linear regression is called the lasso (Tibshirani, 1996). In deep learning L1 is used less than L2, mostly when we want sparse weights, for example to prune a network. Strictly, plain gradient descent on ∣w∣|w| makes a weight hop back and forth across zero, since the slope jumps from −λ-\lambda to +λ+\lambda; practical L1 solvers clip a weight to zero when a step would carry it across.

Two maps of the same data loss, one with a circular L2 penalty and one with a diamond L1 penalty
Figure 5. Circle against diamond. Defaults λ = 0.8: L2 lands at (0.714, 0.214), L1 at (0.6, 0). The diamond’s corners sit on the axes, so the best point often has a coordinate of exactly zero.

Early stopping

In one sentence: train with gradient descent, watch the validation error, and keep the weights from the moment it was lowest.

Gradient descent does not jump straight to the overfit answer. It starts from small weights (we start the polynomial at all zeros) and the big, easy pattern, the wave, is learned first because it reduces the loss fastest. Fitting the noise needs the huge, carefully cancelling weights we saw earlier, and those take a long time to build. So partway through training there is a moment when the model has learned the pattern but not yet the noise.

Here is the degree 7 polynomial trained by plain gradient descent on training MSE, learning rate η=0.5\eta = 0.5, weights starting at 0:

Epoch (full pass)Training MSEValidation MSE
10.5640.396
1000.2560.161
1,0000.1210.059
7,506 (best)0.0550.040
30,0000.0240.063
200,0000.0220.071

Training error keeps falling all the way. Validation error falls, bottoms out at 0.040 around epoch 7,506, then climbs. Early stopping keeps the epoch 7,506 weights.

In practice we do not know the bottom in advance, so we use patience: evaluate on the validation set every so often, save the weights whenever validation error is the best so far, and stop if it has not improved for, say, 10 evaluations. Then reload the saved best weights. Prechelt (1998) compared several such stopping rules in detail.

Everyday picture: baking bread. It gets better in the oven up to a point, then burns. You do not set a timer for “as long as possible”; you watch it and take it out at its best.

Early stopping and weight decay are close cousins: both keep weights from growing large. Stopping early simply does not give them the time to grow.

Training and validation error over training, with the early-stopping point marked
Figure 6. Take it out before it burns. Training error keeps falling. Validation error bottoms out at 0.040 near epoch 7,506 and then rises to 0.071 by epoch 200,000. The gold marker is the early-stopping point.

Dropout

In one sentence: during training, switch off a random half (or some other fraction) of the neurons on every step, so no neuron can rely on any particular other neuron being there.

Take a hidden layer with outputs h\mathbf{h} (like h1,h2h_1, h_2 in post 1). On each training step, dropout draws a fresh random mask m\mathbf{m} of 0s and 1s, one per neuron, and computes:

h~i=mi hi1−p,mi={0with probability p1with probability 1−p\tilde{h}_i = \frac{m_i \, h_i}{1 - p}, \qquad m_i = \begin{cases} 0 & \text{with probability } p \\ 1 & \text{with probability } 1 - p \end{cases}

Worked by hand

A hidden layer of 4 neurons outputs h=[2,1,3,0.5]\mathbf{h} = [2, 1, 3, 0.5]. The next layer computes one output with weights v=[0.5,1,−0.5,2]\mathbf{v} = [0.5, 1, -0.5, 2]. Dropout rate p=0.5p = 0.5.

Without dropout: 0.5×2+1×1+(−0.5)×3+2×0.5=1+1−1.5+1=0.5 \times 2 + 1 \times 1 + (-0.5) \times 3 + 2 \times 0.5 = 1 + 1 - 1.5 + 1 = 1.5.

One training step with dropout. The coin flips give the mask m=[1,0,1,0]\mathbf{m} = [1, 0, 1, 0].

  1. Zero the dropped neurons: [2,0,3,0][2, 0, 3, 0].
  2. Scale survivors by 1/(1−0.5)=21/(1 - 0.5) = 2: h~=[4,0,6,0]\tilde{\mathbf{h}} = [4, 0, 6, 0].
  3. Output: 0.5×4+1×0+(−0.5)×6+2×0=2−3=0.5 \times 4 + 1 \times 0 + (-0.5) \times 6 + 2 \times 0 = 2 - 3 = −1.

That is very different from 1.5. On a single step dropout is noisy on purpose. But on average it is right. There are 24=162^4 = 16 possible masks, all equally likely when p=0.5p = 0.5; averaging the output over all 16 gives exactly 1.5, the no-dropout answer. Here is why: each neuron is kept half the time and doubled when kept, so on average it contributes 12×2hi=hi\tfrac12 \times 2 h_i = h_i, its normal amount.

What would change without the scaling? Each neuron would contribute 12hi\tfrac12 h_i on average, and the average output would be 0.75, half the true value. At test time, with every neuron on, the next layer would suddenly receive twice what it was trained on. The 1/(1−p)1/(1-p) factor exists to stop exactly that mismatch.

Switching it off at test time

At test time we want a single, stable answer, so dropout is switched off: every mi=1m_i = 1 and no scaling. Because of inverted dropout, the layer already expects outputs of this average size, so nothing else needs to change. The output is 1.5. (The original papers instead left training unscaled and multiplied the weights by 1−p1 - p at test time; the result is the same, inverted dropout just moves the work into training.) In code this is the difference between a model’s training mode and evaluation mode; forgetting to switch is a classic bug that makes predictions random.

Why it works

Two views, both from the original papers.

Everyday picture: a football team that practises with random players sitting out each drill. Nobody can rely on the star striker always being there, so everyone learns to play every role a bit, and the full team on match day is stronger for it.

Four neurons, some switched off at random, the survivors doubled, feeding one output
Scale survivors by 1/(1 − p)
Figure 7. Dropout one operation at a time. Mask [1, 0, 1, 0] gives [4, 0, 6, 0] and output −1. The average over all 16 masks is 1.5, the same as test mode with every neuron on. Without the ×2 scaling the average would be 0.75.

Data augmentation

In one sentence: make more training data by changing the examples you have in ways that should not change the answer.

A photo of a cat flipped left to right is still a cat. Cropped a little, brightened a little, still a cat. Each change gives the network a “new” example for free, and teaches it which differences do not matter. For sound, you can add quiet background noise, change the volume, or shift the pitch slightly; for speech recognition, a popular trick called SpecAugment (Park et al., 2019) blanks out random strips of time and frequency in the spectrogram (the picture of sound from the audio basics post), which is dropout applied to the input.

The one rule: the change must not change the label. Flipping a “6” upside down makes a “9”; speeding up speech too much changes who seems to be speaking.

Label smoothing

In one sentence: instead of telling the network the right answer is 100% certain, tell it the right answer is, say, 93% certain and spread the rest over the other classes.

With cross-entropy (from post 2), a one-hot target like [1,0,0][1, 0, 0] can only be matched by probabilities of exactly 1 and 0, which need infinitely large logits. The network keeps pushing its logits further apart forever, becoming overconfident. Label smoothing (Szegedy et al., 2016) replaces the target with

yksmooth=(1−ε) yk+εKy_k^{\text{smooth}} = (1 - \varepsilon)\, y_k + \frac{\varepsilon}{K}

Worked by hand with post 2’s cat, dog, bird example (K=3K = 3, true class cat, ε=0.1\varepsilon = 0.1): cat gets 0.9×1+0.1/3=0.9 \times 1 + 0.1/3 = 0.933, dog and bird each get 0+0.1/3=0 + 0.1/3 = 0.033. With the logits (2,1,0)(2, 1, 0) the network’s probabilities are (0.665,0.245,0.090)(0.665, 0.245, 0.090), so the gradient p−yp - y becomes (0.665−0.933, 0.245−0.033, 0.090−0.033)=(0.665 - 0.933,\ 0.245 - 0.033,\ 0.090 - 0.033) = (−0.268, 0.211, 0.057), compared with (−0.335,0.245,0.090)(-0.335, 0.245, 0.090) for the hard target. The push on “cat” is gentler, and once the cat probability passes 0.933 the gradient on it reverses: the network is told to stop growing more confident.

Hard and smoothed targets beside the network's probabilities, with the resulting pushes
Figure 8. A softer target. With ε = 0.1 the cat target drops to 0.933 and dog and bird rise to 0.033; the gradient on the logits (2, 1, 0) becomes (−0.268, 0.211, 0.057).

What to remember

Try it yourself

Sources