← Назад к вопросам

Какой метод перекрёстной проверки вы бы использовали для набора данных временных рядов?

1.7 Middle🔥 161 комментариев
#Временные ряды#Метрики и оценка моделей

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какой метод перекрёстной проверки вы бы использовали для набора данных временных рядов?

Обычная k-fold кросс-валидация категорически запрещена для временных рядов (time series), так как она нарушает временной порядок данных и приводит к информационной утечке из будущего в прошлое (data leakage). Вместо этого используются специализированные методы, которые уважают временную последовательность.

Почему обычная k-fold не работает для временных рядов

# НЕПРАВИЛЬНО: k-fold нарушает временной порядок
train_indices = [0, 2, 4, 6, 8]  # Случайная выборка
test_indices = [1, 3, 5, 7, 9]

# На обучении модель видит будущие значения (5, 6, 7, 8, 9)
# На тесте проверяем на прошлые значения (1, 3, 5)
# Это нереалистично и приводит к завышению точности!

1. Walk-Forward Validation (Временный порядок)

Самый важный и правильный метод. Обучаются на прошлых данных, тестируются на будущих.

# Визуально:
Fold 1: [Train: 0-20] [Test: 21-25]
Fold 2: [Train: 0-25] [Test: 26-30]
Fold 3: [Train: 0-30] [Test: 31-35]

Тренировочное окно расширяется со временем

Реализация:

from sklearn.model_selection import TimeSeriesSplit
from sklearn.linear_model import LinearRegression
import numpy as np

# Генерируем временной ряд
np.random.seed(42)
X = np.arange(100).reshape(-1, 1)
y = np.sin(X.ravel() / 10) + np.random.normal(0, 0.1, 100)

# TimeSeriesSplit — встроенный метод для walk-forward
tscv = TimeSeriesSplit(n_splits=5)

for fold, (train_idx, test_idx) in enumerate(tscv.split(X)):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    
    model = LinearRegression()
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    
    print(f"Fold {fold+1}: Train={train_idx}, Test={test_idx}, Score={score:.4f}")

# Результат:
# Fold 1: Train=[ 0 ... 19], Test=[20 ... 39], Score=0.9234
# Fold 2: Train=[ 0 ... 39], Test=[40 ... 59], Score=0.9156
# Fold 3: Train=[ 0 ... 59], Test=[60 ... 79], Score=0.8925
# Fold 4: Train=[ 0 ... 79], Test=[80 ... 99], Score=0.8112

2. Expanding Window (Растущее окно)

Тренировочное окно растёт, тестовое остаётся фиксированным размером.

# Визуально:
Fold 1: [Train: 0-10]     [Test: 11-20]
Fold 2: [Train: 0-20]     [Test: 21-30]
Fold 3: [Train: 0-30]     [Test: 31-40]
Fold 4: [Train: 0-40]     [Test: 41-50]

# Реализация
def expanding_window_cv(X, y, window_size=10, test_size=10):
    for start in range(len(X) - window_size - test_size):
        train_end = start + window_size
        test_end = train_end + test_size
        
        X_train = X[start:train_end]
        y_train = y[start:train_end]
        X_test = X[train_end:test_end]
        y_test = y[train_end:test_end]
        
        yield X_train, X_test, y_train, y_test

# Использование
for fold, (X_train, X_test, y_train, y_test) in enumerate(expanding_window_cv(X, y)):
    model = LinearRegression()
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    print(f"Fold {fold+1}: Score={score:.4f}")

3. Sliding Window (Скользящее окно)

Оба окна (тренировка и тест) движутся по времени с фиксированным размером.

# Визуально:
Fold 1: [Train: 0-20]  [Test: 21-30]
Fold 2: [Train: 11-30] [Test: 31-40]
Fold 3: [Train: 21-40] [Test: 41-50]

# Реализация
def sliding_window_cv(X, y, train_size=20, test_size=10):
    for start in range(0, len(X) - train_size - test_size, 5):  # шаг=5
        train_end = start + train_size
        test_end = train_end + test_size
        
        X_train = X[start:train_end]
        y_train = y[start:train_end]
        X_test = X[train_end:test_end]
        y_test = y[train_end:test_end]
        
        yield X_train, X_test, y_train, y_test

# Использование
for fold, (X_train, X_test, y_train, y_test) in enumerate(sliding_window_cv(X, y)):
    model = LinearRegression()
    model.fit(X_train, y_train)
    score = model.score(X_test, y_test)
    print(f"Fold {fold+1}: Score={score:.4f}")

4. Backtesting (для финансовых данных)

Специализированный метод для торговых стратегий.

# Визуально:
Fold 1: [Train: 0-252]  [Test: 253-281]
Fold 2: [Train: 0-281]  [Test: 282-310]
Fold 3: [Train: 0-310]  [Test: 311-339]

# где 252 = кол-во торговых дней в году

def backtesting_cv(data, train_years=3, test_months=1):
    train_size = train_years * 252  # дней в году
    test_size = test_months * 21    # торговых дней в месяце
    
    for start in range(0, len(data) - train_size - test_size, test_size):
        train_end = start + train_size
        test_end = train_end + test_size
        
        yield (data.iloc[start:train_end], data.iloc[train_end:test_end])

Сравнение методов

МетодПрименениеРазмер TrainРазмер TestПримеры
Walk-ForwardОбщий случайРастётФиксированARIMA, XGBoost
ExpandingМодели нуждаются больше историиРастёт максимальноФиксированLSTM, Prophet
SlidingКогда история должна быть ограниченаФиксированФиксированАвтотрейдинг
BacktestingФинансовые предсказанияФиксирован (годы)Фиксирован (месяцы)Прогноз акций

Практический пример с scikit-learn

from sklearn.model_selection import TimeSeriesSplit
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
import numpy as np

# Данные (100 наблюдений)
X = np.arange(100).reshape(-1, 1)
y = np.sin(X.ravel() / 10) * 10 + np.random.normal(0, 1, 100)

# 5-fold time series cross-validation
tscv = TimeSeriesSplit(n_splits=5)

scores = []
for train_idx, test_idx in tscv.split(X):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    
    # Обучить модель
    model = RandomForestRegressor(n_estimators=10, random_state=42)
    model.fit(X_train, y_train)
    
    # Оценить
    y_pred = model.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    mae = mean_absolute_error(y_test, y_pred)
    
    scores.append({'MSE': mse, 'MAE': mae})
    print(f"MSE: {mse:.4f}, MAE: {mae:.4f}")

# Средние результаты
avg_mse = np.mean([s['MSE'] for s in scores])
avg_mae = np.mean([s['MAE'] for s in scores])
print(f"\nAverage MSE: {avg_mse:.4f}, Average MAE: {avg_mae:.4f}")

КРИТИЧЕСКАЯ РЕКОМЕНДАЦИЯ

# НЕПРАВИЛЬНО для временных рядов:
from sklearn.model_selection import KFold, cross_val_score
kf = KFold(n_splits=5, shuffle=True)  # ✗ shuffle=True нарушает порядок!

# ПРАВИЛЬНО:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)    # ✓ Соблюдает временной порядок
Какой метод перекрёстной проверки вы бы использовали для набора данных временных рядов? | PrepBro