Lesson 17 of 30

Chapter 17 — Ensemble Classification Models

Random Forest • Extra Trees • Gradient Boosting • Histogram-Based Boosting

Combining many decision trees to improve stability, generalization, and predictive accuracy

BRIDGE FROM CHAPTER 16  A single decision tree is easy to read but can have high variance. Ensemble methods deliberately combine many trees so that the weaknesses of one tree are reduced by the collective prediction.

Chapter overview

Ensemble classification replaces the idea of relying on one fitted model with a stronger principle: build several related models and combine their predictions. Tree ensembles are especially important because a single decision tree is flexible but unstable. Random forests and Extra Trees create many diverse trees and average them, while gradient boosting constructs trees sequentially so that each stage improves the mistakes left by the current ensemble.

This chapter develops four practical ensemble families. Random forests use bootstrap resampling and random feature selection to decorrelate trees. Extra Trees introduce even more randomization by drawing candidate split thresholds. Gradient boosting adds shallow trees sequentially under a differentiable loss, with the learning rate and number of estimators controlling how aggressively the ensemble adapts. Histogram-based gradient boosting bins continuous features so large tabular datasets can be processed more efficiently and, in scikit-learn, provides native missing-value support.

CENTRAL IDEA  An ensemble succeeds when its component models are individually useful but make sufficiently different errors. Diversity reduces the chance that the same training-sample accident dominates every prediction.

 

Learning objectives

  • Explain why combining models can reduce variance and improve predictive stability.
  • Distinguish averaging ensembles from sequential boosting ensembles.
  • Explain bootstrap sampling, random feature selection, voting, and out-of-bag evaluation in a random forest.
  • Compare random forests with Extra Trees and explain the effect of randomized split thresholds.
  • Describe gradient boosting as sequential correction under a loss function and relate weak learners to ensemble strength.
  • Explain the roles of learning_rate, n_estimators, tree depth, and regularization in the bias-variance trade-off.
  • Explain why histogram binning accelerates gradient-boosted tree training on larger datasets.
  • Use native missing-value handling in histogram-based gradient boosting when appropriate.
  • Compare decision trees, random forests, and gradient boosting under one leakage-safe experimental protocol.
  • Interpret impurity-based and permutation feature importance cautiously and avoid causal conclusions.

Table 17.1. Chapter structure

Section

Focus

Student outcome

17.1Motivation for ensemblesExplain averaging, diversity, variance reduction, and stability
17.2Random forestConnect bootstrap samples, random features, voting, OOB scoring, and importance
17.3Extra TreesExplain additional split randomness and compare speed, bias, and variance
17.4Gradient boostingUnderstand sequential correction, weak learners, shrinkage, and complexity
17.5Histogram gradient boostingUse binned features, efficient training, and missing-value support
17.6Model comparisonSelect an ensemble according to accuracy, interpretability, compute, and data size
LabTree vs forest vs boostingRun a fair model comparison and protect the final test set

 

17.1 Motivation for ensemble learning

A decision tree can adapt to nonlinear relationships and interactions, but it is sensitive to the exact observations used for training. If a slightly different sample changes the first few splits, the entire downstream tree can change. Ensemble learning addresses this instability by combining the predictions of multiple fitted models instead of trusting one model structure.

17.1.1 Combining multiple models

Figure 17.1. The ensemble principle

MODEL 1

MODEL 2

MODEL 3

COMBINE

different sample or randomness

 

different sample or randomness

 

different sample or randomness

 

vote or average probabilities

 

The component models are often called base estimators or base learners. In tree ensembles, each base estimator is a decision tree produced under a different sample, feature subset, split randomization, or stage of boosting. The final classifier aggregates these component outputs into one prediction.

Table 17.2. Two major ensemble strategies

Strategy

How models are built

How predictions combine

Typical examples

Averaging / bagging-styleModels are trained largely independently with injected randomnessAverage class probabilities or aggregate votesRandom forest, Extra Trees
BoostingModels are added sequentially, each improving the current ensembleSum stage contributions, then transform to probabilities/classesGradient boosting, histogram gradient boosting

 

17.1.2 Reducing variance

Variance describes sensitivity to the particular training sample. A high-variance model can fit one dataset extremely well but change noticeably when observations are added, removed, or resampled. Deep decision trees are a classic high-variance model family. Averaging several diverse trees can cancel part of this sample-specific noise because an unusual branch that appears in one tree is unlikely to appear in exactly the same form in every tree.

VARIANCE INTUITION  Averaging is most effective when the individual models are not perfectly correlated. If every tree makes the same mistake for the same reason, adding more identical trees does not remove that error.

 

This is why random forests inject randomness at two levels: the training observations are resampled and the candidate features considered at each split are restricted. The goal is not to make every tree maximally accurate by itself. The goal is to create a collection whose averaged prediction generalizes well.

17.1.3 Improving predictive stability

Stability refers to how much the model output changes under reasonable perturbations of the training data or random seed. An ensemble can be more stable than a single tree because the final decision is distributed across many structures. One tree might classify an observation differently after a small resample, while a forest of hundreds of trees may preserve nearly the same average probability.

PYTHON  •   Observe instability of single trees versus a forest
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier

X, y = load_breast_cancer(return_X_y=True)
query_probabilities = {"tree": [], "forest": []}

for seed in range(20):
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, stratify=y, random_state=seed
    )

    tree = DecisionTreeClassifier(random_state=seed)
    forest = RandomForestClassifier(
        n_estimators=300,
        max_features="sqrt",
        random_state=seed,
        n_jobs=-1,
    )

    tree.fit(X_train, y_train)
    forest.fit(X_train, y_train)

     query_probabilities["tree"].append(tree.predict_proba(X_test[[0]])[0, 1])
     query_probabilities["forest"].append(forest.predict_proba(X_test[[0]])[0, 1])

print("Tree probabilities:", query_probabilities["tree"][:5])
print("Forest probabilities:", query_probabilities["forest"][:5])

 

EXPERIMENTAL CAUTION  The query row changes when the split seed changes in this short demonstration. For a formal stability study, hold a fixed external query set and repeatedly resample only the training data.

 

17.1.4 Increasing model accuracy

Higher accuracy is a possible consequence of ensembling, not an automatic guarantee. A random forest may substantially outperform one unrestricted tree because variance is reduced. Gradient boosting may achieve even higher predictive accuracy on structured tabular data by correcting systematic residual errors. But ensemble gains depend on the dataset, metrics, hyperparameters, leakage control, and evaluation design.

MODEL-SELECTION RULE  Do not choose an ensemble because it is more sophisticated. Choose it because it produces a meaningful, reproducible improvement under the metric and deployment constraints defined for the problem.

 

17.2 Random forest

A random forest is an averaging ensemble of decision trees. Each tree is trained with controlled randomness so the trees do not all reproduce the same structure. For classification, the forest combines the trees through class-probability averaging or an equivalent aggregate decision. The resulting model is usually less sensitive to sampling noise than a single deep tree.

Figure 17.2. Random forest training and prediction

BOOTSTRAP

RANDOM FEATURES

MANY TREES

AVERAGE

sample rows with replacement

 

subset of features at each split

 

fit trees independently

 

combine class probabilities

 

17.2.1 Bootstrap samples

A bootstrap sample is created by drawing training observations with replacement. If the training set contains n rows, a bootstrap sample also contains n draws by default, but some original rows can appear more than once and others may not appear at all. Different trees receive different bootstrap samples, which helps create diversity.

OUT-OF-BAG INTUITION  For a bootstrap sample of size n drawn from n training rows, an individual row is left out of a particular sample with probability close to 36.8% when n is large. These unused rows form that tree’s out-of-bag observations.

 

PYTHON  •   Inspect bootstrap behavior with resampling
import numpy as np

rng = np.random.default_rng(42)
n = 12
sample_indices = rng.choice(n, size=n, replace=True)
selected = set(sample_indices.tolist())
out_of_bag = sorted(set(range(n)) - selected)

print("Bootstrap draw:", sample_indices)
print("Unique selected rows:", len(selected))
print("Out-of-bag rows:", out_of_bag)

 

17.2.2 Multiple decision trees

Each bootstrap sample is used to fit a decision tree. Forest trees are often allowed to grow relatively deep because the ensemble will average their predictions. A single deep tree may overfit strongly, but hundreds of diverse deep trees can still produce a stable aggregate. Parameters such as max_depth, min_samples_leaf, and max_leaf_nodes remain available when stronger regularization is needed.

The number of trees is controlled by n_estimators. Increasing n_estimators usually makes the aggregate prediction more stable until the performance plateaus. It also increases training time, prediction time, and model size. Unlike tree depth, adding more trees normally does not create the same kind of overfitting curve; the main practical question is when additional trees stop producing a useful improvement.

17.2.3 Random feature selection

If every tree could consider every feature at every split, a very strong predictor might dominate the root of most trees, making the forest highly correlated. Random forests therefore consider only a random subset of features at each candidate split. The max_features parameter controls the size of that subset. For classification, sqrt is a common default because it creates substantial diversity while preserving informative choices.

Table 17.3. Sources of diversity in a random forest

Mechanism

What changes

Why it helps

Bootstrap rowsEach tree sees a different resampled training setPerturbs the observations that influence the learned rules
Random feature subsetEach split sees only part of the feature setPrevents the same dominant feature from controlling every tree
Random tie behaviorEquivalent candidate choices may resolve differentlyAdds minor additional structural diversity

 

17.2.4 Majority voting and probability averaging

A simplified classroom explanation says that the trees vote for a class and the majority wins. In scikit-learn’s RandomForestClassifier, the class probabilities predicted by the individual trees are averaged, and the final class is selected from those averaged probabilities. This distinction matters when leaf class distributions are not pure.

FOREST PROBABILITY  For class k, the forest probability can be viewed as the average of the class-k probabilities produced by the fitted trees. The final label is the class with the largest averaged probability.

 

17.2.5 Training a random forest

PYTHON  •   Fit a random forest with reproducible settings
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, roc_auc_score

data = load_breast_cancer(as_frame=True)
X = data.data
y = data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.25,
    stratify=y,
    random_state=42,
)

forest = RandomForestClassifier(
    n_estimators=400,
    max_features="sqrt",
    min_samples_leaf=2,
    bootstrap=True,
    oob_score=True,
    random_state=42,
    n_jobs=-1,
)
forest.fit(X_train, y_train)

pred = forest.predict(X_test)
prob = forest.predict_proba(X_test)[:, 1]

print("Test accuracy:", round(accuracy_score(y_test, pred), 3))
print("Test ROC AUC:", round(roc_auc_score(y_test, prob), 3))
print("OOB score:", round(forest.oob_score_, 3))

 

REPRODUCIBILITY NOTE  Set random_state when you want to reproduce the same bootstrap draws and randomized feature choices. n_jobs=-1 uses available CPU cores for independent tree operations and can reduce training time.

 

17.2.6 Out-of-bag evaluation

Out-of-bag evaluation uses observations that were not included in a tree’s bootstrap sample. For each training observation, the forest can aggregate predictions only from trees for which that observation was out of bag. These predictions provide an internal estimate of generalization without creating a separate validation split for that specific purpose.

In scikit-learn, set oob_score=True together with bootstrap=True. The fitted classifier exposes oob_score_ and oob_decision_function_. The OOB estimate is useful for quick diagnostics and for following forest convergence, but it does not replace a carefully designed test set, group-aware split, temporal validation, or cross-validation when the data structure requires them.

PYTHON  •   Inspect out-of-bag class probabilities
oob_probabilities = forest.oob_decision_function_

print("OOB probability matrix shape:", oob_probabilities.shape)
print("First OOB probability vector:", oob_probabilities[0])
print("OOB score:", forest.oob_score_)

 

OOB LIMITATION  A row may receive too few OOB predictions when the forest contains very few trees. Use enough estimators, and do not interpret OOB scoring as protection against time leakage, entity leakage, or an invalid target definition.

 

17.2.7 Feature importance

A fitted random forest exposes impurity-based feature_importances_. The importance of a feature accumulates the weighted impurity reduction produced by splits using that feature across the ensemble. This can be useful for exploratory ranking, but it is model-specific and can favor continuous or high-cardinality variables with many possible split points. Correlated features can also divide or substitute for one another’s importance.

PYTHON  •   Rank impurity-based forest importance
import pandas as pd

importance = pd.Series(
    forest.feature_importances_,
    index=X_train.columns,
).sort_values(ascending=False)

print(importance.head(10))

 

Permutation importance provides a more model-agnostic alternative. It measures how much a chosen evaluation score deteriorates when one feature is randomly permuted while the others remain unchanged. It should be computed on validation or test-like data rather than only on the training data.

PYTHON  •   Compute permutation importance on held-out data
from sklearn.inspection import permutation_importance

result = permutation_importance(
    forest,
    X_test,
    y_test,
    scoring="roc_auc",
    n_repeats=20,
    random_state=42,
    n_jobs=-1,
)

permutation_rank = pd.Series(
    result.importances_mean,
    index=X_test.columns,
).sort_values(ascending=False)

print(permutation_rank.head(10))

 

INTERPRETATION CAUTION  Feature importance is not causal importance. A feature can be predictive because it is correlated with an outcome, a proxy, a workflow artifact, or even a leakage variable. Always check availability at prediction time and domain plausibility.

 

17.3 Extra Trees

Extremely Randomized Trees, implemented by ExtraTreesClassifier, form another ensemble of randomized decision trees. Like random forests, they use random subsets of candidate features. The main additional randomization is the split threshold: instead of exhaustively searching the best threshold for each candidate feature, random thresholds are drawn and the best among those randomized candidates is selected.

Figure 17.3. Random forest versus Extra Trees at a split

RANDOM FOREST

CANDIDATE FEATURES

EXTRA TREES

search strong thresholds

 

random feature subset

 

draw random thresholds

 

17.3.1 Increased split randomness

Extra randomness can decorrelate the fitted trees even when they see the same training observations. This can lower variance further, although the additional randomness can increase bias because the algorithm may not choose the locally optimal threshold available for the current node.

BIAS-VARIANCE TRADE-OFF  Extra Trees typically trades a little more bias for lower correlation and potentially lower variance. Whether this improves validation performance is empirical and dataset-dependent.

 

17.3.2 Differences from random forests

Table 17.4. Random forest and Extra Trees

Aspect

Random forest

Extra Trees

Training rowsBootstrap sampling is commonly usedUses the full training sample by default; bootstrap is optional
Feature candidatesRandom subset at each splitRandom subset at each split
Threshold selectionSearches candidate thresholds to optimize the split criterionDraws random thresholds and selects among them
Tree correlationReduced by row and feature randomnessOften reduced further by threshold randomness
Typical biasModerateCan be slightly higher
Typical varianceLower than a single treeCan be lower still when randomness decorrelates trees
ParallelismTrees can be fit in parallelTrees can be fit in parallel

 

17.3.3 Speed and variance considerations

Random threshold generation can reduce the amount of split-search work, so Extra Trees may train faster than a comparable random forest on some datasets. The exact runtime depends on sample size, feature count, tree depth, data representation, number of estimators, hardware, and parallelism. The stronger randomization also means that Extra Trees should not automatically be described as “better” or “worse”; it should be evaluated under the same cross-validation and resource constraints as the random forest.

PYTHON  •   Compare random forest and Extra Trees under the same folds
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.model_selection import StratifiedKFold, cross_validate

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

models = {
    "random_forest": RandomForestClassifier(
        n_estimators=400,
        max_features="sqrt",
        min_samples_leaf=2,
        random_state=42,
        n_jobs=-1,
    ),
    "extra_trees": ExtraTreesClassifier(
        n_estimators=400,
        max_features="sqrt",
        min_samples_leaf=2,
        random_state=42,
        n_jobs=-1,
    ),
}

for name, model in models.items():
    scores = cross_validate(
        model,
        X_train,
        y_train,
        cv=cv,
        scoring=["accuracy", "roc_auc"],
        return_train_score=False,
        n_jobs=-1,
    )
    print(
        name,
        "accuracy=", round(scores["test_accuracy"].mean(), 3),
        "roc_auc=", round(scores["test_roc_auc"].mean(), 3),
    )

 

CURRENT IMPLEMENTATION NOTE  In current scikit-learn, ExtraTreesClassifier uses bootstrap=False by default; out-of-bag scoring is therefore available only when bootstrap=True is explicitly enabled.

 

17.4 Gradient boosting

Gradient boosting builds an additive model sequentially. The first stage creates an initial prediction. Each later tree is fitted to improve the current ensemble according to the selected loss function. Instead of averaging many independent trees, the algorithm constructs a coordinated sequence in which later stages focus on remaining errors.

Figure 17.4. Sequential correction in gradient boosting

INITIAL MODEL

TREE 1

TREE 2

TREE 3

FINAL

starting prediction

 

correct current errors

 

correct remaining errors

 

refine again

 

sum stage outputs

 

17.4.1 Sequential correction of errors

For squared-error regression, boosting is often introduced as fitting residuals. For classification, the underlying formulation is more general: each stage fits a tree to the negative gradient of the classification loss with respect to the current model score. The practical intuition remains useful—new trees concentrate on aspects of the data that the current ensemble handles poorly.

ADDITIVE MODEL  A boosting ensemble can be written conceptually as F_M(x) = F_0(x) + learning_rate × Σ h_m(x), where each h_m is a fitted stage and M is the number of boosting iterations.

 

17.4.2 Weak learners

Boosting commonly uses shallow decision trees as weak learners. A weak learner is not expected to solve the entire classification problem independently. Its role is to contribute a modest, structured improvement that becomes useful after many stages are combined. Tree depth therefore has a different interpretation than in a single decision tree: shallow trees often work well because the sequence can gradually build complex decision boundaries.

Table 17.5. Tree depth in boosting

Tree size

What each stage can represent

Typical effect

Decision stump / very shallowSimple threshold or low-order interactionHigh bias per tree, strong regularization
Depth 2–3Moderate interactionsCommon practical compromise
Deep treesComplex local structure per stageLower bias but higher variance and greater overfitting risk

 

17.4.3 Learning rate

The learning rate scales the contribution of each new tree. A smaller learning_rate means the ensemble changes more cautiously at each stage and usually needs more estimators. This shrinkage can improve generalization because no single tree is allowed to dominate the fit. A large learning rate reaches a strong training fit quickly but can overshoot useful structure or overfit earlier.

COUPLED HYPERPARAMETERS  learning_rate and n_estimators should be considered together. Reducing the learning rate without increasing the number of stages can underfit; increasing the number of stages without sufficient shrinkage can overfit.

 

17.4.4 Number of estimators

n_estimators controls how many boosting stages are added in GradientBoostingClassifier. Too few stages leave systematic errors uncorrected. Too many can fit noise, especially when individual trees are deep or the learning rate is high. Validation curves or cross-validation help identify a useful region.

PYTHON  •   Train a gradient boosting classifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score

gb = GradientBoostingClassifier(
    n_estimators=250,
    learning_rate=0.05,
    max_depth=2,
    min_samples_leaf=5,
    random_state=42,
)
gb.fit(X_train, y_train)

pred = gb.predict(X_test)
prob = gb.predict_proba(X_test)[:, 1]

print("Accuracy:", round(accuracy_score(y_test, pred), 3))
print("ROC AUC:", round(roc_auc_score(y_test, prob), 3))

 

17.4.5 Inspecting the number of stages

GradientBoostingClassifier exposes staged prediction methods that make it possible to evaluate the partially built ensemble after each boosting iteration. This is useful for understanding when validation performance stops improving.

PYTHON  •   Build a validation curve across boosting stages
import matplotlib.pyplot as plt
from sklearn.metrics import roc_auc_score

train_auc = []
valid_auc = []

for train_prob, valid_prob in zip(
    gb.staged_predict_proba(X_train),
    gb.staged_predict_proba(X_test),
):
     train_auc.append(roc_auc_score(y_train, train_prob[:, 1]))
     valid_auc.append(roc_auc_score(y_test, valid_prob[:, 1]))

plt.figure(figsize=(8, 4.5))
plt.plot(train_auc, label="Training ROC AUC")
plt.plot(valid_auc, label="Validation ROC AUC")
plt.xlabel("Boosting stage")
plt.ylabel("ROC AUC")
plt.title("Performance across gradient boosting stages")
plt.legend()
plt.tight_layout()
plt.show()

 

EVALUATION DISCIPLINE  The code above uses X_test as a validation example only for illustration. In a real workflow, reserve a validation set or cross-validation for stage selection and keep the final test set untouched.

 

17.4.6 Tree depth and interactions

The max_depth parameter controls the depth of each regression tree used internally by GradientBoostingClassifier. A depth-1 tree can represent one main split at a stage, while deeper trees can model conditional interactions. Because hundreds of stages can be added, moderate depths often provide enough flexibility for tabular classification without making every stage excessively complex.

17.4.7 Bias-variance trade-off

Table 17.6. Gradient boosting complexity controls

Parameter change

Likely training effect

Generalization risk

Increase n_estimatorsMore opportunities to reduce training lossCan overfit if continued too far
Decrease learning_rateSmaller correction at each stageMay need more stages and training time
Increase max_depthMore complex correction per stageHigher variance and interaction complexity
Increase min_samples_leafSmoother, less local leavesCan increase bias but reduce overfitting
Use subsample < 1Introduces stochastic boostingCan reduce variance but adds randomness

 

PRACTICAL TUNING ORDER  Start with shallow trees, choose a conservative learning rate, then adjust the number of stages with validation. Only add depth when the simpler boosted model clearly underfits.

 

17.5 Histogram-based gradient boosting

Traditional gradient boosting repeatedly evaluates ordered feature values while constructing trees. Histogram-based gradient boosting first bins continuous values into a limited number of discrete intervals. Split search then operates on compact bin statistics rather than sorting every raw feature value at every node. This can make training substantially faster on larger datasets.

Figure 17.5. Histogram-based split search

RAW VALUES

BIN VALUES

HISTOGRAMS

SPLITS

continuous measurements

 

map to integer bins

 

aggregate bin stats

 

search bin boundaries

 

17.5.1 Efficient processing of larger datasets

The computational advantage becomes important when the dataset contains many thousands of observations or more. Scikit-learn’s user guide notes that HistGradientBoostingClassifier can be orders of magnitude faster than GradientBoostingClassifier when sample sizes are above tens of thousands. For small datasets, the traditional implementation can remain competitive because binning itself approximates the available split points.

The number of boosting iterations is controlled by max_iter rather than n_estimators. Other important controls include learning_rate, max_leaf_nodes, max_depth, min_samples_leaf, l2_regularization, and early_stopping.

17.5.2 Handling continuous variables

Continuous features are quantized into bins. Observations in the same bin are treated together during split search, which reduces the cost of evaluating candidate thresholds. The model still learns nonlinear tree-based relationships; the bins are an internal computational representation rather than a requirement that the analyst manually discretize every feature.

DO NOT PRE-BIN AUTOMATICALLY  Manual discretization before HistGradientBoostingClassifier is usually unnecessary unless the bins have domain meaning. Let the estimator construct its own internal histograms so binning remains part of the fitted model.

 

17.5.3 Missing-value support

HistGradientBoostingClassifier has native support for NaN values. During training, the tree grower learns whether missing observations should be sent to the left or right child at each split according to the gain. At prediction time, missing values follow the learned direction. This can remove the need for a separate numerical imputation step when NaN has a meaningful and stable interpretation in the data pipeline.

PYTHON  •   Fit histogram gradient boosting with missing values
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier

X_missing = X_train.copy()

# Educational example: insert missing values in one feature.
rng = np.random.default_rng(42)
rows = rng.choice(len(X_missing), size=25, replace=False)
X_missing.iloc[rows, 0] = np.nan

hist_gb = HistGradientBoostingClassifier(
    learning_rate=0.08,
    max_iter=250,
    max_leaf_nodes=15,
    min_samples_leaf=10,
    l2_regularization=0.1,
    random_state=42,
)
hist_gb.fit(X_missing, y_train)

print("Training score:", round(hist_gb.score(X_missing, y_train), 3))

 

MISSING DOES NOT MEAN HARMLESS  Native NaN support prevents a technical failure, but the meaning of missingness still requires domain analysis. A missing value caused by future workflow, selective measurement, or data collection bias can remain a leakage or fairness problem.

 

17.5.4 Traditional versus histogram gradient boosting

Table 17.7. Two scikit-learn gradient boosting implementations

Aspect

GradientBoostingClassifier

HistGradientBoostingClassifier

Split representationUses raw ordered feature valuesUses binned feature values and histograms
Iteration parametern_estimatorsmax_iter
Large datasetsCan become slower as sample size growsDesigned for efficient larger-data training
Missing valuesTypically require preprocessingNaN values supported natively
Small datasetsCan be attractive because exact split candidates remain availableBinning can be an unnecessary approximation
Feature importance attributeProvides impurity-based feature_importances_Prefer model-agnostic approaches such as permutation importance

 

PYTHON  •   Compare traditional and histogram boosting runtimes
from time import perf_counter
from sklearn.ensemble import GradientBoostingClassifier, HistGradientBoostingClassifier

models = {
    "gradient_boosting": GradientBoostingClassifier(
        n_estimators=200,
        learning_rate=0.05,
        max_depth=2,
        random_state=42,
    ),
    "hist_gradient_boosting": HistGradientBoostingClassifier(
        max_iter=200,
        learning_rate=0.05,
        max_leaf_nodes=15,
        random_state=42,
    ),
}

for name, model in models.items():
    start = perf_counter()
    model.fit(X_train, y_train)
    elapsed = perf_counter() - start
    print(name, "seconds=", round(elapsed, 4))

 

RUNTIME EXPERIMENT  Measure runtime on the dataset and hardware that matter to your project. A speed claim from a small classroom dataset does not automatically generalize to production scale.

 

17.6 Comparing ensemble methods

Random forests, Extra Trees, gradient boosting, and histogram gradient boosting all use collections of trees, but they solve different modeling problems. Forest methods emphasize diversity and averaging. Boosting emphasizes sequential correction. The best choice depends on predictive accuracy, data size, missingness, interpretability expectations, latency, memory, training budget, and tuning capacity.

17.6.1 Random forest for robust general performance

A random forest is often a strong first nonlinear ensemble for tabular data. It requires relatively little feature scaling, tolerates complex interactions, offers straightforward parallel training across trees, provides OOB diagnostics, and usually reduces the instability of a single tree. It is therefore an excellent benchmark when a decision tree is too variable but a heavily tuned boosting system is not yet justified.

17.6.2 Gradient boosting for high predictive accuracy

Gradient boosting is often highly competitive on structured tabular datasets because later stages can focus on systematic errors left by earlier stages. The benefit comes with more interacting hyperparameters and a sequential training process. Learning rate, number of stages, tree complexity, subsampling, regularization, and early stopping should be validated carefully rather than selected from training performance.

17.6.3 Interpretability and computation trade-offs

A small single tree can be explained as explicit rules. An ensemble containing hundreds of trees cannot be interpreted in the same direct way even though each component is a tree. Global importance, permutation importance, partial dependence, accumulated local effects, SHAP-style explanations, and local surrogate tools can summarize behavior, but they are explanations of a complex system rather than a replacement for a simple rule list.

Table 17.8. Practical comparison of tree classifiers

Criterion

Decision tree

Random forest

Extra Trees

Gradient boosting

Hist gradient boosting

Main mechanismOne recursive treeBootstrap + random features + averagingRandom features + random thresholds + averagingSequential additive correctionSequential correction using histogram bins
VarianceHigh when unrestrictedUsually much lowerOften very low through extra randomizationControlled by shrinkage and tree sizeControlled by shrinkage, leaf limits, regularization
Training parallelismSingle treeHigh across treesHigh across treesStages are sequentialStages are sequential but split computation is optimized
Scaling neededUsually noUsually noUsually noUsually noUsually no
OOB evaluationNoYes with bootstrapOnly if bootstrap is enabledPossible only in stochastic settings; CV often preferredUse validation / CV / early stopping
Native NaN support in current sklearnEstimator-dependent / check versionCurrent implementations have specific missing-value behavior; validate carefullyCurrent implementation supports NaN random splitsTypically preprocess NaNYes
Direct rule interpretabilityHigh for small treeLow as an ensembleLow as an ensembleLow as an ensembleLow as an ensemble
Typical first useInterpretable baselineRobust nonlinear benchmarkFast randomized benchmarkAccuracy-focused tabular modelLarger tabular data and/or NaNs

 

SELECTION PRINCIPLE  Compare models under identical folds, features, preprocessing, metrics, and resource accounting. A 0.2-point metric gain may not justify a 10× increase in latency, memory, tuning effort, or explanation complexity.

 

17.6.4 A compact decision guide

Table 17.9. Which model should I try?

Situation

Good starting candidate

Reason

Need a readable rule structureConstrained decision treeDirect paths and thresholds are visible
Single tree is unstableRandom forestAveraging reduces sampling variance
Want stronger randomization and fast tree ensembleExtra TreesRandom thresholds decorrelate trees
Need very strong tabular accuracy and can tuneGradient boostingSequential correction can capture subtle structure
Large tabular datasetHistGradientBoostingClassifierHistogram split search is optimized for scale
Numerical features contain NaNsHistGradientBoostingClassifierNative missing-value routing
Need OOB diagnosticRandomForestClassifierOOB estimates are directly supported with bootstrap

 

Practical lab — Compare a decision tree, random forest, and gradient boosting classifier

Students compare three tree-based classifiers under one controlled binary-classification experiment. The Breast Cancer Wisconsin diagnostic dataset is used because it is built into scikit-learn, contains only numerical features, and allows the lab to focus on model behavior rather than data acquisition. The exercise is educational and must not be interpreted as a clinical decision system.

Lab objectives

  • Create stratified training, validation, and test subsets.
  • Train one constrained decision tree, one random forest, and one gradient boosting classifier.
  • Compare training and validation accuracy, balanced accuracy, ROC AUC, runtime, and model complexity.
  • Inspect the random forest out-of-bag score without using it as the only model-selection criterion.
  • Plot validation ROC curves for the three models.
  • Select a model using a predeclared validation rule before touching the final test set.
  • Refit the selected configuration on development data and perform one final test evaluation.
  • Explain why the most accurate model is not automatically the best operational model.

Table 17.10. Practical lab design

Element

Choice

Reason

DatasetBreast Cancer Wisconsin (diagnostic)Built-in numerical binary classification dataset
Split60% train / 20% validation / 20% testSeparates model comparison from final evaluation
StratificationUse target labels in both splitsPreserves class proportions approximately
ScalingNoneTree splits are based on order and thresholds rather than feature scale
Primary selection metricValidation ROC AUCUses ranking information across thresholds
Secondary metricBalanced accuracyMakes both classes contribute equally to the score
Tie rulePrefer lower validation time/complexity when scores are practically tiedAvoid unnecessary complexity
Random state42Reproducible split and randomized model behavior

 

CLASS MEANING  In this dataset, scikit-learn encodes malignant as 0 and benign as 1. Always attach the class names to class-specific precision, recall, and confusion-matrix results.

 

Step 1 — Load the dataset and tools

PYTHON  •   Load data and evaluation functions
from time import perf_counter
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import (
    accuracy_score,
    balanced_accuracy_score,
    classification_report,
    confusion_matrix,
    roc_auc_score,
    roc_curve,
)
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

data = load_breast_cancer(as_frame=True)
X = data.data
y = data.target

print("Shape:", X.shape)
print("Class names:", list(data.target_names))
print(y.value_counts().sort_index())

 

Step 2 — Create protected subsets

PYTHON  •   Create 60/20/20 stratified partitions
X_train, X_temp, y_train, y_temp = train_test_split(
    X,
    y,
    test_size=0.40,
    stratify=y,
    random_state=42,
)

X_valid, X_test, y_valid, y_test = train_test_split(
    X_temp,
    y_temp,
    test_size=0.50,
    stratify=y_temp,
    random_state=42,
)

print("Train:", X_train.shape)
print("Validation:", X_valid.shape)
print("Test:", X_test.shape)

 

PROTECTED TEST SET  Do not use X_test or y_test to choose hyperparameters, select a model family, choose a threshold, or redesign features. The test set is opened only after the comparison rule is frozen.

 

Step 3 — Define three comparable models

PYTHON  •   Create the candidate classifiers
models = {
    "decision_tree": DecisionTreeClassifier(
        max_depth=4,
        min_samples_leaf=5,
        random_state=42,
    ),
    "random_forest": RandomForestClassifier(
        n_estimators=400,
        max_features="sqrt",
        min_samples_leaf=2,
        bootstrap=True,
        oob_score=True,
        random_state=42,
        n_jobs=-1,
    ),
    "gradient_boosting": GradientBoostingClassifier(
        n_estimators=250,
        learning_rate=0.05,
        max_depth=2,
        min_samples_leaf=5,
        random_state=42,
    ),
}

 

These settings are intentionally moderate rather than extensively tuned. The goal of the first comparison is to understand model-family behavior. A later extension can tune each family with cross-validation under a controlled search budget.

Step 4 — Train and collect validation metrics

PYTHON  •   Fit each model under one evaluation function
def evaluate_model(name, model):
    start = perf_counter()
    model.fit(X_train, y_train)
    fit_seconds = perf_counter() - start

    train_pred = model.predict(X_train)
    valid_pred = model.predict(X_valid)
    valid_prob = model.predict_proba(X_valid)[:, 1]

    return {
        "model": name,
        "fit_seconds": fit_seconds,
        "train_accuracy": accuracy_score(y_train, train_pred),
        "valid_accuracy": accuracy_score(y_valid, valid_pred),
         "valid_balanced_accuracy": balanced_accuracy_score(y_valid, valid_pred),
        "valid_roc_auc": roc_auc_score(y_valid, valid_prob),
    }

records = [
    evaluate_model(name, model)
    for name, model in models.items()
]

results = pd.DataFrame(records).sort_values(
    "valid_roc_auc",
    ascending=False,
)
print(results.round(4).to_string(index=False))

 

Step 5 — Compare overfitting gaps

PYTHON  •   Measure the training-validation accuracy gap
results["accuracy_gap"] = (
    results["train_accuracy"] - results["valid_accuracy"]
)

print(
    results[[
        "model",
        "train_accuracy",
        "valid_accuracy",
        "accuracy_gap",
    ]].round(4).to_string(index=False)
)

 

  • Which model has the largest gap between training and validation accuracy?
  • Does the random forest reduce the instability expected from a single tree?
  • Does gradient boosting improve validation ranking performance without producing an excessive gap?
  • Would a very high training score change your selection if validation performance were weaker?

Step 6 — Inspect the random forest OOB estimate

PYTHON  •   Compare OOB and validation accuracy
forest = models["random_forest"]

print("Forest OOB score:", round(forest.oob_score_, 4))
print(
    "Forest validation accuracy:",
    round(forest.score(X_valid, y_valid), 4),
)

 

The two numbers do not need to be identical. They are based on different prediction sets and sampling mechanisms. The purpose is to see whether the OOB estimate gives a plausible internal diagnostic consistent with held-out validation behavior.

Step 7 — Plot validation ROC curves

PYTHON  •   Compare ranking behavior across thresholds
plt.figure(figsize=(7.5, 5.5))

for name, model in models.items():
    prob = model.predict_proba(X_valid)[:, 1]
    fpr, tpr, _ = roc_curve(y_valid, prob)
    auc = roc_auc_score(y_valid, prob)

    plt.plot(fpr, tpr, label=f"{name} (AUC={auc:.3f})")

plt.plot([0, 1], [0, 1], linestyle="--", label="Chance")
plt.xlabel("False positive rate")
plt.ylabel("True positive rate")
plt.title("Validation ROC curves")
plt.legend()
plt.tight_layout()
plt.show()

 

THRESHOLD PERSPECTIVE  ROC AUC compares ranking quality across thresholds. A deployed classifier still requires an operating threshold aligned with error costs, capacity, and class-specific consequences.

 

Step 8 — Add structural diagnostics

PYTHON  •   Summarize model structure and forest size
tree = models["decision_tree"]
gb = models["gradient_boosting"]

print("Decision tree depth:", tree.get_depth())
print("Decision tree leaves:", tree.get_n_leaves())
print("Random forest trees:", len(forest.estimators_))
print("Gradient boosting stages:", gb.n_estimators_)
print("Trees per boosting stage:", gb.n_trees_per_iteration_)

 

Model complexity cannot be reduced to one universal number. A single depth-4 tree, 400 independently fitted forest trees, and 250 sequential boosting stages represent different computational and explanatory structures. Record these differences rather than comparing only predictive scores.

Step 9 — Apply a predeclared selection rule

For this lab, select the model with the highest validation ROC AUC. If two models differ by less than 0.005 ROC AUC, prefer the one with the higher validation balanced accuracy. If both metrics are effectively tied, prefer the simpler or faster operational choice and document the reason.

PYTHON  •   Select the model without reading the test set
selection = results.copy()

best_auc = selection["valid_roc_auc"].max()
near_best = selection[
     selection["valid_roc_auc"] >= best_auc - 0.005
].copy()

selected_row = near_best.sort_values(
     ["valid_balanced_accuracy", "fit_seconds"],
    ascending=[False, True],
).iloc[0]

selected_name = selected_row["model"]
print("Selected model:", selected_name)
print(selected_row.round(4))

 

PREDECLARE BEFORE FINAL EVALUATION  The exact tie tolerance and decision hierarchy should be defined before seeing test results. Otherwise, apparently objective rules can be modified until they favor the preferred test outcome.

 

Step 10 — Refit the selected model on development data

PYTHON  •   Combine train and validation after model selection
X_development = pd.concat([X_train, X_valid])
y_development = pd.concat([y_train, y_valid])

selected_model = models[selected_name]
selected_model.fit(X_development, y_development)

 

Step 11 — Evaluate the selected model once on test data

PYTHON  •   Produce the final held-out evaluation
test_pred = selected_model.predict(X_test)
test_prob = selected_model.predict_proba(X_test)[:, 1]

print("Final test accuracy:", round(accuracy_score(y_test, test_pred), 3))
print(
    "Final test balanced accuracy:",
     round(balanced_accuracy_score(y_test, test_pred), 3),
)
print("Final test ROC AUC:", round(roc_auc_score(y_test, test_prob), 3))
print()
print("Confusion matrix:")
print(confusion_matrix(y_test, test_pred))
print()
print(classification_report(
    y_test,
    test_pred,
    target_names=data.target_names,
    digits=3,
))

 

CLINICAL-USE WARNING  This lab demonstrates machine-learning methodology only. The built-in dataset, split, metrics, and model comparison are not sufficient for clinical deployment, diagnosis, or patient-level decision making.

 

Step 12 — Inspect feature importance using a common method

Because the three model families expose different internal structures, permutation importance provides a common post-hoc comparison method. Compute it on validation-like or final evaluation data only after deciding what interpretive question you are asking.

PYTHON  •   Calculate permutation importance for the selected model
from sklearn.inspection import permutation_importance

perm = permutation_importance(
    selected_model,
    X_test,
    y_test,
    scoring="roc_auc",
    n_repeats=20,
    random_state=42,
    n_jobs=-1,
)

importance = pd.Series(
    perm.importances_mean,
    index=X_test.columns,
).sort_values(ascending=False)

print(importance.head(10))

 

Required student interpretation

1. Compare the validation ROC AUC and balanced accuracy of the three candidate models. Which model ranked first under the declared rule?

2. Compare each model’s training and validation accuracy. Which model shows the strongest evidence of overfitting?

3. How close is the forest OOB score to its validation accuracy? What does the difference suggest?

4. Does the ensemble advantage come mainly from variance reduction, sequential error correction, or both?

5. How much extra fitting time did the ensembles require relative to the decision tree?

6. If the random forest and gradient boosting model are practically tied, which would you deploy and why?

7. Why would direct inspection of one small tree remain easier than explanation of a forest or boosting ensemble?

8. Why is permutation importance more comparable across the three candidate families than their internal importance mechanisms?

9. Why should the final test set be evaluated only after the selection rule has been frozen?

10. Write a 250–350 word recommendation that considers predictive performance, stability, computation, interpretability, and the limitations of this educational dataset.

Expected observations

  • The constrained decision tree is usually the easiest model to explain but may have a larger training-validation sensitivity than the forest.
  • The random forest usually produces a more stable validation score than a single tree because many randomized trees are averaged.
  • Gradient boosting can be highly competitive because later stages focus on systematic errors left by earlier stages.
  • The OOB estimate should be treated as an internal diagnostic rather than a replacement for a protected final test set.
  • Training time and artifact size increase when hundreds of trees are stored.
  • A small difference in ROC AUC may be less important than simpler deployment, faster prediction, lower memory use, or clearer explanations.
  • Feature importance rankings can change across model families because they summarize different fitted decision structures.

Lab deliverables

  • A reproducible notebook containing the protected split and three candidate models.
  • A comparison table containing training accuracy, validation accuracy, balanced accuracy, ROC AUC, fit time, and overfitting gap.
  • The random forest out-of-bag score and a short explanation of OOB evaluation.
  • One validation ROC curve containing all three models.
  • The declared model-selection rule and selected model name.
  • One final test confusion matrix and classification report for the selected model only.
  • A top-10 permutation-importance ranking with an interpretation caution.
  • A 250–350 word recommendation comparing accuracy, stability, computation, and interpretability.

Extension challenges

  • Add ExtraTreesClassifier and HistGradientBoostingClassifier to the comparison while keeping the same validation protocol.
  • Replace the single validation set with StratifiedKFold cross-validation and report mean plus standard deviation for each metric.
  • Create a controlled search over n_estimators and max_features for the random forest.
  • Create a learning_rate × n_estimators experiment for gradient boosting and visualize the validation surface.
  • Compare impurity-based forest importance with permutation importance and explain disagreements.
  • Inject NaN values into selected numerical features and compare explicit median imputation with HistGradientBoostingClassifier native missing-value handling.
  • Measure prediction latency and serialized model size in addition to predictive metrics.
  • Repeat the comparison across several random split seeds and report performance variability.

Chapter summary

Ensemble classification improves on the limitations of a single decision tree by combining multiple trees. Random forests create diversity with bootstrap samples and random feature subsets, then average predictions. This design usually reduces variance and provides a useful out-of-bag diagnostic. Extra Trees adds another source of diversity by selecting from randomized split thresholds, which can reduce tree correlation and sometimes improve computational efficiency at the cost of additional bias.

Gradient boosting follows a different strategy. Trees are added sequentially so that each stage improves the current ensemble under the selected loss. The learning rate controls how strongly each stage contributes, the number of estimators controls how long boosting continues, and tree depth controls the complexity of each correction. These choices jointly determine the bias-variance trade-off. Histogram-based gradient boosting accelerates split search by binning continuous features and is especially attractive for larger tabular datasets; in scikit-learn it also supports NaN values natively.

Model choice should be based on honest validation rather than algorithm reputation. A random forest is a strong robust nonlinear benchmark. Gradient boosting can provide excellent predictive accuracy but needs careful tuning and sequential training. Extra Trees is worth comparing when stronger randomization is useful. Histogram gradient boosting is a practical option when sample size or missing values make it advantageous. Interpretability, runtime, memory, stability, probability quality, and deployment cost belong in the decision alongside the primary metric.

Table 17.11. Key terms

Term

Meaning

EnsembleA model that combines predictions from multiple fitted estimators.
Base learnerAn individual model that contributes to an ensemble.
Bootstrap sampleA training sample drawn with replacement from the original training data.
Out-of-bag observationA training row not selected in a particular bootstrap sample.
Random feature selectionConsidering only a random subset of features at a tree split.
Random forestAn averaging ensemble of randomized decision trees.
Extra TreesA randomized tree ensemble that also draws candidate split thresholds randomly.
Gradient boostingA sequential additive ensemble that fits new stages to improve the current loss.
Weak learnerA deliberately simple component model whose small contribution becomes useful in an ensemble.
Learning rateThe shrinkage factor applied to each boosting stage.
Histogram binningGrouping continuous values into discrete bins for more efficient split search.
Permutation importanceImportance measured by the decrease in evaluation performance after permuting one feature.

 

Knowledge check

1. Why can averaging many decision trees reduce variance?

2. Why would training identical trees on identical data fail to create a useful ensemble?

3. What is a bootstrap sample, and how does it create out-of-bag observations?

4. Why does a random forest restrict the candidate features considered at each split?

5. How does scikit-learn combine class predictions in RandomForestClassifier?

6. What is an OOB score, and why does it not replace a carefully designed test set?

7. How does Extra Trees introduce more randomness than a random forest?

8. Why can Extra Trees reduce variance while potentially increasing bias?

9. What does it mean for gradient boosting to build trees sequentially?

10. Why are shallow trees often suitable weak learners for boosting?

11. How are learning_rate and n_estimators related?

12. What happens when boosting trees are made too deep?

13. Why can histogram-based gradient boosting train faster on large tabular datasets?

14. How does HistGradientBoostingClassifier handle NaN values?

15. When would you prefer a random forest over gradient boosting even if their validation scores were similar?

16. Why is a forest harder to interpret directly than a depth-4 decision tree?

17. Why should impurity-based feature importance not be interpreted as causality?

18. What experimental controls are required for a fair comparison of a tree, forest, and boosted model?

Instructor notes and suggested timing

Table 17.12. Suggested teaching plan

Session

Content

Suggested duration

Teaching method

1Ensemble motivation; variance, diversity, averaging60–75 minLecture, instability demonstration
2Random forest: bootstrap, random features, OOB75–90 minBoard explanation + Python demonstration
3Feature importance and Extra Trees60–75 minComparison exercise + code
4Gradient boosting mechanism and tuning75–90 minSequential visual explanation + validation curves
5Histogram-based gradient boosting60–75 minRuntime/missing-value demonstration
6Model-comparison framework60 minTrade-off table + discussion
7Practical lab120–150 minNotebook work, model selection, interpretation
8Lab review and extension60–90 minPeer comparison and instructor correction

 

NEXT STEP  The next chapter can build on these ensemble foundations by introducing systematic hyperparameter tuning, cross-validation search strategies, and more formal model-selection procedures.

 

Reference note

Implementation details in this chapter were aligned with the scikit-learn 1.9.0 stable documentation for ensemble methods, including RandomForestClassifier, ExtraTreesClassifier, GradientBoostingClassifier, and HistGradientBoostingClassifier. Students should check the documentation for the installed scikit-learn version when reproducing examples in another environment.