CO2 Topic 7 of 25
Regularisation: ridge, lasso, elastic net and the bias-variance trade-off
A model with many features can memorise the training data. A penalty on large weights trades a little bias for much less variance, and L1 can remove features altogether.
You will be able to
- Explain bias and variance
- Write the ridge, lasso and elastic net costs
- Explain geometrically why L1 gives sparse weights
- Tune regularisation strength with validation data
- Compare models with compare_regularisation
Read
- Textbook 1, Ch. 4, pp. 136–147
- Textbook 2, Ch. 3, pp. 57–79
- Textbook 3, Ch. 6, pp. 225–264
Intuition
Give a model many features and few rows, and it finds patterns that are only coincidence. It scores well in training, and worse on new students. Regularisation adds a price for large weights, so that a weight must earn its size by truly reducing the error.
How it works
Bias and variance.
| Meaning | Symptom | |
|---|---|---|
| High bias | Too simple, misses the pattern | Poor training and validation scores |
| High variance | Too flexible, fits noise | Great training, poor validation |
Expected error = bias² + variance + irreducible noise
Regularisation accepts a little more bias, for much less variance.
The three penalties:
Ridge (L2): Cost = MSE + α Σ wᵢ²
Lasso (L1): Cost = MSE + α Σ |wᵢ|
Elastic net: Cost = MSE + α [ r Σ |wᵢ| + (1 − r)/2 Σ wᵢ² ]
Don't penalise the intercept, and do scale the features first, because the penalty depends on a weight's size.
Why does L1 give exact zeros? Picture the error as contour ellipses around the best weights. The penalty confines the weights to a shape around the origin. The ridge shape is a circle, so the ellipses usually touch it away from an axis. The lasso shape is a diamond, whose corners lie on the axes, so the ellipses tend to touch a corner, where a weight is exactly 0.
| Ridge | Lasso | Elastic net | |
|---|---|---|---|
| Shrinks weights | Yes | Yes | Yes |
| Sets weights to exactly 0 | No | Yes | Yes |
| Correlated features | Shares weight among them | Picks one, somewhat at random | Keeps groups |
In LogisticRegression, C works the other way: C = 1/α, so a small C means strong regularisation.
Run it: shrinkage and comparison
We add 30 pure-noise features, so that there is something to overfit and to remove.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from placement_predict import load_data, preprocessor, train_val_test
df = load_data(n=800)
X_train, X_val, X_test, y_train, y_val, y_test = train_val_test(df)
prep = preprocessor().fit(X_train)
rng = np.random.default_rng(0)
def with_noise(X):
return np.c_[prep.transform(X), rng.normal(size=(len(X), 30))]
A, B = with_noise(X_train), with_noise(X_val)
def compare_regularisation(A, y_train, B, y_val, C_values):
rows = []
for C in C_values:
common = dict(C=C, solver="saga", max_iter=4000)
lasso = LogisticRegression(penalty="l1", **common).fit(A, y_train)
ridge = LogisticRegression(penalty="l2", **common).fit(A, y_train)
enet = LogisticRegression(penalty="elasticnet", l1_ratio=0.5, **common).fit(A, y_train)
rows.append({"C": C, "Lasso_val": lasso.score(B, y_val), "Ridge_val": ridge.score(B, y_val),
"ElasticNet_val": enet.score(B, y_val), "Lasso_nonzero": int((lasso.coef_ != 0).sum()),
"Ridge_nonzero": int((ridge.coef_ != 0).sum())})
return pd.DataFrame(rows)
table = compare_regularisation(A, y_train, B, y_val, [0.001, 0.01, 0.1, 1, 10])
print(f"Features in total: {A.shape[1]}, of which 30 are pure noise")
print(table.round(3).to_string(index=False))
fig, ax = plt.subplots(1, 2, figsize=(10, 3.2))
ax[0].semilogx(table["C"], table["Lasso_val"], "o-", label="lasso")
ax[0].semilogx(table["C"], table["Ridge_val"], "o-", label="ridge")
ax[0].set(xlabel="C (smaller means stronger penalty)", ylabel="validation accuracy"); ax[0].legend()
ax[1].semilogx(table["C"], table["Lasso_nonzero"], "o-", label="lasso")
ax[1].semilogx(table["C"], table["Ridge_nonzero"], "o-", label="ridge")
ax[1].set(xlabel="C", ylabel="non-zero weights"); ax[1].legend()
plt.tight_layout()
plt.show()
Try this: raise the noise features from 30 to 300. Does the best C change?
Common mistakes
- Regularising unscaled features. A feature in large units gets a small weight, and so escapes the penalty.
- Confusing C with α. Bigger C means weaker regularisation.
- Choosing C using the test set. Use validation data or cross-validation.
Problems
- MSE = 4, weights (2, −1), α = 0.5. Calculate the ridge cost and the lasso cost.
- Train accuracy 0.99, validation 0.72: is the problem bias or variance? What do you try first?
- Train accuracy 0.70, validation 0.69 where 0.90 is known to be possible: bias or variance?
- Lab 5: write
compare_regularisationas above, and print which features lasso keeps at the best C. - Draw the circle and diamond with an error ellipse, and show where each one touches.
Flashcards
Say the meaning to yourself, then select the card to check.
- High variance
- Great on training data, poor on validation data.
- Ridge (L2)
- Adds α Σ wᵢ². It shrinks weights but rarely to zero.
- Lasso (L1)
- Adds α Σ |wᵢ|. It can set weights to exactly zero.
- C in LogisticRegression
- The inverse of penalty strength: smaller C means stronger regularisation.