ML from Scratch · Lecture 2 of 5

Bagging vs Boosting, and XGBoost from the Objective Up

Two opposite ways to combine weak learners: average independent ones, or build them in sequence to fix each other's mistakes. Gradient boosting as gradient descent, and the second-order maths inside XGBoost.

November 15, 2026By Diganta Bhattacharya5 min read

Bias, variance and two strategies

Expected test error decomposes as $\text{bias}^2+\text{variance}+\text{noise}$.

AdaBoost: reweighting mistakes

Start with equal sample weights. At round $m$ fit a weak classifier $h_m$, compute its weighted error $\varepsilon_m$, give it a vote $\alpha_m=\tfrac12\ln\frac{1-\varepsilon_m}{\varepsilon_m}$, and multiply the weights of misclassified points by $e^{\alpha_m}$. The final model is $\operatorname{sign}\sum_m\alpha_mh_m(x)$. AdaBoost turns out to be stagewise minimisation of the exponential loss $e^{-yF(x)}$, a hint at the general view below.

Gradient boosting: descent in function space

We want $F$ minimising $\sum_i\ell(y_i,F(x_i))$. Treat the predictions $F(x_i)$ as parameters and take a gradient step:

$$r_{im}=-\frac{\partial\ell(y_i,F(x_i))}{\partial F(x_i)}\Big|_{F=F_{m-1}},\qquad F_m=F_{m-1}+\eta\,h_m,\quad h_m\approx r_{\cdot m}.$$

We can't move each $F(x_i)$ freely (we need a function that generalises), so we fit a small tree $h_m$ to the pseudo-residuals $r_{im}$. For squared loss $\ell=\tfrac12(y-F)^2$, $r=y-F$: each tree literally fits what's left over.

With few rounds the fit is a crude staircase (high bias). As rounds increase it hugs the curve. With a large learning rate and many rounds, the test error eventually rises as the model starts fitting noise. The shrinkage η trades rounds for robustness: small η plus many trees usually generalises best.

XGBoost: a regularised, second-order version

XGBoost (Chen & Guestrin, 2016) writes the objective at round $t$ with an explicit penalty on tree complexity:

$$\mathcal L^{(t)}=\sum_i\ell\big(y_i,\hat y_i^{(t-1)}+f_t(x_i)\big)+\gamma T+\tfrac12\lambda\sum_{j=1}^Tw_j^2,$$

where $T$ is the number of leaves and $w_j$ the leaf values. Take a second-order Taylor expansion (the same trick as Lecture 4 of the Black–Scholes series) with $g_i=\partial_{\hat y}\ell$ and $h_i=\partial^2_{\hat y}\ell$:

$$\mathcal L^{(t)}\approx\sum_{j=1}^T\Big[G_jw_j+\tfrac12(H_j+\lambda)w_j^2\Big]+\gamma T,\qquad G_j=\sum_{i\in I_j}g_i,\;H_j=\sum_{i\in I_j}h_i.$$

Each leaf is an independent quadratic in $w_j$, minimised at

$$w_j^{*}=-\frac{G_j}{H_j+\lambda},\qquad \mathcal L^{*}=-\frac12\sum_j\frac{G_j^2}{H_j+\lambda}+\gamma T.$$

So a split of a leaf into $L$ and $R$ is worth

Split gain$$\text{Gain}=\frac12\Big[\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{(G_L+G_R)^2}{H_L+H_R+\lambda}\Big]-\gamma.$$

Split only if Gain > 0, so γ acts as a minimum gain and automatically prunes. λ shrinks leaf weights, which is ridge regularisation on the tree. Because only $g$ and $h$ are needed, any twice-differentiable loss works: squared error ($g=\hat y-y$, $h=1$), logistic ($g=p-y$, $h=p(1-p)$), Poisson, ranking losses and so on.

The engineering that made it win Kaggle

import xgboost as xgb
model = xgb.XGBRegressor(n_estimators=2000, learning_rate=0.03, max_depth=6,
                         subsample=0.8, colsample_bytree=0.8, reg_lambda=1.0, gamma=0.0,
                         early_stopping_rounds=100)
model.fit(X_tr, y_tr, eval_set=[(X_va, y_va)], verbose=200)
Tuning order that worksFix η ≈ 0.05 and use early stopping for the number of trees. Then tune depth / min_child_weight, then subsampling, then λ/γ. Lower η at the end. (Most of the Kaggle playground pages in the Kaggle section follow exactly this loop.)

Key takeaways

Exercises

  1. Derive g and h for the logistic loss ℓ = −[y ln p + (1−y) ln(1−p)], p = σ(ŷ).
  2. Compute the XGBoost gain for G_L = −4, H_L = 3, G_R = 2, H_R = 2, λ = 1, γ = 0.5. Do we split?
  3. Implement gradient boosting with depth-2 regression trees from Lecture 1 and compare η = 1 with η = 0.1.