SythraOpen app

Ridge, Lasso, and Elastic Net in Python: The Math of Regularization Explained

Regularization prevents overfitting in linear regression by adding a penalty term to the cost function: Ridge (L2) squares weights to shrink them smoothly, Lasso (L1) uses absolute values to zero out irrelevant features, and Elastic Net blends both.

Sythra

9 min read

XLinkedIn
Ridge, Lasso, and Elastic Net in Python: The Math of Regularization Explained — cover illustration

Regularization is a technique that penalizes overly large feature weights during model training, preventing linear models from overfitting to noise in the training data. Ridge (L2), Lasso (L1), and Elastic Net are the three primary regularization algorithms used in regression modeling.

Imagine grading a student's essay. If you tell them: "Use as many complex vocabulary words as you want — no penalties," they will stuff every sentence with unnecessary jargon to sound smart, resulting in a convoluted essay that confuses readers (overfitting). But if you say: "Every complex word costs you a small penalty — use them only if they are genuinely necessary," the student becomes deliberate, keeping only what truly adds meaning. That is exactly how regularization restrains regression weights.

1. Why Regularization Is Necessary (Combating Overfitting)

Standard Ordinary Least Squares (OLS) regression minimizes only the training prediction error. When a dataset contains many features, noise, or correlated columns (multicollinearity), OLS assigns gigantic positive and negative weights to cancel out noise, causing the model to collapse when making predictions on unseen test data.

Regularization adds a second objective: keep model weights as small as possible. A feature can only maintain a large weight if it improves predictions enough to justify its penalty cost.

2. Ridge vs. Lasso vs. Elastic Net: The Decision Matrix

ModelPenalty TypeWeight BehaviorMulticollinearity HandlingBest Used For
Ridge RegressionL2 Norm (λwj2\lambda \sum w_j^2)Shrinks weights smoothly toward zero (never exactly 0).Stable: Groups correlated features and shrinks them together.When many features all contribute small-to-moderate predictive signals.
Lasso RegressionL1 Norm (λwj\lambda \sum |w_j|)Forces irrelevant weights to exactly zero (sparse model).Unstable: Arbitrarily selects 1 correlated feature and zeroes the rest.Automatic feature selection when only a few features truly matter.
Elastic NetL1 + L2 Blend (λ[rwj+(1r)wj2]\lambda [r\sum|w_j| + (1-r)\sum w_j^2])Selects groups of correlated features while enforcing sparsity.Best of both: Maintains grouped feature selection without dropping clusters.High-dimensional data (p>np > n) or datasets with strongly correlated feature clusters.

3. The Geometric Intuition: Why Lasso Zeroes Out Weights

Why does Lasso (L1) force weights to exactly zero while Ridge (L2) only shrinks them?

  • Ridge (L2 Circle Constraint wj2C\sum w_j^2 \le C): The constraint region is a smooth circle. The elliptical contours of the MSE loss function touch the circle at smooth points off the axes, shrinking weights evenly without landing on the axes (wj0w_j \neq 0).
  • Lasso (L1 Diamond Constraint wjC\sum |w_j| \le C): The constraint region is a diamond with sharp corners located directly on the coordinate axes. The elliptical MSE loss contours almost always intersect the diamond at one of these sharp corners, forcing one or more weights to exactly zero (wj=0w_j = 0).

4. The Mathematical Formulations

1. Baseline OLS Loss Function

J(w)=12mi=1m(y^(i)y(i))2J(w) = \frac{1}{2m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2

2. Ridge Regression (L2 Regularization)

Jridge(w)=12mi=1m(y^(i)y(i))2+λj=1nwj2J_{\text{ridge}}(w) = \frac{1}{2m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2 + \lambda \sum_{j=1}^{n} w_j^2

Ridge Gradient Update Rule:

wj:=wjα[1mi=1m(y^(i)y(i))xj(i)+2λwj]w_j := w_j - \alpha \left[ \frac{1}{m}\sum_{i=1}^m (\hat{y}^{(i)} - y^{(i)})x_j^{(i)} + 2\lambda w_j \right]

3. Lasso Regression (L1 Regularization)

Jlasso(w)=12mi=1m(y^(i)y(i))2+λj=1nwjJ_{\text{lasso}}(w) = \frac{1}{2m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2 + \lambda \sum_{j=1}^{n} |w_j|

Lasso Subgradient:

Jwj=1mi=1m(y^(i)y(i))xj(i)+λsign(wj)\frac{\partial J}{\partial w_j} = \frac{1}{m}\sum_{i=1}^m (\hat{y}^{(i)} - y^{(i)})x_j^{(i)} + \lambda \cdot \text{sign}(w_j)

4. Elastic Net (L1 + L2 Hybrid)

Jelastic(w)=12mi=1m(y^(i)y(i))2+λ[rj=1nwj+(1r)j=1nwj2]J_{\text{elastic}}(w) = \frac{1}{2m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2 + \lambda \left[ r \sum_{j=1}^{n} |w_j| + (1-r) \sum_{j=1}^{n} w_j^2 \right]

5. Worked Example (The Mathematical Pull Toward Zero)

Let's compare the derivative penalty pull for a weight with λ=0.5\lambda = 0.5 as ww shrinks:

MethodDerivative FormulaPull at w=4.0w = 4.0Pull at w=0.1w = 0.1Result at Small Weights
Ridge (L2L_2)2λw2\lambda w2(0.5)(4)=4.02(0.5)(4) = 4.02(0.5)(0.1)=0.12(0.5)(0.1) = 0.1Pull vanishes near zero — weight idles near zero but never reaches 0.
Lasso (L1L_1)λsign(w)\lambda \cdot \text{sign}(w)0.50.50.50.5Constant pull toward zero — drives weight directly into 0.

6. Code: Python Implementation From Scratch & Scikit-Learn

1. From-Scratch Regularized Gradient Descent (NumPy)

import numpy as np

np.random.seed(42)
m, n = 100, 3

# True relationship: y = 3*x1 + 0*x2 - 1.5*x3 (feature 2 is useless noise)
X = np.random.randn(m, n)
true_w = np.array([3.0, 0.0, -1.5])
y = X @ true_w + np.random.randn(m) * 0.5

def train_regularized(X, y, penalty="ridge", alpha=0.05, lam=0.1, r=0.5, epochs=2000):
    m, n = X.shape
    w = np.zeros(n)
    b = 0.0
    
    for _ in range(epochs):
        y_pred = X @ w + b
        error = y_pred - y
        
        grad_w = (1 / m) * (X.T @ error)
        grad_b = (1 / m) * np.sum(error)
        
        # Apply specific regularization penalty gradient
        if penalty == "ridge":
            grad_w += 2 * lam * w
        elif penalty == "lasso":
            grad_w += lam * np.sign(w)
        elif penalty == "elastic":
            grad_w += lam * (r * np.sign(w) + (1 - r) * 2 * w)
            
        w -= alpha * grad_w
        b -= alpha * grad_b
        
    return w, b

w_ridge, _ = train_regularized(X, y, penalty="ridge", lam=0.3)
w_lasso, _ = train_regularized(X, y, penalty="lasso", lam=0.1)
w_elastic, _ = train_regularized(X, y, penalty="elastic", lam=0.2, r=0.5)

print("Ridge weights:  ", np.round(w_ridge, 3))
print("Lasso weights:  ", np.round(w_lasso, 3))
print("Elastic weights:", np.round(w_elastic, 3))

2. Production Scikit-Learn with Automated Cross-Validation

from sklearn.linear_model import RidgeCV, LassoCV, ElasticNetCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

# 1. RidgeCV (L2 with automated optimal alpha search)
ridge_pipe = make_pipeline(StandardScaler(), RidgeCV(alphas=[0.01, 0.1, 1.0, 10.0]))
ridge_pipe.fit(X, y)
print("Ridge Optimal Alpha:", ridge_pipe.named_steps["ridgecv"].alpha_)
print("Ridge Learned Weights:", np.round(ridge_pipe.named_steps["ridgecv"].coef_, 3))

# 2. LassoCV (L1 feature selection)
lasso_pipe = make_pipeline(StandardScaler(), LassoCV(cv=5, random_state=42))
lasso_pipe.fit(X, y)
print("Lasso Optimal Alpha:", np.round(lasso_pipe.named_steps["lassocv"].alpha_, 4))
print("Lasso Learned Weights:", np.round(lasso_pipe.named_steps["lassocv"].coef_, 3))

# 3. ElasticNetCV (L1 + L2 blend)
elastic_pipe = make_pipeline(StandardScaler(), ElasticNetCV(l1_ratio=[0.1, 0.5, 0.9], cv=5, random_state=42))
elastic_pipe.fit(X, y)
print("ElasticNet Optimal l1_ratio:", elastic_pipe.named_steps["elasticnetcv"].l1_ratio_)
print("ElasticNet Learned Weights:", np.round(elastic_pipe.named_steps["elasticnetcv"].coef_, 3))

7. Common Pitfalls & Best Practices

  • Always Scale Features First: Regularization penalizes the raw magnitude of weights. If feature 1 ranges 010–1 and feature 2 ranges 0100,0000–100,000, unscaled regularization will unfairly crush feature 2. Always use StandardScaler.
  • Never Regularize the Bias (bb): The intercept represents the global baseline offset, not feature importance. Penalizing bb damages model calibration.
  • Tune via Cross-Validation: Never guess λ\lambda (or α\alpha). Use RidgeCV, LassoCV, and ElasticNetCV to select optimal penalty strengths.

Summary

  • Regularization prevents overfitting by penalizing large weights in the cost function.
  • Ridge (L2) shrinks all weights smoothly, handling multicollinearity effectively.
  • Lasso (L1) zeroes out irrelevant features entirely, producing sparse, interpretable models.
  • Elastic Net combines L1 and L2 penalties, providing stability for correlated feature clusters.

Common questions

What is the difference between Ridge (L2) and Lasso (L1) regression?

Ridge adds a penalty proportional to the square of the weights (sum w^2), shrinking all weights smoothly without zeroing them out. Lasso adds a penalty proportional to the absolute value (sum |w|), forcing irrelevant feature weights to exactly zero for automatic feature selection.

Why does Lasso produce sparse weights with zeros while Ridge does not?

Geometrically, Lasso constraint regions are diamonds with sharp corners situated on the coordinate axes, causing elliptical error contours to intersect at the axes (w = 0). Algebraically, the derivative of |w| is a constant sign(w), maintaining a constant pull into zero.

When should I use Elastic Net instead of Ridge or Lasso?

Use Elastic Net when working with high-dimensional data (p > n) or datasets where multiple features are strongly correlated. Elastic Net avoids Lasso's erratic selection while still producing sparse models.

Why is feature scaling mandatory before running Ridge or Lasso regression?

Regularization penalizes weight magnitudes uniformly. Features on larger numerical scales naturally have smaller weights and are unfairly over-penalized unless all features are standardized to mean 0 and variance 1.