Прогнозирование временных рядов с использованием Pytorch
Прогнозирование временных рядов играет важную роль в анализе данных, имея приложения, начиная от прогнозирования трендов на фондовом рынке и заканчивая прогнозированием погоды. В данной статье мы погрузимся в область прогнозирования временных рядов, используя PyTorch и нейронные сети LSTM (Long Short-Term Memory). Мы также рассмотрим критически важные этапы предварительной обработки данных, от которых зависит точность наших прогнозов.
Временные ряды - это, по сути, набор наблюдений, проводимых через регулярные промежутки времени. Прогнозирование временных рядов пытается оценить будущие значения на основе обнаруженных в исторических данных закономерностей и тенденций.
Скользящие средние и традиционные подходы вроде ARIMA испытывают трудности с захватом долгосрочных зависимостей в данных. LSTM — это тип рекуррентной нейронной сети, который преуспевает в захвате зависимостей через время и способен улавливать сложные паттерны.
В этой статье мы будем использовать Pytorch для прогнозирования временных рядов.
Здесь мы использовали Yahoo Finance, чтобы получить набор данных фондового рынка.
Чтобы установить Yahoo Finance, мы можем использовать следующую команду
!pip install yfinance
Шаг 1. Импортируйте необходимые библиотеки.
import pandas as pdimport numpy as npimport mathimport matplotlib.pyplot as pltimport matplotlib.dates as mdatesimport seaborn as snsfrom sklearn.preprocessing import MinMaxScalerimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torch.utils.data import Dataset, DataLoader
Шаг 2. Загрузка набора данных
На этом этапе мы используем библиотеку yfinance для загрузки исторических данных о фондовом рынке для Apple Inc. (AAPL) с Yahoo Finance.
# Импорт необходимых модулейimport yfinance as yf # Импорт библиотеки для работы с финансовыми даннымиfrom datetime import date, timedelta, datetime # Импорт модулей для работы с датами# Определение конечной даты как текущей даты в формате 'YYYY-MM-DD'end_date = date.today().strftime("%Y-%m-%d")# Определение начальной даты как '1990-01-01'start_date ='1990-01-01'# Загрузка данных о ценах акций компании Apple (AAPL) с Yahoo Financedf = yf.download('AAPL', start=start_date, end=end_date)
[*********************100%%**********************] 1 of 1 completed
Шаг 3: Предварительная обработка данных
Постройте тенденцию временных рядов, используя Matplotlib
defdata_plot(df):# Создание копии DataFrame для построения графиков df_plot = df.copy()# Вычисление числа столбцов и строк для размещения графиков ncols =2 nrows =int(round(df_plot.shape[1] / ncols, 0))# Создание графического окна с заданным числом строк и столбцов fig, ax = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, figsize=(14, 7))# Построение линейных графиков для каждого столбца данныхfor i, ax inenumerate(fig.axes): sns.lineplot(data=df_plot.iloc[:, i], ax=ax)# Построение линейного графика ax.tick_params(axis="x", rotation=30, labelsize=10, length=0)# Настройка меток оси X ax.xaxis.set_major_locator(mdates.AutoDateLocator())# Автоматическое определение меток для оси X# Автоматическое расположение графиков на холсте fig.tight_layout()# Отображение графиков plt.show()# Вызов функции для построения графиков на основе переданного DataFramedata_plot(df)
Линейные графики, отображающие характеристики акций Apple Inc. во времени
Разделение набора данных на тестовый и обучающий
Мы следуем общепринятой практике разделения данных на тренировочный и тестовый наборы. Мы вычисляем длину тренировочных наборов данных и выводим их формы для подтверждения разделения. Обычно, разделение составляет 80:20 для тренировочного и тестового наборов.
# Вычисление длины обучающих данных (80% от общего объема данных)training_data_len = math.ceil(len(df) *.8)# Вывод длины обучающих данныхtraining_data_len# Определение обучающих данных как первых 80% строк первого столбца DataFrametrain_data = df[:training_data_len].iloc[:,:1]# Определение тестовых данных как оставшихся 20% строк первого столбца DataFrametest_data = df[training_data_len:].iloc[:,:1]# Вывод размеров обучающих и тестовых данныхprint(train_data.shape, test_data.shape)
(6794, 1) (1698, 1)
Подготовка набора данных для обучения и тестирования
В этом разделе мы выбираем характеристику (цены на открытие), преобразуем её в необходимый двумерный формат и проверяем конечную форму, чтобы убедиться, что она соответствует ожидаемому формату входных данных модели. Таким образом, этот метод подготавливает обучающие данные к использованию в нейронной сети.
Данные обучения
# Выбор значений цен открытия (Open) в обучающих данныхdataset_train = train_data.Open.values# Преобразование одномерного массива в двумерный массивdataset_train = np.reshape(dataset_train, (-1,1))# Вывод размера преобразованного массиваdataset_train.shape
(6794, 1)
Данные тестирования
# Выбор значений цен открытия (Open) в тестовых данныхdataset_test = test_data.Open.values# Преобразование одномерного массива в двумерный массивdataset_test = np.reshape(dataset_test, (-1,1))# Вывод размера преобразованного массиваdataset_test.shape
(1698, 1)
Мы тщательно подготовили обучающие и тестовые наборы данных, чтобы гарантировать, что наша модель сможет производить точные прогнозы. Мы сформулировали задачу таким образом, чтобы она подходила для обучения с учителем, создавая последовательности соответствующей длины и связанные с ними метки.
Нормализация
Мы применили масштабирование Min-Max, которое является стандартным этапом предварительной обработки в машинном обучении и анализе временных рядов, к данным dataset_test. Это позволяет отрегулировать значения в интервале [0, 1], благодаря чему нейронные сети и другие модели могут сходиться быстрее и работать лучше. Нормализованные значения содержатся в массиве scaled_test и готовы к использованию в моделировании или анализе.
from sklearn.preprocessing import MinMaxScaler # Импорт класса для масштабирования данных# Создание объекта MinMaxScaler с диапазоном значений от 0 до 1scaler =MinMaxScaler(feature_range=(0,1))# Масштабирование обучающих данныхscaled_train = scaler.fit_transform(dataset_train)# Вывод первых 5 элементов масштабированных обучающих данныхprint(scaled_train[:5])# Масштабирование тестовых данныхscaled_test = scaler.fit_transform(dataset_test)# Вывод первых 5 элементов масштабированных тестовых данныхprint(*scaled_test[:5])
На этом этапе необходимо разделить временные ряды на X_train и y_train для обучающего набора и X_test и y_test для тестового набора. Временные ряды преобразуются в задачу обучения с учителем, которая может быть использована для разработки модели. При итерации по данным временных рядов цикл создает входные/выходные последовательности длиной 50 для обучающих данных и последовательности длиной 30 для тестовых данных. Используя эту технику, мы можем предсказывать будущие значения, учитывая временную зависимость данных от предыдущих наблюдений.
Мы подготавливаем обучающие и тестовые данные для нейронной сети, генерируя последовательности заданной длины и соответствующие им метки. Затем эти последовательности преобразуются в массивы NumPy и тензоры PyTorch.
Данные обучения
sequence_length =50# Длина последовательности# Инициализация списков для хранения входных и выходных данных обучающего набораX_train, y_train = [], []# Создание входных и выходных последовательностей для обучающего набора данныхfor i inrange(len(scaled_train) - sequence_length): X_train.append(scaled_train[i:i+sequence_length])# Добавление входной последовательности y_train.append(scaled_train[i+1:i+sequence_length+1])# Добавление выходной последовательности# Преобразование списков в массивы numpyX_train, y_train = np.array(X_train), np.array(y_train)# Преобразование массивов numpy в тензоры PyTorchX_train = torch.tensor(X_train, dtype=torch.float32)y_train = torch.tensor(y_train, dtype=torch.float32)# Вывод размеров обучающих данныхX_train.shape, y_train.shape
Чтобы сделать последовательности совместимыми с нашей моделью глубокого обучения, данные были преобразованы в массивы NumPy и тензоры PyTorch.
Шаг 4. Определите модель класса LSTM
Теперь мы определили сеть PyTorch, используя архитектуру LSTM. Класс состоит из слоя LSTM и линейного слоя. В классе LSTMModel мы инициализировали параметры
input_size : количество признаков во входных данных на каждом временном шаге
hidden_size : скрытые единицы в слое LSTM
количество_слоев : количество слоев LSTM
batch_first= True: данные на входе будут иметь размер партии в качестве первого измерения
Функция super(LSTMModel, self).init() инициализирует родительский класс для создания нейронной сети.
Метод forward определяет прямой проход модели, при котором входные данные x обрабатываются через слои модели для получения вывода.
classLSTMModel(nn.Module):def__init__(self,input_size,hidden_size,num_layers):super(LSTMModel, self).__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)# Инициализация слоя LSTM self.linear = nn.Linear(hidden_size, 1)# Инициализация линейного слояdefforward(self,x):# Проход вперёд через слой LSTM out, _ = self.lstm(x)# Проход через линейный слой для получения выхода out = self.linear(out)return out # Возвращение выхода
Проверьте доступность оборудования
Для кода PyTorch нам нужно проверить аппаратные ресурсы.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # Определение устройства для вычислений
print(device) # Вывод устройства
cuda
Определение модели
Теперь мы определяем модель, функцию потерь и оптимизатор для прогнозирования. Мы откорректировали гиперпараметры модели и установили функцию потерь на среднеквадратичную ошибку. Для оптимизации параметров во время обучения мы использовали оптимизатор Adam
Python3
input_size =1# Размер входных данныхnum_layers =2# Количество слоев LSTMhidden_size =64# Размер скрытого состояния LSTMoutput_size =1# Размер выходных данных# Инициализация модели LSTMmodel =LSTMModel(input_size, hidden_size, num_layers).to(device)# Определение функции потерьloss_fn = torch.nn.MSELoss(reduction='mean')# Определение оптимизатораoptimizer = torch.optim.Adam(model.parameters(), lr=1e-3)# Вывод моделиprint(model)
Шаг 5. Создание загрузчика данных для пакетного обучения
Загрузчик данных играет ключевую роль на этапах обучения и оценки. Поэтому мы подготовили данные для пакетного обучения и тестирования, создав объекты загрузчика данных.
Python3
batch_size =16# Размер пакета данных# Создание набора данных и загрузчика для обучающих данныхtrain_dataset = torch.utils.data.TensorDataset(X_train, y_train)train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)# Создание набора данных и загрузчика для тестовых данныхtest_dataset = torch.utils.data.TensorDataset(X_test, y_test)test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
Шаг 6: Обучение модели и оценка
Теперь мы создали тренировочный цикл на 50 эпох. В предоставленном фрагменте кода модель обрабатывает мини-пакеты обучающих данных, вычисляет потери и обновляет параметры.
num_epochs =50# Количество эпох обучения# Инициализация списков для хранения истории потерь на обучающем и тестовом наборахtrain_hist = []test_hist = []# Цикл обучения модели на протяжении заданного числа эпохfor epoch inrange(num_epochs): total_loss =0.0# Общая потеря для текущей эпохи model.train()# Установка модели в режим обученияfor batch_X, batch_y in train_loader: batch_X, batch_y = batch_X.to(device), batch_y.to(device)# Перемещение данных на устройство (GPU или CPU) predictions =model(batch_X)# Получение прогнозов модели loss =loss_fn(predictions, batch_y)# Вычисление потерь optimizer.zero_grad()# Обнуление градиентов loss.backward()# Обратное распространение ошибки optimizer.step()# Обновление параметров модели total_loss += loss.item()# Обновление общей потери average_loss = total_loss /len(train_loader)# Вычисление средней потери на обучающем наборе train_hist.append(average_loss)# Добавление средней потери в историю обучения model.eval()# Установка модели в режим оценкиwith torch.no_grad(): total_test_loss =0.0# Общая потеря на тестовом набореfor batch_X_test, batch_y_test in test_loader: batch_X_test, batch_y_test = batch_X_test.to(device), batch_y_test.to(device)# Перемещение данных predictions_test =model(batch_X_test)# Получение прогнозов модели test_loss =loss_fn(predictions_test, batch_y_test)# Вычисление потерь total_test_loss += test_loss.item()# Обновление общей потери на тестовом наборе average_test_loss = total_test_loss /len(test_loader)# Вычисление средней потери на тестовом наборе test_hist.append(average_test_loss)# Добавление средней потери в историю тестированияif (epoch+1)%10==0: print(f'Epoch [{epoch+1}/{num_epochs}] - Training Loss: {average_loss:.4f}, Test Loss: {average_test_loss:.4f}')
Epoch [10/50] - Training Loss: 0.0000, Test Loss: 0.0002
Epoch [20/50] - Training Loss: 0.0000, Test Loss: 0.0002
Epoch [30/50] - Training Loss: 0.0000, Test Loss: 0.0002
Epoch [40/50] - Training Loss: 0.0000, Test Loss: 0.0002
Epoch [50/50] - Training Loss: 0.0000, Test Loss: 0.0002
Построение кривой обучения
Мы построили кривую обучения для отслеживания прогресса и получения представления о том, сколько времени и обучения требуется модели для понимания закономерностей.
x = np.linspace(1, num_epochs, num_epochs)# Генерация последовательности значений по оси X# Построение графика потерь на обучающем и тестовом наборах данныхplt.plot(x, train_hist, scalex=True, label="Training loss")# Потери на обучающем набореplt.plot(x, test_hist, label="Test loss")# Потери на тестовом набореplt.legend()# Добавление легендыplt.show()# Отображение графика
Кривая обучения для потерь на обучающей выборке и потерь на тестовой выборке
Шаг 7: Прогнозирование
После обучения нейронной сети на предоставленных данных наступает момент прогнозирования на следующий месяц. Модель предсказывает будущую стоимость открытия и сохраняет будущие значения вместе с соответствующими датами. Используя цикл for, мы собираемся выполнить пошаговое прогнозирование, шаги следующие –
Мы установили количество будущих временных шагов равным 30 и преобразовали тестовую последовательность в массив NumPy, удалив одиночные измерения с помощью
Затем мы преобразовали historical_data в тензор Pytorch. Форма тензора - (1, sequence_length, 1), где sequence_length - длина последовательности исторических данных.
модель дополнительно предсказывает следующее значение на основе
Прогноз затем преобразуется в массив numpy, и извлекается первый элемент.
После завершения цикла мы получаем прогнозируемые значения, которые сохраняются в списке, и генерируются будущие даты для создания индекса для этих значений.
num_forecast_steps =30# Количество шагов прогнозаsequence_to_plot = X_test.squeeze().cpu().numpy()# Извлечение последовательности для прогнозирования# Извлечение последнего наблюдения для прогнозаhistorical_data = sequence_to_plot[-1]print(historical_data.shape)forecasted_values = [] # Список для хранения прогнозируемых значений# Генерация прогнозов на указанное количество шаговwith torch.no_grad():for _ inrange(num_forecast_steps*2):# Преобразование исторических данных в тензор и перемещение на устройство historical_data_tensor = torch.as_tensor(historical_data).view(1, -1, 1).float().to(device)# Получение прогноза от модели и преобразование в массив NumPy predicted_value =model(historical_data_tensor).cpu().numpy()[0,0]# Добавление прогнозируемого значения в список forecasted_values.append(predicted_value[0])# Обновление исторических данных для следующего шага historical_data = np.roll(historical_data, shift=-1) historical_data[-1]= predicted_value# Вычисление дат для будущих прогнозовlast_date = test_data.index[-1]# Последняя дата из тестового набораfuture_dates = pd.date_range(start=last_date + pd.DateOffset(1), periods=30)# Генерация будущих дат# Объединение индексов исходного тестового набора и будущих датcombined_index = test_data.index.append(future_dates)
Последний шаг: Построение графика прогноза
После прогнозирования будущих цен мы можем визуализировать их, используя линейные графики. График был построен для определенного временного интервала. Синяя линия указывает на тестовые данные. Затем мы строим график последних 30 временных шагов индекса тестовых данных, используя график с зеленой линией.
Прогнозируемые значения отображаются с использованием красной линейной диаграммы, которая использует комбинированный индекс, включающий как исторические данные, так и будущие даты.
plt.rcParams['figure.figsize']= [14,4] # Установка размеров графика# Построение графика исходных данных тестового набораplt.plot(test_data.index[-100:-30], test_data.Open[-100:-30], label="test_data", color="b")# Построение графика фактических значений на основе последней доступной последовательностиoriginal_cases = scaler.inverse_transform(np.expand_dims(sequence_to_plot[-1], axis=0)).flatten()plt.plot(test_data.index[-30:], original_cases, label='actual values', color='green')# Построение графика прогнозируемых значенийforecasted_cases = scaler.inverse_transform(np.expand_dims(forecasted_values, axis=0)).flatten()plt.plot(combined_index[-60:], forecasted_cases, label='forecasted values', color='red')# Настройка осей и заголовкаplt.xlabel('Time Step')plt.ylabel('Value')plt.legend()plt.title('Time Series Forecasting')plt.grid(True)# Отображение графикаplt.show()
Прогноз цены открытия акций Apple Inc. на следующий месяц
Построив тестовые данные, фактические значения и прогнозируемые данные модели, мы получили четкое представление о том, насколько хорошо прогнозируемые значения соответствуют временным рядам.
В этой работе тщательно исследована захватывающая область прогнозирования временных рядов с использованием нейронных сетей LSTM и PyTorch. Для сбора исторических данных о фондовом рынке была импортирована библиотека yfinance, после чего был начат этап предварительной обработки данных. Затем были применены ключевые шаги, такие как загрузка данных, разделение на обучающую и тестовую выборки, а также масштабирование данных, чтобы убедиться, что наша модель может точно учиться на данных и делать прогнозы.
Для более точных прогнозов часто требуются дополнительные корректировки, настройка гиперпараметров и оптимизация. Для улучшения способностей предсказания могут быть исследованы методы ансамбля и другие передовые методологии.
Мы только начали изучать огромное поле прогнозирования временных рядов в этом эссе. Есть еще много чему учиться, от управления многовариантными временными рядами до решения практических проблем новаторскими способами. Имея этот опыт, вы готовы использовать PyTorch и нейронные сети LSTM для самостоятельных приключений в прогнозировании временных рядов.