ml-models / Core ML concepts / Regularization core

Regularization

Regularization trades a little fit on the training data for better behavior on new data, by penalizing, constraining or perturbing a model so it cannot use all of its freedom. This page covers the L2 and L1 penalties and their geometry, dropout, and the difference between weight decay and an L2 penalty under Adam.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. L2: ridge and weight decay
  5. L1: lasso and sparsity
  6. Dropout
  7. Weight decay versus an L2 penalty under Adam
  8. Early stopping, augmentation, label smoothing
  9. Worked example
  10. Ridge keeps everything, lasso picks
  11. Inverted dropout keeps the mean
  12. The same decay, two optimizers
  13. Implementation
  14. Tradeoffs
  15. Check yourself
  16. Connections
  17. Read first
  18. Read next
  19. References

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.
Why L1 produces zeros and L2 does not Two panels in a plane of two weights. Ellipses are contours of the training loss, centred away from the origin. On the left a circle (the L2 constraint) touches the smallest contour at a point where both weights are non-zero. On the right a diamond (the L1 constraint) touches it at a corner on an axis, where one weight is exactly zero. L2: both weights non-zero L1: one weight exactly zero

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.

Lasso coefficient path Standardized coefficients of eight features against the L1 penalty, from 0 to 3.2 on a categorical axis. The three informative features start at 3.34, -2.01 and 1.57 and shrink as the penalty grows. The five noise features sit near zero and are exactly zero by a penalty of 0.4, where 3 features remain. At a penalty of 3.2 only 1 feature is left. 0 0.1 0.2 0.4 0.8 1.6 3.2 L1 penalty α 0 dark: informative features grey: noise features

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.

ComponentShape/ParamsMeaningSource
_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:

FeatureTrueOLSRidge α = 20Lasso α = 0.4
x13.03.342.313.06
x2-2.0-2.01-1.42-1.67
x31.51.571.071.15
x40.00.20-0.010.00
x50.0-0.02-0.20-0.00
x60.0-0.010.100.00
x70.0-0.020.030.00
x80.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%.

InputMean of outputsVariance (measured)Variance x²·p/(1−p)
1.01.001.001.00
2.02.024.004.00
-3.0-3.029.009.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 gL2 penalty in the gradientDecoupled (AdamW)
0.12.4911.292
11.1871.292
100.1471.292
1000.0151.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

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 in adam.py.