Gradient Boosting
Fit a tree, compute the residuals, fit the next tree to them, repeat. Each tree follows the negative gradient of the loss; the learning rate shrinks each step; the number of trees is the capacity knob, chosen by early stopping.
The problem, the obvious approach, and why it breaks
Every lesson starts where the work starts: someone has a problem, and the first model that comes to mind looks fine offline.
Boosting builds trees sequentially, each on the errors of the ensemble so far. Why is that a gradient descent, why does it overfit differently from a forest, and why does it find a leak before it finds anything else?
A payments team wants a fraud model with the highest recall they can get at a fixed review capacity. Their gradient-boosted model beats every baseline offline by a wide margin. In production, recall is close to the baseline's, and the top feature by gain turns out to be a status column written by the case-management system after a fraud investigation opens.
Gradient boosting is the strongest tabular learner. Throw every column at it, train until the training loss stops falling, and ship whatever gives the best validation number.
Boosting fits residuals: whatever explains the most remaining error gets the next tree. A leaked column explains almost all of it, so the first trees are built almost entirely on case_status, and the rest of the model is fitted to what little error remains. Validation is superb; production, where the column is null at decision time, is the baseline with extra steps (Data Leakage).
- Boosting fits residuals: whatever explains the most remaining error gets the next tree. A leaked column explains almost all of it, so the first trees are built almost entirely on
case_status, and the rest of the model is fitted to what little error remains. Validation is superb; production, where the column is null at decision time, is the baseline with extra steps (Data Leakage). - Without early stopping, boosting keeps adding trees that fit the label noise — the missed fraud and the mislabelled chargebacks — because after the real signal is exhausted those are the residuals. Boosting has no averaging to absorb that; each tree on noise moves the ensemble toward the noise.
- The training loss curve is smooth and monotone, so "train until it stops falling" runs for thousands of rounds and lands deep in the overfit regime. Nobody looked at the validation curve because the training one was so reassuring.
What is being predicted, and from what data
This domain leads with these two. A target nobody defined precisely is a label nobody can trust, and a dataset nobody can describe is a model nobody can debug.
- Predict whether a transaction is fraudulent. The label is the confirmed-fraud flag from the investigations team, set days to weeks after the transaction.
- The decision is whether to queue the transaction for review; the number that matters is recall at the review capacity, on transactions the investigators have not yet seen.
- One example is one transaction with account aggregates, merchant features, device signals and — from a join to the case table —
case_statusanddays_since_last_review. Tens of millions of rows, a small positive rate. - The label is noisy: investigators miss some fraud, and some "fraud" labels are chargebacks with other causes.
How it actually works
Precisely enough to predict its behaviour — not a framework API.
- Start with a constant prediction. At round
m, compute for every training row the negative gradient of the loss with respect to the current prediction — for squared error that is the residualy − F(x); for log loss it isy − p(x). Fit a small tree to those values. Add it to the ensemble, scaled by a learning rateη:F_m(x) = F_{m−1}(x) + η · h_m(x). The tree is a step down the loss surface taken in function space. - Each tree is shallow — a few levels — so it is a weak, high-bias learner; the ensemble gets its capacity from *many* of them. That makes the number of rounds the capacity knob: more trees, more fit. The learning rate shrinks each step so that many small steps are taken rather than few large ones; smaller
ηneeds more rounds and generally generalises better, which is why the two are tuned together with early stopping on validation deciding the round count (Early Stopping). - Because every round targets the largest remaining error, boosting is greedy about signal in a way a forest is not. That is its strength on clean data and its hazard on dirty data: a leaked feature, a mislabelled cluster or an outlier is the largest residual, and boosting goes there first.
Model 1, residuals, Model 2, residuals, Model 3
Begin with a constant — the mean, or the log-odds of the base rate. Compute how wrong it is on every row. Fit a small tree to that wrongness. Add a fraction of it to the prediction. Compute the new wrongness. Fit the next tree to that. The ensemble is the sum, and every term was fitted to what the terms before it missed.
For squared error the "wrongness" is literally the residual. For log loss it is y − p, the difference between the label and the current probability — the negative gradient of the loss with respect to the prediction. That is why the method is *gradient* boosting: each tree is a step of gradient descent, taken not in parameter space but in the space of functions, with the tree approximating the direction of steepest descent.
1import numpy as np2 3def sigmoid(z):4 return 1.0 / (1.0 + np.exp(-z))5 6def boost(fit_small_tree, X, y, X_val, y_val, lr=0.1, max_rounds=2000, patience=50):7 base = np.log(y.mean() / (1 - y.mean())) # F0: log-odds of the base rate8 F, F_val = np.full(len(y), base), np.full(len(y_val), base)9 trees, best, best_round, since = [], float("inf"), 0, 010 for m in range(max_rounds):11 residual = y - sigmoid(F) # negative gradient of log loss wrt F12 tree = fit_small_tree(X, residual) # a shallow tree on the residuals13 F += lr * tree.predict(X) # one step in function space14 F_val += lr * tree.predict(X_val)15 trees.append(tree)16 p = sigmoid(F_val)17 val_loss = -np.mean(y_val * np.log(p + 1e-12) + (1 - y_val) * np.log(1 - p + 1e-12))18 if val_loss < best:19 best, best_round, since = val_loss, m + 1, 020 else:21 since += 122 if since >= patience:23 break24 return trees[:best_round], base, lr # keep only the rounds up to the minimumThe line that matters is residual = y - sigmoid(F). Whatever explains most of that vector gets the next tree. If a column encodes the label, it explains nearly all of it, and every early round is spent on that column. That is the leak-seeking behaviour, and it is not a bug — it is the algorithm doing exactly what it is for.
The leak it finds first
The fraud model's case_status column is open, confirmed or cleared for transactions that were investigated, and null otherwise. Investigated transactions are overwhelmingly the fraudulent ones. At training time the column is populated for the whole history; at decision time it is null for every transaction, because the investigation has not happened yet.
Boosting's first rounds split on it, and the validation set — built from the same historical table — rewards them. The ensemble's remaining rounds fit what little error is left. The result is a model whose apparent strength is almost entirely a column that will never be populated when it matters.
looks like A categorical from the case-management table, joined by transaction id. Present on every historical row; top of the gain ranking by a wide margin.
why it leaks It is written by the process that produces the label. A transaction has a case status because someone investigated it, and they investigated it because it looked like fraud — the feature is a near-copy of the outcome, delayed.
fix Build the feature table as-of the transaction timestamp, from sources that existed at that instant; drop anything written by the investigation or the chargeback process (Point-in-Time Correctness).
Recall at review capacity far above the rule baseline; the gain ranking dominated by a single column.
Recall at the same capacity roughly level with the rule baseline in the first investigation cycle; the review queue is full of the same transactions the rules would have caught.
- 1
case_statuswas populated in training and null in production; the early rounds that carried most of the model's strength are inert at decision time. - 2The remaining rounds were fitted to residuals that the leaked column had already nearly eliminated, so they learned little about fraud from the honest features.
- 3A minor contribution from label delay: the newest training rows had incomplete investigations and their labels were biased toward negative.
Rounds are capacity; the validation curve chooses them
A forest's trees are independent and more of them cannot overfit further; a boosted model's trees are cumulative and more of them always fit more. The number of rounds is therefore the capacity knob, and it is the one that the training curve cannot set: training loss falls monotonically for as long as you let it.
The learning rate is the other half. A small η means each tree contributes a little, the descent is gentle and more rounds are needed; the result generally generalises better than a large η with few rounds. Tune them together, let early stopping on a grouped, time-ordered validation set pick the round count, and treat that count as an output of training, logged with the artifact.
Every feature the boosted model was trained on is computed from information that existed at the moment the decision is made, and the same is true at every retrain.
holds when The training table is built by a point-in-time join against event timestamps, and new columns cannot enter the feature set without the same as-of discipline.
breaks when A convenient join adds a column from a downstream system — case management, chargebacks, customer service — that is populated in history and empty at decision time.
respond Remove the feature, rebuild the as-of table, and re-select the model on honest numbers. Adding rounds or changing the learning rate does nothing about a leak.
How to build it
Most important first.
- Audit features for leakage *before* boosting anything, and treat a suspiciously dominant feature in the gain ranking as a leak until proven otherwise (The Leakage Audit, Target Leakage).
- Choose the number of rounds by early stopping on a validation set that respects time and entities; never by the training curve (Time-Based Split).
- Regularise structurally: shallow trees, a small learning rate, minimum rows per leaf, row and column subsampling per round. Each is a knob with a validation-chosen value (Regularisation).
- Use a loss that matches the decision — log loss for probabilities, a ranking loss for ordering — and calibrate afterward if calibrated probabilities are needed; boosted scores are not probabilities by default (Calibration).
What to measure
Which number actually maps to the decision — and which numbers look relevant and are not.
- Recall at review capacity on a time-ordered validation set with leak-free features. The offline number the team quoted was on leaked features and measured nothing about the decision.
- The validation curve by round, with the early-stopping point marked. The round count is a decision and the curve is its evidence.
- Gain-based importance, read as a leak detector rather than as insight: a single feature with most of the gain is either the answer or the answer leaking.
What must stay true after deployment
The field this whole domain exists for. A model is a set of assumptions with weights attached; these are the ones a monitor or a test should be checking.
- No feature available at training time is written after the fraud decision point; the feature table is as-of transaction time at every retrain, not just the first one.
- The label noise rate is stable; a change in investigation policy changes what "fraud" means and what the residuals point at.
- The round count chosen by early stopping remains near the validation optimum for the current data volume and noise; both move it.
- Offline: a point-in-time audit of every feature; the round-by-round validation curve under a time-based, account-grouped split; gain importance reviewed for single-feature dominance.
- Online: recall at capacity on the first cycle of investigation outcomes against the leak-free validation number. A production number near the baseline's when validation was far above it is the leak signature.
- Over time: re-run early stopping at each retrain and log the chosen round count; track the validation-versus-production gap as labels arrive.
What can go wrong
- The leak is fixed, the model is retrained, and it is now much less impressive; the team, having promised the leaked number, reaches for more rounds and overfits the noise instead.
- Early stopping is on but the validation set is a random split of transactions from accounts that also appear in training; the curve turns late and the chosen round count is too high (Entity Leakage).
- The learning rate is dropped to squeeze out a last improvement, the round count triples, and inference latency triples with it — nobody re-measured the serving budget.
- Sequential fitting cannot be parallelised across trees the way a forest can; training is a chain of dependent rounds, and small learning rates make it a long one.
- Boosting's sensitivity to signal is its sensitivity to leakage and noise. The same greed that makes it strong on clean features makes it the model most likely to ship a leak with a great number attached.
- The score distribution is not calibrated, and the stronger the boosting the more extreme it tends to be; a probability-consuming downstream needs a calibration step and its own held-out data.
- "Boosting found that case status predicts fraud." It found a column written after the fraud was found. Boosting will always find the leak first because the leak is the largest residual.
- "Training loss is still falling, so add rounds." Training loss falls until the noise is fitted. The round count is chosen where the *validation* curve turns.
- "It beat the baseline by a wide margin offline, so ship it." That gap is exactly the size a leak produces. A margin that large on a hard problem is a reason to audit, not to promote.
Where this applies
ML advice is stated as universal far more often than it is. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALFitting each successive learner to the negative gradient of a differentiable loss is a general method; trees are the usual base learner because they are fast to fit and handle mixed features, but the mechanism and its leak-seeking behaviour hold for any base learner.
- DATA-SPECIFICOn clean tabular data with informative engineered features, boosting is usually the strongest single model; on very small datasets its variance is hard to control, and on raw images or text it has nothing to split on and a network's learned representation wins.
- SIMPLIFIEDThe description uses the squared-error and log-loss gradients and omits second-order terms, leaf-value optimisation and the regularised objective, which the XGBoost / LightGBM lesson adds; the relative offline-versus-online numbers in the device are illustrative of the leak gap, not measured.
Where the depth lives
This domain teaches the model and hands the rest off by name.