Read this after trying the interactive once, then run it again with the suggested experiments.
When you feed raw features with different scales (e.g., Age 18–80 vs. Salary 20k–200k) into a network, the first-layer weights that receive the large feature grow disproportionately. During backpropagation those large weights amplify gradients in early layers (exploding), while deeper layers receive progressively smaller updates (vanishing). The result: unstable training or very slow convergence.
Normalizing inputs at the front door helps the first layer, but internal covariate shift — the distribution of each hidden layer's inputs keeps changing as weights update — still destabilizes deeper layers. BatchNorm fixes this by re-normalizing activations inside the network at every layer.
For a mini-batch of activations z at a given layer:
# batch meanFeature scaling puts the inputs on a common footing. Batch normalisation does the same thing to the activations between layers, and does it continuously during training.
For each channel, over the current mini-batch:
x̂ = (x − μbatch) / √(σ²batch + ε) out = γx̂ + β
The first step standardises to mean 0 and variance 1. The second step is what makes it a layer rather than a fixed transform: γ and β are learned, so the network can rescale and shift the normalised values — and can undo the normalisation entirely if that turns out to be better.
Two parameters per channel, which is why batch normalisation is almost free in parameter count while being one of the most impactful layers available.
Higher learning rates. The main practical benefit. Normalised activations keep gradients in a sensible range, so you can train at rates that would diverge without it — often 10× higher.
Faster convergence. Fewer epochs to the same loss, consistently.
Depth becomes trainable. Networks beyond about 20 layers were unreliable before batch normalisation and residual connections; together they made 100+ layers routine.
Mild regularisation. Each example's normalisation depends on which other examples happened to share its batch, which injects noise. That is why models with batch normalisation often need less dropout.
Less sensitivity to initialisation. A layer that normalises its inputs cares much less about the scale it was initialised with.
The original explanation was "reducing internal covariate shift". Later work argued that the real mechanism is smoothing the loss landscape. The debate is unresolved and does not change how you use it.
This is where the bugs are. During training, statistics come from the current batch. During inference, there may be no batch — a single image must give a deterministic answer — so the layer uses running averages accumulated during training.
That means the layer has two modes, and forgetting to switch is a genuine and common error:
model.train() # batch statistics, running averages updated
# ... training loop ...
model.eval() # running averages used, not updated
with torch.no_grad():
predictions = model(x)Symptoms of getting it wrong: predictions that change depending on what else is in the batch, validation accuracy far below training accuracy for no other reason, or a model that behaves differently on a batch of one.
The momentum parameter controls how fast the running averages update (0.1 in PyTorch means each batch contributes 10%). If your data distribution shifts during training, the running statistics lag behind.
Small batches. With a batch of 2 or 4, the batch statistics are noise. This is a real constraint in detection and segmentation, where large images force small batches — and the reason GroupNorm exists.
Sequence models. Sequences vary in length and normalising across the batch mixes positions that are not comparable. LayerNorm replaced it entirely in transformers.
Batch size 1. The variance is zero and the layer cannot function.
Distribution shift between train and inference. The running averages describe the training data; if production data differs, the normalisation is wrong.
| Alternative | Normalises over | Use when |
|---|---|---|
| BatchNorm | Batch, per channel | CNNs with reasonable batch sizes |
| LayerNorm | All features of one example | Transformers, RNNs |
| GroupNorm | Groups of channels, one example | Small batches in vision |
| InstanceNorm | Each channel of one example | Style transfer, generative models |
| RMSNorm | Scale only, no centring | Modern large language models |
There are two common conventions:
In practice both work well; consistency within your architecture matters more than the placement choice.
| Technique | Normalizes Over | Best For |
|---|---|---|
| Batch Norm | Batch dimension | CNNs with large batches |
| Layer Norm | Feature dimension | Transformers, RNNs |
| Group Norm | Channel groups | Small-batch CNNs |
| Instance Norm | Single sample, single channel | Style transfer |
The conventional order is Conv → BatchNorm → ReLU. Normalising before the activation keeps the pre-activations centred, so roughly half the ReLU units are active — which is what the initialisation assumed.
Two details that appear in real code:
The convolution's bias is redundant. Batch normalisation subtracts the mean, which cancels any constant the convolution added. That is why ResNet-style implementations use bias=False on convolutions followed by normalisation — it saves parameters and changes nothing.
Pre-activation ordering trains deeper. In residual blocks, BN → ReLU → Conv leaves the identity path completely clean, which was shown to train deeper networks more reliably than the original post-activation arrangement.
At inference, batch normalisation can be folded into the preceding convolution: since both are linear at that point, their weights can be combined into a single convolution. Every deployment toolkit does this, and it removes the layer's runtime cost entirely.
γ and β from weight decay. Decaying them towards zero fights what the layer is for.BatchNorm rescales each feature to zero mean and unit variance, then gives the network two parameters to change that back. Both halves matter, and the train/eval difference is where most bugs live.
Does batch normalisation replace input scaling? No. Scale the inputs as well; the first layer benefits from the same treatment as the rest.
Why does my model perform worse in eval mode? Usually the running statistics are poor — too few training steps, unshuffled data, or a distribution that shifted during training.
Can I use it with dropout? Yes, though the combination is often unnecessary. If you use both, dropout goes after the normalisation.
How many parameters does it add? Two per channel, plus two non-trained running statistics per channel.
Is it needed with residual connections? They solve different problems and are normally used together. Removing normalisation from a deep ResNet usually breaks training unless the initialisation is adjusted to compensate.
What is the ε for? To avoid dividing by zero when a channel's variance is tiny. The default of 1e-5 is fine.
model.eval(), or predictions depend on the batch.Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.
Without scrolling back — what is the one-line takeaway from this module?
What does this module say about “Quick Context”?
What does this module say about “The Problem BatchNorm Solves”?
Batch Normalization (BatchNorm) was introduced by Ioffe & Szegedy in 2015 and is now a standard building block in virtually every deep network. It normalizes the inputs to each layer so that training is faster and more stable. This page lets you see what happens with and without it.
AI.Cruz builds and maintains AI.Cruz. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.