◈ quant roadmapPart 3 · Ch 23/45
Quantitative Finance — the Mathematics of Markets · chapter 23

23Trees, Ensembles & Boosting

Every model in this course so far has answered with a formula in x. You hand it a row, it multiplies and adds, and a number comes out. A tree does something else entirely. It cuts the input space into boxes, finds the box your row landed in, and reports the average of the training labels inside it. That sounds almost too simple to be interesting. It is the first model here that discovers where the structure sits instead of being told, and that is why it can hold a regime, a threshold and an interaction that nobody named in advance. Grown all the way down it is nearly unbiased and violently unstable. So it sits at the far right end of Chapter 22's U-curve, which means there are exactly two ways to make it useful. Average many deliberately overfit trees and the variance falls. Or start deliberately underfit and keep adding small trees fitted to what is still left over. Those are Chapter 22's two terms, attacked from opposite sides. And the second one hides the sentence this whole chapter turns on, because the leftover you keep fitting is not a heuristic at all. It is a gradient.

Look at what this page stands on, because nearly all of it is already yours. Chapter 22 gave us the decomposition Err(x₀) = σ² + bias² + variance, and the spray picture that makes it concrete. It gave us the purchase: spend a little bias to buy back a lot of variance. It closed by naming two doors and labelling them both: bagging attacks the variance term, and boosting attacks the bias term. Chapter 21 gave us the U-curve, the hyper-parameter chosen on validation folds, and walk-forward cross-validation with a purge. Chapter 20 gave us RSS, the residual and the bootstrap, and Chapter 12 gave us the Bernoulli variance p(1−p). Chapter 11 gave us the expand-a-square move, Chapter 18 gave us covariance, and Chapter 8 gave us gradient descent. Chapter 2 gave us the limit that turns (1 − 1/n)ⁿ into 1/e, and every one of those gets spent somewhere on this page.

Ridge shrinks a slope; it never bends a line. Drag λ — then drop in one cut at VIX 30.
next month's return, % +4 0 −4 15 30 45 VIX that month one cut, at 30 test error vs λ one cut λ=0 λ huge λ = 0 error now 3.19 best line 3.19 one cut Ch 20 Ch 21 Ch 22 Ch 23 fit a line measured the U bias vs variance which model? you are here
λ = 0 — the best line there is.
What you're looking at — one feature (VIX), one target (next month's return)
calm months, VIX under 30 — they pay about +2.2%
stressed months, VIX 30 and up — about −2.3%
the ridge line, and its test error as λ grows
one cut at 30, two flat levels — error 1.71
λ (lambda) is ridge's shrinkage dial from Ch 22. Turning it up only pivots the line toward the overall mean — at every λ the shape is still one straight line, and the error only climbs. The green dashed level sits under the whole sweep: the regime needs a line that bends at a place the data has to reveal. No λ invents a threshold. That is the door into Ch 23.
Fig. 1. Every month is a dot: how frightened the market was (VIX, across) against what the next month actually paid (%, up). Drag λ and ridge does the only thing it can — it pivots the gold line toward the flat overall mean, and the honest test error climbs the whole way. Then add one cut at VIX 30: two flat levels, and an error that sits below every single point on the λ sweep. Shrinkage moves a line; it cannot make one bend.

So the seam is short, and it has a shape worth noticing. Both of Chapter 22's doors are things you do to a model you already have. That makes the first honest question a different one: which model? And the honest first observation is that everything we have built so far is a weighted sum of the features. Before we can attack either term, we have to ask what lies outside that class.

01A shrunken line is still a line

Chapter 22 handed us a dial and a theory of what the dial buys. It did not hand us a bigger world to dial inside. Ridge and lasso shrink the coefficients of a weighted sum of the features. A shrunken weighted sum is still a weighted sum. Turn λ as far as you like and the fitted surface stays a plane, just a flatter one.

Now ask what a market actually does, starting from a sentence you could hear on any trading desk. When VIX is below 20, momentum continues. Above 30, it reverses. That is not a plane, and no amount of λ will bend one into it, because it is a regime, which means the rule itself changes past a threshold.

Try to write it linearly and you get stuck twice, in two different places. You would need an interaction column, something like momentum × 1{VIX > 30}. So first you must already know that momentum and VIX interact, and then you must already know the threshold sits at 30. Both of those have to be true before you fit anything at all.

"Just add interaction terms" fails twice — once at the till, once at the shape. Drag both sliders.
1 · the counting failure features you own p = 10 p linear terms 10 C(p,2) pairs 45 C(p,3) triples 120 candidates to search 175 2,000 rows of history fewer candidates than rows Ch 21 toll · luck bonus 3.2s and still — look right → 2 · the shape failure one smooth curve degree = 3 VIX 30 10 VIX → 50 residuals (data − curve) below 30 +0.00 above 30 +0.00 turning pts 2 step 0.00/1.50
Drag both — two failures.
What you're looking at — the same bad idea, priced on the left and drawn on the right
candidate terms: p linear, C(p,2) pairs, C(p,3) triples. C(p,2) reads “p choose 2” — how many distinct pairs you can build from p features.
left: M, everything you must search. Right: the truth — a flat level that jumps 1.50 at VIX 30.
the polynomial fit — smooth by construction, so it can bend but never break.
the threshold at 30, and the signed lump of error the curve can never clear away.
Fig. 2. Two panels, because two different things go wrong. Left: at p = 50 features you are already searching 50 linear terms, 1,225 pairs and 19,600 triples — 20,875 candidates against 2,000 rows of history, and Ch 21 already priced that search as roughly a 4.5σ luck bonus on whatever wins. Right: pay all of it and the shape is still wrong. Drag the degree to 15 and the smooth curve buys wiggle everywhere, yet the step it captures across VIX 30 stays a fraction of the real 1.50 — and a signed lump of error sits stubbornly on each side of the break. A smooth basis cannot make a corner; it can only crowd around one.

Basis expansion is the standard answer to the first problem, and it genuinely works. Add squared terms, add every pairwise product, and let ridge sort out which ones matter. Count what that costs before you agree to it. With 50 features there are 1,225 pairwise products and 19,600 triples. You also have to decide in advance how far up to go, and Chapter 21 already priced that decision for us. A bigger candidate set is a bigger search, and the optimism you are charged is set by the size of the search.

But the cost is the easier half of the objection, and almost everyone stops there. Here is the sharper failure: a polynomial can bend, but it cannot put a corner at a location the data has to tell you about. Crank the degree as high as you like and you get a smooth curve that wiggles near 30 without ever committing to a break there. The regime is a discontinuity in the rule, and a smooth basis is structurally the wrong object for it.

So the demand this chapter has to meet is specific, and it is worth stating before we build anything. Not "a non-linear model." We need a model that discovers which variables interact, and where the thresholds sit, from the data, without being handed a list.

02Condition on a box, not on a point

Here is the way I have come to think about what a tree really is, and it is not the flowchart. Chapter 22 told us the best prediction that could possibly exist is f(x₀) = E[y|x₀], the conditional mean. Chapter 10 told us what conditioning does. It shrinks the world down to the conditioning event and re-measures inside it.

So go and actually compute E[y | VIX = 24.37]. You cannot, because no other row in your dataset has VIX equal to 24.37. You have a sample of one, or of none, and an average over one row is not an estimate of anything.

That leaves exactly one move available. Widen the condition until rows appear. Condition on a box around that x, say VIX between 20 and 30 with momentum above zero, and average the y values of the rows that landed inside. The width of that box is not a detail. Make it too narrow and you are averaging two rows, so the answer jumps around wildly. Make it too wide and you have swallowed both regimes, so the answer is a compromise that is wrong everywhere. That is Chapter 22's purchase again, wearing a new costume.

No row in history has VIX exactly 24.37 — so a tree widens a box around it. Before you drag: is the tightest box the best answer?
rows in box 0 box mean ŷ = — calm regime panic regime E[y|x₀] x₀ = 24.37 0 1 2 3 12 20 28 36 VIX → ↑ next-day move % ERROR vs w best 0 w 9.5 wobble — miss — total —
HALF-WIDTH w ±1.50
◀ drag the box wider ▶
THE NOISE tap ▸
shuffles 0spread —
0 rows · no answer
Drag w, then hit reshuffle a few times and watch the faint ghost lines.
What you're looking at — a tree cannot condition on a point, so it conditions on a BOX, and the box's width is the whole bias–variance dial
the rows — every past day: its VIX on the strip below, its next-day move above. None sits at 24.37.
the box & its mean — the tree's answer is just the average of whatever fell inside. Faint gold lines = the last 5 reshuffles.
E[y|x₀] — the true conditional mean from Ch 22: the best number that can exist. Dashed curve = the truth at every VIX.
the miss — how far the box's answer sits from that truth. Narrow box: wobbles. Wide box: parked, and wrong.
Fig. 3. Chapter 22 said the best possible prediction at a point is the conditional mean E[y|x₀], and Chapter 10 said conditioning means re-measuring inside an event. Here is the problem nobody warns you about: VIX = 24.37 is not an event. No day in history had exactly that value, so there is nothing to re-measure inside, and the honest conditional mean is a 0/0. Every tree ever grown solves this the same crude way — it widens the point into a box and averages whatever falls in. Drag w down to a hair: the box holds one row, so the tree's answer is that row's noise, and reshuffle throws it a mile — that is pure Ch 22 variance. Drag w out to swallow: the box now spans the calm days and the panic days, so it answers with a compromise that is wrong on both sides and barely twitches when you reshuffle — pure bias. The white U on the right is the same curve you met in Ch 22, except now you know what its horizontal axis physically is: how wide the box is. Hold that, because the rest of the chapter is two opposite ways to cheat it — bagging grows deep, narrow-boxed trees and averages the wobble away, while boosting grows shallow, wide-boxed trees and hammers the leftover bias down one residual at a time.

That is a tree, and everything else in this chapter is bookkeeping about where to put the boxes.

The bookkeeping has a name: recursive binary splitting. Pick one feature and one threshold, cut the space in two, then repeat the same move inside each half. Because every cut uses a single feature, the boxes it makes are axis-aligned rectangles. And the answer inside a box is the mean, which we can derive in one line rather than assume. The constant c minimising Σ(y − c)² has derivative −2Σ(y − c), and setting that to zero gives c = ȳ. Chapter 20's own loss picks the mean for us.

Place the cuts yourself. Every cut is a node in the flowchart and a wall in the plane — one object, drawn twice.
THE FLOWCHART THE PLANE THE LEAF ALL 50 a leaf holds ONE number — its mean 0 x₁ → 1 x₂ ↑ the staircase truth the whole plane 3186 ÷ 64 = 49.8 WHY THE MEAN? min Σ(y−c)² ⇒ −2Σ(y−c)=0 ⇒ c = the mean MIS-COVERED % by number of cuts 0 1 2 3 No cuts yet: the whole plane is one leaf holding one number.
1 · GROW THE TREEclick →
2 · MOVE THIS CUTno cuts yet
add a cut to place a wall
3 · THE TRUTHflip it
mis-covered 0.0% of the plane
One box. One number: the mean of every row. Add a cut and the flowchart grows.
What you’re looking at — a flowchart and a staircase that are the same object
One cut = one node = one wall. Every leaf of the tree is a box in the plane; the deeper the shade, the bigger the one number it holds. Left branch always means yes.
The rows. Dot size = that row’s y. A box’s number is their mean — because the c that minimises Σ(y−c)² has −2Σ(y−c)=0, i.e. c = ȳ.
The staircase. Read the tree along the dashed slice and it is a step function; the dashed grey line is the truth it is trying to be.
Mis-covered area — where the tree’s number is off by more than 12. On BOXES you can drive it to 0. On DIAGONAL it stalls: boxes cannot hold a slope.
Fig. 4. With no cuts at all the plane is one leaf and the tree is one chip holding one number. That number is not a convention: ask which constant c makes Σ(y−c)² smallest, differentiate once, set it to zero — −2Σ(y−c) = 0, so c = ȳ. One derivative decides every leaf in the chapter. Now ADD CUT 1 and drag. Two things move, and they are the same thing: a node asking “is x₁ below this?”, and a wall in the plane with a box either side. Cut 2 splits only the left box — which is exactly why a tree is a tree and not a grid. Hover a leaf and its rows light in the plane, its chip lights in the flowchart, and the division happens on screen. On BOXES the walls really are at 0.55, 0.60, 0.35, so a patient reader drives the mis-covered area to 0.0%. Then flip to DIAGONAL: first cut helps, second less, third barely, and the bars stop falling. A slope is not made of boxes — which is why one tree is never enough.

Now draw the same fitted tree twice and watch them turn out to be one object. On one side sits the flowchart everyone shows, and on the other sits the surface it implies, which is a staircase: flat on each box, jumping at each cut. The flowchart is the staircase written as instructions. That staircase is the hypothesis class, and it is worth saying exactly what that means. A tree can be any piecewise-constant function on axis-aligned boxes, and it can be nothing else.

Which tells you the honest cost up front. A boundary that runs diagonally through feature space has to be approximated by a staircase of many cuts, and every cut spends data. Trees are cheap at thresholds and expensive at slopes. That is the exact opposite of the linear model, which is precisely why they are worth having.

03Where to cut, and the dullest algorithm here

So where do you cut? The answer is the least sophisticated thing in this chapter, and it is worth saying plainly, because most readers assume something clever is going on. Nothing clever is going on.

Sort each feature, then walk down every possible threshold between consecutive distinct values. For each candidate cut, split the rows into left and right, take each side's mean, and add up the squared deviations inside each side. That total is RSS, Chapter 20's own quantity, computed on this node's rows. The score of the cut is the parent's RSS minus the two children's RSS combined, which in plain words is how much sum-of-squares this cut removed.

Run the split rule by hand: sort, cut, score, sweep — and the "best question" turns out to be the lowest point of a curve you drew yourself.
Sort the twelve rows by x₁. Every midpoint between two neighbours is a candidate — eleven of them. Nothing else is ever tried.
beat
11 candidates · none scored
What you're looking at — one node, and the only arithmetic a tree ever does
Left side of the cut: its rows, its flat prediction (their mean), and one shaded square per row — each square's area is that row's squared miss.
Right side, scored the identical way. Stack the two totals and you have the bar below: RSS(t) = SSleft + SSright — Chapter 20's residual sum of squares, nothing new.
The curve is that bar redrawn once per candidate, on the same axis. The green dot is simply its lowest point — that, and only that, is "the best question".
Fig. 5. The split rule, run by hand — and it turns out to contain no cleverness at all. Twelve rows sit in one node with a target y and two candidate features. Beat 1 sorts the rows by x₁ and marks every midpoint between consecutive distinct values: those eleven ticks are the entire search space, because any cut between the same two neighbours splits the rows identically. Beat 2 takes one candidate and lets each side predict a single flat number — the mean of its own rows — drawn as the blue and violet segments. Beat 3 scores that choice the only way this book ever scores a fit: each row's gap to its side's mean is squared, drawn here as a literal square, and the areas are summed into a stacked bar. That is Chapter 20's RSS, computed on this node's rows, with the flat mean playing the part of the fitted line. Beat 4 is the one that matters: park the cut where you believe the score bottoms out, then paint the curve, and every candidate you did not try appears at once as one dot each. The "best split" is nothing more mysterious than the lowest point of that curve — here RSS 4.70 at x₁ ≤ 4.35, against 55.13 for no split at all. Beat 5 repeats the identical scan on a second feature and plots it on the same axis, so choosing a feature is just taking the deeper valley (16.97 cannot beat 4.70). Beat 6 drops into the left child and draws the same picture at half scale on six rows, where the same procedure finds RSS 4.00 → 0.16. Greedy, exhaustive, one node at a time — and no new mathematics has entered the chapter.

Take the winner, then do the identical thing again inside the left child and inside the right child, and keep going until a stopping rule fires. The usual rules are a minimum number of rows in a leaf, a maximum depth, and a minimum gain worth having. That is the entire training algorithm. Once the columns are sorted it costs O(n·p) per node, which is why a tree on a million rows is not a heroic computation.

Two honest limits belong right here, not in a footnote at the end.

The first is that the scan is greedy. The split at the root is chosen to look best right now, with no idea at all what it makes possible two levels down. So the tree you get is not the best tree, and finding the best tree is NP-hard, which is why nobody looks for it.

Six points, one feature. Take the best first cut and you land a worse tree — press NEXT, then RACE the two roots.
THE DATA · 6 points, one feature x, target y total RSS 35.33 6 0 drop: A · BIGGEST FIRST DROP B · RUNNER-UP ROOT
BEAT 1 OF 5 · SCORE THE CUTS
FIRST CUT · RSS DROP bigger = greedier
A · cut x ≤ 5
B · cut x ≤ 2
FINISHED TREE · RSS LEFT
A · greedy
B · runner-up
press NEXT · five short beats
What you're looking at — the same six points, cut two ways. RSS (residual sum of squares) adds up (point − its box's average)²: it is the score a tree drives down, and 0 means every point sits exactly on its step.
the six points — one feature x (1…6), one target y. A cut "x ≤ k" is the only kind of question a tree may ask.
route A — greedy. The cut that drops RSS most right now (6.53) hands its right child a single point: that whole branch has nothing left to split, and the tree ends at 18.00.
route B — runner-up. A worse first drop (5.33) leaves both children with real work, and the finished tree fits every point: 0.00.
the aha: greedy's cut x ≤ 5 is in the better tree too — one level down, where it is free. Greedy isn't wrong, it's early. And searching every tree is NP-hard, so we keep greedy — then fix it by averaging many of them (bagging) or stacking them on each other's mistakes (boosting).
Fig. 6. Six points, one feature. The greedy root is the cut that drops RSS most immediately (6.53) — and it spends the root isolating a single point, so one whole branch has nothing left to split and the finished two-level tree still misses by 18.00. The runner-up root drops less at once (5.33), leaves both children real work, and finishes at 0.00. The winning cut is not wrong; it is simply one level too early. Searching every possible tree is NP-hard, so every practical algorithm stays greedy — which is exactly why the rest of this chapter improves trees from the outside: average many of them to kill variance (bagging, random forests), or stack them on one another's residuals to kill bias (boosting).

That limit matters more than it sounds, and it pays off later in the chapter. A reader who believes the tree is optimal cannot understand why deliberately handicapping the split choice could ever help. You do not improve an optimal procedure by tying its hands. You can certainly improve a greedy one, and that is exactly what a random forest will do.

The second limit is about the stopping rules, and it is a relabelling rather than a caveat. Those rules are not safety features. Minimum leaf size, maximum depth and minimum gain are Chapter 21's complexity knob wearing work clothes, and we will treat them that way shortly.

04What plays the role of variance for a class

Everything so far assumed y is a number. Make it a class instead, up day or down day, default or no default, and the split score has to change. "The mean of the rows in this box" and "their sum of squared deviations" both need a number to work on.

So ask the question that generates the answer rather than reaching for a formula. What plays the role of variance when the label is a class? A node is pure when every row inside shares a label, and messy when the labels are evenly mixed. We want a quantity that is zero at pure and largest at even odds.

Before we write one down, deal with the instinct you almost certainly have, because it is a good instinct and it is wrong. Why not just split on accuracy, so that you get the most rows right? Here is the smallest example that kills that idea. A node holds 800 rows, 400 of each class. Split A gives children of (300, 100) and (100, 300), while split B gives (200, 400) and (200, 0).

Two candidate splits of the same 800 rows. Accuracy scores them identically — but only one hands you a leaf that is finished forever.
the node — 800 rows, 400▲ 400▼ each dot = 10 rows split A 600 / 800 right 300▲ · 100▼ 100▲ · 300▼ says ▲ · 300 ✓ says ▼ · 300 ✓ split B 600 / 800 right 200▲ · 0▼ 200▲ · 400▼ PURE ▲ · 200 ✓ says ▼ · 400 ✓ impurity — how mixed is a node? misclassification gain — 0 .25 .50 0 ½ 1 p = share of ▲ in the node 0.250 drag here to sweep the split parent .500 score hidden — vote first
Same 800 rows, two candidate splits. Commit before any score appears — which one is the better split?
rows classed right misclass. gain pure leaves
commit first — A or B?
What you're looking at — a score that cannot see the one thing that matters
▲ and are the two classes, one dot = 10 rows. A leaf predicts its majority, so the hollow dots are the rows it gets wrong. Sweep the split: their number never leaves 20 (= 200 rows).
Misclassification rate is two straight lines meeting in a kink. A chord across a straight line lies on it — so the gold marker is pinned and the gain never budges, however pure a child gets.
Gini (solid) and entropy (dashed) bulge upward — strictly concave — so the chord falls beneath the curve and the gold drop grows as a child approaches purity: a leaf that is finished forever.
Fig. 7. Predict first. Split A cuts the node into (300▲,100▼) and (100▲,300▼); split B cuts it into (200▲,0▼) and (200▲,400▼). Pick one, then look: both classify exactly 600 of 800 rows correctly — accuracy cannot tell them apart. Yet B's first child is pure, a leaf that is permanently finished, and A's children are still 3:1 mixtures. Sweep the split and count the hollow dots: always twenty, always 200 rows wrong. The reason is on the right — misclassification rate is two straight lines, and a chord across a straight line lies flat on it, so the gold marker is pinned and the gain never moves. Gini and entropy bow upward, so the chord drops beneath the curve and rewards every step toward purity. That is why trees are grown on Gini or entropy, never on accuracy.

Count the rows each split classifies correctly. Split A gets 300 right on the left and 300 on the right, so 600. Split B gets 400 right on the left and 200 on the right, so 600 again. Accuracy is completely indifferent between them. Now look at split B's right child, which is pure: two hundred rows, no ambiguity, permanently finished, and nothing below it will ever need another cut. Accuracy could not see that, because it only counts majorities and neither split flipped a majority anywhere.

The reason lives in the geometry. Plot the misclassification rate against p and you get two straight lines meeting in a corner. Plot a concave impurity and you get a smooth arch. A straight-line criterion is flat exactly where a curved one is still falling, so it cannot reward a split that purifies without yet flipping a decision.

Now write the curved one down, and watch it turn out to be something we already own. Let p be the fraction of class 1 in a node, and draw a row at random from it. That draw is a Bernoulli(p), and Chapter 12 told us its variance is p(1−p). Gini impurity, defined as 1 − Σ p_k², is algebraically Σ p_k(1 − p_k), and for two classes that is exactly 2p(1−p).

Gini looks like a formula from nowhere — drag p and watch it turn into a number you already met in Ch 12.
the node 100 labels in a box p 0.60 · 0.40 one axis, two scores 0.75 0.50 0.25 0 0.240 0.240 p(1−p) = 0.480 0.480 Σ p(1−p) 1 − Σ p²
A box of labels is most mixed at which p? Pick one — the box jumps there and grades you.
how many classes in the box
class-1 var p(1−p)0.240
Σ per-class var0.480
Gini 1 − Σ p²0.480
Gini ÷ class-1 var2.00×
predict: most mixed at which p?
What you're looking at — the same number built twice, so Gini stops being a formula from nowhere
the blue labels are class 1; the bottom slab is their spread, p(1−p) — Ch 12's Bernoulli variance
every other class adds its own slab; the stack is Σ p(1−p), the total spread of the labels
Gini = 1 − Σ p² is that stack, exactly — for two classes, two equal slabs, so Gini = 2p(1−p)
Fig. 8. One hundred labels in a box, and two scores for how mixed they are — drawn on the same axis so you can catch them being the same number. The blue slab is the variance of a single yes/no label from Ch 12: call it 1 if the label is class 1 and 0 otherwise, and its variance is p(1−p). Stack one such slab per class and you get the total spread of the labels. The gold bar is Gini, 1 − Σ p² — the number a classification tree actually minimises — and it never once leaves the top of that stack, because Σ p(1−p) = Σ p − Σ p² = 1 − Σ p². The algebra line rewrites itself as you drag. With two classes the stack is two equal slabs, so the ratio row sits at exactly 2.00× for every p: Gini is twice the Bernoulli variance, and the dashed blue line shows you the halfway mark it never misses. Push p to 0 or 1 and both collapse to zero — that is all "pure" means. Switch to 3 or 4 classes and the stack grows a slab per class while the gold bar keeps matching it. So the classification criterion and the regression criterion were never two ideas: a tree splits to shrink the spread of the labels inside the box, and Gini is just what that spread is called when the labels are categories.

So Gini is not a new invention with an intimidating name. It is the variance of the label, computed the only way a categorical label allows. The classification criterion and the regression criterion are the same object, which is why one algorithm handles both.

You will also meet entropy, written −Σ p_k log p_k, and I want to be straight about where it comes from. It is Shannon's, and Shannon's theory is not something this course has built. So we are not going to pretend the word information has been earned here. We use entropy for the three properties we actually need: zero when pure, maximal at even odds, strictly concave. In practice it and Gini pick the same split almost every time. And "information gain" is just parent impurity minus the weighted average of the children's impurity, which is the identical shape as the RSS reduction we already built.

05The variance is structural, and pruning will not fix it

Put the tree on Chapter 22's axis and read it. Depth is the complexity dial, and at depth 1 you have two boxes and one threshold. By the time every leaf holds a single row, the training error is exactly zero and the staircase passes through every point.

So a fully grown tree sits at the far right end of the U, with variance enormous and bias near zero, because with enough cuts it can approximate essentially any function. But "enormous variance" is a slogan, and slogans do not transfer. Look at what kind of variance this is, because it is a different animal from anything in Chapter 22.

In a linear model, nudge one data point and every coefficient moves a little, so the fitted plane tilts slightly. The change is small and it is smooth. In a tree, nudge one data point across a threshold near the root and the root split can flip to a different feature entirely. Every box below it, in both subtrees, is then rebuilt from scratch. The prediction changes at points nowhere near the point you moved.

A split is chosen by an argmax — and an argmax is a step function of the data. Commit a guess first: move ONE point by a hair, how much of the partition survives?
x₂ ↑ root: x₂ < 0.50 cross here drag me sideways drag → x₁ → area rewritten: 0% 20 bootstrap refits staircases x → 0 / 20 drawn commit your guess, then move the gold point one notch
① Commit. One tree, grown to depth 3, has cut this cloud into 8 boxes. Nudge ONE point a hair sideways — how much of the partition gets rebuilt?
① pick a guess to unlock
What you’re looking at — a partition that is not a smooth function of the data, and the spray that proves it
The sample. 37 points; the two axes are the features x₁ and x₂, and a dot’s size is its target y. The gold dot is one loud observation (y = 24) sitting in the top band. The tree picks the split with the biggest drop in squared error — that choice is an argmax, so it can only ever jump.
What changed. Red shades every box whose corners moved; the gold readout is that as a share of the plane. Drag the gold dot past x₁ = 0.40 and the root stops asking about x₂ and starts asking about x₁ — so all 8 boxes are refitted, including the ones far from the dot. Same red on the right: 20 trees refitted on 20 bootstrap resamples of the same data.
Ch 22’s comparison. The same 20 resamples fitted with a degree‑4 polynomial: they form a tidy band, because a least‑squares coefficient moves continuously with the data. The staircases don’t — they disagree about where the steps are. That is tree variance: not a bigger number, a different animal. Bagging exists to average it away.
Fig. 9. One point, one hair, and the whole partition is gone. The left panel is a single regression tree grown to depth 3 on 37 points: at every node it scans both features and every threshold, and keeps the cut with the largest drop in squared error. Commit your guess first, then drag the gold dot — the one loud observation, y = 24 — slowly to the right. Nothing happens, nothing happens, and then it passes x₁ = 0.40, the coordinate of one ordinary blue point, and the root question changes from “is x₂ below 0.50?” to “is x₁ below 0.48?”. The readout leaps to 100%: every box is refitted, including boxes in the far corner that the dot was never near, because a child’s split is only defined relative to its parent’s. That is the mechanism people miss. The split location is an argmax, and an argmax over a finite set of candidate thresholds is a step function of the data — it cannot drift, it can only jump. Nothing in Ch 20 or Ch 22 behaved like this: a least‑squares coefficient is a smooth, continuous function of every y, which is exactly why the twenty polynomial refits on the right form a tidy band while the twenty tree refits on the same twenty bootstrap resamples disagree about where the steps even are. So tree variance is not “the same variance, bigger”; it is discontinuous by construction. Hold that picture, because it is the entire argument for bagging: if one tree’s answer is a lottery, draw twenty lotteries and average them, and the jumpiness cancels while the signal survives. (And note what averaging cannot fix: every one of those staircases is roughly right on average. The problem is spread, not bias — which is why the other ensemble, boosting, has to work in the opposite direction.)

The mechanism has a name worth holding onto. A split location is chosen by an argmax over candidates, and an argmax is a step function of the data. So an arbitrarily small change in the data can produce a discontinuous change in the model. Nothing in Chapter 20 or Chapter 22 had that property. That is what makes tree variance structural rather than merely large.

Chapter 22 taught us to picture the spray of refits. Run that experiment again with trees and the picture is not a fuzzy band around a curve. It is a collection of staircases that disagree about where the steps even are.

Pruning is the honest response, and it is a shape we already own. Grow the tree fully, then charge α for every leaf and prune back the subtree that pays for itself least. That is cost-complexity pruning, and it is Chapter 22's penalty with the number of leaves in place of ‖β‖² and α in place of λ. It is tuned exactly the same way, on validation folds.

Pruning walks the U — it never lowers it. Drag α and watch the tree pay rent, one leaf at a time.
one tree, grown deep α buys it back, node by node α = 0.000  →  16 leaves the U it walks along one dot = one pruned subtree 1.00 0.75 0.50 0.25 1 16 leaves |T|  ·  α grows ← ridge 0.372 floor of the U = 0.446 what a tree costs:   R(T)  =  train error  +  α × (number of leaves) 0.088  +  0.000 × 16  =  0.088 Ch 22 charged you for ‖β‖². Same penalty — here the price tag hangs on leaves.
Can any pruned tree beat plain ridge? Guess, then drag.
Pick one — it jumps to the best α.
leaves |T|16
train error0.088
test error0.775
ridge, test0.372
tree 0.775 · ridge 0.372
What you're looking at — a penalty on leaves, and the floor it can never get under
leaves — what α charges rent for; the dashed ring is the next branch to go
train error — falls forever, and even dives under the ridge line. It is lying.
test error — the U from Ch 22. Its floor is the best a single tree can do.
ridge — Ch 22's model on the same data, scored honestly: 0.372, below the floor.
Fig. 10. A tree with sixteen leaves, and the price tag Ch 22 taught you to hang on it. α (alpha) is the penalty rate: the cost charged to a tree is its training error plus α times its number of leaves, written |T|. At α = 0 leaves are free, so the cheapest tree is the biggest one — sixteen leaves, training error 0.088, and a test error of 0.775 that is the worst on the whole chart. Raise α and each leaf starts costing something, so the weakest branches — the ones that bought the least training error — stop paying for themselves and collapse into a single leaf. The dashed gold ring marks whichever branch is next in the queue; that queue is not a choice, it is arithmetic, because a branch dies exactly when α passes the training error it was buying. Watch the two panels move together: the tree shrinks node by node on the left while the gold marker slides leftward along the U on the right. The blue train error only ever falls — it slides right under the violet ridge line and keeps going, which is precisely why in-sample error cannot be trusted (Ch 21). The cyan test error does what Ch 22 promised: too many leaves and it is all variance, too few and it is all bias, with a floor in between. Press jump to the best α and the floor is found: seven leaves, test error 0.446. Now look down. The violet line is a ridge regression on the same data, scored the same honest way: 0.372. The best pruned tree in the entire path is still worse than a straight line with a penalty on it. That is the verdict, and it is the reason the rest of this chapter exists: pruning is a genuine, principled cure — the same regularisation you already know, with leaves in place of ‖β‖² — and it walks you along the U without ever lowering it. To lower the floor you have to stop fixing one tree and start combining many.

And here is the verdict, said flatly, because pretending otherwise wastes your time. Pruning walks you along the U, and it does not lower it. A well-pruned single tree is a mediocre model. Too rigid when it is small, too unstable when it is large, and beaten by a ridge regression on most smooth problems.

So stop trying to fix the tree, and use many of them instead.

06Average the draws

If the problem is that the answer is a wild draw, the oldest instrument in this course applies: average the draws. Chapter 13's law of large numbers and Chapter 16's SE = σ/√n both say the same thing in different words. Averaging independent copies shrinks the spread.

The obstacle is that you do not have many datasets. You have one, and Chapter 20 already solved that exact obstacle. The bootstrap manufactures pseudo-datasets by resampling your rows with replacement.

So draw B bootstrap samples, grow a tree on each, and average the B predictions. That is bagging, short for bootstrap aggregating, and it is about four lines of code.

Now the question that decides how you should build the ingredient, and it is settled by one line of algebra rather than by taste. What is the bias of the average? The B trees are identically distributed, because they came from the same recipe and the same kind of draw. So each of them has the same expectation, and expectation is linear, so the average carries that same expectation too. The bag's bias is exactly one tree's bias, and nothing was paid for it.

Read that again with Chapter 22's dial in mind, because it inverts the instinct you have been building for two chapters. Averaging costs no bias and buys variance. So you want the ingredient to have as little bias as possible, and you genuinely do not care how much variance it has. That means you grow the trees deep and you do not prune them, because inside a bag, pruning is an actual mistake.

Drag B to average more bootstrap trees — then swap the ingredient from a DEEP unpruned tree to one PRUNED at depth 2.
the trees, and their average one tree per bootstrap resample bias at x₀ = 0.01 0 x₀ = 0.25 1 1 tree drawn variance of the bag at x₀ ÷ one tree's variance 1 ½ 0 ? 1 10 100 B — trees in the bag
Average 200 bootstrap trees. Does the variance at x₀ fall all the way to zero? Pick one — B jumps to 200.
what every tree in the bag is
bias at x₀ · one tree0.01
bias at x₀ · the bag0.01
variance · the bag0.041
MSE at x₀ = bias²+var0.041
zero, or a floor?
What you're looking at — jagged trees averaging into one curve, and the part that refuses to average away
each faint staircase is one tree, grown on its own bootstrap resample of the same 40 rows
the bag = the running average of all B of them (grey dashed = the truth it chases)
1/B — what the variance would do if the B trees were independent
what averaging never removes: the bias at x₀, and the floor the fall stops at
Fig. 11. Bagging, watched at one point. Forty noisy rows, and from them B bootstrap resamples — each one a fresh draw of forty rows with replacement, exactly the trick from Ch 20. Fit one regression tree per resample and you get the faint blue staircases: each is a legitimate fit, and each puts its jumps in a different place. Average them and the gold curve emerges — a curve, out of pieces that were never curves. That is the whole idea. The right panel measures what it bought, at the single point x₀ = 0.25: the variance of the bag's prediction there, as a fraction of what one tree alone had. If the B trees were independent, averaging would divide the variance by B — that is the cyan 1/B line, headed for zero. Drag B and watch the gold curve leave it almost at once and level off at 0.44, and stop. Forty-four percent of one tree's variance, and no amount of extra trees moves it. The red bracket is the gap, and the figure leaves it there, unexplained, on purpose. Now the trap. Every tree in the bag is drawn the same way, so they are identically distributed — and the expectation of an average of identically distributed things is just their common expectation. The strip does that in one line: the bag's bias is one tree's bias, for every B. So switch the ingredient to PRUNED. Bias jumps 0.01 → 0.24 and stays there however many trees you add; variance is genuinely lower (0.003 against 0.018 — shallow trees really are steadier), and the MSE is three times worse. You paid in bias for a variance reduction that averaging was about to hand you for free. That is why every tree in a bag is deliberately grown to overfit: in a bag, variance is the cheap thing and bias is the permanent one.

The picture is worth watching rather than describing. One tree is a jagged staircase. Average twenty trees whose steps sit in different places and the jumps smooth out into something that looks like a curve, because the discontinuities are somewhere else in every member.

And watch the variance band as B rises. It collapses fast at first, then less, then it flattens, and it does not flatten at zero. That gap between the measured curve and the 1/B line you expected is exactly what the correlation floor explains.

07The floor more trees cannot reach

The 1/B line assumed the trees were independent. They are not, and it is worth being concrete about why before doing any algebra.

Two bootstrap samples drawn from the same n rows overlap heavily. Each one contains about 63% of the distinct original rows, so any two of them share most of their data. And there is a second reason that does more damage. If one feature is genuinely the strongest predictor, then nearly every tree, on nearly every resample, picks it at the root. The trees are not twenty different opinions. They are twenty variations on the same opinion.

So redo the algebra honestly. Let each tree's prediction at a fixed x₀ have variance σ², and let any two of them have correlation ρ. Then expand the square, exactly the way Chapter 11 taught us to.

Three beats of algebra — then the picture it forces: a slab that never moves and a sliver that evaporates.
1 · one square of pairs each cell = Cov(f_i , f_j) σ² = a tree's      variance ρ  = how alike      two trees B  = how many      trees B² = 2,500 terms, all one lump 50 on the diagonal × σ² 2,450 off-diagonal × ρσ² Var of the average in units of σ² 1.0 0.5 0 one tree the floor ρσ² no B (1−ρ)σ²/B 0.608 σ² the average of 50 fits
You have 50 trees and the variance has stopped falling. Push B to 5000 — how much lower does it go?
the derivation, in three beats
slab  ρσ²0.600
sliver (1−ρ)σ²/B0.008
total0.608
predict: 50 trees → 5000?
What you're looking at — the square of every pair of trees, and the two-part bar it collapses to
the red bulk is the B(B−1) off-diagonal pairs — it tidies to ρσ², a slab with no B in it
the blue diagonal is the B own-variance terms — it tidies to (1−ρ)σ²/B, the sliver B evaporates
gold is the total you actually pay: more trees is never worse, and never enough
Fig. 12. The missing rung, derived and then read. Averaging B tree predictions means taking the variance of a sum, and the variance of a sum is a square: every fit paired with every other fit, B×B cells, each one a covariance. Beat 1 draws that square whole. Beat 2 splits it the only way it can be split — the B cells down the blue diagonal, where a fit is paired with itself and the covariance is just its own variance σ²; and the B(B−1) red cells off it, where two different trees are paired and the covariance is ρσ², with ρ the correlation between two trees. Beat 3 divides by B² and tidies: ρσ² + (1−ρ)σ²/B. Now look at the square again with that in hand — as you drag B, the blue diagonal thins to a hairline while the red bulk swallows everything, because the diagonal is B cells out of B² and the rest is not. The bar on the right is the same two numbers stacked. Drag B and only the blue sliver moves; the red slab does not flinch, because the first term contains no B at all. That is a floor, and the arithmetic is brutal: at ρ = 0.6, fifty trees give 0.608 σ² and five thousand give 0.600 σ² — a hundredfold increase in compute buys about 1.3% of one tree's variance, and 60% of it survives forever. Then drag ρ and watch the whole picture drop. That is the only lever that works, and it is exactly the lever a random forest pulls: hobble every tree with a random subset of features so the trees stop agreeing, and ρ falls. More trees is never worse and never enough; less-alike trees is the whole game.

The variance of the average is (1/B²)[B σ² + B(B−1) ρσ²], and tidying that up gives ρσ² + (1 − ρ)σ²/B. Now read it slowly, because this single line is the most useful thing in the first half of the chapter.

The second term is the one everybody expects. It falls toward zero as B grows, and it is why more trees never hurt. The first term does not contain B at all, which makes it a floor.

Put numbers on it, because the abstraction hides how brutal this is. At ρ = 0.6, fifty trees give you 0.608σ² and five thousand trees give you 0.600σ². A hundredfold increase in compute bought you about 1.3% of one tree's variance. Sixty per cent of the original variance is still sitting there after infinitely many trees.

So the question "how many trees?" has a boring answer. Enough, then stop, because more trees are never worse and never enough. The interesting question is the one the formula hands you, and it is the only lever left. How do you lower ρ? Everything a random forest does that looks strange is an answer to that one question.

08Hide the best feature on purpose

Decorrelate them, and there is only one honest way to do it. If every tree keeps choosing the same feature at the root, stop letting them all see it.

At every split, not once per tree but at every single node, draw a random subset of m features out of p and choose the best cut among only those. Trees forced to build around the second-best or third-best predictor look genuinely different from trees that used the strongest one. So ρ falls. Bagging plus per-split feature subsampling is the random forest, and that is the entire difference.

Notice that this is per split and not per tree. The per-tree version is a real method and it is a weaker one, and people routinely mix the two up. The per-split version is also why the forest still finds the strong feature at all. With p = 100 and m = 10, that feature appears in about a tenth of the candidate sets, so it wins somewhere inside almost every tree. Just not everywhere.

One dial, three linked readouts. Before you drag — does hiding the best feature from a tree help the forest, or wreck it?
ONE DIAL, THREE THINGS MOVING √p p/3 ρ 1 0 .25 .13 test error min 1 12 m (features per split) ρ tree-to-tree 0.880 one tree 0.1400 the forest 0.2055 WHO WON THE ROOT SPLIT? F1 wins 50/50 · 1 in play F1 F12 VARIANCE OF THE AVERAGE ρσ² + (1−ρ)σ²/B no B in it — the floor more trees cannot touch it floor now ρσ² = 0.1232
PREDICT · THEN drag m →
m · FEATURES PER SPLIT12
DEFAULTS · starting points
every tree asks the same
What you're looking at — m is how many of the 12 features a tree is allowed to even look at when choosing each split. Shrink it and you buy independence with tree strength.
ρ — how alike two trees are, measured as the chance they pick the same first question. At m = p it is 0.88; at m = 1 it is 0.07.
one tree — the average error of a single tree. Hiding features makes every tree worse: 0.1400 → 0.2411. That is the price.
the forest — error after averaging 50 trees. Var(average) = ρσ² + (1−ρ)σ²/B. Only the second term shrinks with more trees, so ρ is a floor, and m is the only lever on it.
root-split winners — one bar per feature, F1 strongest. The dotted line is an even share (50/12). At m = p one column takes everything; smaller m spreads the mass.
Fig. 13. Twelve features, one of them clearly the strongest, and a forest of fifty trees. At m = 12 — every tree free to consider everything — the bar chart is a single column: the same feature wins the root split in all 50 trees, ρ = 0.880, and each individual tree is at its best, 0.1400. Yet the forest is at its worst, 0.2055. Now drag m down. Every number moves at once: ρ collapses 0.880 → 0.073, single-tree error climbs 0.1400 → 0.2411, and the forest traces a U that bottoms out at 0.1612. The inset says why. Averaging B trees leaves Var = ρσ² + (1−ρ)σ²/B; the second term is already negligible at B = 50, but the first has no B in it at all. Buying more trees cannot move it. Only ρ can, and per-split feature subsampling is the only lever that touches ρ. That is why hiding the best feature from a tree is not sabotage — it is the only way to buy independence, and you pay for it in tree strength. Press the two defaults: √p ≈ 3 gives 0.1676 and p/3 = 4 gives 0.1630, both good, neither the actual minimum, which sits at m = 5. They are starting points, not truths — m has its own U-curve and is a hyper-parameter like any other, to be chosen on held-out data.

Now read the tradeoff straight off the floor formula, because it is already there and nothing new is needed. Shrinking m lowers ρ, which lowers the floor. Good. It also makes each individual tree worse, because the tree is sometimes forbidden from using the best available cut, which raises σ² and adds a little bias. Bad. The total is a falling factor multiplied by a rising one, so m has its own U-curve.

That makes m a hyper-parameter in Chapter 21's exact sense, chosen on validation folds. It also makes the famous defaults, √p for classification and p/3 for regression, starting points rather than truths. On a market dataset with a hundred weakly-informative features and one strong one, the right m is an empirical question.

One more thing falls out for free, and it is a genuinely elegant consequence of the bootstrap. Every bootstrap sample leaves some rows out. Which fraction? Row i is missed on a single draw with probability 1 − 1/n, and there are n draws, so it is left out entirely with probability (1 − 1/n)ⁿ. Chapter 2 told us that limit, and it is 1/e, or 36.8%.

Out-of-bag error is free — and on a time series it is poison. Count the leftover rows first, derive 1/e second, then break it.
the 100 rows — n = 100 no draw yet · every row untouched out of bag: — of 100 press DRAW → (1 − 1/n)ⁿ  vs  n the share of rows one draw misses 0.25 0.30 0.40 0.50 2 10 100 1000 1/e = 0.3679 n = 100 → 0.3660 0 draws counted yet
Guess first: a bootstrap draw takes 100 rows from these 100, with replacement. How many rows does it never touch?
what the rows mean
draws so far0
your average OOB
(1−1/n)ⁿ at that n0.3660
guess, then press DRAW
What you’re looking at — the 37% you counted, the limit that explains it, and the day it turns into a leak
gold = rows this draw never touched — out-of-bag. Count them: ~37 every time, free honest test data
blue = the formula (1−1/n)ⁿ, the chance one row is missed by all n draws — it settles on 1/e = 0.3679
red = in-bag rows dated after the row being scored — the tree grading day 31 was trained on day 80
Fig. 14. One hundred rows of data, and one bootstrap draw over them — the same resampling trick from Ch 20, now doing a second job nobody asked it to do. Press DRAW: the sample takes 100 rows with replacement, so some rows get picked twice or three times and others get picked not at all. The picked rows go dim; the untouched ones stay gold. Count them. It is never exactly the same number, and it is always near 37. Only after you have counted it does the formula earn its place: a single row survives one draw with probability 1 − 1/n, and it must survive all n draws, so it is missed with probability (1 − 1/n)ⁿ — the blue curve. Sweep n and watch it flatten onto 1/e = 0.3679, Ch 2's limit arriving as an explanation of something you already measured. Your gold dots pile up at n = 100 around that line, scattering the way a random count should. That is why out-of-bag error is free: every tree in a forest is grown on a different bootstrap, so every row can be graded by the ~37% of trees that never saw it — a validation set you did not have to carve out. Now flip to time series. Nothing about the arithmetic changes, and everything about its meaning does: the rows are days, and the row being graded is day 31, but the tree grading it was trained on days 40, 60, 88 — the red cells, with arrows pointing forward in time. The bootstrap draws rows uniformly; it has never heard of a clock. Out-of-bag honesty is honesty about membership, not about time, and on a price series that gap is Ch 21's lookahead leak wearing a friendly name. Use OOB on cross-sectional data; use a walk-forward split on anything with a date on it.

So about a third of the rows are out-of-bag for each tree, and every row can be predicted using only the trees that never saw it. That is a held-out estimate with no folds and no extra fitting, which is a lovely thing to get for nothing.

Then the caveat that saves real money: out-of-bag scoring draws rows uniformly at random. On a time series that means the trees scoring today were trained on tomorrow. It is Chapter 21's lookahead leak wearing a friendly name and printed by default. Out-of-bag error is a fine model-selection tool on cross-sectional data, and it is not a substitute for a purged walk-forward split on anything with a date column.

09The other direction: start rigid and step

The forest attacked one of Chapter 22's two terms. Now go at the other one, and notice that every design choice has to invert.

If you want to reduce bias, averaging is useless, and we proved that a moment ago. The average of B identically distributed fits has exactly the bias of one of them, so no amount of averaging can move it. Something has to change between one fit and the next.

So try the crudest possible version of changing. Start with the dumbest model in existence: predict the overall mean of y for every row. That has enormous bias and essentially no variance, which is the far left end of the U, the mirror image of where the forest started.

Now compute what is left over, r = y − F, for every row. Fit a small tree, a stump or depth 3, to those leftovers. Notice what that tree is doing, because it is not trying to predict y. It is trying to predict what the current model is getting wrong, which is a different and much smaller job. Add a fraction of it to the model, F ← F + ν·h, with ν something like 0.05. Recompute the leftovers, fit another small tree, and repeat the whole move M times.

Boosting runs on exactly one fuel: the residual. Step the rounds and watch it burn — then hand round 1 a depth‑20 tree and watch it vanish in a single go.
1 · data + the fit F · round 0 0 1 2 x 2 · the residual r + the tree h 0 no tree yet — press STEP 3 · the tree fitted this round x < 0.13 ? yes no −0.87 +0.12 1 question · 2 leaves weak on purpose 4 · the fuel gauge = total |r| fuel left 100% · Σ|r| = 16.5 train RMSE 0.644 test RMSE (held out) 0.634 no fuel — the loop is dead
Round 0: F is one flat line at the mean. Panel 2 is the residual r — the ONLY fuel boosting has. Press STEP.
the tree each round
round 0 · fuel 100% · press STEP
What you’re looking at — one boosting round, drawn four ways at once
The 32 training points, y = f(x) + noise. The grey dashes are the truth f, which the model never sees.
F, the fit so far. Round 0 is a flat line at the mean ȳ. Each round adds one whole tree at full step, so F grows into a staircase — and the test RMSE is the gold bar in panel 4.
r = y − F, the residual — the only fuel boosting has. Faint dots are what the round faced, solid dots what it left behind, and the gauge is their total length Σ|r|.
h, the tree fitted to r this round. Depth 1 = one question, two leaves: it nibbles. Depth 20 = 32 leaves, one per point: it swallows r whole, the gauge hits 0%, and rounds 2–50 have nothing left to fit.
Fig. 15. One boosting round, shown four ways at once. Start at round 0: the fit F is a single flat line at the mean, and every bit of structure in the data is sitting in the residual r = y − F — total length Σ|r| = 16.5, the full tank. Press STEP and a depth‑1 stump — literally one question, two leaves — is fitted to that residual and added straight onto F. The faint dots show what the round faced, the solid dots what it left, and the gauge drains a little: 83% after one round, 47% by round 5, 24% by round 50, while the flat line becomes a staircase that closes on the truth. Now break it: press depth 20. That tree has 32 leaves for 32 points, so it fits the residual exactly. In one round the residual dots collapse onto the axis, the gauge reads 0%, and training RMSE is 0.000 — and rounds 2 to 50 change nothing at all, because every one of them is handed a residual of exactly zero. The sequence is dead at round 1, and the honest number is out of sample: held‑out RMSE 0.437 against the stump sequence’s 0.345. That is the mechanical reason the boosting ingredient must be weak: the residual is the fuel, and a first tree strong enough to explain everything leaves nothing for the rest of the sequence to learn from. It is a requirement, not a matter of taste. (Watch the stump run past round 4 too — test error turns up and keeps rising to round 50 while training error keeps falling. Boosting will happily fit noise if you let it; that is what early stopping on a validation set is for.)

That is boosting, and three features of it are worth pinning down straight away, because all three are the opposite of the forest.

The trees are grown sequentially, because each one is fitted to a target defined by all the previous ones, so this cannot be parallelised the way bagging trivially can. The pieces are summed with weight ν, not averaged with weight 1/B. The model is F₀ + ν·h₁ + ν·h₂ + … and it grows rather than staying in place.

And the ingredient is deliberately weak, which is not a stylistic preference. The residual is the fuel. If your first tree is deep and fits everything, the leftovers are nearly zero, and every subsequent tree has nothing to work with except noise. Try it and the sequence stalls in a single round. Boosting has to be handed a job that is not finished.

10★★ The leftover is the gradient

Everything in the last section was correct and none of it was explained. Why the residual and not something else? Why does adding a fraction of a correction converge instead of oscillating? What do you do when your loss is not squared error, when you want to be robust to outliers, or when the label is a class and you are scoring a probability?

Every one of those questions has the same answer, and it is one line of Chapter 3 calculus.

Forget trees for a moment, and forget models entirely. Suppose I let you set the N training predictions to anything you liked. A lookup table, F = (F(x₁), …, F(x_N)), holding N free numbers with no structure at all. And I score you with L(F) = ½ Σ (yᵢ − Fᵢ)².

That is an ordinary function of N variables, and it is exactly the object Chapter 8 built gradient descent for. So take the gradient. ∂L/∂Fᵢ = −(yᵢ − Fᵢ), which means the negative gradient in coordinate i is (yᵢ − Fᵢ). The residual. Not something analogous to the residual, and not a relative of it, but the same number we have been computing since Chapter 20.

CodeRun — put the model away. Ten rows, ten free numbers F, one question: what is the negative gradient?
$ python residual_is_gradient.py --loss squared F = ten FREE numbers - no model. L = 0.5 * sum (y - F)^2 dL/dF_i = ????? you first - write it down max |difference| = ? # descent gave a direction at the 10 rows and NOWHERE else tree.fit(x, STEP) <- fit the STEP, never y tree.predict(0.36) = ????? <- x never seen in training tree.predict(0.73) = ????? <- x never seen in training the step at each of the 10 rows + the tree that carries it everywhere 0 0.36 0.73 one wild row x=0 x=1
1 · YOUR GUESSpick one
Before anything runs: what is the i-th component of the negative gradient −dL/dF_i? F is just ten free numbers.
2 · RUN ITtap
3 · SWAP THE LOSStap
commit a guess, then RUN
What you’re looking at — two vectors that print the same numbers. “Fit the leftovers” was never a heuristic; it was steepest descent wearing different clothes.
y − F, the residual — the leftovers you were told to fit. One blue bar per row, and it never changes.
−dL/dF, the step — differentiate 0.5(y−F)² in F and you get −(y−F), so the step is +(y−F). Gold bar — dead level with blue.
the tree — descent spoke at 10 x’s and nowhere else, and a lookup table is silent at x=0.36. The tree carries the step everywhere.
swap the loss — |y−F| makes the step sign(y−F), so the wild row’s +3.2 becomes +1.0. Same loop, new target: a booster that shrugs off outliers.
Fig. 16. Put the model away — not the trees, the whole idea of a model. On screen are ten training rows and ten numbers F₁…F₁₀ you may set to absolutely anything, scored by L = ½∑(yᵢ−Fᵢ)². That is a function of ten variables, and Ch 8 told you exactly what to do with one of those: take the gradient and step against it. So commit, before anything runs, to the i-th component of −∂L/∂F. Most people write something cautious, or reach for the chain rule through the model — the reflex Ch 8 built. Now run it. The negative gradient prints, and on the very next line the residual y − F prints, and the two lines are identical element by element, max difference 0.0. Sit in that, because it is the whole chapter: differentiating ½(yᵢ−Fᵢ)² with respect to Fᵢ gives −(yᵢ−Fᵢ), so “fit what’s left over” was never a heuristic that happens to work — it was steepest descent in different clothes. Then the second stroke, which turns the identity into a method: descent just told you where to move at ten points and nowhere else, and a lookup table has nothing to say about a row it has never seen — the only kind of row you are ever paid for. So a small tree is fitted to the STEP, not to y, and it does the one missing job: it carries the direction off your ten rows and out into the whole input space, answering at x = 0.36 and x = 0.73. Descent, but every step must be a function a tree can say — that constraint is the entire content of the phrase gradient descent in function space. Now turn the dial and watch the machine stay put while the target changes: swap L for ∑|yᵢ−Fᵢ| and the step becomes sign(yᵢ−Fᵢ), plus-one and minus-one, so the wild row’s +3.2 collapses to +1.0 and the next tree chases direction while ignoring magnitude — a booster that shrugs off an outlier, built without one new line of algorithm. That is why the word gradient is in the name: the loop is fixed, and the loss decides what you hand the tree.

So "fit what's left over" was never a heuristic. It was steepest descent, and almost nobody writes the line that says so. Textbooks put "fit the residuals" in the algorithm box, then put "boosting can be viewed as gradient descent in function space" in a remark a page later, and never write the derivative that makes those the same sentence.

Now the second half, which is where the phrase in function space stops being decoration. Gradient descent has just handed you a step at the N training rows and nowhere else. You would be updating a lookup table, and a lookup table is silent at a new x. That is the only kind of row you will ever be paid for.

So we fit a small tree to those N step values. Not to y, but to the step. The tree is the interpolator. It takes a direction that exists only on your data and extends it to the whole input space, snapping the ideal step onto the closest thing our model class can actually express. That constraint is the only difference from ordinary gradient descent, and it is why each step is a function rather than a vector.

Everything else transfers verbatim: ν is Chapter 8's learning rate η, and M is how many steps you take.

And now the payoff, because the loss has become a dial you can turn. Squared loss gives a negative gradient of (y − F). Absolute loss gives sign(y − F), so the trees chase direction rather than magnitude and a single outlier stops dominating. That is a robust booster, for free, with no new algorithm. A loss that scores probabilities gives its own negative gradient, and that is the door into Chapter 24. This is what the word gradient is doing in "gradient boosting". The algorithm is fixed, and the loss chooses the target.

11B is safe, M is a dial

Now the asymmetry, and it is the thing to walk out of this chapter holding, because it is where real money gets lost.

In a forest, B is not a tuning parameter. More trees shrink the second term of ρσ² + (1 − ρ)σ²/B and touch nothing else. You stop when the curve flattens, and running longer is a waste of electricity rather than a mistake.

In a booster, M is a complexity dial in Chapter 21's exact sense, because every round adds another ν-sized step of capacity. So the model becomes monotonically less biased and monotonically more able to chase this particular sample's noise, and the test curve is the U all over again.

The reason nothing inside the algorithm will warn you is worth stating as a mechanism, not as a warning. Boosting descends on the training loss, which falls at every round by construction. So the algorithm's own scoreboard says "better" forever, and there is no internal signal anywhere inside it to stop.

One knob spelled n_estimators in both libraries — on the left it is harmless, on the right it is a countdown.
0 .1 .2 .3 .4 out-of-sample error FOREST: average B trees BOOSTING: add M rounds 1 10 100 500 1 10 100 500 B — trees averaged M — rounds added floor — it never turns up this gap = noise M*
B = 8  err 0.1610
M = 8  test 0.2003
       train 0.1571
M* = ?  (drag M →)
both still falling — keep going
No test set exists inside — only the green line.
What you're looking at — one shared error axis, two opposite machines
forest: test error vs B
boosting: test error vs M
boosting: TRAIN error
the gap = noise being fitted
every axis reversed
FOREST (bagging)
BOOSTING
the loop
parallel — trees never see each other
sequential — each fits the last's residuals
combine by
averaging the B predictions
adding M small steps together
more of it
shrinks a variance term that vanishes
keeps descending — into the noise
it attacks
variance (Ch22)
bias (Ch22)
n_estimators
B: safe to raise, costs only time
M: a distance travelled — must be stopped
Fig. 17. Left: forest error against B — it falls, flattens, and stays flat however far you drag. Right: boosting error against M — it turns and climbs while the training curve underneath keeps falling. Halve ν and the minimum slides right by the same step each time, because what is really spent is ν·M.

Which makes M a hyper-parameter, chosen exactly as Chapter 21 prescribed. On a validation fold, walk-forward, purged to your label horizon, and never on the test set. That is early stopping, and it is not a library convenience. It is the only honest way to choose a distance to travel when the thing you are descending is not the thing you care about.

Two consequences follow, and the first is that ν and M are one knob wearing two labels. What has been spent is roughly ν × M, so halving ν means roughly doubling M to arrive at the same place. Small ν is better, and we can now name why precisely. Many tiny steps take a smoother path through function space and give any single tree less opportunity to latch onto a quirk. That is Chapter 22's shrinkage, moved off the coefficients and onto the steps.

Second, since we know how to buy variance reduction cheaply, we can smuggle the forest's trick into the booster. Fit each round on a random subsample of rows and a random subset of columns. That is stochastic gradient boosting, and it is why every serious implementation exposes subsample and colsample parameters.

Finally, the folklore, killed cleanly. You will read that boosting resists overfitting. It does not. That belief comes from AdaBoost on clean, high-signal classification data, where test error kept falling long after training error hit zero. Chapter 20 told us a daily-return near 0.01 is normal. In a dataset that thin, a booster will find spurious structure quickly and confidently, and it will do it while its training curve looks magnificent.

12XGBoost, taken apart

Every serious tabular result you will read about is XGBoost or LightGBM. So let us take the brands apart and see how much of the difference is a real idea. It is not much, and the real idea is one you already own.

Go back to the step, where we are minimising L(F) and we want the best move. Boosting as we built it uses the first derivative only, which is plain gradient descent. But Chapter 4 says a smooth function near a point is its value, plus slope times step, plus half its curvature times step squared. So expand the loss around the current prediction to second order and keep both terms. For each row, gᵢ = ∂L/∂Fᵢ and hᵢ = ∂²L/∂Fᵢ². If you are doing descent anyway, do Newton.

Then add the regularizer explicitly into the objective instead of leaving it to the stopping rules. Charge γ per leaf, and add ½λ times the sum of squared leaf values. That second piece is Chapter 22's ridge penalty applied not to coefficients but to the numbers a leaf is allowed to output.

The leaf value, derived in four lines — Taylor to second order, plus Ch 22's ridge — then the same tree grown two ways.
1 · Taylor-expand the loss where you stand loss prediction F → here now L(y, F + w) ≈ L + g·w + ½·h·w² g = slope of the loss now h = how fast that slope bends F = what the model says today w = what the new leaf adds ohh — no new maths here that is Ch 4's Taylor picture, kept as far as the second term.
step the derivation →
1/7 · Taylor to 2nd order
loss ⇒ a parabola in w
What you're looking at — one quadratic, one unknown, one line of algebra
g, h — the loss's slope and bend; summed over a leaf they become G, H
w* — the leaf's output, and the parabola's vertex
λ shrinks w*; γ is the fee a split must beat
leaf-wise: same leaf budget, deeper tree, more loss cut
Fig. 18. The whole of XGBoost's leaf rule, one beat at a time. Taylor-expand the loss to second order and every row hands you two numbers, a slope g and a bend h; add Ch 22's ridge ½λw² — the same penalty, moved off the coefficients and onto the leaf's own value — and one leaf's objective collapses to G·w + ½(H+λ)w², a plain parabola in one unknown. Setting its derivative to zero gives w* = −G/(H+λ) in a single line: a Newton step with a ridge in the denominator. Drag λ and the vertex slides toward zero — fastest for the leaves holding fewest rows, which is exactly what min_child_weight, a floor on H, is protecting against. Substituting back turns the same expression into the split gain with γ sitting inside it, so a split that does not pay is never made. Then press GROW: level-wise fills each layer, leaf-wise chases the single most promising leaf, and the identical data grows two very different trees.

Inside one leaf j, the objective is Σ(gᵢw + ½hᵢw²) + ½λw², which is a plain quadratic in the single number w. Chapter 8's condition ∇f = 0 gives it in one line: w* = −G/(H + λ), with G = Σg and H = Σh. The most famous expression in applied machine learning, and no new mathematics entered the room.

Look at what it is. The numerator is the accumulated gradient in that leaf, and the denominator is its curvature plus λ. So it is a Newton step with a ridge in the bottom, which means a leaf holding few rows or low-curvature rows is automatically damped. That is precisely the surgical shrinkage Chapter 22 described, shrinking hardest where the data spoke least.

Substitute w* back and the leaf contributes −½G²/(H + λ). So the gain of a proposed split is the two children's scores minus the parent's, minus γ. That last term is what makes this different in kind from the split rule we built earlier. The complexity charge is inside the criterion, so a split that does not pay for itself is never made, and pruning stops being a separate pass afterwards.

The famous dials stop being superstition at that point. λ shrinks leaf values, and γ charges per leaf. And min_child_weight is a threshold on H, the summed curvature in a leaf. For squared loss hᵢ = 1, so H is just the row count. Under a probability loss hᵢ = p(1−p), so the same parameter means something completely different, and almost nobody says so.

LightGBM's differences are engineering rather than statistics, and they are worth knowing exactly. It bins each continuous feature into a few hundred buckets, so the split scan costs O(bins) per node instead of O(n). And it grows leaf-wise, always splitting the single most promising leaf anywhere in the tree, rather than level by level. That gets more gain per leaf and it overfits harder on small data. So the honest summary is one statistical upgrade, one explicit regularizer, and a great deal of very good engineering.

13The cliff, the gift, and the lie in the importance chart

Now the practical truth, and it starts with the single fact about trees that costs quants the most money. A tree cannot extrapolate.

Its prediction is the mean of a box, and outside the range it was trained on there are no new boxes. The boundary leaf simply extends forever, flat. Feed a tree trained on prices between 100 and 140 a price of 200 and it returns whatever the 130-to-140 box said. A linear model would at least keep going in the right direction, often wrongly, but it moves.

Three cells that actually run. Commit to an answer, press RUN, read the real stdout — the cliff, the gift, and the ranking that will not sit still.
1 · the cliff python · really executed stdout ▸ prediction vs x trained here 0 10 20 0 10 20 the cliff ?
Trained only on x in [0,10]. What comes out at x = 20?
predict at x = 20.0  · drag →
tree says
a straight line says
pick an answer, then press RUN
What you’re looking at — one model, three printed truths: it cannot extrapolate, it does not care about units, and it cannot rank twins
blue = the tree’s prediction. Past the last leaf it is a horizontal line forever — the boundary leaf’s mean
violet = a straight line fit to the same data, which keeps climbing. That gap is why you model returns, never levels
gold = the numbers the cell printed — the split cuts, the bars that never move, the importance you must not trust
Fig. 19. Three cells, really executed, each one asking you to commit before it prints. Cell 1 — the cliff. A tree is fitted to the most learnable relationship there is, y = x, on x between 0 and 10, and then asked for x = 20. A straight line answers 20.000. The tree answers about 9.4, and drag the slider out to 22 and it answers 9.4 there too, and at 200, and at a million. A tree is a stack of yes/no questions, so every prediction it can ever make is the mean of one leaf, and the rightmost leaf holds the eight training rows with the largest x. There is no leaf beyond it, so the blue curve goes horizontal at the edge of the data and stays there, while the violet line keeps climbing. That is the single most important limitation in this chapter for anyone who works with prices: a trending series wanders out of its own training range by construction, and a tree meets it with a flat line. It is exactly why you model returns and spreads — quantities that live in a range the model has already seen — and never raw levels. Cell 2 — the gift. Ch 22 spent a whole chapter proving that scaling a feature changes everything, because a penalty of λ∑β2 charges a big coefficient more than a small one, so units are part of the model. Here the same instinct is wrong, and the printout is blunt about it: max |p_raw − p_log| = 0.0, every row identical to the last bit. Flip the toggle and watch why. The dots on the number line stampede toward each other when the axis becomes log(1 + x) — the spacing is unrecognisable — but the gold cut stays wedged between the same two neighbours, and the prediction bars below never twitch. A tree only ever asks “is this value above that one?”, so it uses the order of a feature and nothing else. Any monotone squash — log, square root, standardising, ranking — preserves order, so it changes nothing. Penalised linear models care about units; trees do not. Same data, opposite rule, and now you know which lever each model is actually pulling. Cell 3 — the lie. Two of the four columns are near-identical twins (mom and mom_copy, correlated 0.999), one is a genuinely useful signal (vol), and one is pure random noise. Two hundred bootstrap refits, the ranking recorded each time, and the lines cross constantly: whichever twin happens to win the root split takes credit for the whole effect, so “the most important feature” is settled by a coin flip. Now switch to permutation importance, measured by shuffling one column at a time on the rows each tree never saw. The noise column is pinned to the bottom and stops wandering, because shuffling something that was never predictive cannot hurt an honest out-of-sample score — while the twins keep swapping, because they are interchangeable and no method on earth can tell you which one the world actually uses. Split counts reward a feature for being used; permutation on held-out data measures whether the use helped. Neither is causal (Ch 18), and a ranking that changes when you resample the same data was never a finding.

This is not a curiosity to file away. It is why you model returns, spreads, ranks and z-scores rather than raw levels. That is the same instinct Chapter 5 gave you when it made log-returns additive across time. A tree fitted on a trending series will confidently flatline the moment the series leaves its historical range, and it will do it without any warning in the output.

The mirror-image fact is a gift, and it is the exact opposite of Chapter 22's hardest-won hygiene rule. A tree only ever asks whether a feature is above or below a threshold, so it uses only the order of a feature and nothing else. Take logs, standardize, winsorize, cube it, and any strictly increasing transform hands you back the identical tree, bit for bit. In Chapter 22 the moment we added a penalty, the units of the data became a modelling decision. Under a tree, units are irrelevant.

Two more properties fall straight out of the hypothesis class. Depth is interaction order, because a leaf at depth d is defined by d conditions ANDed together. So a booster of stumps is a purely additive model that can hold no interactions at all, and depth 3 buys you three-way interactions and no more. That is a far more meaningful way to choose depth than a grid search. And missing values need no imputation, because a split can learn a default direction for rows whose value is absent.

Then the honest warning about the output everyone trusts most. Feature importance by split count is biased toward high-cardinality and continuous features, simply because they offer more thresholds to try and therefore more chances to win a scan. And when two features are near-identical, the credit between them is split arbitrarily by whichever one happened to win at each node.

You have now met that disease three times. Chapter 20 called it a pinned sum with a free split, and Chapter 22 showed it as the lasso's unstable selected set. Here it is the importance ranking reshuffling under a bootstrap. So bootstrap the whole fit and watch the ranking move before you write any of it into a research note. Permutation importance on held-out data is the honest replacement.

And finally, when do trees lose? When the truth is smooth and near-linear, because a staircase spends many splits to approximate what one coefficient says exactly. When you must extrapolate, or when the structure is genuinely a low-dimensional linear factor model. And in the thinnest-signal regimes, where a heavily shrunk linear model with an economic argument behind it will often beat a booster that has enough freedom to find beautiful things that are not there.

14Two directions, one minimum

Stand back and look at what this chapter actually was, because it was not three algorithms.

Chapter 22 left us a decomposition with exactly two movable terms and a door. A fully grown tree is the most extreme object we can put on that axis, with bias near nothing and variance enormous, which puts it at the far right end. Everything since has been an approach to the minimum from one side or the other.

Bagging and the random forest approach from the right. Keep the near-zero bias, average the variance away, and when averaging hits the floor ρσ², decorrelate to lower the floor itself. Boosting approaches from the left. Start deliberately rigid where variance is nothing and bias is everything, take small steps downhill against the bias, and stop before you have travelled far enough to start collecting variance.

One U-curve, drawn once — and two ways into its minimum: average from the right, step from the left
σ² — the floor you cannot buy one minimum bias² variance the next tree fits y − F(x) predict first: which arrow spends which term? the overall mean (one number) one fully grown tree Run boosting — step in from the left step — boosting from the overall mean · tap Run bagging — average in from the right average — bagging, forest from one deep tree · tap you are here Ch 20 fit Ch 21 measured Ch 22 decomposed Ch 23 attacked both Ch 24 swaps the loss
Ch 22's U, drawn once. Two ways in: average from the right, step from the left. Run one, then the other.
p = the probability the model gives
one fully grown tree
bias²0.00
variance0.50
σ² floor0.22
✓ prices overfitting
✗ does not repair a leak
✗ does not undo a selection
two directions, one minimum
What you're looking at — Ch 22's U-curve, entered from both ends at once
total error — the U from Ch 22. The dashed gold line is the minimum both routes aim at.
bias² — boosting eats this band from the left: fit the residuals, take a small step, repeat.
variance — bagging squeezes this band from the right: grow deep trees, decorrelate them, average.
σ² — the noise floor. No ensemble buys it, repairs a leak, or undoes a selection.
Fig. 20. Two chapters ago the error was cut into three pieces — bias², variance, and a σ² floor nobody can buy — and that cut is the only reason this chapter had two methods in it rather than three algorithms. Here is the same U, drawn once. The far left of the axis is the emptiest model there is: one number, the overall mean, handed to everybody. Its variance is nothing, because it barely looks at the data, and almost all of its error is bias² — the violet band, fat on the left. The far right is one fully grown tree, split until every training point sits alone in its own box: zero bias, training error zero, and a decision boundary made of slivers. That is the blue band, fat on the right, and it is variance — redraw the sample and the whole tree changes shape. Both ends are bad, and Ch 22 told you the good place is somewhere in between. Now run the arrows. Bagging comes in from the right and never moves along the axis at all: it keeps the deep tree, grows hundreds more on bootstrap resamples, and averages them — so the blue band collapses in place while the violet band does not move a pixel, which is the whole point of a random forest, and why the trick that matters is handicapping each tree to a random subset of features so the trees decorrelate enough for the averaging to actually work. Boosting comes in from the left, starting from that single mean, and each round fits a shallow tree to what is still wrong — the residuals — adding it in with a small learning rate. Watch the violet marker slide down and the blue band thicken behind it: that is bias being paid for with variance, one small step at a time, which is exactly gradient descent from Ch 8 with a function added at each step instead of a parameter nudged. Same building block, opposite mechanisms, one minimum. Then flip the LOSS control. Nothing about the picture changes except what "still wrong" means: the negative gradient stops being y − F(x) and becomes a probability residual, y − p(x) — the same purchase in a new currency, and the door into Ch 24. The ledger on the right is the part that survives the chapter: an ensemble prices overfitting, and prices nothing at all about a leaked pipeline or the two hundred variants you already tried by hand.

Two directions, one minimum, one decomposition. And both are Chapter 22's shape in a new currency, which is to buy the variance back with structure. The structure here is not a fence around the coefficients, but "many fits averaged" in one case and "small steps, stopped early" in the other.

Two things travel with you out of this chapter, and the first is the ledger from Chapter 22, entirely unchanged. An ensemble prices overfitting. It does not repair a leak, and it does not undo a selection. A gradient booster fitted on a pipeline that standardized before splitting is still leaking. A strategy chosen as the best of two hundred backtests is still cursed, no matter how carefully it was early-stopped. These tools make the bias-variance purchase cheaper, and they buy nothing else. That warning matters more here than it did last chapter, because these methods are strong enough to be seductive.

The second is a door, because everything on this page predicted a number. But the negative gradient is defined by the loss, and we watched the target change when we swapped the loss. So hand the machine a loss that scores a probability instead of a squared error, and the identical loop trains a classifier. That is exactly where Chapter 24 starts. And it starts with the honest follow-up question that comes with it: once a model outputs a class, how do you score it without lying to yourself, especially when one class is rare?

iolinked.com
Written by Ajai Raj