Что такое cross-validation

Зачем нужна cross-validation

Обычный train/test split имеет проблему: метрика модели зависит от конкретного разделения. При одном split AUC 0.85, при другом 0.78. Какой оценке доверять?

Cross-validation (CV) решает это через повторные оценки на разных разделениях. Получаем несколько метрик и их среднее, что даёт более стабильный estimate качества модели.

Cross-validation — стандарт в ML. На соревнованиях Kaggle все winners используют CV для валидации своих моделей.

K-fold cross-validation

Самая распространённая схема. Данные делятся на k равных частей (folds). Каждая часть по очереди становится test set, остальные — train set.

K = 5 — стандарт. Данные режутся на 5 частей, модель обучается 5 раз. Получаем 5 метрик, считаем среднее и стандартное отклонение.

K = 10 — даёт больше вариативности, но дольше. Для маленьких датасетов лучше.

Leave-one-out — крайний случай, k = n. Каждый example проверяется отдельно. Очень затратно вычислительно, используется редко.

Stratified K-fold

Когда данные несбалансированы (например, 5% positive), обычный K-fold может создать folds, где в test set вообще нет positive. Это портит evaluation.

Stratified K-fold сохраняет пропорции классов в каждом fold. Если в общей выборке 5% positive, в каждом fold будет примерно столько же.

Для классификации stratified — default. Для регрессии используют обычный K-fold.

Time series cross-validation

Для временных рядов нельзя случайно перемешивать данные. Обучать модель на будущем и предсказывать прошлое — data leakage.

Time series CV работает по-другому. Первый fold — предсказание последнего периода на основе всех предыдущих. Второй — на основе всех кроме последнего. И так далее.

Fold 1: train [t1...t4], test [t5]
Fold 2: train [t1...t5], test [t6]
Fold 3: train [t1...t6], test [t7]

Это симулирует реальную production ситуацию, где вы всегда предсказываете future.

Больше таких примеров с разборами — в Telegram-тренажёре. Короткие сессии, прогресс по темам, объяснения после каждого ответа.

Пример в Python

Простой K-fold:

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

model = RandomForestClassifier()
scores = cross_val_score(model, X, y, cv=5, scoring='roc_auc')

print(f'Mean AUC: {scores.mean():.3f} (+/- {scores.std():.3f})')

Stratified K-fold:

from sklearn.model_selection import StratifiedKFold, cross_val_score

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='roc_auc')

Time series:

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(X):
    X_train, X_test = X[train_idx], X[test_idx]
    # train and evaluate

Зачем нужен средний и std

Cross-validation даёт не одну цифру, а распределение. Среднее — ваша оценка модели. Стандартное отклонение — насколько стабильна модель.

AUC 0.85 с std 0.01 — хорошая стабильная модель. AUC 0.85 с std 0.1 — модель нестабильна, на одних данных работает отлично, на других плохо. Это сигнал проблем (маленькая выборка, нестабильные features, overfitting).

Cross-validation для hyperparameter tuning

CV — основа правильного tuning гиперпараметров. Вместо одного train/validation split использовать K-fold для каждого hyperparameter combination.

Grid Search с CV:

from sklearn.model_selection import GridSearchCV

params = {
    'max_depth': [3, 5, 10],
    'min_samples_split': [2, 5, 10]
}

grid = GridSearchCV(model, params, cv=5, scoring='roc_auc')
grid.fit(X_train, y_train)

print(f'Best params: {grid.best_params_}')
print(f'Best CV AUC: {grid.best_score_:.3f}')

Для каждой combinati параметров делается 5-fold CV, выбирается лучшая.

Nested cross-validation

Более строгий подход. Когда вы используете CV для выбора hyperparameters, а потом для оценки модели — это может быть оптимистично.

Nested CV: outer loop для evaluation, inner loop для tuning. Дорого, но даёт самую честную оценку.

Используется в academic research и Kaggle. В production обычно хватает обычного CV.

Если готовишься к собесу — бот @kariernik_bot закрывает 80% технических вопросов. SQL, Python, A/B, продуктовые метрики — всё в одном месте.

Ограничения

CV дороже вычислительно. K-fold CV занимает в k раз больше времени. Для больших датасетов это может быть проблемой.

CV не защищает от всех проблем. Если в данных есть data leakage (target попал в features), CV его не поймает. Если данные сильно сдвинулись между train и production, CV может overstate качество.

Для очень маленьких датасетов (< 100) CV — обязательна, но результаты очень нестабильны. Надо признавать высокую variance.

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

Первая — делать feature engineering на всех данных, потом CV. Это leakage: скалирование и нормализация должны быть внутри fold.

# Правильно
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('model', RandomForestClassifier())
])
scores = cross_val_score(pipe, X, y, cv=5)

Вторая — использовать shuffle для time series. Data leakage в самом явном виде.

Третья — слишком мало folds. K = 2 даёт слишком variable оценку. Минимум K = 3, стандарт 5-10.

Четвёртая — игнорировать std. Одно среднее не даёт полной картины. Всегда показывайте std или CI.

Читайте также

FAQ

K = 5 или K = 10?

K = 5 — стандарт. K = 10 — для маленьких датасетов. K = 3 — если вычислительный бюджет ограничен.

CV нужна всегда?

Для ML-моделей на серьёзных задачах — да. Для быстрых прикидок хватает train/test split.

Почему CV может быть неправильной?

Data leakage в features, нарушение time dependencies, не учёт groups в данных (один пользователь может попасть в train и test).

CV заменяет test set?

Нет. CV — для выбора модели и hyperparameters. Финальная оценка — на отдельном test set, который не видела модель.