Tree-Based Algorithms

Lesson 2

Overfitting, Bias-Variance & Cross-Validation

Why a deep tree memorizes noise, how to split its error into bias, variance, and noise, and how to measure generalization honestly

A single decision tree, grown deep enough, will classify every training point perfectly. It can also be useless on new data. That gap between "perfect on what it has seen" and "poor on what it has not" is the single most important idea in machine learning, and it is exactly the weakness that random forests and boosting exist to fix. This lesson gives you the vocabulary and the tools to name that gap precisely: bias, variance, irreducible noise, and the honest measuring stick of cross-validation.

We build it on three pillars at once. The math decomposes expected test error into three terms you can reason about. The intuition comes from a dartboard and an animated k-fold split. And the code lets you watch the U-shaped error curve appear, and lets you cross-validate a real tree in your browser.

Underfitting versus overfitting

Every supervised model sits somewhere on a spectrum of capacity, its freedom to bend to the data.

  • Underfitting (too little capacity). The model is too simple to capture the real pattern. A depth-1 stump splitting a spiral, or a straight line through a curve. Training error is high, and test error is high too, because there is genuine structure the model never learned.
  • Overfitting (too much capacity). The model is flexible enough to trace not just the signal but the noise in the training sample. A depth-20 tree that carves out a private rectangle around every point. Training error falls toward zero, but test error climbs, because the memorized quirks do not repeat on new data.

The tell is the gap between training error and validation error. When both are high, you are underfitting. When training error is low but validation error is much higher, you are overfitting. Train the network below and watch the two curves: as capacity or training time grows, the red validation curve stops tracking the green training curve and turns back upward. That divergence is overfitting, made visible.

Capacity (width)8
Training set size40
Label noise0.6
Weight decay (L2)0.000
Dropout0.00
train lossvalidation lossearly-stop
Epoch 0Train Val Gap
fitted boundary (train pts)
Press Train — watch the red (val) curve.

Crank the capacity slider up, or shrink the training set size, and the gap widens. Add data, weight decay, dropout, or early stopping, and it narrows. Trees have their own version of every one of those knobs (max_depth, min_samples_leaf, pruning), but the phenomenon is identical. To fix it deliberately rather than by trial and error, we need to name where the error comes from.

The bias-variance decomposition

Fix a single input xx. Assume the true label is a deterministic function plus noise:

y=f(x)+ε,E[ε]=0,Var(ε)=σ2y = f(x) + \varepsilon, \qquad \mathbb{E}[\varepsilon] = 0, \qquad \operatorname{Var}(\varepsilon) = \sigma^2

Here f(x)f(x) is the best possible answer and ε\varepsilon is irreducible measurement noise: two houses with identical features still sell for slightly different prices. Now let f^\hat f be a model trained on a random training set D\mathcal{D}. Because D\mathcal{D} is random, f^(x)\hat f(x) is itself a random quantity: retrain on a fresh sample and the prediction shifts. Taking expectations over both the noise and the draw of the training set, the expected squared error at xx splits into three clean pieces:

E[(yf^(x))2]=(E[f^(x)]f(x))2Bias2[f^]+E[(f^(x)E[f^(x)])2]Var[f^]+σ2noise\mathbb{E}\big[(y - \hat f(x))^2\big] = \underbrace{\big(\mathbb{E}[\hat f(x)] - f(x)\big)^2}_{\text{Bias}^2[\hat f]} + \underbrace{\mathbb{E}\big[(\hat f(x) - \mathbb{E}[\hat f(x)])^2\big]}_{\operatorname{Var}[\hat f]} + \underbrace{\sigma^2}_{\text{noise}}

In the compact form you should memorize:

E[(yf^(x))2]=Bias2[f^]+Var[f^]+σ2\mathbb{E}\big[(y - \hat f(x))^2\big] = \text{Bias}^2[\hat f] + \operatorname{Var}[\hat f] + \sigma^2

Read each term:

  • Bias² is how far the average model (averaged over all possible training sets) lands from the truth f(x)f(x). High bias means the model class is systematically wrong no matter which sample you train on. A linear model on a curve, or a shallow stump: they miss the target the same way every time.
  • Variance is how much a single trained model bounces around that average as the training set changes. High variance means the model is chasing the sample. A deep unpruned tree: two trees grown on two samples from the same source can look wildly different.
  • σ² (irreducible noise) is the floor. No model, however perfect, can beat it, because the labels themselves carry randomness. It is why chasing zero test error is a mistake.

Only the first two terms are under your control. This is why we say error is a trade-off: simplifying a model (shallower tree, more pruning) lowers variance but usually raises bias; enriching it (deeper tree) lowers bias but raises variance. The art is finding the capacity where their sum is smallest.

Seeing it on a dartboard

Picture the bullseye as the truth f(x)f(x), and each dart as one model trained on one random dataset. Bias is how far the cluster of darts sits from the bullseye. Variance is how spread out the darts are. Toggle the two knobs below and throw again.

🎯 Bias–Variance Dartboard

Each dart is a model trained on a fresh sample. Variance = how scattered the shots are; bias = how far the cluster’s centre sits from the bullseye.

Bias (offset)
Variance (spread)
a dart = one model’s prediction
cluster centre → bias arrow
dashed ring → variance

Expected error ≈ bias² + variance + irreducible noise. Simple models miss the centre (high bias); flexible ones jump around with the data (high variance). The sweet spot minimises the sum.

Bias²
0.013
Variance
0.107
Error
0.120
bias² + variance = reducible error (+ noise σ²)

Low bias, high variance. Centred on average, but each fit lands somewhere different — averaging many models (bagging) tames the spread. The overfit tree.

The four corners map onto four kinds of model:

  • Low bias, low variance is the goal: a tight cluster on the bullseye. Rare, and usually the result of the right capacity plus plenty of data.
  • Low bias, high variance is the lone deep tree: centered on the truth on average, but any single tree scatters far. This is precisely the case bagging (random forests) attacks, by averaging many high-variance trees to shrink the spread.
  • High bias, low variance is the stump: consistent, but consistently off. Boosting attacks this by adding weak learners to reduce bias.
  • High bias, high variance is the worst case: wrong and unstable.

Hold onto that second corner. The whole reason random forests work is that a deep tree is a low-bias, high-variance machine, and variance is the one component you can cancel by averaging.

The U-shaped test-error curve

Now sweep capacity from low to high and plot the error. Training error only ever falls: more capacity can always fit the training set better. Test (validation) error tells a different story. It starts high (underfit, bias-dominated), falls to a minimum, then rises again (overfit, variance-dominated). That is the famous U.

The bottom of the U is the sweet spot, the capacity where bias² + variance is minimized. Everything to the left is too simple; everything to the right is memorizing noise. Below, we make the U appear for real: validation_curve trains a DecisionTreeClassifier at every max_depth from 1 to 17, with 5-fold cross-validation at each depth, and plots training error against validation error. Press Run.

Python
First run loads the Python runtime (~10 MB) — takes ~5–10 seconds. Subsequent runs are instant.

The left arm of the U is high bias, the right arm is high variance, and the dashed line marks the depth where their sum bottoms out. Try widening flip_y to 0.2 and watch the right arm climb harder: more label noise means more for a deep tree to overfit.

k-fold cross-validation

The U-curve above needed a validation error at every depth. Where did it come from? Not from the test set, which we must never touch until the very end, and not from a single train/validation split either, because one split is a coin flip: get an easy validation slice and you look great, get a hard one and you look terrible. The estimate wobbles.

k-fold cross-validation removes the luck. Split the training data into kk equal folds. Then, kk times over, hold out one fold as validation, train on the other k1k-1, and score on the held-out fold. Every point is validated exactly once, and every point is trained on k1k-1 times. Average the kk scores:

CV^(k)=1ki=1kerr(foldi)\widehat{\text{CV}}_{(k)} = \frac{1}{k} \sum_{i=1}^{k} \operatorname{err}(\text{fold}_i)

where err(foldi)\operatorname{err}(\text{fold}_i) is the error of the model trained on every fold except ii, evaluated on fold ii. The mean is a low-variance estimate of generalization error, and the standard deviation across folds tells you how much you can trust it. Step through the mechanics below: each fold takes its turn in red as the held-out validation set, and the scores accumulate into the CV estimate on the right.

🔁 k-Fold Cross-Validation

Every block is validated exactly once. The test set stays locked — CV squeezes k honest held-out estimates out of the training pool alone.

Folds (k)4
validation fold (held out)
training folds
test set · locked 🔒

24 labelled blocks, split into k contiguous folds. Each round holds one fold out for validation and trains on the other k−1. Average the k scores → a low-variance estimate that never peeks at the test set.

Press Run — fold 1 will be held out first.
Fold 1
Fold 2
Fold 3
Fold 4
Test · locked 🔒
Per-fold validation score
Fold 1
Fold 2
Fold 3
Fold 4
CV estimate
run to fill in

Notice two things. First, the locked test set on the side is never involved: cross-validation squeezes an honest estimate out of the training pool alone, which is what lets you compare models and tune hyperparameters without ever peeking at the final exam. Second, larger kk means each model trains on more data (so less bias in the estimate) but costs kk full fits and gives correlated folds. k=5k = 5 and k=10k = 10 are the standard compromises; k=nk = n is leave-one-out, maximally thorough and maximally expensive.

Now run it for real. Below, cross_val_score with an explicit KFold object evaluates a depth-5 tree across 5 shuffled folds and reports the mean plus or minus the standard deviation, then contrasts that with the jumpy accuracy of eight different single splits.

Python
First run loads the Python runtime (~10 MB) — takes ~5–10 seconds. Subsequent runs are instant.

The single-split accuracies swing noticeably from seed to seed. The cross-validated estimate averages that luck away, which is why CV, not one split, is the honest way to choose max_depth, compare a tree against a forest, or report a number you would stake a decision on.

Test yourself

Check your understanding

1 / 4

A model has very low training error but much higher validation error. Which term of the decomposition is dominating?

Interview readiness

Q1MathGoogleMetaAmazon

Derive the bias-variance decomposition of expected squared error and explain what each term means.

Q2ConceptualAppleMicrosoftNetflix

You cross-validate two models: model A scores 0.90 ± 0.01 and model B scores 0.91 ± 0.06 (mean ± std over 5 folds). Which do you ship, and why?

Test your understanding

Prof is ready

Prof will ask you questions about Overfitting, the bias-variance decomposition, and k-fold cross-validation — not explain it. You'll be surprised what you don't know until you have to say it.

Finished this lesson?

Read through the lesson first (0/20s).