AdaBoost
Reweight examples each round to focus on mistakes, and derive the exponential-loss view of AdaBoost.
Bagging averages many trees fit to perturbed copies of the data, reducing variance. Boosting takes the opposite approach: fit weak learners sequentially, each one trained to correct the previous ensemble's mistakes. AdaBoost (adaptive boosting) is the original[1], and its analysis as exponential-loss minimization seeds the modern gradient-boosting view[2].
The algorithm
For binary labels :
- Initialize weights .
- For :
- Fit a weak learner minimizing the weighted error .
- Compute its weight in the ensemble:
- Update example weights:
- Return the additive predictor .
Each round upweights misclassified points (the exponent is positive when ), pulling the next learner's attention toward them. Correctly classified points are downweighted.
Weak learners
A weak learner does only marginally better than random guessing (). For tabular problems the usual choice is a decision stump (a one-split tree). Stumps are deliberately underpowered, so each one captures a tiny piece of structure, and boosting builds the full classifier from their sum.
When exactly, and the round contributes nothing. When , : a perfect weak learner gets full weight.
AdaBoost as exponential-loss minimization
The remarkable connection: AdaBoost is stagewise additive modeling with the exponential loss .
Theorem. Define . AdaBoost's choice of at round is exactly the pair that minimizes
over all , with fixed.
Sketch. Set . The objective factors as . For a -valued , split the sum into correctly and incorrectly classified groups; the inner minimization in for fixed gives the AdaBoost formula for and the outer one in becomes the weighted-error minimization.
This recasting matters because it generalizes: swap the exponential loss for any differentiable loss and the algorithm becomes gradient boosting (next lesson).
Why AdaBoost works
- Bias reduction: each weak learner is high-bias, but the sum can fit arbitrarily complex functions. The training error decays exponentially in for weak learners with edge.
- Margin maximization: even after training error hits zero, more rounds keep increasing the margin , which generally improves test performance. This is why AdaBoost often does not overfit even with many rounds.
Failure modes
- Label noise wrecks AdaBoost. Mislabeled points get exponentially upweighted, and the ensemble chases them. Regularized variants (LogitBoost, GentleBoost) help.
- Computational: each round refits on reweighted data, and the rounds are sequential. Random forests can train each tree in parallel; boosting cannot.
Check it on arrays
import numpy as np
rng = np.random.default_rng(0)
n, d = 400, 2
X = rng.normal(size=(n, d))
y = np.sign(X[:, 0] * X[:, 1] - 0.3 + 0.4 * rng.normal(size=n))
def weighted_stump(X, y, w):
"""Best 1-split stump: feature, threshold, sign, weighted error."""
n, d = X.shape
best = (None, None, 1, 1.0)
for j in range(d):
order = np.argsort(X[:, j])
for k in range(1, n):
t = (X[order[k - 1], j] + X[order[k], j]) / 2
for sign in (+1, -1):
pred = sign * np.where(X[:, j] <= t, +1, -1)
err = np.sum(w * (pred != y))
if err < best[3]:
best = (j, t, sign, err)
return best
def adaboost(X, y, T=30):
n = len(y)
w = np.full(n, 1 / n)
stumps, alphas = [], []
for _ in range(T):
j, t, sign, eps = weighted_stump(X, y, w)
if eps >= 0.5 - 1e-9: break
alpha = 0.5 * np.log((1 - eps) / max(eps, 1e-12))
pred = sign * np.where(X[:, j] <= t, +1, -1)
w = w * np.exp(-alpha * y * pred); w /= w.sum()
stumps.append((j, t, sign)); alphas.append(alpha)
return stumps, alphas
def ada_predict(X, stumps, alphas):
F = np.zeros(len(X))
for (j, t, sign), a in zip(stumps, alphas):
F += a * sign * np.where(X[:, j] <= t, +1, -1)
return np.sign(F)
# Try increasing T and watch train + held-out
idx = rng.permutation(n); tr, te = idx[:300], idx[300:]
for T in [1, 5, 20, 50]:
stumps, alphas = adaboost(X[tr], y[tr], T=T)
tr_acc = np.mean(ada_predict(X[tr], stumps, alphas) == y[tr])
te_acc = np.mean(ada_predict(X[te], stumps, alphas) == y[te])
print(f"T={T:>2}: train acc={tr_acc:.3f} test acc={te_acc:.3f} (stumps used: {len(stumps)})")Training accuracy climbs as more stumps are added; test accuracy also improves and then plateaus.
Exercises
Exercise 1. A weak learner at round achieves weighted error . Compute its ensemble weight , and the unnormalized weight-update multiplier for a correctly classified example and for a misclassified one.
Solution
Step 1. Apply the ensemble-weight formula:
Step 2. For a correctly classified example, , so the multiplier is . The weight shrinks.
Step 3. For a misclassified example, , so the multiplier is . The weight grows. The ratio of the two multipliers is , so a miss gets upweighted by exactly the odds of the learner being right.
Exercise 2. Show that after the round- update with the AdaBoost choice of , the reweighted distribution makes the current weak learner look like random guessing: its weighted error under equals .
Solution
Step 1. Split the round- weighted mass by correctness. Misclassified examples carry total weight , correct ones , under . The update multiplies misses by and hits by .
Step 2. Substitute . The new unnormalized mass on misses is , and on hits is . The two masses are equal.
Step 3. Since the normalizer is their sum , the new weighted error on the misses is
The next round therefore cannot reuse : it has been made to look useless, forcing the boosting to find new structure.
Exercise 3 (code). Run one AdaBoost round by hand in numpy. Given fixed stump predictions and labels, compute the weighted error, , and the updated normalized weights, then verify the property from Exercise 2: the stump's weighted error under the new weights is .
Solution
Step 1. Start from uniform weights, score the stump, derive , apply the exponential reweighting, normalize, then recompute the weighted error.
import numpy as np
y = np.array([+1, +1, -1, -1, +1, -1, +1, -1], dtype=float)
pred = np.array([+1, -1, -1, -1, +1, +1, +1, -1], dtype=float) # 2 of 8 wrong
w = np.full(len(y), 1 / len(y))
eps = np.sum(w * (pred != y))
alpha = 0.5 * np.log((1 - eps) / eps)
w_new = w * np.exp(-alpha * y * pred)
w_new /= w_new.sum()
eps_new = np.sum(w_new * (pred != y))
assert np.isclose(eps, 0.25) # 2 wrong out of 8 under uniform weights
assert np.isclose(eps_new, 0.5) # reweighting neutralizes this stump
print(f"eps={eps:.3f}, alpha={alpha:.3f}, eps under new weights={eps_new:.3f}")Step 2. The initial weighted error is , giving as in Exercise 1. After the update the same stump scores exactly , confirming the equal-mass result derived by hand: AdaBoost rebalances the weights so the just-used learner is no better than a coin flip.
The next lesson generalizes this idea to any differentiable loss with gradient boosting.
References
- [1]Freund, Y., & Schapire, R. E. (1997). "A decision-theoretic generalization of on-line learning and an application to boosting". Journal of Computer and System Sciences, 55(1), 119-139. The original AdaBoost algorithm (doi:10.1006/jcss.1997.1504).↩
- [2]Hastie, T., Tibshirani, R., & Friedman, J. (2009). The Elements of Statistical Learning (2nd ed.). Springer. AdaBoost as stagewise additive modeling with exponential loss (doi:10.1007/978-0-387-84858-7).↩