Условие
Почему random KFold не подходит для time series и как делать walk-forward validation?
Решение
Подход
Random KFold перемешивает наблюдения → train может содержать будущие точки → leakage. На time series CV должен уважать порядок времени.
Walk-forward (expanding window)
Окно train растёт, тестовое окно — следующий период.
[----train1----][test1]
[--------train2--------][test2]
[----------train3-----------][test3]
Walk-forward (sliding window)
Train фиксированной длины, скользит.
[----train1----][test1]
[----train2----][test2]
[----train3----][test3]
Реализация
from sklearn.model_selection import TimeSeriesSplit
import numpy as np
# Expanding
tscv = TimeSeriesSplit(n_splits=5, test_size=30) # 30 точек на test
for tr_idx, te_idx in tscv.split(X):
model.fit(X[tr_idx], y[tr_idx])
preds = model.predict(X[te_idx])
score(y[te_idx], preds)
# Sliding (вручную)
def sliding_cv(y, train_size=365, test_size=30, step=30):
n = len(y)
for start in range(0, n - train_size - test_size + 1, step):
tr = np.arange(start, start + train_size)
te = np.arange(start + train_size, start + train_size + test_size)
yield tr, teGap (purged CV)
При forecast h>1 между train и test должен быть gap = h, иначе target из test видится через лаги:
def purged_split(n, train_size, test_size, gap, step):
for start in range(0, n - train_size - gap - test_size + 1, step):
tr = np.arange(start, start + train_size)
te = np.arange(start + train_size + gap, start + train_size + gap + test_size)
yield tr, teЧто репортить
- Mean / median по folds (с CI).
- Каждый fold отдельно — видно стабильность.
- Trend в performance: если последние folds хуже — model drift.
scores = []
for tr, te in tscv.split(X):
model.fit(X[tr], y[tr])
s = mape(y[te], model.predict(X[te]))
scores.append(s)
print(f"Mean MAPE: {np.mean(scores):.2f}, std: {np.std(scores):.2f}")
print(f"Per fold: {scores}")Panel data (много серий)
GroupKFold по серии — не подходит для multi-series prediction. Лучше: train на 80% времени всех серий, test на 20% последних timesteps всех серий.
Подводные камни
shuffle=Trueв KFold на time series — самая типичная ошибка. Метрика переоценена на 20-50%.- Gap = horizon обязательно: при forecast h=7, между train и test должно быть 7 точек разрыва. Иначе таргет в test «протекает» через rolling features.
- Sliding vs expanding: sliding устойчив к concept drift (отрезает старые данные); expanding использует все данные.
- Hyperparameter tuning: nested CV — outer walk-forward, inner для tuning. Иначе tuning по test fold = leakage.
- Stationarity check: если серия меняется radically (COVID), CV scores нестабильны — отделите эту зону.
Эталонный ответ
Random KFold ломает time order → leakage. Walk-forward CV: expanding window (train растёт, sklearn TimeSeriesSplit) или sliding window. При forecast h>1 — gap = h между train и test (purged). Отчёт: mean ± std по folds, тренд per fold для отслеживания drift. Hyperparameter tuning — nested CV.