Amazon GluonTS: Deep Learning для временных рядов

В июне 2019 года компания Amazon выложила в открытый доступ замечательный инструмент — GluonTS, позволяющий максимально быстро и эффективно строить, оценивать и использовать модели временных рядов, основанные на глубоком обучении и вероятностном подходе.

До этого момента одним из самых известных релизов открытой библиотеки для работы с временными рядами был Facebook Prophet, выпущенный в феврале 2017 года. В отличие от GluonTS, Facebook использовал более простой метод — аддитивную нелинейную модель временного ряда. Эта модель давала вполне адекватные результаты при минимуме затраченных усилий, однако не очень хорошо справлялась с данными, где отсутствовала ярко выраженная сезонность.

Что внутри GluonTS?

Amazon пошёл дальше и использовал рекуррентные нейронные сети (в частности, LSTM), а также свёртки и механизмы внимания, обернув всё это в крайне удобную верхнеуровневую библиотеку. В частности, GluonTS содержит: — инструменты, необходимые для построения и обучения наиболее распространённых архитектур нейронных сетей, а также компоненты для моделирования и трансформации вероятностных распределений; — механизмы для загрузки и предварительной обработки данных, в том числе автоматической генерации признаков из временных рядов; — несколько готовых к использованию state-of-the-art прогнозных моделей; — инструменты для оценки и сравнения различных моделей.

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

Пример на реальных данных

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

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

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('currency.csv', index_col=0)
df.index = pd.to_datetime(df.index)

df[:100].plot(linewidth=2, figsize=(17, 7))
plt.grid(axis='x')
plt.legend()
plt.show()

Во временном ряду хорошо видна 30-дневная цикличность, связанная с определёнными внунтриигровыми событиями, а также ярко выраженная недельная сезонность — по выходным игроки больше тратят валюту, а по будням активность спадает.

Попробуем построить модель. GluonTS предоставляет верхнеуровневую абстрацию Dataset, которая переводит разнородные форматы данных в один, удобный для последующей работы моделей. В частности, ListDataset переводит данные в список словарей, где отдельно записаны значения ряда и таймстэмпы. Для создания такого датасета мы передаём наш исходный временной ряд, указываем его частоту (в данном случае у нас дневные данные, поэтому частота "D"), а также точку, до которой наш ряд будет отнесён к тренировочной выборке:

from gluonts.dataset.common import ListDataset
training_data = ListDataset(
    [{"start": df.index[0], "target": df.currency_spent[:"2017-08-01"]}],
    freq = "D"
)

Посмотрим, в какой формат преобразовались данные:

training_data.list_data

[Out:] [{'start': Timestamp('2017-05-01 00:00:00', freq='D'),
  'target': array([1199436., 1045515.,  586111.,  856601.,  793775.,  606535.,
         1112763., 1121218.,  813844.,  903343.,  863465.,  639224.,
         1030389., 1132645., 1018672., 1726870., 1378430.,  532950.,
          828238.,  823948.,  592549.,  939337.,  862611.,  551557.,
          878375.,  784535.,  613603., 1054658., 1026401.,  682284.,
          986644.,  924769.,  633489., 1044957., 1088685.,  798582.,
         1139786., 1066560.,  754706., 1199406., 1186341.,  958210.,
         1564553., 1470865., 1201275., 2418723., 2123070.,  978338.,
         1536623., 1420586.,  966259., 1232735., 1090762.,  763828.,
         1153383., 1074039.,  733943., 1103070., 1123779.,  752524.,
         1123866., 1051964.,  756827., 1109486., 1059961.,  691291.,
          985221.,  932805.,  641340., 1038572., 1037868.,  732303.,
          962492.,  875898., 1029902., 1917268., 1662445.,  791812.,
         1061339.,  968767.,  685321., 1020324.,  995864.,  785353.,
         1192613., 1068292.,  710820., 1048429.,  991163.,  701672.,
         1239717., 1261953.,  857930.], dtype=float32),
  'source': SourceContext(source='list_data', row=1)}]

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

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

В качестве модели возьмём рекомендованную архитектуру, основанную на глубокой авторегрессионной модели (DeepAR). Как и при создании датасета, нужно не забыть указать частоту данных, на которой предстоит тренироваться. Также зададим число эпох, равное 20, а горизонт прогноза (т. е. на сколько шагов вперед модель должна уметь прогнозировать) — равным 30 дням.

from gluonts.model.deepar import DeepAREstimator
from gluonts.trainer import Trainer

estimator = DeepAREstimator(
    freq="D", 
    prediction_length=30, 
    trainer=Trainer(epochs=20)
)
predictor = estimator.train(training_data=training_data)

После непродолжительной тренировки модель готова и можно смотреть на прогноз. Снова создадим ListDataset, на сей раз для отложенной выборки, и воспользуемся удобной утилитой to_pandas, чтобы перевести результаты прогноза в удобный для визуализации формат:

from gluonts.dataset.util import to_pandas

test_data = ListDataset(
    [{"start": df.index[0], "target": df.currency_spent[:"2017-08-01"]}],
    freq = "D"
)

for test_entry, forecast in zip(test_data, predictor.predict(test_data)):
    to_pandas(test_entry).plot(linewidth=2, figsize=(15, 7), label="historical values")
    forecast.plot(color='g', prediction_intervals=[50.0, 90.0], label="forecast")

plt.legend(loc='upper left')
plt.grid(axis='x')

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

Вывод

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

Подробнее о настройке гиперпараметров и сравнении качества GluonTS с другими методами прогнозирования временных рядов вы сможете узнать на занятии «Анализ временных рядов» курса «Machine Learning».