Как настроить валидацию для моделей временных рядов?

Ответ

Для временных рядов стандартная случайная разбивка данных недопустима, так как нарушает временную зависимость. Необходимо использовать методы, сохраняющие порядок наблюдений.

1. Разделение по времени (Time-based Hold-out) Самый простой метод — выделить последний отрезок ряда в качестве тестовой выборки.

split_index = int(len(series) * 0.8)  # 80% на обучение, 20% на тест
train, test = series[:split_index], series[split_index:]

2. Перекрестная проверка с расширяющимся окном (Expanding Window CV) Обучающая выборка постоянно увеличивается, тестовое окно сдвигается вперед. Реализуется через TimeSeriesSplit в scikit-learn.

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(series):
    X_train, X_test = series.iloc[train_index], series.iloc[test_index]
    # Обучить модель на X_train, оценить на X_test

3. Перекрестная проверка со скользящим окном (Sliding Window CV) Размер обучающего окна фиксирован, окно "скользит" по ряду. Полезно для моделирования, где актуальна только недавняя история.

def sliding_window_cv(series, train_size, test_size, step=1):
    n_splits = (len(series) - train_size - test_size) // step + 1
    for i in range(0, n_splits * step, step):
        train_end = i + train_size
        test_end = train_end + test_size
        yield series.iloc[i:train_end], series.iloc[train_end:test_end]

# Пример: окно обучения = 100 точек, окно теста = 20 точек
for train, test in sliding_window_cv(series, train_size=100, test_size=20, step=20):
    # ... обучение и оценка

Ключевые принципы:

  • Сохранение порядка: Данные для обучения всегда должны предшествовать данным для тестирования.
  • Учет сезонности: При разбиении границы фолдов должны проходить между сезонами, а не разрезать их.
  • Многократное тестирование: Использование нескольких фолдов дает более надежную оценку устойчивости модели к разным временным периодам.

Ответ 18+ 🔞

Слушай, а вот это реально важный момент, который многие на разрыв жопы забывают. Представь, ты пытаешься предсказать завтрашнюю цену на нефть, а для обучения берёшь данные за 2023 год, а для теста — за 2020-й. Это же ёперный театр! Получится, что твоя модель, по сути, заглядывает в будущее, чтобы угадать прошлое. Пизда рулю такой модели, доверия к ней — ноль ебать. Временные ряды — они как жизнь, идут строго из прошлого в будущее, и нарушать этот порядок — себя же и обманывать.

Так что, чувак, забудь про стандартный train_test_split с shuffle=True. Тут нужны другие подходы, которые временную структуру не ломают.

1. Просто отрежь по времени (Time-based Hold-out) Самый понятный метод, его хоть сейчас в дело. Берёшь ряд, и просто, блядь, отрезаешь последний кусок под тест. Как будто последние 20% времени — это неизвестное будущее, которое надо спрогнозировать.

split_index = int(len(series) * 0.8)  # 80% на обучение, 20% на тест
train, test = series[:split_index], series[split_index:]

Всё, просто и без затей. Но есть нюанс: оценка модели будет зависеть только от одного, последнего периода. А если в этот период случилась какая-нибудь ядрёна вошь вроде кризиса? Модель может показать себя хуже некуда, хотя в целом она норм.

2. Кросс-валидация с растущим окном (Expanding Window CV) Вот это уже серьёзнее. Суть: обучающая выборка с каждым новым фолдом как бы "разрастается", поглощая прошлое, а тестовое окошко сдвигается вперёд. В scikit-learn для этого есть готовый инструмент.

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(series):
    X_train, X_test = series.iloc[train_index], series.iloc[test_index]
    # Обучаешь модель на X_train, оцениваешь на X_test

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

3. Кросс-валидация со скользящим окном (Sliding Window CV) А это для прагматиков. Тут размер обучающего окна фиксированный. Окно "скользит" по ряду, и ты каждый раз учишься только на последних N наблюдениях. Идеально, если твоя модель должна реагировать на недавние тренды, а древняя история уже не актуальна.

def sliding_window_cv(series, train_size, test_size, step=1):
    n_splits = (len(series) - train_size - test_size) // step + 1
    for i in range(0, n_splits * step, step):
        train_end = i + train_size
        test_end = train_end + test_size
        yield series.iloc[i:train_end], series.iloc[train_end:test_end]

# Пример: учимся на 100 точках, тестируем на 20, шаг = 20
for train, test in sliding_window_cv(series, train_size=100, test_size=20, step=20):
    # ... обучение и оценка

Типа "забудь, что было год назад, смотри на последние три месяца". Часто в трейдинге так делают.

Главные правила, которые в голове держать надо:

  • Порядок — святое: Обучение всегда должно быть ДО теста во времени. Никаких скачков туда-сюда. Это аксиома, ёпта.
  • Сезонность не рви: Если у тебя данные с сезонностью (продажи мороженого летом), резать надо между сезонами. Нельзя разрезать июнь пополам, это пиздец как неверно. Нужно, чтобы модель училась на полных циклах.
  • Тестируй много раз: Один фолд — это как один бросок костей. Сделай несколько фолдов (скользящих или расширяющихся), чтобы понять, как модель ведёт себя на разных временных отрезках. Так надёжнее, волнение ебать меньше будет, когда в продакшн запускать.