Прогнозирование будущих значений с помощью RNN, LSTM и GRU с использованием PyTorch

Использование алгоритмов для прогнозирования будущих значений

В моем предыдущем сообщении в блоге я помог вам приступить к созданию некоторых рекуррентных нейронных сетей (RNN), таких как vanilla RNN, LSTM и GRU, с использованием PyTorch. Если вы еще не видели его, я настоятельно рекомендую вам сначала посмотреть его, так как я буду основываться на некоторых концепциях и коде, которые я там предоставил. Точно так же я буду придерживаться того же набора данных Почасовое потребление энергии PJM и в этом посте. В прошлый раз мы перестали делать прогнозы на тестовом наборе и оценивать производительность модели на основе фактических значений. Несмотря на хорошее начало, нам не хватало кусочка головоломки.

Что, если нас попросят сделать прогнозы для временных шагов, фактических значений которых у нас нет? Обычно это относится к прогнозированию временных рядов; мы начинаем с исторических данных временных рядов и предсказываем, что будет дальше. Этот пост покажет вам, как предсказывать будущие значения, используя RNN, LSTM и модель GRU, которую мы создали ранее. Так что, в отличие от того, этот будет относительно коротким — я надеюсь.

Прогнозирование с помощью функций DateTime

Чтобы генерировать прогнозы на будущее, я добавляю в класс Optimization новую функцию, которая, как и метод evaluation, принимает forecast_loader, batch_size и n_features и генерирует n_steps количество прогнозов в будущем. В отличие от оценки, сгенерированные прогнозы связаны не с фактическими значениями в DataFrame, а с количеством шагов, заданных в качестве входных данных. Конечно, это всего лишь один из способов, но вы также можете попробовать, ограничив свой прогноз датой.

В зависимости от частоты набора данных, с которым мы работаем, мы можем делать ежечасные, ежедневные или ежемесячные прогнозы. Один из способов сделать это — вручную передать эти аргументы нашим функциям прогнозирования. Ну, это не достаточно весело, я полагаю. Мы также можем написать простую функцию для получения даты начала и временной частоты набора данных. Он просто получает последнее значение DateTimeIndex и добавляет интервал времени между предыдущим индексом. Что касается частоты, аналогично, мы вернем временной интервал между этими двумя точками. Используя эти два значения, start_date и freq, мы можем сгенерировать DateTimeIndex, охватывающий период прогнозирования.

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

Теперь пришло время создать экземпляр DataLoader для прогнозируемых значений. Вы, возможно, уже задаетесь вопросом: "как, черт возьми, мы собираемся заполнить целевые значения (y) без фактических значений". Я нашел быстрое решение проблемы, просто установив значение для всех индексов прогноза до 0 и игнорирования тех, которые находятся на последних этапах.

Если вы проверите функцию в классе Optimization, называемую forecast_with_predictors, вы, вероятно, заметите, что я игнорирую часть целевых значений экземпляра DataLoader. И, честно говоря, я не уверен, что это лучшая практика, поэтому не стесняйтесь звонить мне, если вы можете придумать более элегантный способ решить эту проблему.

Затем мы вызываем функцию forecast_with_predictors модуля Optimization для создания прогнозов. Как и следовало ожидать, эта функция возвращает значения, масштабированные в соответствии с преобразованием, выбранным на предыдущем шаге.

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

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

Прогнозирование с запаздыванием

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

Опять же, мы добавляем еще один метод, forecast_with_lag, в класс Optimization для создания прогнозов с использованием скользящей логики.

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

*_, (X, y) = test_loader_iter

Следующая строка сдвигает значения второго измерения тензора на единицу, так что тензор [[[x1, x2, x3, ... , xn ]]] становится [[[xn, x1, x2, ... , x(n-1)]]].

X = torch.roll(X, shifts=1, dims=2)

Строка ниже выбирает первый элемент из последнего измерения трехмерного тензора и устанавливает для этого элемента предсказанное значение, хранящееся в NumPy ndarray (yhat), [[xn+1]]. Затем новый входной тензор становится [[[x(n+1), x1, x2, ... , x(n-1)]]]

X[..., -1, 0] = yhat.item(0)

Как и ранее, мы можем генерировать прогнозы для временных рядов с запаздывающими наблюдениями и отображать прогнозируемые будущие значения. Чтобы избежать избыточности, я поделюсь некоторыми примерами результатов только после нескольких периодов обучения. Но вы можете проверить, как я это делаю, в блокноте Google Colab, которым я поделился в этом посте.

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

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

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

Заключительные слова

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

Наконец, вот ссылка на блокнот Google Colab, если вы хотите посмотреть полный блокнот с новыми методами прогнозирования. В следующем посте я планирую рассмотреть некоторые методы регуляризации и их использование в прогнозировании временных рядов. Но посмотрим, как пойдет.