Lesson · xgboost 3.2.0 · scikit-learn 1.8.0 · NumPy 2.4.4

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
  1. Start here
  2. The running rows
  3. A sum of trees
  4. The objective
  5. Taylor expansion
  6. Leaf weight and gain
  7. g and h per objective
  8. Two trees by hand
  9. Logistic by hand
  10. Finding splits
  11. Growth and the rest
  12. One run, end to end
  13. Every hyperparameter
  14. Effects, measured
  15. Order of tuning
  16. How this was checked
  17. Misconceptions
  18. Exercises
  19. Summary
  20. Review cards
  21. Sources
Audience
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.

rowstudy x1sleep x2score ypass
118350
225400
337501
444450
558701
665550
776801
887851

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 loss l 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, F0, and adds trees one at a time. Each tree ft maps a row to a leaf weight. The prediction after t trees is the sum so far:

Ft(x)=Ft−1(x)+ηft(x),FK(x)=F0+η∑t=1Kft(x)

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, y−Ft−1(x). 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 t 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:

  1. Read the weight. Row 1 goes to a leaf whose weight is w = −12.
  2. Shrink it by eta. The step is 0.3 × (−12) = −3.6.
  3. 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.

Obj=∑i=1nl(yi,y^i)+∑t=1KΩ(ft)

The penalty is where XGBoost differs most from plain gradient boosting. For a tree with T leaves and weights w1,…,wT, it is

Ω(f)=γT+12λ∑j=1Twj2+α∑j=1T|wj|
  • γ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 l=12(y−y^)2 on our rows, with η=1 and λ=1. 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:

  1. Price the leaves. γT = 0 × 2 = 0.
  2. Price the sizes. ½λΣw² = ½ × 1 × ((−12)² + 12²) = 144, and the alpha term is 0.
  3. Add the loss. The objective is 361 + 144 = 505.

4Taylor expansion: reduce any loss to two numbers

When tree t 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.

Obj(t)=∑i=1nl(yi,y^i(t−1)+ft(xi))+Ω(ft)+constant

For squared error, this is easy to minimize. For a general loss it is not, because ft sits inside l. So XGBoost replaces l 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 y^ be its current prediction, and let w be the amount that this tree will add. We want l(y,y^+w) as a function of w.

Step 2. For any smooth function, a Taylor expansion of second order around a point gives:

l(y,y^+w)≈l(y,y^)+w∂l∂y^+12w2∂2l∂y^2

Step 3. Name the two derivatives. They are evaluated at the current prediction, so they are just numbers for this row:

gi=∂l(yi,y^i)∂y^i,hi=∂2l(yi,y^i)∂y^i2

Step 4. Substitute this into the objective, and drop l(yi,y^i), because it does not depend on the new tree:

Obj(t)≈∑i=1n[gift(xi)+12hift(xi)2]+Ω(ft)

Look at what happened to the loss. It is gone. All that remains of it is two numbers per row, gi and hi. 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, −g/h, 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 h 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, g is the prediction minus y, and h=1. Under logistic loss with probability p, g=p−y and h=p(1−p). Section 6 derives these and the formulas for the other objectives.

  1. Squared error, row 1. The prediction is 57.5 and y = 35, so g = 22.5 and h = 1.
  2. 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 wj. So we can group the sum by leaf. Let Ij be the rows in leaf j, and define the two sums that run the whole algorithm:

Gj=∑i∈Ijgi,Hj=∑i∈Ijhi

The best weight for a leaf

Step 1. Add in Ω with α=0 for now. Inside leaf j, every ft(xi) equals wj, so:

Obj(t)=∑j=1T[Gjwj+12(Hj+λ)wj2]+γT

Step 2. Each bracket depends on one wj only, so each leaf can be solved separately. Each bracket is a parabola, and we can set its derivative to 0:

Gj+(Hj+λ)wj=0

Step 3. Solve for the weight:

wj*=−GjHj+λ

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, h=1 and g is minus the residual, so w* is the mean residual in the leaf, pulled toward zero by λ. With λ=0, it would be exactly that mean.

The tree then stores ηw*, as section 2 showed. Here is the left child of the first split in section 7:

  1. Sum the rows. The child has G = 60 and H = 4.
  2. Take the Newton step. w = −60/(4 + 1) = −12.
  3. Store eta times it. With eta 0.3, the leaf stores −3.6.

The score of a leaf

Put wj* back into the bracket to see how low the objective goes for that leaf:

Gjwj*+12(Hj+λ)(wj*)2=−12Gj2Hj+λ

So for a whole tree with a fixed structure, the objective is

Obj*=−12∑j=1TGj2Hj+λ+γT

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 |Gj| is large compared with Hj.

The gain of a split

Now compare a leaf with the two leaves that would replace it. The rows in I divide into IL and IR, so G=GL+GR and H=HL+HR. The objective changes by the score after, minus the score before, minus one more γ for the extra leaf:

Gain=12[GL2HL+λ+GR2HR+λ−G2H+λ]−γ

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:

gainxgb=GL2HL+λ+GR2HR+λ−G2H+λ

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 γ=250 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.
  1. Score the children. For study < 4.5, each child scores 60²/5 = 720.
  2. Subtract the parent. The root has G = 0, so its score is 0, and the dumped gain is 1440.
  3. 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 G2/(H+λ) 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 GL=60 and HL=4 for the left child of that first split, η=1 in the table below. Lambda shrinks both the weight and the gain:

lambdaleft leaf weightdumped gain
0−15.0001800.000
1−12.0001440.000
10−4.286514.286
100−0.57769.231

How reg_alpha soft-thresholds G

Now keep α. The bracket for a leaf becomes Gw+12(H+λ)w2+α|w|. The absolute value has a kink at 0, so the two sides need separate cases.

For w > 0: the derivative is G+(H+λ)w+α. Setting it to 0 gives w=−(G+α)/(H+λ). That is positive only if G&lt;−α.

For w < 0: the derivative is G+(H+λ)w−α. Setting it to 0 gives w=−(G−α)/(H+λ). That is negative only if G&gt;α.

Otherwise, |G|≤α. Neither side has a solution, so the minimum sits on the kink and w=0.

These three cases are one operation, called soft thresholding. It moves G toward 0 by α and stops at 0:

Tα(G)=sign(G)max(|G|−α,0),w*=−Tα(G)H+λ,score=Tα(G)2H+λ

This is why alpha can zero a leaf, and why it can remove a split. On the running rows with lambda 1 and GL=60, any alpha of 60 or more removes the gain completely, so the node stays a leaf: no split at alpha 60.

alphaleft leaf weight
0−12.000
10−10.000
40−4.000
600.000
700.000
  1. Soft-threshold G. With alpha 10, G = 60 moves 10 toward 0, to T = 50.
  2. 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 m sets a hard limit on the size of a weight, applied after the formula and before eta. When m&gt;0:

w=clip(−Tα(G)H+λ,−m,m)

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:

score=−(2Gw+(H+λ)w2)

With no clipping and no alpha, this equals G2/(H+λ). With clipping it is smaller, so clipping lowers both the weight and the gain.

mdsleft leaf weightdumped gain
0−12.0001440.000
20−12.0001440.000
10−10.0001400.000
5−5.000950.000
1−1.000230.000

When alpha and max_delta_step are both set, the source uses this same score with the raw G, 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.
  1. Take the Newton step. For the left child, w = −60/5 = −12.
  2. Clip it. With max_delta_step=5, the weight becomes −5.000.
  3. Score the clipped weight. Each child scores −(2 × 60 × (−5) + 5 × 25) = 475, so the gain is 950.000.
  4. 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 Gw+12(H+λ)w2+α|w|. 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, m is the margin, p is the output after the link, and each row's values are multiplied by its sample weight.

objectivelossghoutput
reg:squarederror12(m−y)2m−y1m
binary:logistic
reg:logistic
binary:logitraw
−ylnp−(1−y)ln(1−p)p−ymax(p(1−p),10−16)p=1/(1+e−m) (logitraw returns m)
multi:softmax
multi:softprob
−lnpypk−𝟙 per class k, where 𝟙 is 1 for the true classmax(2pk(1−pk),10−16)softmax of the margins
count:poissonem−ymem−yem+δ, with δ = max_delta_step (0.7 by default)em
reg:gammaye−m+m1−ye−mye−mem
reg:tweedie−ye(1−ρ)m1−ρ+e(2−ρ)m2−ρ−ye(1−ρ)m+e(2−ρ)m−y(1−ρ)e(1−ρ)m+(2−ρ)e(2−ρ)mem
reg:absoluteerror|m−y|sign(m−y)1 (placeholder)m
reg:quantileerrorpinball: (1−α)(m−y) above the label, α(y−m) below1−α if m≥y, else −α1 (placeholder)m
reg:pseudohubererrorδ2(s−1), where s=1+(z/δ)2 and z=m−yz/s1/s3m
reg:squaredlogerror12(ln(1+m)−ln(1+y))2ln(1+m)−ln(1+y)1+mmax(−ln(1+m)+ln(1+y)+1(1+m)2,10−6)m
binary:hingemax(0,1−my′), where y′=2y−1−y′ if my′&lt;1, else 01 if my′&lt;1, else the smallest positive float0 or 1
survival:coxnegative partial log-likelihood. A negative label means right-censored.emrk−𝟙, with rk a running sum of 1/∑em over risk sets, and 𝟙 = 1 for an observed eventemrk−e2msk, with sk the running sum of squares of the same termsem
survival:aftlikelihood of lny=m+σZ, with possibly censored boundsexact event, normal: −z/σ, where z=(lny−m)/σexact event, normal: 1/σ2em
rank:pairwise
rank:ndcg
rank:map
pairs within a query. For each pair where i is more relevant than j:λij=(σ(si−sj)−1)|Δ|. Add it to gi, subtract it from gj.2max(σ(1−σ),ε)|Δ|, added to both documentsa score to sort by

In the ranking row, σ is the sigmoid of the score difference, and Δ is the change in the metric if i and j swapped: the change in NDCG, the change in MAP, or 1 for pairwise. It may be divided by |si−sj|+0.01 (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 H. 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.

  1. Find the formulas. Under reg:gamma, g=1−ye−m and h=ye−m.
  2. Substitute. At m = 0 with y = 4, g = 1 − 4 = −3 and h = 4.

Why L1-type losses need special handling

The absolute loss |m−y| is a V shape. Its second derivative is 0 everywhere except the kink, where it is undefined. If you used that honest h=0, the weight would be −G/λ, 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 h=1, 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.

leafj=η·quantileα{yi−y^i:i∈Ij}

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 g=±1 and h=1.

  • 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 α(n+1) in the sorted values, from common::Quantile in src/common/stats.h.

Since the placeholder h is 1 and H 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 h.

  1. Collect the residuals. The left leaf of the reg:absoluteerror tree holds −17.5, −12.5, −7.5, −2.5.
  2. Take the quantile. Their median is −10.000.
  3. 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, g=y^−y and h=1.

rowx1x2yprediction before this treeg = prediction − yh
1183557.522.51
2254057.517.51
3375057.57.51
4444557.512.51
5587057.5−12.51
6655557.52.51
7768057.5−22.51
8878557.5−27.51

Round 1: scan every split at the root

At the root, G=0 and H=8. So the parent's score is 02/(8+1)=0. 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:

GL=22.5+17.5+7.5+12.5=60,HL=4
gain=6024+1+(−60)24+1−0=1440

Every candidate follows the same arithmetic:

splitGLHLGRHRgainnote
study < 1.522.51−22.57316.406
study < 2.5402−406761.905
study < 3.547.53−47.55940.104
study < 4.5604−6041440chosen
study < 5.547.55−47.53940.104
study < 6.5506−5021190.476
study < 7.527.57−27.51472.656
sleep < 4.512.51−12.5797.656
sleep < 5.532.53−32.55440.104
sleep < 6.5104−10440
sleep < 7.5−10610247.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 G=60 and H=4. Its own score is 720, from 602 over 4+1. Scan it the same way:

splitGLHLGRHRgainnote
study < 1.522.5137.53−115.312negative gain
study < 2.5402202−53.333negative gain
study < 3.547.5312.51−77.812negative gain
sleep < 4.512.5147.53−77.812negative gain
sleep < 6302302−120negative gain
sleep < 7.537.5322.51−115.312negative 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 H, 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 G=−60 and H=4.

splitGLHLGRHRgainnote
study < 5.5−12.51−47.53−77.812negative gain
study < 6.5−102−502146.667
study < 7.5−32.53−27.51−77.812negative gain
sleep < 5.52.51−62.53259.688chosen
sleep < 6.5−202−402−53.333negative gain
sleep < 7.5−47.53−12.51−77.812negative 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 w=−G/(H+λ), and the tree stores ηw.

noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 8080—1440study < 4.5
left1, 2, 3, 4604−12−3.6—leaf
right5, 6, 7, 8−60412—259.688sleep < 5.5
right, left62.51−1.25−0.375—leaf
right, right5, 7, 8−62.5315.6254.6875—leaf

Add each row's leaf to its prediction. The RMSE falls from 17.5 to 13.9108.

rowbeforethis tree addsaftery
157.5−3.653.935
257.5−3.653.940
357.5−3.653.950
457.5−3.653.945
557.54.687562.187570
657.5−0.37557.12555
757.54.687562.187580
857.54.687562.187585

Round 2: the residuals changed, so everything is recomputed

Now g has changed, though h is still 1. The second tree fits what the first tree left.

rowx1x2yprediction before this treeg = prediction − yh
1183553.918.91
2254053.913.91
3375053.93.91
4444553.98.91
5587062.1875−7.81251
6655557.1252.1251
7768062.1875−17.81251
8878562.1875−22.81251

Scan the root again:

splitGLHLGRHRgainnote
study < 1.518.91−19.61257226.63
study < 2.532.82−33.51256518.998
study < 3.536.73−37.41255569.949
study < 4.545.64−46.31254844.785chosen
study < 5.537.78755−38.53608.489
study < 6.539.91256−40.6252777.646
study < 7.522.17−22.81251321.2
sleep < 4.58.91−9.6125751.099
sleep < 5.524.9253−25.63755264.804
sleep < 6.57.11254−7.825422.307
sleep < 7.5−11.8611.0875260.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:

splitGLHLGRHRgainnote
study < 1.518.9126.73−59.045negative gain
study < 2.532.8212.82−2.645negative gain
study < 3.536.738.91−39.545negative gain
sleep < 4.58.9136.73−39.545negative gain
sleep < 622.8222.82−69.312negative gain
sleep < 7.526.7318.91−59.045negative gain

Right child:

splitGLHLGRHRgainnote
study < 5.5−7.81251−38.53−27.889negative gain
study < 6.5−5.68752−40.6252131.943
study < 7.5−23.53−22.81251−30.702negative gain
sleep < 5.52.1251−48.43753159.836chosen
sleep < 6.5−15.68752−30.6252−34.307negative gain
sleep < 7.5−38.53−7.81251−27.889negative gain
noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 8−0.712580.0792—844.785study < 4.5
left1, 2, 3, 445.64−9.12−2.736—leaf
right5, 6, 7, 8−46.312549.2625—159.836sleep < 5.5
right, left62.1251−1.0625−0.3187—leaf
right, right5, 7, 8−48.4375312.10943.6328—leaf
rowbeforethis tree addsaftery
153.9−2.73651.16435
253.9−2.73651.16440
353.9−2.73651.16450
453.9−2.73651.16445
562.18753.632865.820370
657.125−0.318756.806255
762.18753.632865.820380
862.18753.632865.820385

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 ηw, and cover is the node's H.


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:

  1. Score the parent. At the root, G = 0 and H = 8, so the parent's score is 0.
  2. Score every threshold. study < 4.5 gives 60²/5 + (−60)²/5 − 0 = 1440, and the table lists the other thresholds.
  3. 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 h 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 m is in log-odds, p=1/(1+e−m), g=p−y, and h=p(1−p).

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 p=0.5, with g=0.5 or −0.5, and h=0.25.

rowymarginpg = p − yh = p(1 − p)
1000.50.50.25
2000.50.50.25
3100.5−0.50.25
4000.50.50.25
5100.5−0.50.25
6000.50.50.25
7100.5−0.50.25
8100.5−0.50.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 HL and HR to reach 1. With h=0.25 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.

splitGLHLGRHRgainnote
study < 1.50.50.25−0.51.750.291blocked by min_child_weight
study < 2.510.5−11.51.067blocked by min_child_weight
study < 3.50.50.75−0.51.250.254blocked by min_child_weight
study < 4.511−111chosen
study < 5.50.51.25−0.50.750.254blocked by min_child_weight
study < 6.511.5−10.51.067blocked by min_child_weight
study < 7.50.51.75−0.50.250.291blocked by min_child_weight
sleep < 4.50.50.25−0.51.750.291blocked by min_child_weight
sleep < 5.51.50.75−1.51.252.286blocked by min_child_weight
sleep < 6.511−111
sleep < 7.501.500.50blocked 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 H=1, so no further split can give both sides 1:

splitGLHLGRHRgainnote
study < 1.50.50.250.50.75−0.157blocked by min_child_weight
study < 2.510.500.50.167blocked by min_child_weight
study < 3.50.50.750.50.25−0.157blocked by min_child_weight
sleep < 4.50.50.250.50.75−0.157blocked by min_child_weight
sleep < 610.500.50.167blocked by min_child_weight
sleep < 7.50.50.750.50.25−0.157blocked by min_child_weight

Right child:

splitGLHLGRHRgainnote
study < 5.5−0.50.25−0.50.75−0.157blocked by min_child_weight
study < 6.500.5−10.50.167blocked by min_child_weight
study < 7.5−0.50.75−0.50.25−0.157blocked by min_child_weight
sleep < 5.50.50.25−1.50.750.986blocked by min_child_weight
sleep < 6.500.5−10.50.167blocked by min_child_weight
sleep < 7.5−0.50.75−0.50.25−0.157blocked by min_child_weight
noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 8020—1study < 4.5
left1, 2, 3, 411−0.5−0.15—leaf
right5, 6, 7, 8−110.50.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 p has moved away from 0.5, so h has fallen below 0.25 for every row. Four rows no longer sum to 1, so no split is valid, even the even split:

rowymarginpg = p − yh = p(1 − p)
10−0.150.46260.46260.2486
20−0.150.46260.46260.2486
31−0.150.4626−0.53740.2486
40−0.150.46260.46260.2486
510.150.5374−0.46260.2486
600.150.53740.53740.2486
710.150.5374−0.46260.2486
810.150.5374−0.46260.2486
splitGLHLGRHRgainnote
study < 1.50.46260.2486−0.46261.74020.249blocked by min_child_weight
study < 2.50.92510.4972−0.92511.49160.915blocked by min_child_weight
study < 3.50.38770.7458−0.38771.2430.153blocked by min_child_weight
study < 4.50.85030.9944−0.85030.99440.725blocked by min_child_weight
study < 5.50.38771.243−0.38770.74580.153blocked by min_child_weight
study < 6.50.92511.4916−0.92510.49720.915blocked by min_child_weight
study < 7.50.46261.7402−0.46260.24860.249blocked by min_child_weight
sleep < 4.50.46260.2486−0.46261.74020.249blocked by min_child_weight
sleep < 5.51.46260.7458−1.46261.2432.179blocked by min_child_weight
sleep < 6.510.9944−10.99441.003blocked by min_child_weight
sleep < 7.501.491600.49720blocked by min_child_weight
noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 801.988800—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 h, 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:

rowymarginpg = p − yh = p(1 − p)
1000.50.50.25
2000.50.50.25
3100.5−0.50.25
4000.50.50.25
5100.5−0.50.25
6000.50.50.25
7100.5−0.50.25
8100.5−0.50.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
splitGLHLGRHRgainnote
study < 1.50.50.25−0.51.750.291
study < 2.510.5−11.51.067
study < 3.50.50.75−0.51.250.254
study < 4.511−111
study < 5.50.51.25−0.50.750.254
study < 6.511.5−10.51.067
study < 7.50.51.75−0.50.250.291
sleep < 4.50.50.25−0.51.750.291
sleep < 5.51.50.75−1.51.252.286chosen
sleep < 6.511−111
sleep < 7.501.500.50no gain
Show the child scans for this tree

Left child:

splitGLHLGRHRgainnote
study < 30.50.2510.5−0.419negative gain
study < 510.50.50.25−0.419negative gain
sleep < 4.50.50.2510.5−0.419negative gain

Right child:

splitGLHLGRHRgainnote
study < 20.50.25−211.2chosen
study < 400.5−1.50.750.286
study < 6−0.50.75−10.5−0.19negative gain
study < 7.5−11−0.50.25−0.3negative gain
sleep < 6.5−0.50.25−11−0.3negative gain
sleep < 7.5−1.50.7500.50.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.

noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 8020—2.286sleep < 5.5
left2, 4, 61.50.75−0.8571−0.2571—leaf
right1, 3, 5, 7, 8−1.51.250.6667—1.2study < 2
right, left10.50.25−0.4−0.12—leaf
right, right3, 5, 7, 8−2110.3—leaf

Now the second round. Here is the point of this section: h now differs from row to row. A row that the model is more sure about has a smaller h, so it counts for less in H. That is how this one formula becomes a Newton step on a curved loss.

rowymarginpg = p − yh = p(1 − p)
10−0.120.470.470.2491
20−0.25710.43610.43610.2459
310.30.5744−0.42560.2445
40−0.25710.43610.43610.2459
510.30.5744−0.42560.2445
60−0.25710.43610.43610.2459
710.30.5744−0.42560.2445
810.30.5744−0.42560.2445
splitGLHLGRHRgainnote
study < 1.50.470.2491−0.3941.71560.232
study < 2.50.90610.495−0.83011.46970.826
study < 3.50.48050.7395−0.40451.22520.204
study < 4.50.91660.9854−0.84060.97930.778
study < 5.50.49111.2298−0.4150.73480.205
study < 6.50.92711.4758−0.85110.48890.832
study < 7.50.50161.7202−0.42560.24450.236
sleep < 4.50.43610.2459−0.36011.71880.198
sleep < 5.51.30820.7377−1.23221.22691.665chosen
sleep < 6.50.88260.9822−0.80660.98250.719
sleep < 7.50.03151.47110.04450.49360negative gain
Show the child scans for tree 2

Left child:

splitGLHLGRHRgainnote
study < 30.43610.24590.87210.4918−0.322negative gain
study < 50.87210.49180.43610.2459−0.322negative gain
sleep < 4.50.43610.24590.87210.4918−0.322negative gain

Right child:

splitGLHLGRHRgainnote
study < 20.470.2491−1.70220.97780.96chosen
study < 40.04450.4936−1.27670.73340.26
study < 6−0.38110.738−0.85110.4889−0.112negative gain
study < 7.5−0.80660.9825−0.42560.2445−0.208negative gain
sleep < 6.5−0.42560.2445−0.80660.9825−0.208negative gain
sleep < 7.5−1.27670.73340.04450.49360.26
noderowsGHw = −G/(H+λ)leaf = ηwgainsplit
root1, 2, 3, 4, 5, 6, 7, 80.0761.9647−0.0256—1.665sleep < 5.5
left2, 4, 61.30820.7377−0.7528−0.2258—leaf
right1, 3, 5, 7, 8−1.23221.22690.5533—0.96study < 2
right, left10.470.2491−0.3763−0.1129—leaf
right, right3, 5, 7, 8−1.70220.97780.86070.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 H, 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.

  1. Find h per row. At p = 0.5, h = 0.25.
  2. 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.

methodcandidatesbuiltweights
exactevery gap between distinct valuesat each node, from sorted columns—
approxat most max_bin cut pointsagain before each treeh of the current round
hist (and auto)at most max_bin cut pointsonce, before trainingonly 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 GL and HL with one addition, and GR=G−GL 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 g and h 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 binparent Σgparent Σhleft child Σgleft child Σhright = parent − left, Σgright Σh
412.5112.5100
520217.512.51
6−22.5100−22.51
7−2027.51−27.51
810222.51−12.51

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:

∑i[gif(xi)+12hif(xi)2]=∑i12hi(f(xi)+gihi)2+constant

This is a weighted least-squares problem. The target for each row is −gi/hi, and its weight is hi. So a row with larger h 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 h, 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: h changes every round. And it is why hist, sketching once with no h, is faster but doesn't adapt. The docs say that on losses with varying h, approx sometimes gives better accuracy, but hist with a higher max_bin can usually match it. On squared error, h 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' g and h 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 G=−5 and H=2. 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:

splitmissing rows sent left: gainmissing rows sent right: gain
sleep < 4.523.43897.656
sleep < 640.000107.143
sleep < 7.547.61910.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.

  1. Send the missing rows left. At sleep < 6, the gain is 40.000.
  2. Send them right. The gain is 107.143.
  3. 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 2k−1 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 methodleaveswhat happened
exact4The children's large gains stop pruning from reaching the root, so the root survives.
hist1The root's gain is below 1, so it stops immediately.
approx1The 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.
  1. Read the gains. The XOR root has gain 0.000125, and its children have 2.450 and 2.500.
  2. 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.
  3. Apply the rule of hist. The root is below 1, so it is never split: 1 leaf.

base_score: where the sum starts

F0 is estimated from the training labels. The rule depends on the objective:

objectiveestimated base_scorerule
reg:squarederror57.5000mean of the labels
binary:logistic0.5000mean of the labels
reg:logistic0.5000mean of the labels
count:poisson57.5000mean of the labels
reg:gamma57.5000mean of the labels
reg:tweedie57.5000mean of the labels
reg:absoluteerror52.5000median of the labels
reg:quantileerror85.0000the α quantile of the labels (α = 0.9 here)
reg:squaredlogerror0.8010one Newton step from margin 0
binary:hinge0.0000one Newton step from margin 0
survival:cox1.0000one Newton step from margin 0, then exp
reg:pseudohubererror113664one 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 h is tiny, so −G/H 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 g and h 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 k trees were dropped, tree sets the new tree's weight to 1/(k+η) and multiplies dropped trees by k/(k+η). forest uses 1/(1+η) 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 ft is a linear function. Each round does coordinate descent: for one feature at a time, it takes a Newton step on that coefficient, using g and h weighted by the feature's value. The update from the source is

Δwk=−∑igixik+λ′wk±α′∑ihixik2+λ′,λ′=λ∑iweighti,α′=α∑iweighti

where the sign of α′ pushes the coefficient toward zero, and the update stops at −wk 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 K classes, each row has K margins, so K pairs of g and h. By default, each round grows K trees, one per class, each on its own g and h: 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:

typemeaningstudy (f0)sleep (f1)
weightnumber of splits on the feature2.0002.000
gainaverage gain of those splits1142.393209.762
coveraverage sum of h reaching those splits8.0004.000
total_gainsum of their gains2284.785419.524
total_coversum of their covers16.0008.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

partclassic gradient boosting (Friedman 2001)XGBoost
What a tree fitsLeast squares to the negative gradient, so only g shapes the tree.The gain formula, so both g and h shape the tree.
Leaf valuesA line search per leaf after the tree is grown.The Newton step −G/(H+λ), consistent with how splits were scored.
RegularizationShrinkage, depth, and subsampling. No penalty in the objective.Ω in the objective, so it directly changes splits and weights, plus all of those.
Missing valuesDepends on the implementation. Often impute first.A learned default direction per split.
ScaleExact scan in the original form.Sketches, histograms, subtraction, column blocks, GPU.

11One run, from the data to a prediction

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.

Where each parameter acts

Fig. 11.1 · interactive

Before the first round

Each round, num_boost_round times

3Compute each row's marginbase_margin
13Add the trees to the margins
14Evaluate, and stop early if askedeval_seteval_metricearly_stopping_rounds

Prediction for a new row

15Start from the starting valuebase_scorebase_margin
16Route the row through each treemissingiteration_rangeearly_stopping_rounds
17Convert the marginobjectiveoutput_margin
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

  1. 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.
  2. 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.

  1. 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.
  2. 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.

  1. 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 g2+0.1h2, 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.
  2. Sample the tree's features. colsample_bytree keeps max(1,⌊rate×count⌋) features for the whole tree, chosen at random. With feature_weights, a feature's chance of being chosen follows its weight.
  3. 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 ηw, with w=−Tα(G)/(H+λ) 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.

  1. 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.
  2. 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.
  3. 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.
  4. Split, and set the two new leaves. Each child becomes a leaf that stores ηw, 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.
  5. 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_depth
max_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:

    1. 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.
    2. 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

    1. 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.
    2. 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.
    3. 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 p=1/(1+e−m). 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.

    stepsettings in this runround 1round 2
    1 binmax_bin 256Each distinct value has its own bin, so the split that exact writes as study < 4.5 is written as study < 5.
    2 startbase_score unsetThe mean of the labels, 57.5.
    3 margins—57.5 for every row53.9, 57.125 or 62.1875, by leaf
    4 g and hno weightsroot G = 0, H = 8root G = −0.7125, H = 8
    5 rowssubsample 1All 8 rows.
    6 featurescolsample_bytree 1Both features.
    7 rooteta 0.3, lambda 1stores 0 until it splitsstores 0.3 × 0.0792 until it splits
    8 candidatescolsample_bylevel, colsample_bynode 1Both features at every node.
    9 scoremin_child_weight 1, lambda 1root: study < 5, gain 1440; right child: sleep < 6, gain 259.688; left child: every gain is negativethe same splits, with gains 844.785 and 159.836; left child negative again
    10 decidegamma 0, max_depth 2The 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 leaveseta 0.3, num_parallel_tree 1−3.6, −0.375, 4.6875−2.736, −0.3187, 3.6328
    12 refitreg:squarederrorNone. Leaves are refit only under the L1-type objectives.
    13 margins—RMSE from 17.5 to 13.9108RMSE to 11.2634
    14 evaluateno evaluation setnext roundstop: 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.
      1. Start from the starting value. The margin starts at 57.5.
      2. 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.
      3. Route through tree 2. The splits are the same, so the row reaches the leaf that stores 3.6328.
      4. 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.

      1. Find the step. colsample_bylevel acts in step 8, after colsample_bytree in step 6.
      2. 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.
      3. 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.

      settingwhat 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:softmaxSteps 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 ft 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 Ft=Ft−1+ηft. 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

      settingtrain RMSEtest RMSEtotal leaves
      0.012.0912.3173199
      0.031.0521.4283162
      0.10.6731.2683020
      0.30.3121.4163059
      1.00.0702.0963074

      gamma

      alias min_split_loss
      Default in 3.2.0
      0
      Valid range
      [0, ∞)
      Acts on
      The cost γT 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

      settingtrain RMSEtest RMSEtotal leaves
      00.6731.2683020
      10.8001.2911851
      50.9291.3301215
      201.0641.396868
      1001.3911.644574

      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 2d 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

      settingtrain RMSEtest RMSEtotal leaves
      11.7201.818400
      21.1811.429800
      30.8751.2711588
      40.6731.2683020
      60.3001.3748983
      80.0491.53624802
      120.0011.79264791

      min_child_weight

      Default in 3.2.0
      1
      Valid range
      [0, ∞)
      Acts on
      A candidate is valid only if HL and HR 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 H≤0 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

      settingtrain RMSEtest RMSEtotal leaves
      00.6731.2683020
      10.6731.2683020
      100.6851.2592588
      500.8091.2451964
      2001.0851.2861219

      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: w←clip(w,−m,m), 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 −G/(H+λ) 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 h=em+max\_delta\_step.
      Works with
      scale_pos_weight, eta, count:poisson.

      Measured

      settingmean ptest loglossAUCrecall at 0.5precision at 0.5largest |leaf|
      00.0350.1840.8830.4440.8429.689
      0.10.0300.1600.8990.4260.9020.100
      0.50.0360.1740.8910.4910.8150.500
      10.0340.1770.8950.4810.8251.000
      50.0350.1860.8810.4720.8795.000

      Measured

      settinglargest |leaf|test RMSE after 3 rounds
      0.05.2504.397
      0.70.7001.800
      2.00.7202.177

      lambda

      alias reg_lambda
      Default in 3.2.0
      1 for trees
      Valid range
      [0, ∞)
      Acts on
      The 12λ∑w2 term of Ω. It adds to H in both the weight −G/(H+λ) and the score G2/(H+λ).
      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

      settingtrain RMSEtest RMSEtotal leaves
      00.6261.2743103
      10.6731.2683020
      100.7771.3012964
      1000.9291.3183081
      10001.4781.6813155

      alpha

      alias reg_alpha
      Default in 3.2.0
      0 for trees
      Valid range
      [0, ∞)
      Acts on
      The α∑|w| term of Ω. It soft-thresholds G: G moves toward 0 by α, and becomes 0 when |G|≤α, 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

      settingtrain RMSEtest RMSEtotal leaves
      00.6731.2683020
      10.6661.2803025
      100.7841.2842800
      1001.3151.5721324
      10003.1023.251514

      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

      settingtrain RMSEtest RMSEtotal leaves
      0.20.7871.2402863
      0.50.6601.2493019
      0.80.6341.2463052
      1.00.6731.2683020

      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 g2+λh2, 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

      settingtrain RMSEtest RMSEtotal leaves
      uniform, subsample 0.10.9411.3502654
      uniform, subsample 0.30.7391.2732935
      gradient_based, subsample 0.10.8811.4042530
      gradient_based, subsample 0.30.7021.2382816

      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.
      Works with
      colsample_bylevel, colsample_bynode, feature_weights.

      Measured

      settingtrain RMSEtest RMSEtotal leaves
      0.21.0051.6663094
      0.50.7071.2933042
      0.80.6811.2753043
      1.00.6731.2683020

      colsample_bylevel

      Default in 3.2.0
      1
      Valid range
      (0, 1]
      Acts on
      At each new depth, chooses features from the ones that the tree already holds. The scan at that level uses only these.
      Raising it
      Toward 1, no extra sampling.
      Lowering it
      Each level sees fewer features, so the splits within a level choose from fewer features.
      Watch for
      It samples from the tree's set, so it stacks on colsample_bytree.
      Works with
      colsample_bytree, colsample_bynode.

      Measured

      settingtrain RMSEtest RMSEtotal leaves
      0.20.8001.4243066
      0.50.6821.2753059
      0.80.6751.2853026
      1.00.6731.2683020

      colsample_bynode

      Default in 3.2.0
      1
      Valid range
      (0, 1]
      Acts on
      At each split, chooses features from the ones that the level holds. This is the per-split sampling that random forests use.
      Raising it
      Toward 1, no extra sampling.
      Lowering it
      Each split sees fewer features, so trees decorrelate most directly.
      Watch for
      The docs say exact does not support it.
      Works with
      num_parallel_tree, colsample_bytree, colsample_bylevel.

      Measured

      settingtrain RMSEtest RMSEtotal leaves
      0.20.8051.4053037
      0.50.6981.2763019
      0.80.6841.2913013
      1.00.6731.2683020

      tree_method

      Default in 3.2.0
      auto, which runs hist
      Valid range
      auto, exact, approx, hist
      Acts on
      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

      settingtrain RMSEtest RMSEtotal leaves
      exact0.6811.2882807
      approx0.6731.2683020
      hist0.6731.2683020

      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.
      Works with
      min_child_weight, max_delta_step, base_score, sample weights.

      Measured

      settingmean ptest loglossAUCrecall at 0.5precision at 0.5largest |leaf|
      10.0360.1240.9030.4910.9140.969
      30.0430.1160.9060.5460.8680.407
      100.0480.1140.9090.5830.8870.287
      17.50.0520.1130.9110.6020.8230.273
      1000.0620.1270.9030.6390.7420.561

      grow_policy

      Default in 3.2.0
      depthwise
      Valid range
      depthwise, lossguide
      Acts on
      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

      settingtrain RMSEtest RMSEtotal leaves
      depthwise, depth 4, leaves 00.6731.2683020
      depthwise, depth 3, leaves 00.8751.2711588
      lossguide, depth 0, leaves 41.0571.297800
      lossguide, depth 0, leaves 80.8221.2531600
      lossguide, depth 0, leaves 160.5551.2963200
      lossguide, depth 0, leaves 320.2711.3166400
      lossguide, depth 0, leaves 640.0731.38212800

      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

      settingtrain RMSEtest RMSEtotal leaves
      21.7201.818400
      41.0571.297800
      80.8221.2531600
      160.5551.2963200
      320.2711.3166400

      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

      settingtrain RMSEtest RMSEtotal leaves
      23.2163.5323200
      41.5752.1643200
      80.9501.4793192
      160.7721.2953171
      640.6751.2683108
      2560.6731.2683020
      10240.6831.2812855

      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

      settingtrain RMSEtest RMSEtotal leaves
      11.1931.512794
      21.1811.4831585
      51.1871.4923954
      101.1611.4767923

      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

      settingsteps down along feature 0steps up along feature 0test RMSE
      none30452.227
      (1,0,0)0262.220
      (-1,0,0)002.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

      settingpaths breaking a grouptrain RMSEtest RMSE
      none00.6731.268
      [[0,1,2,3,4],[5,6,7,8,9]]00.7011.271
      [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]]01.3901.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

      settingtrees after 50 roundstest RMSE
      one_output_per_tree1501.719
      multi_output_tree501.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.
      Choices
      grow_colmaker, grow_histmaker, grow_quantile_histmaker, grow_gpu_hist, grow_gpu_approx, sync, refresh, prune.
      Watch for
      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.
      Works with
      max_cat_threshold, enable_categorical, feature_types.

      Measured

      settingtrain RMSEtest RMSEtotal leaves
      4 (partition)0.9561.00580
      41 (one-hot)1.4561.50467

      max_cat_threshold

      Default in 3.2.0
      64
      Valid range
      at least 1
      Acts on
      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

      settingtrain RMSEtest RMSEtotal leaves
      12.4472.53777
      21.4601.50868
      50.9901.03780
      100.9641.00380
      640.9561.00580

      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

      settingtrain RMSEtest RMSEtotal leaves
      0.00.8421.2971553
      0.11.5591.8371597
      0.32.4892.7031600
      0.52.8903.0851600

      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

      settingtrain RMSEtest RMSEtotal leaves
      00.8451.3061579
      10.9151.3471582

      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

      settingtrain RMSEtest RMSEtotal leaves
      0.01.5591.8371597
      0.51.0311.4211580
      0.90.8841.3241550

      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

      settingtrain RMSEtest RMSEtotal leaves
      uniform1.5591.8371597
      weighted1.6221.8931596

      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 1/(k+η), dropped trees multiplied by k/(k+η). forest: new tree weight 1/(1+η), dropped trees multiplied by 1/(1+η).
      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

      settingtrain RMSEtest RMSEtotal leaves
      tree1.5591.8371597
      forest1.1691.5211594

      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

      settingtrain RMSEtest RMSEzero coefficients of 20
      09.97610.1950
      0.118.14618.3520
      182.15481.9200
      10147.065145.1870

      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

      settingtrain RMSEtest RMSEzero coefficients of 20
      09.97610.1950
      0.19.98610.1622
      110.29810.39415
      1024.97125.13415
      100161.562159.16520

      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

      settingtrain RMSEtest RMSEzero coefficients of 20
      0.195.09394.9080
      0.511.54711.7880
      1.09.97610.1950

      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

      settingtrain RMSEtest RMSEzero coefficients of 20
      shotgun9.97610.1950
      coord_descent9.97610.1950

      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 O(features2) 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

      settingtrain RMSEtest RMSEzero coefficients of 20
      cyclic23.15523.7890
      shuffle23.63924.4080
      random34.95036.9221
      greedy10.07810.1746
      thrifty22.89623.3340

      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

      settingtrain RMSEtest RMSEzero coefficients of 20
      010.07810.1746
      1101.099101.19317
      346.35245.51915
      1010.48710.51015

      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

      settingtest RMSEmean predictionmean label
      1.14.3985.8805.948
      1.54.3695.7185.948
      1.94.4065.5385.948

      huber_slope

      Default in 3.2.0
      1
      Valid range
      non-zero. The source rejects 0.
      Acts on
      The δ in Pseudo-Huber's g=z/s and h=1/s3. Below |z|≈δ 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

      settingclean test RMSEclean test MAE
      0.11.2681.005
      11.6351.279
      102.9191.533
      1006.7223.496

      Measured

      settingclean test RMSEclean test MAE
      reg:squarederror6.6943.677
      reg:absoluteerror1.6131.102
      reg:pseudohubererror1.6351.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: 1−α 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

      settingshare of test labels at or below the prediction
      0.10.195
      0.50.516
      0.90.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.
      Watch for
      Only with survival:aft or the aft-nloglik metric.
      Works with
      aft_loss_distribution_scale, label_lower_bound, label_upper_bound.

      Measured

      settingtest aft-nloglik
      normal, scale 0.51.490
      normal, scale 1.01.714
      normal, scale 2.02.129
      logistic, scale 0.51.636
      logistic, scale 1.02.001
      logistic, scale 2.02.445
      extreme, scale 0.51.577
      extreme, scale 1.01.824
      extreme, scale 2.02.257

      aft_loss_distribution_scale

      Default in 3.2.0
      1
      Valid range
      (0, ∞)
      Acts on
      The σ in z=(lny−m)/σ. On an uncensored row under normal, g=−z/σ and h=1/σ2, 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

      settingtest NDCG@10total leaves
      topk0.858774
      mean0.863671

      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

      settingtest NDCG@10total leaves
      10.831708
      50.860738
      100.848755
      300.858774

      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 log2(1+S)/S with S 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

      settingtest NDCG@10total leaves
      true0.858774
      false0.857770

      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 |si−sj|+0.01, 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

      settingtest NDCG@10total leaves
      true0.858774
      false0.863778

      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

      settingtest NDCG@10total leaves
      false0.858774
      true0.855767

      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 Lp 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

      settingtest NDCG@10total leaves
      0.50.860769
      1.00.855767
      2.00.853774

      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: 2rel−1 when true, or rel 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

      settingtest NDCG@10total leaves
      true0.858774
      false0.852782

      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 l, so it sets g, h, the output transform, the default metric and how base_score is estimated.
      Choices
      Regression: reg:squarederror, reg:squaredlogerror, reg:logistic, reg:pseudohubererror, reg:absoluteerror, reg:quantileerror, reg:gamma, reg:tweedie. Classification: binary:logistic, binary:logitraw, binary:hinge, multi:softmax, multi:softprob. Counts and survival: count:poisson, survival:cox, survival:aft. Ranking: rank:ndcg, rank:map, rank:pairwise.
      Watch for
      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

      settingclean test RMSEclean test MAE
      reg:squarederror6.6943.677
      reg:absoluteerror1.6131.102
      reg:pseudohubererror1.6351.279

      Measured

      settingtest NDCG@10total leaves
      rank:ndcg0.858774
      rank:pairwise0.856770
      rank:map0.832757

      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
      F0, 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.
      Works with
      objective, base_margin, scale_pos_weight, n_estimators.

      Measured

      settingtest RMSE, 1 roundstest RMSE, 10 roundstest RMSE, 50 roundstest RMSE, 200 roundstest RMSE, 500 rounds
      estimated (mean)4.7833.0831.5081.2681.265
      013.4985.7521.4951.2781.277
      10077.56630.3951.6471.2331.237

      eval_metric

      Default in 3.2.0
      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.
      Works with
      early_stopping_rounds, disable_default_eval_metric, maximize.

      seed

      alias random_state
      Default in 3.2.0
      0
      Valid range
      integer
      Acts on
      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

      settingseed 0seed 1seed 2
      no sampling1.2681.2681.268
      subsample 0.51.2491.2491.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 F0 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_rate 1, subsample 0.8, colsample_bynode 0.8, reg_lambda 1e−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
      K, the number of rounds in FK=F0+η∑ft.
      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

      settingtrain RMSEtest RMSE
      102.8993.083
      501.1561.508
      1000.8421.297
      2000.6731.268
      5000.3731.265
      10000.1601.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

      settingbest_iterationtrees trainedbest test RMSE
      528341.689
      1028391.689
      5028791.689
      200282291.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: F0 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_iteration 28.
      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 F0 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.
      Works with
      colsample_bytree, colsample_bylevel, colsample_bynode.

      Measured

      settingsplits on feature 0 (%)test RMSE
      all equal12.81.443
      feature 0 weighted 1028.11.352

      group

      Default in 3.2.0
      None
      Valid range
      sizes of consecutive query groups
      Acts on
      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 F 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.

      1. 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.
      2. 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.
      3. 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.
      4. 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 h in mind, as in section 8.
      5. Then add randomness: subsample and colsample_bytree, about 0.5 to 1. They often help a little, and they're cheap.
      6. Then regularize: lambda, alpha and gamma. Search on a log scale. The useful scale depends on g and h, so a value from another problem means little.
      7. 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.

      1. 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.
      2. 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 g and h 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 g and h. Under logistic loss, nothing is fitted to y−p directly.

      min_child_weight is the minimum rows in a leaf.

      It is a minimum sum of h. 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 H. 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 GL=2.5 and HL=1. The others have GR=−62.5 and HR=3. 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 p=0.9 and y=1, what are g and h? What weight does the Newton step give this one row, with lambda 0?

      Show the answer

      g=−0.1 and h=0.09. The weight is −g/h=1.111. 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 p is near 0, so h=p(1−p) 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 |G|=60, which is below 70. So each child's score is 0 and the gain is 0, and no split happens. The root has G=0, 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 g and h, so G and H double. With λ=0, the weight −G/H is unchanged, and gains double, which doesn't change which is largest. With λ=1, −2G/(2H+1) differs from −G/(H+1), 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
      FK=F0+η∑ft
      Training
      Trees are added one at a time, and older trees stay frozen.

      Objective

      Loss
      Second-order Taylor, so only g and h remain.
      Penalty
      γT+12λ∑w2+α∑|w|

      Leaf

      Weight
      −Tα(G)/(H+λ), then clip, then multiply by η.
      Score
      Tα(G)2/(H+λ)

      Split

      Gain
      Children's scores minus the parent's score. The library has no one half.
      Valid if
      HL,HR≥ min_child_weight, and gain reaches gamma.

      Candidates

      exact
      Every gap.
      approx, hist
      max_bin cuts, using h 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?

      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?

      GL2/(HL+λ)+GR2/(HR+λ)−G2/(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.

      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.

      ↗Sources

      • Chen and Guestrin, XGBoost: A Scalable Tree Boosting System (2016): the objective, gain, weighted sketch, sparsity-aware split.
      • Friedman, Greedy Function Approximation: A Gradient Boosting Machine (2001).
      • XGBoost 3.2.0 parameter documentation, and the tree method, categorical, multi-output and DART tutorials at the same version.
      • XGBoost source at tag release_3.2.0: src/tree/param.h, src/tree/split_evaluator.h, src/tree/updater_prune.cc, src/tree/hist/evaluate_splits.h, src/objective/*, src/linear/coordinate_common.h, src/gbm/gbtree.cc.
      • The installed package's docstrings and signatures, and Booster.save_config().

      Checked with verify_xgboost_lesson.py against xgboost 3.2.0.