How to do it...

Execute the following steps to train an RNN for a time series prediction problem.

  1. Import the libraries:
import yfinance as yf
import numpy as np

import torch
import torch.optim as optim
import torch.nn as nn
from torch.utils.data import (Dataset, TensorDataset,
DataLoader, Subset)
from chapter_10_utils import create_input_data, custom_set_seed

from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import MinMaxScaler

device = 'cuda' if torch.cuda.is_available() else 'cpu'
  1. Define the parameters:
# data
TICKER = 'INTL'
START_DATE = '2010-01-02'
END_DATE = '2019-12-31'
VALID_START = '2019-07-01'
N_LAGS = 12

# neural network
BATCH_SIZE = 16
N_EPOCHS = 100
  1. Download and prepare the data:
df = yf.download(TICKER, 
start=START_DATE,
end=END_DATE,
progress=False)

df = df.resample("W-MON").last()
valid_size = df.loc[VALID_START:END_DATE].shape[0]
prices = df['Adj Close'].values.reshape(-1, 1)
  1. Scale the time series of prices:
valid_ind = len(prices) – valid_size

minmax = MinMaxScaler(feature_range=(0, 1))

prices_train = prices[:valid_ind]
prices_valid = prices[valid_ind:]

minmax.fit(prices_train)

prices_train = minmax.transform(prices_train)
prices_valid = minmax.transform(prices_valid)

prices_scaled = np.concatenate((prices_train,
prices_valid)).flatten()
  1. Transform the time series into input for the RNN:
X, y = create_input_data(prices_scaled, N_LAGS)
  1. Obtain the naïve forecast:
naive_pred = prices[len(prices)-valid_size-1:-1]
y_valid = prices[len(prices)-valid_size:]

naive_mse = mean_squared_error(y_valid, naive_pred)
naive_rmse = np.sqrt(naive_mse)
print(f"Naive forecast – MSE: {naive_mse:.4f}, RMSE: {naive_rmse:.4f}")

The naive forecast results in the following performance:

Naive forecast – MSE: 4.1568, RMSE: 2.0388
  1. Prepare the DataLoader objects:
# set seed for reproducibility
custom_set_seed(42)

valid_ind = len(X) – valid_size

X_tensor = torch.from_numpy(X).float().reshape(X.shape[0],
X.shape[1],
1)
y_tensor = torch.from_numpy(y).float().reshape(X.shape[0], 1)

dataset = TensorDataset(X_tensor, y_tensor)

train_dataset = Subset(dataset, list(range(valid_ind)))
valid_dataset = Subset(dataset, list(range(valid_ind, len(X))))

train_loader = DataLoader(dataset=train_dataset,
batch_size=BATCH_SIZE,
shuffle=True)
valid_loader = DataLoader(dataset=valid_dataset,
batch_size=BATCH_SIZE)
  1. Define the model:
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, n_layers, output_size):
super(RNN, self).__init__()
self.rnn = nn.RNN(input_size, hidden_size,
n_layers, batch_first=True,
nonlinearity='relu')
self.fc = nn.Linear(hidden_size, output_size)

def forward(self, x):
output, _ = self.rnn(x)
output = self.fc(output[:,-1,:])
return output
  1. Instantiate the model, the loss function, and the optimizer:
model = RNN(input_size=1, hidden_size=6, 
n_layers=1, output_size=1).to(device)
loss_fn = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
  1. Train the network:
PRINT_EVERY = 10
train_losses, valid_losses = [], []

for epoch in range(N_EPOCHS):
running_loss_train = 0
running_loss_valid = 0

model.train()

for x_batch, y_batch in train_loader:

optimizer.zero_grad()

x_batch = x_batch.to(device)
y_batch = y_batch.to(device)
y_hat = model(x_batch)
loss = torch.sqrt(loss_fn(y_batch, y_hat))
loss.backward()
optimizer.step()
running_loss_train += loss.item() * x_batch.size(0)

epoch_loss_train = running_loss_train / len(train_loader.dataset)
train_losses.append(epoch_loss_train)

with torch.no_grad():
model.eval()
for x_val, y_val in valid_loader:
x_val = x_val.to(device)
y_val = y_val.to(device)
y_hat = model(x_val)
loss = torch.sqrt(loss_fn(y_val, y_hat))
running_loss_valid += loss.item() * x_val.size(0)

epoch_loss_valid = running_loss_valid / len(valid_loader.dataset)

if epoch > 0 and epoch_loss_valid < min(valid_losses):
best_epoch = epoch
torch.save(model.state_dict(), './rnn_checkpoint.pth')

valid_losses.append(epoch_loss_valid)

if epoch % PRINT_EVERY == 0:
print(f"<{epoch}> – Train. loss: {epoch_loss_train:.4f} Valid. loss: {epoch_loss_valid:.4f}")

print(f'Lowest loss recorded in epoch: {best_epoch}')
  1. Plot the losses over epochs. This step is identical to the corresponding steps in the previous recipes, so we do not include the code for brevity. Running the code results in the following plot:

  1. Load the best model (with the lowest validation loss):
state_dict = torch.load('rnn_checkpoint.pth')
model.load_state_dict(state_dict)
  1. Obtain the predictions:
y_pred = []

with torch.no_grad():

model.eval()

for x_val, y_val in valid_loader:
x_val = x_val.to(device)
y_hat = model(x_val)
y_pred.append(y_hat)

y_pred = torch.cat(y_pred).numpy()
y_pred = minmax.inverse_transform(y_pred).flatten()
  1. Evaluate the predictions. This step is identical to the corresponding steps in the previous recipes, so we do not include the code for brevity. Running the code results in the following plot:

The RNN achieved the following performance on the validation set:

RNN's forecast – MSE: 4.0315, RMSE: 2.0079

We managed to beat the benchmark in terms of the considered performance evaluation metrics. However, the most probable case is that the model simply overfits to the data. For better diagnostics, we should use another, larger set purely for testing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.39.55