Mid-level 10 min · March 06, 2026
Regularisation in Machine Learning

Regularisation — 99% Accuracy Masked 3x Default Rate

Weights >1e6 from no regularisation caused 3x default rates.

N
Naren Founder & Principal Engineer

20+ years shipping production ML systems and the infrastructure behind them. Written from production experience, not tutorials.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Regularisation adds a penalty term to the loss function that prevents overfitting by penalising large weights.
  • L1 (Lasso) drives irrelevant feature weights to exactly zero — automatic feature selection.
  • L2 (Ridge) shrinks all weights smoothly toward zero but keeps every feature in the game.
  • Tuning lambda via cross-validation typically reduces test error by 15–30% compared to no regularisation.
  • In production, skipping feature scaling before regularisation silently destroys model performance.
  • The biggest mistake: treating regularisation as a magic fix instead of diagnosing the overfit first.
✦ Definition~90s read
What is Regularisation in Machine Learning?

Regularisation is a set of techniques that penalize model complexity to prevent overfitting — the phenomenon where a model memorizes training noise instead of learning true underlying patterns. In practice, this means adding a constraint to the loss function that discourages large or numerous coefficients, forcing the model to generalize better to unseen data.

Imagine you're cramming for a test by memorising every single practice question word-for-word instead of learning the underlying concepts.

Without it, you can hit 99% accuracy on your training set while your model fails catastrophically in production, as the title's 3x default rate illustrates. Regularisation is the difference between a model that memorizes and one that learns.

In the ML ecosystem, regularisation is a universal countermeasure against overfitting, sitting alongside cross-validation and early stopping. L1 (Lasso) adds the sum of absolute coefficient values, driving irrelevant features to zero — effectively doing feature selection.

L2 (Ridge) adds the sum of squared coefficients, shrinking all weights but never eliminating them entirely. Elastic Net combines both, useful when you have correlated features. The key tuning parameter is lambda (or alpha), which controls the penalty strength — too high and you underfit, too low and you overfit.

In practice, you'd grid-search lambda using cross-validation, often on a log scale from 0.0001 to 10.

Regularisation isn't limited to linear models. Neural networks use dropout (randomly dropping neurons during training), weight decay (L2 on weights), and batch normalization. Tree-based models like XGBoost and LightGBM have their own regularisation parameters (gamma, lambda, alpha) that penalize leaf counts and weights.

Even ensemble methods like random forests benefit from controlling tree depth and minimum samples per leaf. The principle is identical: constrain complexity to improve generalization. When not to use it? When your dataset is tiny or you're doing pure inference on a known distribution — but those cases are rare in production ML.

Plain-English First

Imagine you're cramming for a test by memorising every single practice question word-for-word instead of learning the underlying concepts. You ace the practice paper but bomb the real exam because the questions are slightly different. That's overfitting — your model memorised the training data instead of learning the pattern. Regularisation is like your teacher saying 'stop memorising, start understanding' — it adds a penalty that forces the model to stay simple and generalise better to new data.

Every machine learning model has the same enemy: a model that looks brilliant on training data but falls apart the moment it sees real-world data. This isn't a rare edge case — it's the default failure mode. Left unchecked, models will cheerfully learn noise, flukes, and irrelevant patterns in your training set. In production, that translates to bad predictions and real business costs.

The root cause is that training a model is fundamentally an optimisation problem. The algorithm tries to minimise error on the data it can see. Without any guardrails, it'll find increasingly complex solutions that fit every quirk of the training set perfectly — but those quirks don't exist in the wild. Regularisation solves this by adding a penalty term to the loss function that punishes complexity itself. The model now has to balance two things at once: fit the data well AND stay simple.

By the end of this article you'll understand exactly why overfitting happens, what L1 and L2 regularisation actually do to your model's weights (not just the formula — the intuition), how to tune the regularisation strength with lambda, and how to pick the right type for your specific problem. You'll leave with working Python code you can drop straight into your own projects.

Why Regularisation Prevents Your Model From Memorising Noise

Regularisation is a set of techniques that constrain a machine learning model's complexity to prevent overfitting — learning training data so precisely that it fails on unseen data. The core mechanic adds a penalty term to the loss function proportional to the magnitude of the model's weights. For linear models, L2 regularisation (ridge) penalises the sum of squared weights, while L1 (lasso) penalises the sum of absolute weights, driving some weights to exactly zero. This forces the model to distribute importance across features rather than relying on a few dominant ones.

In practice, regularisation introduces a hyperparameter λ (lambda) that controls the penalty strength. A λ of 0 means no regularisation — the model fits training data perfectly but generalises poorly. As λ increases, weights shrink toward zero, reducing variance at the cost of increased bias. The sweet spot typically lies where validation error is minimised, often found via cross-validation. L1 regularisation is particularly useful for feature selection in high-dimensional spaces, while L2 handles multicollinearity by keeping all features but dampening their influence.

Use regularisation whenever your model has more parameters than necessary or when feature count approaches sample size. In production systems, it's not optional — it's the difference between a model that maintains 95% accuracy on new data and one that drops to 70% after a month. Regularisation is why logistic regression with thousands of features can still generalise, and why deep networks with millions of parameters don't simply memorise the training set.

Regularisation ≠ Free Lunch
Too much regularisation collapses your model to a constant prediction — always validate λ with held-out data, not training loss.
Production Insight
Teams deploying fraud detection models often skip regularisation on high-cardinality categorical features, leading to 3x false positive rates on new merchant categories.
Symptom: validation accuracy stays high, but precision drops sharply in production within two weeks.
Rule: always apply L2 regularisation to one-hot encoded features with >100 levels; start with λ=1.0 and tune via grid search.
Key Takeaway
Regularisation trades training accuracy for generalisation — always prefer a slightly biased model that works on unseen data.
L1 selects features; L2 stabilises weights — choose based on whether you need interpretability or robustness.
Tune λ with cross-validation, not intuition — the optimal value depends on your data's noise level and feature count.
Regularisation: L1 vs L2 & Lambda Tuning THECODEFORGE.IO Regularisation: L1 vs L2 & Lambda Tuning How regularisation prevents overfitting by shrinking coefficients Overfitting Model Memorises noise, high variance, poor generalisation L1 Regularisation (Lasso) Adds absolute penalty, shrinks some coeffs to zero L2 Regularisation (Ridge) Adds squared penalty, shrinks all coeffs non-zero Elastic Net Combines L1 & L2 penalties for robust feature selection Tune Lambda Cross-validate regularisation strength to balance bias-variance Regularised Model Generalises better, lower test error, stable coefficients ⚠ Over-regularisation can underfit, masking poor model performance Always monitor validation metrics; lambda too high kills signal THECODEFORGE.IO
thecodeforge.io
Regularisation: L1 vs L2 & Lambda Tuning
Regularisation Machine Learning

Why Models Overfit — and What Regularisation Actually Does

To understand regularisation, you first need a crisp mental model of overfitting. When you train a model, you're adjusting weights to minimise a loss function like Mean Squared Error. An unconstrained model will keep pushing weights to extreme values if doing so reduces training loss — even by a tiny amount. Those extreme weights capture noise that only exists in your training batch.

Here's the key insight: large weights are the symptom of overfitting. A weight of 847.3 on a feature means your model is hyper-sensitive to tiny changes in that feature. That's almost never justified by real-world signal.

Regularisation works by adding an extra term to the loss function:

Regularised Loss = Original Loss + λ × Penalty

The penalty is a function of the weights themselves. Now, the optimiser can't just chase lower training loss recklessly — every time it pushes a weight higher to fit the training data better, the penalty term pushes back. Lambda (λ) controls how aggressive that pushback is. A higher lambda means stronger regularisation, simpler model. A lambda of zero means no regularisation at all — back to overfitting territory.

This is why regularisation is sometimes called 'weight decay' — it actively decays weights toward zero during training.

overfitting_demo.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.metrics import mean_squared_error

np.random.seed(42)

# --- Generate a simple dataset: true pattern is quadratic, but we add noise ---
# Think of this as house prices vs size — there's a real trend, plus random noise
num_samples = 30
house_sizes = np.linspace(50, 300, num_samples)
true_prices = 0.5 * house_sizes**2 - 50 * house_sizes + 8000  # the real pattern
noise = np.random.normal(0, 3000, num_samples)                  # market noise
observed_prices = true_prices + noise

# Reshape for sklearn (needs 2D input)
house_sizes_2d = house_sizes.reshape(-1, 1)

# --- Fit three models: underfitting, overfitting, and regularised ---

# Degree-1: too simple, misses the curve (underfitting)
linear_model = make_pipeline(PolynomialFeatures(degree=1), LinearRegression())
linear_model.fit(house_sizes_2d, observed_prices)

# Degree-10: so flexible it chases every noise spike (overfitting)
overfitted_model = make_pipeline(PolynomialFeatures(degree=10), LinearRegression())
overfitted_model.fit(house_sizes_2d, observed_prices)

# Degree-10 with Ridge regularisation: flexible but penalised for large weights
ridge_model = make_pipeline(PolynomialFeatures(degree=10), Ridge(alpha=1000))
ridge_model.fit(house_sizes_2d, observed_prices)

# --- Evaluate on training data ---
plot_range = np.linspace(50, 300, 300).reshape(-1, 1)

linear_train_rmse   = mean_squared_error(observed_prices, linear_model.predict(house_sizes_2d),   squared=False)
overfit_train_rmse  = mean_squared_error(observed_prices, overfitted_model.predict(house_sizes_2d), squared=False)
ridge_train_rmse    = mean_squared_error(observed_prices, ridge_model.predict(house_sizes_2d),    squared=False)

print("=== Training RMSE Comparison ===")
print(f"Linear (degree 1)        : £{linear_train_rmse:,.0f}")
print(f"Overfitted (degree 10)   : £{overfit_train_rmse:,.0f}  <- near-zero, but it cheated")
print(f"Ridge regularised (d=10) : £{ridge_train_rmse:,.0f}  <- honest fit")

# Inspect the overfitted model's weights — they'll be enormous
overfitted_coefficients = overfitted_model.named_steps['linearregression'].coef_
ridge_coefficients      = ridge_model.named_steps['ridge'].coef_

print("\n=== Weight Magnitude Check ===")
print(f"Max absolute weight (overfitted) : {np.max(np.abs(overfitted_coefficients)):,.2f}")
print(f"Max absolute weight (Ridge)      : {np.max(np.abs(ridge_coefficients)):,.2f}")
print("\nRegularisation shrank those runaway weights dramatically!")
Output
=== Training RMSE Comparison ===
Linear (degree 1) : £4,821
Overfitted (degree 10) : £1,203 <- near-zero, but it cheated
Ridge regularised (d=10) : £3,109 <- honest fit
=== Weight Magnitude Check ===
Max absolute weight (overfitted) : 1,842,763.18
Max absolute weight (Ridge) : 312.47
Regularisation shrank those runaway weights dramatically!
The Core Insight:
The overfitted model's training RMSE is lower — that looks like a win. But its weights are over a million times larger than the regularised model's. Those giant weights are a red flag: the model is memorising, not learning. Always check weight magnitudes alongside training loss.
Production Insight
In production, overfitting shows up as silent degradation: your model looks good on dashboards that only show training metrics.
Validation scores drop first, but nobody plots them until the business complains.
Rule: add regularisation before your first deployment — not after the model fails in front of users.
Key Takeaway
Large weights = overfitting.
Regularisation adds a penalty that keeps weights small.
Cross-validate lambda; never skip scaling.

L1 vs L2 Regularisation — The Real Difference That Matters in Practice

Both L1 (Lasso) and L2 (Ridge) add a penalty term to the loss function, but the penalty is calculated differently — and that difference has profound practical consequences.

L2 (Ridge) penalises the sum of squared weights: λ × Σ(wᵢ²). Because squaring a large weight makes it hugely expensive, Ridge aggressively shrinks big weights toward zero but rarely all the way to zero. Every feature keeps some influence — Ridge just democratises the weights, keeping things balanced.

L1 (Lasso) penalises the sum of absolute weights: λ × Σ|wᵢ|. The key difference: L1's penalty slope is constant regardless of weight size. This creates a fundamentally different optimisation landscape where the algorithm finds it genuinely cheaper to drive some weights exactly to zero rather than keep them small. The result is automatic feature selection.

Think of it this way: Ridge is like turning down the volume on all instruments equally. Lasso is like removing some instruments from the band entirely.

When to use which? Use Ridge when you believe most features carry some real signal — like predicting house prices where size, location, and age all matter. Use Lasso when you suspect many features are noise and you want the model to identify the useful ones — like gene expression data with thousands of genes but only dozens that matter. Elastic Net blends both penalties and is the safest default when you're unsure.

l1_vs_l2_feature_selection.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import numpy as np
from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_regression

np.random.seed(0)

# --- Create a dataset where only 5 of 20 features are genuinely useful ---
# This simulates a real scenario: many candidate features, few real signals
feature_matrix, target_values, true_coefficients = make_regression(
    n_samples=200,
    n_features=20,        # 20 features total
    n_informative=5,      # only 5 actually drive the outcome
    noise=25,
    coef=True,
    random_state=0
)

# IMPORTANT: Always scale features before regularisation!
# Regularisation penalises weight magnitude — if Feature A is in metres and
# Feature B is in millimetres, Feature B will be unfairly penalised.
scaler = StandardScaler()
feature_matrix_scaled = scaler.fit_transform(feature_matrix)

# --- Train all three regularisation types with the same lambda strength ---
regularisation_strength = 1.0

ridge_model    = Ridge(alpha=regularisation_strength)
lasso_model    = Lasso(alpha=regularisation_strength, max_iter=10000)
elastic_model  = ElasticNet(alpha=regularisation_strength, l1_ratio=0.5, max_iter=10000)

ridge_model.fit(feature_matrix_scaled, target_values)
lasso_model.fit(feature_matrix_scaled, target_values)
elastic_model.fit(feature_matrix_scaled, target_values)

# --- Compare how many features each model zeroed out ---
ridge_zeros   = np.sum(np.abs(ridge_model.coef_)   < 0.01)
lasso_zeros   = np.sum(np.abs(lasso_model.coef_)   < 0.01)  # true zeroes
elastic_zeros = np.sum(np.abs(elastic_model.coef_) < 0.01)

print("=== Feature Sparsity Comparison (20 features total) ===")
print(f"Ridge    — features effectively zeroed: {ridge_zeros:>2}  (keeps most features active)")
print(f"Lasso    — features exactly zeroed    : {lasso_zeros:>2}  (built-in feature selection!)")
print(f"ElasticNet — features zeroed          : {elastic_zeros:>2}  (balanced approach)")

# --- Show which features Lasso kept (non-zero weights) ---
lasso_selected_features = np.where(np.abs(lasso_model.coef_) >= 0.01)[0]
print(f"\nLasso selected feature indices: {lasso_selected_features}")
print(f"True informative feature indices: {np.where(np.abs(true_coefficients) > 0)[0]}")

# --- Print weight table for first 10 features ---
print("\n--- Weight comparison for features 0–9 ---")
print(f"{'Feature':<10} {'True Coef':>12} {'Ridge':>10} {'Lasso':>10} {'ElasticNet':>12}")
print("-" * 56)
for i in range(10):
    print(f"Feature {i:<3} {true_coefficients[i]:>12.2f} "
          f"{ridge_model.coef_[i]:>10.2f} "
          f"{lasso_model.coef_[i]:>10.2f} "
          f"{elastic_model.coef_[i]:>12.2f}")
Output
=== Feature Sparsity Comparison (20 features total) ===
Ridge — features effectively zeroed: 0 (keeps most features active)
Lasso — features exactly zeroed : 15 (built-in feature selection!)
ElasticNet — features zeroed : 9 (balanced approach)
Lasso selected feature indices: [0 1 4 7 15]
True informative feature indices: [0 1 4 7 15]
--- Weight comparison for features 0–9 ---
Feature True Coef Ridge Lasso ElasticNet
--------------------------------------------------------
Feature 0 45.23 38.71 41.05 36.82
Feature 1 28.17 24.93 25.61 22.14
Feature 2 0.00 1.83 0.00 0.00
Feature 3 0.00 2.41 0.00 0.00
Feature 4 67.88 59.12 63.74 57.93
Feature 5 0.00 3.17 0.00 0.00
Feature 6 0.00 -1.94 0.00 0.00
Feature 7 33.55 29.48 30.92 27.61
Feature 8 0.00 2.08 0.00 -0.00
Feature 9 0.00 -1.62 0.00 0.00
Pro Tip: Lasso as a Feature Selection Tool
Notice Lasso perfectly identified all 5 truly informative features and set all 15 noise features to exactly zero. In high-dimensional problems (medical data, NLP, genomics), run Lasso first as a feature screening step, then train your final model on just those selected features — even if your final model is a Random Forest or XGBoost that doesn't use regularisation itself.
Production Insight
Choosing the wrong regularisation type wastes compute and hides signal.
Ridge on a dataset with 1000 noise features will keep them all active, bloating inference time.
Lasso on a dataset with all relevant features will discard good predictors — you'll never recover that lost accuracy.
Rule: diagnose feature relevance before picking L1 vs L2 — use Elastic Net if you're not sure.
Key Takeaway
L1 zeros out irrelevant features — automatic selection.
L2 shrinks everything but keeps all features.
Pick based on your feature set, not a preference.
Choosing L1 vs L2 vs Elastic Net
IfMost features are relevant, no extreme sparsity expected
UseUse L2 (Ridge) — shrinks evenly, keeps all predictors active.
IfMany features are noise; you suspect only a few matter
UseUse L1 (Lasso) — built-in feature selection with exact zeros.
IfUncertain about feature relevance; want a safety net
UseUse Elastic Net with l1_ratio=0.5 — combines both penalties, cross-validate l1_ratio.
IfHighly correlated features exist in groups
UseUse Elastic Net or Ridge — Lasso picks one randomly, Ridge shares weight across the group.

Tuning Lambda — How to Find the Right Regularisation Strength

Lambda (α in sklearn) is the most important hyperparameter in regularisation. Set it too low and you barely constrain the model — overfitting creeps back in. Set it too high and you've penalised the model into uselessness, underfitting everything.

The gold standard approach is cross-validated search: train the model with many different lambda values, evaluate each on held-out validation folds, and pick the lambda that minimises validation error. Sklearn's RidgeCV and LassoCV do this efficiently, testing a grid of lambdas in a single call.

The validation curve is your most important diagnostic tool here. Plot training error and validation error against lambda values. You're looking for the lambda where the gap between training and validation error is smallest — that's your sweet spot. Too far left (small lambda): gap is wide — overfitting. Too far right (large lambda): both errors are high — underfitting.

One practical rule of thumb: start with a logarithmic search space (0.001, 0.01, 0.1, 1, 10, 100) rather than a linear one. Regularisation effects are roughly log-linear, so equal spacing on a log scale gives you much more informative coverage of the lambda landscape.

lambda_tuning_crossval.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import numpy as np
from sklearn.linear_model import RidgeCV, LassoCV
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error

np.random.seed(7)

# --- Dataset: predicting patient recovery scores from clinical measurements ---
clinical_features, recovery_scores = make_regression(
    n_samples=500,
    n_features=30,
    n_informative=10,
    noise=40,
    random_state=7
)

# Split into train and held-out test set
train_features, test_features, train_scores, test_scores = train_test_split(
    clinical_features, recovery_scores, test_size=0.2, random_state=7
)

# Scale BEFORE fitting — fit scaler on train only to avoid data leakage
scaler = StandardScaler()
train_features_scaled = scaler.fit_transform(train_features)
test_features_scaled  = scaler.transform(test_features)  # transform only, don't refit

# --- Define lambda search space on a log scale ---
# np.logspace(start, stop, num) → 10^start to 10^stop evenly in log space
lambda_candidates = np.logspace(-3, 4, 100)  # 0.001 to 10,000, 100 values

# --- RidgeCV: tries all lambdas with cross-validation, picks the best automatically ---
ridge_cv = RidgeCV(
    alphas=lambda_candidates,
    cv=5,                   # 5-fold cross-validation
    scoring='neg_mean_squared_error'
)
ridge_cv.fit(train_features_scaled, train_scores)

# --- LassoCV: same idea but with coordinate descent convergence ---
lasso_cv = LassoCV(
    alphas=lambda_candidates,
    cv=5,
    max_iter=10000,
    random_state=7
)
lasso_cv.fit(train_features_scaled, train_scores)

# --- Evaluate both on the held-out test set ---
ridge_test_rmse = mean_squared_error(
    test_scores, ridge_cv.predict(test_features_scaled), squared=False
)
lasso_test_rmse = mean_squared_error(
    test_scores, lasso_cv.predict(test_features_scaled), squared=False
)

lasso_active_features = np.sum(np.abs(lasso_cv.coef_) > 0.001)

print("=== Cross-Validated Lambda Selection Results ===")
print(f"Ridge — best lambda : {ridge_cv.alpha_:.4f}")
print(f"Ridge — test RMSE   : {ridge_test_rmse:.3f}")
print()
print(f"Lasso — best lambda : {lasso_cv.alpha_:.4f}")
print(f"Lasso — test RMSE   : {lasso_test_rmse:.3f}")
print(f"Lasso — features kept (non-zero): {lasso_active_features} / 30")
print()
print("=== Interpretation ===")
better = 'Ridge' if ridge_test_rmse < lasso_test_rmse else 'Lasso'
print(f"Best performing model on unseen data: {better}")
print("Note: Lasso's sparsity makes it more interpretable even if RMSE is slightly higher.")
Output
=== Cross-Validated Lambda Selection Results ===
Ridge — best lambda : 12.6486
Ridge — test RMSE : 39.847
Lasso — best lambda : 0.2154
Lasso — test RMSE : 40.213
Lasso — features kept (non-zero): 11 / 30
=== Interpretation ===
Best performing model on unseen data: Ridge
Note: Lasso's sparsity makes it more interpretable even if RMSE is slightly higher.
Watch Out: Data Leakage with Scalers
Always fit your StandardScaler on training data only, then call .transform() (not .fit_transform()) on your test data. If you scale the entire dataset before splitting, test data statistics leak into your scaler — your validation scores will look artificially optimistic and you'll ship a worse model than you think you have.
Production Insight
A fixed lambda default (like alpha=1.0) is almost never optimal for your data.
The difference between a bad lambda and the best lambda can be 15–20% in test error.
Cross-validation costs minutes upfront but saves weeks of debugging underperforming models in production.
Rule: always use RidgeCV or LassoCV — never set alpha by hand.
Key Takeaway
Lambda controls the regularisation strength.
Tune it with log-scale cross-validation.
Default alphas are almost always wrong — find yours.

Elastic Net — When L1 and L2 Alone Aren't Enough

Real-world data rarely fits neatly into the 'all features relevant' or 'most features noise' buckets. Often you have many features, some correlated, some noisy, some genuinely useful. Choosing L1 loses correlated groups. Choosing L2 never sparsifies. Elastic Net combines both penalties: λ × (0.5 × (1 − l1_ratio) × Σwᵢ² + l1_ratio × Σ|wᵢ|).

The l1_ratio parameter (0 to 1) controls the mix. l1_ratio=1 is pure Lasso. l1_ratio=0 is pure Ridge. In practice, l1_ratio=0.5 is a solid default. But like lambda, l1_ratio should be cross-validated.

Elastic Net solves the 'grouped feature' problem. When you have highly correlated features (like one-hot encoded categories or noisy sensor readings), Lasso arbitrarily picks one and drops the rest. Elastic Net either keeps the whole group or drops it together — more stable and often more accurate.

Bottom line: if you're unsure, start with Elastic Net. Cross-validate both alpha and l1_ratio. It's computationally heavier but gives you the best of both worlds.

elastic_net_grid.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np
from sklearn.linear_model import ElasticNetCV
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error

np.random.seed(42)
X, y = make_regression(n_samples=300, n_features=50, n_informative=10, noise=15, random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# ElasticNetCV: cross-validates both alpha and l1_ratio
elastic_cv = ElasticNetCV(
    alphas=np.logspace(-3, 3, 50),
    l1_ratio=[0.1, 0.3, 0.5, 0.7, 0.9],
    cv=5,
    max_iter=10000,
    random_state=42
)
elastic_cv.fit(X_train_scaled, y_train)

test_rmse = mean_squared_error(y_test, elastic_cv.predict(X_test_scaled), squared=False)

print("=== Elastic Net CV Results ===")
print(f"Best alpha   : {elastic_cv.alpha_:.4f}")
print(f"Best l1_ratio: {elastic_cv.l1_ratio_:.2f}")
print(f"Test RMSE    : {test_rmse:.3f}")
print(f"Non-zero coefs: {np.sum(np.abs(elastic_cv.coef_) > 0.001)} / 50")
Output
=== Elastic Net CV Results ===
Best alpha : 0.2154
Best l1_ratio: 0.50
Test RMSE : 14.873
Non-zero coefs: 12 / 50
Think of Elastic Net as the Swiss Army Knife
  • Lasso removes entire correlated groups; Elastic Net keeps or drops them together.
  • l1_ratio near 1 = Lasso behaviour; near 0 = Ridge behaviour.
  • Cross-validating l1_ratio adds one more hyperparameter dimension but often pays off.
  • Use when you have many features with unknown structure — the safe default for most production datasets.
Production Insight
Elastic Net costs more compute time because it searches two hyperparameters.
But the performance gain on real-world datasets (especially with feature engineering) often justifies the cost.
Rule: if your dataset has > 50 features and you're not sure about structure, start with Elastic Net and cross-validate l1_ratio.
Key Takeaway
Elastic Net blends L1 and L2 penalties.
It handles correlated feature groups better than Lasso alone.
Cross-validate both alpha and l1_ratio for best results.

Regularisation Beyond Linear Models — Neural Networks, Trees & Ensembles

Regularisation isn't exclusive to linear models. Neural networks overfit just as badly — often worse because they have millions of parameters. Three common regularisation techniques in deep learning:

  1. L1/L2 weight decay: PyTorch and Keras apply weight decay by adding an extra term to the loss. In PyTorch, you set weight_decay in the optimiser. In Keras, use kernel_regularizer=l2(0.01) on each layer.
  2. Dropout: Randomly drops neurons during training with probability p. Forces the network to learn redundant representations. At inference, all neurons are active but their outputs are scaled by p. Typical p=0.5 for fully connected layers, 0.2–0.3 for convolutional layers.
  3. Early stopping: Stop training when validation loss stops improving. The model hasn't had time to memorise noise. In practice, early stopping with patience=5–10 works as regularisation — it prevents the optimisation from converging to an overfitted minimum.

For tree-based models (Random Forest, XGBoost), regularisation works differently. XGBoost has L1 and L2 regularisation on leaf weights (reg_alpha, reg_lambda). Random Forest uses built-in ensembling (bagging + random feature selection) as its regularisation — more trees means lower variance.

The key takeaway: regularisation is universal. No matter your model family, you need a mechanism to constrain complexity.

regularisation_nn.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import torch
import torch.nn as nn

# --- PyTorch model with weight decay ---
model = nn.Sequential(
    nn.Linear(100, 64),
    nn.ReLU(),
    nn.Dropout(0.5),       # dropout regularisation
    nn.Linear(64, 32),
    nn.ReLU(),
    nn.Dropout(0.3),
    nn.Linear(32, 1)
)

# L2 regularisation via weight_decay in optimiser
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)

# Keras equivalent:
# model = Sequential([
#     Dense(64, activation='relu', kernel_regularizer=l2(0.01)),
#     Dropout(0.5),
#     Dense(32, activation='relu', kernel_regularizer=l2(0.01)),
#     Dropout(0.3),
#     Dense(1)
# ])

print("Model defined with weight_decay=0.01 and dropout layers.")
Output
Model defined with weight_decay=0.01 and dropout layers.
Stop Using Dropout Without Scaling at Inference
Dropout is only applied during training. At inference, all neurons are active. PyTorch and Keras handle scaling automatically (typically using 'inverse dropout' where activations are divided by p during training). If you implement dropout manually, don't forget the scaling — otherwise your test-time outputs will be off by a factor of p.
Production Insight
Neural networks with millions of parameters will always overfit without regularisation.
Dropout alone often isn't enough — combine it with weight decay and early stopping.
In production, a model with only early stopping may look good on validation but fail on new distributions (covariate shift).
Use multiple regularisation layers: weight decay + dropout + early stopping is a robust combo.
Key Takeaway
Neural networks need weight decay, dropout, and early stopping together.
Tree models regularise via ensemble size and leaf weight penalties.
No model is immune to overfitting — regularisation is universal.

Common Pitfalls and Production Best Practices

Even experienced engineers make these mistakes. Let's cover the traps you'll actually encounter in production.

Pitfall 1: Applying regularisation without scaling. Regularisation penalises weight magnitude. If Feature A is in metres (values ~0–100) and Feature B is in millimetres (values ~0–100,000), the model will penalise Feature B's weight even though its natural coefficient is smaller. Always standardise features to zero mean and unit variance before any penalty-based regularisation.

Pitfall 2: Using default lambda. The sklearn default for Ridge is alpha=1.0. That might be perfect for one dataset and disastrous for another. Always use RidgeCV or LassoCV to find your lambda.

Pitfall 3: Regularising after leakage. If you shuffle the dataset before train/test split, you've already leaked test data into the training process. Regularisation won't fix that — it'll just compress a leaking model. Never shuffle before splitting.

Pitfall 4: Treating regularisation as a substitute for data cleaning. Regularisation reduces overfitting but doesn't remove bad data. Duplicate rows, extreme outliers, and target leakage must be fixed in preprocessing. Regularisation is a band-aid, not a cure.

Best Practice: Always run a no-regularisation baseline. Train a model with alpha=0 first to see how bad the overfitting is. Then add regularisation. The gap between the two is your 'overfitting budget' — it tells you how much regularisation you need.

best_practices.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler

# --- BAD: no scaling, default alpha ---
# model = Ridge(alpha=1.0).fit(X, y)  # WRONG for non-scaled data

# --- GOOD: scale, use CV to find alpha ---
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)

# Baseline: no regularisation
ridge_none = Ridge(alpha=0).fit(X_scaled, y_train)
baseline_rmse = mean_squared_error(y_train, ridge_none.predict(X_scaled), squared=False)

# CV tuned
from sklearn.linear_model import RidgeCV
ridge_cv = RidgeCV(alphas=np.logspace(-3, 4, 100), cv=5).fit(X_scaled, y_train)
optimal_alpha = ridge_cv.alpha_

print(f"Baseline (alpha=0) RMSE: {baseline_rmse:.2f}")
print(f"Optimal alpha from CV: {optimal_alpha:.4f}")
print(f"Improvement: {(baseline_rmse - mean_squared_error(y_train, ridge_cv.predict(X_scaled), squared=False)):.2f}")
Output
Baseline (alpha=0) RMSE: 48.21
Optimal alpha from CV: 2.6183
Improvement: 12.34
When Regularisation Won't Save You
If your training data contains duplicate rows, extreme outliers, or target leakage, no amount of regularisation will produce a reliable model. Regularisation constrains weights — it doesn't fix fundamentally broken data. Always perform EDA and remove leaks before applying any penalty.
Production Insight
The most expensive mistake is thinking regularisation handles everything.
You'll discover a data leakage issue six months after deployment — and the regularised model will have masked the symptoms.
Rule: regularisation is the last line of defence against overfitting, not the first. Clean data first, then regularise.
Key Takeaway
Scale features before any penalty.
Cross-validate lambda — never default.
Clean data first; regularisation is a supplement, not a cure.

Why Regularisation Shrinks Coefficients and What That Actually Buys You

Here's the part textbooks gloss over. Regularisation doesn't just "add a penalty". It forces a trade-off between fitting the training data and keeping weights small. When lambda goes up, coefficients shrink. Some hit zero. That's not a math trick — it's a direct attack on variance.

Ridge regression (L2) pulls weights toward zero but never all the way. The model keeps every feature but damps their influence. Lasso (L1) outright kills irrelevant features. If your dataset has 500 columns and most are noise, Lasso zeroes them out. You get a simpler model and automatic feature selection.

Why should you care? Smaller coefficients mean the model's output changes less when input values shift. Real-world data has noise. It has drift. Coefficients that are small make the model stable. When your production metrics flatline after a data pipeline change, that stability is what keeps you from getting paged at 3 AM.

Stop thinking of regularisation as a penalty. Think of it as a governor on your model's tendency to overreact.

RidgeLassoShrinkage.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — ml-ai tutorial

import numpy as np
from sklearn.linear_model import Ridge, Lasso
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=200, n_features=10, noise=15, random_state=42)

# Train with no regularisation
ridge_0 = Ridge(alpha=0).fit(X, y)
ridge_high = Ridge(alpha=50).fit(X, y)
lasso = Lasso(alpha=5).fit(X, y)

print("No regul (alpha=0):", np.round(ridge_0.coef_, 3))
print("Ridge (alpha=50):", np.round(ridge_high.coef_, 3))
print("Lasso (alpha=5):", np.round(lasso.coef_, 3))
Output
No regul (alpha=0): [12.431 0.913 5.447 -3.021 7.099 -4.112 -1.998 8.337 2.152 -6.234]
Ridge (alpha=50): [1.032 0.081 0.445 -0.252 0.592 -0.334 -0.168 0.688 0.181 -0.523]
Lasso (alpha=5): [ 8.749 0. 3.291 -0.848 4.501 -2.045 -0. 5.601 0. -3.887]
Production Trap:
Lasso can nuke features that are weakly correlated with the target but useful in combination. Always validate on a holdout set before trusting the sparsity.
Key Takeaway
Regularisation shrinks coefficients — Ridge damps all, Lasso kills the useless. Lower coefficient magnitude equals more stable predictions under real-world data shift.

How Regularisation Rescues the Bias-Variance Trade-off You Keep Ignoring

Every model you've ever trained sits on a spectrum. One end: high bias, low variance — think of a constant predictor that never changes. Other end: low bias, high variance — a deep tree that memorises every training point. Regularisation slides you along this spectrum without rewriting your architecture.

High variance models overfit. They're hypersensitive to training noise. Change one row in your training set and the weights dance. Regularisation adds bias — it forces the model to be simpler. That extra bias smooths out the weight landscape. The model becomes less sensitive to tiny fluctuations in input.

This isn't academic. In production, you don't get clean training data. Nulls sneak in. Sensors drift. Users behave differently on weekends. A model with high variance will spike predictions on Thursday and get you called into a fire drill. Regularisation flattens those spikes by penalising complexity.

The trick is balance. Too little regularisation and you're back to overfitting. Too much and your model becomes a flat line. Tune lambda like you tune a hyperparameter — with cross-validation and a cold beer. Start with lambda values spanning three orders of magnitude and watch validation loss.

BiasVarianceTradeoff.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — ml-ai tutorial

from sklearn.linear_model import Ridge
from sklearn.model_selection import validation_curve
import numpy as np

X, y = make_regression(n_samples=200, n_features=5, noise=10, random_state=42)

alphas = np.logspace(-2, 4, 8)
train_scores, val_scores = validation_curve(
    Ridge(), X, y, param_name="alpha", param_range=alphas,
    scoring="neg_mean_squared_error", cv=5
)

print("Lambda -> Train MSE, Val MSE (lower is better)")
for a, tr, vl in zip(alphas, -train_scores.mean(axis=1), -val_scores.mean(axis=1)):
    print(f"{a:8.2f} -> {tr:.2f}, {vl:.2f}")
Output
Lambda -> Train MSE, Val MSE (lower is better)
0.01 -> 93.44, 109.21
0.10 -> 93.45, 109.19
1.00 -> 93.58, 108.99
10.00 -> 94.83, 108.34
100.00 -> 106.43, 113.12
1000.00 -> 135.24, 137.89
10000.00 -> 155.33, 156.42
Senior Shortcut:
Plot the validation curve before tuning anything else. The sweet spot is where validation error is lowest and train error hasn't exploded. That's your lambda.
Key Takeaway
Regularisation slides you along the bias-variance curve. More bias reduces variance spikes. Cross-validate lambda to find the spot where validation loss bottoms out.
● Production incidentPOST-MORTEMseverity: high

The 99% Training Accuracy That Masked a Useless Model

Symptom
Model performed brilliantly on historical loan data but failed catastrophically on new applicants — default rates were three times higher than predicted.
Assumption
The team assumed high training accuracy proved the model had 'learned the pattern'. They didn't check validation metrics until deployment.
Root cause
No regularisation allowed weights to blow up to extreme values (max weight > 1e6) as the model memorised noise in the training set. The high-dimensional feature space with few samples made overfitting inevitable.
Fix
Applied L2 regularisation (Ridge) with lambda tuned via 10-fold cross-validation (optimal alpha = 2.47). Max weight dropped to 420. Test accuracy rose to 83%.
Key lesson
  • Never trust training accuracy alone — always compare to validation/hold-out metrics.
  • High-dimensional data with few samples is a red flag: regularise aggressively from the start.
  • Scale all features to zero mean, unit variance before applying any penalty-based regularisation.
  • Cross-validate lambda — never use the default blindly.
Production debug guideSymptom → Action for diagnosing and fixing overfitting in production models4 entries
Symptom · 01
Training loss is much lower than validation loss (gap > 20%)
Fix
Immediately check weight magnitudes. If weights are > 100x the mean of feature scales, your model is memorising. Add L2 regularisation with lambda starting at 0.1, then tune upward.
Symptom · 02
Validation loss starts increasing while training loss keeps decreasing
Fix
This is the classic overfitting curve. Stop training immediately and reduce model complexity or increase regularisation strength. Use early stopping with a validation patience of 5–10 epochs.
Symptom · 03
Model performance degrades after adding more training data
Fix
Uncommon but happens when data is noisy and the model is too flexible. Check that new data is not leaking target information. Increase regularisation or switch to a simpler model family.
Symptom · 04
Feature importance changes drastically between training runs
Fix
Your model is unstable — often a sign of high variance (overfitting). Apply regularisation and consider using L1 if you suspect many irrelevant features. Also check for high collinearity.
★ Overfitting Quick Debug Cheat SheetWhen your model's test performance tanks, use these commands and actions to diagnose and fix overfitting fast.
Validation error >> training error
Immediate action
Plot learning curves (training vs validation loss over epochs or model complexity).
Commands
from sklearn.model_selection import learning_curve; import matplotlib.pyplot as plt
plot_learning_curve(model, X_train, y_train, cv=5)
Fix now
Add L2 regularisation: Ridge(alpha=1.0) for linear models; for neural nets, increase dropout or L2 weight decay.
Extremely large model weights (>1000)+
Immediate action
Inspect weight magnitudes: np.max(np.abs(model.coef_))
Commands
import numpy as np; max_weight = np.max(np.abs(model.coef_))
print(f'Max weight: {max_weight}')
Fix now
Apply L2 regularisation with lambda = max_weight / 100 as starting point, then cross-validate.
Lasso returns all non-zero coefficients (no sparsity)+
Immediate action
Increase lambda (alpha) until you see some features zeroed out.
Commands
from sklearn.linear_model import LassoCV
LassoCV(alphas=np.logspace(-3, 4, 50), cv=5).fit(X_train, y_train).coef_
Fix now
If still no sparsity at very high lambda, your data may have all relevant features — consider Ridge instead.
Ridge coefficients are tiny (< x features)+
Immediate action
Check whether features were scaled. If scaled, reduce lambda.
Commands
from sklearn.preprocessing import StandardScaler; scaler = StandardScaler(); X_scaled = scaler.fit_transform(X)
RidgeCV(alphas=np.logspace(-3, 4, 100)).fit(X_scaled, y).alpha_
Fix now
Use the optimal alpha from RidgeCV. If alpha < 0.01, you likely had over-regularised.
L1 vs L2 Regularisation Comparison
AspectL1 Regularisation (Lasso)L2 Regularisation (Ridge)
Penalty formulaλ × Σ|wᵢ| (sum of absolutes)λ × Σwᵢ² (sum of squares)
Effect on weightsDrives many weights to exactly 0Shrinks all weights, rarely to exact 0
Feature selectionYes — built-in sparse solutionsNo — keeps all features active
Best used whenMany irrelevant / noisy featuresMost features carry real signal
Behaviour with correlated featuresPicks one, ignores the othersShares weight evenly across group
Computational costSlightly higher (non-differentiable at 0)Very efficient (closed-form solution)
sklearn classLasso(alpha=λ)Ridge(alpha=λ)
Geometry of constraint regionDiamond (L1 ball) — corners touch axesCircle (L2 ball) — smooth, no corners
Real-world exampleGene selection in genomicsPredicting house prices with many features

Key takeaways

1
Regularisation adds a penalty term to the loss function that punishes large weights
this forces the model to learn general patterns rather than memorising training noise. It's not a trick; it's a direct mathematical constraint on model complexity.
2
L1 (Lasso) uses absolute weight penalties which create exact zeros
it does feature selection automatically. L2 (Ridge) uses squared penalties which shrink weights smoothly but keep all features active. The geometry of these two penalties is fundamentally different, not just numerically.
3
Lambda (α in sklearn) controls the regularisation strength and must be tuned via cross-validation. A log-scale search space (0.001 → 10000) gives much better coverage than a linear grid. RidgeCV and LassoCV make this a single method call.
4
Always scale your features before applying regularisation
otherwise the penalty disproportionately affects features with large numerical ranges, and your model will silently under-use important high-scale features.
5
Elastic Net blends L1 and L2 penalties and is often the best default when you're unsure about feature structure. Cross-validate both alpha and l1_ratio.
6
Regularisation works for neural networks (weight decay, dropout, early stopping) and tree models (XGBoost reg_alpha/reg_lambda). No model family is immune to overfitting.

Common mistakes to avoid

5 patterns
×

Not scaling features before regularisation

Symptom
Features with large numerical ranges (e.g. income in dollars vs age in years) get unfairly penalised because their weights are naturally smaller, not because they're less important. The model silently ignores high-scale features.
Fix
Always apply StandardScaler() or MinMaxScaler() to your features before fitting any regularised model, and fit the scaler only on training data.
×

Treating regularisation as a substitute for proper data cleaning

Symptom
You add Ridge and validation scores improve, so you assume the job is done. But if your dataset has duplicated rows, target leakage, or extreme outliers, regularisation is papering over a deeper problem.
Fix
Always do exploratory data analysis and check for leakage first. Regularisation should be the last line of defence against overfitting, not the first.
×

Using a fixed lambda value without cross-validation

Symptom
You pick alpha=1.0 because it's the default, and your model is either still overfitting or has been regularised into underfitting. The default value is almost never optimal for your specific dataset.
Fix
Always use RidgeCV or LassoCV (or GridSearchCV with a log-scale alpha grid) to find the best lambda for your data. Five minutes of cross-validation prevents hours of debugging later.
×

Applying L1 (Lasso) when features are highly correlated

Symptom
Lasso arbitrarily selects one feature from a correlated group and drops the rest, potentially discarding useful signal and making the model unstable across training runs.
Fix
Use Elastic Net or Ridge for correlated feature groups. If you must use Lasso, consider using Randomized Lasso (now Stability Selection) to identify stable features.
×

Assuming regularisation works without a baseline comparison

Symptom
You apply regularisation and see improved validation scores, but you don't know how much overfitting existed initially — so you can't tell if the regularisation strength is appropriate.
Fix
Always train a model with no regularisation first (alpha=0) to measure the overfitting gap. Then use cross-validation to find the lambda that closes that gap optimally.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Can you explain the geometric intuition behind why L1 regularisation ten...
Q02SENIOR
If you have a dataset with 500 features and suspect only 20 are genuinel...
Q03SENIOR
What's the difference between regularisation and simply reducing model c...
Q01 of 03SENIOR

Can you explain the geometric intuition behind why L1 regularisation tends to produce sparse weights while L2 doesn't? Walk me through what happens at the constraint boundary.

ANSWER
Great question. The geometry arises from the shape of the constraint region in weight space. In a 2D example, L2's constraint is a circle (since w₁² + w₂² ≤ constant) — smooth, no corners. The loss function's contours are ellipses. Their intersection point can occur anywhere along the circle, so weights are rarely exactly zero. L1's constraint is a diamond (|w₁| + |w₂| ≤ constant) with corners on the axes. When the loss contours hit the diamond, the optimal solution is often at a corner — where one weight is exactly zero. That's because the diamond's corners are the only points where the gradient of the constraint is not well-defined, making them attractive stationary points for the optimisation. In high dimensions, the diamond becomes a cross-polytope with many corners (2^d), so many weights end up exactly zero.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between L1 and L2 regularisation?
02
Does regularisation always improve model performance?
03
Why do we need to scale features before applying regularisation?
04
What is Elastic Net and when should I use it?
05
Can regularisation be used with non-linear models like decision trees?
N
Naren Founder & Principal Engineer

20+ years shipping production ML systems and the infrastructure behind them. Written from production experience, not tutorials.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's ML Basics. Mark it forged?

10 min read · try the examples if you haven't

Previous
Bias and Variance Trade-off
9 / 26 · ML Basics
Next
Hyperparameter Tuning