Problem
The overfitting page showed that a flexible model fits noise, and the bias–variance page showed why: its predictions swing from one training set to the next. Shrinking the model by hand (fewer features, shallower trees) is a blunt tool, and for neural networks there is no obvious knob.
Regularization is a family of smoother knobs. Each one gives up a controlled amount of training fit (adds a little bias) to cut variance, and the strength is a hyperparameter chosen by validation.
Intuition
Think of the training loss as a bowl and the model parameters as a ball resting at the bottom. With noisy data the bottom sits at a place that exploits the noise: some weights are large, balanced against each other. A penalty adds a second bowl centred at zero and the ball settles between them. The stronger the penalty, the closer to zero.
- L2 (ridge, weight decay) penalizes squared weights. It shrinks all weights smoothly, rarely to exactly zero, and handles correlated features by splitting weight among them.
- L1 (lasso) penalizes absolute values. It can set weights exactly to zero, so it also selects features.
- Dropout randomly removes units during training, so no unit can rely on a particular partner being present.
- Early stopping, data augmentation and label smoothing regularize without touching the weights directly: they limit how far training goes, enlarge the dataset with label-preserving changes, or soften the targets.
The penalized fit is where the loss contours first touch the constraint region. A circle has no corners; a diamond's corners lie on the axes. The picture is schematic.
Mechanics
L2: ridge and weight decay
Add λ‖w‖² (or ½λ‖w‖², depending on convention) to the loss. For linear regression the minimizer has a closed form that differs from ordinary least squares by one term on the diagonal:
w = (XᵀX + αI)⁻¹ Xᵀy
The αI makes the matrix invertible even with correlated or duplicated features, and shrinks each weight along the directions the data barely constrains (those with small singular values) the most. scikit-learn's _solve_cholesky literally adds α to the diagonal of XᵀX and solves.
For gradient-trained models the penalty's gradient is λw, so each step pulls every weight towards zero in proportion to its size. That is why L2 is also called weight decay.
The linear regression page has the full derivation and path.
L1: lasso and sparsity
Add λ‖w‖₁ instead. The penalty has a corner at zero, so the minimizer sets a weight exactly to zero whenever the data's pull on it is weaker than λ. Coordinate descent updates one weight at a time using the soft-threshold function S(ρ, λ) = sign(ρ)·max(|ρ| − λ, 0), which is exactly where the zeros come from.
Elastic net mixes the two with a ratio l1_ratio; scikit-learn's objective is 1/(2n)‖y − Xw‖² + α·l1_ratio·‖w‖₁ + ½α(1 − l1_ratio)‖w‖².
Note that scikit-learn divides the squared error by 2n, so its α is on a different scale from Ridge's.
Each line is one coefficient. Grey lines reach zero and stay there; the informative ones shrink gradually and drop out last.
Dropout
During training, each unit's output is set to zero with probability p, and the survivors are multiplied by 1/(1 − p) ("inverted" dropout). The scaling keeps the expected output unchanged, so at evaluation time the layer is simply the identity and nothing needs rescaling.
Because a different random subnetwork is trained at every step, a unit cannot depend on another being present; the effect resembles averaging an enormous ensemble of thinned networks, though with shared weights.
PyTorch's nn.Dropout checks 0 ≤ p ≤ 1 in its constructor and calls F.dropout(input, p, self.training, ...), so model.eval() switches it off.
Forgetting eval() at inference time is a common bug that makes predictions noisy.
Weight decay versus an L2 penalty under Adam
With plain SGD, adding λw to the gradient and shrinking the weight by a factor (1 − ηλ) are the same thing. With an adaptive optimizer like Adam they are not. Adam divides the update by the running root-mean-square of the gradient, and if the penalty is folded into the gradient, the decay is divided too:
weights whose loss gradients are large are decayed little, and weights with tiny gradients are decayed a lot.
AdamW (Loshchilov and Hutter, 2019) applies the decay directly to the weights, w ← w(1 − lr·λ), outside the adaptive step, so every weight decays at the same rate.
In PyTorch v2.15.0, AdamW is Adam with decoupled_weight_decay=True; the branch is in adam.py. See the optimizers page for the rest of Adam.
Early stopping, augmentation, label smoothing
Early stopping halts training at the minimum of validation error (see the overfitting page); for linear models it behaves much like an L2 penalty. Data augmentation adds label-preserving transformed copies of the data (crops, flips, noise) so the model sees more variety: it regularizes by enlarging the training set, not by shrinking weights. Label smoothing replaces a one-hot target with (1 − ε) on the true class and ε/(K − 1) elsewhere, which stops the network from pushing its logits to infinity to reach a probability of exactly 1.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
_solve_cholesky |
X (n, p), alpha |
Ridge solver: adds α to the diagonal of XᵀX and solves for the weights. | _ridge.py |
Ridge.fit |
alpha=1.0 |
The public ridge estimator; validates input and delegates to the shared solver. | _ridge.py |
ElasticNet.fit |
alpha, l1_ratio |
Coordinate descent for the mixed L1 and L2 objective; Lasso is the special case l1_ratio=1. |
_coordinate_descent.py |
Lasso |
alpha=1.0 |
The L1-only estimator, a subclass of ElasticNet. |
_coordinate_descent.py |
Dropout |
p=0.5 |
Module that zeroes elements with probability p in training and passes through in evaluation. | dropout.py |
F.dropout |
input, p, training |
Checks the range of p, then calls the C++ kernel that samples the mask and rescales. | functional.py |
AdamW |
weight_decay=1e-2 |
Calls Adam with decoupled_weight_decay=True. |
adamw.py |
| decoupled decay branch | param, lr, weight_decay |
param.mul_(1 - lr * weight_decay) when decoupled, otherwise grad.add(param, alpha=weight_decay) (the L2 form). |
adam.py |
Worked example
Ridge keeps everything, lasso picks
Data: 40 rows, 8 standardized features. Only the first three matter (true weights 3, −2, 1.5); the other five are noise. The label has noise σ = 1. Fits on standardized features with centered labels:
| Feature | True | OLS | Ridge α = 20 | Lasso α = 0.4 |
|---|---|---|---|---|
| x1 | 3.0 | 3.34 | 2.31 | 3.06 |
| x2 | -2.0 | -2.01 | -1.42 | -1.67 |
| x3 | 1.5 | 1.57 | 1.07 | 1.15 |
| x4 | 0.0 | 0.20 | -0.01 | 0.00 |
| x5 | 0.0 | -0.02 | -0.20 | -0.00 |
| x6 | 0.0 | -0.01 | 0.10 | 0.00 |
| x7 | 0.0 | -0.02 | 0.03 | 0.00 |
| x8 | 0.0 | -0.12 | -0.24 | -0.00 |
Least squares gives small non-zero weights to the five noise features. Ridge shrinks everything, the useful weights and the noise weights alike (x1 falls from 3.34 to 2.31), and none reaches zero. Lasso at α = 0.4 sets all five noise weights to exactly 0 and keeps the three real ones at 3.06, -1.67 and 1.15: it found the right support. It also shrank the real weights, as the path in the diagram shows: the number of surviving features goes 8, 5, 4, 3, 3, 2, 1 as α runs through 0, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2.
Inverted dropout keeps the mean
Apply dropout with p = 0.5 to the vector 1.0, 2.0, -3.0, repeating 4,000 times with different random masks. A kept element is multiplied by 1/(1 − p) = 2, a dropped one is 0. The kept fraction comes out at 50.2%.
| Input | Mean of outputs | Variance (measured) | Variance x²·p/(1−p) |
|---|---|---|---|
| 1.0 | 1.00 | 1.00 | 1.00 |
| 2.0 | 2.02 | 4.00 | 4.00 |
| -3.0 | -3.02 | 9.00 | 9.00 |
The means match the inputs, so at evaluation time (no mask, no scaling) the layer gives the same values on average. Each output is a noisy version of its input with variance x²·p/(1 − p): larger inputs get noisier, and that noise is the regularizer, since the next layer must work with unreliable inputs.
The same decay, two optimizers
One weight starts at 5.0. Its loss gradient alternates between +g and −g (so the loss alone does not move it), learning rate 0.01, decay 0.1, 300 Adam steps. Without any decay it ends at 4.98. The table gives how far each form of decay pulls it down relative to that:
| Gradient size g | L2 penalty in the gradient | Decoupled (AdamW) |
|---|---|---|
| 0.1 | 2.491 | 1.292 |
| 1 | 1.187 | 1.292 |
| 10 | 0.147 | 1.292 |
| 100 | 0.015 | 1.292 |
The decoupled column is constant: 1.292 at every gradient scale, equal to the weight multiplied by (1 − 0.01·0.1) per step. The L2 column falls from 2.491 at g = 0.1 to 0.015 at g = 100: Adam's division by the gradient's size shrinks the penalty to almost nothing for weights with large gradients. With the L2 form, "weight decay 0.1" therefore means something different for every parameter; with AdamW it means the same thing everywhere. This is the reason transformer training uses AdamW.
Implementation
import torch
from torch import nn
from sklearn.linear_model import Lasso, Ridge
# sklearn: closed-form ridge, coordinate-descent lasso
ridge = Ridge(alpha=20.0).fit(X, y)
lasso = Lasso(alpha=0.4).fit(X, y)
print((lasso.coef_ != 0).sum(), "features kept")
# PyTorch: dropout in the model, decoupled decay in the optimizer
model = nn.Sequential(
nn.Linear(64, 128), nn.ReLU(), nn.Dropout(p=0.1),
nn.Linear(128, 10),
)
# no decay on biases and norm scales is the common convention
decay = [p for n, p in model.named_parameters() if p.ndim > 1]
no_decay = [p for n, p in model.named_parameters() if p.ndim <= 1]
opt = torch.optim.AdamW(
[{"params": decay, "weight_decay": 0.01},
{"params": no_decay, "weight_decay": 0.0}],
lr=1e-3,
)
model.train() # dropout active
# ... training steps ...
model.eval() # dropout off, identity
Always standardize features before an L1 or L2 penalty: the penalty treats all weights alike, so features on a larger scale would otherwise be penalized less for the same effect.
Tradeoffs
- Strength is a hyperparameter. Too weak and nothing changes; too strong and the model underfits (the right arm of the validation curve). Choose it by cross-validation, on a log grid.
- Lasso is unstable with correlated features. Among a group of near-duplicates it keeps one, and which one can change with the sample. Elastic net or ridge keeps the group.
- Shrinkage biases the kept weights. Lasso's survivors are smaller than their true values, as the worked example shows; a common fix is to refit an unpenalized model on the selected features.
- Dropout is noisy. It slows convergence, and it interacts with normalization layers (batch statistics change between training and evaluation). It matters less in large, data-rich transformer pretraining, where it is often set to 0.
- Penalties do not fix bad data. If the validation set leaks, regularization strength chosen on it will be too weak; see data leakage.
- Which weights to decay. Biases and normalization gains are usually left out of weight decay.
Check yourself
Why does an L1 penalty give exact zeros while an L2 penalty only shrinks?
The L1 penalty's slope is constant (λ) all the way to zero, so if the loss's pull on a weight is below λ the minimum sits exactly at zero. The L2 penalty's slope 2λw vanishes at zero, so a small pull always leaves a small non-zero weight.
Why does inverted dropout multiply the kept units by 1/(1 − p)?
So the expected output equals the input: a unit survives with probability 1 − p, and the scaling cancels that factor. Evaluation can then skip dropout and the scale entirely.
With Adam, which weights does an L2 penalty folded into the gradient decay least?
Those with large loss gradients, since Adam divides the whole gradient (penalty included) by its running RMS. AdamW removes this dependence by decaying the weights directly.
Connections
Read first
- Overfitting and underfitting intro
- Loss functions intro
Read next
No page builds on this one yet.
References
- Hastie, Tibshirani and Friedman. The Elements of Statistical Learning, sections 3.4 (shrinkage) and 3.8 (lasso and elastic net).
- Tibshirani. Regression shrinkage and selection via the lasso. JRSS B, 1996.
- Srivastava, Hinton, Krizhevsky, Sutskever and Salakhutdinov. Dropout: a simple way to prevent neural networks from overfitting. JMLR 2014.
- Loshchilov and Hutter. Decoupled weight decay regularization. ICLR 2019. arXiv:1711.05101.
- scikit-learn 1.9.1:
_solve_cholesky,ElasticNet.fit. - PyTorch v2.15.0:
Dropout, decoupled weight decay inadam.py.