XGBoost Hyperparameters · A Practical Tuning Guide

13 min read

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.

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.