Cross-validation простыми словами

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Cross-validation (CV) — базовая техника в ML для честной оценки модели. Если используете train-test split один раз, можете сильно зависеть от того, какая именно часть данных попала в test. CV разбивает данные на K частей и усредняет результат — получаете более robust оценку.

На собеседовании ML/DS это обязательная тема: «как оценить модель честно», «что такое K-Fold», «когда stratified CV», «как проверять time series models». Плюс CV нужна для hyperparameter tuning — иначе подстраиваетесь под test и снова overfit.

В статье:

  • Зачем нужна CV (простая аналогия)
  • K-Fold CV — базовый вариант
  • Stratified CV для классификации
  • Time series CV (специальная)
  • Leave-One-Out (когда применять)
  • Практика в Python

Короткое объяснение

Разбиваем данные на K частей. Обучаемся на K-1, тестируемся на 1. Повторяем K раз. Усредняем.

Так каждая строка попадает в test ровно 1 раз. Результат — честная оценка, не зависящая от случайного split.

Простой пример

1000 строк, K=5:

  • Fold 1: тест=rows 1-200, train=rows 201-1000 → accuracy 0.83
  • Fold 2: тест=rows 201-400, train=rest → accuracy 0.81
  • Fold 3: тест=rows 401-600, train=rest → accuracy 0.85
  • Fold 4: тест=rows 601-800, train=rest → accuracy 0.80
  • Fold 5: тест=rows 801-1000, train=rest → accuracy 0.82

CV score = 0.822. Плюс std = 0.019.

Если std большой → модель unstable, результаты случайны.

K-Fold в Python

from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(n_estimators=100)
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')

print(f"Accuracy: {scores.mean():.3f} ± {scores.std():.3f}")

Сколько K

  • K=5: default. Хороший баланс точности и compute.
  • K=10: чуть точнее, долго.
  • K=3: быстро, но менее надёжно.
  • Leave-One-Out (K=N): самый точный, но жутко медленный.

Для больших данных K=5 оптимально.

Stratified K-Fold

Для классификации с несбалансированным классами (например, 5% churn).

Обычный K-Fold может случайно дать fold с 0% churn. Stratified сохраняет пропорцию в каждом fold.

from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for train_idx, test_idx in skf.split(X, y):
    ...

Правило: для classification — всегда stratified.

Time Series CV

Для time series нельзя разбивать случайно — нельзя обучаться на будущем, чтобы предсказывать прошлое.

Правильный подход:

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
    # train_idx всегда до test_idx в хронологии
    ...

Структура:

Fold 1: train [0-200],   test [201-400]
Fold 2: train [0-400],   test [401-600]
Fold 3: train [0-600],   test [601-800]
Fold 4: train [0-800],   test [801-1000]

Leave-One-Out (LOO)

Extreme случай K = N. Каждая точка — test, остальные — train.

Плюсы: использует максимум данных для обучения. Минусы: очень медленно для большого N.

Используйте при маленьких датасетах (< 100 строк).

Hyperparameter tuning через CV

from sklearn.model_selection import GridSearchCV

params = {
    'max_depth': [3, 5, 7, 10],
    'min_samples_leaf': [1, 5, 10]
}

grid = GridSearchCV(RandomForestClassifier(), params, cv=5, scoring='accuracy')
grid.fit(X, y)

print(grid.best_params_)
print(grid.best_score_)

Это правильный способ подбирать hyperparameters — без cheating через test set.

Nested CV

Для unbiased оценки выбранной best модели:

  • Outer loop: 5-Fold для оценки
  • Inner loop: 5-Fold для hyperparameter tuning

Дорого, но строго.

Типичные ошибки

1. Data leakage

Нормализовать (StandardScaler.fit) на всём X перед CV → test data утекает в train.

Правильно: Pipeline([('scaler', StandardScaler()), ('model', ...)]) + cross_val_score.

2. Неслучайный порядок данных

Если данные отсортированы по target, первый fold может быть все 1, другой все 0. Используйте shuffle=True.

3. Stratified для регрессии

StratifiedKFold только для классификации. Для регрессии — обычный KFold.

4. CV на time series без time-aware split

Информация о будущем «течёт» в train. Обязательно TimeSeriesSplit.

На собесе

«Что такое cross-validation?» Разбиение данных на K частей, ротация train/test для unbiased оценки.

«Зачем 5 fold?» Баланс точности и compute. Для маленьких данных больше, для huge меньше.

«Как CV на time series?» TimeSeriesSplit — train всегда из прошлого.

«Pipeline + CV важно?» Да, чтобы избежать data leakage при preprocessing.

Связанные темы

FAQ

CV нужен для каждой модели?

Для robust оценки — да. Для quick prototyping — train-test split достаточно.

K=5 или K=10?

5 default. 10 для critical ML (medical, finance).

CV на огромных данных?

Overkill. Train-val-test split достаточно.

Как посчитать std в CV?

scores.std() — стандартное отклонение метрик по folds. Высокое std — unstable модель.


Тренируйте ML — откройте тренажёр с 1500+ вопросами для собесов.