Skip to content
learn
  1. Learn
  2. Machine Learning
  3. Trees and Ensembles

Machine Learning ML-06.02

Bagging and random forests

Train many trees on bootstrap samples and let them vote: how bagging cuts variance, why random forests subsample features, and free out-of-bag error.

Intermediate 12 min Last reviewed

The big idea

Ask one person to guess the number of jelly beans in a jar and the answer may be wildly off. Ask a hundred people and average their guesses, and the average is usually much closer than most individuals. The errors point in different directions and cancel. This only works if the guesses are somewhat independent: a hundred people copying the same loud neighbour are no better than one.

A deep decision tree is like one confident, erratic guesser. It has low bias (it can fit almost anything) but high variance: change a few training rows and you get a different tree. BaggingBootstrap aggregating: train many copies of a model on different bootstrap samples of the data and average (or vote on) their predictions. trains many trees on slightly different versions of the data and averages them. A random forestBagged decision trees where each split also considers only a random subset of the features, which makes the trees less alike and their average more accurate. adds one more trick to make the trees disagree more, so their errors cancel better.

How it works

Bagging: bootstrap samples, many trees, one vote A training set box feeds four bootstrap sample boxes, each drawn with replacement. Each sample trains one tree. For a new example, three trees vote class B and one votes class A. The votes flow to a majority vote box, which outputs class B. The paths of the three class B trees and the output glow. training set n rows sample 1 with replacement sample 2 sample 3 sample 4 tree 1 votes B tree 2 votes A tree 3 votes B tree 4 votes B majority vote 3 of 4 say B B Figure 1. Each tree sees a different bootstrap sample, so each makes different mistakes. The vote (or the average of probabilities) keeps what they agree on. Here three of four trees say B, so the forest says B.

Step 1: Make many training sets from one

A bootstrap sampleA sample of n rows drawn with replacement from a dataset of n rows; some rows appear several times and about 37 percent do not appear at all. draws n rows from your n training rows with replacement. Some rows are picked twice or three times, others not at all. The chance a given row is never picked is (1 - 1/n)^n, which approaches 1/e, about 0.368. So each sample contains about 63 percent of the distinct rows (the code below measures 63.4 percent), and each tree trains on a different 63 percent.

Step 2: Grow a deep tree on each sample, then average

Each tree is grown deep, with little or no pruning: low bias, high variance. For classification the forest takes a majority vote or, better, averages the predicted probabilities. For regression it averages the predicted numbers. Training is embarrassingly parallelWork that can be split into independent pieces with no communication between them, so it scales almost perfectly across cores or machines., since no tree depends on another.

Step 3: Why averaging helps, and where it stops helping

If each tree's prediction has variance sigma^2 and any two trees have correlation rho, the average of B trees has variance

The second term shrinks as you add trees. The first does not: correlated trees make the same mistakes, and no amount of voting removes a shared mistake. With rho = 0.66 the ensemble variance can never fall below 66 percent of one tree's; with rho = 0.3 it can fall to 30 percent.

Ensemble variance flattens at the correlation floor Variance of an average of B trees relative to one tree, for B from 1 to 100. With correlation 0.66 the curve drops quickly from 1 and flattens at 0.66. With correlation 0.3 it drops to a floor of 0.30, glowing. Dashed lines mark both floors. 2040608010000.250.50.751number of trees Bvariance vs one tree rho = 0.66: floor 0.66 rho = 0.3: floor 0.30 Figure 2. The formula above, plotted. Adding trees removes the uncorrelated part of the error within a few dozen trees; after that only less correlated trees can push the floor down.

Step 4: Random forests decorrelate the trees

If one feature is very strong, every bagged tree splits on it at the root and the trees look alike. A random forest forbids this: at each split, it considers only a random subset of features, typically the square root of the number of features for classification (max_features="sqrt") and a third of them for regression. Each tree is a little weaker, but the trees are less correlated, and Figure 2 shows why that usually wins.

In the code, 50 trees using all 20 features at every split have an average pairwise correlation of 0.66; with 4 random features per split it falls to 0.58.

Step 5: Out-of-bag error, a free validation set

Each row was left out of about 37 percent of the trees. Predict each row using only those trees and you get an out-of-bag (OOB)An accuracy or error estimate for a bagged ensemble computed by predicting each training row only with the trees whose bootstrap sample did not include it. estimate of test performance, without a separate validation split. It is close to a cross-validation estimate at no extra training cost.

Going deeper

Code: tree vs bagging vs forest, OOB and correlation

forest.pyruns with python3 + numpy + scikit-learn
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier

X, y = make_classification(n_samples=1200, n_features=20, n_informative=8,
                           n_redundant=6, flip_y=0.05, random_state=0)

# A bootstrap sample leaves out about 1/e = 36.8 percent of rows
rng = np.random.default_rng(0)
idx = rng.integers(0, len(X), len(X))
print(f"unique rows in one bootstrap sample: {len(np.unique(idx)) / len(X):.1%}")

models = {
    "one deep tree": DecisionTreeClassifier(random_state=0),
    "bagging, 100 trees": BaggingClassifier(DecisionTreeClassifier(), n_estimators=100, random_state=0),
    "random forest, 100": RandomForestClassifier(n_estimators=100, random_state=0),
}
print("\nmodel                 5-fold accuracy")
for name, m in models.items():
    s = cross_val_score(m, X, y, cv=5)
    print(f"{name:<20}  {s.mean():.3f} +/- {s.std():.3f}")

# Out-of-bag accuracy is a free validation estimate
print("\ntrees  OOB accuracy")
for n in [25, 100, 300]:
    rf = RandomForestClassifier(n_estimators=n, oob_score=True, random_state=0).fit(X, y)
    print(f"{n:>5}  {rf.oob_score_:.3f}")

print()
# Feature subsampling makes trees less alike: correlation of their predictions on held-out rows
Xtr, Xte, ytr = X[:900], X[900:], y[:900]
for mf, label in [(None, "all 20 features per split"), ("sqrt", "sqrt(20) = 4 per split")]:
    rf = RandomForestClassifier(n_estimators=50, max_features=mf, random_state=0).fit(Xtr, ytr)
    P = np.array([t.predict_proba(Xte)[:, 1] for t in rf.estimators_])
    c = np.corrcoef(P)[np.triu_indices(50, 1)].mean()
    print(f"{label}: mean correlation between trees {c:.2f}")

Output

unique rows in one bootstrap sample: 63.4%

model                 5-fold accuracy
one deep tree         0.859 +/- 0.018
bagging, 100 trees    0.908 +/- 0.020
random forest, 100    0.913 +/- 0.017

trees  OOB accuracy
   25  0.901
  100  0.916
  300  0.918

all 20 features per split: mean correlation between trees 0.66
sqrt(20) = 4 per split: mean correlation between trees 0.58

One deep tree scores 0.859 in 5-fold cross-validation. Bagging 100 trees scores 0.908 and a random forest 0.913: the big jump comes from averaging, and the extra step from feature subsampling is small on this dataset, inside the noise. OOB accuracy climbs from 0.901 with 25 trees to 0.916 with 100 and 0.918 with 300, the flattening Figure 2 predicts.

Tuning (there is not much)

  • n_estimators: more is never worse for accuracy, only slower. Stop when OOB error flattens; a few hundred is typical.
  • max_features: the main knob. Smaller means less correlated, weaker trees.
  • min_samples_leaf and max_depth: usually left loose; raising min_samples_leaf smooths probabilities and shrinks the model.
  • Forests do not overfit by adding trees, but they can still overfit noisy data through very deep trees, which is why the leaf size sometimes matters.

Limits

A forest's prediction is an average of leaf values, so a regression forest can never predict beyond the range of training targets: it cannot extrapolate a trend. Forests are also large (hundreds of deep trees) and slower to predict than one tree, and you lose the readable flowchart. Feature importance ( Feature importance and SHAP ) partly restores interpretability.

Trade-offs

OptionWins whenLoses when
Single decision tree You need a readable rule set Accuracy and stability
Bagged trees Any high-variance model needs stabilizing Trees stay correlated when one feature dominates
Random forest A strong default with almost no tuning; OOB error for free Size, prediction latency, extrapolation
Gradient boosting Squeezing out the last points of accuracy on tabular data Needs more tuning and is sequential to train

Common mistakes

  • Bagging a low-variance model such as linear regression and expecting a big gain; bagging helps unstable models.
  • Treating OOB accuracy as valid when rows are not independent (repeated customers, time series); use grouped or time-based validation instead.
  • Pruning the individual trees hard: forests want deep, low-bias trees and let averaging handle variance.
  • Expecting a regression forest to predict prices higher than any seen in training.
  • Tuning n_estimators by grid search; just use enough trees.

In the wild

Random forests are a standard first strong model for tabular problems in industry and science: fraud and credit scoring, churn prediction, ecology and genomics studies, and quality control. Their appeal is robustness: they work reasonably well with default settings, handle mixed feature types and give a built-in validation estimate. Many teams use a forest as the baseline that a tuned gradient boosting model must beat.

Check yourself

  1. About what fraction of distinct training rows does a bootstrap sample contain?

    Show answer

    C. 63 percent Each row is missed with probability (1 - 1/n)^n, about 1/e = 0.37, so about 63 percent of rows appear at least once.

  2. Your forest has 50 trees and OOB accuracy is still rising slowly. What does adding 450 more trees risk?

    Show answer

    B. Only extra training and prediction time Adding trees reduces variance towards the correlation floor and does not overfit. The cost is compute and memory.

  3. Why does a random forest consider only a random subset of features at each split?

    Show answer

    B. To make the trees less correlated, so averaging cancels more error Correlation sets the floor on ensemble variance. Feature subsampling stops every tree from using the same dominant split.

  4. A regression forest predicts house prices. The highest training price is 900,000. A new mansion should be worth 2,000,000. What will the forest predict?

    Show answer

    B. At most about 900,000 Leaves predict averages of training targets, so the forest can never predict outside the range it saw.

Recap

  • Bagging trains each tree on a bootstrap sample (about 63 percent of distinct rows) and averages or votes.
  • Averaging B trees with correlation rho leaves variance rho sigma^2 + (1 - rho) sigma^2 / B: more trees help until the correlation floor.
  • Random forests consider a random subset of features per split to decorrelate trees.
  • Out-of-bag predictions give a free validation estimate.
  • Forests need little tuning but are large, cannot extrapolate and lose single-tree readability.

Get new lessons by email

Get new posts on backend and AI internals. No spam, unsubscribe anytime.

Your email is only used to send new posts. Privacy