Cross-validation

scikit-learn · Practical · 11 min

A single split is noisy; cross-validation gives a more reliable score and its spread.

k-fold cross-validation splits the training data into k folds, trains on k-1 of them and validates on the remaining one, k times. You get k scores; their mean is the estimate and their spread tells you how much to trust it.

  • cross_val_score(model, X, y, cv=5, scoring="roc_auc") returns the array of scores.
  • cross_validate(...) returns a dict with fit times, scores for several metrics (scoring=["accuracy", "f1"]), optional training scores and fitted estimators.
  • cross_val_predict(...) returns out-of-fold predictions for every sample, handy for plots and error analysis but not a substitute for a score.

With an integer cv and a classifier, scikit-learn uses StratifiedKFold, which keeps class proportions in each fold; for regressors it uses KFold. Neither shuffles by default. If your rows are sorted (by date, by class), pass a splitter explicitly: KFold(5, shuffle=True, random_state=0).

Pass the whole pipeline as the model, so preprocessing is refitted inside each fold. Cross-validation is for choosing models and hyperparameters on the training set; the final test set is still kept aside for one last check.

n_jobs=-1 runs folds in parallel. Five or ten folds is typical; more folds means less bias but more computation and noisier individual scores.

Example

from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, cross_validate, StratifiedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_breast_cancer(return_X_y=True)
pipe = make_pipeline(StandardScaler(), LogisticRegression())

scores = cross_val_score(pipe, X, y, cv=5)
print(scores.round(3), scores.mean().round(3))
# [0.982 0.982 0.974 0.974 0.991] 0.981

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
res = cross_validate(pipe, X, y, cv=cv, scoring=["accuracy", "roc_auc"])
print(sorted(res))  # ['fit_time', 'score_time', 'test_accuracy', 'test_roc_auc']
print(res["test_roc_auc"].mean().round(3))  # 0.995

Remember

  • Report the mean and the spread of fold scores
  • Integer cv on a classifier means stratified folds without shuffling
  • Cross-validate the whole pipeline, not pre-transformed data

Watch out for

  • Using cross_val_predict output to compute a single score and calling it the CV score.

Check yourself

  1. You call cross_val_score(clf, X, y, cv=5) on a classifier where X is sorted by class. What splitter is used?

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

Official docs: scikit-learn.org/stable/modules/cross_validation.html