XGBoost: its loss, its split, and every hyperparameter
XGBoost builds a model as a sum of small trees. Each new tree fixes what the earlier trees got wrong. This lesson derives the formulas that choose each split and each leaf value, works two trees by hand on eight rows, and then explains every parameter by the part of those formulas it changes.
The concepts of this lesson and how they build on each other
Lesson map
Each box is one concept, with an estimate of the minutes that it takes. An arrow runs from a concept to the one that builds on it. A box's bar fills as you master its practice, and each box links to its section.Contents
You know what a decision tree predicts, you can read Python, and you have seen a derivative as the slope of a function. The lesson does not assume that you know gradient boosting, Taylor expansions, or how any tree library works. It explains each idea before it uses it.
Version
Every number comes from xgboost 3.2.0 on Python 3.13, with CPU only. A number on this page either comes from that library or is marked as read from its source or docs. Anything inferred is marked inference.
Scope
This page is complete on its own. Its check script recomputes every number on it, and it lives in assets/xgboost-lesson/verify_xgboost_lesson.py.
After this lesson, you can
derive the leaf weight and the gain from the objective;
compute g and h for a loss, and say why that is all that the tree builder needs;
grow a tree by hand and predict what get_dump shows;
say which term of which formula any parameter changes;
predict how gamma, lambda, alpha and min_child_weight differ.
How to read this page. Sections 1 to 8 are one argument, so read them in order. Sections 9 and 10 cover the machinery around the formulas, and section 11 puts every step of training and prediction in order. Section 12 is a reference you can search. Figures in sections 1 to 11 and 13 change when you use their controls. The practice boxes check each answer, and a box is mastered after two right answers in a row. Closed-book checkpoint quizzes follow sections 4, 6, 8, 10 and 11.
1The running rows
Every worked example uses these eight rows. Each row is a student. There are two features, hours studied and hours slept. There are two targets: an exam score for regression, and a pass or fail for classification.
row
study x1
sleep x2
score y
pass
1
1
8
35
0
2
2
5
40
0
3
3
7
50
1
4
4
4
45
0
5
5
8
70
1
6
6
5
55
0
7
7
6
80
1
8
8
7
85
1
The eight rows on the study and sleep plane
Fig. 1.1 · interactive
Label
Each dot is one row of the table, numbered as there, at its hours studied and hours slept. With score, the label is the exam score and a darker dot has a higher score. With pass, a filled dot passed and a hollow dot failed. Figs. 11.6 and 11.7 use this same plane, where each split of a tree becomes a straight line.
The scores have mean 57.5. A model with no information would predict 57.5 for everyone. Its root mean squared error (RMSE) on these rows is 17.5. The goal is to do better with small trees.
Four words are used all through the lesson. A leaf is an end node of a tree, and it holds one number. That number is the leaf's weight, written w. The margin is the model's raw output, before any conversion such as turning a score into a probability. The lossl measures how wrong one prediction is for one row.
2A boosted model is a sum of trees
A boosted model does not grow one big tree. It starts from one number, , and adds trees one at a time. Each tree maps a row to a leaf weight. The prediction after trees is the sum so far:
The number is the learning rate, also called shrinkage. In XGBoost it is eta. It multiplies each tree's output before the tree joins the sum, so the model takes only part of each correction. The next tree then sees what is left and corrects part of that.
Under squared error, "what is left" is the residual, . The figure shows that residual as a line from each prediction to the true score. Move the slider and watch the lines shrink. Set eta to 1, and the lines shrink quickly, but each tree then fits individual rows more closely, which is how a boosted model overfits.
Predictions and residuals as trees are added
Fig. 2.1 · interactive
Each dot is a true score, and each square is the current prediction. The line between them is the residual that the next tree will reduce. The trees are depth 2 with lambda=1, computed in your browser by the same rules as tree_method="exact". The check script compares this code with real xgboost trees.
This shows the first property to remember: the trees are not trained together. When tree is built, all earlier trees are frozen. So training reduces to one repeated question: given the current predictions, which one tree helps most? The next three sections answer it.
Add one tree to a prediction
A tree's leaf weight never joins the sum in full. It is multiplied by eta first. Here is row 1 in the first tree of section 7, with eta 0.3:
Read the weight. Row 1 goes to a leaf whose weight is w = −12.
Shrink it by eta. The step is 0.3 × (−12) = −3.6.
Add the step. The prediction moves from 57.5 to 53.9.
3The objective: loss plus a price on complexity
To choose a tree, we need a number that says how good a candidate is. XGBoost uses an objective: the total loss over all rows plus a penalty on each tree's complexity.
The penalty is where XGBoost differs most from plain gradient boosting. For a tree with leaves and weights , it is
γT charges a fixed price per leaf. A split adds one leaf, so it must pay at least . This is gamma.
½λΣw² charges for large weights, and it charges most for the largest. This is lambda.
αΣ|w| also charges for size, but its charge per unit stays the same as a weight approaches zero. So a weight can become exactly 0. This is alpha.
The penalty shapes the leaf weights themselves, not only the choice of split. Section 5 shows how.
What one split buys
Take the squared loss on our rows, with and . Before any tree, every row predicts 57.5, and the total loss is 1225. Now split on study < 4.5. Each leaf's weight is the sum of its residuals divided by its row count plus λ, a rule that section 5 derives. The left leaf's residuals sum to −60, so its weight is −60/(4 + 1) = −12, and the right leaf's sum to 60, so its weight is 12. The loss falls to 361. The penalty adds 144, so the objective is 505. The split lowered the objective by 720.
What a tree costs and what it saves
Fig. 3.1 · interactive
The first tree on the running rows with eta 1. Raise gamma until the 3-leaf tree stops beating the 2-leaf tree. Raise lambda and watch the penalty and the savings change together. This figure uses the objective as written, so its gamma is the of . Section 5 shows that the library's gamma is compared with twice this drop.
Check
Why not just minimize the training loss?
Show the answer
A tree can always lower training loss by splitting further, until each row has its own leaf. That memorizes the rows. The penalty makes each split pay for itself, so a split that saves little is refused.
Price a tree
The penalty has one part for each term of . Here is the split on study < 4.5 from above, with gamma 0, lambda 1 and alpha 0:
Price the leaves. γT = 0 × 2 = 0.
Price the sizes. ½λΣw² = ½ × 1 × ((−12)² + 12²) = 144, and the alpha term is 0.
Add the loss. The objective is 361 + 144 = 505.
4Taylor expansion: reduce any loss to two numbers
When tree is built, earlier trees are frozen. So write the objective as a function of this tree only. Terms for old trees are constants, so we drop them.
For squared error, this is easy to minimize. For a general loss it is not, because sits inside . So XGBoost replaces with a parabola that matches it at the current prediction.
Slope, curvature, and the parabola
The derivation below uses two derivatives of the loss, taken at the row's current prediction. The first derivative is the slope: how fast the loss changes as the prediction moves. The second derivative is the curvature: how fast that slope itself changes. Near the current prediction, a parabola with the same value, slope and curvature follows the loss closely. That parabola is the Taylor expansion of second order.
Here is one row under logistic loss, with label 1 and current margin 0. Its loss is 0.6931, its slope is −0.5, and its curvature is 0.25. For a step of w = 1, the parabola gives 0.6931 − 0.5 + ½ × 0.25 = 0.3181, and the true loss is 0.3133. For a step of w = 3, the parabola gives 0.6931 − 1.5 + ½ × 0.25 × 9 = 0.3181 again, but the true loss is 0.0486. So the parabola is close for small steps and far off for large ones. Fig. 4.1 below shows the same comparison at any margin.
The derivation, one step at a time
Step 1. Fix one row. Let be its current prediction, and let be the amount that this tree will add. We want as a function of .
Step 2. For any smooth function, a Taylor expansion of second order around a point gives:
Step 3. Name the two derivatives. They are evaluated at the current prediction, so they are just numbers for this row:
Step 4. Substitute this into the objective, and drop , because it does not depend on the new tree:
Look at what happened to the loss. It is gone. All that remains of it is two numbers per row, and . In this library, g is called the gradient and h is called the hessian. g says which way the loss falls and how steeply. h says how quickly that slope changes, which tells us how far to trust it.
The parabola XGBoost actually minimizes
Fig. 4.1 · interactive
The solid curve is the true loss as a function of the step w. The dashed curve is the parabola from g and h. They touch at w = 0 and have the same slope and curvature there. Under squared error they are the same curve. The dot marks the parabola's lowest point, , the step that is best for this one row alone.
Why one tree builder can serve any loss
The code that builds the trees never looks at the loss. It receives two arrays, g and h, and it works only with their sums. To support a new loss, you write a function that returns those two arrays. Nothing else changes. That is what the obj argument of xgboost.train does.
A custom objective that returns margin - y and ones gave a get_dump identical, character for character, to the built-in reg:squarederror on the running rows. Section 6 repeats this test for each formula.
The loss has to be twice differentiable for this to work as derived, with positive so the parabola opens upward. Losses that fail this need special handling, which section 6 describes.
Compute g and h for one row
Substitute the row's current prediction into the two derivatives. Under squared error, is the prediction minus , and . Under logistic loss with probability , and . Section 6 derives these and the formulas for the other objectives.
Squared error, row 1. The prediction is 57.5 and y = 35, so g = 22.5 and h = 1.
Logistic loss, row 1. The margin is 0, so p = 0.5. With y = 0, g = 0.5 and h = 0.5 × 0.5 = 0.25.
5Leaf weight, leaf score and gain
Now use the structure of a tree. A tree gives every row in a leaf the same value, the weight . So we can group the sum by leaf. Let be the rows in leaf , and define the two sums that run the whole algorithm:
The best weight for a leaf
Step 1. Add in with for now. Inside leaf , every equals , so:
Step 2. Each bracket depends on one only, so each leaf can be solved separately. Each bracket is a parabola, and we can set its derivative to 0:
Step 3. Solve for the weight:
Read this as a Newton step: one step of Newton's method jumps straight to the lowest point of a parabola, and here the parabola is the leaf's bracket above. Under squared error, and is minus the residual, so is the mean residual in the leaf, pulled toward zero by . With , it would be exactly that mean.
The tree then stores , as section 2 showed. Here is the left child of the first split in section 7:
Sum the rows. The child has G = 60 and H = 4.
Take the Newton step. w = −60/(4 + 1) = −12.
Store eta times it. With eta 0.3, the leaf stores −3.6.
The score of a leaf
Put back into the bracket to see how low the objective goes for that leaf:
So for a whole tree with a fixed structure, the objective is
This is how XGBoost scores a tree shape without knowing anything else about the loss. A leaf is good when its rows pull in the same direction, because then is large compared with .
The gain of a split
Now compare a leaf with the two leaves that would replace it. The rows in divide into and , so and . The objective changes by the score after, minus the score before, minus one more for the extra leaf:
This is the split function. It is what the tree builder evaluates at every candidate threshold. Everything about XGBoost's trees comes from this one line and from the leaf weight above.
What the library does differently
The paper's formula has a factor of one half and subtracts . The library does neither. Its number is the one that get_dump calls gain and that feature_importances_ averages:
It then compares that number directly with gamma. So in the library, gamma is a threshold on twice the objective drop. Setting gamma=500 acts like in the objective as written above. The docs' tutorial writes the formula with the one half, and the code leaves it out.
On the running rows the first split has dumped gain 1440, and half of that, 720, is exactly the objective drop of 720 computed in section 3. Under exact, hist and approx, gamma=1000 and gamma=1439 keep the split, and gamma=1441 removes it. If gamma compared with half the gain, 1000 would already remove it.
The formula is in TreeEvaluator::CalcGainGivenWeight in src/tree/split_evaluator.h, and the comparison is in CPUExpandEntry::IsValidImpl in src/tree/hist/expand_entry.h, at tag release_3.2.0.
Score the children. For study < 4.5, each child scores 60²/5 = 720.
Subtract the parent. The root has G = 0, so its score is 0, and the dumped gain is 1440.
Compare with gamma. The split stays while its gain reaches gamma, so gamma=1000 keeps it and gamma=1441 removes it.
The gain of a split: two children's scores minus the parent's
Fig. 5.1 · interactive
The candidates are the root splits of section 7's first tree, where G = 0 and H = 8. Each score is for one node. The purple bar is the gain that get_dump reports, and the orange line on it is gamma: the split stays while the bar reaches the line. The last bar is the paper's objective drop, half the gain minus gamma, which is positive exactly when the gain is above twice gamma. The scores come from the same function as the tree lab, which the check script tests in Node against xgboost.
Lambda in the running rows
With and for the left child of that first split, in the table below. Lambda shrinks both the weight and the gain:
lambda
left leaf weight
dumped gain
0
−15.000
1800.000
1
−12.000
1440.000
10
−4.286
514.286
100
−0.577
69.231
How reg_alpha soft-thresholds G
Now keep . The bracket for a leaf becomes . The absolute value has a kink at 0, so the two sides need separate cases.
For w > 0: the derivative is . Setting it to 0 gives . That is positive only if .
For w < 0: the derivative is . Setting it to 0 gives . That is negative only if .
Otherwise, . Neither side has a solution, so the minimum sits on the kink and .
These three cases are one operation, called soft thresholding. It moves toward 0 by and stops at 0:
This is why alpha can zero a leaf, and why it can remove a split. On the running rows with lambda 1 and , any alpha of 60 or more removes the gain completely, so the node stays a leaf: no split at alpha 60.
alpha
left leaf weight
0
−12.000
10
−10.000
40
−4.000
60
0.000
70
0.000
Soft-threshold G. With alpha 10, G = 60 moves 10 toward 0, to T = 50.
Take the Newton step. w = −50/(4 + 1) = −10.000, as in the table.
How max_delta_step clips the weight
The parameter max_delta_step sets a hard limit on the size of a weight, applied after the formula and before eta. When :
The node's score then cannot use the closed form, because the weight is no longer the minimizer. So the library evaluates the objective at the clipped weight:
With no clipping and no alpha, this equals . With clipping it is smaller, so clipping lowers both the weight and the gain.
mds
left leaf weight
dumped gain
0
−12.000
1440.000
20
−12.000
1440.000
10
−10.000
1400.000
5
−5.000
950.000
1
−1.000
230.000
When alpha and max_delta_step are both set, the source uses this same score with the raw , and no alpha term appears in it. On our rows with alpha 10 and max_delta_step 5 that gives gain 950.000, which equals the dump.
These rules are CalcWeight and CalcGainGivenWeight in src/tree/param.h and split_evaluator.h.
Take the Newton step. For the left child, w = −60/5 = −12.
Clip it. With max_delta_step=5, the weight becomes −5.000.
Score the clipped weight. Each child scores −(2 × 60 × (−5) + 5 × 25) = 475, so the gain is 950.000.
Store eta times it. The clip comes first, so with eta 0.3 the leaf stores 0.3 × (−5) = −1.5.
One leaf's objective, as a function of its weight
Fig. 5.2 · interactive
The curve is . The dot is the weight that XGBoost picks. Raise alpha past |G| to see the minimum stay on the kink at 0. Set max_delta_step smaller than |w| to see the weight stop short of the minimum.
Check
A leaf has G = −30 and H = 5. With lambda 1 and alpha 0, what are its weight and its score? What about alpha 10?
Show the answer
The weight is 5.000, and the score is 150.000. With alpha 10, G moves to −20, so the weight is 3.333 and the score is 66.667. Alpha lowered both.
6g and h for each objective
Choosing an objective means choosing these two formulas, plus the link that turns the margin into an output. In the table, is the margin, is the output after the link, and each row's values are multiplied by its sample weight.
objective
loss
g
h
output
reg:squarederror
binary:logistic reg:logistic binary:logitraw
(logitraw returns )
multi:softmax multi:softprob
per class , where is 1 for the true class
softmax of the margins
count:poisson
, with = max_delta_step (0.7 by default)
reg:gamma
reg:tweedie
reg:absoluteerror
(placeholder)
reg:quantileerror
pinball: above the label, below
if , else
(placeholder)
reg:pseudohubererror
, where and
reg:squaredlogerror
binary:hinge
, where
if , else
if , else the smallest positive float
0 or 1
survival:cox
negative partial log-likelihood. A negative label means right-censored.
, with a running sum of over risk sets, and = 1 for an observed event
, with the running sum of squares of the same terms
survival:aft
likelihood of , with possibly censored bounds
exact event, normal: , where
exact event, normal:
rank:pairwise rank:ndcg rank:map
pairs within a query. For each pair where is more relevant than :
. Add it to , subtract it from .
, added to both documents
a score to sort by
In the ranking row, is the sigmoid of the score difference, and is the change in the metric if and swapped: the change in NDCG, the change in MAP, or 1 for pairwise. It may be divided by (lambdarank_score_normalization). Each query's sums may be scaled afterwards (lambdarank_normalization).
For every row but the ranking one, a custom objective using these exact formulas grew the same trees as the built-in objective, from margin 0. This includes the factor of 2 in softmax's h: a one-leaf softmax tree stores −0.0075, which only that factor explains. For survival:aft the check covers exact events under the normal distribution. For ranking, one query of two documents from margin 0 gives leaves of exactly ±1 and a root cover of 1, which agrees with the formula. The full NDCG and MAP code was read from source but not re-derived.
Loss, g and h for one row
Fig. 6.1 · interactive
lossgh
Each curve has its own vertical scale, since only the shape matters. Under logistic, h falls toward 0 when the model is sure, in either direction, so sure rows carry little weight in . Under Poisson, gamma and Tweedie, g and h are exponential in the margin. That is why large steps are dangerous there.
Read g and h from the table
Find the objective's row in the table, then substitute the margin and the label.
Find the formulas. Under reg:gamma, and .
Substitute. At m = 0 with y = 4, g = 1 − 4 = −3 and h = 4.
Why L1-type losses need special handling
The absolute loss is a V shape. Its second derivative is 0 everywhere except the kink, where it is undefined. If you used that honest , the weight would be , and it would grow with the row count rather than settle at a sensible size. Pinball loss has the same shape with two different slopes.
XGBoost handles this in two steps. It grows the tree structure with a placeholder , so the gain formula still runs. Then it throws away the leaf values and sets each leaf directly to the value that minimizes the true loss. For absolute loss, the best constant is the median of the residuals in that leaf. For pinball, it is the quantile.
Here is that on our rows, with reg:absoluteerror. The intercept is the median of the labels, 52.5. The tree splits at study < 5 with gain 6.400, computed from and .
Left leaf, 4 rows, residuals −17.5, −12.5, −7.5, −2.5. Their median is −10.000, and times eta 0.3 that gives leaf −3.000.
Right leaf, 4 rows, residuals 2.5, 17.5, 27.5, 32.5. Their median is 22.500, and times eta 0.3 that gives leaf 6.750.
The placeholder formula would have given a leaf of −0.240 on the left. That is what tree_method="exact" actually stores: the −0.240 and 0.240 come from an exact run, because the refit happens only under hist and approx. No warning is shown. So use hist with these objectives.
For pinball with quantile_alpha=0.75, the intercept is 77.5, and the split is study < 7 with gain 1.071. The left leaf has 6 residuals (−42.5, −37.5, −32.5, −27.5, −22.5, −7.5), whose 0.75 quantile is −18.750, so the leaf stores −5.625. The right leaf has 2 residuals (2.5, 7.5), so it stores 2.250 from a quantile of 7.500. Under exact, these would be the placeholders −0.064 and 0.150. The quantile rule is the library's own, with position in the sorted values, from common::Quantile in src/common/stats.h.
Since the placeholder is 1 and counts rows, min_child_weight counts rows for these objectives. Also, lambda and alpha affect only the structure, since the refit replaces the leaf values (inference from the refit code). And reg:pseudohubererror exists because it avoids all of this: it is twice differentiable, with a real .
Collect the residuals. The left leaf of the reg:absoluteerror tree holds −17.5, −12.5, −7.5, −2.5.
Take the quantile. Their median is −10.000.
Shrink by eta. The leaf stores 0.3 × (−10) = −3.000.
7Two trees by hand
Now do the whole algorithm once. The settings are objective="reg:squarederror", tree_method="exact", max_depth=2, eta=0.3, lambda=1, gamma=0, min_child_weight=1.
Round 1: g and h
XGBoost first estimates the intercept. Under squared error, this is the mean, 57.5. So every row starts at 57.5. For squared error, and .
row
x1
x2
y
prediction before this tree
g = prediction − y
h
1
1
8
35
57.5
22.5
1
2
2
5
40
57.5
17.5
1
3
3
7
50
57.5
7.5
1
4
4
4
45
57.5
12.5
1
5
5
8
70
57.5
−12.5
1
6
6
5
55
57.5
2.5
1
7
7
6
80
57.5
−22.5
1
8
8
7
85
57.5
−27.5
1
Round 1: scan every split at the root
At the root, and . So the parent's score is . Each candidate threshold sits halfway between two neighboring distinct values. Sleep has 5 distinct values, so it has only 4 candidates. Here is one by hand, study < 4.5. Rows 1 to 4 go left:
Every candidate follows the same arithmetic:
split
GL
HL
GR
HR
gain
note
study < 1.5
22.5
1
−22.5
7
316.406
study < 2.5
40
2
−40
6
761.905
study < 3.5
47.5
3
−47.5
5
940.104
study < 4.5
60
4
−60
4
1440
chosen
study < 5.5
47.5
5
−47.5
3
940.104
study < 6.5
50
6
−50
2
1190.476
study < 7.5
27.5
7
−27.5
1
472.656
sleep < 4.5
12.5
1
−12.5
7
97.656
sleep < 5.5
32.5
3
−32.5
5
440.104
sleep < 6.5
10
4
−10
4
40
sleep < 7.5
−10
6
10
2
47.619
The best is study < 4.5. Notice that the second best, study < 6.5, is not a neighbor of it. Gain is not a smooth curve along a feature, so the algorithm has to check them all.
Round 1: the left child stays a leaf
The left child holds rows 1 to 4, with and . Its own score is 720, from over . Scan it the same way:
split
GL
HL
GR
HR
gain
note
study < 1.5
22.5
1
37.5
3
−115.312
negative gain
study < 2.5
40
2
20
2
−53.333
negative gain
study < 3.5
47.5
3
12.5
1
−77.812
negative gain
sleep < 4.5
12.5
1
47.5
3
−77.812
negative gain
sleep < 6
30
2
30
2
−120
negative gain
sleep < 7.5
37.5
3
22.5
1
−115.312
negative gain
Every gain is negative, so depth 2 is allowed but no split happens. All four residuals in this node have the same sign. Splitting them cannot separate rows whose residuals have opposite signs. It only makes two leaves with smaller , and then pulls harder on each. This is the objective at work. Gamma is 0 here, so the refusal comes from alone. Set to 0 in Fig. 7.1, and the same node splits, with gain 100.000.
Round 1: the right child splits
The right child holds rows 5 to 8, with and .
split
GL
HL
GR
HR
gain
note
study < 5.5
−12.5
1
−47.5
3
−77.812
negative gain
study < 6.5
−10
2
−50
2
146.667
study < 7.5
−32.5
3
−27.5
1
−77.812
negative gain
sleep < 5.5
2.5
1
−62.5
3
259.688
chosen
sleep < 6.5
−20
2
−40
2
−53.333
negative gain
sleep < 7.5
−47.5
3
−12.5
1
−77.812
negative gain
The best is sleep < 5.5. Row 6 goes left, and rows 5, 7, 8 go right. Row 6 is the student who scored less than the others on this side, and its residual has the opposite sign, so it is exactly the row that this split should isolate.
Round 1: leaf weights and the update
Each leaf's weight is , and the tree stores .
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
0
8
0
—
1440
study < 4.5
left
1, 2, 3, 4
60
4
−12
−3.6
—
leaf
right
5, 6, 7, 8
−60
4
12
—
259.688
sleep < 5.5
right, left
6
2.5
1
−1.25
−0.375
—
leaf
right, right
5, 7, 8
−62.5
3
15.625
4.6875
—
leaf
Add each row's leaf to its prediction. The RMSE falls from 17.5 to 13.9108.
row
before
this tree adds
after
y
1
57.5
−3.6
53.9
35
2
57.5
−3.6
53.9
40
3
57.5
−3.6
53.9
50
4
57.5
−3.6
53.9
45
5
57.5
4.6875
62.1875
70
6
57.5
−0.375
57.125
55
7
57.5
4.6875
62.1875
80
8
57.5
4.6875
62.1875
85
Round 2: the residuals changed, so everything is recomputed
Now has changed, though is still 1. The second tree fits what the first tree left.
row
x1
x2
y
prediction before this tree
g = prediction − y
h
1
1
8
35
53.9
18.9
1
2
2
5
40
53.9
13.9
1
3
3
7
50
53.9
3.9
1
4
4
4
45
53.9
8.9
1
5
5
8
70
62.1875
−7.8125
1
6
6
5
55
57.125
2.125
1
7
7
6
80
62.1875
−17.8125
1
8
8
7
85
62.1875
−22.8125
1
Scan the root again:
split
GL
HL
GR
HR
gain
note
study < 1.5
18.9
1
−19.6125
7
226.63
study < 2.5
32.8
2
−33.5125
6
518.998
study < 3.5
36.7
3
−37.4125
5
569.949
study < 4.5
45.6
4
−46.3125
4
844.785
chosen
study < 5.5
37.7875
5
−38.5
3
608.489
study < 6.5
39.9125
6
−40.625
2
777.646
study < 7.5
22.1
7
−22.8125
1
321.2
sleep < 4.5
8.9
1
−9.6125
7
51.099
sleep < 5.5
24.925
3
−25.6375
5
264.804
sleep < 6.5
7.1125
4
−7.825
4
22.307
sleep < 7.5
−11.8
6
11.0875
2
60.813
It is the same split, but now with gain 844.785 rather than 1440. The first tree took 30% of the correction, so the same split still has the most to give. The left child again stays a leaf, and the right child splits again:
Show the child scans for tree 2
Left child:
split
GL
HL
GR
HR
gain
note
study < 1.5
18.9
1
26.7
3
−59.045
negative gain
study < 2.5
32.8
2
12.8
2
−2.645
negative gain
study < 3.5
36.7
3
8.9
1
−39.545
negative gain
sleep < 4.5
8.9
1
36.7
3
−39.545
negative gain
sleep < 6
22.8
2
22.8
2
−69.312
negative gain
sleep < 7.5
26.7
3
18.9
1
−59.045
negative gain
Right child:
split
GL
HL
GR
HR
gain
note
study < 5.5
−7.8125
1
−38.5
3
−27.889
negative gain
study < 6.5
−5.6875
2
−40.625
2
131.943
study < 7.5
−23.5
3
−22.8125
1
−30.702
negative gain
sleep < 5.5
2.125
1
−48.4375
3
159.836
chosen
sleep < 6.5
−15.6875
2
−30.625
2
−34.307
negative gain
sleep < 7.5
−38.5
3
−7.8125
1
−27.889
negative gain
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
−0.7125
8
0.0792
—
844.785
study < 4.5
left
1, 2, 3, 4
45.6
4
−9.12
−2.736
—
leaf
right
5, 6, 7, 8
−46.3125
4
9.2625
—
159.836
sleep < 5.5
right, left
6
2.125
1
−1.0625
−0.3187
—
leaf
right, right
5, 7, 8
−48.4375
3
12.1094
3.6328
—
leaf
row
before
this tree adds
after
y
1
53.9
−2.736
51.164
35
2
53.9
−2.736
51.164
40
3
53.9
−2.736
51.164
50
4
53.9
−2.736
51.164
45
5
62.1875
3.6328
65.8203
70
6
57.125
−0.3187
56.8062
55
7
62.1875
3.6328
65.8203
80
8
62.1875
3.6328
65.8203
85
The RMSE falls to 11.2634. It takes many more rounds to get far, because of eta. Try Fig. 2.1 for that.
Comparing with the real model
Here is what booster.get_dump(with_stats=True) prints for this exact model. Each number in the tables above matches it. Note that the leaf holds , and cover is the node's .
The check script requires the gain, threshold, feature, cover and leaf of every node in both trees to equal the hand values, within 0.01% for the single-precision rounding. It also requires booster.predict to equal the hand predictions.
The default method hist grows the same trees with the same gains. Only how it writes the threshold differs: f0<5 instead of f0<4.5, because hist compares with the next value in its bins rather than a midpoint. The dump's missing also differs, 2 for hist and 1 for exact, since neither saw a missing value here and each has its own scan order. The dumped gain, 1440, is the same.
Tree lab: every parameter of the formulas, on the running rows
Fig. 7.1 · interactive
Click a node to see its candidate splits. Purple bars are positive gain, the highlighted row was chosen, red means negative, and grey means blocked by min_child_weight. This runs the same exact-method rules as the hand work above. The check script compares this code with real xgboost on 220 settings of these controls.
Check
In Fig. 7.1, set gamma to 300 on the first tree. What happens, and why does the root survive?
Show the answer
The right child's gain is 259.688, which is below 300, so that split is pruned and the right child becomes a leaf. The root's gain of 1440 is above 300, so the root stays. The tree now has 2 leaves.
Scan a small node yourself
Each practice node below has its rows sorted by one feature, with h = 1 per row, as under squared error. Score every threshold with the gain formula, and pick the largest, as the root scan of round 1 did:
Score the parent. At the root, G = 0 and H = 8, so the parent's score is 0.
Score every threshold. study < 4.5 gives 60²/5 + (−60)²/5 − 0 = 1440, and the table lists the other thresholds.
Choose the largest. No other candidate beats 1440, so the root splits on study < 4.5.
8The same rows under logistic loss
Squared error does not show half of the mechanism, because is always 1. Logistic loss shows it. Use the pass column as the label, and keep the same settings. The objective is binary:logistic. The margin is in log-odds, , , and .
This section is a faded example. It shows the first step in full. After that, it asks you to predict each step before showing it, and in the last part you do the scan yourself.
XGBoost estimates base_score as the mean of the labels, 0.5. It converts that to a margin of 0 with the logit, ln(p/(1 − p)), which undoes the formula for p. So every row starts at , with or , and .
row
y
margin
p
g = p − y
h = p(1 − p)
1
0
0
0.5
0.5
0.25
2
0
0
0.5
0.5
0.25
3
1
0
0.5
−0.5
0.25
4
0
0
0.5
0.5
0.25
5
1
0
0.5
−0.5
0.25
6
0
0
0.5
0.5
0.25
7
1
0
0.5
−0.5
0.25
8
1
0
0.5
−0.5
0.25
The default min_child_weight now counts differently
Predict
Each row has h = 0.25, and min_child_weight is 1. How many rows must each child hold, and which root splits stay valid?
Show the answer
Each child needs 4 rows, because 4 × 0.25 = 1. On eight rows, only the four-and-four splits stay valid: study < 4.5 and sleep < 6.5.
min_child_weight requires and to reach 1. With per row, a child needs 4 rows. On eight rows, that leaves only the even four-and-four splits. Under squared error, the same setting needed one row.
split
GL
HL
GR
HR
gain
note
study < 1.5
0.5
0.25
−0.5
1.75
0.291
blocked by min_child_weight
study < 2.5
1
0.5
−1
1.5
1.067
blocked by min_child_weight
study < 3.5
0.5
0.75
−0.5
1.25
0.254
blocked by min_child_weight
study < 4.5
1
1
−1
1
1
chosen
study < 5.5
0.5
1.25
−0.5
0.75
0.254
blocked by min_child_weight
study < 6.5
1
1.5
−1
0.5
1.067
blocked by min_child_weight
study < 7.5
0.5
1.75
−0.5
0.25
0.291
blocked by min_child_weight
sleep < 4.5
0.5
0.25
−0.5
1.75
0.291
blocked by min_child_weight
sleep < 5.5
1.5
0.75
−1.5
1.25
2.286
blocked by min_child_weight
sleep < 6.5
1
1
−1
1
1
sleep < 7.5
0
1.5
0
0.5
0
blocked by min_child_weight
Only study < 4.5 and sleep < 6.5 pass. The chosen split gives leaves of −0.15 and 0.15.
Show the child scans for this tree
Left child. Each child of a four-and-four split has , so no further split can give both sides 1:
split
GL
HL
GR
HR
gain
note
study < 1.5
0.5
0.25
0.5
0.75
−0.157
blocked by min_child_weight
study < 2.5
1
0.5
0
0.5
0.167
blocked by min_child_weight
study < 3.5
0.5
0.75
0.5
0.25
−0.157
blocked by min_child_weight
sleep < 4.5
0.5
0.25
0.5
0.75
−0.157
blocked by min_child_weight
sleep < 6
1
0.5
0
0.5
0.167
blocked by min_child_weight
sleep < 7.5
0.5
0.75
0.5
0.25
−0.157
blocked by min_child_weight
Right child:
split
GL
HL
GR
HR
gain
note
study < 5.5
−0.5
0.25
−0.5
0.75
−0.157
blocked by min_child_weight
study < 6.5
0
0.5
−1
0.5
0.167
blocked by min_child_weight
study < 7.5
−0.5
0.75
−0.5
0.25
−0.157
blocked by min_child_weight
sleep < 5.5
0.5
0.25
−1.5
0.75
0.986
blocked by min_child_weight
sleep < 6.5
0
0.5
−1
0.5
0.167
blocked by min_child_weight
sleep < 7.5
−0.5
0.75
−0.5
0.25
−0.157
blocked by min_child_weight
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
0
2
0
—
1
study < 4.5
left
1, 2, 3, 4
1
1
−0.5
−0.15
—
leaf
right
5, 6, 7, 8
−1
1
0.5
0.15
—
leaf
Predict
After the first tree, every p moves away from 0.5. Can round 2 find a valid split?
Show the answer
No. h = p(1 − p) is largest at p = 0.5, so every h is now below 0.25, and four rows no longer reach H = 1.
Then round 2 grows nothing. Now has moved away from 0.5, so has fallen below 0.25 for every row. Four rows no longer sum to 1, so no split is valid, even the even split:
row
y
margin
p
g = p − y
h = p(1 − p)
1
0
−0.15
0.4626
0.4626
0.2486
2
0
−0.15
0.4626
0.4626
0.2486
3
1
−0.15
0.4626
−0.5374
0.2486
4
0
−0.15
0.4626
0.4626
0.2486
5
1
0.15
0.5374
−0.4626
0.2486
6
0
0.15
0.5374
0.5374
0.2486
7
1
0.15
0.5374
−0.4626
0.2486
8
1
0.15
0.5374
−0.4626
0.2486
split
GL
HL
GR
HR
gain
note
study < 1.5
0.4626
0.2486
−0.4626
1.7402
0.249
blocked by min_child_weight
study < 2.5
0.9251
0.4972
−0.9251
1.4916
0.915
blocked by min_child_weight
study < 3.5
0.3877
0.7458
−0.3877
1.243
0.153
blocked by min_child_weight
study < 4.5
0.8503
0.9944
−0.8503
0.9944
0.725
blocked by min_child_weight
study < 5.5
0.3877
1.243
−0.3877
0.7458
0.153
blocked by min_child_weight
study < 6.5
0.9251
1.4916
−0.9251
0.4972
0.915
blocked by min_child_weight
study < 7.5
0.4626
1.7402
−0.4626
0.2486
0.249
blocked by min_child_weight
sleep < 4.5
0.4626
0.2486
−0.4626
1.7402
0.249
blocked by min_child_weight
sleep < 5.5
1.4626
0.7458
−1.4626
1.243
2.179
blocked by min_child_weight
sleep < 6.5
1
0.9944
−1
0.9944
1.003
blocked by min_child_weight
sleep < 7.5
0
1.4916
0
0.4972
0
blocked by min_child_weight
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
0
1.9888
0
0
—
leaf
The default of 1 stopped this model after one split. With few rows and an output near 0 or 1, this is a common cause of a model that learns nothing. It comes from , not from the row count.
min_child_weight is a limit on each child's H, not on its row count
Fig. 8.1 · interactive
Data
On the left, each bar is one row's h = p(1 − p) under binary:logistic. On the right, each root candidate has two bars, the H of its left child and of its right child, and the orange line is min_child_weight. A candidate is valid only when both bars reach the line. A grey candidate is blocked, and the valid candidate with the largest gain is marked. In round 1, every h is 0.25, so a child needs 4 rows to reach 1. In round 2, every h is below 0.25, so 4 rows no longer reach 1.
With min_child_weight = 0
Remove the block, and every candidate is back. The rows start from the same g and h:
row
y
margin
p
g = p − y
h = p(1 − p)
1
0
0
0.5
0.5
0.25
2
0
0
0.5
0.5
0.25
3
1
0
0.5
−0.5
0.25
4
0
0
0.5
0.5
0.25
5
1
0
0.5
−0.5
0.25
6
0
0
0.5
0.5
0.25
7
1
0
0.5
−0.5
0.25
8
1
0
0.5
−0.5
0.25
Your turn
Use the g and h in the table above. With no block, which root split wins, and what is its gain?
Show the answer
sleep < 5.5 wins. Its left side holds rows 2, 4 and 6, with GL = 1.5 and HL = 0.75. Its right side has GR = −1.5 and HR = 1.25. So the gain is 1.5²/1.75 + 1.5²/2.25 − 0 = 2.286.
Show the scan of the root
split
GL
HL
GR
HR
gain
note
study < 1.5
0.5
0.25
−0.5
1.75
0.291
study < 2.5
1
0.5
−1
1.5
1.067
study < 3.5
0.5
0.75
−0.5
1.25
0.254
study < 4.5
1
1
−1
1
1
study < 5.5
0.5
1.25
−0.5
0.75
0.254
study < 6.5
1
1.5
−1
0.5
1.067
study < 7.5
0.5
1.75
−0.5
0.25
0.291
sleep < 4.5
0.5
0.25
−0.5
1.75
0.291
sleep < 5.5
1.5
0.75
−1.5
1.25
2.286
chosen
sleep < 6.5
1
1
−1
1
1
sleep < 7.5
0
1.5
0
0.5
0
no gain
Show the child scans for this tree
Left child:
split
GL
HL
GR
HR
gain
note
study < 3
0.5
0.25
1
0.5
−0.419
negative gain
study < 5
1
0.5
0.5
0.25
−0.419
negative gain
sleep < 4.5
0.5
0.25
1
0.5
−0.419
negative gain
Right child:
split
GL
HL
GR
HR
gain
note
study < 2
0.5
0.25
−2
1
1.2
chosen
study < 4
0
0.5
−1.5
0.75
0.286
study < 6
−0.5
0.75
−1
0.5
−0.19
negative gain
study < 7.5
−1
1
−0.5
0.25
−0.3
negative gain
sleep < 6.5
−0.5
0.25
−1
1
−0.3
negative gain
sleep < 7.5
−1.5
0.75
0
0.5
0.286
Your turn
The right child splits again at study < 2. What value does each of the three leaves store?
Show the answer
Each leaf stores 0.3 × (−G/(H + 1)). Rows 2, 4 and 6 store −0.2571, row 1 stores −0.12, and rows 3, 5, 7 and 8 store 0.3.
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
0
2
0
—
2.286
sleep < 5.5
left
2, 4, 6
1.5
0.75
−0.8571
−0.2571
—
leaf
right
1, 3, 5, 7, 8
−1.5
1.25
0.6667
—
1.2
study < 2
right, left
1
0.5
0.25
−0.4
−0.12
—
leaf
right, right
3, 5, 7, 8
−2
1
1
0.3
—
leaf
Now the second round. Here is the point of this section: now differs from row to row. A row that the model is more sure about has a smaller , so it counts for less in . That is how this one formula becomes a Newton step on a curved loss.
row
y
margin
p
g = p − y
h = p(1 − p)
1
0
−0.12
0.47
0.47
0.2491
2
0
−0.2571
0.4361
0.4361
0.2459
3
1
0.3
0.5744
−0.4256
0.2445
4
0
−0.2571
0.4361
0.4361
0.2459
5
1
0.3
0.5744
−0.4256
0.2445
6
0
−0.2571
0.4361
0.4361
0.2459
7
1
0.3
0.5744
−0.4256
0.2445
8
1
0.3
0.5744
−0.4256
0.2445
split
GL
HL
GR
HR
gain
note
study < 1.5
0.47
0.2491
−0.394
1.7156
0.232
study < 2.5
0.9061
0.495
−0.8301
1.4697
0.826
study < 3.5
0.4805
0.7395
−0.4045
1.2252
0.204
study < 4.5
0.9166
0.9854
−0.8406
0.9793
0.778
study < 5.5
0.4911
1.2298
−0.415
0.7348
0.205
study < 6.5
0.9271
1.4758
−0.8511
0.4889
0.832
study < 7.5
0.5016
1.7202
−0.4256
0.2445
0.236
sleep < 4.5
0.4361
0.2459
−0.3601
1.7188
0.198
sleep < 5.5
1.3082
0.7377
−1.2322
1.2269
1.665
chosen
sleep < 6.5
0.8826
0.9822
−0.8066
0.9825
0.719
sleep < 7.5
0.0315
1.4711
0.0445
0.4936
0
negative gain
Show the child scans for tree 2
Left child:
split
GL
HL
GR
HR
gain
note
study < 3
0.4361
0.2459
0.8721
0.4918
−0.322
negative gain
study < 5
0.8721
0.4918
0.4361
0.2459
−0.322
negative gain
sleep < 4.5
0.4361
0.2459
0.8721
0.4918
−0.322
negative gain
Right child:
split
GL
HL
GR
HR
gain
note
study < 2
0.47
0.2491
−1.7022
0.9778
0.96
chosen
study < 4
0.0445
0.4936
−1.2767
0.7334
0.26
study < 6
−0.3811
0.738
−0.8511
0.4889
−0.112
negative gain
study < 7.5
−0.8066
0.9825
−0.4256
0.2445
−0.208
negative gain
sleep < 6.5
−0.4256
0.2445
−0.8066
0.9825
−0.208
negative gain
sleep < 7.5
−1.2767
0.7334
0.0445
0.4936
0.26
node
rows
G
H
w = −G/(H+λ)
leaf = ηw
gain
split
root
1, 2, 3, 4, 5, 6, 7, 8
0.076
1.9647
−0.0256
—
1.665
sleep < 5.5
left
2, 4, 6
1.3082
0.7377
−0.7528
−0.2258
—
leaf
right
1, 3, 5, 7, 8
−1.2322
1.2269
0.5533
—
0.96
study < 2
right, left
1
0.47
0.2491
−0.3763
−0.1129
—
leaf
right, right
3, 5, 7, 8
−1.7022
0.9778
0.8607
0.2582
—
leaf
The check script compares every node of both trees, under both settings, with real dumps, and compares the hand margins with predict(output_margin=True). You can reproduce all of these in Fig. 7.1 by choosing logistic and setting min_child_weight.
Note that the dump's cover is , not a row count. A leaf reporting a cover of 0.25 holds one row here. This is why cover looks so odd on classification models.
Rows needed under logistic loss
min_child_weight counts h, so the number of rows that a child needs depends on p.
Find h per row. At p = 0.5, h = 0.25.
Divide and round up. 1/0.25 = 4, so each child needs 4 rows.
9Finding splits: which candidates get scanned
The gain formula says how to score a candidate. It does not say which candidates to try. That is what tree_method decides. The gain formula is the same under all of them.
method
candidates
built
weights
exact
every gap between distinct values
at each node, from sorted columns
—
approx
at most max_bin cut points
again before each tree
h of the current round
hist (and auto)
at most max_bin cut points
once, before training
only the sample weights from the user
From doc/treemethod.rst at release 3.2.0: approx "runs sketching before building each tree" with hessian as weights, and hist sketches once "using only user provided weights". updater_approx.cc passes hess into its batch spec, and updater_quantile_hist.cc does not.
Exact greedy
For one feature, sort the rows by value. Walk through them, moving one row at a time from one side to the other. Each move updates and with one addition, and follows. So a whole feature costs one sort plus one pass, and no row is summed twice. That is the scan you did by hand. With millions of rows and many features, though, one pass per node per feature is still too slow.
Histograms
The idea is to pick a limited set of cut points per feature and replace each value with its bin number. Then for a node, instead of walking rows in order, add each row's and to its bin. Now a scan walks the bins, not the rows. Each candidate still uses the same gain formula on the running sums.
On our rows with hist, each value has its own bin, so max_bin is not limiting and the gains equal exact's. With max_bin=2, each feature has one threshold left, near its median. On these rows the best split happens to be that median cut, so the root still finds f0 < 5 with gain 1440.000. On the larger dataset in the max_bin table, two bins are far worse, because the best split is usually somewhere else.
Histogram subtraction
A node's rows are exactly its two children's rows. So the parent's histogram is the sum of the children's histograms, bin by bin. That means you only need to build the histogram of one child, normally the smaller one. The other follows by subtraction, at a cost proportional to the bins, not the rows. Here is that on the running rows, for the sleep feature, after the root split on study:
sleep bin
parent Σg
parent Σh
left child Σg
left child Σh
right = parent − left, Σg
right Σh
4
12.5
1
12.5
1
0
0
5
20
2
17.5
1
2.5
1
6
−22.5
1
0
0
−22.5
1
7
−20
2
7.5
1
−27.5
1
8
10
2
22.5
1
−12.5
1
Histogram subtraction: right child = parent − left child
Fig. 9.1 · interactive
Show
Each panel is a histogram of the sleep feature after the root split on study: one bar per sleep bin, holding the sum of g (or h) of the node's rows in that bin. The first tree's g is used, from section 7. The right panel is not built from the right child's rows: each of its bars is the parent's bar minus the left child's bar. Click a bin to see the subtraction.
src/tree/hist/histogram.h calls this the subtraction trick and uses common::SubtractionHist. The parameter max_cached_hist_node limits how many parent histograms are kept for it.
Weighted quantile sketch: why h is the weight
Which cut points are best? Equal numbers of rows per bin sounds natural. But look again at the quadratic objective for a row. Complete the square:
This is a weighted least-squares problem. The target for each row is , and its weight is . So a row with larger matters more to the objective, and it deserves finer resolution. The paper therefore picks cut points so that each bin holds about the same total , not the same row count. A sketch is a compact structure that finds these weighted quantiles without sorting everything in memory.
Cut points by count and by h
Fig. 9.2 · interactive
Equal
The 60 rows are real: a logistic model on generated data, with h ranging from 0.060 to 0.248. Each bar's height is that row's h, and the dashed lines are the cut points. Rows in the middle are where the model is unsure, so h is high. The ends are where it is sure, so h is low. Rows below 2.5 or above 7.5 are 45.0% of the rows, but carry only 27.1% of the total h; rows from 4 to 6 are 25.0% of the rows. This figure shows the ideal cut rule, not the library's sketch, which is approximate.
This is why approx sketches again before each tree: changes every round. And it is why hist, sketching once with no , is faster but doesn't adapt. The docs say that on losses with varying , approx sometimes gives better accuracy, but hist with a higher max_bin can usually match it. On squared error, is constant, so the two agree: on the 2000 training rows used in section 12, they gave the same test RMSE to three decimals, in the tree_method table.
Missing values: learn a default direction
XGBoost needs no imputation. For each split, it learns which side missing values should go to. The scan runs over present values only, with the missing rows' and added to one side, then again with them added to the other side. The better of the two becomes that split's default direction.
Hide sleep for rows 2 and 7, and take sleep alone at the root of the first tree. The missing rows have and . The remaining values are 4, 5, 7, 8, so the candidates are at 4.5, 6 and 7.5. Each one is computed both ways:
split
missing rows sent left: gain
missing rows sent right: gain
sleep < 4.5
23.438
97.656
sleep < 6
40.000
107.143
sleep < 7.5
47.619
10.000
The best is sleep < 6, with missing rows going right, for gain 107.143.
Missing values: drag the threshold and the missing rows
Fig. 9.3 · interactive
The dots are the six rows with a sleep value, labeled with their g; rows 2 and 7, whose sleep is hidden, sit in the box. Drag the purple threshold, which stops at the three candidates, and drag the box to either side of it; both handles also take the arrow keys. The bars below are the gains of all six combinations, from the scan that the check script compares with the dumped tree. The outlined bar is the largest, which sets the default direction.
The dumped tree has that threshold, that gain and that default direction. Declaring a sentinel with missing=-999 gives identical trees to using NaN. An undeclared -999 gives a different model, since it is treated as a real value.
When there are no missing values, a direction is still stored but it was not learned from anything. So on new data with a missing value, a model never trained on missing values sends it down an arbitrary side. exact and hist even disagree on it: section 7 showed each choosing a different default. inference from those dumps and the scan order in source.
Send the missing rows left. At sleep < 6, the gain is 40.000.
Send them right. The gain is 107.143.
Keep the larger. The default direction is right, with gain 107.143.
Categorical splits
With enable_categorical=True and a column marked c, XGBoost can split a category set directly. There are two forms, and max_cat_to_onehot chooses between them: fewer categories than that value means one-hot, and otherwise partition.
Take twelve rows with six categories, two rows each, whose labels have mean 27.000.
One-hot tests one category against all the others. The best of the six gives gain 1060.606 by hand and 1060.606 in the dump, sending category 3 one way.
Partition sorts categories by their leaf weight, then scans the sorted order like a numeric feature. The order here is 0, 2, 4, 1, 5, 3. For a squared-type objective, sorting this way finds the best two-group split without checking all of them, which is Fisher's result, cited in the docs. It gives gain 2314.286 by hand and 2314.286 in the dump, sending categories 0, 2, 4 one way.
Categorical splits: one category against the rest, or a sorted partition
Fig. 9.4 · interactive
Form
Each bar is one category's leaf weight, −G/(H + λ), from its two rows. One-hot tests each category against all the others, and the number under a bar is that split's gain. Partition sorts the categories by weight and tests each cut of that order, like thresholds on a numeric feature; the number on a cut is its gain. The best gain of each form, outlined, equals the dumped tree's gain.
Partition gets a much larger gain from one split. max_cat_threshold limits how many categories that sorted scan may place on one side. exact rejects categorical data.
10Growth, pruning and the rest of the algorithm
Tree growth: depthwise or lossguide
With the same leaf budget, a tree can be grown in two orders. depthwise finishes each level before the next. lossguide always expands the leaf with the largest gain. The figure shows both on real trees, with 8 leaves each.
The order that nodes appear in a real tree
Fig. 10.1 · interactive
First tree, 600 rows and 5 features of the section 12 data, eta 0.3, tree_method="hist", max_leaves=8. Node ids in the dump follow the expansion order, which is what the slider follows. Depthwise here reaches depth 3 with 8 leaves and total gain 9463.5. Lossguide reaches depth 4 with 8 leaves and total gain 9606.1.
Lossguide always finds at least as much gain for the same leaf count here, because it spends each leaf on the best available split. It can also go deep on one side, which is why it can overfit sooner. The grow_policy table in section 12 shows this. The check script requires this gain ordering on this example only; it isn't a theorem.
Pruning with gamma: two different behaviors
The source and the behavior differ by method. hist and approx test gamma when they consider a node. A split below gamma isn't made, so its children are never tried. exact first grows the whole tree as long as gain is positive. Then its prune updater walks up from the leaves and removes a split only if its gain is below gamma and both children are leaves.
That matters for a split that looks weak but sets up strong ones. Twenty rows in an XOR pattern show this: neither feature helps alone, but both together separate the rows. With gamma 0, the root's gain is only 0.000125, while its children's gains are 2.450 and 2.500. Now set gamma to 1:
tree method
leaves
what happened
exact
4
The children's large gains stop pruning from reaching the root, so the root survives.
hist
1
The root's gain is below 1, so it stops immediately.
approx
1
The same as hist.
So the same gamma gives a different tree, and a comparison between tree methods has to allow for that.
Drag gamma: exact prunes from the leaves up, hist stops at the root
Fig. 10.2 · interactive
On the left are the XOR rows: five rows in each cell, labeled 0 or 1, so neither feature separates them alone. The two trees are what xgboost keeps under each method; a faded node was not kept. Drag the orange handle, or use the arrow keys; it stops at the gamma values that change either tree, and each tree shown is the library's own result at that value.
NeedPrune and TryPruneLeaf in src/tree/param.h and updater_prune.cc. exact's saved config has updater: grow_colmaker,prune, and hist's has only grow_quantile_histmaker.
Read the gains. The XOR root has gain 0.000125, and its children have 2.450 and 2.500.
Apply the rule of exact. Both child splits reach gamma 1, so they stay. The root is below 1, but its children are not leaves, so it stays too: 4 leaves.
Apply the rule of hist. The root is below 1, so it is never split: 1 leaf.
base_score: where the sum starts
is estimated from the training labels. The rule depends on the objective:
objective
estimated base_score
rule
reg:squarederror
57.5000
mean of the labels
binary:logistic
0.5000
mean of the labels
reg:logistic
0.5000
mean of the labels
count:poisson
57.5000
mean of the labels
reg:gamma
57.5000
mean of the labels
reg:tweedie
57.5000
mean of the labels
reg:absoluteerror
52.5000
median of the labels
reg:quantileerror
85.0000
the α quantile of the labels (α = 0.9 here)
reg:squaredlogerror
0.8010
one Newton step from margin 0
binary:hinge
0.0000
one Newton step from margin 0
survival:cox
1.0000
one Newton step from margin 0, then exp
reg:pseudohubererror
113664
one Newton step from margin 0, which goes far off on these labels
When scale_pos_weight is not 1, logistic objectives use a Newton step from margin 0 rather than the mean, giving 0.7311 on our rows at a weight of 3. The value for Pseudo-Huber shows the risk in the Newton rule. At margin 0 and labels near 57, each is tiny, so is huge. Setting base_score by hand avoids that. In any case its effect fades with rounds, as the base_score table in section 12 shows.
FitIntercept::InitEstimation and FitInterceptGlmLike::InitEstimation in src/objective/init_estimation.cc. The numbers come from save_config() and from the formula.
Sampling
subsample draws rows each round, and colsample_bytree, colsample_bylevel and colsample_bynode draw features. Neither changes the formulas. Row sampling removes rows from the sums. Column sampling removes candidates from the scan. Two trees that see different data make different mistakes, and averaging different mistakes reduces variance, which is the same reason that random forests work. num_parallel_tree builds this in directly: several trees per round, each on its own sample, with eta divided among them.
DART: boosting with dropout
Under booster="dart", each round drops a random subset of existing trees. Then and come from the model without them, so the new tree is fitted to a partial model. The idea is to stop later trees from only touching up a few rows. Afterwards, the new tree and the dropped trees are rescaled, so that the total stays near where it was.
The rule depends on normalize_type. If trees were dropped, tree sets the new tree's weight to and multiplies dropped trees by . forest uses for both.
With rate_drop=1, one_drop=1, eta 0.3 and 3 rounds, the saved tree weights are 0.6689, 0.6689, 0.4348 under tree and 0.5917, 0.5917, 0.7692 under forest, equal to these formulas. predict uses all trees by default, and differs by 1.835 on average from predict(training=True).
gblinear: boosting without trees
Under booster="gblinear", each is a linear function. Each round does coordinate descent: for one feature at a time, it takes a Newton step on that coefficient, using and weighted by the feature's value. The update from the source is
where the sign of pushes the coefficient toward zero, and the update stops at if alpha would carry it across zero. The bias takes its own Newton step first, with no penalty: 0.0000 after one full step on our rows. Sums of linear functions are linear, so more rounds refine the same coefficients, unlike trees. The final model is regularized linear regression, solved iteratively. It can't capture any interaction or curve.
Multi-class and multi-output
Under softmax with classes, each row has margins, so pairs of and . By default, each round grows trees, one per class, each on its own and : 3 classes over 5 rounds gave 15 trees. With multi_strategy="multi_output_tree", one tree covers all outputs, and each leaf holds a vector of size 3: 5 rounds gave 5 trees. Weights come from each output's formula, and gains add over outputs. The docs mark this as experimental, and it needs hist.
Early stopping
Rounds keep lowering training loss, but held-out loss eventually rises. Early stopping watches an evaluation set after each round, and stops when early_stopping_rounds rounds pass with no improvement. With several sets or metrics, the last of each is used.
What it returns needs care. In the run below, the best round was 28 (zero-based), and the booster still holds 39 rounds. The wrapper's predict stops at best_iteration, but Booster.predict uses every tree unless you pass iteration_range. That is an RMSE of 1.6887 versus 1.6916 on the same booster. EarlyStopping(save_best=True) is the way to cut the model.
Early stopping: the best round, the patience rounds, and what predict uses
Fig. 10.3 · interactive
Predict with
All rounds
Validation, rounds 15 to 39
The run of this section: max_depth=8, eta 0.3 and early_stopping_rounds=10 on the section 12 data. The blue line is the training RMSE, and the orange line is the validation RMSE that early stopping watches. The vertical line is the best round, and the amber rounds after it are the 10 patience rounds, which the booster still holds. The purple bar under the axis covers the rounds whose trees the chosen predict uses. The right panel zooms in on the validation curve, where the rise after the best round is visible.
Types of feature importance
These all come from the same dump, read differently. On our two trees:
type
meaning
study (f0)
sleep (f1)
weight
number of splits on the feature
2.000
2.000
gain
average gain of those splits
1142.393
209.762
cover
average sum of h reaching those splits
8.000
4.000
total_gain
sum of their gains
2284.785
419.524
total_cover
sum of their covers
16.000
8.000
Five importance types from the same two trees
Fig. 10.4 · interactive
Each pair of bars is one importance type, scaled so that study and sleep add up to 100%. The labels are the values that get_score returns. Study and sleep split equally often, so weight gives them equal shares. Study's splits have much larger gains, so gain and total_gain rank it far above sleep. Study's splits also sit higher in the trees, where more rows pass, so it leads on cover as well.
The sklearn property feature_importances_ is average gain, normalized to sum to 1: 0.8449 and 0.1551 here. The three views can rank features differently. A feature used once with a large gain scores high on gain and low on weight. None of these says what a feature contributes to one prediction. For that, use pred_contribs.
How XGBoost differs from classic gradient boosting
part
classic gradient boosting (Friedman 2001)
XGBoost
What a tree fits
Least squares to the negative gradient, so only shapes the tree.
The gain formula, so both and shape the tree.
Leaf values
A line search per leaf after the tree is grown.
The Newton step , consistent with how splits were scored.
Regularization
Shrinkage, depth, and subsampling. No penalty in the objective.
in the objective, so it directly changes splits and weights, plus all of those.
Missing values
Depends on the implementation. Often impute first.
Sections 2 to 10 explained each mechanism separately. This section puts them in the order that the library runs them, and it names each parameter at the step where it acts. A parameter changes the model only at its step. So if you know the step, you know what the parameter can change and what it cannot.
The order below is for the default settings: booster="gbtree", tree_method="hist" on the CPU, and one output per row, as in regression or binary classification. The table at the end of this section lists what changes under other settings.
The round is LearnerImpl::UpdateOneIter in src/learner.cc. The trees of a round are GBTree::DoBoost and BoostNewTrees in src/gbm/gbtree.cc. One tree is UpdateTree in src/tree/updater_quantile_hist.cc, and one node is in src/tree/hist/evaluate_splits.h, src/tree/hist/expand_entry.h and src/tree/driver.h. All are at release 3.2.0.
Each box is one step, and each dashed frame repeats: once per round, once per tree of the round, and once per node. A chip names a parameter that acts at that step, and a chip in a frame's title sets how often the frame repeats. Choose a parameter, or click a chip, to mark every step where it acts. The line under the figure then links to the parameter's entry in section 12. Choose a setting to outline, in orange, the steps that it changes; the table at the end of this section is the source of those marks.
Before the first round
Bin the features. Under hist, each feature gets at most max_bin cut points (256 by default), and each value is replaced by the number of its bin. The cut points come from the feature values, weighted by sample_weight only, so they stay fixed for the whole run (section 9). A value equal to missing (NaN by default) is stored as absent, and so is any entry that a sparse matrix does not store. With enable_categorical=True, a column of type c gets one bin per category. The wrapper's fit bins when it builds its QuantileDMatrix, and xgb.train with a plain DMatrix bins when it grows the first tree. Both give the same cut points, because the cut points do not use g or h.
Set the starting value. If you don't set base_score, the library estimates it from the training labels at the start of the first round. The rule depends on the objective, and a scale_pos_weight other than 1 changes the rule for the logistic objectives (section 10). The value is stored as a margin, so under the logistic objectives 0.5 becomes the logit 0. If the training matrix has a base_margin, each row starts from its own base_margin instead.
Steps 1 and 2: the cut points and the starting value
Fig. 11.2 · interactive
max_bin
The two upper lines are the features of the running rows, each dot labeled with its row number. The orange marks are the cut points that QuantileDMatrix made for the chosen max_bin, and a split can only be written as "value < cut point". The shaded bands are the bins. The lowest line holds the eight scores, and the starting value is their mean. With max_bin=3, study < 5 is no longer a cut point, so the first split becomes a weaker one.
Each round
The wrapper runs n_estimators rounds (100 when unset), and xgb.train runs num_boost_round rounds (10 by default). Each round runs steps 3 to 14.
Compute each row's margin. A row's margin is its starting value plus the stored leaf value of every earlier tree. The library keeps the training margins in a cache, so each round adds only the newest trees.
Compute g and h. The objective gives g and h at the row's margin (section 6). Both are multiplied by the row's sample_weight. Under reg:squarederror, reg:gamma and the three logistic objectives, a row whose label is exactly 1 also has its weight multiplied by scale_pos_weight. Under the logistic objectives, h is at least 10−16.
A round then grows num_parallel_tree trees (1 by default), and each uses eta divided by num_parallel_tree as its learning rate. A multi-class model does this once for each class. Steps 5 to 12 run once for each tree.
Sample the rows. With subsample below 1, each row is kept with probability subsample, independently of the other rows, so the number of kept rows varies. In this release, the first two rows of the training data are an exception: every tree keeps them, as Fig. 11.3 shows. A row that is not kept gets g = 0 and h = 0 for this tree. So it adds nothing to any sum, but it still goes through the tree, and step 13 still adds the tree's leaf to its margin. sampling_method="gradient_based" keeps a row with a probability that grows with , and it divides a kept row's g and h by that probability. seed starts the random generator once, and seed_per_iteration=True restarts it every round from the seed and the round number.
Sample the tree's features.colsample_bytree keeps features for the whole tree, chosen at random. With feature_weights, a feature's chance of being chosen follows its weight.
Start the tree at the root. The root's G and H are summed over all rows, and the rows that step 5 did not keep add 0. The root starts as a leaf that stores , with clipped by max_delta_step, and w is 0 when H is below min_child_weight. If the root never splits, the tree is that one leaf.
Step 5: one coin per row, except the first two
Fig. 11.3 · interactive
subsample
Rows kept by the first tree
How often each row was kept
Both panels are measured on the 8 running rows with xgboost. On the left, the bars count the rows that the first tree kept, over 200 seeds. The hollow dots are what 8 independent coins would give, and the filled dots are what 2 kept rows plus 6 coins would give. On the right, each bar is how often one row was kept over 300 seeds; each row's gradient was set to a different power of 2, so each kept row adds its own bit to the root's G. The dashed line is the rate. At 0.8, the mean was 6.90 rows and not 6.4, the counts ran from 4 to 8, and rows 3 to 8 were kept 0.82 of the time.
The coins come from RandomReplace in src/tree/hist/sampler.h: a generator that multiplies its state by 16807, modulo 263. Its starting state comes from a 32-bit random number, so the first two outputs are still below 2−3 of the generator's range, and both always fall under subsample when subsample is at least 0.125. So rows 1 and 2 are always kept, with one thread or with four. On 1,000 rows the kept fraction at 0.5 was 0.504, so the effect matters only on small data.
Steps 6 and 8: the three feature draws
Fig. 11.4 · interactive
Ten features, f0 to f9. Each row keeps a subset of the row above it: the tree's features (step 6), then one depth's features, then one node's features (step 8). The counts follow max(1, ⌊rate × count⌋). Which features are drawn is random, so "Draw again" changes them but never the counts. The line under the figure gives the largest counts that xgboost used, over 10 trees of depth 6 on 10 features, for the same two rates. The check script measured all 100 pairs of the first two rates, and no tree or depth ever used more features than the rule allows.
Nodes are taken in grow_policy order. depthwise takes every node of one depth before the next depth, and lossguide takes the node with the largest gain first (section 10). Steps 8 and 9 run when a node is created, and steps 10 and 11 run when its turn comes.
Choose the node's candidate features. The node starts from the tree's features of step 6. colsample_bylevel draws a subset of them once for each depth of this tree, and colsample_bynode draws a subset of the depth's features for each node. Each draw uses the same count rule as step 6, so the counts shrink in turn: with 10 features and 0.5 for both of the first two, each depth gets 2. With interaction_constraints, every feature is allowed at the root. Below the root, a feature is allowed only if a split above this node used it, or it is in a group that holds every feature used above this node.
Score every candidate split. For each candidate feature, the node's histogram is scanned bin by bin (section 9). The scan first counts the rows with a missing value on the right. Only if the node has missing values for this feature is the scan repeated with them on the left. A candidate is skipped when either side has an H below min_child_weight. Its gain is the two children's scores minus the node's score, and lambda, alpha and max_delta_step enter those scores (section 5). With monotone_constraints, each child weight is first clamped to a range that the splits above set. If the two weights are then in the wrong order, the candidate gets a gain of −∞. A categorical feature is scanned one-hot or as a sorted partition, by max_cat_to_onehot and max_cat_threshold (section 9). The node keeps its best candidate.
Decide whether to split. The node splits only if every rule in this list holds. Otherwise it stays a leaf.
The best gain is above 10−6.
Both children have an H above 0.
The best gain is not less than gamma, so a gain equal to gamma splits.
The node's depth is less than max_depth (6 by default, and 0 means no limit).
The tree has fewer leaves than max_leaves (0 by default, which means no limit).
A child that would sit at depth max_depth is never scored in step 9, because it could not split.
Split, and set the two new leaves. Each child becomes a leaf that stores , with w from its own G and H as in step 7, and clamped to its monotone range. Here η is eta divided by num_parallel_tree. The child keeps this value unless it splits later. Then the children's histograms are made: the child with the smaller H is built from its rows, and the other child's is the parent's minus it (section 9). max_cached_hist_node limits how many histograms are kept for this. Steps 8 to 11 then repeat for the new children.
Refit the leaves, for L1-type objectives only. Under reg:absoluteerror and reg:quantileerror, after the tree stops growing, each leaf's value is replaced by η times the median, or the quantile_alpha quantile, of the residuals of the leaf's kept rows (section 6). Under every other objective, the leaves keep their values from steps 7 and 11.
Step 10: drag gamma and watch the tree change
Fig. 11.5 · interactive
max_depthmax_leaves
Tree 1 of section 7, on the running rows. Each box is a node that some setting can create; a solid box splits, a green box is a leaf, and a faded box is not reached. Drag the orange handle along the gamma axis, or focus it and use the arrow keys. It stops only at the values where the tree can change: 0, each split's gain, where gain equal to gamma still splits, and just above each gain. Click a node to see its five rules for the current settings. The page grows the tree with the same function that the check script runs in Node against xgboost on 54 settings.
After the round's trees are grown:
Add the trees to the margins. The new trees join the model, and each training row's cached margin gains the leaf value of each new tree. This includes the rows that step 5 did not keep.
Evaluate, and stop early if asked. After the round, the library computes each eval_metric on each evaluation set. With early_stopping_rounds, training stops when the last metric on the last set has not improved for that many rounds, and best_iteration records the best round (section 10). Otherwise the next round starts at step 3.
Prediction for a new row
Start from the starting value. The row starts at the stored base_score, as a margin, or at its own base_margin if you pass one.
Route the row through each tree. At each split, a missing value goes to the split's default side. A numeric value goes left if it is less than the threshold, and right if it is not, so a value equal to the threshold goes right. A category goes right if it is in the split's category set. At the leaf, the row's margin gains the leaf's stored value. The wrapper's predict uses the trees of rounds 0 to best_iteration when early stopping set it, and every tree otherwise. Booster.predict uses every tree unless you pass iteration_range.
Convert the margin. The objective's link turns the margin into the output. reg:squarederror returns the margin as it is, and binary:logistic returns . predict_proba returns 1 − p and p, and XGBClassifier.predict returns class 1 only when p > 0.5. output_margin=True returns the margin without the link.
These step rules were each tested on a fitted model:
Row sampling: with subsample=0.5 on the 8 running rows, 200 seeds kept between 2 and 7 rows for the first tree, 5.05 on average and not 4, because rows 1 and 2 are always kept. The other rows were kept 0.51 of the time. With subsampling on, the training metric, which comes from the cached margins, equals the metric from predict on every row. So step 13 adds each leaf to the rows that the tree did not keep.
Feature sampling: with 10 features, colsample_bytree=0.29 gave each tree at most 2 features, not 3.
Parallel trees: with num_parallel_tree=2 and no sampling, the first round grew two identical trees, each storing −1.8 where section 7's tree stores −3.6.
scale_pos_weight: under reg:squarederror with three labels equal to 1, scale_pos_weight=3 raised the root's cover from 8 to 14.
Unsplit root: with base_score=50 and a gamma that no split can reach, the first tree is one leaf that stores 0.3 × 60/9 = 2.
Missing values: on 1,093 splits of a model trained on 10 dense features, every split sends a missing value right.
Two of these results lead to mistakes in practice. First, a model trained with base_margin still estimates base_score, and predict adds it to any row that comes without a base_margin. Trained with base_margin 0 on the running rows, one row predicts 15.3333 with its base_margin and 72.8333 without it, which is 57.5 more. Second, under hist, missing values at prediction time go right at every split that had none in training, because the scan with them on the left never ran. So a feature that first goes missing at prediction time is treated as a large value, without any warning.
Section 7's run, step by step
Here is the run of section 7 again, with tree_method="hist", which grows the same trees. The settings are max_depth=2, eta 0.3, lambda 1, and the defaults for everything else. Each row of the table is one step, with the settings that act there and what they did in each round.
step
settings in this run
round 1
round 2
1 bin
max_bin 256
Each distinct value has its own bin, so the split that exact writes as study < 4.5 is written as study < 5.
2 start
base_score unset
The mean of the labels, 57.5.
3 margins
—
57.5 for every row
53.9, 57.125 or 62.1875, by leaf
4 g and h
no weights
root G = 0, H = 8
root G = −0.7125, H = 8
5 rows
subsample 1
All 8 rows.
6 features
colsample_bytree 1
Both features.
7 root
eta 0.3, lambda 1
stores 0 until it splits
stores 0.3 × 0.0792 until it splits
8 candidates
colsample_bylevel, colsample_bynode 1
Both features at every node.
9 score
min_child_weight 1, lambda 1
root: study < 5, gain 1440; right child: sleep < 6, gain 259.688; left child: every gain is negative
the same splits, with gains 844.785 and 159.836; left child negative again
10 decide
gamma 0, max_depth 2
The root and the right child split. The left child stays a leaf, because its best gain is not above 10−6. The depth-2 children are never scored.
11 leaves
eta 0.3, num_parallel_tree 1
−3.6, −0.375, 4.6875
−2.736, −0.3187, 3.6328
12 refit
reg:squarederror
None. Leaves are refit only under the L1-type objectives.
13 margins
—
RMSE from 17.5 to 13.9108
RMSE to 11.2634
14 evaluate
no evaluation set
next round
stop: 2 rounds done
The same run, one step at a time
Fig. 11.6 · interactive
Round
Each dot is a running row at its study and sleep values, numbered as in section 1. The label above a dot is the number that the current step uses: the margin, then g, then the new margin. In steps 8 and 9, the lines are a node's candidate splits: the thick purple line is the best one, grey lines have a smaller positive gain, and dashed red lines have a negative gain. Shaded regions are leaves, labeled with the value they store; blue is negative and orange is positive. A row on a line belongs to the right or upper side, because a value equal to the threshold goes right. Click any step in the list to jump to it.
Predict a new row
Take a new student who studied 6 hours and whose sleep is missing. Steps 15 to 17 give the prediction, and the missing value meets the second split of each tree.
Steps 15 to 17: drag a row through both trees
Fig. 11.7 · interactive
Model
Drag the purple dot, or focus it and use the arrow keys. It moves in half-hour steps, and the strips under and beside the plane stand for a missing value: drop the dot there, or use the buttons, to make a feature missing. The shading is the model's prediction at each point, and the right panel shows the dot's path through each tree. The page routes rows with the same function that the check script compares with predict on 60 rows, including missing values and values equal to a threshold.
Start from the starting value. The margin starts at 57.5.
Route through tree 1. Study 6 is not less than 5, so the row goes right. Sleep is missing, so the row goes to the default side, which is right. The leaf stores 4.6875.
Route through tree 2. The splits are the same, so the row reaches the leaf that stores 3.6328.
Add, then convert. 57.5 + 4.6875 + 3.6328 = 65.8203. Squared error has no link, so this is the prediction, and predict returns it.
Now take section 8's logistic model, with min_child_weight 0. The same row starts at margin 0. Sleep is missing at the root, so the row goes right, and study 6 is not less than 2, so it goes right again. The two trees add 0.3 and 0.2582, for a margin of 0.5582 and p = 0.6360. That is above 0.5, so XGBClassifier.predict returns class 1. A row with p exactly 0.5 gets class 0.
Place a parameter at its step
If you know where a parameter acts, you can answer most questions about it. A parameter in step 1 changes the data before any tree exists, so max_bin cannot depend on how training goes. A parameter in steps 5 and 6 changes which rows and features a tree may use, but not the formulas. A parameter in step 9 changes the scores of candidates, and one in step 10 only accepts or refuses the best candidate.
Find the step.colsample_bylevel acts in step 8, after colsample_bytree in step 6.
Apply the rule in order. With 10 features, step 6 keeps max(1, ⌊0.5 × 10⌋) = 5. Step 8 draws from those 5, so it keeps max(1, ⌊0.5 × 5⌋) = 2.
State the effect. Each node at that depth scores 2 features, and the formulas are unchanged.
What changes under other settings
Fig. 11.1 marks these steps when you choose a setting there.
setting
what changes in the steps
tree_method="exact"
There is no step 1. Step 9 scans the sorted values of every feature at every node. After the tree is grown, the prune updater removes splits below gamma from the leaves up (section 10). The default side for missing values can differ: on the running rows, exact sends them left (section 7).
tree_method="approx"
Step 1 runs again before each tree, with h as the weights of the sketch (section 9).
booster="dart"
Before step 3, some earlier trees are dropped from the margins. After step 13, the new tree and the dropped trees are rescaled (section 10).
booster="gblinear"
Steps 5 to 12 are replaced by one pass of coordinate descent over the coefficients (section 10).
multi:softprob and multi:softmax
Steps 5 to 12 run once for each class, each on that class's g and h (section 10).
multi_strategy="multi_output_tree"
One tree per round covers every output, and each leaf holds a vector (section 10).
12Every hyperparameter
This section has 152 entries, and it covers all 143 names found by enumerating the installed library and the documented list: XGBModel.__init__, get_params(), the fit, predict, train, cv and DMatrix signatures, the callback classes, get_config(), and save_config() under every booster, tree method and objective. The check script fails if any of them lacks an entry here. Wherever save_config() reports a default, the script also compares it with the card.
"Acts on" tells you which formula term or algorithm step the parameter changes. An alias is a second name for the same setting. The Python wrapper usually offers both names.
General parameters
These choose the kind of model and how the run behaves. Only booster changes the mathematics.
booster
Default in 3.2.0
gbtree
Valid range
gbtree, dart, gblinear
Acts on
Chooses what is in the additive model: a tree, a tree with dropout, or a linear function (section 10).
Choices
gbtree grows trees. dart grows trees and drops old ones while it trains. gblinear adds a linear function each round, so the model stays linear in the features.
Watch for
gblinear cannot find interactions, and every tree parameter does nothing under it. The validation warning tells you so.
Works with
DART parameters need dart. The gblinear parameters need gblinear.
device
Default in 3.2.0
cpu
Valid range
cpu, cuda, cuda:<ordinal>, gpu, gpu:<ordinal>
Acts on
Chooses where g, h and the histograms are computed. The formulas do not change.
Choices
Only CUDA GPUs are supported, according to the docs. In distributed runs, cuda:<ordinal> is an error, so use cuda.
Watch for
This page ran on CPU only, so no GPU number here was measured.
Works with
tree_method: hist and approx have GPU updaters. exact has none.
verbosity
Default in 3.2.0
1
Valid range
0 silent, 1 warning, 2 info, 3 debug
Acts on
Only controls printed messages. Does not change the fitted trees.
Raising it
More messages, such as the pruning summary that updater_prune.cc logs at info level.
Lowering it
Fewer messages. 0 also hides warnings about unused parameters.
Watch for
It can be set per Booster or globally with xgboost.config_context() and set_config.
Works with
validate_parameters reports through this channel.
validate_parameters
Default in 3.2.0
true in Python, saved as 1
Valid range
true or false
Acts on
After configuration, the library warns about any parameter that no part of it read. Does not change the fitted trees.
Watch for
This is what catches a typo or a parameter for another booster. It also warned for every removed name in the last group, which is how this page confirms they are gone. Keep it on.
Works with
verbosity must be at least 1 to see the warning.
nthread
alias n_jobs
Default in 3.2.0
0, which means all available threads
Valid range
0 or a positive integer
Acts on
Threads for building histograms and scanning. Does not change the fitted trees. The exception is gblinear with shotgun, which is nondeterministic by design.
Raising it
Faster, until threads contend for the same cores.
Lowering it
Slower. This page fits with 1 so every measured number repeats.
Watch for
Inside a grid search in scikit-learn, parallel fits times parallel threads can oversubscribe the machine.
Works with
n_jobs is the scikit-learn spelling. xgboost.get_config() also holds a global nthread, and the docs recommend the Booster one over it.
disable_default_eval_metric
Default in 3.2.0
false, saved as 0
Valid range
true or false
Acts on
Removes the objective's default metric from the evaluation list. Does not change the fitted trees.
Watch for
Without any other metric, evals_result stays empty. The check script confirms both behaviors.
Works with
eval_metric, early_stopping_rounds (needs a metric).
use_rmm
Default in 3.2.0
false (global config)
Valid range
true or false
Acts on
Global setting: allocate GPU memory through the RAPIDS Memory Manager. Does not change the fitted trees.
Watch for
Only meaningful on a GPU build with RMM. Not exercised on this page.
Works with
device.
use_cuda_async_pool
experimental
Default in 3.2.0
false (global config)
Valid range
true or false
Acts on
Global setting, new in 3.2.0: use the memory pool of the CUDA driver. Does not change the fitted trees.
Watch for
The docs mark it experimental and say Windows is not supported yet. Not exercised on this page.
Works with
device, use_rmm.
fail_on_invalid_gpu_id
internal
Default in 3.2.0
0
Valid range
true or false
Acts on
Raise an error instead of falling back when a GPU ordinal is invalid. Does not change the fitted trees.
Watch for
It appears in save_config() but not on the documented parameter page.
Works with
device.
Parameters of the tree booster
These apply when booster is gbtree or dart. The measured tables use one generated dataset: make_friedman1, 2000 training rows and 1000 test rows, 200 rounds, depth 4, eta 0.1, except where a table says otherwise.
eta
alias learning_rate
Default in 3.2.0
0.3
Valid range
[0, 1] in the docs. The source sets only a lower bound of 0.
Acts on
The factor in . It multiplies each leaf after the tree is built. It does not enter the gain.
Raising it
Each tree corrects more of the error. Fewer rounds reach a given training loss, and later trees have less left to fix.
Lowering it
Each tree corrects less. You need more rounds, and the model averages over more trees. At 0.01 with 200 rounds the model has not finished learning.
Watch for
Eta and the number of rounds trade against each other, so tune them together. The first split of every tree is unaffected by eta, because gains come from g and h. Only later trees see the change.
Works with
n_estimators, early_stopping_rounds, num_parallel_tree (eta is divided among the trees of one round).
Measured
setting
train RMSE
test RMSE
total leaves
0.01
2.091
2.317
3199
0.03
1.052
1.428
3162
0.1
0.673
1.268
3020
0.3
0.312
1.416
3059
1.0
0.070
2.096
3074
gamma
alias min_split_loss
Default in 3.2.0
0
Valid range
[0, ∞)
Acts on
The cost in . A split survives only if its dumped gain reaches gamma.
Raising it
More splits fail, so trees shrink. Train error rises, and test error can fall if the removed splits were noise.
Lowering it
At 0, any positive gain splits.
Watch for
Gamma compares with the dumped gain, which has no factor of one half. So gamma = 1000 keeps a split with gain 1440 even though half of that is 720. Its useful size depends on the scale of g and h, so a good value for one target means nothing for another. exact prunes bottom-up, but hist and approx stop at a weak node (section 10).
Works with
lambda (lowers gains), max_depth, tree_method.
Measured
setting
train RMSE
test RMSE
total leaves
0
0.673
1.268
3020
1
0.800
1.291
1851
5
0.929
1.330
1215
20
1.064
1.396
868
100
1.391
1.644
574
max_depth
Default in 3.2.0
6
Valid range
[0, ∞). 0 means no limit, under hist and approx only.
Acts on
A stopping rule in the growth loop: a node at this depth becomes a leaf. A path of depth d can use d features, so it limits the order of interaction.
Raising it
Each tree can have up to leaves. Train error falls quickly and overfitting begins sooner.
Lowering it
Trees become simpler, and depth 1 trees can only add up single-feature effects.
Watch for
exact rejects 0. Without the limit under hist, and with lambda 0 and no min_child_weight, one tree on 600 rows grew 599 leaves and reached depth 17. The docs warn that deep trees use a lot of memory.
Works with
max_leaves, grow_policy, min_child_weight, gamma.
Measured
setting
train RMSE
test RMSE
total leaves
1
1.720
1.818
400
2
1.181
1.429
800
3
0.875
1.271
1588
4
0.673
1.268
3020
6
0.300
1.374
8983
8
0.049
1.536
24802
12
0.001
1.792
64791
min_child_weight
Default in 3.2.0
1
Valid range
[0, ∞)
Acts on
A candidate is valid only if and both reach this value. It is a sum of h, not a count of rows.
Raising it
More candidates are blocked, so trees shrink. The effect is strongest where h is small.
Lowering it
Allows smaller leaves. At 0, only leaves with are still refused.
Watch for
Under squared error, h = 1, so the value counts rows. Under logistic with p near 0 or 1, h is near 0, so a default of 1 can need hundreds of rows. On our logistic rows, h = 0.25 per row, so a child needs 4 rows (section 8).
Works with
The objective (it sets h), scale_pos_weight and sample weights (they multiply h).
Measured
setting
train RMSE
test RMSE
total leaves
0
0.673
1.268
3020
1
0.673
1.268
3020
10
0.685
1.259
2588
50
0.809
1.245
1964
200
1.085
1.286
1219
max_delta_step
Default in 3.2.0
0, except count:poisson, which sets 0.7
Valid range
[0, ∞). 0 means no cap.
Acts on
Clips the weight: , before eta. The node score is then computed from the clipped weight, so the gain changes too.
Raising it
A larger cap, which matters less and less.
Lowering it
A smaller cap makes every update smaller, however large the gradient sum is.
Watch for
It helps when h is tiny, such as rare positives under logistic, because can then be very large. In the table, eta is 1 so the cap on the largest leaf is plain to see. Under count:poisson this one number also raises h, as .
Works with
scale_pos_weight, eta, count:poisson.
Measured
setting
mean p
test logloss
AUC
recall at 0.5
precision at 0.5
largest |leaf|
0
0.035
0.184
0.883
0.444
0.842
9.689
0.1
0.030
0.160
0.899
0.426
0.902
0.100
0.5
0.036
0.174
0.891
0.491
0.815
0.500
1
0.034
0.177
0.895
0.481
0.825
1.000
5
0.035
0.186
0.881
0.472
0.879
5.000
Measured
setting
largest |leaf|
test RMSE after 3 rounds
0.0
5.250
4.397
0.7
0.700
1.800
2.0
0.720
2.177
lambda
alias reg_lambda
Default in 3.2.0
1 for trees
Valid range
[0, ∞)
Acts on
The term of . It adds to H in both the weight and the score .
Raising it
Weights shrink toward 0, most of all in leaves with small H. Gains fall too.
Lowering it
At 0, a leaf takes the full Newton step, which is unstable when H is small.
Watch for
It rarely prunes by itself: in the table, leaf counts barely move while train error rises. It shrinks instead. Pair it with gamma to get both. Under gblinear the default is 0 and the meaning is different (see that group).
Works with
gamma, alpha, min_child_weight.
Measured
setting
train RMSE
test RMSE
total leaves
0
0.626
1.274
3103
1
0.673
1.268
3020
10
0.777
1.301
2964
100
0.929
1.318
3081
1000
1.478
1.681
3155
alpha
alias reg_alpha
Default in 3.2.0
0 for trees
Valid range
[0, ∞)
Acts on
The term of . It soft-thresholds G: moves toward 0 by , and becomes 0 when , in both the weight and the score.
Raising it
Small gradient sums go to exactly 0, so some leaves become exactly 0 and weak splits lose their gain. Leaf counts fall, unlike with lambda.
Lowering it
At 0, no thresholding.
Watch for
Its scale is the scale of G, so its useful size depends on the target and on how many rows a node holds.
Works with
lambda, max_delta_step.
Measured
setting
train RMSE
test RMSE
total leaves
0
0.673
1.268
3020
1
0.666
1.280
3025
10
0.784
1.284
2800
100
1.315
1.572
1324
1000
3.102
3.251
514
subsample
Default in 3.2.0
1
Valid range
(0, 1]
Acts on
Before each round, unsampled rows get g = h = 0 (read from hist/sampler.cc), so they are absent from every G and H of that round's tree. This changes the sums, not the formula.
Raising it
Toward 1, every tree sees every row.
Lowering it
Each tree sees fewer rows, so trees differ more from each other. Train error rises. Here even 0.2 did not hurt test error, so a low value is not always a loss.
Watch for
It uses the seed, so results change with seed. Leaves can be fitted on few rows when combined with a low min_child_weight.
Works with
sampling_method, seed, num_parallel_tree, eta.
Measured
setting
train RMSE
test RMSE
total leaves
0.2
0.787
1.240
2863
0.5
0.660
1.249
3019
0.8
0.634
1.246
3052
1.0
0.673
1.268
3020
sampling_method
Default in 3.2.0
uniform
Valid range
uniform, gradient_based
Acts on
Chooses how subsample picks rows. gradient_based keeps a row with probability that grows with , and scales up the g and h of kept rows so the sums stay roughly right.
Choices
uniform gives each row the same chance. gradient_based favours rows with large gradients, and the docs say subsample may go as low as 0.1.
Watch for
It needs tree_method=hist. The 3.2.0 parameter page says CPU and GPU both work. The older table of tree methods still says GPU only, and so does the wrapper's docstring. It ran on CPU here. At 0.3 it beat uniform in this one test and at 0.1 it lost, so this table does not support the claim.
Works with
subsample, tree_method.
Measured
setting
train RMSE
test RMSE
total leaves
uniform, subsample 0.1
0.941
1.350
2654
uniform, subsample 0.3
0.739
1.273
2935
gradient_based, subsample 0.1
0.881
1.404
2530
gradient_based, subsample 0.3
0.702
1.238
2816
colsample_bytree
Default in 3.2.0
1
Valid range
(0, 1]
Acts on
Before each tree, chooses the features that the whole tree may scan. It removes candidates from the scan.
Raising it
Toward 1, every feature is available.
Lowering it
Trees differ more and each tree is weaker. There are fewer candidates to scan, so each tree is cheaper.
Watch for
When only a few features carry signal, a low value makes many trees miss them, as 0.2 does here. The three colsample settings multiply: 0.5 of each on 64 features leaves 8 per split, according to the docs.
Chooses the split-finding step (section 9). It chooses which candidates the gain formula is applied to, and not the formula.
Choices
exact scans every value. approx sketches with h as the weight before each tree. hist sketches once before training, using only user weights.
Watch for
exact cannot do categorical data, max_depth=0, or lossguide, and it does not refit L1 leaves. Its pruning also differs. Approx and hist gave the same totals on this squared-error data, where h is constant, and the docs explain that weighted sketching makes no difference there.
Works with
max_bin, grow_policy, updater, gamma.
Measured
setting
train RMSE
test RMSE
total leaves
exact
0.681
1.288
2807
approx
0.673
1.268
3020
hist
0.673
1.268
3020
scale_pos_weight
Default in 3.2.0
1
Valid range
[0, ∞) in the source
Acts on
Multiplies g and h of positive rows, so it is the same as a sample weight on positives. The check script confirms the trees are identical.
Raising it
Positive rows count for more. Mean predicted probability rises, so probabilities stop matching the true rate, and recall rises while precision falls.
Lowering it
Toward 1, no reweighting.
Watch for
AUC changed little in the table, while recall and precision moved more. Do not use it if you need calibrated probabilities. It also raises h, so min_child_weight blocks fewer splits. When it is not 1, the estimated base_score uses a Newton step instead of the mean: 0.7311 on our rows for a value of 3. On the positive rate of 5.4%, the ratio of negatives to positives is 17.5.
Chooses the order nodes are expanded in the growth loop (section 10).
Choices
depthwise expands level by level. lossguide always expands the leaf with the largest gain, so trees can be deep and lopsided.
Watch for
It needs hist or approx. Use lossguide with max_leaves as the size limit, and set max_depth=0, or depth limits both. Under lossguide 16 leaves overfit more here than depth 4 does, even with about the same total leaves.
Works with
max_leaves, max_depth, tree_method.
Measured
setting
train RMSE
test RMSE
total leaves
depthwise, depth 4, leaves 0
0.673
1.268
3020
depthwise, depth 3, leaves 0
0.875
1.271
1588
lossguide, depth 0, leaves 4
1.057
1.297
800
lossguide, depth 0, leaves 8
0.822
1.253
1600
lossguide, depth 0, leaves 16
0.555
1.296
3200
lossguide, depth 0, leaves 32
0.271
1.316
6400
lossguide, depth 0, leaves 64
0.073
1.382
12800
max_leaves
Default in 3.2.0
0, which means no limit
Valid range
[0, ∞)
Acts on
A stopping rule: a tree stops once it has this many leaves. The docs phrase this as the maximum number of nodes to add. It is measured here as leaves per tree.
Raising it
Trees grow bigger, as in the table, where each tree reaches its limit.
Lowering it
Trees grow smaller. Two leaves make a stump.
Watch for
The docs say exact does not use it. Under depthwise, it stops the tree partway through a level.
Works with
grow_policy, max_depth.
Measured
setting
train RMSE
test RMSE
total leaves
2
1.720
1.818
400
4
1.057
1.297
800
8
0.822
1.253
1600
16
0.555
1.296
3200
32
0.271
1.316
6400
max_bin
Default in 3.2.0
256
Valid range
at least 2
Acts on
The number of histogram bins per feature, so the number of candidate thresholds under hist and approx.
Raising it
More candidates, closer to exact. Scans and memory cost more.
Lowering it
Fewer candidates. The best split may not be among them. Two bins are too few here, and 64 matches 256.
Watch for
With fewer distinct values than bins, nothing changes. On our 8 rows, max_bin=2 still finds f0 < 5 with gain 1440.000, because the best split happens to be the median cut. The table shows what two bins cost on real data. A QuantileDMatrix takes this at construction, and it should match the training parameter.
Works with
tree_method, QuantileDMatrix.
Measured
setting
train RMSE
test RMSE
total leaves
2
3.216
3.532
3200
4
1.575
2.164
3200
8
0.950
1.479
3192
16
0.772
1.295
3171
64
0.675
1.268
3108
256
0.673
1.268
3020
1024
0.683
1.281
2855
num_parallel_tree
Default in 3.2.0
1
Valid range
positive integer
Acts on
Grows this many trees per round, and all of them fit the same g and h. Eta is divided by this number inside the round, so a round still takes one eta step.
Raising it
More trees per round are averaged in effect. Cost rises in proportion, as the leaf counts show.
Lowering it
1 is ordinary boosting.
Watch for
Without row or column sampling all the trees of a round are identical, so it adds cost with no benefit. On our rows with eta 0.3, two parallel trees each store a leaf of −1.800 where a single tree stores −3.600.
Works with
subsample, colsample_bynode, the XGBRF* wrappers.
Measured
setting
train RMSE
test RMSE
total leaves
1
1.193
1.512
794
2
1.181
1.483
1585
5
1.187
1.492
3954
10
1.161
1.476
7923
monotone_constraints
Default in 3.2.0
none, saved as ()
Valid range
tuple or dict of -1, 0, 1 per feature
Acts on
During split scoring, a split on a constrained feature is rejected unless its child weights are in the required order. Then child weights are bounded by the midpoint of the two, so later splits cannot undo the order.
Choices
1 means the prediction cannot fall as the feature rises. -1 means it cannot rise. 0 means no constraint.
Watch for
Measured along one feature, the constraint removes all steps in the forbidden direction. If the data truly has the opposite trend, the model goes flat and loses accuracy, as the -1 row shows.
Works with
interaction_constraints, tree_method.
Measured
setting
steps down along feature 0
steps up along feature 0
test RMSE
none
30
45
2.227
(1,0,0)
0
26
2.220
(-1,0,0)
0
0
2.569
interaction_constraints
Default in 3.2.0
none, saved as an empty string
Valid range
nested list of feature indices or names
Acts on
Limits the candidates at a node: the scan may use a feature only when it shares an allowed group with every feature already on the path.
Choices
[[0, 1], [2, 3, 4]] lets 0 and 1 interact, and lets 2, 3, 4 interact, but no path mixes the two groups.
Watch for
Single-feature groups turn the model into an additive one, which raises the error here because Friedman's function has a real interaction. The check script walks every path and confirms none breaks a group.
Works with
monotone_constraints, colsample_*.
Measured
setting
paths breaking a group
train RMSE
test RMSE
none
0
0.673
1.268
[[0,1,2,3,4],[5,6,7,8,9]]
0
0.701
1.271
[[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]
0
1.390
1.784
multi_strategy
experimental
Default in 3.2.0
one_output_per_tree
Valid range
one_output_per_tree, multi_output_tree
Acts on
Chooses the form of the tree for multi-target and multi-class tasks: one scalar tree per output, or one tree with a vector in each leaf.
Choices
one_output_per_tree grows K trees per round. multi_output_tree grows one, and its gain sums over outputs.
Watch for
The docs call it work in progress. multi_output_tree needs hist: exact rejects it, as the check script confirms.
Works with
num_class, tree_method, quantile_alpha with a list.
Measured
setting
trees after 50 rounds
test RMSE
one_output_per_tree
150
1.719
multi_output_tree
50
1.695
updater
alias updater_seq
Default in 3.2.0
set from tree_method: grow_quantile_histmaker for hist
Valid range
comma-separated list of updaters
Acts on
The ordered list of steps that build or change a tree. tree_method is a preset for this list. exact saves grow_colmaker,prune, and that second step is the pruning difference in section 10.
Setting it by hand makes the library ignore tree_method, and it warns. Under gblinear, the same name chooses shotgun or coord_descent.
Works with
process_type, tree_method, refresh_leaf.
process_type
Default in 3.2.0
default
Valid range
default, update
Acts on
default grows new trees. update takes each existing tree in turn and runs updaters on it without adding trees.
Choices
update works with the refresh and prune updaters only.
Watch for
Pruning our first tree with gamma=300 in update mode took it from 3 leaves to 2.
Works with
updater, refresh_leaf, xgb_model, gamma.
refresh_leaf
Default in 3.2.0
1
Valid range
0 or 1
Acts on
Used by the refresh updater: recompute node statistics on new data, and with 1 also recompute leaf values. Tree structure stays.
Raising it
1: leaves change. Refreshing on shifted labels moved one leaf from −3.6000 to −1.2000.
Lowering it
0: only statistics such as cover change, and predictions stay.
Watch for
The docs note that refresh samples no rows.
Works with
process_type, updater.
max_cached_hist_node
Default in 3.2.0
saved as 18446744073709551615, which means not set. The effective default is 65536 on CPU and 4096 on GPU.
Valid range
at least 1
Acts on
How many node histograms are held in memory for the subtraction step (section 9). The formulas and trees stay the same.
Raising it
More memory and fewer rebuilds.
Lowering it
Less memory. A cache of 2 still gave identical trees in the check script, so it affects cost, not the model.
Watch for
The docs say to leave it alone unless you grow very deep trees.
Works with
max_depth, tree_method.
max_cat_to_onehot
Default in 3.2.0
4
Valid range
at least 1
Acts on
Chooses the split form for a categorical feature: with fewer categories than this, test one category against the rest. Otherwise scan a partition of sorted categories (section 9).
Raising it
More features use one-category-against-rest splits, which are weaker per split.
Lowering it
More features use partitions.
Watch for
The docs mark it experimental. At depth 2 on 40 categories, partition splits are far better here. It needs enable_categorical, and exact cannot use categorical data.
Limits a partition split: at most this many categories, taken in sorted order, go to one side.
Raising it
Partitions can hold more categories, which fits better and can overfit on rare ones.
Lowering it
Partitions hold fewer. At 1 here, each split isolates one category and the model does worst.
Watch for
The docs mark it experimental. It only matters for features that use partition splits.
Works with
max_cat_to_onehot.
Measured
setting
train RMSE
test RMSE
total leaves
1
2.447
2.537
77
2
1.460
1.508
68
5
0.990
1.037
80
10
0.964
1.003
80
64
0.956
1.005
80
sparse_threshold
internal
Default in 3.2.0
0.2
Valid range
[0, 1]
Acts on
Used when hist stores its binned data: the source describes it as the threshold for treating a feature as sparse. Inferred to affect storage only, not splits.
Watch for
It appears in save_config() but not on the documented parameter page. Leave it alone.
Works with
tree_method.
debug_synchronize
internal
Default in 3.2.0
0
Valid range
0 or 1
Acts on
Debug check that all distributed trees are identical after building. Does not change the fitted trees.
Watch for
Saved in the config, but not on the documented parameter page.
Works with
Distributed training.
default_direction
internal
Default in 3.2.0
learn
Valid range
learn, left, right
Acts on
Applies to the exact updater only: whether the default direction for missing values is learned from the two scans or forced to one side.
Watch for
Saved under colmaker_train_param, not on the documented parameter page.
Works with
missing, tree_method.
opt_dense_col
internal
Default in 3.2.0
1
Valid range
[0, 1]
Acts on
Applies to the exact updater only: the source calls it a speed optimization for dense columns.
Watch for
Saved under colmaker_train_param, not on the documented parameter page.
Works with
tree_method.
DART parameters
These apply only when booster=dart. Tables use 100 rounds. Dropout changes which residuals a new tree fits, so the new tree and the dropped ones must be rescaled to keep the prediction consistent (section 10).
rate_drop
Default in 3.2.0
0
Valid range
[0, 1]
Acts on
Before computing g and h, each old tree is dropped with this probability, so g and h are computed against a partial model.
Raising it
More trees are dropped each round. Training slows, because each tree no longer simply corrects the full model. In 100 rounds, errors here rise with the rate.
Lowering it
At 0, DART matches gbtree for gains, as the first row shows when compared with the 100-round line of n_estimators.
Watch for
The check script confirms predict uses all trees unless training=True.
Works with
one_drop, skip_drop, normalize_type, sample_type.
Measured
setting
train RMSE
test RMSE
total leaves
0.0
0.842
1.297
1553
0.1
1.559
1.837
1597
0.3
2.489
2.703
1600
0.5
2.890
3.085
1600
one_drop
Default in 3.2.0
0
Valid range
0 or 1
Acts on
If the random draw dropped nothing, drop one tree anyway.
Raising it
1: at least one tree is always dropped, which matters when rate_drop is small. The table sets it to 0.001.
Lowering it
0: a round may drop nothing.
Watch for
skip_drop has priority over it.
Works with
rate_drop, skip_drop.
Measured
setting
train RMSE
test RMSE
total leaves
0
0.845
1.306
1579
1
0.915
1.347
1582
skip_drop
Default in 3.2.0
0
Valid range
[0, 1]
Acts on
With this probability, a round skips dropout and acts like gbtree.
Raising it
Fewer rounds drop trees, so training gets closer to gbtree, as the falling errors show.
Lowering it
At 0, every round may drop.
Watch for
The docs say a non-zero value has priority over rate_drop and one_drop.
Works with
rate_drop, one_drop.
Measured
setting
train RMSE
test RMSE
total leaves
0.0
1.559
1.837
1597
0.5
1.031
1.421
1580
0.9
0.884
1.324
1550
sample_type
Default in 3.2.0
uniform
Valid range
uniform, weighted
Acts on
Chooses how dropped trees are selected.
Choices
uniform treats all trees alike. weighted drops in proportion to each tree's current weight.
Watch for
The effect is small here. It only matters with a non-zero rate_drop.
Works with
rate_drop, normalize_type.
Measured
setting
train RMSE
test RMSE
total leaves
uniform
1.559
1.837
1597
weighted
1.622
1.893
1596
normalize_type
Default in 3.2.0
tree
Valid range
tree, forest
Acts on
Chooses the weights after a round that dropped k trees.
Choices
tree: new tree weight , dropped trees multiplied by . forest: new tree weight , dropped trees multiplied by .
Watch for
With rate_drop=1, eta 0.3 and 3 rounds, the saved weights are 0.6689, 0.6689, 0.4348 under tree, and 0.5917, 0.5917, 0.7692 under forest. Both follow the documented formulas.
Works with
rate_drop, eta.
Measured
setting
train RMSE
test RMSE
total leaves
tree
1.559
1.837
1597
forest
1.169
1.521
1594
gblinear parameters
These apply only when booster=gblinear. The shared names lambda, alpha, eta and updater are documented here with their gblinear meaning: defaults 0, 0, 0.5 and shotgun. The cost is expressed per row: the library multiplies lambda and alpha by the sum of instance weights. On our rows with one standardized feature, one full step gives weight 16.0936 at lambda 0, which equals the least-squares slope 16.0936, and weight 8.0468 at lambda 1, which is the same formula with the row count added to the denominator. The tables use a truly linear dataset with 20 features, 5 informative. Each uses 50 rounds, except where noted.
lambda (gblinear)
Default in 3.2.0
0 under gblinear
Acts on
Under gblinear, this is lambda: the L2 penalty on coefficients, multiplied by the sum of row weights.
Raising it
Coefficients shrink toward 0, and the multiplication by row count makes this far stronger than under trees. A value of 1 is very large here.
Lowering it
At 0, plain least-squares coordinate descent.
Watch for
Do not reuse a value from a tree model.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
0
9.976
10.195
0
0.1
18.146
18.352
0
1
82.154
81.920
0
10
147.065
145.187
0
alpha (gblinear)
Default in 3.2.0
0 under gblinear
Acts on
Under gblinear, this is alpha: the L1 penalty, multiplied by the sum of row weights. It soft-thresholds each coefficient update.
Raising it
More coefficients become exactly 0, as the last column shows.
Lowering it
At 0, all coefficients stay.
Watch for
This is the Lasso mechanism, so it does feature selection.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
0
9.976
10.195
0
0.1
9.986
10.162
2
1
10.298
10.394
15
10
24.971
25.134
15
100
161.562
159.165
20
eta (gblinear)
Default in 3.2.0
0.5 under gblinear
Acts on
Under gblinear, this is eta: it multiplies each coordinate update. The table is 5 rounds, to show the convergence speed.
Raising it
Convergence is quicker.
Lowering it
Convergence is slower.
Watch for
At 1 with lambda 0 on one standardized feature, one step reaches least squares.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
0.1
95.093
94.908
0
0.5
11.547
11.788
0
1.0
9.976
10.195
0
updater (gblinear)
Default in 3.2.0
shotgun under gblinear
Acts on
Under gblinear, this is updater: the fitting algorithm.
Choices
shotgun runs parallel coordinate descent, and the docs say it is nondeterministic. coord_descent is ordinary coordinate descent and is deterministic. On one thread here, they give the same result.
Watch for
Choose coord_descent for repeatable results with several threads.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
shotgun
9.976
10.195
0
coord_descent
9.976
10.195
0
feature_selector
Default in 3.2.0
cyclic
Valid range
cyclic, shuffle, random, greedy, thrifty
Acts on
Chooses the order of coordinate updates within a round.
Choices
cyclic goes through features in order, shuffle reorders them each round, and random draws with replacement. greedy picks the feature with the largest change each time, at cost. thrifty reorders by the size of change once per round.
Watch for
This table is 3 rounds, to show how quickly each one converges. The table uses updater=coord_descent. The check script confirms that shotgun rejects greedy and thrifty.
Works with
top_k, updater.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
cyclic
23.155
23.789
0
shuffle
23.639
24.408
0
random
34.950
36.922
1
greedy
10.078
10.174
6
thrifty
22.896
23.334
0
top_k
Default in 3.2.0
0, which means all features
Valid range
non-negative integer
Acts on
Used by greedy and thrifty: only this many top features per class are updated per round.
Raising it
More features are updated, so convergence is quicker at more cost.
Lowering it
Fewer features change per round, so the model stays sparse, but it learns slowly. The table is 3 rounds of greedy.
Watch for
It has no effect under cyclic, shuffle or random.
Works with
feature_selector.
Measured
setting
train RMSE
test RMSE
zero coefficients of 20
0
10.078
10.174
6
1
101.099
101.193
17
3
46.352
45.519
15
10
10.487
10.510
15
tolerance
internal
Default in 3.2.0
0
Valid range
[0, ∞)
Acts on
The source describes it as: stop when the largest weight update is below this number. Saved under gblinear_train_param.
Watch for
It is not on the documented parameter page. Not measured here.
Works with
booster.
max_row_perbatch
internal
Default in 3.2.0
18446744073709551615
Valid range
positive integer
Acts on
The source describes it as the maximum rows per batch. Saved under gblinear_train_param.
Watch for
It is not on the documented parameter page. Not measured here.
Works with
booster.
Objective-specific parameters
Each of these belongs to one objective. It either changes the g and h formulas of section 6 or changes which rows and pairs produce them. The ranking tables use 100 generated queries of 30 documents with graded relevance, 70 for training and 30 for testing.
num_class
Default in 3.2.0
0, which means not multi-class
Valid range
at least 1 in the source; multi-class training needs 2 or more
Acts on
Sets K: the number of outputs per row, the softmax in g and h, and K trees per round unless multi_strategy changes that.
Watch for
It is required for multi:softmax and multi:softprob. The scikit-learn wrapper infers it from the labels.
Works with
objective, multi_strategy.
tweedie_variance_power
Default in 3.2.0
1.5
Valid range
(1, 2)
Acts on
The in the Tweedie g and h. It sets how the assumed variance grows with the mean.
Raising it
Toward 2, closer to gamma.
Lowering it
Toward 1, closer to Poisson.
Watch for
Only with reg:tweedie. Test RMSE moved little here, because RMSE is not the deviance that the objective optimizes, and the mean of predictions drifts from the mean of the labels as the power changes. Its default metric name includes the power, as in tweedie-nloglik@1.5.
Works with
objective, eval_metric.
Measured
setting
test RMSE
mean prediction
mean label
1.1
4.398
5.880
5.948
1.5
4.369
5.718
5.948
1.9
4.406
5.538
5.948
huber_slope
Default in 3.2.0
1
Valid range
non-zero. The source rejects 0.
Acts on
The in Pseudo-Huber's and . Below the loss acts like squared error, and above it like absolute error.
Raising it
More errors stay in the quadratic zone, so outliers pull harder, and it approaches squared error.
Lowering it
More rows sit in the linear zone, so outliers pull less, and h falls for every row.
Watch for
Pick on the scale of your target's errors. With labels far from 0 the estimated intercept breaks: on our rows, with labels near 57, it is 113664. The table sets base_score to the median for that reason.
Works with
base_score, eval_metric (mphe).
Measured
setting
clean test RMSE
clean test MAE
0.1
1.268
1.005
1
1.635
1.279
10
2.919
1.533
100
6.722
3.496
Measured
setting
clean test RMSE
clean test MAE
reg:squarederror
6.694
3.677
reg:absoluteerror
1.613
1.102
reg:pseudohubererror
1.635
1.279
quantile_alpha
Default in 3.2.0
none: training without it is an error
Valid range
scalar or list in (0, 1)
Acts on
The in the pinball g: above the label and below. It also picks which quantile the leaf refit uses.
Raising it
The model predicts a higher quantile, so more labels fall at or below the prediction.
Lowering it
It predicts a lower quantile.
Watch for
A list fits one output per quantile. On this data the share of labels below the prediction moves in the right direction but stays short of the target at the extremes, so check coverage on held-out data before trusting a band.
Works with
objective, tree_method (the refit needs hist or approx).
Measured
setting
share of test labels at or below the prediction
0.1
0.195
0.5
0.516
0.9
0.838
aft_loss_distribution
Default in 3.2.0
normal
Valid range
normal, logistic, extreme
Acts on
The noise distribution in the AFT likelihood, which sets its g and h.
Choices
Try the one that matches the shape of the noise on the log of the time. The generated noise here is normal, and normal scores best.
The in . On an uncensored row under normal, and , as the check script confirms.
Raising it
Noise is assumed wider, so h falls and each step is larger per unit of g.
Lowering it
Noise is assumed tighter, so h rises.
Watch for
The default is not stated in the docs; the value here is from save_config(). It is in the same table as the distribution above.
Works with
aft_loss_distribution.
lambdarank_pair_method
Default in 3.2.0
topk
Valid range
topk, mean
Acts on
Chooses which document pairs produce g and h in the ranking objectives.
Choices
topk pairs each document at the top of the model's ranking with the query's documents. mean samples pairs for each document.
Watch for
topk focuses on the top of the list, which is what a cut-off NDCG measures.
Works with
lambdarank_num_pair_per_sample.
Measured
setting
test NDCG@10
total leaves
topk
0.858
774
mean
0.863
671
lambdarank_num_pair_per_sample
Default in 3.2.0
not set, saved as 4294967295. The effective value is 32 for topk and 1 for mean, according to the source.
Valid range
[1, ∞)
Acts on
How many documents or pairs produce gradients per query. It is the truncation level for topk, and the pairs per document for mean.
Raising it
More pairs per query, at more cost.
Lowering it
Fewer. At 1 here, the ranking is clearly worse.
Watch for
To train for ndcg@6, the docs suggest 6 with topk.
Works with
lambdarank_pair_method.
Measured
setting
test NDCG@10
total leaves
1
0.831
708
5
0.860
738
10
0.848
755
30
0.858
774
lambdarank_normalization
Default in 3.2.0
true, saved as 1
Valid range
true or false
Acts on
Scales the g and h of a query: by with twice the sum of the query's lambda sizes under topk, and by the pair count under mean, according to the source.
Watch for
The docs say it can sometimes stagnate training. It made almost no difference here.
Works with
lambdarank_pair_method.
Measured
setting
test NDCG@10
total leaves
true
0.858
774
false
0.857
770
lambdarank_score_normalization
Default in 3.2.0
true, saved as 1
Valid range
true or false
Acts on
Divides the metric change of a pair by , so pairs already far apart count less.
Watch for
The docs say this acts like regularization and may prevent convergence. Turning it off helped slightly on this data.
Works with
objective.
Measured
setting
test NDCG@10
total leaves
true
0.858
774
false
0.863
778
lambdarank_unbiased
Default in 3.2.0
false, saved as 0
Valid range
true or false
Acts on
Estimates the ratios of position bias while training, and divides each pair's g and h by them, to remove the bias from click data.
Watch for
It is for click logs where position changes the chance of a click. Our generated relevance has no position bias, so a small loss here is expected.
Works with
lambdarank_bias_norm.
Measured
setting
test NDCG@10
total leaves
false
0.858
774
true
0.855
767
lambdarank_bias_norm
Default in 3.2.0
the docs say 2.0, but 3.2.0 saves 1
Valid range
at least 0
Acts on
The p of the normalization in position debiasing.
Watch for
The documentation and the library disagree on the default in 3.2.0. The source file ranking_utils.h agrees with the saved value. It is only used when lambdarank_unbiased is true.
Works with
lambdarank_unbiased.
Measured
setting
test NDCG@10
total leaves
0.5
0.860
769
1.0
0.855
767
2.0
0.853
774
ndcg_exp_gain
Default in 3.2.0
true, saved as 1
Valid range
true or false
Acts on
Sets the gain in NDCG deltas: when true, or when false.
Watch for
When true, the docs say relevance cannot exceed 31. Test NDCG@10 on this page uses the exponential gain, which favours the true setting.
Works with
objective, eval_metric.
Measured
setting
test NDCG@10
total leaves
true
0.858
774
false
0.852
782
Learning task parameters
These define what is being optimized and how it is scored. Section 6 gives the mathematics of each objective.
objective
Default in 3.2.0
reg:squarederror
Valid range
a name below or a callable
Acts on
Sets , so it sets g, h, the output transform, the default metric and how base_score is estimated.
The scale of g and h changes with it, so gamma, min_child_weight and alpha values do not carry over between objectives. rank:map needs binary labels. The first table has outliers in the training labels and scores on clean test rows.
Works with
Almost everything.
Measured
setting
clean test RMSE
clean test MAE
reg:squarederror
6.694
3.677
reg:absoluteerror
1.613
1.102
reg:pseudohubererror
1.635
1.279
Measured
setting
test NDCG@10
total leaves
rank:ndcg
0.858
774
rank:pairwise
0.856
770
rank:map
0.832
757
base_score
Default in 3.2.0
estimated from the training labels. Saved per model.
Valid range
a number, or a list for several outputs, in the output's scale
Acts on
, the prediction before any tree. It is in output space, so a logistic objective converts it with the logit before use. If any base_margin is supplied, it is not used.
Raising it
Only the starting point moves. Early rounds must undo a bad start.
Lowering it
The same, in the other direction.
Watch for
With enough rounds it fades away: the table shows a poor start costs a lot at round 1 and little at round 500. Setting a value turns off the estimate. See boost_from_average. The estimate for Pseudo-Huber can be far off, so set it by hand there.
depends on the objective (for example rmse, logloss, mlogloss)
Valid range
a name, a list of names, or a callable in the wrapper
Acts on
Measures progress only. It never enters g, h or the gain. But early stopping uses it to choose when to stop.
Choices
Regression: rmse, rmsle, mae, mape, mphe. Classification: logloss, error, error@t, merror, mlogloss, auc, aucpr. Ranking: pre, ndcg, map, with cut-offs ndcg@n, map@n, pre@n, and with ndcg-, map-, ndcg@n-, map@n- to score empty lists as 0. Counts, survival and distributions: poisson-nloglik, gamma-nloglik, cox-nloglik, gamma-deviance, tweedie-nloglik, aft-nloglik, interval-regression-accuracy.
Watch for
When there are several metrics, the last one drives early stopping. In the wrapper a callable metric is assumed to be a cost, so it is minimized unless you say otherwise.
Sets the random generator that draws rows, columns, and dropped trees. It enters no formula.
Choices
Changing it gives a different draw. The first row shows that it changes nothing when no setting draws a sample of rows or features. Even with sampling, the change is small here.
Watch for
Identical results also need the same thread count and data order. The shotgun linear updater is nondeterministic.
Works with
subsample, colsample_*, rate_drop.
Measured
setting
seed 0
seed 1
seed 2
no sampling
1.268
1.268
1.268
subsample 0.5
1.249
1.249
1.235
seed_per_iteration
Default in 3.2.0
false, saved as 0
Valid range
true or false
Acts on
Reseeds the random generator from the round number, each round.
Choices
With subsample=0.5 on this data, test RMSE is 1.249 with false and 1.227 with true.
Watch for
Rarely needed. It changes the draws, so results differ from a run without it.
Works with
seed, subsample.
boost_from_average
internal
Default in 3.2.0
1, turned off when you set base_score
Valid range
0 or 1
Acts on
A saved flag that records whether was estimated.
Watch for
You do not set it. It is listed because save_config() shows it.
Works with
base_score.
num_target
internal
Default in 3.2.0
1, or the label columns
Valid range
at least 1
Acts on
The number of target columns, set from the data.
Watch for
You do not set it.
Works with
multi_strategy.
num_feature
internal
Default in 3.2.0
set from the data
Valid range
at least 1
Acts on
The number of feature columns.
Watch for
You do not set it.
Works with
Data.
num_trees
internal
Default in 3.2.0
set by training
Valid range
non-negative integer
Acts on
The count of trees in the model, saved in the config. It is K times num_parallel_tree times the rounds, for scalar trees.
Watch for
You do not set it.
Works with
n_estimators.
specified_updater
internal
Default in 3.2.0
False
Valid range
true or false
Acts on
A saved flag that records whether you set updater by hand.
Watch for
You do not set it.
Works with
updater.
Parameters of the scikit-learn wrapper
The wrapper classes are XGBRegressor, XGBClassifier, XGBRanker, XGBRFRegressor and XGBRFClassifier. Their constructor defaults are None and they pass only what you set, so the booster's defaults above apply. The check script confirms get_params() gives None before fit. The RF wrappers change four defaults: learning_rate1, subsample0.8, colsample_bynode0.8, reg_lambda1e−05. They put n_estimators into num_parallel_tree for a single round. These entries exist only in the wrapper or take a wrapper-specific form.
importance_type
Default in 3.2.0
None, which gives average gain for trees
Valid range
gain, weight, cover, total_gain, total_cover; only weight for linear
Acts on
Sets what feature_importances_ reports. Does not change the fitted trees.
Choices
See section 10. Our two-tree model reports 0.8449 and 0.1551 for study and sleep: average gain, normalized to sum to 1.
Watch for
The gain type favours features that are used late, in few splits, and cover and weight can rank the same model's features differently.
Works with
booster.
sample_weight
Default in 3.2.0
None
Valid range
one non-negative number per row
Acts on
A fit argument. It multiplies g and h of each row, so a weight of 2 equals repeating the row, as the check script confirms.
Watch for
It enters min_child_weight through h. In ranking, weights are per group rather than per row, according to the DMatrix docs.
Works with
scale_pos_weight, min_child_weight, weight.
eval_set
Default in 3.2.0
None
Valid range
list of (X, y) pairs
Acts on
A fit argument: the data for monitoring. It never enters g or h.
Watch for
The last set drives early stopping. Do not tune against your final test data, or its score stops being honest.
Works with
early_stopping_rounds, eval_metric, evals.
sample_weight_eval_set
Default in 3.2.0
None
Valid range
list of per-row weights
Acts on
A fit argument: weights for the metric on each evaluation set. Does not change the fitted trees. It can change when early stopping fires.
Works with
eval_set.
base_margin_eval_set
Default in 3.2.0
None
Valid range
list of margins
Acts on
A fit argument: margins to start from when scoring each evaluation set, matching any training base_margin.
Watch for
Forgetting it gives misleading validation scores for a model trained with margins.
Works with
base_margin, eval_set.
verbose
Default in 3.2.0
True (False for XGBRanker)
Valid range
bool or integer
Acts on
A fit argument: print the metric each round or every n rounds. Does not change the fitted trees.
Works with
verbose_eval, eval_set.
eval_group
Default in 3.2.0
None
Valid range
list of group arrays
Acts on
An XGBRanker.fit argument: query groups of each evaluation set.
Works with
eval_qid, group.
eval_qid
Default in 3.2.0
None
Valid range
list of query id arrays
Acts on
An XGBRanker.fit argument: query ids of each evaluation set.
Works with
eval_group, qid.
Training-loop arguments
These belong to xgboost.train, xgboost.cv and the callback classes. They control the loop around the trees rather than any one tree.
num_boost_round
alias n_estimators
Default in 3.2.0
10 in xgboost.train; None in the wrapper, which then uses 100
Valid range
positive integer. n_estimators is the wrapper's name.
Acts on
, the number of rounds in .
Raising it
Train error keeps falling. Test error falls, flattens, and eventually rises.
Lowering it
Less fitting, and faster training and prediction.
Watch for
Choose it with early stopping rather than by hand. In the table, one 1000-round model is read at several points with iteration_range.
Works with
eta, early_stopping_rounds.
Measured
setting
train RMSE
test RMSE
10
2.899
3.083
50
1.156
1.508
100
0.842
1.297
200
0.673
1.268
500
0.373
1.265
1000
0.160
1.277
early_stopping_rounds
Default in 3.2.0
None
Valid range
positive integer
Acts on
Stops the loop when the last metric on the last evaluation set has not improved for this many rounds. Trees are still fitted during the wait.
Raising it
More patience: more trees trained after the best one, at more cost. In the table it trains more trees and finds the same best iteration.
Lowering it
Less patience: a noisy dip can stop the run early.
Watch for
In the wrapper, predict stops at best_iteration (28 here). But the booster keeps all 39 trees, and Booster.predict uses all of them unless told otherwise. Test RMSE was 1.6887 at the best iteration and 1.6916 with all trees. Its best score was 1.6887.
Works with
eval_metric, eval_set, evals, eta, save_best.
Measured
setting
best_iteration
trees trained
best test RMSE
5
28
34
1.689
10
28
39
1.689
50
28
79
1.689
200
28
229
1.689
evals
Default in 3.2.0
None
Valid range
list of (DMatrix, name) pairs
Acts on
The xgboost.train form of eval_set. It never enters g or h.
Watch for
The last entry drives early stopping.
Works with
eval_set, evals_result.
evals_result
Default in 3.2.0
None
Valid range
an empty dict
Acts on
An output argument of xgboost.train: it fills with the metric history. Does not change the fitted trees.
Works with
evals.
obj
Default in 3.2.0
None
Valid range
a function from predictions and DMatrix to (g, h)
Acts on
Replaces the built-in g and h. This is the mechanism of section 4: the builder needs only these two arrays. A custom squared error gave the same dump as the built-in one.
Watch for
It receives raw margins, so apply any link yourself. With no base_score, a custom objective here saved an intercept of 0.5000, so set it yourself when you need a particular start.
Works with
custom_metric, base_score.
custom_metric
Default in 3.2.0
None
Valid range
a function from predictions and DMatrix to (name, value)
Acts on
Adds your own metric to the evaluation list. Does not change the fitted trees.
Watch for
The docs say it receives the transformed predictions, unlike obj, so check which one you get.
Works with
maximize, early_stopping_rounds.
maximize
Default in 3.2.0
None
Valid range
bool
Acts on
Tells the early-stopping logic that a larger value of a custom metric is better. Does not change the fitted trees.
Watch for
Getting this wrong stops at the worst model. EarlyStopping has its own argument of the same name.
Works with
custom_metric, early_stopping_rounds.
verbose_eval
Default in 3.2.0
True in train; None in cv
Valid range
bool or integer
Acts on
Prints the metric each round or every n rounds. Does not change the fitted trees.
Works with
evals, verbose.
xgb_model
Default in 3.2.0
None
Valid range
Booster, wrapper model, or path
Acts on
Starts from existing trees: becomes that model's output, so new trees fit what it left. Training 10 rounds and then 5 more gave 15 rounds in the check.
Watch for
Parameters should be compatible with the original model. It is also how process_type=update gets its trees.
Works with
process_type, n_estimators.
callbacks
Default in 3.2.0
None
Valid range
list of TrainingCallback
Acts on
Runs code around each round, as below. It can change eta or stop the run, but not the gain.
Watch for
The docs say callbacks keep state, so create a new one for each training run.
Works with
The next entries.
nfold
Default in 3.2.0
3
Valid range
integer ≥ 2
Acts on
An xgboost.cv argument: the number of folds. It changes which rows each model trains on.
Works with
folds, stratified.
stratified
Default in 3.2.0
False
Valid range
bool
Acts on
An xgboost.cv argument: keep class ratios in each fold.
Watch for
Worth setting for imbalanced classes.
Works with
nfold.
folds
Default in 3.2.0
None
Valid range
KFold object or list of (in, out) indices
Acts on
An xgboost.cv argument: your own fold assignment.
Works with
nfold.
metrics
Default in 3.2.0
()
Valid range
string or list
Acts on
An xgboost.cv argument: metrics to report, the same as eval_metric.
Works with
eval_metric.
fpreproc
Default in 3.2.0
None
Valid range
function
Acts on
An xgboost.cv argument: a function that transforms each fold's train, test and params, such as setting scale_pos_weight per fold.
Works with
nfold.
as_pandas
Default in 3.2.0
True
Valid range
bool
Acts on
An xgboost.cv argument: return a DataFrame when pandas is installed. Does not change the fitted trees.
show_stdv
Default in 3.2.0
True
Valid range
bool
Acts on
An xgboost.cv and EvaluationMonitor argument: print the fold standard deviation. Does not change the fitted trees.
shuffle
Default in 3.2.0
True
Valid range
bool
Acts on
An xgboost.cv argument: shuffle rows before making folds.
Works with
seed.
rounds
Default in 3.2.0
required
Valid range
positive integer
Acts on
An EarlyStopping argument: the patience, the same as early_stopping_rounds.
Works with
save_best, min_delta.
metric_name
Default in 3.2.0
None, which means the last metric
Valid range
string
Acts on
An EarlyStopping argument: which metric to watch.
Works with
eval_metric.
data_name
Default in 3.2.0
None, which means the last set
Valid range
string
Acts on
An EarlyStopping argument: which evaluation set to watch.
Works with
evals.
save_best
Default in 3.2.0
False
Valid range
bool
Acts on
An EarlyStopping argument: cut the returned model at the best iteration. Here the booster kept 29 rounds, with best_iteration28.
Watch for
The docs say it applies to tree boosters only, and not to cv.
Works with
early_stopping_rounds.
min_delta
Default in 3.2.0
0
Valid range
non-negative number
Acts on
An EarlyStopping argument: the smallest change that counts as an improvement.
Raising it
Tiny improvements no longer reset the patience.
Works with
rounds.
learning_rates
Default in 3.2.0
required
Valid range
function of the round, or a sequence
Acts on
A LearningRateScheduler argument: sets eta per round.
Works with
eta.
rank
Default in 3.2.0
0
Valid range
integer
Acts on
An EvaluationMonitor argument: which worker prints. Does not change the fitted trees.
period
Default in 3.2.0
1
Valid range
positive integer
Acts on
An EvaluationMonitor argument: how many rounds between prints. Does not change the fitted trees.
logger
Default in 3.2.0
communicator_print
Valid range
callable
Acts on
An EvaluationMonitor argument: the function that receives the text. Does not change the fitted trees.
directory
Default in 3.2.0
required
Valid range
path
Acts on
A TrainingCheckPoint argument: the output folder. Does not change the fitted trees.
name
Default in 3.2.0
model
Valid range
string
Acts on
A TrainingCheckPoint argument: the pattern for file names. Does not change the fitted trees.
as_pickle
Default in 3.2.0
False
Valid range
bool
Acts on
A TrainingCheckPoint argument: pickle the Booster with its parameters rather than saving only the model. Does not change the fitted trees.
interval
Default in 3.2.0
100
Valid range
positive integer
Acts on
A TrainingCheckPoint argument: rounds between checkpoints. Does not change the fitted trees.
Watch for
The docs warn that checkpointing is slow.
Data arguments
These are arguments of DMatrix and QuantileDMatrix. Some also exist on the wrapper's constructor or fit. They change the rows that the formulas see, so some of them do change the model.
weight
Default in 3.2.0
None
Valid range
one non-negative number per row
Acts on
The DMatrix form of sample_weight. It multiplies g and h per row.
Works with
sample_weight.
base_margin
Default in 3.2.0
None
Valid range
one margin per row and output
Acts on
Replaces for each row, in margin space, and turns off base_score. The objective checks in section 6 start from this with margin 0.
Watch for
It must be supplied again at prediction, and the margin is not added back automatically unless you pass it there.
Works with
base_score, base_margin_eval_set.
missing
Default in 3.2.0
numpy.nan
Valid range
a float
Acts on
Marks which entries take the default direction in the scan (section 9).
Watch for
A sentinel such as -999 gives a different model unless you declare it. When declared, the dumps are identical to using NaN, as the check script confirms.
Works with
default_direction, tree_method.
silent
removed
Default in 3.2.0
False on DMatrix
Valid range
bool
Acts on
The Booster parameter silent was removed, and the library warns it is not used: use verbosity. The DMatrix argument with this name still exists, for messages during construction.
Works with
verbosity.
feature_names
Default in 3.2.0
None
Valid range
list of strings
Acts on
Names shown in dumps and importance. Does not change the fitted trees.
Watch for
Prediction validates names when validate_features is true.
Works with
validate_features.
feature_types
Default in 3.2.0
None
Valid range
list of c, q, int, i
Acts on
c marks a categorical column, which changes the split form. The docs say other types do not affect the model.
Works with
enable_categorical, max_cat_to_onehot.
enable_categorical
experimental
Default in 3.2.0
False
Valid range
bool
Acts on
Allows categorical splits on columns marked c, or on categorical DataFrame columns.
Watch for
The docs mark it experimental. exact rejects it, as the check script confirms.
Works with
feature_types, tree_method.
feature_weights
Default in 3.2.0
None
Valid range
one positive number per feature
Acts on
Sets each feature's chance of being selected when colsample_* samples. It has no effect without column sampling.
Raising it
A feature with a larger weight is drawn more often. In the table, a weight of 10 on feature 0 raises its share of splits.
Watch for
The wrapper raises ValueError for non-positive values, according to its docs.
Defines the queries for ranking objectives, as qid does in another form.
Works with
qid, objective.
qid
Default in 3.2.0
None
Valid range
one query id per row
Acts on
Defines the queries. Pairs that make g and h are only formed inside a query.
Works with
group, objective.
label_lower_bound
Default in 3.2.0
None
Valid range
one number per row
Acts on
The lower end of each survival interval for survival:aft.
Works with
label_upper_bound.
label_upper_bound
Default in 3.2.0
None
Valid range
one number per row, may be infinite
Acts on
The upper end of each survival interval. An infinite value means right-censored. Equal bounds mean an exact event time.
Works with
label_lower_bound.
data_split_mode
Default in 3.2.0
DataSplitMode.ROW
Valid range
ROW, COL
Acts on
For distributed data: whether workers hold rows or columns. Not exercised on this page.
Works with
Distributed training.
ref
Default in 3.2.0
None
Valid range
a training DMatrix
Acts on
A QuantileDMatrix argument: reuse the cut points of the training data on validation data.
Watch for
The docs warn that without it, validation data is binned on different cuts and information may be lost.
Works with
max_bin.
max_quantile_batches
experimental
Default in 3.2.0
None
Valid range
positive integer
Acts on
A QuantileDMatrix argument for GPU data from an iterator: limits batches per sub-stream, to bound memory. Not exercised on this page.
Works with
max_bin.
Prediction arguments
These belong to Booster.predict and the wrapper's predict and predict_proba. They run after training, so none changes the model.
output_margin
Default in 3.2.0
False
Valid range
bool
Acts on
Returns before the link function. Section 8 uses it to compare hand margins with the model.
pred_leaf
Default in 3.2.0
False
Valid range
bool
Acts on
Returns the leaf index of each row in each tree.
pred_contribs
Default in 3.2.0
False
Valid range
bool
Acts on
Returns SHAP values, one per feature plus a bias column, that sum to the margin, according to the docs.
approx_contribs
Default in 3.2.0
False
Valid range
bool
Acts on
Uses a faster approximation for contributions. The docs do not recommend changing it.
pred_interactions
Default in 3.2.0
False
Valid range
bool
Acts on
Returns the SHAP values of feature pairs, according to the docs.
validate_features
Default in 3.2.0
True
Valid range
bool
Acts on
Checks that the data's feature names match the model's.
training
Default in 3.2.0
False
Valid range
bool
Acts on
Matters only for DART: true applies dropout during prediction. Two calls differed by a mean of 1.835 here.
Watch for
Leave it false for inference.
Works with
booster.
iteration_range
Default in 3.2.0
(0, 0), which means all trees in Booster.predict
Valid range
(begin, end) in rounds
Acts on
Uses only these rounds, half open. The wrapper defaults to best_iteration after early stopping.
Works with
early_stopping_rounds.
strict_shape
Default in 3.2.0
False
Valid range
bool
Acts on
Keeps the output shape fixed at (rows, outputs) even for one output.
Removed parameters
These names appear in older tutorials and code. In 3.2.0, none of them does anything. Each was passed to xgboost.train with validation on, and each drew the library's warning that it was not used, so a typo in one of these names cannot pass silently under the default settings. silent is listed in the Data group, because its DMatrix argument remains.
n_gpus
removed
Default in 3.2.0
none
Acts on
Used to set GPU count. Use device.
gpu_id
removed
Default in 3.2.0
none
Acts on
Used to choose a GPU. Use device="cuda:<ordinal>".
predictor
removed
Default in 3.2.0
none
Acts on
Used to choose the prediction backend. It now follows device.
gpu_page_size
removed
Default in 3.2.0
none
Acts on
Used for GPU external memory.
single_precision_histogram
removed
Default in 3.2.0
none
Acts on
Used to reduce histogram memory.
sketch_eps
removed
Default in 3.2.0
none
Acts on
Used to set sketch accuracy. Use max_bin.
sketch_ratio
removed
Default in 3.2.0
none
Acts on
Used by the older sketch code. Use max_bin.
enable_feature_grouping
removed
Default in 3.2.0
none
Acts on
Used to bundle sparse features in the older histogram code.
num_pbuffer
removed
Default in 3.2.0
none
Acts on
Used by an old prediction buffer.
max_conflict_rate
removed
Default in 3.2.0
none
Acts on
Used with feature grouping.
max_search_group
removed
Default in 3.2.0
none
Acts on
Used with feature grouping.
deterministic_histogram
removed
Default in 3.2.0
none
Acts on
Used by GPU hist.
dsplit
removed
Default in 3.2.0
none
Acts on
Used to choose the distributed split. See data_split_mode.
13Effects, measured
This figure draws the tables from section 12 so you can compare their shapes. Every sweep fixes all other parameters and changes one. Most use make_friedman1 with 3000 rows, split into 2000 for training and 1000 for testing. Numbers were measured on generated data with one thread and a fixed seed. They show the direction and size on this data. They don't predict the best value for yours.
Parameter sweeps
Fig. 13.1 · interactive
Each line has its own vertical scale, so compare shapes and read the values from the table. The settings are evenly spaced, even where their values are not.
A few patterns repeat across these tables, and they follow from the formulas:
lambda shrinks leaves without removing them. Total leaves barely change as it grows, because it lowers weights and gains together. gamma, alpha and min_child_weight do remove leaves.
Train error always moves with capacity, and test error has a minimum.max_depth shows this most clearly: train error goes close to 0 at depth 12, and test error is best at about depth 3 or 4.
Eta and rounds trade against each other. Eta 0.01 at 200 rounds has not finished learning, and eta 1 has overfit by then.
14The order to tune things in
Everything in this section is advice from common practice. Unlike the rest of this page, it can't be checked by a script, and it won't suit every dataset.
Get the objective and metric right first. They define what the model is for. A metric that doesn't match the business goal makes all the later tuning pointless.
Set up a validation scheme that mimics production. Use a split by time if the data changes over time. Random splits on such data make every setting look better than it is.
Fix eta at about 0.1 and use early stopping for the rounds. Set rounds high and let early_stopping_rounds choose. This takes one dimension out of the search.
Tune tree size next:max_depth and min_child_weight. These usually matter most. Try depth from 3 to 8. Set min_child_weight with the scale of in mind, as in section 8.
Then add randomness:subsample and colsample_bytree, about 0.5 to 1. They often help a little, and they're cheap.
Then regularize:lambda, alpha and gamma. Search on a log scale. The useful scale depends on and , so a value from another problem means little.
Finally, lower eta (for example to 0.03), and let early stopping raise the rounds. It usually gains a little at a linear cost in time.
For imbalanced classes, decide first whether you need calibrated probabilities. If you do, avoid scale_pos_weight. If you need ordering or a shifted threshold, it's a reasonable tool. Leave max_delta_step at 0 unless updates are unstable.
Most of the parameters in section 12 should stay at their defaults for most work. If a default is doing badly, the reason is usually the data, the features or the validation, not a hyperparameter.
Name the parameter by the term that it changes
Each parameter in section 12 acts on one part of the formulas. To name the parameter for an effect, first find the term that the effect needs.
Find the term. A node at a fixed depth must become a leaf, whatever its gain. That is a stopping rule in the growth loop.
Find the parameter.max_depth sets that rule. gamma would compare the gain instead.
15How this page was checked
Running python assets/xgboost-lesson/verify_xgboost_lesson.py recomputes each number here with xgboost 3.2.0 and fails loudly if any differs. It does these things:
It rebuilds both worked examples by hand, and requires every gain, threshold, cover and leaf to match real dumped trees.
It compares every tagged number on the page with its recomputed value, and it fails if a recomputed value has no place on the page, or if the page tags a number nothing computes.
It grows trees from each and formula with a custom objective, and requires the same trees as the built-in objective.
It enumerates all 143 parameter names from the library, and fails if any has no entry. It compares the defaults with save_config().
It passes each removed parameter to the library and requires the "not used" warning.
It runs this page's tree engine in Node on 220 settings, and compares it with xgboost.
What could not be verified
No GPU run. device, use_rmm, use_cuda_async_pool and max_quantile_batches are described from the docs only.
Some internal names have no documentation, such as opt_dense_col and sparse_threshold. Their entries come from the one-line descriptions in the source, and they were not measured.
The full code for the NDCG and MAP deltas was read from source but not tested by formula. The same is true of the AFT gradients for censored rows.
The sketch figure shows the ideal equal-weight rule, not the library's approximate sketch.
Where the docs and library disagree, the page says so. The two found are lambdarank_bias_norm, where the docs say 2 and the library saves 1, and max_cached_hist_node, where the docs say 65536 and the library saves 18446744073709551615 as a "not set" marker, which the source then resolves to 65536 on CPU.
Last run on 4 November 2026 with xgboost 3.2.0, scikit-learn 1.8.0 and NumPy 2.4.4: 7,346 checks passed and 0 failed.
16Misconceptions
Each tree fits the residuals.
That is only literally true for squared error. In general, each tree minimizes the quadratic built from and . Under logistic loss, nothing is fitted to directly.
min_child_weight is the minimum rows in a leaf.
It is a minimum sum of . It equals a row count only under squared error, and it can need many rows under logistic, as section 8 shows.
Gamma is on the scale of the paper's gain.
The library compares gamma with the gain without the one half. Its size also depends on the loss scale, so a good value for one target means nothing on another.
Eta changes which split is chosen.
Eta isn't in the gain formula. The first tree's structure doesn't depend on it at all. It affects later trees only through the predictions it leaves behind.
lambda prunes the tree.
It rarely does. It shrinks weights and gains together, and total leaves barely change in the measured table. Use gamma or alpha if you want fewer leaves.
All tree methods give the same pruning under gamma.
exact prunes bottom-up after growing, and hist and approx stop at the node. On XOR-like data, this gives 4 leaves versus 1.
reg:absoluteerror behaves the same under every tree method.
Leaves are refit to quantiles only under hist and approx. exact keeps the placeholder values, with no warning.
After early stopping, all prediction calls use the best round.
The wrapper's predict does. Booster.predict uses every tree unless you pass iteration_range.
Cover is a count of rows.
Cover is . Under logistic loss, a node of 4 rows can have a cover below 1.
17Exercises
Check yourself on everything
Ten closed-book questions over the whole lesson, in mixed order. Try them without scrolling back. Each missed question names the part to review.
A multi-part problem
One new setting, five parts. Each part uses one skill from an earlier section.
Open exercises
Try each exercise before you open its answer.
Exercise 1
The right child in section 7 has G = −60 and H = 4. Without looking, compute the gain of the split that isolates row 6 (g = 2.5) with lambda 1.
Show the answer
Row 6 has and . The others have and . The three scores are 3.1250, 976.5625 and 720.0000. So the gain is 3.1250 + 976.5625 − 720.0000 = 259.6875, which is the dumped gain.
Exercise 2
Under binary:logistic at and , what are g and h? What weight does the Newton step give this one row, with lambda 0?
Show the answer
and . The weight is . Because h is small, a confident and correct row alone would get a large step in margin. That is why and the other rows in the leaf matter.
Exercise 3
You raise min_child_weight from 1 to 5 on a squared-error model and on a logistic model with rare positives. Which one changes more, and why?
Show the answer
The logistic model changes more. With rare positives, most is near 0, so is small and a child needs many rows to reach 5. Under squared error, it only needs 5 rows.
Exercise 4
Predict what happens with alpha=70 on the running rows with depth 1 and lambda 1.
Show the answer
Both children of the best split have , which is below 70. So each child's score is 0 and the gain is 0, and no split happens. The root has , so it stays a single leaf of 0. Check this in Fig. 7.1 with depth 1 and alpha 70.
Exercise 5
Why does doubling every sample weight change a model when lambda is 1, but not change it when lambda is 0 and gamma and min_child_weight are 0?
Show the answer
Weights multiply and , so and double. With , the weight is unchanged, and gains double, which doesn't change which is largest. With , differs from , so regularization has effectively weakened.
Exercise 6
A colleague sets num_parallel_tree=10 and leaves subsample and the colsample settings at 1. What do they get?
Show the answer
Ten identical trees per round, since nothing differs between them, each with eta divided by 10. The prediction is the same as one tree, and the cost is ten times as high.
18Summary
Model
Form
Training
Trees are added one at a time, and older trees stay frozen.
Objective
Loss
Second-order Taylor, so only and remain.
Penalty
Leaf
Weight
, then clip, then multiply by .
Score
Split
Gain
Children's scores minus the parent's score. The library has no one half.
Valid if
min_child_weight, and gain reaches gamma.
Candidates
exact
Every gap.
approx, hist
max_bin cuts, using weights and one sketch respectively.
One round
Order
Margins, then g and h. For each tree: rows, then features. For each node: candidates, scores, then the gamma, depth and leaf rules. Then add the trees to the margins and evaluate.
Predict
Start value, plus one leaf per tree, then the link.
Knobs by mechanism
Change sums
subsample, weights, scale_pos_weight.
Change candidates
colsample_*, max_bin, constraints.
Change formula
lambda, alpha, max_delta_step.
Stop growth
max_depth, max_leaves, gamma, min_child_weight.
19Review cards
Come back to these after about a day, a week, and a month. Try to answer each question before you open it. The same cards are below as Question::Answer lines that you can copy into a note tagged #flashcards for Obsidian's Spaced Repetition plugin.
What two numbers per row does the tree builder see?
and , the first and second derivatives of the loss at the current prediction.
What is the optimal leaf weight?
, soft-thresholded by alpha, clipped by max_delta_step, and then multiplied by eta when stored.
What does get_dump's gain equal?
. No one half, and no gamma subtracted.
What is cover in a dump?
The sum of for the node's rows, which is the node's .
With gamma 0, why can a node stay a leaf when all its g values have the same sign?
Lambda penalizes each smaller child more, so the children's scores can total less than the parent's.
How does alpha differ from lambda?
Alpha moves toward zero and can make a leaf exactly 0 or remove a split. Lambda adds to and only shrinks.
Why is h the sketch weight?
Completing the square turns the objective into least squares with target and weight .
Difference between hist and approx?
hist sketches once, with user weights only. approx sketches before each tree, with as the weight.
What does count:poisson do with max_delta_step?
It sets 0.7 by default, and the value both raises by and clips leaf weights.
How do L1-type objectives get leaf values?
Grow with , then under hist or approx replace each leaf by eta times the residual quantile. exact skips this step.
How does gamma behave under exact and hist?
exact grows then prunes bottom-up, so strong children can save a weak parent. hist stops at the weak node.
What does num_parallel_tree do to eta?
It divides it among the trees of the round.
What does min_child_weight compare with?
Each child's sum of h, so under logistic loss it can need many rows.
Where do rows with a missing value go at a split?
To the default direction, learned by trying them on both sides and keeping the larger gain.
In one hist round, which comes first: g and h, the tree's row sample, or the tree's feature sample?
g and h for every row, then each tree's row sample, then that tree's feature sample. The features for each depth and each node are drawn later, as nodes are scored.
Under hist, where does a missing value go at a split that had no missing values in training?
Right. The scan with missing rows on the left runs only when the node has missing values for that feature.
Does eta change the first tree's splits?
No. Eta is not in the gain, so it changes later trees only through the predictions that it leaves behind.
What two numbers per row does the tree builder see?::g and h, the first and second derivatives of the loss at the current prediction.
What is the optimal leaf weight?::−G/(H + λ), soft-thresholded by alpha, clipped by max_delta_step, and then multiplied by eta when stored.
What does get_dump's gain equal?::GL²/(HL + λ) + GR²/(HR + λ) − G²/(H + λ). No one half, and no gamma subtracted.
What is cover in a dump?::The sum of h for the node's rows, which is the node's H.
With gamma 0, why can a node stay a leaf when all its g values have the same sign?::Lambda penalizes each smaller child more, so the children's scores can total less than the parent's.
How does alpha differ from lambda?::Alpha moves G toward zero and can make a leaf exactly 0 or remove a split. Lambda adds to H and only shrinks.
Why is h the sketch weight?::Completing the square turns the objective into least squares with target −g/h and weight h.
Difference between hist and approx?::hist sketches once, with user weights only. approx sketches before each tree, with h as the weight.
What does count:poisson do with max_delta_step?::It sets 0.7 by default, and the value both raises h by e^δ and clips leaf weights.
How do L1-type objectives get leaf values?::Grow with h = 1, then under hist or approx replace each leaf by eta times the residual quantile. exact skips this step.
How does gamma behave under exact and hist?::exact grows then prunes bottom-up, so strong children can save a weak parent. hist stops at the weak node.
What does num_parallel_tree do to eta?::It divides it among the trees of the round.
What does min_child_weight compare with?::Each child's sum of h, so under logistic loss it can need many rows.
Where do rows with a missing value go at a split?::To the default direction, learned by trying them on both sides and keeping the larger gain.
In one hist round, which comes first: g and h, the tree's row sample, or the tree's feature sample?::g and h for every row, then each tree's row sample, then that tree's feature sample. The features for each depth and each node are drawn later, as nodes are scored.
Under hist, where does a missing value go at a split that had no missing values in training?::Right. The scan with missing rows on the left runs only when the node has missing values for that feature.
Does eta change the first tree's splits?::No. Eta is not in the gain, so it changes later trees only through the predictions that it leaves behind.