Batch Normalization
Normalize activations across a batch, apply a learned scale and shift, and understand why training and evaluation behave differently.
Jump to a section
Why this matters
Batch normalization rescales activations using statistics computed across a mini-batch, then applies a learned scale and shift. For an input shaped (N, C), it treats each feature column separately.
Its most important distinction is training versus inference. Training uses batch statistics; evaluation normally uses stored running estimates. Confusing those modes can change predictions even when the model weights stay fixed.
Build the mental model
For a batch of activations shaped (N, C) (N examples, C features) BN computes the mean and variance down each column (across the N examples) and standardizes that feature. Before the learned affine transform, each feature has zero batch mean and variance close to one (depending on ). Learned and then set its scale and offset. They can undo normalization for fixed statistics, but cannot exactly undo every batch's changing statistics with one fixed pair of parameters.
Center and rescale one feature
Each displayed batch has its own mean and population variance removed.
Exact inputs and equations appear with the figure.
Preparing the scientific figure…
Training-time normalization of two synthetic batches, with γ = 1 and β = 0. Layer norm uses a different normalization axis; inference uses stored batch statistics.
Authored analytic exampleExperiment with related Studio inputs
Compare the same four activations before and after normalization. The batch mean is zero and its population variance is five.
Exact training-time normalization for one feature, with γ = 1, β = 0, and ε = 0.00001. Inference uses stored statistics.
Inspect the related Studio values
x,y,series 1,-3,Before 1,-1.3416394448610998,After 2,-1,Before 2,-0.4472131482870333,After 3,1,Before 3,0.4472131482870333,After 4,3,Before 4,1.3416394448610998,After
Contrast with Layer Norm, which normalizes across the features of a single example (along C, per row): independent of batch size, which is why Transformers use LN, not BN.
Work through the math
For a mini-batch (per feature), BN computes:
is a small constant for numerical stability. and are learned per feature.
Train vs. eval: the critical distinction. At training time, and are computed from the current batch. The layer also maintains running estimates via an exponential moving average:
With PyTorch's default track_running_stats=True, evaluation uses fixed running statistics, making this layer independent of the other examples in the batch. If tracking is disabled, it uses batch statistics in evaluation too. Forgetting to switch to eval mode (model.eval()) is a classic bug: predictions then depend on whatever else is in the batch.
Key takeaway: Batch Norm is two different behaviors wearing one layer: batch statistics while training, frozen running statistics at inference. The
model.eval()switch is not optional; it changes what the layer computes.
The normalization-family landscape, and when each applies:
| Method | Normalizes over | Depends on batch size? | Typical home |
|---|---|---|---|
| Batch Norm | Each channel over batch and spatial/temporal axes, when present | Yes; effective sample count also includes spatial positions | CNNs, MLPs |
| Layer Norm | Configured trailing axes; usually the hidden features of each token | No | Transformers, RNNs |
| Instance Norm | Spatial axes of each channel, per example | No | Style transfer, image generation |
| Group Norm | Each channel group and spatial axes, per example | No | Detection/segmentation with small batches |
Read the implementation
For a two-dimensional (N, C) input, this matches the default PyTorch distinction: the training forward pass uses population variance, while the running estimate uses the unbiased sample variance. Training needs at least two examples here.
1import torch
2from torch import Tensor, nn
3
4
5class BatchNorm1d(nn.Module):
6 def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1) -> None:
7 super().__init__()
8 self.eps, self.momentum = eps, momentum
9 self.gamma = nn.Parameter(torch.ones(num_features)) # learnable scale
10 self.beta = nn.Parameter(torch.zeros(num_features)) # learnable shift
11 self.register_buffer("running_mean", torch.zeros(num_features))
12 self.register_buffer("running_var", torch.ones(num_features))
13
14 def forward(self, x: Tensor) -> Tensor: # x: (N, num_features)
15 if self.training:
16 if x.size(0) < 2:
17 raise ValueError("Training requires at least two examples")
18 mean = x.mean(dim=0)
19 var = x.var(dim=0, unbiased=False) # biased var, as in BN
20 with torch.no_grad(): # update running stats
21 self.running_mean.mul_(1 - self.momentum).add_(self.momentum * mean)
22 unbiased_var = var * x.size(0) / (x.size(0) - 1)
23 self.running_var.mul_(1 - self.momentum).add_(self.momentum * unbiased_var)
24 else:
25 mean, var = self.running_mean, self.running_var # frozen at inference
26 x_hat = (x - mean) / torch.sqrt(var + self.eps)
27 return self.gamma * x_hat + self.beta
28
29
30bn = BatchNorm1d(16)
31out = bn(torch.randn(32, 16))
32assert out.shape == (32, 16)
33bn.eval() # switch to running stats
34assert bn(torch.randn(1, 16)).shape == (1, 16) # works for batch size 1Questions and trade-offs
- Conceptual: What does Batch Norm actually normalize, and along which axis? (Each feature, across the examples in the batch: per-column for
(N, C)input.) - Implementation: What changes between training and inference, and why? (Train uses batch stats + updates running averages; eval uses frozen running stats so output is independent of batch composition.)
- Applied: Why does BN struggle with very small batch sizes, and what would you use instead? (Batch stats become noisy/unreliable; use Group Norm or Layer Norm, which don't depend on batch size.)
- Systems-level: Why do Transformers use Layer Norm rather than Batch Norm? (LN normalizes per-example across features: independent of batch size and sequence length, and well-behaved for variable-length sequences and small/streaming batches.)
- Failure modes: Your model trains well but predictions are unstable in production. What's a likely BN-related cause? (Forgot
model.eval(), so BN uses batch stats at inference: outputs depend on what else is batched together.)
Check your understanding
Without looking: write the four BN equations (mean, variance, normalize, scale-shift), name what and are for, and explain in one sentence exactly what differs at inference time. Then compare against Stage 3.
Sources and further reading
- Ioffe and Szegedy, Batch Normalization: normalization and learned affine parameters.
- PyTorch BatchNorm1d documentation: running statistics, variance estimators, and evaluation behavior.
Oliver Perrin
Machine Learning Engineer · Founder, LiminalML
LiminalML brings together concept explanations, mathematical examples, and working visuals. Find more writing by Oliver on the LiminalML Substack.
Keep exploring the idea.
Ask a follow-up in a guided session, work through the self-check, or open the visual in Studio to build on it.
Related concepts
Deep Learning
Backpropagation
Trace local derivatives backward through a computation and connect the chain rule to a manual implementation.
Optimization
Gradient Descent (SGD, Momentum, Adam)
Follow updates across a loss surface, then connect learning rate, momentum, and Adam to the update equations.
Deep Learning
Transformer Architecture
Follow attention, feed-forward layers, normalization, and residual connections through a Transformer block.