XGBoost Tutorial
XGBoost derived from first principles — the loss, the gradient, the Hessian, the gain, the whole machine — taught through a single meteorological question: how cold will tonight get?
Every chapter pairs a derivation with an instrument you can turn over in your hands. Nothing is asserted that you cannot poke. By the end you will be able to write down XGBoost's objective from memory, explain what every hyperparameter does to that objective, and predict — before touching a slider — what changing it will do to a model.
The observing programme below is sequential: each chapter uses the machinery of the last. The glyphs are live — they are drawn by the same code you'll meet inside.
The Loss Function
Everything in XGBoost — every split, every leaf value, every hyperparameter — is in service of one number. Before we grow a single tree, we must decide precisely what it means to be wrong.
1.1 — A forecast and its price
Our running problem, start to finish: at 18:00 UTC an observer at a small atmospheric observatory records the evening conditions — cloud cover, wind speed, dewpoint depression, time of year — and must forecast the overnight temperature drop: how far the thermometer will fall between the evening reading and dawn minimum. Clear, calm, dry winter nights radiate ferociously and can drop 12 °C or more; overcast, windy summer nights barely drop 2 °C. The physics is real, the interactions are nonlinear, and the record is imperfect. It is a perfect job for boosted trees.
A model produces a prediction ŷ for a true outcome y. A loss function l(y, ŷ) converts the pair into a price: zero when the forecast is perfect, growing as it degrades. The choice of loss is a modelling decision, not a technicality — it declares which mistakes you care about. Forecast a 10 °C drop when the true drop is 4 °C and the road-gritting lorries roll out for nothing; forecast 4 °C when the truth is 10 °C and the roads ice over. Squared error says these two failures cost the same; a real forecaster might disagree, and later we will see that XGBoost lets you say so.
1.2 — Two derivatives are all you get
Here is the single most important design decision in XGBoost, stated up front so you can watch everything else flow from it. The algorithm never looks at your loss function directly. For each data point it asks the loss for exactly two numbers, evaluated at the current prediction:
Everything downstream — leaf values, split gains, min_child_weight, why logistic regression "just works" in the same code — is arithmetic on g and h. Swap the loss, and only these two formulas change; the entire tree-growing machine is untouched. This is why XGBoost ships regression, classification, ranking and survival objectives in one engine, and why you can hand it any twice-differentiable loss of your own invention.
1.3 — The gallery of losses
Meet the candidates. For regression on our temperature drop, the default is squared error; its gradient is the (signed) error itself and its Hessian is the constant 1 — a fact that will quietly simplify half the formulas in this course, and quietly hide the other half's purpose. Absolute error is more robust to freak nights but has a broken second derivative. Pseudo-Huber interpolates between them. And log-loss, for classifying will there be a ground frost?, has the most instructive Hessian of all.
- Squared: drag ŷ. The gradient line is straight (g = ŷ − y) and the Hessian is flat at 1. Curvature never changes — the loss trusts big corrective steps.
- Absolute: the gradient is ±1 no matter how wrong you are — a 12 °C error shouts no louder than a 0.5 °C one — and h = 0 everywhere. Hold that thought: in Chapter III a zero Hessian will try to divide by zero, and you'll see exactly why libraries fake h = 1 here.
- Pseudo-Huber: quadratic near the truth, linear far away. Watch h fade towards zero in the tails — distant outliers get gradient but lose authority.
- Log-loss: now ŷ is a raw score pushed through a sigmoid, p = σ(ŷ). The gradient is the beautifully simple p − y, and h = p(1−p) peaks at maximum uncertainty (p = ½) and vanishes when the model is confident. The Hessian is literally a confidence meter. Chapter VII builds on this.
Boosting: a Committee of Corrections
One tree is a crude forecaster. Boosting builds a committee in which each new member is hired for one job only: to correct the standing error of everyone hired before it.
2.1 — The additive model
The final model is a sum of K small trees, each mapping the evening observations x to a number:
Fitting all K trees jointly is hopeless — the space of tree ensembles is combinatorial. So boosting fits them stagewise: freeze everything built so far, and ask only what one more tree should be.
2.2 — What should the new tree fit?
Take the simplest case: squared loss, l = ½(y − ŷ)². The gradient at the current prediction is gi = ŷi − yi — the negative of the residual. So "fit the new tree to the residuals," the folk description of boosting, is secretly "fit the new tree to the negative gradient of the loss." That reframing is the whole trick: gradients exist for any loss, residuals only for squared error. Boosting is gradient descent in the space of functions — each tree is one downhill step, and η is the step size.
Watch it happen. Below is a slice of our problem with everything held fixed except wind: clear-sky nights, drop against wind speed. The physics gives a nonlinear curve — calm nights decouple the surface air and cool hard; even a few m/s of wind stirs warmer air down and kills the drop. We'll fit it with stumps (depth-1 trees, one split each), the weakest learner there is.
- Set η = 1 and add trees one at a time. Fast — and jagged. Each stump commits fully to its correction, and later trees spend their lives correcting earlier trees' overcommitments. Watch the residual plot: it doesn't shrink smoothly, it sloshes.
- Reset. Set η = 0.05 and add 50. Slower, but the fit is calmer and the residuals decay like a discharging capacitor. Shrinkage leaves headroom — room for future trees to disagree.
- Notice the model is a staircase. A sum of stumps is piecewise constant; more trees means finer stairs. Depth will buy interactions in Chapter VI.
- Keep adding trees at high η and watch the model start chasing individual noisy points. That is overfitting happening live — the committee has begun memorising the minutes.
The Newton Step: Deriving the Leaf
This is the load-bearing chapter. In roughly a page of algebra we derive the two formulas that are XGBoost: the optimal value of a leaf, and the quality score of a tree. Every hyperparameter you will ever tune appears here with a job description.
3.1 — The regularised objective
At boosting round t, we seek the tree ft minimising the total loss plus a penalty for complexity. XGBoost's signature move is putting the penalty inside the objective from the start, rather than pruning as an afterthought:
3.2 — Taylor to the rescue
EQ 3.1 is unoptimisable as written: ft sits inside an arbitrary loss. So we approximate the loss around the current prediction with a second-order Taylor expansion — slope and curvature, exactly the two numbers Chapter I said we'd need:
Now the decisive regrouping. A tree assigns every point to exactly one leaf. So instead of summing over points, sum over leaves, pooling the parabolas of the points inside each. Writing Ij for the set of points in leaf j, and defining the leaf totals Gj = Σ gi and Hj = Σ hi over i ∈ Ij:
3.3 — The two formulas
Differentiate one leaf's parabola with respect to wj, set to zero:
Substitute w★ back in, and each leaf contributes −½G²/(H+λ) to the objective. The best achievable score for a given structure is therefore:
Five nights share a leaf. Each slider is that night's gradient gi (for squared loss: current prediction − truth, so negative means "the model under-forecast the drop"). Each night contributes a private parabola (thin curves); the leaf must pick one output w minimising their sum plus the λ term (bright curve).
- Drag all five gradients negative (model under-forecasting everywhere). The parabolas agree, the pooled bowl is deep and off-centre, and w★ is a confident positive correction.
- Now set them to alternate signs, roughly summing to zero. The bowl is still curved (H unchanged) but centred at zero — the leaf, hearing contradictory demands, wisely does almost nothing. This leaf needs splitting, not averaging: exactly what Chapter IV detects.
- Sweep λ from 0 to 20 with strong agreement among the gradients. Watch w★ slide towards zero — never past it. λ can only shrink, never flip.
- Now the payoff of Chapter I: imagine these were log-loss points, each with its own hi = pi(1−pi). Confident points (h≈0) would bring flat parabolas — loud gradients but no authority over where the minimum sits. The Hessian is a per-point credibility weight, and −G/(H+λ) is a credibility-weighted vote. And if every h were 0 (absolute loss), the denominator would be bare λ — division by zero at λ=0. That is why h=0 losses need patching.
Growing the Tree: the Gain
EQ 3.5 grades a finished tree, but trees are grown one split at a time. The gain asks the only question a growing tree ever needs answered: is this room better as two rooms?
4.1 — Better as two rooms?
Take a leaf with totals G, H. A candidate split — say wind < 3.1 m/s — divides its residents into a left set (GL, HL) and a right set (GR, HR). Score both arrangements with EQ 3.5 and subtract. Structure-score improvement, minus the toll for the extra leaf:
4.2 — The exact greedy scan
How does the algorithm find the best split? With no cleverness whatsoever — and that is worth seeing once in your life. For each feature: sort the leaf's points by that feature, then walk the sorted order maintaining running totals GL, HL (the right totals are just G−GL, H−HL). Every gap between consecutive values is a candidate threshold; EQ 4.1 costs a handful of arithmetic operations per candidate. Best gain across all features and thresholds wins. This is the exact greedy algorithm; Chapter VII shows the histogram trick that big data forces upon it.
The lab below is that scan, opened up. Forty-eight nights, one feature (evening cloud cover), and the gradients from a model that so far predicts only the global mean drop — so gi = ȳ − yi: clear nights (left) dropped far more than the mean predicted and carry big negative gradients; overcast nights carry positive ones. An argument waiting to be resolved.
- Drag the threshold across the plot and watch the gain curve trace out the exact greedy scan. The maximum sits where teal and rose populations separate — around 2–3 oktas, the physics of radiative cooling rediscovered by arithmetic.
- Raise γ until the entire gain curve sinks below the hurdle. Verdict: leaf stays whole. You have just pre-pruned a tree by hand.
- Raise λ. The gain curve deflates everywhere — but not uniformly: thresholds carving off small groups (small H in the denominator's company) deflate fastest. λ is quietly biased against splits justified by few points.
- Push min_child_weight up and watch the flanks of the curve get struck out: splits isolating a handful of extreme nights are forbidden outright, however tempting their gain. The three levers overlap in effect — all fight tiny, overconfident leaves — but by different mechanisms: λ shrinks, γ tolls, min_child_weight forbids.
Missing Data: the Default Direction
On the coldest nights, the anemometer ices up. Our wind column is missing precisely when wind matters most. Most algorithms make you impute and pray; XGBoost turns the gap itself into signal.
5.1 — Learn where the ghosts go
Every split in an XGBoost tree carries, alongside its feature and threshold, a learned default direction. During the split scan, the points with a missing value can't be sorted with the rest — so the algorithm simply tries both dispositions. Compute EQ 4.1 with all missing points massed on the left; compute it again with them massed on the right; the split's gain is the better of the two, and the winning side is stamped onto the node. At prediction time, a night arriving with no wind reading follows the stamp.
This is the sparsity-aware split, and it costs almost nothing: the missing points' pooled totals Gmiss, Hmiss are added to one side or the other of sums we were already maintaining. Two extra additions per candidate.
The deep point: if missingness is informative — icing means cold, a sensor that saturates means extreme values, a survey question skipped means something — the default direction learns the meaning of absence. Imputing the column mean would have actively destroyed that signal, quietly filing the iciest nights of the record under "average breeze".
- Park the threshold near 3 m/s and flip the toggle. The missing nights are mostly hard-drop, negative-gradient nights (icing ⇒ cold, calm), so sending them left — in with the calm nights they resemble — scores far higher. The arithmetic discovers the meteorology of the gap.
- Press let the algorithm choose: it scans every threshold under both dispositions and reports the champion — one line of bookkeeping in the real code.
- A subtlety worth savouring: the default direction is chosen per node. Deeper in a tree, "wind missing" can be routed differently under different cloud regimes. Absence is allowed to mean different things in different weather.
The Observatory Model
Everything assembles. A real (if synthetic) dataset, a complete XGBoost implemented in the page you are reading — exact greedy scan, Newton leaves, learned default directions — and every lever on one panel. Your job: overfit it, then rescue it.
6.1 — The data
420 observing nights, generated from a plausible boundary-layer story plus honest noise. Four features at 18:00: cloud (oktas 0–8), wind (m/s — missing on about one night in eight, preferentially the icy ones), dpd (dewpoint depression, °C — dry air radiates to space more freely), month (1–12, standing in for night length). Target: overnight drop in °C. The generator hides a strong cloud×wind interaction — clear and calm is worth far more than the sum of clear and calm — which is precisely what depth > 1 exists to find. 300 nights train the model; 120 held-out nights judge it.
6.2 — The algorithm, complete
for t = 1 … n_trees:
gᵢ ← ∂l/∂ŷ, hᵢ ← ∂²l/∂ŷ² at ŷ = Ft−1(xᵢ) // Ch I
draw a row subsample; draw a column subsample // Ch VII
grow tree: recursively take the best-gain split (EQ 4.1, both missing directions) // Ch IV–V
… while gain > 0, Hchild ≥ min_child_weight, depth < max_depth
set each leaf to w★ = −G/(H+λ) // Ch III
Ft(x) ← Ft−1(x) + η·tree(x) // Ch II
stop early if validation loss hasn't improved in k rounds
- Baseline. Defaults, train. Note both RMSEs and the gap between them. Inspect tree 1 (big confident structure) versus the last tree (shallow, timid leaves — later corrections are refinements of refinements).
- Underfit on purpose. depth 1, 30 trees, η 0.05. Train RMSE stays high and the two curves hug: the model lacks capacity — stumps cannot express cloud×wind. Bias, visualised.
- Overfit on purpose. depth 6, η 0.5, 300 trees, λ 0, everything else off. Train RMSE dives towards the noise floor while validation bottoms out and climbs. That climbing rose curve is the sound of a model memorising 300 particular nights.
- Rescue with each lever separately, from the overfit settings: first λ ≈ 10 alone; reset, then γ ≈ 5 alone; reset, then min_child_weight ≈ 15 alone; reset, then subsample 0.6 alone. Each closes the gap by a different mechanism — shrinking leaves, refusing splits, forbidding small leaves, decorrelating trees. Then combine, lower η to 0.05, and switch early stopping to 30: the brass marker plants itself at the honest optimum.
- Read the importance panel. Cloud and wind should dominate, month behind, dpd modest — the generator's own recipe recovered. At depth 1, watch the interaction-dependent share collapse.
The Extended Instrument Case
The core machine is complete. What remains are the refinements that made XGBoost an industrial tool — each a small idea, and each now a one-paragraph corollary of things you already know.
7.1 — The objective zoo
Chapter I promised that changing the loss changes only g and h. Cash the promise: to turn our regression engine into a frost classifier (y ∈ {0,1}), keep every line of tree code and swap two formulas. The model's raw score z becomes a probability via the sigmoid p = 1/(1+e−z), and log-loss gives:
The same plug-in socket accepts multiclass softmax (one tree per class per round), Poisson counts, Tweedie for rainfall-like zero-inflated targets, quantile losses for "what drop will only 1 night in 10 exceed?", ranking losses, survival times, and any custom pair of (g, h) callbacks you write yourself. The engine never knows the difference.
7.2 — Histograms and the quantile sketch
The exact greedy scan sorts every feature in every node — brutal at a hundred million rows. The approximate algorithm replaces raw values with a few hundred bins whose edges are (weighted) quantiles of the feature, then scans bin boundaries only. Two refinements matter: the quantiles are weighted by h — bins hold equal curvature, not equal counts, so resolution concentrates where the objective still has structure — and the weighted quantile sketch computes them in one streaming pass with provable error bounds, which is a genuine contribution of the XGBoost paper. Modern hist mode adds a lovely accounting trick: a child's histogram equals its parent's minus its sibling's, so each split's second histogram is free.
7.3 — Randomness as regulariser
subsample shows each tree only a random fraction of rows; colsample_bytree / _bylevel / _bynode hide a random fraction of features per tree, per level, or per split. Both are borrowed from bagging and random forests, and both work for the same reason: trees that see different evidence make decorrelated mistakes, and decorrelated mistakes partially cancel in the sum (EQ 2.1). Column subsampling has a second virtue: it forces the ensemble to develop backup routes around a dominant feature — insurance for the day the anemometer really does die. You already felt both in Lab 6, step 4.
7.4 — Constraints, priors, and other house rules
- Monotone constraints. Physics says the drop cannot increase with cloud cover. Declare it, and during the scan any split whose two leaf values would violate the ordering is discarded, with bounds propagated down the subtree. You trade a little training fit for a model that cannot embarrass you in front of a physicist — often a validation gain, since the constraint is true.
- Interaction constraints. Permit only declared feature groups to co-occur along a root-to-leaf path — "cloud may interact with wind, but month works alone."
- base_score. F₀ — the prediction before any tree exists. For regression, the target mean; for rare-event classification, the log-odds of the base rate, so early trees model structure instead of wasting rounds discovering that frost is uncommon.
- α (L1, reg_alpha). λ's sharper sibling: soft-thresholds each leaf, w★ = −sign(G)·max(0, |G|−α)/(H+λ). Small pooled gradients snap to exactly zero — leaves must clear a minimum conviction to speak at all.
- DART. Dropout for trees: each round, temporarily delete a random subset of existing trees, fit the new tree to the gradients of the diminished ensemble, then rescale. Fights the pattern where late trees only polish the residual crumbs of early ones.
- max_delta_step. A hard clamp on any leaf's |w★| — a belt to λ's braces, mostly for wildly imbalanced logistic problems where early Hessians are near zero and Newton steps explode.
7.5 — Viva voce
Close the notes. If the course has worked, these should feel less like trivia than like consequences.
Why does XGBoost require a twice-differentiable loss when plain gradient boosting needs only one derivative?
λ = 0, and a leaf ends up containing points whose Hessians are all ≈ 0. What happens, mechanically?
Your training data has no missing values, but production data will. Does the default direction still get learned?
Why does a split's gain never exceed the sum of its children's future gains — i.e. why can γ prune too eagerly?
min_child_weight = 200 barely changes your squared-loss model but devastates your log-loss model on the same features. Why?
Halving η while doubling n_trees usually improves validation loss. What is the honest cost, besides compute?
The Cheat Sheet
The whole course on one plate. If you can reconstruct each line's why, the instrument is yours.
III wj★ = −GjHj+λ — each leaf: a pooled, ballasted Newton step
IV Gain = ½[GL²/(HL+λ) + GR²/(HR+λ) − G²/(H+λ)] − γ — split iff it resolves an argument worth the toll
II Ft = Ft−1 + η·ft — add it, damped; repeat; stop when validation says so
The levers, with job descriptions
| lever | where it lives | what it really does |
|---|---|---|
| eta (η) | EQ 2.2 | Damps every tree's contribution. Leaves headroom for future corrections; the one lever that fights over- and underfit together, paid in trees. |
| lambda (λ) | EQ 3.4 denominator | Ballast in every leaf. λ phantom residents voting "do nothing"; shrinks weights towards zero, hits small-H leaves hardest, deflates all gains. |
| gamma (γ) | EQ 4.1 tail | Toll per new leaf. A split must resolve ≥ γ of gradient argument or the room stays whole. Pre-pruning, derived from the objective. |
| min_child_weight | constraint on HL, HR | Floor on leaf curvature. Refuses leaves whose pooled parabola is too shallow to trust. Counts points under squared loss; counts doubt under log-loss. |
| max_depth | growth recursion | Cap on interaction order. Depth d can express interactions among ≤ d features along a path. Depth 1 = additive model. |
| subsample | per-round row draw | Decorrelates trees' mistakes so they cancel in the sum; also 25–50% faster per tree. |
| colsample_* | per tree/level/node feature draw | Forces backup routes around dominant features; decorrelation in feature space. |
| alpha (α) | soft-threshold on w★ | Minimum conviction to speak. Leaves with |G| ≤ α output exactly zero. |
| early stopping | the training loop | Lets validation choose n_trees. The only lever measured, not guessed. |
| base_score | F₀ | The prior. Start at the answer a model with no features would give. |
The one-sentence version
XGBoost repeatedly asks every data point for its complaint (g) and its credibility (h), grows a tree whose every split separates the loudest disagreements — tolled by γ, ballasted by λ — sets each leaf to the pooled Newton step of its residents, adds the tree at a fraction η of full strength, and stops when held-out nights say the corrections have become memories.
Where next, if you want it: the 2016 paper (Chen & Guestrin, XGBoost: A Scalable Tree Boosting System) will now read as an engineering memo about ideas you own — its cache-aware block layout and out-of-core columns are Chapter IV's scan made mechanically sympathetic. LightGBM's GOSS and histogram subtraction, CatBoost's ordered boosting and target statistics, and NGBoost's probabilistic outputs are all dialects of the same grammar: g, h, gain, shrink, repeat.
Built as a single self-contained file. The dataset is synthesised in your browser from a seeded generator (a boundary-layer fable, not observatory data); the XGBoost inside is a faithful exact-greedy implementation in about two hundred lines of vanilla JS — view source, it's all here.