Ensemble Methods: Bagging & Random Forest
1788 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Ensemble Methods: Bagging & Random Forest ## 🎯 Learning Objectives - Explain why ensembles outperform individual models - Describe bagging (Bootstrap Aggregating) and how it reduces variance - Build and tune a Random Forest classifier - Understand the difference between bagging and boosting - Implement gradient b...

Ensemble Methods: Bagging & Random Forest
🎯 Learning Objectives
- Explain why ensembles outperform individual models
- Describe bagging (Bootstrap Aggregating) and how it reduces variance
- Build and tune a Random Forest classifier
- Understand the difference between bagging and boosting
- Implement gradient boosting with sklearn
📋 Prerequisites
- Decision Trees — the base learner for most ensembles
- Bootstrap sampling — sampling with replacement
- Bias-variance tradeoff — ensembles reduce variance (bagging) or bias (boosting)
📖 Core Content
7.1 Intuition: The Wisdom of Crowds
Imagine you need to guess the number of jelly beans in a jar. One person's guess is likely off. But if you ask 100 people and average their guesses, the average is surprisingly accurate — often closer than any individual. This is the wisdom of crowds.
Ensemble methods apply the same idea to ML. Instead of training one model, we train many diverse models and combine their predictions. The ensemble is typically more accurate, more stable, and less prone to overfitting than any single model.
(Diagram)
7.2 Bagging: Bootstrap Aggregating
Bagging works in two steps:
- Bootstrap: Create B new training sets by sampling with replacement from the original dataset (each about 63% unique, 37% duplicated or missing).
- Aggregate: Train a model on each bootstrap sample, then average (regression) or majority vote (classification).
python# runnable from sklearn.ensemble import BaggingClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_score import numpy as np X, y = make_classification(n_samples=500, n_features=20, random_state=42) # Single tree tree = DecisionTreeClassifier(random_state=42) tree_scores = cross_val_score(tree, X, y, cv=5) print(f"Single tree: {tree_scores.mean():.3f} ± {tree_scores.std():.3f}") # Bagged ensemble bag = BaggingClassifier( estimator=DecisionTreeClassifier(), # Note: use 'estimator' not 'base_estimator' in newer sklearn n_estimators=100, max_samples=0.8, bootstrap=True, random_state=42 ) bag_scores = cross_val_score(bag, X, y, cv=5) print(f"Bagging (100 trees): {bag_scores.mean():.3f} ± {bag_scores.std():.3f}")
Why bagging works: Decision trees have high variance — a small change in training data produces a very different tree. By averaging many trees trained on different bootstrap samples, the variance drops while bias stays roughly the same.
7.3 Random Forest
Random Forest is bagging plus one extra trick: at each split, only a random subset of features is considered. This decorrelates the trees — without it, the trees would all choose the strongest feature for their root split and look very similar.
(Diagram)
Key parameters:
n_estimators: number of trees (higher is better, diminishing returns after ~100-500)max_features: number of features considered at each split (default: sqrt(n) for classification, n/3 for regression)max_depth: max tree depth (default: unlimited — trees grow fully)min_samples_leaf: minimum samples per leaf (default: 1 for classification)oob_score: use Out-Of-Bag samples as internal validation
python# runnable from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score wine = load_wine() X, y = wine.data, wine.target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) rf = RandomForestClassifier( n_estimators=200, max_depth=10, min_samples_leaf=2, max_features='sqrt', oob_score=True, random_state=42 ) rf.fit(X_train, y_train) y_pred = rf.predict(X_test) print(f"Test accuracy: {accuracy_score(y_test, y_pred):.3f}") print(f"OOB score: {rf.oob_score_:.3f}") # Feature importance for name, imp in sorted(zip(wine.feature_names, rf.feature_importances_), key=lambda x: x[1], reverse=True)[:5]: print(f" {name}: {imp:.3f}")
7.4 Out-of-Bag (OOB) Evaluation
Each bootstrap sample contains about 63% of the original data. The remaining 37% (out-of-bag) serve as a built-in validation set — no need for a separate validation split or cross-validation.
For each tree, predict on its OOB samples. Aggregate OOB predictions across all trees to get an unbiased performance estimate. This is essentially free internal cross-validation.
7.5 Boosting: Sequential Ensemble
Unlike bagging (parallel, independent trees), boosting trains trees sequentially, each one correcting the mistakes of the previous ones.
(Diagram)
AdaBoost: Increases weights on misclassified examples. Each new tree focuses on hard cases.
Gradient Boosting: Each tree predicts the residual errors (gradient of the loss) of the previous ensemble.
python# runnable from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import numpy as np X, y = make_classification(n_samples=500, n_features=20, random_state=42) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) gb = GradientBoostingClassifier( n_estimators=100, learning_rate=0.1, max_depth=3, min_samples_leaf=5, subsample=0.8, # Stochastic: use 80% of data per tree random_state=42 ) gb.fit(X_train, y_train) y_pred = gb.predict(X_test) print(f"GB test accuracy: {accuracy_score(y_test, y_pred):.3f}")
7.6 Comparison: Bagging vs Boosting
| Aspect | Bagging / Random Forest | Boosting (AdaBoost, GBM) |
|---|---|---|
| Training | Parallel (independent) | Sequential (dependent) |
| Goal | Reduce variance | Reduce bias |
| Overfitting | Low (averaging helps) | High (can overfit if too many rounds) |
| Base learners | Deep trees (high variance) | Shallow trees (weak learners) |
| Speed | Fast (parallelizable) | Slow (sequential) |
| Data scaling | Not needed | Not needed |
| Key hyperparams | n_estimators, max_features | n_estimators, learning_rate, subsample |
7.7 When to Use / Not Use
| ✅ When to Use | ❌ When NOT to Use |
|---|---|
| Need high accuracy (RF is often top-3) | Interpretability is critical (use single tree) |
| Mixed data types | Very sparse data (linear models better) |
| Many features (RF handles well) | Production latency is tight (RF is large) |
| Imbalanced data (RF handles OK) | Need calibrated probabilities (use logistic reg) |
📐 Key Formulas / Concepts
| Concept | Formula/Definition | Notes |
|---|---|---|
| Bootstrap | Sample with replacement | ~63% unique per sample |
| Bagging prediction | y^=B1∑y^b (regression) | or majority vote (classification) |
| OOB Error | Error on out-of-bag samples | Free validation |
| Random Forest | Bagging + random feature subset at each split | max_features = sqrt(n) for classif |
| Boosting | y^=∑αbhb(x) | Weighted sum of weak learners |
| Learning Rate | η < 1 shrinks each tree's contribution | Lower η = more trees needed |
⚠️ Common Pitfalls
Pitfall 1: Too Few Trees
The mistake: Using n_estimators=10 and wondering why RF doesn't beat a single tree.
Why: Random Forest needs enough trees to stabilize the variance reduction. With too few, the ensemble still has high variance.
Fix: Start with n_estimators=100. Check if increasing to 500 helps. Plot OOB error vs n_estimators — stop when it plateaus.
Pitfall 2: Overfitting with Boosting
The mistake: Using n_estimators=1000 with learning_rate=1.0 and deep trees.
Why: Boosting is prone to overfitting — each tree greedily fixes the last errors, eventually memorizing noise.
Fix: Use shallow trees (max_depth=2-4), higher learning_rate with fewer trees, or early stopping.
Pitfall 3: Ignoring max_features in RF
The mistake: Setting max_features = n_features (all features considered at every split).
Why: Trees become highly correlated — losing the key benefit of Random Forest. All trees look similar.
Fix: Use sqrt(n) for classification, n/3 for regression. This forces diversity.
📝 Practice Questions
Q1: What fraction of original data appears in a bootstrap sample?About 63.2% (approximately 1 - 1/e). The probability a specific example is NOT selected in a sample of size m (with replacement) is (1 - 1/m)^m → 1/e ≈ 36.8%. So 63.2% appear at least once. Q2: Why does random forest outperform a single decision tree?
- Variance reduction: Averaging B independent trees reduces variance by factor ≈ B (but trees aren't fully independent)
- Feature randomization: Decorrelates trees further
- Robustness: Less sensitive to noise and outliers
A single decision tree can change completely with minor data changes. RF averages over this instability. Q3: What is the difference between bagging and pasting?
- Bagging: Bootstrap sampling (with replacement) — some examples appear multiple times, some appear 0 times.
- Pasting: Sampling without replacement — each example appears at most once per sample. Pasted samples are smaller subsets.
Bagging is more common because it creates more diverse training sets. Q4: Your RF has training accuracy 0.99 and test accuracy 0.85. What do you do?The gap (0.14) suggests overfitting. Solutions:
- Reduce max_depth (limit tree growth)
- Increase min_samples_leaf
- Increase n_estimators (more averaging reduces variance)
- Reduce max_features (more randomness)
- Increase data if possible
The OOB score should be close to test score — if OOB is also 0.99, overfitting is confirmed. Q5: Explain how gradient boosting works in one sentence.Gradient boosting trains trees sequentially where each new tree predicts the negative gradient (residual errors) of the loss function with respect to the current ensemble's predictions. Q6: When would you choose Gradient Boosting over Random Forest?Choose Gradient Boosting when:
- You need the highest possible accuracy (GB tends to win competitions)
- Data is not too large (< 100k rows)
- You have time for hyperparameter tuning
- You're using shallow trees (weak learners)
Choose Random Forest when:
- Simplicity and robustness matter
- Data is very large (RF parallelizes easily)
- You worry about overfitting (RF is more robust)
- Quick baseline needed Q7: What is subsample in gradient boosting?
subsample(default 1.0) specifies the fraction of training data used per tree. Value 0.8 means each tree uses 80% of data (randomly selected). This adds randomness (like bagging) and helps prevent overfitting. Called Stochastic Gradient Boosting. Q8: Implement a Random Forest regressor for the Boston housing dataset.pythonfrom sklearn.ensemble import RandomForestRegressor from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error import numpy as np housing = fetch_california_housing() X, y = housing.data, housing.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42) rf = RandomForestRegressor(n_estimators=200, max_depth=15, random_state=42) rf.fit(X_train, y_train) y_pred = rf.predict(X_test) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) print(f"RMSE: ${rmse:.3f}k")Q9: What is a "weak learner" in boosting context?A weak learner is a model that performs slightly better than random (e.g., accuracy > 50% for binary classification). In boosting, weak learners are typically decision stumps (depth 1 trees) or shallow trees (depth 2-3). The magic of boosting is combining many weak learners into one strong ensemble. Q10: How does early stopping work in gradient boosting?Monitor validation error after each tree is added. If validation error hasn't improved for n_iter_no_change rounds, stop training. This prevents overfitting and saves computation. sklearn'sGradientBoostingClassifiersupports this withn_iter_no_changeandvalidation_fractionparameters. Q11: Why does RF not require feature scaling?Because decision trees (the base learners) are threshold-based, not distance-based. A tree tests "is feature > t?" regardless of the feature's scale. Doubling a feature's scale just doubles the threshold — the split point adapts. So RF works with raw data without any scaling. Q12: Compare bagging with pasting.
| Aspect | Bagging | Pasting |
|---|---|---|
| Sampling | With replacement | Without replacement |
| Unique examples per sample | ~63% | 100% (if sample size = original) |
| Diversity | Higher (some examples appear multiple times) | Lower |
| Duplicates in sample | Yes | No |
| Typical use | Default for most ensembles | When you want all examples used |
Bagging is generally preferred because the extra diversity helps ensembles more.
🔗 Cross-References
- Next Topic: Support Vector Machines
- Related: Decision Trees — base learner
- Related: Boosting Deep Dive — detailed gradient boosting
- External: IITM BSCS2004 Week 7, Hands-On ML Ch. 7, ISLR Ch. 8 Join Discord PreviousDecision TreesNextEnsemble Boosting