Regularization
Dropout, weight decay, augmentation, and fighting overfitting.
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.
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).
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.
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:
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:
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.
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.
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:
Here is that same idea on the exact eleven points from the code cell above — three models, three levels of flexibility, one truth:
Fitting a degree- polynomial means choosing coefficients. Each training point gives one equation the curve must satisfy.
- 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.
- 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.
- 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 .
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 loss | Validation loss | Diagnosis | What to do |
|---|---|---|---|
| High | High (similar) | Underfitting — model too weak, or under-trained | More capacity, train longer, less regularization, better features |
| Low | Much higher | Overfitting — model memorized | Everything in this lesson: more data, augmentation, weight decay, dropout, early stopping |
| Low | Low | Healthy — ship it | Check your validation set is genuinely representative |
| High | Low | Something is wrong | Usually a bug: leaked data, a broken split, or dropout left on at evaluation time |
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.
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.
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.
Take a single labelled photo of a handwritten 7. Ask what changes would leave a human's answer unchanged:
- Shift it two pixels right. Still a 7. → new training example.
- Rotate it 10 degrees. Still a 7. → new training example.
- Dim the whole image by 40%. Still a 7. → new training example.
- Add faint speckle noise. Still a 7. → new training example.
- 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.
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.
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.
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.
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.
Take a model that predicts and two settings that both pass close to your data:
- Setting A: . Sum of squares: .
- Setting B: . Sum of squares: about .
Setting B works by having two huge terms almost exactly cancel. Now nudge the input a little — move from to . 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 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:
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 — 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 with . The geometric picture below shows why that one change makes weights land on exactly zero:
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.
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.
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.
A hidden layer outputs eight activations: . We use a drop probability of .
- Draw a mask. Flip a coin per unit: keep or drop. Say we get — six of the eight dropped this time.
- Apply it. Multiply elementwise: . Those six neurons contribute nothing to this pass, and gradients won't flow back through them either.
- Notice the problem. The layer used to sum to ; now it sums to . 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.
- Fix it by scaling up. Divide the survivors by , i.e. double them: . 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:
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.
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.
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.
| Technique | What it does | Reach for it when |
|---|---|---|
| Label smoothing | Replaces hard targets like 1.0 with 0.9, so the model stops chasing infinite confidence | Classification, especially with noisy labels; standard in modern vision and translation |
| Ensembling | Trains several models and averages their predictions, cancelling their independent errors | Accuracy matters more than compute; competitions |
| Batch / Layer normalization | Stabilizes activation scales; the batch statistics add incidental noise that regularizes | Almost always — but see Normalization and Initialization |
| Smaller model | Removes the capacity to memorize in the first place | Small datasets where the gap stays wide despite everything else |
| Transfer learning | Starts from weights pretrained on far more data, so far less is learned from your small set | You have thousands of examples, not millions — usually the single biggest win |
| Noise injection | Adds noise to inputs, weights, or gradients, so exact settings cannot be memorized | Related to augmentation; also improves robustness |
| Multi-task learning | Trains on several related tasks at once; shared layers must serve all of them | Related labelled tasks are available |
Putting it together — the practical recipe
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:
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 lastWhich of these should NEVER be applied to the validation set?
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.
- 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 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
• 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.
Then work through these:
- 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.
- Sweep the strength. Take the winning setup and sweep
weight_decayacross[0, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1]. Plot final training and validation accuracy against on a log axis. You should reproduce the U-shape you found in the lab, with underfitting at the right-hand end. - Break an augmentation on purpose. Add
RandomHorizontalFlipto 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. - Verify the dropout contract. Take a trained model, run the same input through it 100 times with
model.train()and then once withmodel.eval(). Compare the mean of the 100 stochastic outputs to the single deterministic one, and confirm the scaling is doing what the code cell above claims. - Read the source. Skim §1 and the practical guide of the dropout paper and find where they justify 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.