Ch.18: Why Lasso Zeroes Out Coefficients (And Ridge Never Does)
Inspired by: YouTube
Ch.17 established the difference empirically: Ridge coefficients shrink toward zero but never reach it, Lasso coefficients can land on exactly zero. That's a famous machine learning interview question, ask why, and the honest answer needs the actual closed-form math, not just the shrink-vs-zero observation. This post derives it.
1. Setting Up the Same Way as Ridge
Start the same way the video does. The loss is
Substituting and then turns that into
Ridge's version of this (Ch.14) replaced the last term with , which is smooth everywhere and differentiates cleanly, giving directly. Lasso's is not differentiable at , an absolute value has a sharp corner there, so the single clean derivative doesn't exist. That corner is the entire reason this post needs three cases instead of one.
2. Three Cases Instead of One Derivative
Right before splitting into cases, the video makes one small algebra move for convenience: it writes the penalty as instead of . Nothing about the model changes, this is just relabeling the penalty's strength so that the factor of produced by differentiating the squared term cancels cleanly, instead of leaving a sitting in every formula.
Case : here , so , an ordinary smooth parabola. Differentiating with respect to and setting it to zero:
Dividing through by and solving for :
This is only a valid solution if it actually lands in , i.e. if .
Case : here , and setting the derivative to zero the same way:
Valid only if this lands in , i.e. if .
Case : at the corner itself, doesn't have a single derivative, it has a subgradient, every slope value between and counts as valid at that one point. The minimum sits at whenever zero is among those valid slopes, which works out to exactly when neither of the two cases above was satisfiable, i.e. .
To keep the three cases readable side by side, write and , plain OLS's own numerator and denominator. Put together, this is the soft-thresholding formula:
3. Why This Actually Reaches Zero and Ridge's Formula Never Does
Line up the two formulas directly:
Ridge's lives in the denominator. Growing makes the denominator bigger, which makes the fraction smaller, but a nonzero numerator divided by any finite number is still nonzero. The only way Ridge's hits exactly zero is if the numerator itself was zero to begin with. That's the whole reason Ch.16's point 1 holds: shrinkage without ever quite arriving.
Lasso's lives in the numerator, subtracted directly from it. Once grows past , the numerator would have to cross zero and flip sign, but the case split forbids that: the branch is only valid while it's positive, and the branch is only valid while it's negative. The moment neither branch's own condition holds anymore, the formula falls through to the third case and locks at exactly , and it stays there for every larger afterward, there's no branch left that would ever pull it back off zero. That's the algebraic reason feature selection is possible: the penalty competes directly against the numerator, and once it wins, it wins outright.
4. Verifying Against Scikit-Learn
sklearn.linear_model.Lasso doesn't use this post's exact loss, it minimizes rather than , so its alpha and this derivation's differ by a factor of : . Accounting for that, the closed-form soft-threshold and Lasso.coef_ should match exactly:
import numpy as np
from sklearn.datasets import make_regression
from sklearn.linear_model import Lasso
X, y = make_regression(n_samples=100, n_features=1, n_informative=1, n_targets=1, noise=20, random_state=13)
x = X.ravel()
n = len(x)
xbar, ybar = x.mean(), y.mean()
num = np.sum((x - xbar) * (y - ybar))
den = np.sum((x - xbar) ** 2)
def soft_threshold_m(lam):
if num > lam:
return (num - lam) / den
elif num < -lam:
return (num + lam) / den
return 0.0
for alpha in [0, 0.5, 1, 2, 5, 10, 50]:
lam = n * alpha
print(alpha, soft_threshold_m(lam), Lasso(alpha=alpha).fit(X, y).coef_[0])
# 0 27.8281 27.8281
# 0.5 27.2524 27.2524
# 1 26.6766 26.6766
# 2 25.5251 25.5251
# 5 22.0707 22.0707
# 10 16.3133 16.3133
# 50 0.0000 0.0000Every value matches to four decimal places. The derivation also predicts exactly where the coefficient should hit zero: solving for gives , which computes to 24.17 for this dataset. Scanning Lasso's actual output near that boundary confirms it:
for alpha in [24.0, 24.16, 24.17, 24.2, 25]:
print(alpha, Lasso(alpha=alpha).fit(X, y).coef_[0])
# 24.0 0.1927
# 24.16 0.0084
# 24.17 0.0
# 24.2 0.0
# 25 0.0The coefficient is still (barely) nonzero at alpha=24.16 and has snapped to exactly 0.0 by alpha=24.17, right where the formula says it should.
5. Ridge and Lasso Side by Side
Plotting both coefficient paths on the same synthetic dataset makes the numerator-vs-denominator difference visible directly:
Both curves start at the exact same point, m≈27.83, because at alpha=0 both formulas collapse back to plain OLS. From there they diverge, and the shape of each curve is a direct fingerprint of where sits in its formula.
Ridge's has in the denominator, so the curve is a hyperbola: growing makes the denominator bigger, which shrinks the fraction, but a nonzero numerator divided by an ever-larger number only ever approaches zero, it never lands on it. That's why the blue curve keeps bending flatter without ever touching the x-axis, still sitting above 16 even at alpha=60.
Lasso's has in the numerator instead, with just a fixed constant out front. That makes it a straight line in with slope , right up until the case split kicks in: once passes num (here alpha=24.17), the formula switches to the third case and pins at exactly 0 for good. So the red line isn't a curve that happens to hit zero, it's a straight line that gets clipped the moment it would try to cross zero, which is exactly what the case-by-case derivation in section 2 predicts.
Same starting point, same-looking algebra, and the only structural difference, numerator versus denominator, is enough to turn one into a curve that never arrives and the other into a line that arrives and stops.
This derivation only covers the single-feature case, matching how the intuition is easiest to see. The general multi-feature version doesn't have as clean a closed form since the terms don't separate as neatly, and Scikit-Learn's actual Lasso solves it with coordinate descent instead. That from-scratch implementation is the next post.
