Ch.64: Deriving XGBoost's Formulas From Scratch
Inspired by: YouTube
The regression and classification walkthroughs used two formulas without justifying either: the similarity score, for regression and for classification, and the matching leaf output formula, the same numerator without the square. Both were presented as "this is what you compute," with a promise to come back to where they came from. This post is that derivation, starting from XGBoost's actual objective function and ending at the exact formulas used in both earlier posts.
1. The Objective Function, Built Up Stage by Stage
Gradient boosting's additive structure means a model after stages is a sum of functions, , where each is one fitted tree (or, for , a constant baseline). Every one of those trees makes some amount of error, and the quantity you want to minimize at any stage combines that error with a penalty on model complexity:
The regularization term only applies to decision trees, not to a constant baseline, since a flat number has no leaves to penalize. For the single-tree case this is:
is the number of leaves in that tree, is leaf 's output value (its "weight"), and , are two separate regularization knobs: penalizes having too many leaves, penalizes leaves with large output magnitudes. This is the piece the XGBoost overview referred to as "a regularization term baked directly into the loss."
At stage , every tree before the current one is already fixed, so splits the objective into "everything already accounted for" and "the one new tree":
The goal at every stage is the same: find the (equivalently, the leaf weights of the -th tree) that minimizes this expression. That makes it an optimization problem, the same flavor as fitting and in linear regression by minimizing mean squared error, just with a far less convenient function to minimize.
2. Why This Can't Be Differentiated Directly
Ordinary optimization differentiates the objective with respect to the parameter being solved for, sets that derivative to zero, and solves. In linear regression, the loss as a function of and traces out a smooth parabola, differentiable everywhere, which is exactly what makes that route work.
here is a decision tree, and a decision tree's output is piecewise constant: the same output for every row that lands in a given leaf, then a sudden jump for rows in the next leaf over. Fitting the same two curves, a line and a shallow tree, to one dataset makes the shape difference concrete:
A straight line is differentiable at every point. The staircase is not, at each jump the slope is undefined, there's no single tangent line to speak of. Since is built out of , plotting the objective function against inherits that same jaggedness instead of a clean parabola. Partial differentiation with respect to breaks down at exactly the points where it would be needed.
3. Taylor Series: Smoothing a Function Into a Polynomial
The fix is to approximate the non-differentiable objective with something that is differentiable, close enough to be useful, close enough to be trustworthy. The Taylor series is the standard tool for exactly this: given a complicated function and a nearby point , it builds a polynomial approximation out of 's derivatives at :
Each additional term tightens the approximation around . Expanding around term by term:
With one term the fit only holds very close to ; with three terms it tracks the true curve over a visibly wider range. That's the general trade: more terms buys more accuracy, farther from . XGBoost's objective function stops at two terms, a second-order (quadratic) approximation, which turns out to be exactly enough.
4. Applying the Taylor Expansion to the Objective
Treat as the function being approximated, with and the expansion point , the prediction already locked in before this tree gets added. is then just , so the second-order expansion is:
where and are the first and second derivatives of the loss with respect to the previous prediction, evaluated at :
is called the gradient, the Hessian (the standard name for a second derivative in optimization). Substituting this approximation into the objective:
The next move is to notice that doesn't involve at all, it's a fixed number by the time this tree is being fit. Since the entire point is to differentiate with respect to and that term vanishes under differentiation anyway, it can be dropped without changing the answer:
Checking this against the paper confirms it line for line: a gradient term, a Hessian term, and the regularization term, nothing else.
5. From Rows to Leaves
is still sitting unexpanded. Writing it out:
There are now two different summations, one over rows ( to ), one over leaves ( to ), and they need to become one. The bridge is that , the tree's output for row , is just the output of whichever leaf row lands in. If denotes the set of row indices that fall into leaf , then summing over all rows one at a time is equivalent to grouping rows by leaf first and summing within each group:
Reusing the regression walkthrough's finished tree makes this concrete. That tree has three leaves: (row with CGPA 9.0), (rows with CGPA 6.7 and 7.5), (row with CGPA 5.0). Summing over all four rows directly, or summing it leaf by leaf and then adding the three leaf totals together, reach the identical number, the rows are just visited in a different order.
Within a single leaf , every row shares the same tree output, for every , by definition of what a leaf is. That lets be pulled out of the inner sum entirely:
This used the fact that the term already sums over , so its -th piece, , folds directly into the -th leaf's bracket. Writing and for brevity:
Every term here is a function of a single leaf's weight , nothing couples one leaf's weight to another's. That's what makes the next step possible.
6. Solving for the Optimal Leaf Weight
Each leaf's contribution, , is a plain quadratic in , exactly the shape a parabola takes, so it can be minimized the ordinary way: differentiate with respect to , set to zero, solve.
This is the general leaf weight formula, valid for any differentiable loss function, with the specific loss only entering through and . To check it reproduces the regression and classification formulas used earlier, it just needs and for each loss:
Regression, squared error :
Classification, log loss with in log-odds space and (the same conversion used in gradient boosting for classification):
Both of these are checkable with symbolic differentiation rather than by hand:
import sympy as sp
y, yhat, z = sp.symbols('y yhat z')
# regression: L = 1/2 (y - yhat)^2
L_reg = sp.Rational(1, 2) * (y - yhat)**2
print("g_reg:", sp.diff(L_reg, yhat)) # yhat - y
print("h_reg:", sp.diff(L_reg, yhat, 2)) # 1
# classification: L = log loss, z is log-odds, p = sigmoid(z)
p = 1 / (1 + sp.exp(-z))
L_cls = -(y * sp.log(p) + (1 - y) * sp.log(1 - p))
g_cls = sp.simplify(sp.diff(L_cls, z))
h_cls = sp.simplify(sp.diff(L_cls, z, 2))
print("g_cls:", g_cls)
print("h_cls:", h_cls)g_reg: -y + yhat
h_reg: 1
g_cls: (-y + (1 - y)*exp(z))/(exp(z) + 1)
h_cls: exp(z)/(exp(2*z) + 2*exp(z) + 1)
h_cls simplifies to (since once ), and g_cls simplifies to by the same substitution, confirming both by hand matches what sympy returns.
Plugging these into :
Both are exactly the leaf output formulas used without proof in the regression and classification posts. The "sum of residuals" in both earlier posts was always , and the "" or "" in the denominator was always .
7. The Minimum Loss, and Where the Similarity Score Comes From
is the weight that minimizes each leaf's contribution to the objective, but it doesn't yet say what that minimum value is. Substituting back into :
Summed across all leaves, the minimum achievable objective for a given tree structure is:
The paper calls this a structure score, a single number that says how good a candidate tree shape is, lower is better since it's a loss. Flip the sign and drop the constant and (neither affects which of several candidate splits scores highest, since a positive constant multiplier and an additive term that's identical across candidates can't change which one wins), and what's left is:
For a single leaf (T=1), this is . Substituting for regression gives , exactly the regression similarity score. Substituting the classification gives , exactly the classification version. Both formulas trace back to the same two-line substitution, the only thing that ever changes between regression and classification is what loss function and come from.
8. From Similarity Score to Split Gain
A split takes one leaf (the parent) and replaces it with two (left and right child). Using from Section 7, the loss before splitting (one leaf, ) is:
and the loss after splitting (two leaves, ) is:
A split is worth making exactly when it reduces the loss, so the quantity that matters is :
This is exactly the split criterion the regression and classification posts used, , with two differences made explicit here: an overall factor of , which doesn't change which candidate split wins since every candidate gets scaled the same way, and a term, set to zero in both earlier posts' worked examples. That is not cosmetic in general: it's the same hyperparameter that sets a minimum required gain before a split is allowed to happen at all. A candidate split whose raw doesn't clear gets rejected outright, regardless of how it compares to other candidates, which is the exact mechanism behind XGBoost's pruning behavior.
9. What to Take Away
- XGBoost's objective at stage is loss on the current prediction plus a regularization term on the newest tree, , that penalizes both leaf count and leaf weight magnitude.
- That objective is non-differentiable in closed form with respect to the new tree, because a tree's output is piecewise constant, so a second-order Taylor expansion (around the previous stage's prediction) replaces it with a differentiable quadratic approximation.
- The expansion introduces (gradient, first derivative of the loss) and (Hessian, second derivative), the only place the specific loss function enters the rest of the derivation.
- Regrouping the per-row sum into a per-leaf sum, using for every row in leaf , turns the objective into an independent quadratic for each leaf's weight.
- Minimizing each leaf's quadratic gives , which reduces to for squared error and for log loss, exactly the leaf output formulas used without derivation in the regression and classification posts.
- Plugging back into the objective gives a structure score for the whole tree; comparing a parent leaf's score against its two children's scores (after a split) produces the gain formula, , the earlier posts' simplified with the constant factor and the pruning threshold made explicit.
