Cross-validation стратегии на собеседовании Data Scientist
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем разбирать на собесе
CV — основной инструмент честной оценки модели. На собесе DS обязательно: «как валидировать модель на временных рядах», «зачем stratified», «что такое group leakage». Уровень middle/senior — детали nested CV и custom CV для бизнес-задач.
Главная боль без понимания — DS использует случайный k-fold на временных рядах, получает 0.95 AUC на CV и 0.6 в проде.
Базовый k-fold
Делим данные на K частей. На каждой итерации тренируем на K-1, валидируем на одной. Усредняем метрику.
from sklearn.model_selection import KFold, cross_val_score
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='roc_auc')
print(scores.mean(), scores.std())Зачем shuffle=True. Если данные в таблице упорядочены (по дате, по классу), без shuffle K folds могут получиться смещёнными.
Сколько K. 5 или 10 — компромисс между bias и variance оценки. K=10 даёт меньше bias (модель тренируется на 90% данных), но дороже.
Stratified k-fold
При классификации с несбалансированными классами обычный k-fold может выдать fold, в котором почти нет minority class. Stratified k-fold сохраняет пропорцию классов в каждом fold.
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for train_idx, val_idx in skf.split(X, y):
...Дефолт для классификации. cross_val_score с классификатором использует stratified автоматически.
Расширение — StratifiedGroupKFold (sklearn ≥ 1.0): сохраняет пропорцию классов и при этом не разрывает группы.
Time-series CV
В временных рядах нельзя случайно перемешивать — это leakage. Будущие данные используются для тренировки, что в проде невозможно. Все CV-метрики надуты.
Корректная схема — expanding window:
fold 1: [train: 1-100][val: 101-110]
fold 2: [train: 1-110][val: 111-120]
fold 3: [train: 1-120][val: 121-130]
fold 4: [train: 1-130][val: 131-140]from sklearn.model_selection import TimeSeriesSplit
tss = TimeSeriesSplit(n_splits=5, gap=0, test_size=10)
for train_idx, val_idx in tss.split(X):
...Sliding window — фиксированная длина train (если хотите не учить на старых паттернах).
gap — буфер между train и val, важен, когда между ними может быть leakage (например, target известен с задержкой 7 дней, а val начинается через 1 день).
Признак-leakage помимо времени. Скользящие средние, lag-фичи на момент t не должны видеть данные > t. Считать фичи покадрово.
Group k-fold
Если в данных есть группы, которые нельзя разрывать между train и val — GroupKFold.
from sklearn.model_selection import GroupKFold
gkf = GroupKFold(n_splits=5)
for train_idx, val_idx in gkf.split(X, y, groups=user_ids):
...Когда нужен:
- Несколько событий на одного пользователя (предсказываем покупку — пользователь не должен попасть в train и val одновременно).
- Кросс-сессии (разные сессии одного юзера).
- Медицинские данные с пациентами.
- Несколько сэмплов на один документ / товар / адрес.
Без groups → leakage: модель учит «этот пользователь конвертит» вместо общего паттерна → CV-оценка надута.
Leave-one-out и leave-p-out
LOO (leave-one-out) — k = N. На каждой итерации train на N-1, val на 1.
from sklearn.model_selection import LeaveOneOut
loo = LeaveOneOut()Свойства:
- Bias оценки минимальный (модель тренируется почти на полных данных).
- Variance оценки максимальная (один объект — нестабильно).
- Стоимость огромная (N тренировок).
- Применяется на маленьких датасетах (< 1k объектов) или когда деление дорогое.
Leave-p-out — на каждой итерации val из p объектов. Комбинаторно дорого, редко применяется.
Nested CV для выбора гиперпараметров
Если CV используется и для выбора гиперпараметров, и для оценки качества — оценка предвзята (мы оптимизируем под валидацию).
outer fold (для оценки):
inner fold (для подбора гиперпараметров):
train inner / val inner
best params -> train на outer.train -> eval на outer.valfrom sklearn.model_selection import GridSearchCV, cross_val_score
inner_cv = KFold(n_splits=3)
outer_cv = KFold(n_splits=5)
clf = GridSearchCV(estimator=model, param_grid=grid, cv=inner_cv)
nested_scores = cross_val_score(clf, X, y, cv=outer_cv)
print(nested_scores.mean())Когда нужен. Маленькие датасеты, серьёзная статья, важна честная оценка обобщения. На больших данных — обычно одно train/val/test разбиение работает не хуже и дешевле.
Частые ошибки
Случайный shuffle на временных рядах. Главный баг с надутой метрикой. Всегда TimeSeriesSplit или ручной expanding window.
Не делать stratified для дисбаланса. Минорный класс может пропасть из fold.
Делить пользователей между train и val. Group leakage. Использовать GroupKFold.
Считать препроцессинг до CV. StandardScaler.fit(X) перед cross_val_score → leakage статистики val в train. Используй Pipeline — make_pipeline(StandardScaler(), model).
Tuning на test. Несколько раз пере-тюнили под test → test перестал быть честным. Финальная оценка только на отложенном hold-out.
Не учитывать gap. В time-series модель из train видит target с лагом, в проде такого не будет.
Считать LOO лучшим CV. На больших данных дорого без выгод. Variance оценки выше, чем у k-fold с k=5/10.
Использовать AUC на k-fold с очень маленьким classroomом. Если в fold лишь 1 положительный объект — AUC нестабильна. Stratified помогает.
Не фиксировать random_state. Каждый запуск даёт разные результаты — невоспроизводимо.
Связанные темы
- Bias-variance trade-off на собесе DS
- Time series CV и features на собесе DS
- Hyperparameter tuning на собесе DS
- Class imbalance на собесе DS
- Подготовка к собесу Data Scientist
FAQ
Сколько фолдов выбрать?
Дефолт 5 или 10. На маленьких датасетах (< 1k) — больше фолдов. На больших (> 100k) — 3-5 хватает, экономит время.
Stratified для регрессии существует?
Да, через бинаризацию таргета: разбить y на квантили и стратифицировать по корзинам. Полезно при skewed target.
k-fold можно для time-series?
Только если задача — predict over different users at random time, и время не критично. В предсказании будущего на основе прошлого — никогда.
Что такое purged k-fold?
Расширение time-series CV для финансовых данных: между train и val вводится gap (purge) и embargo, чтобы предсказания не использовали данные «слишком близко». Marcos López de Prado, «Advances in Financial ML».
Почему CV метрика выше прода?
Возможные причины: leakage (group/time), distribution shift между train и prod, target shift, изменение бизнеса. Чек: попробовать adversarial validation, проверить временной leakage.
Это официальная информация?
Нет. Статья основана на документации scikit-learn, Hastie 2009, и стандартных подходах в ML.
Тренируйте Data Science — откройте тренажёр с 1500+ вопросами для собесов.