Day 5 · Overfitting, Regularization & Cross-Validation
Suggested pace: 1–1.5 hrs learn · 2–3 hrs code · rest to debug and document What you should know first ☐ Day 4: loss and fitting a line ☐ Why we split data into training and test sets (Day 4) ☐ Day 1: mean and standard d
Suggested pace: 1–1.5 hrs learn · 2–3 hrs code · rest to debug and document
What you should know first ☐ Day 4: loss and fitting a line ☐ Why we split data into training and test sets (Day 4) ☐ Day 1: mean and standard deviation If one of these feels shaky, spend 15 minutes revisiting it before starting. That is normal and saves time later.
Goal of today Learn the #1 problem in machine learning — a model that looks great on training data but fails in the real world — and the tools to detect and fix it.
Big picture — read this first The real goal is performance on unseen data, not memorizing the training set. Underfitting means the model has not captured enough useful structure. Overfitting means it has captured training-specific details that do not generalize. Train/validation/test separation protects the final evaluation from decisions made during model development. Data leakage is especially important: information from the future or from the test set must not accidentally influence training or tuning.
1. The Big Idea: Generalization
The goal of ML is NOT to do well on data it has already seen. The goal is to do well on new, unseen data. This ability is called generalization.
Exam analogy
| Student | What they did | Result |
|---|---|---|
| A | Didn’t study enough | Fails practice AND real exam → Underfitting |
| B | Understood the concepts | Does well on both → Good fit ✓ |
| C | Memorized exact practice answers | 100% on practice, fails real exam (new questions) → Overfitting |
Worked Problem · Memorizing Versus Learning
Problem. Training data: x = 1, 2, 3 gives y = 2, 4, 6. A memorizer stores the table. A learner finds the rule y = 2x. Both are asked about x = 4.
- The memorizer is perfect on the training rows, because it just looks them up.
- For x = 4 there is no row, so it has no answer.
- The learner uses the rule: 2 × 4 = 8.
- Generalization means doing well on data you have not seen.
table = {1: 2, 2: 4, 3: 6}
print(table.get(4))
print(2 * 4)None
8Answer. The memorizer gives None; the learner gives 8.
2. Underfitting vs Overfitting
Underfit (too simple) Good fit Overfit (too complex)
y | • • y | • • y | • •
| • —————— • | • ╱‾╲ • | •╱\╱‾\ •
| • • • | •╱ ╲• • | •/ \/\•
+---------- x +---------- x +---------- x
straight line ignores smooth curve follows wiggly line passes through
the curve in the data the trend every point, incl. noiseUnderfitting Good fit Overfitting Model complexity Too low Training error High Right Low Low Too high Very low High Test/validation error High Gap between train Small — Large High variance & test Small Also called High bias
Rule to diagnose:
- Both errors high → underfitting
- Train error low, test error high → overfitting
Why does overfitting happen?
- Model too complex (too many features, too high polynomial degree, too deep tree)
- Too little training data
- Data contains noise (random errors) and the model memorizes the noise
- Training too long (neural networks)
Worked Problem · Compare A Simple And A Complex Model
Problem. Points follow a noisy straight line. Fit a degree-1 and a degree-9 polynomial to 10 training points and compare the errors on the training data and on 40 new points.
- The true pattern is a straight line plus noise. A degree-9 curve has enough freedom to pass through all 10 training points exactly.
- Compute the MSE on the training points for both models.
- Compute the MSE on the new points for both models.
- Expect: the complex model wins on training data but loses on new data. That gap is overfitting.
import numpy as np
rng = np.random.default_rng(1)
f = lambda x: 2 * x + 1
xtr = np.linspace(0, 1, 10); ytr = f(xtr) + rng.normal(0, 0.3, 10)
xte = np.linspace(0.02, 0.98, 40); yte = f(xte) + rng.normal(0, 0.3, 40)
for deg in (1, 9):
c = np.polyfit(xtr, ytr, deg)
tr = ((np.polyval(c, xtr) - ytr) ** 2).mean()
te = ((np.polyval(c, xte) - yte) ** 2).mean()
print(deg, round(tr, 4), round(te, 4))1 0.0356 0.0862
9 0.0 0.2635Answer. The degree-9 model has the smaller training error but the *larger test error*. The degree-1 model is the better model.
Your Turn · Diagnose From The Numbers
Problem. Model X: training accuracy 99%, validation accuracy 70%. Model Y: training 60%, validation 58%. Name the problem for each and one fix.
Try it on paper or in a notebook first. Then check the solution.
Solution — Step By Step
- Model X has a huge gap between training and validation: it memorized the training data. That is overfitting. Fix: more data, regularization, dropout, or a simpler model.
- Model Y is poor on both and the gap is small: it cannot even fit the training data. That is underfitting. Fix: a more powerful model, better features, or train longer.
Answer. X: overfitting (big gap). Y: underfitting (both low).
3. Bias vs Variance
Think of dart throwing:
Low bias, low variance High bias, low variance Low bias, high variance High bias,
high variance
(accurate + steady) (steady but off-target) (centered but scattered) (worst)
(o) tight at center ●●● tight, off center ● ● around center ● ●
scattered, off- Bias = error from wrong assumptions /too simple a model. (Assuming a straight line for curved data.) → Underfitting
- Variance = error from being too sensitive to the exact training data. Change the training set slightly and the model changes wildly. → Overfitting
Tradeoff: Making the model more complex lowers bias but raises variance. We want the sweet spot.
Error
|\ / ← total error
| \ variance ↑ /
| \ ___________/
| \_____ /
| bias ↓ ^ sweet spot
+------------------------ model complexity →How to fix
| Problem | Fixes |
|---|---|
| High bias (underfit) | Add features, more complex model, reduce regularization, train longer |
| High variance (overfit) | More data, simpler model, regularization, fewer features, dropout, early stopping |
Worked Problem · Measure Bias And Variance
Problem. The true value is 10. Model A, trained on 4 different samples, predicts [7, 7.2, 6.8, 7]. Model B predicts [8, 12, 9, 11]. Find each model's average prediction (bias) and spread (variance).
- Model A mean = 28 ÷ 4 = 7. It is 3 below the truth, so it has high bias.
- Model A's predictions barely move: variance ≈ 0.02 (low).
- Model B mean = 40 ÷ 4 = 10, right on target: low bias.
- Model B's predictions swing: variance = 2.5 (high).
import numpy as np
for p in ([7, 7.2, 6.8, 7], [8, 12, 9, 11]):
p = np.array(p); print(round(p.mean(), 2), round(p.var(), 3))7.0 0.02
10.0 2.5Answer. A: mean 7, variance 0.02 (biased but steady). B: mean 10, variance 2.5 (accurate on average but unstable).
4. Train / Validation / Test Split
Golden rule: never judge a model on data it trained on.
Split your data into 3 parts:
| Set | Job | Analogy | Typical size |
|---|---|---|---|
| Training | Model learns from this | Practice problems | 60–70% |
| Validation | Compare models, tune hyperparameters | Mock exam | 15–20% |
| Test | Final honest score. Use ONCE at the very end. | Real exam | 15–20% |
Why need both validation AND test? If you tune settings by looking at the test set, you slowly “leak” test information into your choices — your final score becomes over-optimistic. Test set must stay hidden.
from sklearn.model_selection import train_test_split
# First: split off 20% for test
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size = 0.2,
random_state = 42)
# Then: split remaining into train (75% of 80% = 60% total) and val (20% total)
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size = 0.25,
random_state = 42)Code
random_state=42→ same split every run (reproducible)- For classification add
stratify=yto keep class proportions equal in every split
*Watch out Data Leakage — a silent killer* Information from validation/test sneaking into training. Most common way:
# ❌ WRONG: scaling using ALL data
X_scaled = scaler.fit_transform(X)
X_train, X_test = train_test_split(X_scaled, ...)
# ✅ RIGHT: split first, learn scaling from TRAIN only
scaler = StandardScaler().fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)Rule: fit on train only; transform on everything. (Pipelines do this automatically.)
Worked Problem · Make A 60 / 20 / 20 Split
Problem. Split 100 rows into 60 for training, 20 for validation and 20 for testing.
train_test_splitmakes two parts. First hold out the test set:test_size=0.2leaves 80 rows and 20 for testing.- Then split the 80 rows again. We want 20 validation rows, which is 20 ÷ 80 = 0.25 of them.
- Use
random_stateso the split is reproducible. - Never touch the test set until the very end.
import numpy as np
from sklearn.model_selection import train_test_split
X = np.arange(100).reshape(100, 1); y = np.arange(100) % 2
X_tmp, X_te, y_tmp, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
X_tr, X_val, y_tr, y_val = train_test_split(X_tmp, y_tmp, test_size=0.25,
random_state=42)
print(len(X_tr), len(X_val), len(X_te))60 20 20Answer. Sizes 60, 20, 20.
Your Turn · Keep The Class Ratio
Problem. Labels have 90 zeros and 10 ones. Split with test_size=0.2, with and without stratify. How many ones are in the stratified test set?
Try it on paper or in a notebook first. Then check the solution.
Solution — Step By Step
- The test set has 20 rows. With stratify, it keeps the 90/10 ratio: 18 zeros and 2 ones.
- Without stratify, a random split could produce 0 or 5 ones by bad luck.
- Pass
stratify=yto keep the ratio.
import numpy as np
from sklearn.model_selection import train_test_split
y = np.array([0] * 90 + [1] * 10); X = np.arange(100).reshape(-1, 1)
_, _, _, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
print(np.bincount(y_te))[18 2]Answer. [18 2]: the test set keeps the 90/10 ratio.
5. Polynomial Regression — a model whose complexity we can
control
Linear regression draws a straight line. What if data is curved? Add powers of x as new features:
Degree 1: ŷ = w₁x + b
Degree 2: ŷ = w₁x + w₂x² + b (a parabola)
Degree 3: ŷ = w₁x + w₂x² + w₃x³ + b
...It’s still linear regression (linear in the weights), just with extra features x², x³ ... .
Degree = the complexity knob:
- Degree 1 → underfit (straight line)
- Degree 3–4 → probably good
- Degree 15 → wild wiggles, overfit
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
model = make_pipeline(PolynomialFeatures(degree = 5), LinearRegression())
model.fit(X_train, y_train)Code PolynomialFeatures(degree=3) turns x into [1, x, x², x³].
Worked Problem · A Curve Is A Line In Disguise
Problem. Fit y = x² on x = [1, 2, 3, 4] using linear regression on the two features x and x².
- Create a new feature column x²: [1, 4, 9, 16].
- Stack [x, x²] as the model's inputs.
- Linear regression now finds weights for x and x². The best fit is 0·x + 1·x², with intercept 0.
import numpy as np
from sklearn.linear_model import LinearRegression
x = np.array([1, 2, 3, 4.]); y = x ** 2
m = LinearRegression().fit(np.column_stack([x, x ** 2]), y)
print((np.round(m.coef_, 3) + 0.0).tolist(), round(m.intercept_, 3) + 0.0)[0.0, 1.0] 0.0Answer. Weights [0.0, 1.0] and intercept 0.0: the model is still linear in its weights, but it draws a curve.
6. Regularization — punishing complexity
6.1 The idea
Overfit models usually have huge weights (to make the curve wiggle sharply). So add a penalty for large weights into the loss:
New loss = MSE + penalty(weights)
Now the model must balance: fit the data well AND keep the weights small. Result: smoother, simpler curve.
6.2 Ridge (L2)
Loss = MSE + α · (w₁² + w₂² + ... )
- Shrinks all weights toward zero (but never exactly zero)
- Good when many features are useful/correlated
6.3 Lasso (L1)
Loss = MSE + α · (|w₁| + |w₂| + ... )
- Pushes some weights to exactly zero → automatically *removes useless features* (feature selection)
- Good when you suspect many features are irrelevant
6.4 ElasticNet = mix of both
| Ridge (L2) | Lasso (L1) |
|---|---|
| Penalty Sum of squares | Sum of absolute values |
| Weights become Small | Small, many exactly 0 |
| Use for Keeping all features | Selecting features |
| sklearn Ridge(alpha=1.0) | Lasso(alpha=0.1) |
6.5 The strength: alpha (λ)
- α = 0 → no regularization (plain regression, may overfit)
- α small → weak penalty
- α large → weights ≈ 0 → underfit Tune α with validation set / cross-validation. Try
0.001, 0.01, 0.1, 1, 10, 100.
Worked Problem · Turn The Alpha Dial
Problem. Fit Ridge to x = [1, 2, 3, 4], y = [2, 4, 6, 8] with alpha = 0, 1 and 100. How does the weight change?
- With alpha = 0 there is no penalty, so we get the ordinary fit w = 2.
- A larger alpha charges more for big weights, so the weight shrinks.
- At alpha = 100 the weight is squeezed far below 2.
import numpy as np
from sklearn.linear_model import Ridge
X = np.array([[1], [2], [3], [4.]]); y = np.array([2, 4, 6, 8.])
print([round(float(Ridge(alpha=a).fit(X, y).coef_[0]), 3) for a in (0, 1, 100)])[2.0, 1.667, 0.095]Answer. The weights are [2.0, 1.667, 0.095]: bigger alpha → smaller weight → simpler model.
6.6 Always scale features before Ridge/Lasso
The penalty treats all weights equally, so features must be on the same scale.
from sklearn.linear_model import Ridge, Lasso
from sklearn.preprocessing import StandardScaler
model = make_pipeline(PolynomialFeatures(15), StandardScaler(), Ridge(alpha = 1.0))Worked Problem · Compute A Ridge Loss
Problem. A model has weights [3, -2, 1], a training MSE of 2.0 and λ = 0.1. Compute the Ridge loss = MSE + λ × Σw².
- Square each weight: 9, 4, 1. Their sum is 14.
- Penalty = λ × 14 = 0.1 × 14 = 1.4.
- Total loss = 2.0 + 1.4 = 3.4.
- A bigger weight costs more, so training prefers smaller weights unless a big weight really helps.
import numpy as np
w = np.array([3, -2, 1])
print(round(2.0 + 0.1 * (w ** 2).sum(), 2))3.4Answer. Ridge loss = 3.4 (2.0 error + 1.4 penalty).
Your Turn · Ridge Shrinks Weights
Problem. Fit ordinary least squares and Ridge (alpha = 1) on two nearly identical features. Which model has smaller total weight size?
Try it on paper or in a notebook first. Then check the solution.
Solution — Step By Step
- Features that move together make OLS weights large and unstable.
- Ridge adds a penalty on the weights, so it settles on smaller ones.
- Compare Σ|w| for both models.
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
X = np.array([[1, 1.0], [2, 2.1], [3, 2.9], [4, 4.2]]); y = np.array([2, 4, 6, 8.1])
ols = LinearRegression().fit(X, y); rid = Ridge(alpha=1.0).fit(X, y)
print(abs(rid.coef_).sum() < abs(ols.coef_).sum())TrueAnswer. True. Ridge keeps the weights smaller, which makes the model less jumpy on new data.
7. Cross-Validation (CV)
7.1 The problem
A single train/validation split can be lucky or unlucky. Maybe the validation set had all the easy examples. Your score might be misleading, especially with small data.
7.2 The solution — k-fold CV
Split data into k equal parts (folds). Repeat k times: each time use a different fold as validation, rest as training. Average the k scores.
5-fold example:
Round 1: [VAL][TRN][TRN][TRN][TRN] → score 1
Round 2: [TRN][VAL][TRN][TRN][TRN] → score 2
Round 3: [TRN][TRN][VAL][TRN][TRN] → score 3
Round 4: [TRN][TRN][TRN][VAL][TRN] → score 4
Round 5: [TRN][TRN][TRN][TRN][VAL] → score 5
Final = average of 5 scores (also look at std: small std = stable model)Every data point gets to be validation exactly once.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv = 5, scoring = "neg_mean_squared_error")
mse_scores = - scores
# sklearn returns NEGATIVE MSE (higher = better convention)
print(mse_scores.mean(), mse_scores.std())7.3 Code
Why negative? sklearn always maximizes scores, so error metrics are negated. Just flip the sign.
7.4 Related tools
| Tool | Use |
|---|---|
| StratifiedKFold | Classification: keeps class ratios in each fold |
| GridSearchCV | Tries many hyperparameter values with CV, returns the best |
| RidgeCV , LassoCV | Automatically pick the best alpha |
| Leave-One-Out | k = n; only for tiny datasets |
from sklearn.model_selection import GridSearchCV
grid = GridSearchCV(Ridge(), {"alpha": [0.01, 0.1, 1, 10, 100]}, cv = 5,
scoring = "neg_mean_squared_error")
grid.fit(X_train, y_train)
print(grid.best_params_)Worked Problem · Summarize 5 Folds
Problem. Five-fold cross-validation gives accuracies [0.80, 0.84, 0.78, 0.82, 0.86]. Report the mean and the spread.
- Mean = (0.80 + 0.84 + 0.78 + 0.82 + 0.86) ÷ 5 = 4.10 ÷ 5 = 0.82.
- Gaps from the mean: −0.02, +0.02, −0.04, 0, +0.04. Squared: 0.0004, 0.0004, 0.0016, 0, 0.0016. Their sum is 0.004.
- Variance = 0.004 ÷ 5 = 0.0008, so the standard deviation is √0.0008 ≈ 0.028.
- Report it as 0.82 ± 0.03.
import numpy as np
s = np.array([0.80, 0.84, 0.78, 0.82, 0.86])
print(round(s.mean(), 3), round(s.std(), 3))0.82 0.028Answer. 0.82 ± 0.028. A small spread means the score does not depend much on which rows landed in the test fold.
Your Turn · See The Folds
Problem. With 6 samples and KFold(n_splits=3), which indexes are in the test part of each fold?
Try it on paper or in a notebook first. Then check the solution.
Solution — Step By Step
- 3 folds over 6 samples means 2 samples per fold.
- Without shuffling, folds take consecutive blocks: [0, 1], then [2, 3], then [4, 5].
- Each sample is in the test part exactly once.
import numpy as np
from sklearn.model_selection import KFold
for tr, te in KFold(n_splits=3).split(np.arange(6)):
print(te.tolist())[0, 1]
[2, 3]
[4, 5]Answer. [0, 1], [2, 3], [4, 5]. Every sample is tested exactly once.
8. ★ ASSIGNMENT — Full Walkthrough
import numpy as np, matplotlib.pyplot as plt
np.random.seed(0)
X = np.sort(np.random.rand(60, 1) * 6 - 3, axis = 0) # values from -3 to 3
y = 0.5 * X ** 3 - X ** 2 + 2 * X + np.random.randn(60, 1) * 3 # cubic curve + noise
y = y.ravel()Step 1 — Make curved data
from sklearn.model_selection import train_test_split
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size = 0.3,
random_state = 1)Step 2 — Split
from sklearn.metrics import mean_squared_error
train_err, val_err = [], []
for d in range(1, 16):
m = make_pipeline(PolynomialFeatures(d), StandardScaler(), LinearRegression())
m.fit(X_train, y_train)
train_err.append(mean_squared_error(y_train, m.predict(X_train)))
val_err.append(mean_squared_error(y_val, m.predict(X_val)))Step 3 — Train degrees 1 to 15, record errors
plt.plot(range(1,16), train_err, "o-", label = "train")
plt.plot(range(1,16), val_err, "s-", label = "validation")
plt.yscale("log") ; plt.xlabel("Polynomial degree") ; plt.ylabel("MSE") ; \
plt.legend() ; plt.show()Step 4 — Plot How to read your plot:
- Train error keeps going down as degree increases (more flexible = memorizes more)
- Validation error goes down, hits a minimum (best degree, ~3), then goes up (overfitting)
- Gap widening between the curves = overfitting
for name, reg in [("Linear", LinearRegression()), ("Ridge", Ridge(alpha = 1)),
("Lasso", Lasso(alpha = 0.1,
max_iter = 50000))]:
m = make_pipeline(PolynomialFeatures(15), StandardScaler(), reg).fit(X_train,
y_train)
print(name, mean_squared_error(y_val, m.predict(X_val)))Step 5 — Add Ridge and Lasso at high degree Expected: plain linear at degree 15 is bad; Ridge/Lasso are much better — regularization tamed the model.
from sklearn.model_selection import cross_val_score
results = {}
for name, reg in [("Deg3 Linear", make_pipeline(PolynomialFeatures(3),
StandardScaler(),
LinearRegression())),
("Deg15 Linear", make_pipeline(PolynomialFeatures(15),
StandardScaler(),
LinearRegression())),
("Deg15 Ridge", make_pipeline(PolynomialFeatures(15),
StandardScaler(),
Ridge(alpha = 1))),
("Deg15 Lasso", make_pipeline(PolynomialFeatures(15),
StandardScaler(),
Lasso(alpha = 0.1, max_iter = 50000)))]:
s = - cross_val_score(reg, X, y, cv = 5, scoring = "neg_mean_squared_error")
results[name] = (s.mean(), s.std())
print(results)Step 6 — 5-fold CV comparison Put results in a table in the README and write 3–4 lines of conclusions.
9. Summary Decision Guide
Training error high? → UNDERFIT → bigger model / more features / weaker
regularization
Train low, val high? → OVERFIT → more data / simpler model / Ridge-Lasso / early
stop
Both low and close? → Good fit → keep the model, then confirm with CV
Unsure if score is reliable? → use Cross-Validation
Final number to report → TEST set, used once10. Common Mistakes
| Mistake | Why bad |
|---|---|
| Tuning on the test set | Over-optimistic result |
| Scaling before splitting | Data leakage |
| Not scaling before Ridge/Lasso | Unfair penalty |
| Judging by training score | Doesn’t show generalization |
| Very large alpha | Model underfits |
11. Practice Questions
- Train error 2%, test error 30%. Diagnosis? → Overfitting
- Train error 25%, test error 27%. Diagnosis? → Underfitting (high bias)
- Ridge vs Lasso — what’s the main difference?
- Why keep a separate test set beyond validation?
- Explain 5-fold CV in your own words.
- Why must we
fitthe scaler on training data only? - What happens to a model when alpha → ∞?
12. Quick Summary
- Goal = generalize to new data
- Underfit: too simple. Overfit: memorized noise
- Bias = too simple; Variance = too sensitive
- Split: train / validation / test; avoid leakage
- Regularization = penalize large weights (Ridge shrinks, Lasso zeroes out)
- Cross-validation = rotate validation folds for reliable estimates
Next: Day 6 — Metrics, Decision Trees, Boosting
⚡ Quick Recap — Day 5
3.1 Underfitting vs Overfitting
Underfitting Good fit Overfitting
Model Too simple Train error High Test error Just right Low Low Too complex Very low High High Analogy Didn’t study Understood concepts Memorized answers
Overfitting = model memorizes noise in training data → fails on new data.
3.2 Bias vs Variance
- Bias = error from wrong/too-simple assumptions (underfit).
- Variance = error from being too sensitive to training data (overfit).
- Tradeoff: More complexity → ↓ bias, ↑ variance. Goal: sweet spot.
Total Error = Bias² + Variance + Irreducible noise
Fixes
- High bias → more features, complex model, less regularization
- High variance → more data, simpler model, regularization, dropout, early stopping
3.3 Train / Validation / Test Split
| Set | Purpose | Typical % |
|---|---|---|
| Train | Learn parameters | 60–70 |
| Validation | Tune hyperparameters, choose model | 15–20 |
| Test | Final, honest score. Touch only once. | 15–20 |
from sklearn.model_selection import train_test_split
X_tr, X_tmp, y_tr, y_tmp = train_test_split(X, y, test_size = 0.3, random_state =
42)
X_val, X_te, y_val, y_te = train_test_split(X_tmp, y_tmp, test_size = 0.5,
random_state = 42)Data leakage: never fit scalers/encoders on the full data — fit on train only, then transform val/test.
3.4 Polynomial Regression
Fit curves by adding powers of x: ŷ = w₁x + w₂x² + w₃x³ + …
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression
model = make_pipeline(PolynomialFeatures(degree = 5), LinearRegression())- Degree 1 → underfit (straight line)
- Degree ~3–4 → good
- Degree 15 → wild wiggles, overfit
3.5 Regularization
Idea: Penalize large weights → forces simpler models.
Ridge (L2) Lasso (L1)
Loss MSE + α·Σw² Effect Shrinks weights MSE + α·Σ|w| Sets some weights exactly 0 toward 0 Use Many correlated features Feature selection Lasso(alpha=0.1) Sklearn Ridge(alpha=1)
- alpha (λ) = strength. Bigger → simpler model (more bias).
- ElasticNet = L1 + L2 mix.
- Always scale features before Ridge/Lasso.
3.6 Cross-Validation
Problem: One train/val split can be lucky/unlucky. Solution: k-fold CV — split data into k parts; train on k-1, validate on the remaining; rotate k times; average the scores.
Fold 1: [VAL][ T ][ T ][ T ][ T ]
Fold 2: [ T ][VAL][ T ][ T ][ T ]
...from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv = 5, scoring = "neg_mean_squared_error")
print( - scores.mean(), scores.std())- Use StratifiedKFold for classification (keeps class ratio).
- Use GridSearchCV to tune hyperparameters with CV.
Day 5 Assignment Recipe
- Loop degree 1→15, record train & validation MSE.
- Plot both curves — train keeps falling, validation is U-shaped. The bottom of the U = best degree.
- Repeat with Ridge and Lasso at high degree → validation error stays low.
- 5-fold CV to compare all models fairly.
Beginner checkpoint — Day 5 ■ Explain the day’s main idea without using the textbook wording. ■ Write one tiny example from memory. ■ Identify one common failure mode. ■ Explain how today’s topic connects to the next day. ■ Write one question you still cannot answer.
Move on when you can… ☐ tell overfitting from underfitting using train and validation numbers ☐ make a train / validation / test split ☐ explain what Ridge does to the weights ☐ report cross-validation as mean ± spread ☐ say why the test set is used only once If you cannot tick most of these, repeat the worked problems for this day. Taking an extra day is fine; it is not falling behind.