~/teaching · machine learning · session 6, wednesday 16 december 2026
Random forests, gradient boosting and XGBoost, then the honest comparison of two models
Why averaging trees works, how boosting descends a gradient, and when a lead of 0.01 of AUC means nothing.
Abstract
This deck is session 6 of the Machine Learning course at Centrale Méditerranée (option DDEFI, 2026-2027). It starts where the decision trees left off: a deep tree is accurate on its training rows and unstable everywhere else. The morning repairs that instability in two opposite ways. A random forest grows many trees side by side and averages them; boosting grows them one after the other, each one fitted to what the previous ones got wrong.
Each mechanism is derived on the board and then checked on small numbers: the 63.2 % of rows in a bootstrap draw, the variance of an average of correlated trees, the pseudo-residual of the log loss, the optimal leaf value and split gain of XGBoost. The last part asks the question every project eventually meets: the challenger beats the baseline by 0.01 of AUC, is that a result or the luck of the folds? Nested cross-validation and the corrected resampled t-test of Nadeau and Bengio answer it.
After reading, you should be able to explain why decorrelating trees matters more than adding them, read a variable importance without being fooled, write gradient boosting in a few lines of Python, and compare two models with a rule fixed before the results. The running example is the credit default data set of Yeh and Lien (2009) used throughout the course.
Plan of the deck
- Bootstrap and bagging. Resampling the training rows with replacement, and why an average of unstable trees has a lower variance.
- Random forests. Drawing candidate variables at each node to lower the correlation between trees, the out-of-bag error and the settings that matter.
- Variable importance. Impurity-based importance against permutation importance, and why the first one is biased.
- Gradient boosting. AdaBoost for history, then Friedman's boosting as gradient descent over predictions, worked by hand on three points.
- XGBoost and its relatives. Second-order expansion, regularised leaves, histograms and missing values, with LightGBM and CatBoost on one slide.
- Lab: boosting from scratch. A short regressor that reproduces scikit-learn, and an XGBoost whose number of trees is chosen by early stopping.
- The honest comparison. Nested cross-validation, the variance of a cross-validation, the corrected paired test and multiple testing.
- The champion rule. Writing the decision rule before the table, reading the worst errors, and weighing a small gain against its cost.
Bagging: why an average of trees has less variance
If you train many trees on slightly different copies of the data and average them, the wobble of each tree partly cancels out, as long as the trees do not all make the same mistakes.
The expected squared error of a model splits into noise, squared bias and variance. A deep, unpruned tree has a small bias, because it follows the data closely, and a large variance: remove a few rows and the first split changes, and with it the whole tree. Breiman (1996) called such procedures unstable and showed that they are precisely the ones that gain from averaging. An average keeps the bias of its members and only works on the variance.
To get many trees from one data set, bagging (bootstrap aggregating) uses the bootstrap of Efron (1979): draw rows with replacement from the training rows, train a deep tree on the draw, repeat times and average the predictions. A given row is missed by one draw with probability , so by all draws with probability , which tends to .
On the 18,000 training rows of the course, each draw therefore holds about 11,378 distinct rows, and the remaining 36.8 % are out of bag for that tree. The key formula of the section is the variance of an average of trees, each of variance , with pairwise correlation (Hastie, Tibshirani and Friedman, 2009, equation 15.1).
The proof is a counting argument: the variance of a sum is the sum of all covariances, there are diagonal terms worth and cross terms worth , and everything is divided by . The second term fades as trees are added; the first, , is a floor that no number of trees can move.
| Trees | |||
|---|---|---|---|
| 10 | 0.370 | 0.190 | 0.100 |
| 100 | 0.307 | 0.109 | 0.010 |
| 1,000 | 0.3007 | 0.1009 | 0.001 |
Check for ten trees and , using the count: ten diagonal terms and ninety cross terms give , the same as . Going from 100 to 1,000 trees buys 0.006 of variance; bringing from 0.3 down to 0.1 would buy 0.2. The lesson for practice: decorrelate first.
Random forests: decorrelate the trees
A random forest is bagging plus one trick: at each split, a tree may only choose among a few randomly drawn variables, so the trees stop looking alike.
In the credit data one variable, the repayment status of the last month, dominates. Plain bagging puts it at the root of nearly every tree, the trees resemble each other, is high and the variance hits its floor. Breiman's 2001 random forest offers each node only variables drawn out of . With the scikit-learn default for classification, , the dominant variable is available at a given root only about 17 % of the time. Each tree gets slightly worse, the forest gets better. With you are back to bagging.
The algorithm
- Resample. For each of the trees, draw a bootstrap sample of the training rows.
- Grow without pruning. At every node, draw candidate variables and split on the best of them; let the tree grow deep so that its bias stays low.
- Average. Predict with the mean of the trees; scikit-learn averages probabilities, Breiman's original rule takes a majority vote.
Breiman also proved that the forest's error converges as grows: more trees cost time and memory, never accuracy. The number of trees is therefore not a setting that overfits; you raise it until the out-of-bag error stops moving. That error is a free validation: each row is predicted only by the trees whose bootstrap draw missed it, roughly 184 trees out of 500.
max_featuresacts on and is usually the most influential setting (Probst, Wright and Boulesteix, 2019).min_samples_leafsmooths the probabilities: one-row leaves make each tree answer 0 or 1.n_estimatorsonly acts on the vanishing term; it is set by watching the out-of-bag curve, not tuned.- A forest's probabilities tend to avoid 0 and 1, so check the reliability curve before using them with a decision threshold.
Variable importance: MDI is biased, permutation is honest
The importance score built into a forest favours variables with many possible cut points, so measure importance instead by scrambling a column on unseen data and seeing how much the score drops.
The mean decrease in impurity (MDI), the feature_importances_ attribute of scikit-learn, adds up the Gini gains of every split made on variable , weighted by the share of rows reaching the node, and averages over trees. Permutation importance asks a different question: how much validation AUC do we lose if the link between and the target is broken by shuffling its column?
In the deck's illustration, a validation AUC of 0.780 falls to 0.742, 0.748 and 0.745 over three shuffles of the repayment variable, an importance of . Strobl, Boulesteix, Zeileis and Hothorn (2007) explained why MDI misleads. A continuous variable offers thousands of thresholds, and one of them always separates the classes a little by chance; a binary variable gets a single try. MDI is computed on the training rows, where the bottom of a deep tree splits on noise and books it as gain. And it describes what the tree used, not what predicts. In a simulation shown in the deck, a pure noise column comes third under MDI and near zero under permutation.
Remark (The limit of permutation). Correlated variables share the credit: the six monthly bill amounts can replace one another, so shuffling one of them costs little and each looks unimportant while the group matters. Permute the group together, add a noise column as a control, repeat about ten shuffles and treat any importance below two standard deviations as zero. SHAP values take over in the next session.
Gradient boosting: gradient descent over predictions
Boosting builds trees one after another, and each new tree is trained to correct the errors that the sum of the previous trees still makes.
AdaBoost (Freund and Schapire, 1997) reweighted misclassified points so that the next weak learner focused on them. Friedman, Hastie and Tibshirani (2000) read it as an additive model fitted for an exponential loss, which opened the door to any loss. Friedman (2001) then recast boosting as the gradient descent of the course, with one change of unknown: instead of a parameter vector, the unknown is the vector of predictions . The descent direction for client is the pseudo-residual.
A raw descent step would say nothing about a client outside the training set, so a regression tree is fitted to the and added with a small step . For the squared loss the pseudo-residual is the plain error . For the log loss, with , the chain rule and give the model's surprise.
Worked example: three points, stumps, ν = 0.5
Take the points , , and the squared loss. The start is the mean, , so the first residuals are . A stump (a tree with one split) compares the cut at 1.5 (sum of squares 24.5) with the cut at 2.5 (sum 8) and keeps 2.5, predicting on the left and 6 on the right. Half of it is added. In round 2 the residuals are , and now the cut at 1.5 wins (3.125 against 8): the second tree splits where the first did not, because it works on what is left.
| 1 | 1 | 6 | −5 | −3 | 4.5 | −3.5 | −3.5 | 2.75 |
|---|---|---|---|---|---|---|---|---|
| 2 | 5 | 6 | −1 | −3 | 4.5 | 0.5 | 1.75 | 5.375 |
| 3 | 12 | 6 | 6 | 6 | 9 | 3 | 1.75 | 9.875 |
Three settings hang together. A small learning rate (shrinkage) generalises better at the price of more trees: in the deck's simulation, reaches its best validation loss after 5 trees and then degrades, while bottoms out at 87 trees in a wide valley. Trees stay short, because depth sets the order of interactions, and boosting reduces bias by adding weak learners rather than variance by averaging strong ones: Hastie et al. recommend 4 to 8 leaves. The number of trees then becomes an output of early stopping on a validation set.
XGBoost: a Newton step per leaf
XGBoost is the same boosting with a better local approximation of the loss, a penalty on leaves, and engineering that makes it fast and tolerant of missing values.
Chen and Guestrin (2016) replace each client's loss by its second-order Taylor expansion around the current model, with gradient and curvature (for the log loss, and ), and add a penalty per leaf and on the squared leaf values. Since a tree gives the same value to every client of leaf , the objective splits into one parabola per leaf, with a closed-form minimum.
Worked example: four clients, λ = 1, γ = 0
- Gradients. Two defaults predicted at 0.6 and 0.3, two good clients at 0.4 and 0.2: and .
- One leaf. , , so , so everyone goes up a little, because the two missed defaults weigh more.
- Two leaves. Defaults apart: , and for the good clients , against without , which shows how the penalty shrinks small leaves.
- Gain. , far above the 0.000 and 0.095 of the two splits that isolate one client. With no split would pass: pruning is built into growth.
For the squared loss, and without the optimal leaf is the mean of its residuals, which is exactly Friedman's version. Two engineering additions matter in practice: thresholds searched on quantile histograms (256 bins by default) rather than every value, and a learned default direction for missing values at each split. LightGBM (2017) grows trees leaf-wise for speed; CatBoost (2018) uses symmetric trees and ordered target statistics for categorical variables. Once tuned, the three usually land close to one another.
Nested cross-validation: choose inside, measure outside
If you pick the best settings on some folds and then report the score on those same folds, you report the luckiest setting, not the true quality of the model.
Suppose twenty settings all have a true AUC of 0.770 and each cross-validated estimate carries noise with a standard deviation of 0.005. Keeping the best picks the luckiest: the maximum of twenty normal draws sits on average 1.87 standard deviations above the mean, so the reported score is near 0.779 for a model worth 0.770. Varma and Simon (2006) and Cawley and Talbot (2010) showed this selection bias can be as large as the gap between two algorithms.
The defence separates choosing from measuring. An outer loop splits the data into five folds; for each, a full inner cross-validation on the other four chooses the hyperparameters, the winner is retrained on those four and scored once on the held-out fold. The five scores measure the procedure "search, then train", not one fixed model. Early stopping is a setting like the others and happens in the inner loop.
from sklearn.model_selection import GridSearchCV, StratifiedKFold, cross_val_score inner = StratifiedKFold(3, shuffle=True, random_state=1) # to choose outer = StratifiedKFold(5, shuffle=True, random_state=1) # to measure search = GridSearchCV(model, grid, cv=inner, scoring="roc_auc") scores = cross_val_score(search, X, y, cv=outer, scoring="roc_auc") # five honest AUCs; refit search on all rows for the delivered model
A second trap hides in the spread of those scores. With five folds, two training sets share 75 % of their rows, so the fold scores are correlated. Their mean has the same variance as the forest: plus a covariance floor . Bengio and Grandvalet (2004) proved that no unbiased estimator of this variance exists from a single cross-validation. With and , the true variance of the mean is 0.44 while the naive says 0.2.
The corrected resampled t-test of Nadeau and Bengio
Because the folds overlap, the usual paired t-test is far too confident; the correction widens the error bar by a factor fixed in advance by the size of the folds.
Both models are scored on the same folds, so we work on the paired differences (challenger minus baseline), which removes what is specific to each fold. Dietterich (1998) showed that a t-test treating these differences as independent rejects equality far too often. Nadeau and Bengio (2003) replace by , where is the size of the measured fold over the size of the training part: with five folds, with ten. Bouckaert and Frank (2004) confirmed it behaves well for repeated cross-validation.
Worked example: boosting against logistic regression on five folds
- Differences. AUC gaps of 0.014, 0.002, 0.017, 0.006 and 0.011 (illustrative values from the deck): mean , , .
- Naive test. Standard error , , with 4 degrees of freedom: it looks established.
- Correction. Factor instead of 0.2: the variance is multiplied by 2.25 and the standard error by 1.5, to 0.00405.
- Corrected test. , . The 95 % interval, , runs from about to and contains zero: the lead is not established.
The deck's illustrative comparison table makes the same point at scale: with three seeds times five folds, a boosting ahead by 0.010 gets a corrected of 0.07 where the naive one would have printed 0.0008. Comparing three challengers to one baseline adds a further risk: if none truly wins, the chance that at least one test fires is %. The Bonferroni bound, , holds without any independence assumption.
Remark (Write the rule before the table). With four models, two metrics and several thresholds there is always a criterion under which a favourite wins (the "garden of forking paths" of Gelman and Loken). The course asks each team to file its champion rule, including what happens on a tie, as a timestamped issue before running the comparison. And an established gain still has to pay for its complexity: a credit score is a high-risk system under Annex III of the EU AI Act, a refusal must be explained, and a calibrated logistic regression is cheaper to justify, serve and maintain.
Key takeaways
- An average of trees has variance , so adding trees only removes the second term, lowering the correlation removes the floor.
- A random forest is bagging plus a random draw of candidate variables at each node; more trees never overfit, and the out-of-bag error is a free check.
- Impurity-based importance favours variables with many thresholds and is computed on training data; use permutation on the validation set, with a noise column as a control.
- Gradient boosting fits each tree to the pseudo-residuals , which are for the log loss; use a small step, short trees and early stopping.
- XGBoost's leaf value is a regularised Newton step, and its split gain compares the children's score to the parent's minus .
- Choose hyperparameters in an inner loop and measure in an outer one; then compare models on paired differences with the Nadeau and Bengio correction, under a rule written beforehand.
Glossary
- Bootstrap
- A sample of rows drawn with replacement from the training rows; about 63.2 % of distinct rows appear in it.
- Random forest
- Bagging of deep trees in which each node may only split on variables drawn at random out of .
- Out-of-bag error
- The error of each row predicted only by the trees that did not see it in their bootstrap sample.
- Mean decrease in impurity (MDI)
- Sum of the weighted impurity gains of the splits on a variable, averaged over trees; biased towards variables with many thresholds.
- Permutation importance
- Loss of validation score when a column is shuffled, which breaks its link with the target while keeping its distribution.
- Pseudo-residual
- Minus the derivative of the loss with respect to the current prediction, client by client; the target of the next boosting tree.
- Learning rate (shrinkage)
- The factor that multiplies each new tree before it is added; small values generalise better.
- Early stopping
- Stopping the addition of trees when the validation loss has not improved for a set number of rounds.
- XGBoost
- Gradient boosting with a second-order expansion, penalised leaves, histogram split search and native missing values (Chen and Guestrin, 2016).
- Nested cross-validation
- An inner cross-validation that chooses the settings inside each fold of an outer one that measures.
- Corrected resampled t-test
- Paired t-test on fold differences whose variance factor becomes (Nadeau and Bengio, 2003).
- Preregistration
- Writing the decision rule and metric before seeing the results, so that the choice cannot follow the numbers.
The other notes of the course
One public summary per deck, in the order of the course.
Previous: Kernels, SVM and decision treesNext: SHAP, fairness, PCA and clustering
- IntroductionAI winters, jobs and method
- ToolsKaggle, GitHub, Python: the tools
- Session 1Project framing and least squares
- Session 2Maximum likelihood and logistic regression
- Session 3Gradient descent, ridge and lasso
- Session 4Decision threshold and reproducible ML
- HomeworkA defensible baseline model
- Session 5Kernels, SVM and decision trees
- Session 6Forests, boosting, honest comparison
- Session 7SHAP, fairness, PCA and clustering
- HomeworkA documented champion model
- Session 8Neural networks, drift, conformal prediction
- HomeworkA delivered machine learning project
- Session 9Defending a machine learning project
- ReferenceCredit default scoring project
- ReferenceAsset returns: XGBoost and sentiment
- ReferencePython code for every ML model
- ReferenceDeep learning essentials
- ReferenceOpen data ML projects by sector
- ReferenceData and AI engineer handbook
- ReferenceData engineering, SRE and cloud
- ReferencePanorama of machine learning models
About these notes
Written by the lecturer from the slides of the deck, as a public summary: the key ideas, the formulas and one worked example. The exercises, their solutions, the tests and the grading stay in the deck, for the students of the course.
Revision: .