Использование Darts для оптимизации разработки анализа временных рядов Python

Введение

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

В случае классических задач классификации и регрессии scikit-learn может предоставить большинство утилит, которые могут нам понадобиться для начала работы с хорошим базовым уровнем (например, предварительная обработка данных). -обработка, модели с низким кодом, метрики оценки и т. д.), хотя с временными рядами дело обстоит совсем по-другому. За прошедшие годы стало доступно множество специализированных библиотек, охватывающих некоторые ключевые этапы рабочего процесса анализа временных рядов (например, статистические модели, Prophet , индивидуальное тестирование на истории и т. д.), но до Дартс было невозможно охватить все в одном решении.

Демонстрация

В рамках этой статьи мы рассмотрим практическую демонстрацию того, как использовать Darts для анализа набора временных рядов Delhi Daily Climate от Kaggle [1]. Весь код, использованный в этой статье (и не только!) доступен на моем GitHub и учетных записях Kaggle.

Прежде всего, нам нужно убедиться, что Darts установлен в нашей среде.

pip install darts

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

На данный момент мы готовы импортировать необходимые библиотеки и наборы данных (рис. 1). Чтобы облегчить наш анализ, столбец даты сначала преобразуется из строки в дату и время, а затем устанавливается в качестве индекса фрейма данных.

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import darts
from darts.ad import QuantileDetector

df = pd.read_csv('DailyDelhiClimateTrain.csv')
df["date"] = pd.to_datetime(df["date"])
df = df.set_index('date')
df.head(5)

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

ts = darts.TimeSeries.from_series(df.meantemp)
train, val = ts.split_before(0.75)
train.plot(label="Training Data")
val.plot(label="Validation Data")

Обнаружение аномалий

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

В приведенном ниже примере рассмотрение всего, что ниже 3 % и выше 97 %, в качестве выбросов приводит к общему проценту значений вне квантилей, равному 5,8 % (рис. 3).

anomaly_detector = QuantileDetector(low_quantile=0.03, high_quantile=0.97)
anomalies = anomaly_detector.fit_detect(ts)

l = anomalies.pd_series().values
print("Percentage of values outside quantiles:", 
      round(sum(l)/len(l)*100, 3), "%")

idx = pd.date_range(min(ts.pd_series().index), max(ts.pd_series().index))
anomalies = ts.pd_series()[np.array(l,dtype=bool)].reindex(idx,
                                                         fill_value=np.nan)
normal = ts.pd_series()[~np.array(l,dtype=bool)].reindex(idx, 
                                                         fill_value=np.nan)

normal.plot(color="black", label="Normal")
anomalies.plot(color="red", label="Anomalies")

Базовая модель

На данный момент мы готовы углубиться в наш временной ряд и проверить, присутствует ли в нем какая-либо форма сезонности. Как и ожидалось и показано в приведенном ниже фрагменте кода, похоже, что ряд статистически соответствует аналогичному сезонному шаблону примерно каждый год.

for m in range(2, 370):
        seasonal, period = darts.utils.statistics.check_seasonality(train, 
                                              m=m, max_lag=400, alpha=0.05)
        if seasonal:
            print("Seasonality of order:", str(period))
Seasonality of order: 354
Seasonality of order: 356
Seasonality of order: 361

Имея эту информацию, мы можем обучить первую наивную базовую модель, которая просто учитывает сезонный характер в ряду и никакой другой информации (рис. 4). При таком подходе MAPE (средняя абсолютная ошибка в процентах) составляет 11,35%. Два основных преимущества использования MAPE в качестве нашей оценочной метрики:

  • При использовании абсолютного значения положительные и отрицательные ошибки не компенсируются.
  • Ошибки не зависят от масштабирования зависимой переменной.
k = 361
naive_model = darts.models.NaiveSeasonal(K=k)
naive_model.fit(train)
naive_forecast = naive_model.predict(len(val))

print("MAPE: ", darts.metrics.mape(ts, naive_forecast))
ts.plot(label="Actual")
naive_forecast.plot(label="Naive Forecast (K=" + str(k) + ")")

Выбор статистических моделей

Имея хорошую базовую модель, мы готовы экспериментировать с некоторыми более продвинутыми методами (например, экспоненциальное сглаживание, ARIMA, AutoARIMA, Prophet). При необходимости многие дополнительные модели, такие как: CatBoost, Фильтры Калмана, случайные леса, рекуррентные нейронные сети и временные сверточные сети доступны как часть Darts.

def model_check(model):
    model.fit(train)
    forecast = model.predict(len(val))
    print(str(model) + ", MAPE: ", darts.metrics.mape(ts, forecast))
    return model

exp_smoothing = model_check(darts.models.ExponentialSmoothing())
arima = model_check(darts.models.ARIMA())
auto_arima = model_check(darts.models.AutoARIMA())
prophet = model_check(darts.models.Prophet())
ExponentialSmoothing(), MAPE:  37.758
ARIMA(12, 1, 0), MAPE:  41.819
Auto-ARIMA, MAPE:  32.594
Prophet, MAPE:  9.794

Учитывая приведенные выше результаты, Prophet кажется наиболее многообещающей из моделей, рассмотренных до сих пор в рамках анализа. В любом случае, при некоторой дополнительной работе результаты можно даже улучшить с помощью оптимизации гиперпараметров, особенно за счет использования преимуществ знаний в области бизнеса с традиционными статистическими моделями, такими как ARIMA и экспоненциальное сглаживание. Дополнительные сведения о том, как работает ARIMA, и его различных гиперпараметрах можно найти здесь.

Тестирование на истории

Чтобы еще больше подтвердить качество нашей модели, теперь мы можем протестировать ее, воспроизведя ее с использованием доступных исторических данных (рис. 5). В этом случае регистрируется MAPE 7,8%.

historical_fcast = prophet.historical_forecasts(ts,
                           start=0.6, forecast_horizon=30, verbose=True)

print("MAPE: ", darts.metrics.mape(ts, historical_fcast))
ts.plot(label="Actual")
historical_fcast.plot(label="Backtest 30 days ahead forecast")

Ковариационный анализ

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

В этом примере используется модель N-BEATS (временной ряд нейронного анализа расширения) со столбцами влажности и скорости ветра, используемыми в качестве прошлых ковариат (рис. 6).

humidity = darts.TimeSeries.from_series(df.humidity)
wind_speed = darts.TimeSeries.from_series(df.wind_speed)

cov_model = darts.models.NBEATSModel(input_chunk_length=361, 
                                     output_chunk_length=len(val))
cov_model.fit(train, past_covariates=humidity.stack(wind_speed))
cov_forecast = cov_model.predict(len(val), 
                               past_covariates=humidity.stack(wind_speed))

print("MAPE: ", darts.metrics.mape(ts, cov_forecast))
ts.plot(label="Actual")
cov_forecast.plot(label="Covariate Forecast")

В результате тренировочного процесса зарегистрирован показатель MAPE, равный 10,9%, что в данном случае уступает нашей исходной модели Prophet.

Контакты

Если вы хотите быть в курсе моих последних статей и проектов, подпишитесь на меня на Medium и подпишитесь на мой список рассылки. Вот некоторые из моих контактных данных:

Библиография

[1] Временные ряды ежедневных климатических данных (SUMANTHVRAO, Лицензия CC0: Public Domain). Доступ по адресу: https://www.kaggle.com/datasets/sumanthvrao/daily-climate-time-series-data?select=DailyDelhiClimateTrain.csv