ML Study Hub

Home / MLP / Week 10

Week 10 · Random forests, boosting and tuning

Official syllabus line: Decision Trees, Ensemble Learning and Random Forests (Continued)
About 90 minEnd Term material (see exam radar)BSCS2008 · Ashish Tendulkar
Before you start: MLP week 9 (trees, voting, bagging, forests). MLT week 11 (the theory of AdaBoost).
Watch first: the animated story of this weekAbout 1-2 minutes of real 3D animation with a friendly voice. Pause any time.
Read the words of the video
  1. 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.
  2. 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.
  3. 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.
  4. 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

Think of it like this

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 AdaBoostClassifier with decision stumps and read its weights;
  • explain gradient boosting, learning_rate and n_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

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=1 tree).
  • 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):

EnsembleTrain acc.Test acc.
1 stump0.8200.820
AdaBoost, 10 stumps0.8710.847
AdaBoost, 50 stumps0.9400.907
AdaBoost, 200 stumps0.9370.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 εt\varepsilon_t; sklearn stores them in ada.estimator_weights_ and the errors in ada.estimator_errors_. For our first learner the error was 0.180.18 and the stored weight 1.516=ln⁡0.820.181.516=\ln\frac{0.82}{0.18}.

The idea

A factor-of-two detail. The textbook formula (MLT) is αt=12ln⁡1−εtεt\alpha_t=\tfrac12\ln\frac{1-\varepsilon_t}{\varepsilon_t}. scikit-learn's multi-class version (SAMME) uses αt=learning_rate×[ln⁡1−εtεt+ln⁡(K−1)]\alpha_t=\texttt{learning\_rate}\times\big[\ln\frac{1-\varepsilon_t}{\varepsilon_t}+\ln(K-1)\big], which for two classes is ln⁡1−εtεt\ln\frac{1-\varepsilon_t}{\varepsilon_t}, 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 12\frac12.

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:

  1. Fit a small tree to the targets.
  2. Compute the residuals (target minus prediction).
  3. Fit the next small tree to those residuals.
  4. 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_rate ⇒\Rightarrow need more n_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: 2×3×3=182\times3\times3=18. With cv=5: 18×5=9018\times5=\mathbf{90} fits (+1 for the final refit =91=91).

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?

MethodTrainsReducesStrengthsWatch out
Votingdifferent models, in parallelvariancesimple, diverseneeds diverse models
Bagging / pastingsame model, random subsets, parallelvariancerobust, OOB scorelittle help for stable models
Random forestbagged trees + random featuresvariancestrong default, importancesbig models
AdaBooststumps in sequence, re-weightingbiassimple and strongsensitive to noise/outliers
Gradient boostingtrees on residuals, in sequencebias (then variance with shrinkage)often the best accuracy on tablesmany hyperparameters, sequential
Stackingmeta-model on base predictionsbothhighest accuracyslowest, 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

Solved example: 1. Count the fits

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. 3×2×3=183\times2\times3=18 combinations; ×4\times4 folds =72=\mathbf{72}. (73 with the refit.)

Solved example: 2. Learning rate and trees

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.

Solved example: 3. Boosting vs bagging

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

Your turn: 1. Write the grid key

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__.)

Your turn: 2. Stopping

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.

Your turn: 3. Weights

An AdaBoost learner has weighted error 0.10.1. In scikit-learn's two-class SAMME, what is its weight with learning_rate=1? And with the textbook 12ln⁡\frac12\ln formula?

Need a hint?

ln⁡1−εε\ln\frac{1-\varepsilon}{\varepsilon}.

Show the full solution

sklearn: ln⁡0.90.1=ln⁡9=2.197\ln\frac{0.9}{0.1}=\ln9=\mathbf{2.197}. Textbook: 12ln⁡9=1.099\frac12\ln9=\mathbf{1.099}. The final predictions are the same.

Your turn: 4. Soft or stacking?

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.

Your turn: 5. Which statements are true?

(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

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.

Watch out - real exam trap
  • Counting: multiply the list lengths, then ×\times 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 (or base_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 12\frac12.
  • Stacking needs out-of-fold predictions (cv=).

Quick quiz

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
3 x 2 x 2 = 12 combinations, times 5 folds = 60.

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
Gradient boosting fits each new learner to the errors (residuals) left by the ensemble so far.

Lowering the learning rate in gradient boosting usually requires...

  • fewer estimators
  • more estimators
  • no change
  • a deeper tree
Each learner contributes less, so more are needed.

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.
Gradient boosting is sequential, and StackingClassifier uses cv to create out-of-fold predictions.

Recap

Remember this
  • AdaBoost: AdaBoostClassifier(estimator=stump, n_estimators=50, learning_rate=1.0); re-weights hard examples; can overfit noise; sklearn's weight is ln⁡1−εε\ln\frac{1-\varepsilon}\varepsilon (no 12\frac12).
  • 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 ×\times cv.
Formulas to keep
  • AdaBoost (sklearn, 2 classes): αt=ηln⁡1−εtεt\alpha_t=\eta\ln\frac{1-\varepsilon_t}{\varepsilon_t}; textbook 12ln⁡1−εtεt\frac12\ln\frac{1-\varepsilon_t}{\varepsilon_t}
  • Gradient boosting: Fm=Fm−1+η hm(residuals)F_m=F_{m-1}+\eta\,h_m(\text{residuals})
  • Fits =∏(list lengths)×cv=\prod(\text{list lengths})\times\text{cv}; range(a, b, step) excludes bb
Connected weeks (learn them together)

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