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+ вопросами для собесов.