Knowledge BaseTraining Deep Nets

Regularization

Dropout, weight decay, augmentation, and fighting overfitting.

intermediate#dropout#weight-decay#augmentation

Start here — the one failure that regularization exists to fix

Imagine you hand a student a hundred practice problems, each with the answer printed underneath. Two weeks later they score 100% on those hundred problems. Did they learn the subject?

You can't tell yet. There are two very different students who both score 100%:

  • One understood the material and can solve problems they've never seen.
  • One memorized the answer key and will fall apart the moment the numbers change.

A neural network can be either of these students, and the frustrating part is that they look identical while you're training. That's the entire problem this lesson is about.

is the goal: doing well on new data. is the failure: acing the practice problems, flunking the exam. And is the toolbox for preventing it.

Regularization makes memorizing harder on purpose

Every technique in this lesson works the same way underneath: it puts an obstacle in the way of memorizing. Blur the examples, penalize complicated solutions, randomly break parts of the network, stop training early. Memorizing needs precision and time; the general pattern survives the sabotage. So if you make the easy path (memorize) hard enough, the model is forced onto the hard path (understand).

Think of it like a teacher who never repeats the same question twice:

A lazy student can beat a teacher who reuses last year's exam. They cannot beat a teacher who rewrites every question, hides the answer key, and gives partial marks only for showing the method. Regularization is you being that teacher to your model.

This page adapts to you

It starts from zero and assumes only that you've seen a network being trained. Flip the Depth switch at the top for the formal objectives and derivations — they'll also open automatically once you've completed the prerequisite (Neural Networks).

Seeing overfitting happen — the two curves

You cannot fix what you cannot see, so the first skill is detecting overfitting. It takes two numbers, not one.

Split your data into two piles before training starts:

  • The , which the model learns from.
  • The , which it never learns from and which you use as a stand-in for the real world.

Now track the loss on both after every epoch. That pair of curves is the single most informative plot in deep learning:

The two curves — training loss falls forever, validation loss turns around— interactive, drag & zoom
Loading chart…
One training run, two losses. Up to epoch 11 both fall together — the model is learning real structure that helps everywhere. After epoch 11 they split: training loss keeps sliding toward zero while validation loss climbs. Everything learned after that dotted line is memorization of these particular examples, and it actively hurts on new data. The best model in this entire run existed at epoch 11 and was then destroyed by 19 more epochs of training.

That gap between the two curves has a name.

is simply validation loss minus training loss. Watching it is how you diagnose everything in this lesson.

Measure it yourself

Run this. It fits polynomials of growing flexibility to eleven noisy points and reports the error on those points versus on fresh ones:

Python · runs in your browser
What this does: Fits polynomials of growing degree to 11 noisy points and reports the error on the training points versus on fresh, unseen points. Watch the training error march to zero while the test error turns around and climbs — that is overfitting, measured rather than described.

Read the printed table before moving on. Degree 3 has the lowest error on unseen points. Degree 10 has zero error on the training points — a perfect score — and roughly double the error of degree 3 on new ones. The model with the perfect training score is the worse model.

Try to recall

A colleague reports that their model reached 99.8% accuracy. What is the one question you must ask before being impressed?

Hint: Which pile of data was that measured on?

Why it happens — capacity versus data

Overfitting isn't random bad luck. It comes from a specific mismatch.

is a model's flexibility. Eleven data points and a degree-10 polynomial means eleven equations and eleven free coefficients — the model has exactly enough freedom to thread through every single point, noise included. It doesn't need to find the underlying pattern because there is a memorization solution available, and memorization drives the training loss lower than the truth does.

Enough freedom to fit the noise is enough freedom to be wrong

Real data is signal plus noise. A model with just enough capacity can only afford to capture the signal — that's the cheapest way to lower its loss. A model with far too much capacity can afford to capture the signal and trace every wobble of the noise. The wobbles are different in every dataset, so learning them is learning nonsense that won't be there next time.

Drag the degree slider in the applet below. Watch how a low degree slices straight past the points, a middling degree traces the shape, and a high degree starts snaking through every point exactly:

Polynomial fit to data — turn the flexibility dial— GeoGebra, drag & exploreOpen on GeoGebra →
Loading interactive visualization…
A published GeoGebra applet: enter or drag data points and select the degree of the polynomial fitted to them. Raise the degree and the curve bends harder to pass closer to every point, until it threads through all of them exactly. That perfect pass-through is what a training loss of zero looks like, and the wild swings between the points are what it costs you on new data.

Here is that same idea on the exact eleven points from the code cell above — three models, three levels of flexibility, one truth:

Underfitting, fitting, and overfitting — the same 11 points three ways— interactive, drag & zoom
Loading chart…
Grey dashed is the real pattern the data came from; the model never sees it. Degree 1 is too stiff to bend into the shape at all. Degree 3 lands almost on top of the truth. Degree 10 passes through all eleven points perfectly and pays for it by lurching between them — near x = 0.3 it dives below the truth, and just left of the data it shoots clear off the top of the chart to y = 2.6. Its training error is exactly zero.
Why zero training error is achievable — and why that is the warning sign

Fitting a degree-dd polynomial means choosing d+1d + 1 coefficients. Each training point gives one equation the curve must satisfy.

  1. With 11 points and degree 1, you pick 2 numbers to satisfy 11 equations. Impossible in general — the model must compromise, and the compromise is a straight line through the middle. That's underfitting.
  2. With 11 points and degree 3, you pick 4 numbers for 11 equations. Still overdetermined, so the model must still compromise — and the cheapest compromise is to capture the actual shape.
  3. With 11 points and degree 10, you pick 11 numbers for 11 equations. Exactly determined: a unique curve passes through every point exactly. Training error is 00.

The lesson is in step 3. The model didn't get better — it got enough freedom that it no longer had to compromise, so it stopped being forced to find the pattern. Compromise is where learning comes from. Regularization is the art of putting the compromise back in when you have more capacity than data.

Your model gets 62% accuracy on the training set and 61% on the validation set. What should you do?

Diagnose before you treat

Regularization is a treatment for one specific disease. Applying it to the wrong one makes things worse, so always read the two numbers first:

Training lossValidation lossDiagnosisWhat to do
HighHigh (similar)Underfitting — model too weak, or under-trainedMore capacity, train longer, less regularization, better features
LowMuch higherOverfitting — model memorizedEverything in this lesson: more data, augmentation, weight decay, dropout, early stopping
LowLowHealthy — ship itCheck your validation set is genuinely representative
HighLowSomething is wrongUsually a bug: leaked data, a broken split, or dropout left on at evaluation time
The last row is not a happy accident

Validation loss below training loss almost always means a mistake, not a miracle. The common causes: dropout and augmentation are active during training but off at validation (which alone can explain a small gap), your validation set is easier than your training set, or — worst — training examples leaked into validation and the model has seen them.

Now the toolbox itself. There are five tools, roughly in the order you should reach for them.

Tool 1 — More data, and the cheap version: augmentation

The most effective anti-overfitting technique is not clever. It is more data.

You cannot memorize what you never see twice

Overfitting is memorizing specific examples. If every example the model sees is new, there is nothing to memorize — the only strategy that lowers the loss is learning the actual pattern. This is why models trained on internet-scale data overfit far less than models trained on a thousand hand-labelled images.

Real labelled data is expensive. So we fake it: takes each example and produces variations that are still correctly described by the same label.

Think of it like photographing the same cat from every angle:

A friend who has only ever seen one photo of your cat might not recognize her in a different room. Show them the cat sideways, in dim light, half behind a sofa, and now they know the cat rather than that photo. Augmentation shows the model the same example under every irrelevant variation, so it stops treating the irrelevant details as part of the answer.

One image becomes six

Take a single labelled photo of a handwritten 7. Ask what changes would leave a human's answer unchanged:

  1. Shift it two pixels right. Still a 7. → new training example.
  2. Rotate it 10 degrees. Still a 7. → new training example.
  3. Dim the whole image by 40%. Still a 7. → new training example.
  4. Add faint speckle noise. Still a 7. → new training example.
  5. Crop off a corner. Still a 7. → new training example.

One labelled image, six training examples, no extra labelling cost. And each one teaches something specific: after the shifted version, the model can no longer use the pixel at row 3, column 5 is dark as its rule, because that stopped being reliable. It is pushed toward a rule about shape.

Now the trap. Mirror it left-to-right. A mirrored 7 is still a 7 — but a mirrored 2 is not a 2, and a mirrored letter is not that letter. The transformation must preserve the label for every class in your dataset, or you are training the model on wrong answers.

Python · runs in your browser
What this does: Takes one tiny 9x9 image of the digit 7 and manufactures five more training examples from it — shifted, mirrored, dimmed and speckled. Every variant is still a 7, so the label is unchanged and the model is forced to learn the shape rather than the exact pixels.
Augmentation is a claim about your problem, not a free lunch

Every augmentation asserts this change should not affect the answer. Horizontal flips are fine for cats and wrong for road signs and text. Colour jitter is fine for object recognition and wrong for medical imaging where colour is diagnostic. Aggressive cropping can cut the object out of the frame entirely while keeping its label. When augmentation hurts, it is almost always because one of these claims was false for your data.

Try to recall

Why does augmentation reduce overfitting even though it adds no new information about the world?

Hint: Think about what the model can no longer get away with.

Tool 2 — Weight decay: charge a fee for complexity

Suppose you can't get more data. The next move is to tell the optimizer that not all solutions are equally welcome.

Among all the answers that fit, prefer the boring one

Many different parameter settings fit the training data about equally well. Some of them are wild — enormous positive weights fighting enormous negative ones, producing a function that lurches around between the data points. Others are gentle. Both score the same on training data, but the gentle one is far more likely to be right on new data. Weight decay simply makes the wild ones cost more, so the optimizer stops choosing them.

Think of it like a contract that fines you per moving part:

Two engineers submit bridge designs that both pass every load test. One uses forty struts, the other four hundred, each straining against the next. If the contract charges a fee per strut, the second design stops being competitive — even though it passed the same tests. Weight decay is that fee, charged per unit of weight magnitude, and the loss function is the contract.

adds a second term to the thing being minimized: the ordinary loss, plus a charge proportional to how big the weights are.

Why big weights mean a wild function

Take a model that predicts y=w1x+w2x2y = w_1 x + w_2 x^2 and two settings that both pass close to your data:

  • Setting A: w1=0.9, w2=0.1w_1 = 0.9,\ w_2 = -0.1. Sum of squares: 0.81+0.01=0.820.81 + 0.01 = 0.82.
  • Setting B: w1=240.3, w2=239.5w_1 = 240.3,\ w_2 = -239.5. Sum of squares: about 115,000115{,}000.

Setting B works by having two huge terms almost exactly cancel. Now nudge the input a little — move xx from 1.001.00 to 1.011.01. Setting A's output barely moves. Setting B's cancellation is broken by the nudge, and its output swings wildly, because each term is enormous and they no longer cancel.

That is the whole argument: large weights make the output hypersensitive to small changes in the input. Since new data is exactly the same pattern with small changes, a hypersensitive model is a fragile one. Charging a fee proportional to w12+w22w_1^2 + w_2^2 costs Setting B about 140,000 times more than Setting A, and it disappears from consideration.

Watch the effect on the wildly overfitting degree-10 fit from earlier — same model, same data, just a fee attached:

Python · runs in your browser
What this does: Fits the same wiggly degree-10 polynomial three times — with no penalty, a small penalty, and a large one. Compare the size of the fitted coefficients and the error on unseen points. The penalty makes the model prefer small numbers, and small numbers mean a smoother curve.

The printed numbers make the argument better than any picture: with no penalty the biggest coefficient is in the hundreds and the sum of squared weights is in the hundreds of thousands. A penalty of λ=103\lambda = 10^{-3} — a tiny fee — collapses that to single digits and improves the error on unseen data. The model was never getting anything useful from those enormous cancelling coefficients.

Two flavours of fee: L2 and L1

Squaring the weights is one way to charge for size. Taking absolute values is another, and it behaves very differently.

replaces θi2\sum \theta_i^2 with θi\sum |\theta_i|. The geometric picture below shows why that one change makes weights land on exactly zero:

Why L1 zeroes weights and L2 only shrinks them— interactive, drag & zoom
Loading chart…
Two weights, so the whole parameter space fits on a page. The rings are the loss — lowest at the pale dot, where an unpenalized fit would land. A penalty caps how far out you may go, and the answer is the point on that budget shape closest to the pale dot. The circle has no corners, so its answer sits at an angle with both weights non-zero. The diamond has corners on the axes, and the answer lands exactly on one of them: weight 2 becomes exactly zero. Corners are the whole reason L1 produces sparse models — verified numerically on this surface, where the constrained minimum is (1.00, 0.00) for L1 and (0.74, 0.67) for L2.

You switch from Adam to AdamW and keep weight_decay=0.01 unchanged. Why might the effective amount of regularization change?

Tool 3 — Dropout: randomly break the network while it trains

Weight decay limits how large the weights can be. Dropout attacks a different failure: neurons that only work as a fixed team.

Nobody gets to be indispensable

During training, dropout randomly switches off a fraction of the neurons in a layer — a different random set on every single forward pass. A neuron cannot build a delicate arrangement with one particular partner, because that partner might not be there next time. Each unit is forced to be useful on its own, alongside whatever random subset of colleagues happens to show up.

Think of it like a team where random members call in sick every morning:

If the same five people always work together, they develop shortcuts that only work when all five are present, and the team collapses when one is away. Now make attendance random each day. Everyone has to learn to do their part with whoever shows up, redundancy appears naturally, and the team gets more robust — not less — for having been disrupted daily.

is that idea. The mechanics have one wrinkle worth understanding before the formula.

One dropout pass, by hand

A hidden layer outputs eight activations: [1,2,3,4,5,6,7,8][1, 2, 3, 4, 5, 6, 7, 8]. We use a drop probability of p=0.5p = 0.5.

  1. Draw a mask. Flip a coin per unit: keep or drop. Say we get [0,0,1,1,0,0,0,0][0, 0, 1, 1, 0, 0, 0, 0] — six of the eight dropped this time.
  2. Apply it. Multiply elementwise: [0,0,3,4,0,0,0,0][0, 0, 3, 4, 0, 0, 0, 0]. Those six neurons contribute nothing to this pass, and gradients won't flow back through them either.
  3. Notice the problem. The layer used to sum to 3636; now it sums to 77. The next layer receives a much fainter signal than it will at test time, when dropout is off and everything is present. It would be calibrated for the wrong scale.
  4. Fix it by scaling up. Divide the survivors by 1p=0.51 - p = 0.5, i.e. double them: [0,0,6,8,0,0,0,0][0, 0, 6, 8, 0, 0, 0, 0]. On average across many masks, half the units survive and each is doubled, so the layer's expected output is back to its original value.

Step 4 is called inverted dropout, and it is why you do nothing special at test time: just switch dropout off and the scale already matches.

Here is what the masks look like across a few training steps — same layer, same eight units, a different random pattern every time:

Dropout masks over six training steps— interactive, drag & zoom
Loading chart…
Teal means the unit is kept for that step; dark means it was dropped. Read down any column: unit 5 is absent from every one of these six steps, unit 1 is present for four of them. Read across any row and you get the sub-network that actually trained on that step — a different, thinner network every time. Note that the number kept varies (2 to 6 here); with p = 0.5 you get half on average, not exactly half.
Python · runs in your browser
What this does: Implements dropout by hand on one layer of 8 neurons. Each pass silences a random half of them and scales the survivors up by 1/(1-p). Averaged over many passes the layer's output matches the undropped layer — which is exactly why nothing needs rescaling at test time.
The single most common dropout bug

Forgetting model.eval() before validating or deploying. Dropout stays active, your model gives a different answer every time you call it, and your validation loss looks mysteriously worse than your training loss. Pair it with torch.no_grad() and make it a habit: model.eval() before every evaluation, model.train() before every training loop.

Try to recall

Dropout throws away half the network's computation on every training step. Why does that make the trained model better rather than worse?

Hint: Think about what a neuron can no longer rely on.

Tool 4 — Early stopping: the free one

Look back at the two-curve plot. The best model of that entire run existed at epoch 11, and training continued for 19 more epochs, steadily making it worse. is simply: notice that, and stop.

Training longer is a capacity dial too

It's tempting to think of capacity as fixed by architecture. It isn't. A network that has trained for 3 epochs has effectively explored only a small region of parameter space near its initialization — it is behaving like a simpler model. Training longer lets it reach more extreme, more finely-tuned parameter settings. So how long you train is itself a regularization dial, and early stopping is how you set it using data rather than guesswork.

This is the cheapest technique in the lesson: no new hyperparameter to tune beyond patience, no extra compute — in fact it saves compute — and it works alongside every other tool here.

Your validation loss goes 0.42, 0.40, 0.41, 0.39, 0.43, 0.40, 0.44, 0.47, 0.51 across nine epochs. With patience = 3, which epoch's weights should you deploy?

Tool 5 — the rest of the toolbox

These come up constantly and are worth recognizing even before you use them.

TechniqueWhat it doesReach for it when
Label smoothingReplaces hard targets like 1.0 with 0.9, so the model stops chasing infinite confidenceClassification, especially with noisy labels; standard in modern vision and translation
EnsemblingTrains several models and averages their predictions, cancelling their independent errorsAccuracy matters more than compute; competitions
Batch / Layer normalizationStabilizes activation scales; the batch statistics add incidental noise that regularizesAlmost always — but see Normalization and Initialization
Smaller modelRemoves the capacity to memorize in the first placeSmall datasets where the gap stays wide despite everything else
Transfer learningStarts from weights pretrained on far more data, so far less is learned from your small setYou have thousands of examples, not millions — usually the single biggest win
Noise injectionAdds noise to inputs, weights, or gradients, so exact settings cannot be memorizedRelated to augmentation; also improves robustness
Multi-task learningTrains on several related tasks at once; shared layers must serve all of themRelated labelled tasks are available

Putting it together — the practical recipe

The order to reach for things


1. Diagnose. Read training loss and validation loss. If both are high, you are underfitting — regularization is the wrong medicine entirely.
2. More data first. Real data beats every technique below. If you cannot get it, augment, and consider starting from a pretrained model.
3. Turn on the standing defaults. Weight decay via AdamW, and early stopping with a saved best checkpoint. These are nearly free.
4. Add dropout if the gap is still open, starting around 0.1 to 0.3 and tuning against validation loss.
5. Only then shrink the model. Reducing capacity is a blunt instrument that also lowers the ceiling on what the model could learn.
6. Change one thing at a time and check the validation curve after each. Changing three knobs at once teaches you nothing about which one worked.

Here is the whole toolbox in one PyTorch training loop:

Python · needs a GPU — run on Colab
import torch, torch.nn as nn
from torchvision import transforms

# --- Tool 1: augmentation, applied fresh every time an example is loaded ---
train_tf = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.7, 1.0)),
    transforms.RandomHorizontalFlip(),          # valid for cats, NOT for road signs or text
    transforms.ColorJitter(0.2, 0.2, 0.2),
    transforms.ToTensor(),
])
val_tf = transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224),
                             transforms.ToTensor()])   # never augment the validation set

# --- Tool 3: dropout, declared in the model ---
model = nn.Sequential(
    backbone,
    nn.Dropout(p=0.2),
    nn.Linear(512, num_classes),
)

# --- Tool 2: weight decay, decoupled, and not applied to biases or norm parameters ---
decay, no_decay = [], []
for name, param in model.named_parameters():
    (no_decay if param.ndim <= 1 or "bias" in name else decay).append(param)
opt = torch.optim.AdamW(
    [{"params": decay, "weight_decay": 0.05}, {"params": no_decay, "weight_decay": 0.0}],
    lr=3e-4,
)

# --- Tool 5: label smoothing, built into the loss ---
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)

# --- Tool 4: early stopping with a restored best checkpoint ---
best, patience, waited = float("inf"), 10, 0
for epoch in range(max_epochs):
    model.train()                                # dropout ON
    for x, y in train_loader:
        opt.zero_grad()
        criterion(model(x), y).backward()
        opt.step()

    model.eval()                                 # dropout OFF — the classic bug if omitted
    with torch.no_grad():
        val_loss = sum(criterion(model(x), y).item() for x, y in val_loader) / len(val_loader)

    if val_loss < best:
        best, waited = val_loss, 0
        torch.save(model.state_dict(), "best.pt")     # keep the good one
    else:
        waited += 1
        if waited >= patience:
            break

model.load_state_dict(torch.load("best.pt"))     # restore the best, not the last

Which of these should NEVER be applied to the validation set?

Explain it yourself

Explain to a friend why a model that scores 100% on its training data can be worse than one scoring 90%, using the memorizing-student picture and no formulas. Then explain what dropout and weight decay each do about it. If you cannot say in one sentence what makes weight decay and dropout different from each other, that is the section to reread.

Recap — the key ideas
  • Overfitting is memorizing the training examples instead of learning the pattern. It is invisible unless you hold out data, so always track training loss and validation loss together — the generalization gap between them is the diagnosis.
  • It happens when capacity outruns data: enough freedom to fit the noise means the model is never forced to compromise, and compromise is where learning comes from.
  • Low training loss with a large gap = overfitting. High loss on both = underfitting, where regularization is exactly the wrong fix.
  • More data beats everything. Augmentation is the cheap version: label-preserving variations that destroy the memorization shortcut — but every augmentation is a claim that the change should not affect the label.
  • Weight decay adds a fee proportional to the sum of squared weights, so the optimizer prefers small weights and therefore smooth, insensitive functions. L1 uses absolute values instead, and its corners drive weights to exactly zero. Use AdamW, and exclude biases and norm parameters.
  • Dropout randomly silences units each pass so no neuron can depend on a specific partner, scaling survivors by 1/(1p)1/(1-p) so the expected output is unchanged. Turn it off with model.eval().
  • Early stopping keeps the checkpoint at the bottom of the validation curve instead of the last one. Cheapest tool here, and it composes with all the others.
  • The procedure never changes: diagnose, change one thing, re-read the validation curve.

Practice — and how to make it stick

Learn it the way that actually works


Retrieval practice: before scrolling back, try to name all five tools and, for each, the one sentence describing how it makes memorizing harder. Struggling to recall beats re-reading.
Spaced repetition: mark this topic complete to add it to your Review queue, so it resurfaces right before you would have forgotten it.
Interleaving: mix these exercises with Optimization and Normalization and Initialization problems rather than doing them in one block — weight decay only really clicks once you have seen how it rides along inside the optimizer step.

Start with the lab. It is the real workflow: you never pick a regularization strength by reasoning about it, you pick it by measuring.

Practice lab
Your task: Choose the right amount of regularization the way it is really done — with a validation set. The starter fits a flexible degree-10 model at several weight-decay strengths and prints training and validation error for each. Do the TODOs: (1) finish pick_best so it returns the lambda with the LOWEST VALIDATION error, not the lowest training error; (2) look at the printed table and write a comment explaining why training error only ever gets worse as lambda grows while validation error dips and then rises; (3) change split_seed to 1 and then 2 — does the winning lambda stay the same, and what does that tell you about trusting a 16-example validation set?
editor
terminal
Press Run (⌘/Ctrl+Enter) to execute.
Ask Ada — she can read your terminal

Then work through these:

  1. Force an overfit, then fix it. Train a small CNN on 500 CIFAR-10 images with no regularization and plot both loss curves until the gap is unmistakable. Now add, one at a time and re-plotting each time: random crop and flip, then weight decay, then dropout. Record how much of the gap each one closes — the ranking will surprise you, and it is the most useful thing in this list.
  2. Sweep the strength. Take the winning setup and sweep weight_decay across [0, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1]. Plot final training and validation accuracy against λ\lambda on a log axis. You should reproduce the U-shape you found in the lab, with underfitting at the right-hand end.
  3. Break an augmentation on purpose. Add RandomHorizontalFlip to a digit classifier and watch accuracy on 2s, 5s and 7s specifically. This is the label-preservation trap in the wild, and seeing it once means you will never ship it.
  4. Verify the dropout contract. Take a trained model, run the same input through it 100 times with model.train() and then once with model.eval(). Compare the mean of the 100 stochastic outputs to the single deterministic one, and confirm the 1/(1p)1/(1-p) scaling is doing what the code cell above claims.
  5. Read the source. Skim §1 and the practical guide of the dropout paper and find where they justify p=0.5p = 0.5 for hidden units but a much lower rate for inputs. Ask yourself why the asymmetry exists before reading their answer.

Next: see the other half of what makes deep networks trainable — Normalization and Initialization — or revisit where the shrinkage actually happens inside the optimizer in Optimization.

Key papers