Overfitting and Regularization

Overfitting is the most common way a model that looks excellent in a notebook fails in the real world. This page explains how to recognise it and the regularization techniques that prevent it, with a runnable example.


1. What Overfitting Is

A model overfits when it learns patterns that exist only in its training data — including random noise — and therefore performs worse on new data. The opposite failure, underfitting, is a model too simple to capture the real pattern, which does poorly everywhere. The goal of machine learning is not to fit the training set; it is to generalise to examples the model has never seen.

Regularization is the name for any technique that deliberately limits how closely a model can fit its training data, trading a little training accuracy for better accuracy on unseen data.

2. Why It Matters

Training error is always optimistic. A deep network or a deep decision tree can drive training error to zero on almost any dataset, even one with random labels (Zhang et al., 2017 demonstrated this for image classifiers). If you judge a model by training accuracy you will ship models that fail in production. Every serious workflow therefore holds back validation and test data, and every modern model family ships with regularization knobs whose defaults matter.

The picture is more nuanced for very large neural networks. Heavily over-parameterised models can sit past the classic U-shaped curve, where adding parameters lowers test error again, a phenomenon called double descent (Belkin et al., 2019; Nakkiran et al., 2020). That does not make regularization obsolete: weight decay, data augmentation, dropout and early stopping are still standard in training large models, and fine-tuning a big pretrained model on a few thousand examples overfits quickly without them. The practical message is unchanged — always measure on held-out data.

3. How It Works

Underfitting, good fit, overfitting Underfit (too simple) Good fit Overfit (memorises noise)

The three panels fit the same nine noisy points. A flat line ignores the trend (high bias). A straight sloped line captures the trend and ignores the noise. A curve that passes exactly through every point has zero training error but swings wildly between points (high variance): a new point from the same process will likely be far from it. This is the bias-variance trade-off: expected test error decomposes into bias squared, plus variance, plus irreducible noise, and model complexity moves error between the first two.

Training vs validation loss early stop here training validation epochs loss

The standard diagnostic is the learning curve. Training loss keeps falling as the model trains longer, but validation loss reaches a minimum and then rises: from that point the model is memorising. A large and growing gap between the two curves is the signature of overfitting; both curves high and flat signals underfitting.

Regularization techniques

L1 vs L2 constraint regions L1 (lasso): |w1|+|w2| <= t L2 (ridge): w1^2+w2^2 <= t green ellipses = loss contours

The geometry explains why L1 creates sparsity. Minimising loss subject to a penalty is equivalent to finding where the loss contours first touch the constraint region. The L1 region is a diamond with corners on the axes, and contours usually touch at a corner, where one weight is exactly zero. The L2 circle has no corners, so the touching point usually has both weights small but non-zero.

4. Worked Example

This scikit-learn script fits a degree-15 polynomial with and without regularization and compares training and validation error.

import numpy as np
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

rng = np.random.default_rng(0)
X = rng.uniform(-3, 3, size=(60, 1))
y = np.sin(X).ravel() + rng.normal(0, 0.3, size=60)
Xtr, Xva, ytr, yva = train_test_split(X, y, test_size=0.4, random_state=0)

models = {
    "no regularization": LinearRegression(),
    "ridge alpha=1":     Ridge(alpha=1.0),
    "lasso alpha=0.01":  Lasso(alpha=0.01, max_iter=50_000),
}
for name, reg in models.items():
    m = make_pipeline(PolynomialFeatures(15), StandardScaler(), reg).fit(Xtr, ytr)
    tr = mean_squared_error(ytr, m.predict(Xtr))
    va = mean_squared_error(yva, m.predict(Xva))
    nz = np.sum(np.abs(m[-1].coef_) > 1e-6)
    print(f"{name:18s} train={tr:.3f}  val={va:.3f}  nonzero coefs={nz}")

The unregularized model has the lowest training error and a much higher validation error. Ridge and lasso give up a little training error for a lower validation error, and lasso keeps only a handful of the sixteen polynomial terms. In Keras the equivalent tools are kernel_regularizer=keras.regularizers.l2(1e-4), layers.Dropout(0.3) and the EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True) callback; in PyTorch, pass weight_decay to the optimiser (prefer AdamW, which decouples it correctly).

5. Comparison of Techniques

TechniqueMechanismProsCons
L2 / weight decayPenalise squared weightsStable, almost always helps, one knobDoes not remove features
L1 / lassoPenalise absolute weightsSparse, interpretable modelsUnstable with correlated features
Elastic netL1 + L2Sparsity with stabilityTwo hyperparameters
Early stoppingStop at best validation lossFree, saves computeNeeds a clean validation set
Data augmentationMore varied training dataOften the largest gain for vision and audioMust preserve labels; domain-specific
DropoutRandom unit maskingCheap ensemble effect in deep netsSlows convergence; less useful with batch norm in CNNs
Cross-validationBetter error estimateDetects overfitting reliablyDoes not prevent it; costs k fits

6. Pitfalls

8. Further Reading