LSTM и GRU для прогнозирования цен на акции Amazon
Проблема временного ряда
Прогнозирование временных рядов - интригующая область машинного обучения, которая требует внимания и может быть очень прибыльной, если связана с другими сложными темами, такими как прогнозирование цен на акции. Прогнозирование временных рядов - это применение модели для прогнозирования будущих значений на основе ранее наблюдаемых значений.
По определению временной ряд - это ряд точек данных, индексированных во временном порядке. Этот тип проблем важен, потому что существует множество задач прогнозирования, которые связаны с временной составляющей, и поиск взаимосвязи между данными и временем является ключом к анализу (например, прогноз погоды и прогноз землетрясений). Однако этими проблемами иногда пренебрегают, потому что моделирование этого отношения компонентов времени не так тривиально, как может показаться.
Прогнозирование фондового рынка - это попытка определить будущую стоимость акций компании. Успешное предсказание будущей цены акции может принести значительную прибыль, и эта тема входит в сферу задач временных рядов.
Среди нескольких способов, разработанных на протяжении многих лет для точного прогнозирования сложных и изменчивых колебаний цен на акции, нейронные сети, в частности RNN, показали значительное применение в этой области. Здесь мы собираемся построить две разные модели RNN - LSTM и GRU - с PyTorch, чтобы предсказать рыночную цену Amazon и сравнить их производительность с точки зрения времени и эффективности.
Рекуррентная нейронная сеть (RNN)
Рекуррентная нейронная сеть (RNN) - это тип искусственной нейронной сети, предназначенный для распознавания последовательных шаблонов данных для прогнозирования следующих сценариев. Эта архитектура особенно мощна из-за ее узловых соединений, позволяющих демонстрировать временное динамическое поведение. Еще одна важная особенность этой архитектуры - использование контуров обратной связи для обработки последовательности. Такая характеристика позволяет информации сохраняться, часто ее называют памятью. Такое поведение делает RNN отличными решениями для обработки естественного языка (NLP) и временных рядов. На основе этой структуры были разработаны архитектуры, получившие название Long short-term memory (LSTM) и Gated recurrent units (GRU).
Блок LSTM состоит из ячейки, входного элемента, выходного элемента и элемента забывания. Ячейка запоминает значения за произвольные интервалы времени, а три ворот регулируют поток информации в ячейку и из нее.
С другой стороны, у GRU меньше параметров, чем у LSTM, и у него отсутствует выходной вентиль. Обе структуры могут решить проблему «кратковременной памяти», преследующую обычные RNN, и эффективно сохранять долгосрочные зависимости в последовательных данных.
Хотя LSTM в настоящее время более популярен, ГРУ неизбежно в конечном итоге затмит его из-за превосходной скорости при достижении аналогичной точности и эффективности. Мы увидим, что здесь у нас аналогичный результат, и модель ГРУ также работает лучше в этом сценарии.
Реализация модели
Набор данных содержит исторические цены на акции (за последние 12 лет) 29 компаний, но я выбрал данные Amazon, потому что подумал, что это может быть интересно.
Мы собираемся спрогнозировать цену закрытия акции, а ниже показано поведение данных по годам.
Мы разрезаем фрейм данных, чтобы получить нужный столбец, и нормализовать данные.
from sklearn.preprocessing import MinMaxScaler price = data[['Close']] scaler = MinMaxScaler(feature_range=(-1, 1)) price['Close'] = scaler.fit_transform(price['Close'].values.reshape(-1,1))
Теперь разделим данные на обучающие и тестовые наборы. Перед этим мы должны определить ширину окна анализа. Использование предыдущих временных шагов для прогнозирования следующего временного шага называется методом скользящего окна.
def split_data(stock, lookback): data_raw = stock.to_numpy() # convert to numpy array data = [] # create all possible sequences of length seq_len for index in range(len(data_raw) - lookback): data.append(data_raw[index: index + lookback]) data = np.array(data); test_set_size = int(np.round(0.2*data.shape[0])); train_set_size = data.shape[0] - (test_set_size); x_train = data[:train_set_size,:-1,:] y_train = data[:train_set_size,-1,:] x_test = data[train_set_size:,:-1] y_test = data[train_set_size:,-1,:] return [x_train, y_train, x_test, y_test] lookback = 20 # choose sequence length x_train, y_train, x_test, y_test = split_data(price, lookback)
Затем мы преобразуем их в тензоры, что является базовой структурой для построения модели PyTorch.
import torch import torch.nn as nn x_train = torch.from_numpy(x_train).type(torch.Tensor) x_test = torch.from_numpy(x_test).type(torch.Tensor) y_train_lstm = torch.from_numpy(y_train).type(torch.Tensor) y_test_lstm = torch.from_numpy(y_test).type(torch.Tensor) y_train_gru = torch.from_numpy(y_train).type(torch.Tensor) y_test_gru = torch.from_numpy(y_test).type(torch.Tensor)
Мы определяем некоторые общие значения для обеих моделей относительно слоев.
input_dim = 1 hidden_dim = 32 num_layers = 2 output_dim = 1 num_epochs = 100
LSTM
class LSTM(nn.Module): def __init__(self, input_dim, hidden_dim, num_layers, output_dim): super(LSTM, self).__init__() self.hidden_dim = hidden_dim self.num_layers = num_layers self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_() c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_() out, (hn, cn) = self.lstm(x, (h0.detach(), c0.detach())) out = self.fc(out[:, -1, :]) return out
Создаем модель, задаем критерий и оптимизатор.
model = LSTM(input_dim=input_dim, hidden_dim=hidden_dim, output_dim=output_dim, num_layers=num_layers) criterion = torch.nn.MSELoss(reduction='mean') optimiser = torch.optim.Adam(model.parameters(), lr=0.01)
Наконец, мы обучаем модель более 100 эпох.
import time hist = np.zeros(num_epochs) start_time = time.time() lstm = [] for t in range(num_epochs): y_train_pred = model(x_train) loss = criterion(y_train_pred, y_train_lstm) print("Epoch ", t, "MSE: ", loss.item()) hist[t] = loss.item() optimiser.zero_grad() loss.backward() optimiser.step() training_time = time.time()-start_time print("Training time: {}".format(training_time))
Закончив обучение, мы можем применить прогноз.
Модель хорошо работает с обучающим набором, но плохо работает с тестовым набором. Модель, вероятно, переоснащена, особенно с учетом того, что после 40-й эпохи потери минимальны.
ГРУ
Код для реализации модели GRU очень похож.
class GRU(nn.Module): def __init__(self, input_dim, hidden_dim, num_layers, output_dim): super(GRU, self).__init__() self.hidden_dim = hidden_dim self.num_layers = num_layers self.gru = nn.GRU(input_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x): h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_() out, (hn) = self.gru(x, (h0.detach())) out = self.fc(out[:, -1, :]) return out
Аналогичным образом создаем модель и настраиваем параметры.
model = GRU(input_dim=input_dim, hidden_dim=hidden_dim, output_dim=output_dim, num_layers=num_layers) criterion = torch.nn.MSELoss(reduction='mean') optimiser = torch.optim.Adam(model.parameters(), lr=0.01)
Шаг обучения точно такой же, и результаты, которых мы достигаем, тоже чем-то похожи.
Однако, когда дело доходит до прогноза, модель ГРУ явно более точна с точки зрения прогноза, как мы видим на следующем графике.
Заключение
Обе модели демонстрируют хорошие результаты на этапе обучения, но стагнируют около 40-й эпохи, что означает, что им не нужно заранее определять 100 эпох.
Как и ожидалось, нейронная сеть GRU превзошла LSTM с точки зрения точности, потому что она достигла более низкой среднеквадратичной ошибки (при обучении и, что наиболее важно, в тестовом наборе) и скорости, видно, что GRU потребовалось на 5 секунд меньше для завершения обучения, чем LSTM.
Вопросы, связанные с кодом - https://www.kaggle.com/rodsaldanha/stock-prediction-pytorch