Prévision du cours des actions avec PyTorch – Rodolfo Saldanha
La prévision de séries chronologiques est un domaine fascinant du Machine Learning qui nécessite de l’attention et peut être très rentable s’il est associé à d’autres sujets complexes tels que la prévision du cours des actions. La prévision de séries chronologiques est l’application d’un modèle pour prédire les valeurs futures en fonction des valeurs précédemment observées.
Par définition, une des séries chronologiques est une série de points de données indexés dans l’ordre temporel. Ce type de problème est important car il existe une variété de problèmes de prédiction qui impliquent une composante temporelle, et trouver la relation données / temps est la clé de l’analyse (par exemple, les prévisions météorologiques et la prévision des tremblements de terre). Cependant, ces problèmes sont parfois négligés car la modélisation de cette relation de composante temporelle n’est pas aussi triviale que cela puisse paraître.
Prédiction boursière est l’acte d’essayer de déterminer la valeur future des actions d’une entreprise. La prédiction réussie du prix futur d’une action pourrait générer un bénéfice important, et ce sujet fait partie des problèmes de séries chronologiques.
Parmi les nombreux moyens développés au fil des ans pour prédire avec précision la variation complexe et volatile des cours des actions, les réseaux de neurones, plus précisément les RNN, ont montré une application significative sur le terrain. Ici, nous allons construire deux modèles différents de RNN – LSTM et GRU – avec PyTorch pour prédire Cours de bourse d’Amazon et comparer leurs performances en termes de temps et d’efficacité.
Un réseau neuronal récurrent (RNN) est un type de réseau neuronal artificiel conçu pour reconnaître les modèles séquentiels des données afin de prédire les scénarios suivants. Cette architecture est particulièrement performante du fait de ses connexions de nœuds, permettant d’exposer un comportement dynamique temporel. Une autre caractéristique importante de cette architecture est l’utilisation de boucles de rétroaction pour traiter une séquence. Une telle caractéristique permet à l’information de persister, souvent décrite comme une mémoire. Ce comportement rend les RNN parfaits pour le traitement du langage naturel (NLP) et les problèmes de séries chronologiques. Sur la base de cette structure, des architectures appelées mémoire à court terme longue (LSTM) et unités récurrentes fermées (GRU) ont été développées.
Une unité LSTM est composée d’une cellule, d’une porte d’entrée, d’une porte de sortie et d’une porte d’oubli. La cellule se souvient des valeurs sur des intervalles de temps arbitraires, et les trois portes régulent le flux d’informations entrant et sortant de la cellule.
En revanche, un GRU a moins de paramètres que le LSTM, sans porte de sortie. Les deux structures peuvent résoudre le problème de la «mémoire à court terme» qui afflige les RNN de vanille et conserver efficacement les dépendances à long terme dans les données séquentielles.
Bien que le LSTM soit actuellement plus populaire, le GRU finira par le surpasser en raison d’une vitesse supérieure tout en atteignant une précision et une efficacité similaires. Nous allons voir que nous avons un résultat similaire ici, et le modèle GRU fonctionne également mieux dans ce scénario.
L’ensemble de données contient les cours boursiers historiques (12 dernières années) de 29 sociétés, mais j’ai choisi les données Amazon parce que je pensais que cela pourrait être intéressant.
Nous allons prédire le prix de clôture de l’action, et voici le comportement des données au fil des ans.
Nous découpons le bloc de données pour obtenir la colonne que nous voulons et normalisons les données.
from sklearn.preprocessing import MinMaxScalerprice = data[['Close']]scaler = MinMaxScaler(feature_range=(-1, 1))
price['Close'] = scaler.fit_transform(price['Close'].values.reshape(-1,1))
Maintenant, nous divisons les données en trains et en ensembles de tests. Avant de le faire, nous devons définir la largeur de la fenêtre de l’analyse. L’utilisation de pas de temps antérieurs pour prédire le pas de temps suivant est appelée la méthode de fenêtre coulissante.
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)
Ensuite, nous les transformons en tenseurs, qui est la structure de base pour construire un modèle PyTorch.
import torch
import torch.nn as nnx_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)
Nous définissons quelques valeurs communes pour les deux modèles concernant les couches.
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_layersself.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
def forward(self, x):
self.fc = nn.Linear(hidden_dim, output_dim)
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
Nous créons le modèle, définissons le critère et l’optimiseur.
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)
Enfin, nous formons le modèle sur 100 époques.
import timehist = 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))
Une fois la formation terminée, nous pouvons appliquer la prédiction.
Le modèle se comporte bien avec l’ensemble d’entraînement, mais ses performances sont médiocres avec l’ensemble d’essai. Le modèle est probablement sur-ajusté, surtout en tenant compte du fait que la perte est minime après la 40e époque.
GRU
Le code pour l’implémentation du modèle GRU est très similaire.
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_layersself.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
De même, créer le modèle et définir les paramètres.
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)
L’étape de formation est exactement la même et les résultats que nous obtenons sont également similaires.
Cependant, en ce qui concerne la prédiction, le modèle GRU est clairement plus précis en termes de prédiction, comme nous l’observons dans le graphique suivant.
Les deux modèles ont de bonnes performances en phase d’entraînement mais stagnent autour de la 40e époque, ce qui signifie qu’ils n’ont pas besoin de 100 époques préalablement définies.
Comme prévu, le réseau neuronal GRU a surpassé le LSTM en termes de précision car il a atteint une erreur quadratique moyenne inférieure (à l’entraînement, et surtout, dans l’ensemble de test) et en vitesse, vu que GRU a pris 5 secondes de moins pour terminer l’entraînement que le LSTM.
Questions liées au code – https://www.kaggle.com/rodsaldanha/stock-prediction-pytorch