Cross-validation and hyperparameter search

CatBoost · Practical · 12 min

Cross-validation gives reliable estimates on small data and tells you how many iterations to use when retraining.

`catboost.cv(pool, params, fold_count=5, ...)` runs k-fold cross-validation natively and returns a pandas DataFrame with the mean and standard deviation of each metric per iteration, for train and test folds. Combined with early_stopping_rounds, the row with the best mean test score tells you the iteration count to use when retraining on all the data. Pass stratified=True for classification and shuffle=True unless the data order matters; for time series use type="TimeSeries".

For hyperparameters, models have built-in search methods:

  • model.grid_search(param_grid, X, y, cv=3) tries every combination
  • model.randomized_search(param_distributions, X, y, n_iter=20) samples combinations

Both evaluate each candidate with cross-validation (or a train/test split), refit the model with the best parameters by default (refit=True), and return a dict containing params and cv_results.

For larger searches, Bayesian tuners such as Optuna or Hyperopt explore more efficiently; inside the objective, train with an eval set and early stopping and return the validation score. scikit-learn's GridSearchCV also works because CatBoost estimators follow the sklearn interface.

Tune a few impactful parameters (depth, l2_leaf_reg, learning_rate, random_strength, bagging_temperature) rather than everything. Small datasets show high variance across folds: compare the mean differences with the standard deviation before declaring a winner.

Example

from catboost import CatBoostClassifier, Pool, cv
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=1500, n_features=15, flip_y=0.05, random_state=0)
params = {"loss_function": "Logloss", "eval_metric": "AUC", "iterations": 1000,
          "learning_rate": 0.05, "random_seed": 0, "logging_level": "Silent"}

scores = cv(Pool(X, y), params, fold_count=5, stratified=True, shuffle=True,
            early_stopping_rounds=50)
best = scores["test-AUC-mean"].idxmax()
print(best, round(scores.loc[best, "test-AUC-mean"], 3),
      round(scores.loc[best, "test-AUC-std"], 3))   # e.g. 182 0.967 0.003

model = CatBoostClassifier(iterations=200, logging_level="Silent", random_seed=0,
                           allow_writing_files=False)
result = model.grid_search({"depth": [4, 6], "l2_leaf_reg": [1, 5]}, X, y,
                           cv=3, verbose=False, plot=False)
print(result["params"])                  # e.g. {'depth': 4, 'l2_leaf_reg': 1}
print(model.is_fitted())                 # True: refit with the best params

final = CatBoostClassifier(**{**params, "iterations": int(best) + 1}).fit(X, y)

Remember

  • catboost.cv returns per-iteration mean/std metrics across folds
  • The best mean iteration sets iterations when retraining on all data
  • grid_search/randomized_search evaluate with CV and refit the best model
  • Use Optuna or similar for larger searches; tune a few key parameters

Watch out for

  • Declaring one parameter set better when its advantage is smaller than the fold-to-fold standard deviation.

Check yourself

  1. What does the DataFrame returned by catboost.cv contain?

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

Official docs: catboost.ai/docs/en/concepts/python-reference_cv

Builds on

Unlocks

Nothing builds on this yet.