Применение сети LSTM для прогнозирования данных временных рядов

В этом посте я буду применять сеть с долговременной кратковременной памятью (LSTM) с использованием PyTorch для прогнозирования временных рядов на несколько периодов в будущем. Многие временные ряды демонстрируют сезонные тенденции, как мы можем видеть на графике выше. Например, объем продаж компании может увеличиваться каждый год в праздничный сезон. Это характерно для многих ритейлеров, от крупных компаний до местного воскресного рынка, чьи продажи выросли из-за рождественского сезона.

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

Данные

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

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

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

Подготовка данных для нашей модели

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

Затем я масштабировал обучающие данные, чтобы улучшить производительность модели. Используя MinMaxScaler из sklearn, мы можем легко применить обратное преобразование позже, когда захотим преобразовать прогнозы обратно в исходные единицы (в нашем случае доллары в миллиарды) 💵

Теперь давайте преобразуем масштабированные обучающие данные в Pytorch FloatTensor, чтобы в дальнейшем они были совместимы с нашей моделью.

Чтобы обучить нашу модель, нам нужно создать пары (x,y) из обучающих данных, где x — последовательность длины 16, а y — последовательность длины 8, которую нужно предсказать. Отметим здесь, что пара (x,y) означает, что последовательность y содержит восемь значений, которые следуют непосредственно за последовательностью x. Это важно, поскольку мы хотим использовать самые последние данные при прогнозировании следующих восьми квартальных значений.

Теперь, когда у нас есть пары (x,y), мы можем определить нашу модель LSTM и начать обучение! 🏃‍♂️

Определенная нами модель будет принимать три входа:

  1. input_size — этот вход представляет количество предикторов, которые мы используем в модели. В нашем случае мы используем только одну функцию — прошлый доход. Важно отметить, что, хотя мы используем последовательность значений дохода, это всего лишь одна функция, поэтому input_size равен 1.
  2. hidden_size — этот вход будет определять размер как скрытого состояния, так и состояния ячейки в модели LSTM (подробнее об этом см. здесь).
  3. output_size —вывод Pytorch LSTM представляет собой комбинацию скрытых состояний и состояния ячейки, и чтобы преобразовать их в нужный формат, мы должны использовать линейный слой. output_size сообщит нашей модели количество выходных данных, которые мы хотим получить от линейного слоя (в нашем случае восемь).

Определенная нами модель даст нам два результата:

  1. Во-первых, модель выведет последовательность длины 8, содержащую ее прогнозы доходов на следующие восемь кварталов.
  2. Во-вторых, он выведет self.hidden — кортеж из двух элементов. Первый элемент — это тензор, содержащий вычисленное финальное скрытое состояние. Второй элемент — это тензор, содержащий самую последнюю версию состояния ячейки. Для наших целей они нам не нужны, но могут быть полезны, если вы хотите делать дальнейшие прогнозы, сохраняя при этом предыдущий контекст.

Определив класс нашей модели, теперь нам нужно инициализировать экземпляр класса, а также решить, какой критерий и оптимизатор использовать. Поскольку здесь мы имеем дело с непрерывным выходом, мы можем использовать среднеквадратичную ошибку (MSE) в качестве нашей функции потерь. Работа оптимизатора заключается в настройке весов и смещений сети. Я решил использовать оптимизатор Адама, который является просто альтернативой стохастическому градиентному спуску.

Обучение

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

  1. Определите количество эпох — количество эпох — это гиперпараметр, который указывает, сколько раз процедура обучения будет использовать весь набор данных. Принятие решения о количестве эпох не является точной наукой. Полезным индикатором может быть наблюдение за убытком через определенные промежутки времени и определение момента, когда он больше не уменьшается. Выбор слишком большого количества эпох может привести к переоснащению обучающих данных.
  2. Для каждой пары (x,y) в каждую эпоху мы сначала вычисляем прогнозируемую последовательность ŷ.
  3. После этого мы вычисляем потери (MSE) между ŷ и истинными значениями y.
  4. Затем мы вычисляем градиенты параметров в нейронной сети, используя функцию Pytorch .backward().
  5. После вычисления градиентов последним шагом является соответствующее обновление параметров. Это делается с помощью функции Pytorch optimizer.step().
  6. Вышеуказанные шаги повторяются снова и снова, пока не будет достигнуто указанное количество эпох.
------- Output -------
epoch:    0 loss:0.34732404
epoch:  100 loss:0.00491276
epoch:  200 loss:0.00099649
epoch:  300 loss:0.00120929
epoch:  400 loss:0.00043398
epoch:  500 loss:0.00050389
epoch:  600 loss:0.00022331

Делать прогнозы

Обучив нашу модель, давайте посмотрим, что у нас получилось! Мы хотим спрогнозировать восемь квартальных доходов, опубликованных за 2018–2019 годы, используя шестнадцать значений, опубликованных за 2014–2017 годы.

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

Теперь, когда у нас есть прогнозируемые значения, мы можем сопоставить их с истинными значениями и посмотреть, смогла ли наша модель LSTM уловить тенденции в доходах компаний.

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

Заключение

Для тех, кто сделал это так далеко, я надеюсь, что вы узнали что-то новое! Если у вас есть какие-либо мысли или вопросы, пожалуйста, прокомментируйте ниже. Полный блокнот Jupyter для этого проекта доступен на моем Github здесь. 🪐

Для подробного описания того, как работают сети LSTM под капотом, ознакомьтесь с моей другой статьей 👇