Week 10 · Random forests, boosting and tuning
Read the words of the video
- Imagine a team race, not where everyone runs at once, but like a relay! One runner goes first, and when they finish, they hand off to the next. That's how this week's special machine learning teams work. Each model passes its work to the next, helping solve tough problems together.
- This process is called boosting. One simple model tries to solve a problem. Then, it sees where it made mistakes. The next model learns only from those mistakes, trying to fix them. This goes on, round after round, making the whole team much smarter at difficult tasks.
- AdaBoost is a popular boosting method. When a model gets something wrong, AdaBoost makes those specific mistakes more important for the next model to learn. It's like giving extra homework to practice only the problems you missed. This helps the team focus better on tricky parts.
- For your exams, remember to count how many models are trained in a grid search. Multiply the options for each setting, then multiply by the cross-validation folds! So, boosting builds sequential models, fixing mistakes. Stacking uses a referee model. These methods help us build truly powerful learning teams!
The one-minute story
Last week's committees all worked in parallel: many trees, voting at the end. This week's committees work in a relay.
Boosting trains one very simple model, notices what it got wrong, and trains the next model to fix exactly those mistakes. Round after round, the ensemble gets better at the hard cases. Two famous relays:
- AdaBoost: the next learner pays extra attention to the examples that were misclassified (it re-weights them).
- Gradient boosting: the next learner is trained to predict the leftover error (the residual) of the team so far; each learner nudges the answer a little.
Then stacking takes the opposite approach: instead of a simple vote, train a small "referee" model to decide how to combine everyone's answers.
And because all these have many dials, the last skill is tuning an ensemble and counting how many models that search will train.
By the end of this week you can:
- train
AdaBoostClassifierwith decision stumps and read its weights; - explain gradient boosting,
learning_rateandn_estimators, and stop early; - build a
StackingClassifier; - set up and count a grid search over an ensemble using
estimator__...names; - pick between bagging, forests and boosting.
Words you need
- Boosting - building models sequentially, each correcting the previous ones.
- Weak learner - a model slightly better than guessing; typically a stump (
max_depth=1tree). - Residual - what is left of the target after the current model's prediction.
- Learning rate (shrinkage) - how much each new learner is trusted. Smaller means slower, safer learning (and needs more learners).
- Stacking - training a meta-model on the predictions of base models.
- Staged prediction - the ensemble's predictions after 1, 2, 3, ... learners.
Idea 1: AdaBoost in scikit-learn
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
ada = AdaBoostClassifier(estimator=DecisionTreeClassifier(max_depth=1),
n_estimators=50, learning_rate=0.5, random_state=42)
ada.fit(X_train, y_train)
(In older scikit-learn the keyword is base_estimator=; from version 1.2 it is estimator=. The default base learner is a depth-1 tree, a stump.) Defaults: n_estimators=50, learning_rate=1.0.
On the noisy "two moons" data (500 points, 70/30 split):
| Ensemble | Train acc. | Test acc. |
|---|---|---|
| 1 stump | 0.820 | 0.820 |
| AdaBoost, 10 stumps | 0.871 | 0.847 |
| AdaBoost, 50 stumps | 0.940 | 0.907 |
| AdaBoost, 200 stumps | 0.937 | 0.880 |
One stump is a straight cut; fifty stumps combine into a curved boundary. Notice the last row: past a point, more rounds stop helping and can hurt: boosting can overfit noisy data (the hardest, noisiest points keep getting heavier).
How the vote is weighted (MLT week 11). Each learner gets a weight based on its weighted error ; sklearn stores them in ada.estimator_weights_ and the errors in ada.estimator_errors_. For our first learner the error was and the stored weight .
A factor-of-two detail. The textbook formula (MLT) is . scikit-learn's multi-class version (SAMME) uses , which for two classes is , without the half. The two differ by a constant factor 2, which does not change the final prediction (the sign of the weighted sum), only the numbers printed. If a question gives code and asks for printed values, follow scikit-learn; if it asks for the textbook update, use .
Requirements for the base learner: it must support sample weights (trees do). The key hyperparameters: n_estimators, learning_rate (a smaller rate shrinks each learner's contribution; trade it against more estimators), and the base estimator's own settings (max_depth...), which you reach as estimator__max_depth.
Idea 2: Gradient boosting
For regression with squared error the idea is beautifully simple:
- Fit a small tree to the targets.
- Compute the residuals (target minus prediction).
- Fit the next small tree to those residuals.
- Add it to the ensemble, scaled by
learning_rate. Repeat.
The final prediction is the sum of all the trees' (shrunken) outputs.
from sklearn.ensemble import GradientBoostingClassifier # or ...Regressor
gb = GradientBoostingClassifier(n_estimators=300, learning_rate=0.1, max_depth=2,
random_state=42)
Defaults: n_estimators=100, learning_rate=0.1, max_depth=3, subsample=1.0 (set below 1 for stochastic gradient boosting: each tree trains on a random fraction of the data, adding regularisation and speed).
- Smaller
learning_rateneed moren_estimators(a regularising trade-off; a small rate with many trees usually generalises best). - Too many trees overfit. Choose the number by early stopping:
gb = GradientBoostingClassifier(n_estimators=300, learning_rate=0.1, max_depth=2,
n_iter_no_change=10, validation_fraction=0.2)
gb.fit(X_train, y_train)
gb.n_estimators_ # how many trees it actually kept (we got 104)
or by looking at the test error per number of trees with staged_predict:
errors = [1 - (p == y_test).mean() for p in gb.staged_predict(X_test)]
best_n = int(np.argmin(errors)) + 1
On our moons run the test error bottomed out at 27 trees (8.7%) and had crept up to 9.3% by 300 trees: a small but real overfitting.
Also available: HistGradientBoostingClassifier/Regressor (fast, handles missing values, built for big data), and the external libraries XGBoost, LightGBM and CatBoost.
Idea 3: Stacking
Instead of voting by a fixed rule, learn how to combine. The base models make predictions; a final estimator (the "blender", often a logistic regression) is trained on those predictions:
from sklearn.ensemble import StackingClassifier
stack = StackingClassifier(
estimators=[("rf", RandomForestClassifier(random_state=42)),
("svc", SVC(probability=True, random_state=42))],
final_estimator=LogisticRegression(), cv=5)
To avoid the blender learning from predictions on data the base models had already memorised, scikit-learn trains it on out-of-fold predictions (that is what cv=5 does). On moons the stack scored 0.913. Stacking is often the most accurate (and slowest and hardest to explain).
Idea 4: Tuning an ensemble and counting the work
All the nested pieces use the double-underscore rule from week 2. For AdaBoost with a tree base learner:
from sklearn.model_selection import GridSearchCV
params = {"estimator__max_depth": [1, 2],
"n_estimators": [30, 35, 40],
"learning_rate": [0.1, 0.5, 1.0]}
GridSearchCV(AdaBoostClassifier(estimator=DecisionTreeClassifier()), params, cv=5)
Number of combinations: . With cv=5: fits (+1 for the final refit ).
A real exam question describes the specification in words ("base estimator decision tree classifier with max_depth=1; number of estimators from 30 to 40 in steps of 5; learning rate in ...") and asks how many models are trained or which code does the right search. Convert each phrase to a list, multiply the list lengths, then multiply by the number of CV folds.
Idea 5: Which ensemble when?
| Method | Trains | Reduces | Strengths | Watch out |
|---|---|---|---|---|
| Voting | different models, in parallel | variance | simple, diverse | needs diverse models |
| Bagging / pasting | same model, random subsets, parallel | variance | robust, OOB score | little help for stable models |
| Random forest | bagged trees + random features | variance | strong default, importances | big models |
| AdaBoost | stumps in sequence, re-weighting | bias | simple and strong | sensitive to noise/outliers |
| Gradient boosting | trees on residuals, in sequence | bias (then variance with shrinkage) | often the best accuracy on tables | many hyperparameters, sequential |
| Stacking | meta-model on base predictions | both | highest accuracy | slowest, complex |
Advantages of ensembles (a standard question): better accuracy than single models, less overfitting/variance, robustness. Drawbacks: slower to train and predict, less interpretable, more memory.
Solved examples
Question. GridSearchCV(GradientBoostingClassifier(), {"n_estimators": [100, 200, 300], "learning_rate": [0.05, 0.1], "max_depth": [2, 3, 4]}, cv=4). How many fits (without the refit)?
Solution. combinations; folds . (73 with the refit.)
Question. You halve learning_rate from 0.1 to 0.05. What should you do to n_estimators, and why?
Solution. Increase it (roughly double). Each tree now contributes half as much, so more trees are needed to reach the same fit; the small-rate / many-trees combination usually generalises better.
Question. A single deep decision tree has low bias but high variance. Which ensemble idea fits it? And for a stump (high bias)?
Solution. The deep tree: bagging / random forest (averaging reduces variance). The stump: boosting (sequential combination reduces bias).
Your turn
For AdaBoostClassifier(estimator=DecisionTreeClassifier()), how do you put max_depth in a grid?
Need a hint?
Nested parameter: estimator__....
Show the full solution
{"estimator__max_depth": [1, 2, 3]}. (In older scikit-learn the nested name begins base_estimator__.)
You train GradientBoostingClassifier(n_estimators=500, n_iter_no_change=10). The fitted n_estimators_ is 87. Explain.
Need a hint?
Early stopping.
Show the full solution
Validation performance stopped improving for 10 consecutive rounds, so training stopped at 87 trees instead of building all 500.
An AdaBoost learner has weighted error . In scikit-learn's two-class SAMME, what is its weight with learning_rate=1? And with the textbook formula?
Need a hint?
.
Show the full solution
sklearn: . Textbook: . The final predictions are the same.
You want a model that learns which of your 3 base models to trust for which kind of input. Voting or stacking?
Need a hint?
Learned vs fixed.
Show the full solution
Stacking: a meta-model trained on out-of-fold predictions can learn context-dependent trust; voting just averages.
(a) AdaBoost can overfit noisy labels. (b) Gradient boosting trains its trees independently in parallel. (c) A smaller learning_rate usually needs more estimators. (d) Stacking uses a meta-model.
Need a hint?
Re-read the table.
Show the full solution
(a) True. (b) False: sequential. (c) True. (d) True.
Exam radar
MLP End Term: "Brajesh wants to tune hyperparameters of an AdaBoost model for a classification problem with specifications: base estimator decision trees with max_depth=1, number of estimators from 30 to 40 ... (which code, how many models?)", "which of the following advantages of ensemble methods", "Consider the following parameter grid for regression with RandomForest: how many unique combinations?", "which options represent the largest regularisation / lowest overfitting" (including boosting settings), plus base_estimator / estimator naming.
MLT partner: AdaBoost appears in 22 End Term papers, bagging in 14, random forests in 12.
- Counting: multiply the list lengths, then
cv; add 1 only if the refit is counted. range(30, 41, 5)is[30, 35, 40]: three values (the end 41 is excluded, 40 is included).- Nested names:
estimator__max_depth(orbase_estimator__...in old versions). - AdaBoost is sensitive to noise; gradient boosting overfits with too many trees at a high rate.
- scikit-learn's AdaBoost weight has no .
- Stacking needs out-of-fold predictions (
cv=).
Quick quiz
GridSearchCV over {max_depth: [2,3,4], n_estimators: [50,100], learning_rate: [0.1,0.5]} with cv=5 trains how many models (no refit)?
- 12
- 17
- 60
- 30
In gradient boosting, each new tree is fitted to...
- a random sample of the features only
- the residuals of the current ensemble
- the original targets again
- the test set
Lowering the learning rate in gradient boosting usually requires...
- fewer estimators
- more estimators
- no change
- a deeper tree
Select all TRUE statements. (select all that apply)
- AdaBoost re-weights misclassified examples.
- Soft/hard voting combine models trained independently.
- Gradient boosting builds all trees in parallel.
- Stacking cannot use cross-validation.
Recap
- AdaBoost:
AdaBoostClassifier(estimator=stump, n_estimators=50, learning_rate=1.0); re-weights hard examples; can overfit noise; sklearn's weight is (no ). - Gradient boosting: fit residuals, shrink by
learning_rate;n_estimators=100,learning_rate=0.1,max_depth=3,subsample; early stopping (n_iter_no_change,staged_predict). - Stacking: meta-model on out-of-fold predictions.
- Grid over an ensemble:
estimator__max_depth; fits combinations cv.
- AdaBoost (sklearn, 2 classes): ; textbook
- Gradient boosting:
- Fits ;
range(a, b, step)excludes
- MLT week 11 - Bagging, boosting and AdaBoost: Boosting and random forests: the theory, then the code.
Practise with real exam questions
These come straight from past papers (with the real figures and the official answer key). Open 6 real questions on this week All End Term papers