The parameters that matter

CatBoost · Practical · 12 min

A handful of parameters account for most of the gains; knowing them saves hours of blind tuning.

CatBoost's defaults are tuned to be robust, so tune deliberately and in this rough order:

  • `learning_rate` and `iterations`: the step size of each tree and the number of trees. They trade off: halve the rate, roughly double the trees. Fix a reasonable rate (0.03 to 0.1) and let early stopping choose iterations.
  • `depth` (default 6): depth of each symmetric tree; a tree has 2^depth leaves. Typical range 4 to 10. Deeper trees capture more interactions but overfit faster and train slower.
  • `l2_leaf_reg` (default 3): L2 regularisation on leaf values. Increase it (5 to 30) when overfitting.
  • `random_strength`: noise added to split scores to diversify trees; higher values regularise.
  • Row sampling: bootstrap_type is MVS by default on CPU (Bayesian on GPU). Tune subsample for MVS and Bernoulli, or bagging_temperature for Bayesian, where higher means more aggressive sampling.
  • `border_count`: number of bins for numeric features (254 on CPU by default). Fewer bins train faster.
  • `one_hot_max_size`, `max_ctr_complexity`: control categorical encoding and feature combinations.

Speed knobs: thread_count (defaults to all cores), task_type="GPU", and keeping boosting_type="Plain" rather than "Ordered" on large data.

model.get_all_params() shows every value actually used, including those chosen automatically such as the learning rate. Always tune against a validation set or cross-validation, never the training score.

Example

from catboost import CatBoostRegressor
from sklearn.datasets import make_friedman1
from sklearn.model_selection import train_test_split

X, y = make_friedman1(n_samples=2000, noise=1.0, random_state=0)
X_tr, X_val, y_tr, y_val = train_test_split(X, y, random_state=0)

def val_rmse(**params):
    m = CatBoostRegressor(iterations=2000, early_stopping_rounds=100, verbose=0,
                          random_seed=0, allow_writing_files=False, **params)
    m.fit(X_tr, y_tr, eval_set=(X_val, y_val))
    return round(m.get_best_score()["validation"]["RMSE"], 3), m.tree_count_

print("default lr:", val_rmse())
print("lr=0.03:", val_rmse(learning_rate=0.03))          # (rmse, trees) to compare
print("depth=3:", val_rmse(depth=3))
print("depth=10:", val_rmse(depth=10))
print("l2=20:", val_rmse(l2_leaf_reg=20))

m = CatBoostRegressor(iterations=500, verbose=0, allow_writing_files=False).fit(X_tr, y_tr)
p = m.get_all_params()
print(p["depth"], p["l2_leaf_reg"], round(p["learning_rate"], 3))   # 6 3 <auto-chosen rate>

Remember

  • Fix a learning rate and let early stopping choose iterations
  • depth (4-10) and l2_leaf_reg are the main capacity/regularisation knobs
  • random_strength and row sampling (subsample, bagging_temperature) regularise
  • get_all_params shows the values actually used, including auto-chosen ones

Watch out for

  • Tuning learning_rate and iterations independently without early stopping, which mostly finds the same trade-off twice.

Check yourself

  1. Your model overfits: training loss keeps falling but validation loss rises early. Which change helps most directly?
  2. How many leaves does a CatBoost tree with depth=6 have?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: catboost.ai/docs/en/references/training-parameters/