Your training loss falls. Your validation loss falls at first, then rises. Adding a larger model makes the training result even better and the validation result worse.
That pattern is often called overfitting: the fitted model performs much better on the examples it learned from than on representative held-out examples. The response is not to switch on every available technique. First protect the evidence. Then choose one intervention with a reason.
Lesson details
- Module
- Deep learning
- Lesson
- 5 · Regularisation and normalisation
- Difficulty
- Beginner
- Learning time
- 80 minutes
Before you start
- A measured training and validation loop
- Activations, gradients, optimiser updates, and model modes
Skills you will gain
- Diagnose overfitting before choosing a regularisation method
- Explain dropout scaling and the difference between training and evaluation behaviour
- Apply explicit AdamW parameter groups for weight decay
- Design label-preserving augmentation for training while keeping evaluation deterministic
- Choose between BatchNorm, LayerNorm, and RMSNorm by the axes and statistics they use
- Test mode changes and regularisation choices with controlled ablations
Why this matters
Real training data contains limited coverage, noise, accidental correlations, and repeated patterns. A flexible network can reduce training loss by relying on details that will not remain useful after deployment.
Regularisation gives the learning process a deliberate preference: smaller weights, incomplete activation paths, or task-valid input variation. Normalisation keeps internal numbers on a controlled coordinate system. Both can affect optimisation and validation results, but they do different jobs.
Plain-English mental model
Step 1 — Diagnose before adding a constraint
Overfitting is an evidence pattern, not a feeling. Look for a persistent gap between the training measure and a representative validation measure. Then check easier explanations first:
- Is the split independent, representative, and free from duplicated entities or future information?
- Does validation use the same deterministic preprocessing contract intended for deployment?
- Did evaluation call
model.eval()and disable gradient tracking separately? - Is the chosen measure hiding an important error slice or class?
- Does a simpler baseline show the same gap?
If the data or evaluation path is wrong, more regularisation can hide the symptom without fixing the experiment.
A useful comparison changes one important factor from a reproducible baseline. Keep the data split, checkpoint rule, optimiser schedule, and evaluation measures recorded. Select the factor using validation data, not the final test set.
Step 2 — Three regularisation levers
These methods do not mean “make the model worse.” They make some training solutions less convenient, hoping that a more transferable solution wins.
Dropout: make activation paths unreliable during training
DropoutDropoutA training-only regularisation method that randomly replaces some activations with zero and scales the survivors. At evaluation time, the dropout layer passes every activation through. randomly replaces activations with zero during training. Its setting p is the probability of dropping, not keeping, an activation.
If p = 0.5, each activation has a 50% chance of surviving. PyTorch scales each survivor by 1 / (1 - p), which is 2 here. That scaling keeps the expected activation value unchanged across many random masks.
Suppose the activations are [2, 4, 6, 8] and one fixed teaching mask is [1, 0, 0, 1]:
training = activation × mask ÷ (1 − p)
= [4, 0, 0, 16]
evaluation = [2, 4, 6, 8]
The original mean and this particular training-mask mean both happen to be 5. The general guarantee is about the expectation over masks; one arbitrary mask need not preserve the mean exactly.
Dropout makes repeated training forward passes random. During evaluation, a PyTorch dropout layer becomes an identity operation. That change is controlled by model mode:
model.train() # dropout masks activations
model.eval() # dropout passes activations through
model.eval() does not disable gradient tracking. torch.no_grad() does not switch dropout or BatchNorm to evaluation behaviour. A correct evaluation path normally needs both:
model.eval()
with torch.no_grad():
validation_prediction = model(validation_features)
The dropout probability is a hyperparameter. A larger p is a stronger disturbance, not an automatic improvement. Record where dropout is applied and compare its validation effect.
Weight decay: nudge selected parameters towards zero
Weight decayWeight decayAn optimiser rule that nudges selected parameters towards zero on each update. Its strength is a tunable regularisation setting, not a guarantee of better generalisation. shrinks selected parameters a little on each optimiser update. For a simple decoupled update, the decay part looks like:
weight after decay = weight × (1 − learning rate × decay strength)
Smaller parameters can reduce reliance on a few extreme numeric weights. Zero decay means no shrinkage. Too much decay can prevent the model fitting a real pattern.
AdamW keeps the decay step separate from the gradient moments used for Adam’s adaptive update. That is different from placing a squared-weight penalty inside the loss and passing its gradient through Adam’s adaptive scaling. The toy calculation below shows only where the decay term enters; it is not a full implementation of either optimiser.
weight = 2.0
data_gradient = 0.5
adaptive_scale = 0.2
learning_rate = 0.1
decay = 0.01
coupled = weight - learning_rate * adaptive_scale * (
data_gradient + decay * weight
)
decoupled = (
weight * (1 - learning_rate * decay)
- learning_rate * adaptive_scale * data_gradient
)
print(f'coupled toy update: {coupled:.4f}')
print(f'decoupled toy update: {decoupled:.4f}')Decide which parameters receive decay. A common starting policy decays matrix-shaped weights while excluding one-dimensional bias and normalisation scale/shift parameters. That is a policy to test, not a law. Make the parameter groups explicit so a reviewer can see the choice.
Data augmentation: vary inputs without changing the answer
Data augmentationData augmentationCreating varied training inputs with transformations that should preserve the target for the intended task, while keeping validation and test preprocessing deterministic. creates varied training inputs with transformations that should preserve the target for the intended task.
That last phrase is the contract. A horizontal flip may preserve “contains a cat.” It may reverse a left-versus-right label, make text unreadable, or move an object box unless the box is transformed too.
InvarianceInvarianceThe desired property that a model's relevant output stays stable when an input changes in a way that should not change the answer for the task. means the relevant output should stay stable under a change that should not alter the task answer. Augmentation teaches a chosen invariance by showing transformed training examples with consistent targets.
This small pure-Python example flips a three-by-three mark. For this deliberately narrow label—“contains a diagonal mark”—the label remains valid:
image = ['#..', '.#.', '..#']
label = 'contains a diagonal mark'
flipped = [row[::-1] for row in image]
print('\n'.join(flipped))
print('label:', label)Random augmentation belongs in the training path. Validation and test preprocessing should be deterministic and should match the intended deployment contract. If an input has a paired target—such as a segmentation mask, bounding box, keypoint, or audio timestamp—transform the pair consistently.
An invalid transform is structured label noise. Audit transformed examples visually and by target type before trusting a validation score.
Step 3 — Normalisation starts with an axis
Normalisation changes a collection of values using a statistic such as a mean, variance, or root mean square. Before naming a layer, ask two questions:
- Which values share the statistic—examples in a batch, or features inside one example?
- Does evaluation use current-input statistics or stored training statistics?
In the formulas below, epsilon is a tiny positive value that prevents division by zero. Learnable scale and shift parameters let the layer restore or reshape useful magnitudes after normalisation.
BatchNorm: across examples for each feature
Batch normalisationBatch normalisationA layer that normalises each feature using statistics calculated across training examples in a mini-batch, then applies learned scale and shift parameters. It normally uses stored running statistics during evaluation., usually called BatchNorm, treats each feature separately. During training it calculates that feature’s mean and variance across examples in the mini-batch:
normalised value = (value − batch feature mean)
÷ sqrt(batch feature variance + epsilon)
output = learned scale × normalised value + learned shift
The selected example therefore depends on its batch neighbours during training. Replace one neighbour with a large outlier and the selected example’s output can change.
By default, a PyTorch BatchNorm module also updates running estimates during training and uses those estimates during evaluation. This makes model.train() and model.eval() semantically important. Tiny or non-representative batches can produce noisy training statistics. Accumulating gradients across micro-batches does not make BatchNorm see one larger activation batch; each forward pass still has its own batch statistics.
For an image tensor, BatchNorm commonly has one learned scale and shift per channel. The exact axes depend on the BatchNorm module and input shape, so read the module’s shape contract.
LayerNorm: centre and scale within one example
Layer normalisationLayer normalisationA layer that centres and scales an example using statistics calculated across its selected feature dimensions, followed by learned element-wise scale and shift parameters., or LayerNorm, calculates its mean and variance over selected feature dimensions inside each example:
normalised value = (value − example mean)
÷ sqrt(example variance + epsilon)
output = learned element-wise scale × normalised value
+ learned element-wise shift
Changing another example in the mini-batch does not change this example’s LayerNorm statistics. PyTorch LayerNorm uses current-input statistics in both training and evaluation. It still has learned parameters, but it does not need BatchNorm-style running estimates.
If every feature in the normalised group is identical, the centred values are zero. Epsilon keeps the division defined, but the layer cannot invent variation that was absent.
RMSNorm: scale without subtracting the mean
Root mean square normalisationRMS normalisationA layer that divides an example by the root mean square of values across selected feature dimensions, then applies a learned scale. Unlike layer normalisation, it does not subtract the mean., usually called RMSNorm, calculates the root mean square across selected feature dimensions:
root mean square = sqrt(mean(value²) + epsilon)
output = learned scale × value ÷ root mean square
RMSNorm does not subtract the mean and does not add the LayerNorm-style learned bias in PyTorch’s module. A uniform shift of every feature therefore still changes the normalised direction and values.
Like LayerNorm, its statistics come from the current example rather than other mini-batch examples. Choose it because that behaviour fits the architecture and tested baseline, not because it is newer or has a shorter formula.
Worked example — one row, three normalisations
Take a selected row [1, 2, 4, 8] inside a batch containing the same row shifted upward by one, two, and three. Ignore learnable scale/shift and the tiny epsilon for this teaching calculation.
For LayerNorm, the selected row mean is 3.75 and its population standard deviation is about 2.681. Centre and divide each value to get approximately:
LayerNorm: [-1.026, -0.653, +0.093, +1.585]
For RMSNorm, the selected row’s root mean square is about 4.610. Divide without centring:
RMSNorm: [+0.217, +0.434, +0.868, +1.735]
For BatchNorm, each feature column is an evenly spaced sequence of four values. The selected row is the lowest value in every column, so every feature becomes approximately -1.342:
BatchNorm selected row: [-1.342, -1.342, -1.342, -1.342]
from math import sqrt
selected = [1.0, 2.0, 4.0, 8.0]
batch = [[value + shift for value in selected] for shift in range(4)]
def mean(values):
return sum(values) / len(values)
def standardise(values):
centre = mean(values)
spread = sqrt(mean([(value - centre) ** 2 for value in values]))
return [(value - centre) / spread for value in values]
layer = standardise(selected)
rms = sqrt(mean([value ** 2 for value in selected]))
rms_output = [value / rms for value in selected]
batch_output = []
for feature in range(len(selected)):
column = [row[feature] for row in batch]
batch_output.append(standardise(column)[0])
print('batch:', [f'{value:+.3f}' for value in batch_output])
print('layer:', [f'{value:+.3f}' for value in layer])
print('rms: ', [f'{value:+.3f}' for value in rms_output])Important lines
standardise(selected)- LayerNorm-style statistics come from the four features of this one example.
value / rms- RMSNorm-style scaling keeps the row mean; it does not centre first.
column = [row[feature] for row in batch]- BatchNorm-style training statistics come from one feature across all examples in the mini-batch.
Now use the interactive version. Keep Four nearby examples selected and change only the uniform shift. LayerNorm output stays fixed because centring removes that shift. RMSNorm changes because it does not centre. Then select the outlier batch: only the BatchNorm result for the selected row changes.
| example | f1 | f2 | f3 | f4 |
|---|---|---|---|---|
| selected | 1 | 2 | 4 | 8 |
| peer 1 | 2 | 3 | 5 | 9 |
| peer 2 | 3 | 4 | 6 | 10 |
| peer 3 | 4 | 5 | 7 | 11 |
Across examples · per feature
Batch normalisation
Uses all 4 rows shown. Change the batch and this selected row changes.
Across features · one example
Layer normalisation
Selected mean 3.75 · spread 2.68. A uniform shift is removed by centring.
Across squared features · one example
RMS normalisation
Selected root mean square 4.61. No mean is subtracted, so a uniform shift still matters.
Teaching calculation: learnable scale and bias are fixed to identity, and the tiny epsilon is omitted from displayed statistics. Production modules include those details.
The lab fixes learned scale and shift to identity so you can see the statistics. A production layer learns those parameters and includes epsilon in the denominator.
Step 4 — Assemble an explicit PyTorch policy
This small classifier places LayerNorm after the first linear projection, then applies an activation and dropout. The model uses no BatchNorm, so its activation statistics do not depend on other examples in the mini-batch.
The optimiser groups matrix-shaped weights separately from one-dimensional biases and normalisation parameters. This is one visible starting policy; a real architecture may require named exceptions.
import torch
from torch import nn
class RegularisedMLP(nn.Module):
def __init__(self, input_features: int, hidden_features: int, classes: int):
super().__init__()
self.network = nn.Sequential(
nn.Linear(input_features, hidden_features),
nn.LayerNorm(hidden_features),
nn.GELU(),
nn.Dropout(p=0.2),
nn.Linear(hidden_features, classes),
)
def forward(self, features):
return self.network(features)
model = RegularisedMLP(input_features=8, hidden_features=16, classes=3)
decay_parameters = []
no_decay_parameters = []
for parameter in model.parameters():
group = decay_parameters if parameter.ndim >= 2 else no_decay_parameters
group.append(parameter)
optimizer = torch.optim.AdamW(
[
{'params': decay_parameters, 'weight_decay': 0.01},
{'params': no_decay_parameters, 'weight_decay': 0.0},
],
lr=3e-4,
)
print('decayed tensors:', len(decay_parameters))
print('non-decayed tensors:', len(no_decay_parameters))For this exact model, the expected final two lines are:
There are two linear weight matrices. The two linear biases plus LayerNorm’s learned scale and bias are one-dimensional and enter the no-decay group.
Test mode behaviour directly:
features = torch.ones(1, 8)
model.train()
training_a = model(features)
training_b = model(features) # usually differs because dropout resamples
model.eval()
with torch.no_grad():
evaluation_a = model(features)
evaluation_b = model(features)
assert torch.equal(evaluation_a, evaluation_b)
“Usually differs” is deliberately not an assertion: two random dropout masks can occasionally match. The evaluation equality is the deterministic property this example tests on a fixed device with deterministic model operations.
Step 5 — Choose with a controlled ablation
An ablation is a comparison that removes or changes one component to measure what evidence changes. It does not prove a universal cause, but it is more informative than changing the whole recipe.
Start with these questions:
- Dropout: Which activations receive it? What is
p? Is the validation path definitely in evaluation mode? - Weight decay: Which parameters are included? What is the decay strength? Is the optimiser AdamW or a different rule?
- Augmentation: Which task invariance does each transform represent? Are paired targets transformed consistently?
- Normalisation: Which axes share statistics? Are those statistics current-input or running? What happens with the deployment batch shape?
Compare training curves, the declared validation measure, relevant error slices, and operational measures such as latency. A smaller training–validation gap caused by much worse performance on both sets is not automatically a success.
Real-world AI engineering example
Common mistakes
Adding every regulariser when validation worsens.
Fix — Verify split and evaluation first, then ablate one explicit factor from a reproducible baseline.
Reading dropout p as the keep probability.
Fix — In PyTorch Dropout, p is the probability of replacing an activation with zero.
Leaving the model in training mode during validation.
Fix — Call model.eval() for dropout and BatchNorm behaviour, and use no_grad() separately to stop gradient tracking.
Applying weight decay to every parameter invisibly.
Fix — Create named or shape-based parameter groups and document the policy and exceptions.
Calling an augmentation label-preserving without checking the task.
Fix — State the intended invariance and inspect transformed inputs with every paired target.
Randomly augmenting validation or test examples.
Fix — Use deterministic evaluation preprocessing that matches the deployment contract.
Saying BatchNorm normalises each example independently.
Fix — During training, it uses a feature’s values across mini-batch examples; the module normally uses running estimates at evaluation.
Treating LayerNorm and RMSNorm as identical.
Fix — LayerNorm subtracts the mean and scales; RMSNorm scales by root mean square without centring.
Assuming gradient accumulation gives BatchNorm larger-batch statistics.
Fix — Each micro-batch forward pass calculates its own training BatchNorm statistics.
Knowledge check
Knowledge check
Practice activity
Practice · 35–45 minutes
Write a one-change regularisation experiment
Goal: Produce an experiment card another engineer can review and reproduce.
- Describe the observed training–validation pattern and the checks that ruled out split or evaluation mistakes.
- Choose exactly one of dropout, selective AdamW decay, or a task-valid augmentation.
- State the mechanism, setting, parameter/input scope, and expected effect before running.
- Keep split, seed policy, schedule, checkpoint rule, and evaluation measures fixed.
- Record training and validation curves plus at least one relevant error slice.
- Decide in advance what result would keep, reject, or motivate a narrower follow-up.
Expected result: A short experiment record makes the assumption visible, isolates one change, and protects final test evidence.
Optional challenge: Add a mode test that confirms two fixed-input evaluation forwards match, then document which stochastic operations could still prevent equality in your full system.
Reveal solution
Example: “The baseline improves training loss after epoch 8 while validation
loss rises. The split, duplicates, preprocessing, and evaluation mode passed
review. Add Dropout(p=0.2) after the hidden activation only. Keep all other
settings fixed. Retain it only if the best validation loss improves without
harming the safety-critical error slice.” This is a testable hypothesis, not
a promise that dropout will help.
Exercises
A dropout layer has p = 0.25. What fraction of activations is expected to remain during training, what factor scales the survivors, and what does the layer do during evaluation?
Reveal solution
The expected surviving fraction is 1 - p = 0.75. During training each survivor is scaled by 1 / 0.75, or about 1.333. During evaluation the dropout layer is the identity: it passes every activation through without randomly removing or rescaling it.
A selected example has features [1, 2, 4, 8]. Another example in its training mini-batch is replaced by a large outlier. Which of BatchNorm, LayerNorm, and RMSNorm can change the selected example because of that replacement? Explain why.
Reveal solution
BatchNorm can change because its training statistics for each feature use all examples in the mini-batch. LayerNorm and RMSNorm calculate statistics from the selected example across its feature dimensions, so replacing a neighbouring example does not affect that selected row. LayerNorm centres and scales the row; RMSNorm scales it without subtracting the row mean.
A model has improving training loss, worsening validation loss, BatchNorm layers, dropout, and AdamW. Propose a controlled investigation rather than changing every regularisation setting at once.
Reveal solution
First verify the split and deterministic evaluation path, including model.eval(), because BatchNorm and dropout change mode-dependent behaviour. Restore a reproducible baseline and confirm the divergence. Then change one factor—such as dropout probability, decay strength, or one domain-valid augmentation—while keeping the optimiser schedule, data split, seed policy, and checkpoint rule recorded. Compare the same protected validation measures and useful error slices. Keep the final test set untouched until the configuration is selected.
Key takeaways
- Regularisation constrains reliance; normalisation chooses the values that share a centre or scale.
- Dropout masks and rescales during training, then becomes an identity operation during evaluation.
- Make weight-decay parameter groups and augmentation invariances explicit rather than hiding policy in defaults.
- BatchNorm uses batch neighbours during training; LayerNorm and RMSNorm use each example’s selected features.
- Protect validation evidence, test mode behaviour, and change one important factor at a time.
Flashcards
Flashcards
1 of 9Revision and interview questions
- Explain why regularisation and normalisation are not synonyms.
- Calculate survivor scaling for dropout probabilities 0.1, 0.25, and 0.5.
- Design a weight-decay parameter grouping policy and name one reason to review its exceptions.
- Give one valid and one invalid augmentation for a task you know, including its target contract.
- Draw the axis used by BatchNorm, LayerNorm, and RMSNorm for a batch-by-feature matrix.
- Explain the train/eval behaviour of dropout and BatchNorm.
- Design a single-factor ablation that keeps final test data protected.
One-page sketchnote summary
Print me · one-page revision sheet
Regularise reliance; normalise an axis
Main idea
A trustworthy choice states what values are constrained or normalised, when behaviour changes, which task assumption it encodes, and what held-out evidence would reject it.
Core terms
- Regularisation — constrain reliance
- Dropout — mask activations in training
- Weight decay — shrink selected parameters
- Augmentation — teach a valid invariance
- BatchNorm — down the batch
- LayerNorm — centre across features
- RMSNorm — scale without centring
One picture
Code pattern
train: augment → model.train()
validate: deterministic → model.eval()
compare one declared changeWatch out
- Do not tune on final test data
- Do not confuse p with keep probability
- Do not randomise evaluation transforms
- Do not forget eval mode
- Do not hide decay scope
Remember
- Diagnose first
- Name the axis
- State the mode behaviour
- Ablate one factor
- Inspect task-relevant slices
Three quick questions
- What values share statistics?
- What changes at evaluation?
- Which assumption does this method encode?
Lesson checklist
0 of 8 completeResources
- docsPyTorch — DropoutOfficial contract for training-time masking, survivor scaling, and evaluation behaviour.
- docsPyTorch — BatchNorm1dOfficial reference for per-feature mini-batch statistics, learned affine parameters, and running estimates.
- docsPyTorch — LayerNormOfficial reference for normalising the final selected dimensions using current-input statistics.
- docsPyTorch — RMSNormOfficial reference for root-mean-square scaling without mean subtraction.
- docsPyTorch — AdamWOfficial optimiser reference for weight decay kept separate from Adam moment estimates.
- docsTorchvision — Transforming and augmenting imagesOfficial guide to transform pipelines for images and paired targets such as boxes and masks.