Home › Machine learning › Foundations
algorithm low

Regularization

Problem A model fits its training data well but does worse on new data, with validation error rising while training error falls. This covers the ways to reduce overfitting and when each one helps.

Techniques that trade training fit for generalization: weight decay, dropout, early stopping, label smoothing and data mixing, with the exact update rules and defaults.

O(P) per step for weight decay, O(activations) for dropout; early stopping costs one validation pass per check
weight-decaydropoutearly-stoppingl1mixupoverfitting
Overview

A model that can memorize the training set will, unless memorizing costs more than generalizing. Weight decay pulls weights to zero, dropout deletes random units, early stopping limits how far the optimizer travels, and augmentation or mixup grows the data. Each interacts with the optimizer and learning rate: tune them together.

Key facts

Use when

  • Validation loss rises while training loss falls; small or noisy data; many epochs.
  • Image classification (ResNet SGD weight decay 1e-4, He et al. 2016), language and sequence models (dropout 0.1, Vaswani et al. 2017), tabular models (XGBoost lambda/alpha/subsample, scikit-learn Ridge/Lasso).

Avoid when

  • Single-epoch pretraining on huge corpora: dropout only slows learning (keep weight decay).
  • The gap comes from distribution shift: regularization does not fix a train/test mismatch.

Deep dive

How it works

In: weights, gradients or activations, plus λ or p · Out: shrunk weights, masked activations or a stop signal · State: dropout mask until backward; best validation score and step for early stopping; none for weight decay · Cost: O(P) per step for decay, O(activations) for dropout

MethodUpdate or transformTypical setting
L2 penaltyloss += ½λ‖w‖², so g += λwSGD: λ = 1e-4 to 5e-4
Decoupled weight decayw ← w − lr·λ·w, separate from the gradient stepAdamW: λ = 0.01 to 0.1
L1 (proximal)w ← sign(w)·max(|w| − lr·λ, 0) after the stepsparse linear models
Dropouth ← h ⊙ m / (1 − p), m ~ Bernoulli(1 − p)0.1 (transformers), 0.5 (old MLPs)
Stochastic depthdrop whole residual branches; survival decays linearly with depthlast-block drop 0.5 (paper), 0.1-0.2 in ViTs
Label smoothingtarget = (1 − ε)·onehot + ε/Kε = 0.1
Mixupx = λx_i + (1 − λ)x_j, same for targets, λ ~ Beta(α, α)α = 0.1-0.4 (ImageNet), 1.0 (CIFAR)
Early stoppingkeep the best validation checkpoint; stop after patience checks without improvementpatience 5-20 evals
Max-normrescale each unit's incoming weights to ‖w‖ ≤ cc = 3-4 with dropout
  1. L2 vs decoupled decay. In SGD, w ← w − lr(g + λw) is exactly decay with rate lr·λ. In Adam, the λw term is divided by √v, so weights with large gradients barely decay. AdamW applies the decay outside the adaptive normalization.
  2. Weight decay with normalization layers. A weight feeding a norm layer is scale-invariant, so decay only shrinks its norm. That raises the effective learning rate (lr/‖w‖²). Decay then acts as a learning-rate controller more than a capacity limit.
  3. Dropout samples a sub-network per step. Scaling at train time (inverted) keeps the expected activation equal, so inference needs no change. Backward multiplies the gradient by the same mask and scale.
  4. Early stopping for GD on a quadratic loss behaves like L2 with λ ≈ 1/(lr·steps): fewer steps, stronger regularization.

Code

/// xorshift64*: cheap RNG for masks.
pub struct Rng(pub u64);
impl Rng {
    pub fn next_u64(&mut self) -> u64 {
        self.0 ^= self.0 >> 12; self.0 ^= self.0 << 25; self.0 ^= self.0 >> 27;
        self.0.wrapping_mul(0x2545F4914F6CDD1D)
    }
    pub fn uniform(&mut self) -> f32 { (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32 }
}

/// Inverted dropout. Stores a 1-bit-per-element mask for backward (32x smaller than f32).
pub fn dropout_fwd(x: &mut [f32], p: f32, rng: &mut Rng, mask: &mut Vec<u64>) {
    let scale = 1.0 / (1.0 - p);
    let thresh = (p * (1u64 << 24) as f32) as u64;      // keep iff 24-bit uniform >= thresh
    mask.clear();
    mask.resize((x.len() + 63) / 64, 0);
    for (i, v) in x.iter_mut().enumerate() {
        let keep = (rng.next_u64() >> 40) >= thresh;
        mask[i / 64] |= (keep as u64) << (i % 64);
        *v = if keep { *v * scale } else { 0.0 };
    }
}

pub fn dropout_bwd(dy: &mut [f32], p: f32, mask: &[u64]) {
    let scale = 1.0 / (1.0 - p);
    for (i, g) in dy.iter_mut().enumerate() {
        *g = if mask[i / 64] >> (i % 64) & 1 == 1 { *g * scale } else { 0.0 };
    }
}

/// SGD with L2 folded into the gradient (identical to decay at rate lr·λ for plain SGD).
pub fn sgd_l2(w: &mut [f32], g: &[f32], lr: f32, l2: f32) {
    for (wi, gi) in w.iter_mut().zip(g) { *wi -= lr * (gi + l2 * *wi); }
}

/// Decoupled decay: shrink first, then any optimizer step (here plain SGD).
pub fn sgd_decoupled(w: &mut [f32], g: &[f32], lr: f32, wd: f32) {
    for (wi, gi) in w.iter_mut().zip(g) { *wi = *wi * (1.0 - lr * wd) - lr * gi; }
}

/// Proximal L1 (soft threshold) after a gradient step: produces exact zeros.
pub fn prox_l1(w: &mut [f32], lr: f32, l1: f32) {
    for wi in w.iter_mut() { *wi = wi.signum() * (wi.abs() - lr * l1).max(0.0); }
}

/// Mixup two examples and their one-hot or soft targets with weight lam.
pub fn mixup(xa: &mut [f32], xb: &[f32], ya: &mut [f32], yb: &[f32], lam: f32) {
    for (a, b) in xa.iter_mut().zip(xb) { *a = lam * *a + (1.0 - lam) * b; }
    for (a, b) in ya.iter_mut().zip(yb) { *a = lam * *a + (1.0 - lam) * b; }
}

pub struct EarlyStop { pub best: f32, pub best_step: usize, pub patience: usize, bad: usize }
impl EarlyStop {
    pub fn new(patience: usize) -> Self { Self { best: f32::INFINITY, best_step: 0, patience, bad: 0 } }
    /// Returns (save_checkpoint, stop).
    pub fn check(&mut self, step: usize, val_loss: f32, min_delta: f32) -> (bool, bool) {
        if val_loss < self.best - min_delta {
            self.best = val_loss; self.best_step = step; self.bad = 0;
            (true, false)
        } else { self.bad += 1; (false, self.bad >= self.patience) }
    }
}

fn main() {
    let mut rng = Rng(42);
    let mut x = vec![1.0f32; 100_000];
    let mut mask = Vec::new();
    dropout_fwd(&mut x, 0.1, &mut rng, &mut mask);
    let mean = x.iter().sum::<f32>() / x.len() as f32;
    println!("dropout p=0.1: mean activation {mean:.3} (expected 1.0)");
    let mut g = vec![1.0f32; x.len()];
    dropout_bwd(&mut g, 0.1, &mask);
    assert!(x.iter().zip(&g).all(|(a, b)| a == b));        // same mask, same scale
    let (mut a, mut b) = (vec![2.0f32], vec![2.0f32]);
    sgd_l2(&mut a, &[0.5], 0.1, 0.01);
    sgd_decoupled(&mut b, &[0.5], 0.1, 0.01);
    assert!((a[0] - b[0]).abs() < 1e-6);                    // identical for plain SGD
    let mut w = vec![0.03f32, -0.5, 0.004];
    prox_l1(&mut w, 0.1, 0.05);
    println!("prox L1: {w:?}");                             // small weights become exactly 0
    let mut es = EarlyStop::new(3);
    for (s, l) in [1.0f32, 0.8, 0.82, 0.79, 0.81, 0.80, 0.83].iter().enumerate() {
        let (save, stop) = es.check(s, *l, 1e-3);
        if save { println!("step {s}: new best {l}"); }
        if stop { println!("stop at step {s}, restore step {}", es.best_step); break; }
    }
}

Complexity and constants

  • Weight decay: one multiply per parameter, fused into the optimizer kernel. Free.
  • Dropout: one random number per activation. Storing the mask as bits costs N/8 bytes, vs 4N for an fp32 mask. Fused kernels (and FlashAttention's attention dropout) regenerate the mask from the RNG seed and offset in backward and store nothing.
  • Early stopping: a validation pass every k steps, order of 1-5 % of training compute.
  • Mixup and label smoothing: O(batch × features), negligible.

Efficiency

  • Cost: weight decay is one multiply per parameter inside the optimizer kernel, effectively free. Dropout needs one random number per activation and a mask of N/8 bytes as bits (4N as fp32). Early stopping costs a validation pass every k steps, order of 1-5 % of training compute. Bound: memory bandwidth for mask traffic.
  • Levers:
    • decoupled weight decay in the fused optimizer — no extra pass (Loshchilov and Hutter 2019) — always
    • regenerate the dropout mask from the RNG seed and offset in backward — stores no mask (above, FlashAttention) — attention and large activations
    • stochastic depth — skipped layers cost no compute, so training time falls (Huang et al. 2016) — deep residual nets
    • SWA or EMA of weights — one extra weight copy, no extra training steps (Izmailov et al. 2018) — late-stage training
    • mixup, label smoothing — O(batch × features), negligible (above) — small data
  • Naive → standard → state of the art:
    • fp32 dropout mask stored per layer, separate elementwise kernels · 4N bytes per mask
    • bit-packed mask, fused bias + dropout + residual · N/8 bytes
    • mask regenerated from seed in backward, fused into the surrounding kernel · 0 stored bytes

Implementation notes

  • Build two optimizer parameter groups: decay for weight matrices and conv kernels, no decay for 1-D parameters (biases, norm scales).
  • PyTorch's SGD(weight_decay=λ) adds λw to the gradient (L2), and Adam(weight_decay=λ) does the same. Only AdamW decouples. PyTorch AdamW multiplies λ by lr: λ = 0.1 at lr 3e-4 shrinks by 3e-5 per step.
  • Dropout placement in a transformer: after attention softmax, after each sublayer output before the residual add, and on embeddings. Turn all of them off at inference (model.eval()).
  • Keep the RNG state in the checkpoint if you need bit-exact resume.
  • Early stopping with noisy validation: smooth (EMA) the metric or require min_delta, and keep the best checkpoint rather than the last.
  • In GBDTs, the equivalents are shrinkage (learning rate), λ on leaf weights, row and column subsampling, and early stopping on validation rounds.

Pitfalls

  • Adam(weight_decay=…) believed to be AdamW: decay is then scaled per parameter by 1/√v and is much weaker on frequently-updated weights.
  • Decaying LayerNorm γ toward 0: it slowly shrinks the signal and fights the optimizer.
  • Dropout left on at evaluation, or BatchNorm and dropout interacting (variance shift): dropout before BN changes the variance BN sees between train and test.
  • Tuning weight decay without retuning lr: with AdamW the effective decay per step is lr·λ, so a new lr changes regularization too.
  • Early stopping on the test set: the test score is then optimistic. Use a separate validation split.

Variants and alternatives

Data augmentation (random crops, flips, RandAugment, SpecAugment)usually the strongest regularizer for vision and audio.
CutMixpaste a patch from another image, mix labels by area.
Stochastic weight averaging / EMA of weightsaverage checkpoints along the trajectory for flatter minima. Nearly free and often a small accuracy gain.
DropConnectdrop weights instead of activations.
Spectral normconstrains the Lipschitz constant, common in GAN discriminators.

In production

torch.optim.AdamW, torch.nn.Dropout, timm (drop_path_rate for stochastic depth, Mixup/CutMix, RandAugment), scikit-learn Ridge/Lasso/ElasticNet (alpha), XGBoost lambda/alpha/subsample, Keras EarlyStopping(patience=…, restore_best_weights=True).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
PyTorchPython/C++pytorch/pytorchpip install torch————nn.Dropout, optim.AdamW, optim.swa_utils
burnRusttracel-ai/burncargo add burn————Dropout, AdamWConfig with weight decay
candleRusthuggingface/candlecargo add candle-core candle-nn————candle_nn::optim::AdamW, candle_nn::ops::dropout
optaxPython/JAXgoogle-deepmind/optaxpip install optax————adamw, add_decayed_weights, EMA

Sources

  • Srivastava, Hinton, Krizhevsky, Sutskever, Salakhutdinov, Dropout: A Simple Way to Prevent Neural Networks from Overfitting, JMLR 2014. paper
  • Loshchilov and Hutter, Decoupled Weight Decay Regularization, ICLR 2019. paper
  • Huang, Sun, Liu, Sedra, Weinberger, Deep Networks with Stochastic Depth, ECCV 2016. paper
  • Zhang, Cisse, Dauphin, Lopez-Paz, mixup: Beyond Empirical Risk Minimization, ICLR 2018. paper
  • Yun et al., CutMix: Regularization Strategy to Train Strong Classifiers with Localizable Features, ICCV 2019. paper
  • He, Zhang, Ren, Sun, Deep Residual Learning for Image Recognition, CVPR 2016. paper
  • Vaswani et al., Attention Is All You Need, NeurIPS 2017. paper
  • Izmailov et al., Averaging Weights Leads to Wider Optima and Better Generalization, UAI 2018. paper

Used by

ftrl online learning · plannedFTRL-proximal online logistic regression for sparse, high-volume click prediction
Gradient-boosted decision trees (GBDT)Someone with a table of mixed numeric and categorical features, missing values and non-linear interactions needs a strong predictive model, or a ranking model, without tuning a neural network.
network pruning · plannedRemoving weights/heads/channels (magnitude, structured, 2:4 sparsity) with retraining

Systems (3):

Alternatives and related