Новости и статьи об искусственном интеллекте и нейросетях. Мы собираем и обрабатываем самую актуальную информацию из мира AI. О проекте

Статьи

Прогнозирование временных рядов с sktime в Python

Разбираем библиотеку sktime для прогнозирования временных рядов. На примере данных с HVAC-датчика показана полная цепочка: обработка пропусков, устранение тренда и сезонности, построение конвейеров, сравнение ARIMA и экспоненциального сглаживания, а также кросс-валидация по времени.

17 июня 2026 г.
4 мин
35
Построение моделей машинного обучения для временных рядов с помощью sktime в Python

Введение

Если вы работаете с показаниями датчиков, метриками серверов или любыми данными, поступающими во времени, стандартные пайплайны scikit-learn не совсем подходят. Временные ряды обладают структурой, которую табличные модели игнорируют: сезонность, тренд, временная упорядоченность и зависимость будущих значений от прошлых.

sktime — это Python‑библиотека, созданная специально для таких задач. Она предоставляет API в стиле scikit-learn — fit, predict, transform — но спроектирована с нуля для временных рядов. С её помощью можно решать задачи прогнозирования, классификации, регрессии и кластеризации на временных рядах, пользуясь единообразным интерфейсом.

В этом материале мы разберём практический пример: прогнозирование температуры с промышленного датчика HVAC. Вы узнаете, как sktime обрабатывает временные ряды, как строить конвейеры предобработки, обучать прогнозные модели и оценивать их качество.

Код примеров доступен на GitHub.

Предварительные требования

Понадобится Python 3.10 или выше и базовое знакомство с pandas. Установите необходимые пакеты командой:

pip install sktime pmdarima statsmodels

Если хотите сразу получить все опциональные зависимости, используйте pip install sktime[all_extras].

Что делает sktime полезным

Стоит понять, какую проблему решает sktime. В scikit-learn данные представлены двумерной таблицей: строки — образцы, столбцы — признаки. Временные ряды нарушают это предположение, потому что каждая «строка» на самом деле является последовательностью значений во времени, и порядок этих значений имеет значение.

Основные типы данных, с которыми вы будете работать:

Тип данныхПредставлениеОписание
Серияpd.Series или pd.DataFrameОдиночный временной ряд, используется в обычном прогнозировании.
Панельpd.DataFrame с двухуровневым MultiIndexНабор нескольких независимых временных рядов.
Иерархическийpd.DataFrame с трёх- и более уровневым MultiIndexСтруктурированный набор временных рядов с уровнями агрегации по нескольким измерениям.

Для временной оси sktime поддерживает несколько типов индексов: DatetimeIndex, PeriodIndex, Int64Index и RangeIndex в объектах pandas. Индекс должен быть монотонным. При использовании DatetimeIndex желательно задать атрибут freq.

Подготовка набора данных

Создадим реалистичный набор данных. Представим датчик HVAC на заводе, который ежечасно фиксирует температуру. Показания имеют суточный сезонный паттерн (выше в рабочее время), небольшой повышающий тренд из-за летнего сезона и шум.

import numpy as np
import pandas as pd

np.random.seed(42)

# 90 дней ежечасных измерений, начиная с 1 января 2026 года
n_hours = 90 * 24
timestamps = pd.date_range(start="2026-01-01", periods=n_hours, freq="h")

# Тренд: постепенное повышение на 5 градусов за 90 дней
trend = np.linspace(0, 5, n_hours)

# Суточная сезонность: пик температуры в 14:00, спад в 4:00
hour_of_day = np.arange(n_hours) % 24
daily_cycle = 4 * np.sin(2 * np.pi * (hour_of_day - 4) / 24)

# Шум
noise = np.random.normal(0, 0.8, n_hours)

# Базовая температура около 20°C
temperature = 20 + trend + daily_cycle + noise

# Внесём несколько пропусков (отказ датчика)
dropout_indices = [300, 301, 302, 1440, 1441]
temperature[dropout_indices] = np.nan

y = pd.Series(temperature, index=timestamps, name="temp_celsius")
y.index.freq = pd.tseries.frequencies.to_offset("h")

print(y.head())
print(f"\nShape: {y.shape}")
print(f"Missing values: {y.isna().sum()}")
print(f"Index type: {type(y.index)}")

Результат:

2026-01-01 00:00:00    16.933270
2026-01-01 01:00:00    17.063277
2026-01-01 02:00:00    18.522783
2026-01-01 03:00:00    20.190095
2026-01-01 04:00:00    19.821941
Freq: h, Name: temp_celsius, dtype: float64

Shape: (2160,)
Missing values: 5
Index type: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>

Разделение временных рядов на обучающую и тестовую выборки

Разбиение временных рядов отличается от табличных данных — строки нельзя перемешивать. Деление всегда хронологическое: обучаем на более ранних данных, тестируем на более поздних.

Для этого в sktime есть функция temporal_train_test_split:

from sktime.split import temporal_train_test_split

# Оставляем последние 7 дней (168 часов) под тест
y_train, y_test = temporal_train_test_split(y, test_size=168)

print(f"Train: {y_train.index[0]} → {y_train.index[-1]}")
print(f"Test:  {y_test.index[0]} → {y_test.index[-1]}")
print(f"Train size: {len(y_train)}, Test size: {len(y_test)}")

Результат:

Train: 2026-01-01 00:00:00 → 2026-03-24 23:00:00
Test:  2026-03-25 00:00:00 → 2026-03-31 23:00:00
Train size: 1992, Test size: 168

Функция гарантирует чистое хронологическое разделение без утечки информации из будущего в обучающую выборку.

Определение горизонта прогнозирования

Перед обучением модели нужно указать sktime, какие временные шаги мы хотим предсказать. Для этого используется ForecastingHorizon.

from sktime.forecasting.base import ForecastingHorizon

# Прогнозируем 168 шагов вперёд (7 дней почасовых данных)
# is_relative=False означает использование абсолютных меток времени
fh = ForecastingHorizon(y_test.index, is_relative=False)

print(f"Horizon length: {len(fh)}")
print(f"First forecast point: {fh[0]}")
print(f"Last forecast point:  {fh[-1]}")

Результат:

Horizon length: 168
First forecast point: 2026-03-25 00:00:00
Last forecast point:  2026-03-31 23:00:00

Можно использовать и относительный горизонт вроде fh = [1, 2, 3, ..., 168], что означает «на 1 шаг вперёд, на 2 шага вперёд…». Абсолютный горизонт удобнее, когда есть конкретные временные метки для прогнозов.

Построение конвейера предобработки и прогнозирования

Реальные данные с датчиков содержат пропуски, сезонные закономерности и тренд — всё это нужно учесть до или во время прогнозирования. Класс TransformedTargetForecaster в sktime позволяет объединить преобразования и прогнозную модель в один оценщик. Преобразования применяются к целевой переменной y перед обучением, а на этапе предсказания автоматически обращаются.

from sktime.forecasting.exp_smoothing import ExponentialSmoothing
from sktime.forecasting.compose import TransformedTargetForecaster
from sktime.transformations.series.impute import Imputer
from sktime.transformations.series.detrend import Deseasonalizer, Detrender

pipeline = TransformedTargetForecaster(
    steps=[
        # Шаг 1: заполняем пропуски линейной интерполяцией
        ("imputer", Imputer(method="linear")),
        # Шаг 2: удаляем линейный тренд, чтобы прогнозист видел стационарный ряд
        ("detrender", Detrender()),
        # Шаг 3: убираем суточную сезонность (sp=24 для почасовых данных с 24‑часовым циклом)
        ("deseasonalizer", Deseasonalizer(model="additive", sp=24)),
        # Шаг 4: прогнозируем очищенные стационарные остатки
        ("forecaster", ExponentialSmoothing(trend=None, seasonal=None)),
    ]
)

pipeline.fit(y_train, fh=fh)
y_pred = pipeline.predict()

print(y_pred.head())

Результат:

2026-03-25 00:00:00    21.210066
2026-03-25 01:00:00    21.788986
2026-03-25 02:00:00    22.615184
2026-03-25 03:00:00    23.688449
2026-03-25 04:00:00    24.621127
Freq: h, Name: temp_celsius, dtype: float64

Что делает каждый шаг:

  • Imputer(method="linear") — заполняет пропуски линейной интерполяцией между соседними значениями, что хорошо работает для сенсорных данных.
  • Detrender() — подгоняет линейный тренд к обучающему ряду и вычитает его; при прогнозировании тренд добавляется обратно.
  • Deseasonalizer(sp=24) — удаляет 24‑часовой цикл из остатков; sp обозначает сезонный период.
  • Наконец, ExponentialSmoothing строит прогноз по очищенным от тренда и сезонности остаткам.
  • При вызове predict() все обратные преобразования применяются автоматически в обратном порядке, и на выходе получаются прогнозы в исходной шкале температур.

Оценка прогноза

Sktime интегрирован со стандартными метриками оценки. Для прогнозирования часто выбирают среднюю абсолютную ошибку (MAE) и среднюю абсолютную процентную ошибку (MAPE).

from sktime.performance_metrics.forecasting import (
    mean_absolute_error,
    mean_absolute_percentage_error,
)

mae = mean_absolute_error(y_test, y_pred)
mape = mean_absolute_percentage_error(y_test, y_pred)

print(f"MAE: {mae:.3f} °C")
print(f"MAPE: {mape*100:.2f}%")

Результат:

MAE: 0.584 °C
MAPE: 2.40%

Замена модели прогнозирования

Одно из главных преимуществ интерфейса sktime — чтобы сменить алгоритм, достаточно изменить одну строку. Попробуем модель ARIMA вместо экспоненциального сглаживания и сравним результаты.

from sktime.forecasting.arima import ARIMA

pipeline_arima = TransformedTargetForecaster(
    steps=[
        ("imputer", Imputer(method="linear")),
        ("detrender", Detrender()),
        ("deseasonalizer", Deseasonalizer(model="additive", sp=24)),
        # ARIMA(1,1,1) на очищенных остатках
        ("forecaster", ARIMA(order=(1, 1, 1), suppress_warnings=True)),
    ]
)

pipeline_arima.fit(y_train, fh=fh)
y_pred_arima = pipeline_arima.predict()

mae_arima = mean_absolute_error(y_test, y_pred_arima)
mape_arima = mean_absolute_percentage_error(y_test, y_pred_arima)

print(f"ARIMA MAE: {mae_arima:.3f} °C")
print(f"ARIMA MAPE: {mape_arima*100:.2f}%")

Результат:

ARIMA MAE: 0.586 °C
ARIMA MAPE: 2.41%

Ключевой момент: шаги предобработки — заполнение пропусков, удаление тренда и сезонности — остались неизменными. Поменялся только финальный прогнозист, а всё остальное аккуратно скомпоновалось вокруг него.

Кросс-валидация по времени

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

SlidingWindowSplitter использует скользящее окно: обучающая выборка сдвигается вперёд, сохраняя постоянную длину. ExpandingWindowSplitter наращивает обучающую выборку по мере продвижения, что более уместно, когда хочется использовать всю доступную историю.

from sktime.split import ExpandingWindowSplitter
from sktime.forecasting.model_evaluation import evaluate

# Расширяющееся окно: начинаем с 1800 часов обучения, оцениваем на окнах по 168 часов
cv = ExpandingWindowSplitter(
    initial_window=1800,
    fh=list(range(1, 169)),
    step_length=168,
)

results = evaluate(
    forecaster=pipeline,
    y=y,
    cv=cv,
    scoring=mean_absolute_error,
    return_data=False,
)

print(results[["test__DynamicForecastingErrorMetric", "fit_time"]].round(3))
print(f"\nMean CV MAE: {results['test__DynamicForecastingErrorMetric'].mean():.3f} °C")

Результат:

   test__DynamicForecastingErrorMetric  fit_time
0                                0.627     0.274
1                                0.585     0.100

Mean CV MAE: 0.606 °C

Функция evaluate возвращает DataFrame с метриками и временем для каждого фолда. Усреднённая по кросс-валидации MAE подтверждает, что модель стабильно обобщается на разные временные окна данных.

Следующие шаги

В статье был рассмотрен основной конвейер прогнозирования в sktime, но возможности библиотеки выходят далеко за рамки простых предсказательных задач.

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

Одна из сильнейших сторон sktime — согласованный API и интеграция с обширной экосистемой машинного обучения Python, что упрощает эксперименты и новичкам, и опытным специалистам. Документация sktime и примеры в блокнотах написаны особенно хорошо — их стоит добавить в закладки, если вы регулярно сталкиваетесь с прогнозированием или задачами на временных рядах.

Горячее

Загружаем популярные статьи...

Прогнозирование временных рядов с sktime в Python