Stacking and Voting Classifiers
What you’ll learn
Section titled “What you’ll learn”- hard against soft voting, and a case where they disagree — computed by hand
- why soft voting requires calibrated probabilities to be worth using
- stacking: letting a second-level model learn the weights instead of fixing them
- how the blender is trained on out-of-fold predictions, and why anything else leaks
- a measured comparison where none of the three combinations beats the best member
- when to reach for each, and when to ship the single model
Intuition
Section titled “Intuition”Bagging and boosting both build many copies of one kind of model. Voting and stacking do something different: they take models you already have — a logistic regression, a forest, a boosted tree, a KNN — and combine them.
The diversity is free. Those families make genuinely different assumptions and fail in genuinely different ways, which is exactly the condition Condorcet’s theorem requires.
The two approaches differ in one respect: who decides how much each member counts.
- Voting — you decide, in advance. Equal weights, or weights you supply.
- Stacking — a model learns them from data.
flowchart TD
X["Input x"] --> M1["logistic regression"]
X --> M2["random forest"]
X --> M3["gradient boosting"]
X --> M4["KNN"]
M1 --> V{"combine"}
M2 --> V
M3 --> V
M4 --> V
V -->|"voting: fixed rule"| P1["majority or mean probability"]
V -->|"stacking: learned"| P2["a blender trained on
out-of-fold predictions"]
Hard against soft voting
Section titled “Hard against soft voting”Hard voting counts labels. Soft voting averages probabilities and then thresholds. They are not the same rule, and the difference matters most when confidence is unevenly distributed.
Worked example by hand
Section titled “Worked example by hand”Three classifiers on one instance, predicting :
| Classifier | Hard vote | |
|---|---|---|
| logistic regression | 0.45 | class 0 |
| decision tree | 0.48 | class 0 |
| gradient boosting | 0.93 | class 1 |
Hard voting. Two votes for class 0, one for class 1 → predict class 0.
Soft voting. Average the probabilities:
Opposite answers from identical inputs. Hard voting heard two mild “probably not”s outvote one emphatic “almost certainly”. Soft voting let the confident model carry the weight its confidence implies.
Which is right? Only if the probabilities are trustworthy. If the boosted model is systematically overconfident — and untuned boosted trees frequently are — then 0.93 is not really 0.93, and soft voting has been misled by a number that does not mean what it says.
| Hard voting | Soft voting | |
|---|---|---|
| Uses | Predicted labels | Predicted probabilities |
Needs predict_proba | No | Yes |
| Sensitive to calibration | No | Very |
| Confident members | Count the same as unsure ones | Count more |
| Usually better when | Members are poorly calibrated | Members are well calibrated |
Stacking
Section titled “Stacking”Voting fixes the combination rule. Stacking learns it: fit the members, collect their predictions, and train a second model — the blender, or meta-learner — to map those predictions to the final answer.
The blender can learn things no fixed rule can express: that the forest is reliable except when the KNN strongly disagrees, or that logistic regression should simply be ignored.
Training the blender without cheating
Section titled “Training the blender without cheating”Here is the trap. If you train the members on the training set and then ask them to predict that same training set, their predictions will be far too good — a fitted random forest predicts its own training rows almost perfectly. The blender learns “always trust the forest”, and then meets a forest that is much worse on new data.
The fix is out-of-fold prediction:
flowchart TD A["Training data"] --> B["Split into k folds"] B --> C["For each fold:
train members on the other k-1,
predict this fold"] C --> D["Out-of-fold predictions
one row per training row"] D --> E["Train the blender on those"] A --> F["Refit members on all the data
for use at prediction time"]
Every prediction the blender trains on comes from a model that did not see that row. Its view
of each member’s reliability therefore matches what it will encounter in production.
StackingClassifier does this internally with its cv parameter; it is the entire reason the
class exists rather than a five-line loop.
Measured
Section titled “Measured”| Model | 5-fold CV accuracy |
|---|---|
| logistic regression | 0.8560 |
| decision tree (depth 4) | 0.9000 |
| KNN (k=15) | 0.9020 |
| Gaussian naive Bayes | 0.8580 |
| hard vote | 0.8860 |
| soft vote | 0.9000 |
| stacking (logistic blender) | 0.9000 |
Reading the plot
Section titled “Reading the plot”- Hard voting was the worst combination, 1.6 points below its best member. Two weak members (0.856 and 0.858) get the same vote as KNN at 0.902 and can outvote it.
- Soft voting recovered to 0.9000 by weighting with confidence, which is what soft voting is for.
- Stacking matched soft voting but did not beat it. In principle the blender could learn to discard the weak members entirely; with 500 rows and 4 features of meta-data it does not have enough signal to.
- Nothing beat KNN alone. Report this honestly and ship the single model.
The condition for stacking to pay is that the members be both reasonably strong and genuinely complementary. Here they were complementary but two of them were markedly weaker, and that turned out to be the binding constraint.
See it move
Section titled “See it move”Five classifiers report a probability for each incoming instance. The two tallies show what hard and soft voting each conclude — and how often they disagree.
In code
Section titled “In code”from sklearn.datasets import make_moons
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
X, y = make_moons(n_samples=500, noise=0.3, random_state=1)
members = [
("logistic", make_pipeline(StandardScaler(), LogisticRegression())),
("tree", DecisionTreeClassifier(max_depth=4, random_state=0)),
("knn", make_pipeline(StandardScaler(), KNeighborsClassifier(15))),
("nb", GaussianNB()),
]
for name, model in members:
print(f"{name:<10} {cross_val_score(model, X, y, cv=5).mean():.4f}")
hard = VotingClassifier(members, voting="hard")
soft = VotingClassifier(members, voting="soft")
print(f"{'hard':<10} {cross_val_score(hard, X, y, cv=5).mean():.4f}") # 0.8860
print(f"{'soft':<10} {cross_val_score(soft, X, y, cv=5).mean():.4f}") # 0.9000
# Unequal weights, when you know one member is better
weighted = VotingClassifier(members, voting="soft", weights=[1, 2, 3, 1])from sklearn.ensemble import StackingClassifier
stack = StackingClassifier(
estimators=members,
final_estimator=LogisticRegression(), # keep the blender simple
cv=5, # out-of-fold predictions; do not skip
passthrough=False, # True also gives the blender the raw features
)
print(f"stacking {cross_val_score(stack, X, y, cv=5).mean():.4f}") # 0.9000
stack.fit(X, y)
for name, coef in zip([n for n, _ in members], stack.final_estimator_.coef_[0]):
print(f" blender weight for {name:<10} {coef:+.4f}")Reading the blender’s coefficients is the payoff over voting: it tells you, in numbers, how much the meta-model decided to trust each member.
pch.algoApi sklearn.ensemble.VotingClassifier / StackingClassifier
pch.algoAssumes
- Members make genuinely different errors — different families, not different seeds
- For soft voting, member probabilities are calibrated
- For stacking, enough training data for the blender to learn from J numbers per row
pch.algoCost
- pch.algoTrain
sum of member costs; stacking multiplies by k folds- pch.algoPredict
sum of member costs- pch.algoMemory
sum of member sizes
J = number of members, k = folds used for out-of-fold predictions
pch.algoHyperparams
-
votingdefault hard 'soft' averages probabilities and usually wins when members are calibrated. Check calibration first. -
weightsdefault None Manual per-member weights for voting. A cheap alternative to stacking when you already know the ranking. -
final_estimatordefault LogisticRegression The blender. Keep it simple; it only sees J features. -
cvdefault 5 Folds for out-of-fold predictions. Lower is faster and leakier; do not set it to 'prefit' unless you know exactly why. -
passthroughdefault False Give the blender the original features as well. Occasionally helps, frequently overfits.
pch.algoReachFor
- You already have several tuned models of different families
- The members' errors are genuinely uncorrelated
- The last fraction of a point matters — this is standard practice in competitions
- Inference cost is not a constraint
pch.algoLookElsewhere
- Members are all the same family — bagging or boosting is the right tool
- Prediction latency matters; every member runs on every request
- One member clearly dominates the rest, as measured above
- The training set is small; the blender needs data too
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| Bagging | Boosting | Voting | Stacking | |
|---|---|---|---|---|
| Members | One family, resampled | One family, sequential | Different families | Different families |
| Combination | Equal average | Weighted by | Fixed rule | Learned |
| Diversity from | Data resampling | Reweighting | Algorithm choice | Algorithm choice |
| Training | Parallel | Sequential | Parallel | Parallel, then the blender |
| Tuning burden | Low | High | Lowest | Moderate |
| Typical use | Default tabular baseline | Strongest single model | Quick easy win | Competition final step |
Voting is the cheapest ensemble in this phase: if you already have three tuned models, it is one line and no new training.
-
Three classifiers predict P(class 1) of 0.45, 0.48 and 0.93. What do hard and soft voting decide?
Hard voting counts two mild negatives against one positive. Soft voting averages to 0.62 and lets the confident member carry the weight its confidence implies.
pch.quizShowAnswer
B — Hard predicts class 0 (two votes to one); soft predicts class 1 (mean 0.62) — Hard voting counts two mild negatives against one positive. Soft voting averages to 0.62 and lets the confident member carry the weight its confidence implies.
-
Why must a stacking blender be trained on out-of-fold predictions?
A fitted forest is near-perfect on its own training rows. The blender must see each member's performance on data that member did not train on, which is exactly what the cv parameter arranges.
pch.quizShowAnswer
B — Because in-sample predictions from fitted members are unrealistically good, so the blender would learn to trust members far more than it should — A fitted forest is near-perfect on its own training rows. The blender must see each member's performance on data that member did not train on, which is exactly what the cv parameter arranges.
-
Hard voting scored 0.8860 while its best member scored 0.9020. What went wrong?
Hard voting gives every member an identical say. Members at 0.856 and 0.858 can jointly outvote one at 0.902. Soft voting recovered 1.4 of the lost points.
pch.quizShowAnswer
B — Two of the four members are markedly weaker, and equal votes let them outvote the strongest — Hard voting gives every member an identical say. Members at 0.856 and 0.858 can jointly outvote one at 0.902. Soft voting recovered 1.4 of the lost points.
-
You are soft voting over logistic regression, a calibrated SVM and Gaussian naive Bayes. What should you check?
Soft voting averages probabilities as if they were comparable. An overconfident member effectively gets extra votes. Wrap it in CalibratedClassifierCV or use hard voting.
pch.quizShowAnswer
B — Naive Bayes's calibration — it routinely emits 0.9999 without meaning it, which would dominate the average — Soft voting averages probabilities as if they were comparable. An overconfident member effectively gets extra votes. Wrap it in CalibratedClassifierCV or use hard voting.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Hard and soft disagree
Section titled “Exercise 1 – Hard and soft disagree”Exercise 2 – Build a voting ensemble
Section titled “Exercise 2 – Build a voting ensemble”Exercise 3 – Read the blender’s weights
Section titled “Exercise 3 – Read the blender’s weights”Exercise 4 – Why out-of-fold matters
Section titled “Exercise 4 – Why out-of-fold matters”Exercise 5 – Did the ensemble actually earn its place?
Section titled “Exercise 5 – Did the ensemble actually earn its place?”- Hard voting counts labels; soft voting averages probabilities. On 0.45, 0.48 and 0.93 they give opposite answers.
- Soft voting is only as good as the members’ calibration — an overconfident member effectively gets extra votes.
- Stacking learns the combination weights instead of fixing them, and its coefficients tell you how much each member was trusted.
- The blender must be trained on out-of-fold predictions. A fitted forest scores 1.0 on its own training rows and 0.88 on unseen ones.
- Keep the blender simple; it sees only one number per member.
- Measured here: hard 0.8860, soft 0.9000, stacking 0.9000, best member 0.9020. No combination won. Always run that comparison.
Exercise 6 – Hard voting against soft voting on the same probabilities
Section titled “Exercise 6 – Hard voting against soft voting on the same probabilities”Phase 5 ends here. Continue to Phase 6 - Unsupervised Learning — the same rigour applied to problems with no labels at all, where even “is this good?” needs a new answer.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading