Python for Actuarial Students · Course 3
Lesson 4

Overfitting, Generalization & Regression Regularization

Why a model that fits the past perfectly predicts the future badly, how to measure it honestly, and how shrinkage fixes it.

Lesson 4 · OverviewBy the end of this lesson you can…

  • Explain the bias–variance trade-off and recognize over- and underfitting
  • Split data into training, validation, and test sets, and run K-fold cross-validation
  • Describe Lasso (L1), Ridge (L2), and ElasticNet penalties and their geometry
  • Standardize features correctly, inside a pipeline
  • Choose the penalty strength with LassoCV, RidgeCV, and ElasticNetCV
  • Judge when regularization matters, and when it doesn’t
75 min
Concepts & worked examples
75 min
Lab: a 60-feature cost model

Companion notebook: lesson-04-regularization.ipynb — worked examples and self-checking exercises.

Part 1

The Bias–Variance Trade-off

Too simple, too flexible, and just right.

OverfittingThirty drivers, three polynomials

Polynomials of degree 1, 4, and 15 fitted to 30 noisy points from a U-shaped frequency curve

Underfit (degree 1): misses the U entirely. High bias: wrong on average, whatever sample you draw.

About right (degree 4): follows the curve, ignores the noise.

Overfit (degree 15): threads every point. High variance: a new sample gives a wildly different curve.

DecompositionExpected test error = bias² + variance + noise

Training error falls steadily with polynomial degree while test error is lowest near degree 4 and explodes beyond degree 10
DegreeBias²VarianceTest MSE
14.030.748.58
40.101.034.79
100.9411013.1
155.91409259

×10⁻⁴; averaged over 200 training samples of 30 drivers. The noise floor is 4.0.

Training error always falls as complexity rises. It can’t tell you when to stop. Only data the model hasn’t seen can.

ValidationHonest measurement

Train / validation / test

Trainfit parameters
Validationchoose settings (α, degree, depth)
Testscore once, at the end
from sklearn.model_selection import train_test_split
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, train_size=500, random_state=42)

Every time you look at the test score and change something, the test set becomes a validation set. Its score stops being honest.

K-fold cross-validation

Split the training data into K folds; fit on K − 1, score on the one left out; rotate; average. Every row is validated exactly once.

from sklearn.model_selection import KFold, cross_val_score
kf = KFold(5, shuffle=True, random_state=0)
cross_val_score(ols_pipe, X_tr, y_tr, cv=kf, scoring="r2")
# [0.240 0.232 0.319 0.384 0.370]  mean 0.309

Insurance rows aren’t independent: one policyholder renews for years. Use GroupKFold on the policyholder so the same person never sits on both sides of a split. For pricing, also hold out a whole later year.

Part 2

A High-Dimensional Cost Model

60 features, many correlated, some pure noise.

The dataPredict next year’s cost from this year’s member

bio = pd.read_csv("data/health/biometrics.csv")
# member demographics + 8 chronic conditions
# + 2024 wellness screening (BMI, blood pressure, labs…)
# + 20 lifestyle survey items
# + 2024 encounter counts by category + log 2024 cost
X = pd.get_dummies(features, columns=["sex", "plan", "region"],
                   drop_first=True, dtype=float)
y = np.log1p(cost_2025)        # mean 7.75, sd 1.84
X.shape                        # (8000, 60)

To mimic a small employer group, we fit on 500 members and test on the other 7,500.

Correlation heatmap of the features showing blocks of strongly correlated biometric measures and utilization counts

Seven pairs have |r| > 0.7: BMI–waist 0.87, HbA1c–glucose 0.87, LDL–total cholesterol 0.85, eGFR–creatinine −0.85…

OLSSixty coefficients from 500 members

from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression

ols = make_pipeline(StandardScaler(), LinearRegression())
ols.fit(X_tr, y_tr)
r2_score(y_tr, ols.predict(X_tr))   # 0.482
r2_score(y_te, ols.predict(X_te))   # 0.292
  • Training R² 0.48; test R² 0.29: a large generalization gap
  • The 20 survey items have no effect on cost, yet OLS gives them coefficients summing to 0.90 in absolute value
  • Correlated blocks get large, offsetting coefficients: weight +0.200, waist −0.183

With few observations per parameter, OLS fits the noise. Its estimates are unbiased but have enormous variance, and correlated features make it worse: (X⊤X)−1 blows up when columns are nearly collinear.

Regularization accepts a little bias in exchange for a large cut in variance, by penalizing large coefficients.

Part 3

Shrinkage Methods

Lasso, Ridge, and ElasticNet.

PenaltiesThree loss functions

$$\textbf{Ridge:}\;\; \min_\beta\; \tfrac{1}{2n}\lVert y - \mathbf{X}\beta\rVert^2 + \alpha\lVert\beta\rVert_2^2$$

$$\textbf{Lasso:}\;\; \min_\beta\; \tfrac{1}{2n}\lVert y - \mathbf{X}\beta\rVert^2 + \alpha\lVert\beta\rVert_1$$

$$\textbf{ElasticNet:}\;\; \ldots + \alpha\big(\rho\lVert\beta\rVert_1 + \tfrac{1-\rho}{2}\lVert\beta\rVert_2^2\big)$$

α sets the strength; ρ (l1_ratio) the mix. The intercept is never penalized.

Ridge (L2)Lasso (L1)ElasticNet
Shrinks coefficientsSmoothly toward 0Toward 0, many to exactly 0Both
Feature selectionNoYesYes
Correlated groupShares the weightPicks one, often arbitrarilyKeeps the group together
Best whenMany small effects, multicollinearityA few real effects among manyCorrelated blocks + sparsity

GeometryWhy the Lasso produces zeros

RSS contour ellipses meeting a diamond-shaped L1 constraint at a corner where beta 2 is zero, and a circular L2 constraint at a point where both coefficients are non-zero

Penalized fitting is the same as minimizing the RSS inside a budget region.

  • The L1 diamond has corners on the axes. The RSS ellipses usually touch it first at a corner, where some coefficients are exactly zero
  • The L2 circle has no corners: coefficients shrink but stay non-zero

Tighter budget (larger α) → smaller region → more shrinkage.

ScalingStandardize first — inside the pipeline

The penalty treats every coefficient alike, but a coefficient’s size depends on its feature’s units. Steps per day (sd 3,529) needs a tiny coefficient; diabetes (sd 0.36) a large one. Without scaling, the penalty barely touches the first and crushes the second.

# ✅ scaler learns mean/sd from each training fold only
lasso = make_pipeline(StandardScaler(), LassoCV(cv=kf))

# ❌ unscaled: same α, different model
Lasso(alpha=0.107).fit(X_tr, y_tr)
# keeps 20 features incl. steps_per_day and two survey items
# test R² 0.322 (scaled: 0.355)

Leakage pitfall: StandardScaler().fit(X) on all rows before splitting lets test-set means into training. Put the scaler in a Pipeline so cross-validation refits it on every fold.

Coefficients are now “per standard deviation”. To report them in original units, divide by the scaler’s scale_.

Scale 0/1 dummies too? It’s a choice: after scaling, rare levels are penalized less. Be consistent, and say which you did.

Tuning αCross-validation picks the penalty

from sklearn.linear_model import LassoCV, RidgeCV, ElasticNetCV

lasso = make_pipeline(StandardScaler(),
                      LassoCV(cv=kf, random_state=0)).fit(X_tr, y_tr)
lasso[-1].alpha_               # 0.107
(lasso[-1].coef_ != 0).sum()   # 13 of 60

ridge = make_pipeline(StandardScaler(),
          RidgeCV(alphas=np.logspace(-2, 4, 60), cv=kf))
enet = make_pipeline(StandardScaler(),
          ElasticNetCV(l1_ratio=[0.1, 0.5, 0.9], cv=kf))
Five-fold cross-validated mean squared error against log alpha, with a minimum near alpha 0.107 and the one-standard-error choice at 0.264

The 1-SE rule picks the largest α within one standard error of the minimum: a simpler model (7 features, test R² 0.329) that’s statistically as good.

ResultsTest-set scoreboard (7,500 members)

ModelαTrain R²Test R²Test RMSEFeatures
OLS—0.4820.2921.54860
Ridge2980.4580.3411.49460
Lasso0.1070.4240.3551.47713
ElasticNet (ρ = 0.5)0.1990.4250.3571.47616

Every penalized model has a lower training R² and a higher test R² than OLS. That’s the trade-off working.

Standardized coefficients for OLS, Ridge, and Lasso across 60 features; Ridge shrinks all of them, Lasso sets most to zero

SelectionWhat the Lasso kept

Lasso coefficient paths: pharmacy, lab and prior cost enter first as the penalty weakens

Prior utilization enters first and beats every biometric.

Survey item q04 (β = 0.005) is noise that slipped through; the 1-SE model drops it.

Feature (standardized)LassoOLS
n_pharmacy0.3600.435
n_lab0.2850.254
log_cost_20240.2750.226
n_specialist0.1600.197
hypertension · diabetes0.086 · 0.0450.157 · 0.139
bmi · weight · waist0 · 0 · 0−0.020 · 0.200 · −0.183
20 survey items (sum of |β|)0.0050.899

PerspectiveWhen does regularization matter?

Training membersOLS test R²Lasso test R²
2000.1150.331
5000.2920.355
1,0000.3340.355
2,0000.3530.353
4,0000.3520.354
7,0000.3470.350

With enough data per feature, OLS catches up. Regularization matters most when data is thin relative to the number of parameters:

  • Small employer groups, new products, rare coverages
  • Many-level factors: vehicle model, ZIP code, diagnosis code
  • Interactions: 6 styles × 8 ages × 100 counties is 4,800 cells

Penalized GLMs are credibility in modern dress: thin cells shrink toward the overall mean, just as Bühlmann credibility shrinks toward the collective. Lesson 8 uses a penalized Poisson GLM.

Actuarial Lab · 75 minRegularizing a 60-feature claim cost model

  1. Build the member feature table: members, biometrics, 2024 encounter counts, log 2024 cost
  2. Target: log(1 + 2025 cost). Split 500 train / 7,500 test with random_state=42
  3. Fit OLS in a pipeline; record train and test R²
  4. Fit RidgeCV, LassoCV, ElasticNetCV with 5-fold CV
  5. Which features survive the Lasso? What happened to the survey items and to BMI / weight / waist?
  6. Apply the 1-SE rule; refit; compare
  7. Repeat the OLS vs Lasso comparison at 200 and 2,000 training members

Answer key

OLS test R²0.292
RidgeCV α · test R²298 · 0.341
LassoCV α · test R²0.107 · 0.355
Lasso non-zero features13
1-SE α · features · test R²0.264 · 7 · 0.329

RecapLesson 4 checklist

  • I can explain bias, variance, and why training error misleads
  • I keep a test set untouched and tune with cross-validation
  • I group folds by policyholder and hold out later years
  • I know what the L1 and L2 penalties do, and why the Lasso zeroes coefficients
  • I standardize inside a Pipeline, never before the split
  • I tune α with LassoCV, RidgeCV, ElasticNetCV
  • I know when the 1-SE rule is worth it
  • I know regularization pays most when data is thin
Up next
Lesson 5

Unsupervised Learning & Risk Group Discovery

No target, no labels: K-means finds patient archetypes hidden in 180,000 encounters.