In the previous lesson we improved a model by restraining it with regularization. This lesson takes the opposite route: improving by combining many models so that their individual errors cancel each other out. It is one of the most profitable ideas in all of machine learning: ensemble methods dominate competitions on tabular data and are the de facto standard in production for problems like MercaFresh's churn. Here you'll understand why combining works, meet the three big families — bagging, boosting and stacking —, study Random Forest in detail and pit it against the lone decision tree of 04-03 on our churn dataset, with the same cross-validation from module 6.
Contents
- Why combining models works
- Bagging: bootstrap + aggregation
- Random Forest in detail
- Random Forest on MercaFresh's churn
- Voting classifiers: hard and soft
- Stacking: a model that learns to combine
- Boosting: the sequential family (a preview)
- Comparison table of the three families
- The price: interpretability
Why combining models works
Imagine you ask 1,000 people how many oranges fit on a MercaFresh pallet. Each individual estimate will be poor, but the average of all of them is usually surprisingly good: those who overshoot compensate for those who undershoot. It's the "wisdom of crowds", and it works under two conditions:
- Each estimator is better than chance (even if only slightly).
- The errors are independent (or at least weakly correlated): they don't all miss in the same direction.
With models it's exactly the same. If you have 100 classifiers that are right 70% of the time and their errors were independent, a majority vote would be right far more than 70% of the time: for the majority to fail, more than 50 of them would have to be wrong at once, which is unlikely if each one fails on different customers.
The connection with 06-05 is direct: averaging models reduces variance. A deep decision tree has low bias but sky-high variance (it changes completely with a different training sample); if we average many different trees, each one's random component cancels out and the shared signal remains. The critical condition — and the hardest one — is decorrelation: a hundred copies of the same model add nothing, just like asking the same person a thousand times. All the ingenuity of ensemble methods lies in manufacturing diversity:
| Source of diversity | Technique that uses it |
|---|---|
| Train each model on different data (resampling) | Bagging / Random Forest |
| Limit the features each model sees | Random Forest (feature bagging) |
| Use algorithms from different families | Voting, Stacking |
| Train in sequence, each one on the previous one's errors | Boosting |
Bagging: bootstrap + aggregation
Bagging = Bootstrap AGGregatING. The recipe:
- Bootstrap: generate B samples of the training set, each the same size as the original but sampled with replacement (a customer can appear several times; others, not at all). We already used this idea in statistical inference (02-04); here it serves to manufacture B "alternative versions" of the dataset.
- Training: train a model (typically a deep, unpruned tree) on each sample.
- Aggregation: to predict, classification votes; regression averages.
Each bootstrap sample leaves out, on average, 37% of the observations (the probability that a given example never comes up in n draws with replacement is $(1-1/n)^n \approx e^{-1} \approx 0.37$). That excluded 37% will play a starring role shortly: out-of-bag evaluation.
flowchart TB
D[Training dataset] --> B1[Bootstrap sample 1]
D --> B2[Bootstrap sample 2]
D --> B3[Bootstrap sample B]
B1 --> A1[Tree 1]
B2 --> A2[Tree 2]
B3 --> A3[Tree B]
A1 --> V[Vote / Average]
A2 --> V
A3 --> V
V --> P[Final prediction]
Random Forest in detail
Random Forest is bagging of trees plus one extra trick that multiplies diversity: feature bagging. At every split of every tree, instead of evaluating all the features (as the tree in 04-03 did), only a random subset is considered (by default, $\sqrt{p}$ features in classification, controlled by max_features).
Why does it matter so much? Without it, if one feature is very dominant — in our churn problem, recency is —, every tree would pick it for its first split and they'd all end up looking too much alike: correlated errors, little gain from averaging. By randomly vetoing features at each node, the trees are forced to explore different paths and their errors decorrelate.
Three additional gifts from Random Forest:
- OOB score (out-of-bag): each tree is evaluated on the 37% of observations it never saw in its bootstrap. Aggregating those predictions yields an estimate of performance on unseen data for free, with no separate validation split. It's a complement to (not a substitute for) the cross-validation of 06-03.
- Feature importance: how much each feature reduces impurity, averaged over all trees and all splits. An instant relevance ranking (with caveats we'll cover in Common Mistakes).
- Robustness with barely any tuning: more trees (
n_estimators) never causes overfitting by itself — it only stabilizes the average; the cost is compute time. The knobs that do control each tree's complexity are the ones you already know from 04-03 (max_depth,min_samples_leaf).
Random Forest on MercaFresh's churn
Let's rerun module 6's flagship experiment: predicting churn with the RFM features we built in module 3, comparing the lone tree from 04-03 against the forest, with the same StratifiedKFold from 06-03.
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
# --- MercaFresh churn dataset (RFM features from module 3) ---
rng = np.random.default_rng(42)
n = 1000
recency = rng.gamma(2, 15, n) # days since the last order
frequency = rng.poisson(5, n) + 1 # orders in the last quarter
monetary = rng.gamma(3, 40, n) # quarterly spend in EUR
tenure = rng.uniform(1, 60, n) # months as a customer
incidents = rng.poisson(0.5, n) # complaints
# Churn probability: rises with recency and incidents, falls with frequency
logits = 0.05 * recency - 0.4 * frequency + 0.6 * incidents - 0.01 * tenure - 0.5
churn = (rng.random(n) < 1 / (1 + np.exp(-logits))).astype(int)
X = pd.DataFrame({"recency": recency, "frequency": frequency,
"monetary": monetary, "tenure": tenure,
"incidents": incidents})
y = churn
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# --- Lone tree (04-03) vs Random Forest ---
tree = DecisionTreeClassifier(max_depth=5, random_state=42)
forest = RandomForestClassifier(n_estimators=300, oob_score=True,
random_state=42, n_jobs=-1)
for name, model in [("Tree (04-03)", tree), ("Random Forest", forest)]:
f1 = cross_val_score(model, X, y, cv=cv, scoring="f1")
print(f"{name:15s} F1: {f1.mean():.3f} ± {f1.std():.3f}")
# OOB score and feature importance (training on the full set)
forest.fit(X, y)
print(f"\nOOB accuracy: {forest.oob_score_:.3f}")
print(pd.Series(forest.feature_importances_, index=X.columns)
.sort_values(ascending=False).round(3))Typical observations from the result:
- The forest improves the mean F1 of the tree and, above all, reduces the spread between folds: less variance, exactly as the theory promised. We don't need to fine-tune
max_depthwith validation curves as in 06-05: the forest's trees grow deep and the averaging absorbs their individual overfitting. oob_score_yields a figure consistent with the CV without spending a single fold.feature_importances_points torecency,frequencyandincidentsas the dominant variables — consistent with what MercaFresh's retention team already suspected and with how we generated the data.
Practical note: trees don't need scaling (04-03), so here the pipeline can skip the StandardScaler; if the ensemble included scale-sensitive models (SVM, K-NN, logistic regression), the Pipeline from module 3 becomes mandatory again.
Voting classifiers: hard and soft
Bagging manufactures diversity by resampling data; another route is to combine algorithms of a different nature. In 06-03 we ran a tournament among the classifiers of module 4; the VotingClassifier turns them from rivals into a team:
- Hard voting: each model casts its class and the majority wins.
- Soft voting: the probabilities (
predict_proba) are averaged and the class with the highest mean wins. It's usually better because it incorporates each model's confidence, but it requires all the members to produce reasonably calibrated probabilities.
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# Heterogeneous members: logistic (04-02), K-NN (04-05), Naive Bayes (04-06)
voting = VotingClassifier(
estimators=[
("logreg", Pipeline([("sc", StandardScaler()),
("m", LogisticRegression(max_iter=1000))])),
("knn", Pipeline([("sc", StandardScaler()),
("m", KNeighborsClassifier(n_neighbors=15))])),
("nb", GaussianNB()),
],
voting="soft",
)
f1 = cross_val_score(voting, X, y, cv=cv, scoring="f1")
print(f"Voting (soft) F1: {f1.mean():.3f} ± {f1.std():.3f}")Each scale-sensitive member carries its own pipeline with scaling: the anti-leakage discipline of 06-01 holds inside the ensemble. Voting shines when the members perform similarly but fail on different customers; if one model is clearly worse than the rest, it can drag the vote down (better to remove it or weight it with weights).
Stacking: a model that learns to combine
Voting combines with a fixed rule (majority or mean). Stacking goes one step further: it trains a meta-model that learns from the data how to combine the base models' predictions. For instance, it might learn that "when K-NN and the logistic model disagree, the logistic model tends to be right on long-standing customers".
The fine print is that the meta-model must be trained on out-of-fold predictions (obtained via internal cross-validation), not on predictions over the same data the base models were trained on — otherwise it would inherit their overfitting, a leak of the kind we learned to fear in 06-01. StackingClassifier handles this automatically:
from sklearn.ensemble import StackingClassifier
stacking = StackingClassifier(
estimators=voting.estimators, # the same base models
final_estimator=LogisticRegression(), # simple meta-model
cv=5, # internal CV for the meta-features
)
f1 = cross_val_score(stacking, X, y, cv=cv, scoring="f1")
print(f"Stacking F1: {f1.mean():.3f} ± {f1.std():.3f}")Stacking is the endgame weapon of competitions (often with several layers of meta-models), but in real projects its gain over a good voting ensemble or a good forest is usually small and its maintenance cost, high. Use it when every tenth of a metric point is worth money.
Boosting: the sequential family (a preview)
The techniques above train models in parallel and independently. Boosting trains them in sequence: each new model concentrates on the examples (or errors) where the previous ones failed. Instead of reducing variance by averaging strong models, boosting reduces bias by summing many deliberately weak models (tiny trees) that correct one another.
That inversion of philosophy has important consequences (more power, more overfitting risk, a sequence that can't be parallelized) and has produced the most dominant algorithms on tabular data: Gradient Boosting, XGBoost, LightGBM. It's the subject of the next lesson (07-03); here it's enough to place it on the map.
Comparison table of the three families
| Bagging / Random Forest | Boosting | Stacking | |
|---|---|---|---|
| Training | Parallel, independent | Sequential | Parallel + meta-model |
| Base models | Strong (deep trees), homogeneous | Weak (small trees), homogeneous | Heterogeneous |
| Mainly reduces | Variance | Bias | Both (depending on members) |
| Overfitting risk | Low | Medium-high (needs brakes) | Medium (leaks if the internal CV is done badly) |
| Hyperparameter sensitivity | Low | High | Medium |
| sklearn example | RandomForestClassifier |
GradientBoostingClassifier (07-03) |
StackingClassifier |
The price: interpretability
The single tree of 04-03 had a virtue no ensemble keeps: it could be drawn and explained — "if recency > 30 days and frequency < 3 orders, high risk". A forest of 300 trees is a far more opaque box: nobody can follow 300 votes at once.
Palliatives exist — feature_importances_ gives a global ranking, and in 07-03 we'll mention SHAP for per-customer explanations —, but the underlying decision is a business one: if MercaFresh's retention team needs to justify to management why each customer gets called, an interpretable tree with an F1 of 0.78 may be worth more than an opaque forest with 0.82. If all that matters is being right, the ensemble wins. This performance-interpretability trade-off will resurface in the ethical considerations of 08-04.
Common Mistakes and Tips
- Combining nearly identical models. Three logistic regressions with different seeds are no crowd: without diversity there is no error cancellation. Mix algorithm families or use resampling/feature bagging.
- Treating
n_estimatorsas a complexity knob. In Random Forest, more trees don't overfit: they only stabilize (and cost more). The complexity brakes are each tree's (max_depth,min_samples_leaf). In boosting, watch out: theren_estimatorsdoes increase the overfitting risk, as we'll see in 07-03. - Trusting
feature_importances_blindly. Impurity-based importance favors continuous or high-cardinality features and is split arbitrarily among correlated features. Cross-check withpermutation_importanceon validation data before drawing business conclusions. - Using soft voting with models that lack reliable probabilities. An SVM without
probability=Truedoesn't providepredict_proba, and some models produce poorly calibrated probabilities that distort the average. Vet the members before voting. - Evaluating the stacking with the same CV that generates its meta-features, carelessly. Let
StackingClassifiermanage its internal CV and evaluate the whole ensemble with an external CV (as we did here): it's the clean way to avoid leaks. - Tip: always start with
RandomForestClassifieron default parameters as a "strong baseline". It's hard to break and quickly tells you how much signal is in the data; then decide whether anything fancier is worth it.
Exercises
- The value of decorrelation. Simulate 500 binary predictions from a "committee" of 25 classifiers that are individually right 65% of the time (use
rng.random((500, 25)) < 0.65as a matrix of independent hits) and compute the majority vote's accuracy. Repeat making 20 of the 25 exact copies of the same classifier. Compare both results. - The forest's stabilization curve. On MercaFresh's churn, evaluate
RandomForestClassifierwithn_estimatorsin[1, 5, 10, 25, 50, 100, 200, 400]using the lesson's stratified CV. Plot mean F1 and standard deviation against the number of trees. Beyond how many trees does adding more stop paying off? Does this curve look like an overfitting curve like those in 06-05? - Ensemble tournament. Compare with the same CV: (a) the best individual model from module 4 you obtained in 06-03, (b) a soft
VotingClassifierwith three heterogeneous models, (c)RandomForestClassifier(n_estimators=300). Present the mean F1 ± deviation table and argue which one MercaFresh should deploy, taking interpretability into account as well.
Solutions
- With 25 independent classifiers at 65%, the majority (≥13 hits out of 25) is right around 93-94% of the time: the crowd pulverizes the individual. With 20 identical copies, the "vote" is decided in practice by that one repeated classifier and the accuracy drops back to ~65%. The numerical moral of the whole lesson: without decorrelated errors, the ensemble is theater.
- Mean F1 climbs sharply up to ~50-100 trees and then flattens; the spread between folds shrinks steadily. Past ~200 trees the improvements are noise and you're only paying compute. It is not an overfitting curve: unlike
max_depthin 06-05, there is no descending stretch no matter how many trees you add — the curve converges, it doesn't turn around. - Typical result: the forest tops the table, the voting ensemble comes close (sometimes tying if its members are good and diverse) and the best individual model trails with a larger spread between folds. The deployment decision isn't automatic: with differences of a few hundredths of F1, the interpretability argument can tip the scales toward the simple model; with clear differences, the forest wins and the feature importances serve as a global explanation for the business.
Conclusion
You've seen why combining models works — decorrelated errors that cancel out, variance that collapses when averaging — and the three ways of manufacturing it: bagging (bootstrap + aggregation, culminating in Random Forest with its feature bagging, its OOB score and its importances), voting and stacking (diversity through algorithmic heterogeneity), and boosting, the sequential family we left previewed. On MercaFresh's churn, the forest beat the lone tree of 04-03 with less variance between folds and barely any tuning, at the cost of sacrificing the drawable tree's interpretability. The most powerful member of the family is still pending: boosting, where each model is born to correct the previous one's errors and which today dominates machine learning on tabular data. It's the subject of the next lesson: Gradient Boosting.
Machine Learning Course
Module 1: Introduction to Machine Learning
- What is Machine Learning?
- History and evolution of Machine Learning
- Types of Machine Learning
- Applications of Machine Learning
- The Machine Learning project workflow
Module 2: Foundations of Statistics and Probability
- Basic statistics concepts
- Probability distributions
- Correlation and covariance
- Statistical inference
- Bayes' theorem
Module 3: Data Preprocessing
- Data cleaning
- Handling missing data
- Data transformation
- Encoding categorical variables
- Normalization and standardization
- Feature engineering
Module 4: Supervised Machine Learning Algorithms
- Linear regression
- Logistic regression
- Decision trees
- Support Vector Machines (SVM)
- K-Nearest Neighbors (K-NN)
- Naive Bayes
- Neural networks
Module 5: Unsupervised Machine Learning Algorithms
- Clustering: K-means
- Hierarchical clustering
- Principal Component Analysis (PCA)
- DBSCAN clustering
- Data visualization with t-SNE and UMAP
Module 6: Model Evaluation and Validation
- Data splitting: training, validation and test
- Evaluation metrics
- Cross-validation
- ROC curve and AUC
- Overfitting and underfitting
Module 7: Advanced Techniques and Optimization
- Regularization: Ridge, Lasso and Elastic Net
- Ensemble Learning
- Gradient Boosting
- Deep neural networks (Deep Learning)
- Hyperparameter optimization
Module 8: Model Implementation and Deployment
- Popular frameworks and libraries
- Deploying models to production
- Model maintenance and monitoring
- Ethical and privacy considerations
Module 9: Hands-On Projects
- Project 1: Housing price prediction
- Project 2: Image classification
- Project 3: Sentiment analysis on social media
- Project 4: Fraud detection
- Project 5: Customer segmentation
