ML Atlas

Machine learning from zero · Lesson 5.3 · 28 of 33 lessons ready · 12 min

Gradient boosting: trees that fix each other’s errors

In short

Every tree fixes the errors of the ones before it. After 200 trees at step 1 training falls to 2287, while the test rises to 5944, worse than a line (4095).

Warm-up Two questions from earlier lessons. A minute, and it never blocks the lesson.
From lesson 5.2: Ensembles: why many trees beat one You increase a random forest from 300 to 3,000 trees. What is the most likely effect?

Show the answer

Correct answer: Accuracy stays about the same, while training takes much longer

Once the independent part of the error has averaged out, more trees add computation but little accuracy and no extra memorisation.

From lesson 5.1: Decision trees: twenty questions with data How does a decision tree choose the question at each step?

Show the answer

Correct answer: It tries possible questions and picks the one that leaves the purest groups

The greedy search evaluates candidate splits and keeps the one that lowers impurity, such as the Gini index, the most.

In lesson 5.2 a random forest averaged many trees built independently of each other, and adding trees did no harm. Gradient boosting builds trees one after another. Every new tree learns the residuals, that is, how far the ensemble of trees before it is still off for each example. It adds its correction, multiplied by the learning rate, a number between 0 and 1, to the prediction. The trees are small, often with one question (a stump) or with three. On tables of medium size, tree models, with boosting in front, usually beat neural networks (Grinsztajn et al., 2022). Boosting has one trait a forest does not: later corrections can start correcting noise.

Predict, then check We predict diabetes progression after a year from BMI alone. A straight-line regression has a mean squared error (MSE) of 4095 on the test patients. Gradient boosting adds trees with at most three questions, each with the full step 1. After 200 trees the training MSE falls from 3713 (one tree) to 2287. How does the test do?
How sure are you?

Interactive lab

This interactive lab runs in a browser with JavaScript enabled. The text around it describes what the lab shows.

Show the answer

Correct answer: Worse than the line, because later trees started fixing the training patients' noise

Worse: the test MSE rises to 5944, although training fell to 2287. Every tree learns the residuals of the ones before it, and after a few rounds what is left in the residuals is mostly the random noise of particular patients. On 20 other splits, 200 such trees always lose to the line on the test.

Try it

  • Start where the lab opens: step 1, depth 2, 200 trees. How many steps does the ensemble's function have, and how far apart are the training and validation curves?
  • Move the number of trees down. At how many trees is the validation error lowest? Only then look at the test.
  • Lower the step to 0.1 and take a stump. What does the validation curve look like now as trees are added?
  • Compare boosting's best validation with the dashed line of the straight line. Which one is lower?

Once you have checked it in the lab, let's name what you see.

The idea in plain words

The first tree asks a few questions about BMI and predicts the average disease progression in each group. It is wrong, so we compute the residuals: how much is missing to the truth for each patient. The second tree no longer predicts the disease, only those residuals. Its output times the learning rate is added to the first. The third tree learns what is left after two, and so on. The ensemble grows from small corrections.

At first the corrections catch the real pattern: higher BMI, more disease progression. Once the pattern is caught, what is left in the residuals is mostly noise, the random deviations of the patients the model learns from. Later trees learn exactly that. At step 1 with three-question trees, training falls from 3713 with one tree to 2287 with 200, while the test rises from 4319 to 5944. This is overfitting, as with a tree that is too deep, only this time it grows with the number of trees. In a random forest each tree sees the data on its own, and averaging cancels their errors. In boosting each tree adds itself to the errors of the ones before.

That is why the number of trees, the step and the depth are chosen by validation, not by the test. The lab computes the validation error with 5-fold cross-validation inside the 309 training patients. The lowest is a stump with step 0.1 and 50 trees: validation 3960. The straight line gets 3823 on the same validation, so on BMI alone the line wins. The test confirms it only at the end: 4154 for boosting against 4095 for the line.

Jak to policzono
  • Data: Diabetes (Efron et al., 2004), in scikit-learn under the BSD-3-Clause licence. 442 patients, 10 measurements, disease progression after a year. We take BMI only. Split: 309 patients to learn from, 133 for the test.
  • Model: gradient boosting with regression trees of depth 1 or 2 on BMI alone, squared loss, step 0.05 to 1, up to 200 trees; a straight-line regression on BMI for comparison. The lab's numbers match scikit-learn's GradientBoostingRegressor.
  • Validation: 5-fold cross-validation inside the 309 training patients, the same folds for every setting and for the line.

Why it works

The residual y − F(x) is the direction in which the prediction must move for the squared error to fall fastest. Each tree therefore takes a small step of gradient descent, only in the space of functions instead of weights. Hence the name: gradient boosting. A small step means one tree fixes only part of the error. More trees are needed, but each adds less noise. On 20 other splits, 200 three-question trees at step 0.1 get a test MSE from 3747 to 4802, lower every time than the same trees at step 1 (from 4617 to 6983).

This does not mean boosting always beats a line. On BMI alone the link with the disease is almost linear, and trees can only imitate a line with steps. On 20 other splits, boosting chosen by validation beats the line on the test in 7. Boosting shines where there are many features and their effects are threshold-like and tangled. Then later trees catch dependencies a line cannot express.

An older relative, AdaBoost, changes the weights of the examples instead of using residuals. Examples the ensemble gets wrong weigh more in the next round. In the Atlas entry on AdaBoost, the first stump on breast tumours misclassifies 10.3% of the training tumours. Before round two those misclassified tumours carry exactly 50% of the total weight, so the second stump learns mostly from them.

A common mistake

True or false: “more boosting rounds always help”. False. Every round improves training, but the test only up to a point. At step 1 with three-question trees, 1 tree is best on validation, and 200 trees give 5944 on the test instead of 4319. In the code below the test rises from 4079 after one tree to 6437 after 200, while training falls from 3827 to 2044.

The safe route is a small step, many trees and early stopping from lesson 3.6: stop adding trees when the validation error stops falling. In the code below early stopping keeps 83 of 1000 trees.

Quiz

Three questions to check yourself. Pick an answer, or open the answer under a question. Keyboard: arrow keys choose, Enter or Space confirms.

1. What does the second tree learn in gradient boosting?
Show the answer

Correct answer: The residuals of the first tree, that is, how far off it still is

Every next tree gets the residuals of the ensemble before it and adds a correction times the learning rate. Random samples and independent trees are the random forest.

2. Step 1, three-question trees: training falls from 3713 to 2287, the test rises from 4319 to 5944. What is happening?
Show the answer

Correct answer: Overfitting: the late trees are fixing the training patients' noise

Once the real pattern is caught, noise is what is left in the residuals. Later trees learn it, and on new patients that noise does not repeat.

3. True or false: “more boosting rounds always help”.
Show the answer

Correct answer: False, because after a while the extra rounds only fix the noise left

Training falls with every round, the test does not. At step 1, 1 tree is best on validation, and 200 trees give 5944 on the test.

Key takeaways

  • Gradient boosting builds trees in turn: each learns the residuals of the ensemble before it and adds a correction times the learning rate.
  • Unlike a forest, boosting can overfit: at step 1 the test rises from 4319 to 5944 while training falls.
  • Choose the number of trees, the step and the depth by validation or early stopping, never by the test.
  • Boosting does not always beat a line: on BMI alone the line gets 3823 on validation, the best boosting 3960.

Going further: formula, code, practice

The math

The ensemble after m trees: F_m(x) = F_{m−1}(x) + η · h_m(x), where F_0 is the mean of y on training, η is the learning rate, and the tree h_m fits the residuals r_i = y_i − F_{m−1}(x_i).

For the squared loss L = (y − F)² / 2 the derivative with respect to F is −(y − F), so the residual is minus the gradient of the loss. Each tree takes a step of gradient descent in the space of functions. In AdaBoost the weight of a misclassified example is multiplied by e^α, where α = ln((1 − ε) / ε) and ε is the stump's weighted error.

In code (Python)

The same experiment in scikit-learn: 200 trees at step 1, one stage at a time, then three settings compared by cross-validation inside training, and early stopping.

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split, cross_val_score, KFold
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error as mse

X, y = load_diabetes(return_X_y=True, as_frame=True)
x_tr, x_te, y_tr, y_te = train_test_split(X[["bmi"]], y, test_size=0.3, random_state=0)
big = GradientBoostingRegressor(n_estimators=200, max_depth=2, learning_rate=1.0, random_state=0).fit(x_tr, y_tr)
for m, (p_tr, p_te) in enumerate(zip(big.staged_predict(x_tr), big.staged_predict(x_te)), start=1):
    if m in (1, 10, 50, 200):
        print(f"step 1, depth 2, {m:3d} trees: train MSE {mse(y_tr, p_tr):.0f}, test MSE {mse(y_te, p_te):.0f}")
cv = KFold(5, shuffle=True, random_state=0)
models = {"line": LinearRegression(), "step 1, 200 trees": big,
          "step 0.1, 50 stumps": GradientBoostingRegressor(n_estimators=50, max_depth=1, learning_rate=0.1, random_state=0)}
for name, model in models.items():
    val = -cross_val_score(model, x_tr, y_tr, cv=cv, scoring="neg_mean_squared_error").mean()  # validation inside the training part only
    print(f"{name}: validation MSE {val:.0f}")
stop = GradientBoostingRegressor(n_estimators=1000, max_depth=1, learning_rate=0.1, validation_fraction=0.2, n_iter_no_change=10, random_state=0).fit(x_tr, y_tr)
print(f"early stopping kept {stop.n_estimators_} of 1000 trees; test MSE {mse(y_te, stop.predict(x_te)):.0f}, line {mse(y_te, models['line'].fit(x_tr, y_tr).predict(x_te)):.0f}")

Output

step 1, depth 2,   1 trees: train MSE 3827, test MSE 4079
step 1, depth 2,  10 trees: train MSE 3013, test MSE 4798
step 1, depth 2,  50 trees: train MSE 2122, test MSE 6233
step 1, depth 2, 200 trees: train MSE 2044, test MSE 6437
line: validation MSE 3956
step 1, 200 trees: validation MSE 5728
step 0.1, 50 stumps: validation MSE 4030
early stopping kept 83 of 1000 trees; test MSE 4062, line 3921

The split is different from the lab's, so the numbers are a little different too. The picture is the same: at step 1 training falls from 3827 to 2044, while the test rises from 4079 to 6437. On validation the line (3956) beats the small step (4030), and 200 trees at step 1 do worst (5728). Early stopping keeps 83 trees.

For practitioners
  • In scikit-learn: HistGradientBoostingRegressor and HistGradientBoostingClassifier are fast on large tables, handle missing data themselves and have early stopping (early_stopping). Outside scikit-learn: XGBoost, LightGBM, CatBoost.
  • A typical start: a step of 0.05–0.1, shallow trees, many trees and early stopping on validation. Tune the depth and the step with cross-validation (lesson 3.5), and keep the test for the very end.
  • Boosting is more sensitive to label noise than a forest: every wrong example stays in the residuals and later trees try to fix it. With dirty data a smaller step, row sampling (subsample) and stronger tree regularisation help.
  • Before you reach for boosting, measure a simple baseline (lesson 1.3). When the link is almost linear, a line can win.

Go deeper in the Atlas

Encyclopedia entries on the ideas in this lesson: