Gradient Boosting — XGBoost, LightGBM
Derive gradient and regularized Newton tree updates; run bounded XGBoost and LightGBM experiments with validation-controlled stages.
Original publication date unrecordedUpdated
Lesson preparation & details
Level: intermediate
By the end, you should be able to
- Derive negative-gradient and regularized second-order tree updates
- Select boosting settings and stages on validation with current CPU APIs
Bring with you
Sources checked: . A source check does not change the publication or update date.
Editorial review: · What review means
In this article · 20 sections
Why this session exists
Bagging averages separately trained predictors. Gradient boosting builds a running prediction and asks what correction would reduce its loss next. Each new tree has a target derived from the current ensemble, so the sequence can express relationships that one shallow tree cannot.
This makes boosted trees useful tabular candidates, but not universal competition winners. The learning rate, tree complexity, number of stages, loss and evaluation design interact. We will derive the gradient and second-order updates, calculate a split gain, implement residual boosting, and run bounded XGBoost and LightGBM recipes with an honest validation/test boundary.
Prerequisites
Know decision trees, forest aggregation, regularization and derivatives. Distinguish a raw prediction score from a probability: binary logistic boosting accumulates logits, then applies a sigmoid. Squared-error regression accumulates predictions on the target scale directly.
(a) Intuition · 5 min
Think of successive corrections to an estimate. Begin with a constant, inspect the remaining error, fit a limited model of that error and add part of its prediction. A shallow tree does not need to solve the whole problem; it needs to provide a useful next direction.
The golf analogy is helpful only up to a point: a smaller step does not guarantee a better final result under a finite budget, and repeated corrections can fit mislabeled observations. The loss tells us which errors get attention; a squared-error loss, for example, emphasizes large residuals more than an absolute-error loss.
The three ideas to own before we code
- Sequential target: at round , derive training targets from the current predictions .
- Restricted correction: fit a tree to those targets; its depth/leaf budget limits what one stage can express.
- Controlled update: set , or use loss-specific leaf optimization and shrinkage. Choose stages and other settings with validation, not final test scores.
Successive trees depend on earlier predictions, but work within a round—histograms, feature statistics, distributed reductions—can be parallelized. “Boosting cannot be parallelized” confuses a sequential dependency across stages with all computation inside a stage.
A brief history
The scikit-learn ensemble guide identifies gradient tree boosting as a generalization to differentiable losses and references Friedman (2001). XGBoost's official tutorial develops a regularized second-order tree objective. LightGBM's feature guide describes histogram search and best-first leaf growth, while the CatBoost paper describes ordered boosting and categorical-feature processing to address a particular prediction-shift problem.
This is a progression of mechanisms, not a claim that scikit-learn existed in 2001 or that a single library permanently surpassed the others. Modern XGBoost also offers histogram methods and alternative growth policies; comparing “exact-only XGBoost” against “histogram LightGBM” misstates the available algorithms.
The boosting loop
For half squared loss ,
The residual is the negative gradient, not the gradient. For unhalved squared error there is an extra factor two that can be absorbed into the step-size convention. Initialize with , the best constant under squared error, then fit successive trees to .
Figure text equivalent — labels and relationships
- Node “F0: training mean for squared loss” (square).
- Node “Residual = y - current prediction” (square).
- Node “Fit restricted regression tree to residual” (square).
- Node “Add learning rate times tree output” (square).
- Node “Record training and validation loss” (square).
- Node “Choose stage on validation; evaluate once on test” (square).
- “F0: training mean for squared loss” leads to “Residual = y - current prediction”. Line: normal; ends: arrow point.
- “Residual = y - current prediction” leads to “Fit restricted regression tree to residual”. Line: normal; ends: arrow point.
- “Fit restricted regression tree to residual” leads to “Add learning rate times tree output”. Line: normal; ends: arrow point.
- “Add learning rate times tree output” leads to “Record training and validation loss”. Line: normal; ends: arrow point.
- “Record training and validation loss” leads to “Residual = y - current prediction”: next round. Line: normal; ends: arrow point.
- “Record training and validation loss” leads to “Choose stage on validation; evaluate once on test”. Line: normal; ends: arrow point.
Why fit a tree instead of storing the negative-gradient vector? The vector contains values only at training inputs. A learned function can also predict a correction at a new input. This is functional gradient descent restricted to the directions that our learner can represent.
For binary log loss on logits,
The negative gradient is , while the update is added to , not directly to . For absolute error, subgradients involve the residual sign away from zero, but leaf-value optimization must match the loss; it is not enough to replace residuals with signs and claim an exact MAE algorithm. Custom objectives also need to satisfy a library's derivative/curvature assumptions.
The math — Taylor expansion of the loss
At a stage with current raw scores , add a tree with leaves indexed by . Let and be first and second loss derivatives evaluated at . The local approximation is
Use tree penalty . Dropping constants independent of the new tree and grouping rows by leaf gives
For a fixed leaf assignment with , differentiate and solve:
Splitting one parent into left/right children therefore gives net improvement
Under this displayed definition, accept a candidate for the approximation only when net gain is positive and structural constraints are satisfied. Do not subtract gamma and then require the result to exceed gamma again. Equivalently, one half of the bracketed expression must exceed gamma; the bracket alone must exceed . This algebraic convention is explicit even where explanatory prose in an upstream tutorial is loose about the word “gain.”
Worked leaf/split calculation. Take . Leaf values are and ; the parent is . Net gain is . With learning rate 0.1, the applied left correction is about 0.1333, not 1.3333.
These are exact minimizers of the fixed-structure quadratic surrogate, not of every original loss over every possible tree. Squared loss has an exact quadratic expansion; other losses are approximated locally. Tree search remains greedy. With L1 leaf penalty , the optimal numerator is soft-thresholded: under the same conditions. L1 on leaf weights does not mean global input-feature selection.
Bagging vs Boosting — the great tabular tradeoff
A forest averages randomized predictors fitted to the original labels; boosting sums corrections fitted to the evolving loss gradient. Forest trees are often deeper, while boosting often restricts each tree more strongly, but these are modeling choices rather than definitions.
Boosting can reduce approximation error by combining corrections, and it can also fit noise. Bagging can stabilize variable predictions, and it can change bias through its resampling scheme. Neither algorithm operates on only one term of the bias–variance decomposition. OOB scores can assist a forest's development evaluation; a booster commonly uses a validation curve or CV. A fixed tree count selected honestly by CV is valid—early stopping is useful, not logically mandatory.
Which boosting library to use?
- XGBoost: inspect
tree_methodandgrow_policyexplicitly. The 3.0 API supportshistas well as other search modes anddepthwiseorlossguidegrowth where supported. It provides leaf penalties, row/column sampling, missing-value routing and optional categorical support under documented data requirements. - LightGBM: histograms bin continuous values, reducing the number of candidate boundaries. Best-first leaf growth expands the leaf offering the greatest loss improvement;
num_leaves, minimum leaf support and optional depth limits control complexity. Holding leaf count fixed is not the same experiment as holding depth fixed. - CatBoost: its ordered boosting and categorical processing were designed to reduce a specific target-leakage/prediction-shift mechanism. This is worth considering for category-rich data, but it does not remove the need for time/group splits, feature availability checks or validation.
No fixed “5–10× faster” or “3–8% better” comparison follows. Hardware, sparsity, categorical treatment, search budget and selected quality target affect the result. Grinsztajn et al. (2022) benchmarked a defined set of 45 tabular datasets and reported strong tree-based results on medium-sized data. That is not evidence that trees won nearly every possible dataset or that 70% of all competitions were won by one family.
Hyperparameters that matter (and their sane defaults)
Treat these as candidate settings, not production defaults:
| Choice | Mechanism | What to check |
|---|---|---|
| Learning rate and stages | Scale each correction and number of corrections | Joint validation curve; smaller rate may underfit within the stage budget |
| Depth or leaf count | Limit per-tree interactions and regions | Support in small leaves; latency and model size |
min_child_weight | XGBoost minimum summed Hessian in a child | Not generally a row count; for logistic loss Hessian depends on probabilities |
| Row/column sampling | Restrict observations/candidates during fitting | Rare-class support and validation variability |
| L1/L2 and gamma | Shrink leaf scores and discourage weak splits | Match objective normalization; avoid tuning only one knob in isolation |
| Early-stopping patience | Stop after validation fails to improve sufficiently often | Metric direction, validation ordering and best-iteration prediction behavior |
In LightGBM, setting subsample=0.8 alone does not activate row bagging when subsample_freq=0. The example explicitly sets frequency 1. XGBoost get_score(importance_type='gain') returns average gain where a feature is used; total_gain returns the sum, and unused features can be omitted. Neither is a causal attribution.
(c) Hands-on · 25 min
The CPU lab uses generated data rather than downloading California housing. It retains a scratch residual booster, a learning-rate sweep, early stopping, importance inspection and a LightGBM comparison while bounding fit count and thread use. Versions checked: XGBoost 3.0.5 CPU, LightGBM 4.6.0, scikit-learn 1.7.2.
import numpy as np
import xgboost as xgb
import lightgbm as lgb
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import root_mean_squared_error
from sklearn.tree import DecisionTreeRegressor
X, y = make_regression(n_samples=600, n_features=8, n_informative=5,
noise=15, random_state=42)
Xdev, Xtest, ydev, ytest = train_test_split(X, y, test_size=.2, random_state=42)
Xtr, Xval, ytr, yval = train_test_split(Xdev, ydev, test_size=.25, random_state=43)
# Scratch squared-loss booster: tree fitter supplied by sklearn, stage logic explicit.
base = float(ytr.mean())
Ftr = np.full(len(ytr), base)
Fval = np.full(len(yval), base)
trees, curve, train_curve = [], [], []
eta = .1
for stage in range(60):
tree = DecisionTreeRegressor(max_depth=2, min_samples_leaf=5, random_state=stage)
tree.fit(Xtr, ytr-Ftr)
Ftr += eta*tree.predict(Xtr)
Fval += eta*tree.predict(Xval)
trees.append(tree)
train_curve.append(root_mean_squared_error(ytr, Ftr))
curve.append(root_mean_squared_error(yval, Fval))
best_count = int(np.argmin(curve))+1
print('scratch selected stages', best_count, 'validation', curve[best_count-1])
assert train_curve[-1] <= root_mean_squared_error(ytr, np.full(len(ytr), base))
# All model/learning-rate selection uses validation, not final test.
candidates = []
for rate in [.05, .2]:
m = xgb.XGBRegressor(n_estimators=160, learning_rate=rate, max_depth=3,
tree_method='hist', subsample=.8, colsample_bytree=.8,
reg_lambda=1, min_child_weight=5, objective='reg:squarederror',
eval_metric='rmse', early_stopping_rounds=12, random_state=42, n_jobs=1)
# Validation must be last: the last eval_set entry drives early stopping.
m.fit(Xtr, ytr, eval_set=[(Xtr, ytr), (Xval, yval)], verbose=False)
score = root_mean_squared_error(yval, m.predict(Xval))
candidates.append((score, 'XGBoost', m))
print('rate', rate, 'best zero-based iteration', m.best_iteration, 'validation', score)
assert np.allclose(m.predict(Xval), m.predict(Xval, iteration_range=(0, m.best_iteration+1)))
lm = lgb.LGBMRegressor(n_estimators=160, learning_rate=.1, num_leaves=15,
min_child_samples=10, subsample=.8, subsample_freq=1, colsample_bytree=.8,
random_state=42, n_jobs=1, verbosity=-1)
lm.fit(Xtr, ytr, eval_set=[(Xval, yval)], eval_metric='rmse',
callbacks=[lgb.early_stopping(12, verbose=False)])
candidates.append((root_mean_squared_error(yval, lm.predict(Xval)), 'LightGBM', lm))
score, name, chosen = min(candidates, key=lambda row: row[0])
print('chosen library', name, 'validation', score,
'final test RMSE', root_mean_squared_error(ytest, chosen.predict(Xtest)))
# Scratch comparison is reported after its own validation-selected stage is frozen.
scratch_test = np.full(len(ytest), base)
for tree in trees[:best_count]:
scratch_test += eta*tree.predict(Xtest)
print('scratch final test RMSE', root_mean_squared_error(ytest, scratch_test))
booster = candidates[0][2].get_booster()
print('average split gain', booster.get_score(importance_type='gain'))
print('total split gain', booster.get_score(importance_type='total_gain'))
print('scratch train/validation curve endpoints', train_curve[0], train_curve[-1], curve[0], curve[-1])
# Verify quadratic leaf optimum and net split gain independently.
GL, HL, GR, HR, lam, gamma = -4., 2., 2., 2., 1., .1
left, right, parent = -GL/(HL+lam), -GR/(HR+lam), -(GL+GR)/(HL+HR+lam)
assert np.isclose(GL+(HL+lam)*left, 0)
assert np.isclose(GR+(HR+lam)*right, 0)
Q = lambda g,h,w: g*w + .5*(h+lam)*w*w
net = Q(GL+GR, HL+HR, parent) - Q(GL,HL,left) - Q(GR,HR,right) - gamma
gain = .5*(GL**2/(HL+lam)+GR**2/(HR+lam)-(GL+GR)**2/(HL+HR+lam))-gamma
assert np.isclose(net, gain) and np.isclose(gain, 17/6)What each block does
The scratch loop implements half-squared-loss residual boosting, using sklearn only for a tree fitted at each stage. It stores the trees so the selected prefix can be applied to unseen rows; returning only predictions for one supplied test matrix would not leave a reusable model. The prefix length is chosen from validation RMSE.
The XGBoost constructor contains the early-stopping configuration in the checked API. Its sklearn prediction methods use best_iteration automatically; the assertion checks the same result with an explicit range. The native xgboost.train API has different best-model handling, so do not transplant that claim to every API. The last evaluation set is validation, not training or final test.
The LightGBM fit uses a callback for early stopping and activates row subsampling with a nonzero frequency. Model family and learning rate are chosen using validation. The final test is opened only after choices are fixed. The scratch model is separately frozen before its final comparison; choosing again from these final test scores would consume that holdout.
These small fits verify API compatibility, finite predictions and arithmetic, not the superiority of a library. No promise is made about which round is selected or whether the validation curve visibly turns upward. A flat or still-decreasing validation curve is a possible measured result.
(d) Production reality · 15 min
Failure diagnosis rather than universal settings
Large steps or excessive capacity: inspect training and validation loss by stage. If a smaller rate needs more stages, account for the increased model size and inference latency. Halving the rate does not mathematically guarantee doubling the required count or improving quality. Restricting leaves may help more than reducing rate.
No defensible stage selection: either use a held-out early-stopping set or select stages through appropriate CV. Both are valid. Record the chosen iteration, objective, metric direction and whether the serialized predictor uses the best stage. After early stopping, a deliberate refit on combined development data with a fixed selected count is another procedure that needs evaluation, not an invisible step.
Rare positives or shifted data: accuracy may conceal poor positive recall. Use deployment-relevant splits, AP/PR behavior and an explicit alerting cost or capacity. scale_pos_weight changes the training loss; it is not a guaranteed calibration correction. At 0.1% positives, the negative/positive ratio is about 999, not a universal recommended weight. Test candidate weighting and minimum-Hessian choices with enough positive examples; min_child_weight=100 can be very restrictive under logistic Hessians and cannot be interpreted as “100 rows.”
Noisy labels: squared loss places large gradients on large errors; logistic and other objectives behave differently. Audit extreme residuals for label errors before adding capacity. Robust losses change the problem but do not resolve missing information. Repeated validation search can overfit even when each individual training run uses early stopping.
Choosing a compute/quality trade-off
A low rate and many trees may improve some fits but increases training, storage and prediction work. A higher rate with fewer stages can speed development and meet latency limits while losing quality on a particular task. Row/column sampling and stronger leaf penalties add another way to control fit, with additional search interactions. Compare a small number of justified candidates before launching a broad search, and preserve a final evaluation boundary.
For category-rich tables, native categorical handling may simplify preprocessing, but its rules must match training and serving. For unstructured text/images, learned representations or hybrid models may be useful. Neither “trees always win tabular” nor “deep learning replaces features” follows from the cited benchmark.
Where this shows up in the rest of the plan
Metrics separates ranking summaries from decisions. Feature engineering ensures that the strong learner sees only information available at prediction time. Regularization and model-selection principles still apply even when the library exposes a convenient high-level estimator.
(e) Recall + stretch · 10 min
- Bagging versus boosting? Answer: independently randomized fits combined by averaging versus sequential additions fitted to a current loss-correction target. The bias/variance effects are not exclusive labels.
- Why “gradient”? Answer: fit a function approximating negative derivatives of loss with respect to current training predictions. Residual fitting is the half-squared-loss special case.
- Derive a leaf update. Answer: differentiate to obtain when the denominator is positive. It is conditional on a fixed tree structure.
- Is early stopping mandatory? Answer: no; validation-controlled stage selection is needed, and CV over fixed counts is valid. Early stopping is an efficient common choice.
- XGBoost versus LightGBM? Answer: compare configured search and growth modes, not obsolete exact-only claims. LightGBM uses histogram/best-first methods; XGBoost offers histogram and alternative growth configurations too.
- When try CatBoost? Answer: categorical processing and ordered boosting are relevant candidates for category-rich data; still validate splits and features.
- Lower learning rate effect? Answer: usually needs more stages for similar training progress, but neither an exact inverse law nor improved validation quality is guaranteed.
- Scaling? Answer: standard scaling is usually unnecessary for threshold trees. Encoding, missing-value policy, histogram/precision effects and feature availability still matter.
- What did the 2022 benchmark establish? Answer: results for a specified suite of 45 tabular datasets and tuning protocol, with strong tree performance on medium-sized data—not a universal win rate.
- Fraud stretch. Answer: choose a library that fits data/serving constraints; tune learning rate plus stages, leaf complexity/support, sampling and possibly class weight on time/group-aware validation. Evaluate AP and recall/precision at a feasible operating point, then calibration if scores feed expected-cost calculations. Do not turn illustrative parameters into a validated production configuration.
Explain-out-loud test
Explain why a classification booster updates logits rather than adding residuals to probabilities. Then derive net split gain, carefully stating whether gamma is already subtracted, and identify which dataset controls early stopping in the program.
What comes next
Continue with Evaluation Metrics to choose what “better” means before using a validation curve to select a model.
Sources and execution boundary
Sources checked 2026-10-07: XGBoost 3.0 objective tutorial, API, parameters; LightGBM 4.6 features and parameters; CatBoost paper; Grinsztajn et al.. Only the bounded local CPU programs and scalar identities are exercised; no California-housing download, GPU/distributed training, timing comparison, CatBoost training or production fraud benchmark is claimed.
Pause / Recall / Apply
Can you explain it without the page?
Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.
Stored in this browser only. No account, no sync. Clearing browser data removes your record.