datarekha

Regularization — Ridge, Lasso, Elastic Net

Adding many features makes linear models overfit. Regularization is the fix you reach for before reaching for trees.

7 min read Intermediate Machine Learning Lesson 8 of 33

What you'll learn

  • Why linear models overfit when features outnumber signal
  • L2 (Ridge), L1 (Lasso), and Elastic Net — what each one penalises
  • The alpha hyperparameter and how to tune it
  • Why Lasso doubles as a feature-selection tool

Before you start

Linear regression has one job: drive the squared error on the training data as low as possible. Give it 200 features and 80 rows, and it will happily find weights that make the training error nearly zero — by fitting the noise. The test error will be a disaster.

The fix isn’t fewer features (you usually want all of them). The fix is to add a penalty to the loss that punishes large weights. That penalty is regularization, and it’s the difference between a linear model that ships to production and one that doesn’t.

The intuition

Ordinary least squares minimises:

MSE(w) = (1/n) Σ (y_i − Xw_i)²

Regularization adds a term:

Ridge:  MSE(w) + α · Σ w_j²        (L2 penalty)
Lasso:  MSE(w) + α · Σ |w_j|       (L1 penalty)
ElasticNet: combination of both

Big weights cost you more — so the optimizer prefers smaller, more “conservative” weights. That conservatism is what protects you against fitting noise.

See it on a noisy, many-feature dataset

Predicting house prices again, but with 30 engineered features (one real signal, 29 noise columns). Plain linear regression will memorize the noise; Ridge won’t.

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

rng = np.random.default_rng(0)
n, n_features = 80, 30

# Only the first feature is real signal — the rest are pure noise
X = rng.normal(size=(n, n_features))
y = 3 * X[:, 0] + rng.normal(scale=0.5, size=n)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

ols = Pipeline([("s", StandardScaler()), ("m", LinearRegression())]).fit(X_train, y_train)
rdg = Pipeline([("s", StandardScaler()), ("m", Ridge(alpha=10))]).fit(X_train, y_train)

print(f"plain  LinReg — train R²: {ols.score(X_train, y_train):.3f},"
      f"  test R²: {ols.score(X_test, y_test):.3f}")
print(f"Ridge α=10   — train R²: {rdg.score(X_train, y_train):.3f},"
      f"  test R²: {rdg.score(X_test, y_test):.3f}")

Plain OLS gets near-perfect training R² and pitiful test R². Ridge gives up some training fit and gets much higher test R². That gap is overfitting, and the penalty is what closed it.

Always scale before regularizing

The penalty Σ w_j² treats all weights equally. If one feature is in the range [0, 10⁶] and another is in [0, 1], the first will get a tiny weight and the second a huge one — purely because of units, not importance. The penalty then punishes the second feature unfairly.

The fix is StandardScaler before Ridge / Lasso. Roll it into a Pipeline (recommended) — RidgeCV does NOT scale automatically, it only adds alpha cross-validation. Forgetting to scale is the single most common regularization bug.

Lasso — same idea, sharper consequence

L2 (Ridge) shrinks weights smoothly toward zero. L1 (Lasso) shrinks them to exactly zero. The geometry of the |w| penalty has sharp corners, and the optimizer settles on those corners. Effect: Lasso both regularizes and selects features, in one pass.

import numpy as np
import pandas as pd
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

rng = np.random.default_rng(0)
n, n_features = 80, 30
X = rng.normal(size=(n, n_features))
y = 3 * X[:, 0] - 2 * X[:, 1] + rng.normal(scale=0.5, size=n)   # only 2 real features

pipe = Pipeline([("s", StandardScaler()), ("m", Lasso(alpha=0.1))]).fit(X, y)

coefs = pipe.named_steps["m"].coef_
print("non-zero coefficients (Lasso α=0.1):")
for i, c in enumerate(coefs):
    if abs(c) > 1e-6:
        print(f"  feature {i}: {c:+.3f}")
print(f"\ntotal features kept: {(np.abs(coefs) > 1e-6).sum()} / {n_features}")

Two real signals are recovered; most of the noise features are driven to exactly zero. That gives you a sparse, interpretable model — “here are the 4 features that mattered” — without running a separate feature selection pass.

L2 (Ridge) — roundw₁w₂OLS minboth w ≠ 0 (shrunk)L1 (Lasso) — corneredw₁w₂OLS minw₁ = 0 (sparse)
The penalty’s shape is everything: the round L2 region is touched off-axis (both weights small); the L1 diamond’s corner sits on an axis, so the touch sets a weight to exactly zero.

Elastic Net — the practical default

Lasso has a quirk: when two features are highly correlated, it tends to arbitrarily pick one and zero out the other. Ridge keeps both but shrinks together. ElasticNet combines both penalties:

ElasticNet: MSE(w) + α · [ l1_ratio · Σ|w| + (1 − l1_ratio) · Σw² ]

l1_ratio=0 is Ridge, l1_ratio=1 is Lasso, 0.5 is the standard middle ground. In production tabular models, Elastic Net is often the linear baseline of choice because it inherits Lasso’s feature selection without its instability under correlated features.

Tuning alpha — never by hand

The right alpha depends on your data. Use the built-in CV variants — they sweep alpha over a grid and pick the best on cross-validated score:

import numpy as np
from sklearn.linear_model import RidgeCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(0)
X = rng.normal(size=(200, 20))
y = X @ rng.normal(size=20) + rng.normal(scale=0.5, size=200)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)

# Sweep a log-spaced grid of alphas, pick best by 5-fold CV
alphas = np.logspace(-3, 3, 13)
pipe = Pipeline([("s", StandardScaler()), ("m", RidgeCV(alphas=alphas, cv=5))]).fit(X_tr, y_tr)

print("chosen alpha:", pipe.named_steps["m"].alpha_)
print("test R²:", pipe.score(X_te, y_te).round(3))

RidgeCV and LassoCV are drop-in replacements for Ridge / Lasso that handle the tuning loop for you. Use them.

Quick reference

Penaltysklearn classEffectWhen to use
L2Ridge / RidgeCVShrinks weights toward 0Many features, all probably relevant
L1Lasso / LassoCVDrives some weights to exactly 0Suspect many irrelevant features
L1 + L2ElasticNet / ElasticNetCVBest of bothCorrelated features, want sparsity

In one breath

  • Linear models overfit when features outnumber signal — they fit noise (near-zero train error, terrible test); the fix is a penalty on large weights, not fewer features.
  • Ridge (L2) adds α·Σw² and shrinks weights smoothly toward 0; Lasso (L1) adds α·Σ|w| and drives some weights exactly to 0 (its constraint has corners), doubling as feature selection.
  • ElasticNet mixes both via l1_ratio — the practical default for correlated features (Lasso’s sparsity without its instability).
  • alpha is the penalty strength (0 = OLS, large = everything → 0); never hand-tune — use RidgeCV / LassoCV.
  • Always scale before regularizing — the penalty treats all weights equally, so raw units distort it; forgetting is the #1 regularization bug.

Quick check

Quick check

0/3
Q1You have 200 training rows and 500 engineered features. Plain `LinearRegression` gets train R² of 1.0 and test R² of -0.4. Why?
Q2What does Lasso do that Ridge does not?
Q3What is `alpha` in `Ridge(alpha=10)`?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What is the bias–variance tradeoff?

A model's expected test error splits into bias (error from over-simplified assumptions, causing underfitting), variance (sensitivity to the particular training sample, causing overfitting), and irreducible noise. Adding complexity lowers bias but raises variance, so the best model minimises their sum on unseen data — not the training error.

What problem does ElasticNet solve that neither Lasso nor Ridge can handle alone?

When predictors are highly correlated, Lasso tends to arbitrarily pick one and discard the others, producing unstable feature selection. Ridge retains all correlated features but cannot zero any out. ElasticNet combines both penalties to achieve stable, sparse solutions — it groups correlated features and can shrink the whole group together.

Why does regularization require feature scaling, and what happens if you skip it?

Regularization penalizes large coefficient magnitudes uniformly. If features are on different scales, a feature measured in thousands will naturally have a small coefficient while one measured in fractions will have a large one, so the penalty disproportionately shrinks some features and nearly ignores others. Standardization ensures the penalty is applied equally across all features.

What is the fundamental difference between L1 (Lasso) and L2 (Ridge) regularization, and when do you choose each?

L1 adds the sum of absolute coefficient values to the loss, which drives some coefficients to exactly zero and performs implicit feature selection. L2 adds the sum of squared coefficients, which shrinks all weights proportionally but rarely zeroes any out. Lasso is preferred when you suspect only a few features matter; Ridge is preferred when most features contribute small effects.

What are overfitting and underfitting, and how do you fix each?

Overfitting occurs when a model memorizes training noise and fails to generalize; underfitting occurs when the model is too simple to capture the true signal. Fixes differ: overfitting requires regularization, more data, or reduced complexity; underfitting requires a more expressive model or better features.

How do you select the regularization strength λ, and what does it mean to set it too high or too low?

λ is a bias-variance trade-off knob: too low leaves the model overfit (high variance); too high over-regularizes and underfits (high bias). The standard approach is k-fold cross-validation over a logarithmic grid of λ values, minimizing held-out loss.

What is the Bayesian interpretation of Ridge regression, and what prior does it correspond to?

Ridge regression is equivalent to maximum a posteriori (MAP) estimation with a zero-mean Gaussian prior on the coefficients. The regularization strength λ corresponds to the ratio of the noise variance to the prior variance — stronger regularization means you believe coefficients are drawn from a tighter distribution around zero.

Related lessons

Explore further

Skip to content