XGBoost Hyperparameters · A Practical Tuning Guide
If you’ve ever stared at XGBoost’s 40+ hyperparameters and felt paralyzed, this guide is for you. The good news: for most problems, fewer than 10 parameters matter, and they have a logical tuning order.
The Parameter Hierarchy
XGBoost parameters fall into three categories. Tune them in this order:
Tier 1: Structural parameters (tune first)
These define the fundamental capacity of your model:
max_depth(default: 6): Maximum depth of each tree. Higher → more complex interactions, more risk of overfitting. For tabular data, values between 3–10 cover most use cases. Start with 6.eta/learning_rate(default: 0.3): Step size shrinkage. Lower eta requires more trees (n_estimators) but often generalizes better. Common values: 0.01–0.3.n_estimators(default: 100): Number of boosting rounds. Use with early stopping — set this high (1000+) and let validation performance decide when to stop.
The classic tradeoff: low eta + high n_estimators vs. high eta + low n_estimators. The former is slower to train but often gives slightly better accuracy. For experimentation, use eta=0.1 and n_estimators=500 with early stopping; for production, hypertune both.
Tier 2: Regularization parameters (tune second)
These prevent overfitting:
gamma(default: 0): Minimum loss reduction to make a split. Start with 0.lambda/reg_lambda(default: 1): L2 regularization on leaf weights. Rarely needs tuning.alpha/reg_alpha(default: 0): L1 regularization on leaf weights. Can produce sparser models.min_child_weight(default: 1): Minimum sum of Hessian in a leaf. Higher values prevent the model from learning outliers. For regression, values between 1–10 are typical.
Tier 3: Randomization parameters (tune last)
These introduce randomness to prevent overfitting:
subsample(default: 1.0): Fraction of training samples used per tree. Values 0.6–0.9 are common. Lower = more regularization.colsample_bytree(default: 1.0): Fraction of features used per tree. Values 0.5–0.9.colsample_bylevel(default: 1.0): Fraction of features used per split level.colsample_bynode(default: 1.0): Fraction of features used per split.
Subsampling at 0.8 + colsample_bytree at 0.8 is a common starting point that handles most overfitting without needing to touch gamma or lambda.
Recommended Tuning Sequence
import xgboost as xgb
from sklearn.model_selection import RandomizedSearchCV
# Step 1: Set baseline
params = {
'max_depth': 6,
'eta': 0.1,
'n_estimators': 500,
'subsample': 0.8,
'colsample_bytree': 0.8,
'eval_metric': 'logloss',
}
# Step 2: Tune max_depth and min_child_weight
param_grid_1 = {
'max_depth': [3, 4, 5, 6, 7, 8, 10],
'min_child_weight': [1, 3, 5, 7],
}
# Step 3: Tune gamma
param_grid_2 = {'gamma': [0, 0.1, 0.2, 0.5, 1.0]}
# Step 4: Tune subsample and colsample
param_grid_3 = {
'subsample': [0.6, 0.7, 0.8, 0.9, 1.0],
'colsample_bytree': [0.6, 0.7, 0.8, 0.9, 1.0],
}
# Step 5: Tune lambda and alpha (rarely needed)
param_grid_4 = {
'reg_lambda': [0.1, 0.5, 1, 5, 10],
'reg_alpha': [0, 0.1, 0.5, 1, 5],
}
Use RandomizedSearchCV rather than GridSearchCV for efficiency — Bergstra & Bengio showed random search finds better hyperparameters in fewer iterations when only a subset of parameters matter.
Early Stopping: Don’t Guess n_estimators
model = xgb.XGBClassifier(
n_estimators=2000,
early_stopping_rounds=50,
eval_set=[(X_valid, y_valid)],
verbose=False
)
model.fit(X_train, y_train)
print(f"Best iteration: {model.best_iteration}")
Early stopping eliminates the need to tune n_estimators — set it high (1000–5000) and let the validation set decide. early_stopping_rounds controls patience: how many rounds of no improvement before stopping. Values of 10–50 are typical; lower values risk stopping prematurely due to noise.
Special cases
Imbalanced classes
For classification with imbalanced classes, set scale_pos_weight to the ratio of negative to positive samples:
scale_pos_weight = len(y[y==0]) / len(y[y==1])
This is equivalent to giving more weight to the minority class and often outperforms SMOTE or random oversampling.
Very large datasets
For >1M rows, switch to tree_method='hist' for significantly faster training with minimal accuracy loss. For >10M rows, also enable grow_policy='lossguide' with max_leaves to control complexity:
params = {
'tree_method': 'hist',
'grow_policy': 'lossguide',
'max_leaves': 31,
}
Custom objectives
XGBoost supports custom objective functions via obj parameter:
def custom_squared_error(preds, dtrain):
labels = dtrain.get_label()
grad = 2 * (preds - labels) # gradient
hess = 2 * np.ones_like(labels) # hessian
return grad, hess
model = xgb.train({'verbosity': 0}, dtrain, obj=custom_squared_error)
The custom objective must return (gradient, hessian) as a tuple. This is how XGBoost supports Huber loss, quantile regression, and any other twice-differentiable loss function.