J'ai créé un réseau neuronal LSTM (RNN) avec apprentissage supervisé pour la prédiction du stock de données. Le problème est pourquoi il prédit mal sur ses propres données d'entraînement? (note: exemple reproductible ci-dessous)
J'ai créé un modèle simple pour prédire le cours des 5 prochains jours:
model = Sequential()
model.add(LSTM(32, activation='sigmoid', input_shape=(x_train.shape[1], x_train.shape[2])))
model.add(Dense(y_train.shape[1]))
model.compile(optimizer='adam', loss='mse')
es = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)
model.fit(x_train, y_train, batch_size=64, epochs=25, validation_data=(x_test, y_test), callbacks=[es])
Les résultats corrects sont en y_test
(5 valeurs), donc modélisez les trains, en regardant 90 jours précédents, puis restaurez les poids à partir du meilleur val_loss=0.0030
résultat ( ) avec patience=3
:
Train on 396 samples, validate on 1 samples
Epoch 1/25
396/396 [==============================] - 1s 2ms/step - loss: 0.1322 - val_loss: 0.0299
Epoch 2/25
396/396 [==============================] - 0s 402us/step - loss: 0.0478 - val_loss: 0.0129
Epoch 3/25
396/396 [==============================] - 0s 397us/step - loss: 0.0385 - val_loss: 0.0178
Epoch 4/25
396/396 [==============================] - 0s 399us/step - loss: 0.0398 - val_loss: 0.0078
Epoch 5/25
396/396 [==============================] - 0s 391us/step - loss: 0.0343 - val_loss: 0.0030
Epoch 6/25
396/396 [==============================] - 0s 391us/step - loss: 0.0318 - val_loss: 0.0047
Epoch 7/25
396/396 [==============================] - 0s 389us/step - loss: 0.0308 - val_loss: 0.0043
Epoch 8/25
396/396 [==============================] - 0s 393us/step - loss: 0.0292 - val_loss: 0.0056
Le résultat de la prédiction est assez impressionnant, n'est-ce pas?
C'est parce que l'algorithme a restauré les meilleurs poids de l'époque n ° 5. Okey, enregistrons maintenant ce modèle dans un .h5
fichier, reculons de -10 jours et prédisons les 5 derniers jours (dans un premier exemple, nous avons fait un modèle et validé les 17-23 avril, y compris les week-ends de repos, testons maintenant du 2-8 avril). Résultat:
Cela montre une direction absolument fausse. Comme nous le voyons, c'est parce que le modèle a été formé et a pris le meilleur de l'époque # 5 pour la validation définie le 17-23 avril, mais pas le 2-8. Si j'essaie de m'entraîner davantage, en jouant avec quelle époque choisir, quoi que je fasse, il y a toujours beaucoup d'intervalles de temps dans le passé qui ont une mauvaise prédiction.
Pourquoi le modèle affiche-t-il des résultats erronés sur ses propres données entraînées? J'ai formé des données, il faut se rappeler comment prédire les données sur ce morceau de set, mais prédit mal. Ce que j'ai aussi essayé:
- Utilisez des ensembles de données volumineux avec plus de 50 000 lignes, le cours des actions sur 20 ans, en ajoutant plus ou moins de fonctionnalités
- Créez différents types de modèles, comme l'ajout de couches cachées, de tailles de lots différentes, d'activations de couches différentes, de décrochages, de normalisation par lots
- Créez un rappel EarlyStopping personnalisé, obtenez une perte de valeur moyenne à partir de nombreux ensembles de données de validation et choisissez le meilleur
Peut-être que je manque quelque chose? Que puis-je améliorer?
Voici un exemple très simple et reproductible . yfinance
télécharge les données boursières du S&P 500.
"""python 3.7.7
tensorflow 2.1.0
keras 2.3.1"""
import numpy as np
import pandas as pd
from keras.callbacks import EarlyStopping, Callback
from keras.models import Model, Sequential, load_model
from keras.layers import Dense, Dropout, LSTM, BatchNormalization
from sklearn.preprocessing import MinMaxScaler
import plotly.graph_objects as go
import yfinance as yf
np.random.seed(4)
num_prediction = 5
look_back = 90
new_s_h5 = True # change it to False when you created model and want test on other past dates
df = yf.download(tickers="^GSPC", start='2018-05-06', end='2020-04-24', interval="1d")
data = df.filter(['Close', 'High', 'Low', 'Volume'])
# drop last N days to validate saved model on past
df.drop(df.tail(0).index, inplace=True)
print(df)
class EarlyStoppingCust(Callback):
def __init__(self, patience=0, verbose=0, validation_sets=None, restore_best_weights=False):
super(EarlyStoppingCust, self).__init__()
self.patience = patience
self.verbose = verbose
self.wait = 0
self.stopped_epoch = 0
self.restore_best_weights = restore_best_weights
self.best_weights = None
self.validation_sets = validation_sets
def on_train_begin(self, logs=None):
self.wait = 0
self.stopped_epoch = 0
self.best_avg_loss = (np.Inf, 0)
def on_epoch_end(self, epoch, logs=None):
loss_ = 0
for i, validation_set in enumerate(self.validation_sets):
predicted = self.model.predict(validation_set[0])
loss = self.model.evaluate(validation_set[0], validation_set[1], verbose = 0)
loss_ += loss
if self.verbose > 0:
print('val' + str(i + 1) + '_loss: %.5f' % loss)
avg_loss = loss_ / len(self.validation_sets)
print('avg_loss: %.5f' % avg_loss)
if self.best_avg_loss[0] > avg_loss:
self.best_avg_loss = (avg_loss, epoch + 1)
self.wait = 0
if self.restore_best_weights:
print('new best epoch = %d' % (epoch + 1))
self.best_weights = self.model.get_weights()
else:
self.wait += 1
if self.wait >= self.patience or self.params['epochs'] == epoch + 1:
self.stopped_epoch = epoch
self.model.stop_training = True
if self.restore_best_weights:
if self.verbose > 0:
print('Restoring model weights from the end of the best epoch')
self.model.set_weights(self.best_weights)
def on_train_end(self, logs=None):
print('best_avg_loss: %.5f (#%d)' % (self.best_avg_loss[0], self.best_avg_loss[1]))
def multivariate_data(dataset, target, start_index, end_index, history_size, target_size, step, single_step=False):
data = []
labels = []
start_index = start_index + history_size
if end_index is None:
end_index = len(dataset) - target_size
for i in range(start_index, end_index):
indices = range(i-history_size, i, step)
data.append(dataset[indices])
if single_step:
labels.append(target[i+target_size])
else:
labels.append(target[i:i+target_size])
return np.array(data), np.array(labels)
def transform_predicted(pr):
pr = pr.reshape(pr.shape[1], -1)
z = np.zeros((pr.shape[0], x_train.shape[2] - 1), dtype=pr.dtype)
pr = np.append(pr, z, axis=1)
pr = scaler.inverse_transform(pr)
pr = pr[:, 0]
return pr
step = 1
# creating datasets with look back
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df.values)
dataset = df_normalized[:-num_prediction]
x_train, y_train = multivariate_data(dataset, dataset[:, 0], 0,len(dataset) - num_prediction + 1, look_back, num_prediction, step)
indices = range(len(dataset)-look_back, len(dataset), step)
x_test = np.array(dataset[indices])
x_test = np.expand_dims(x_test, axis=0)
y_test = np.expand_dims(df_normalized[-num_prediction:, 0], axis=0)
# creating past datasets to validate with EarlyStoppingCust
number_validates = 50
step_past = 5
validation_sets = [(x_test, y_test)]
for i in range(1, number_validates * step_past + 1, step_past):
indices = range(len(dataset)-look_back-i, len(dataset)-i, step)
x_t = np.array(dataset[indices])
x_t = np.expand_dims(x_t, axis=0)
y_t = np.expand_dims(df_normalized[-num_prediction-i:len(df_normalized)-i, 0], axis=0)
validation_sets.append((x_t, y_t))
if new_s_h5:
model = Sequential()
model.add(LSTM(32, return_sequences=False, activation = 'sigmoid', input_shape=(x_train.shape[1], x_train.shape[2])))
# model.add(Dropout(0.2))
# model.add(BatchNormalization())
# model.add(LSTM(units = 16))
model.add(Dense(y_train.shape[1]))
model.compile(optimizer = 'adam', loss = 'mse')
# EarlyStoppingCust is custom callback to validate each validation_sets and get average
# it takes epoch with best "best_avg" value
# es = EarlyStoppingCust(patience = 3, restore_best_weights = True, validation_sets = validation_sets, verbose = 1)
# or there is keras extension with built-in EarlyStopping, but it validates only 1 set that you pass through fit()
es = EarlyStopping(monitor = 'val_loss', patience = 3, restore_best_weights = True)
model.fit(x_train, y_train, batch_size = 64, epochs = 25, shuffle = True, validation_data = (x_test, y_test), callbacks = [es])
model.save('s.h5')
else:
model = load_model('s.h5')
predicted = model.predict(x_test)
predicted = transform_predicted(predicted)
print('predicted', predicted)
print('real', df.iloc[-num_prediction:, 0].values)
print('val_loss: %.5f' % (model.evaluate(x_test, y_test, verbose=0)))
fig = go.Figure()
fig.add_trace(go.Scatter(
x = df.index[-60:],
y = df.iloc[-60:,0],
mode='lines+markers',
name='real',
line=dict(color='#ff9800', width=1)
))
fig.add_trace(go.Scatter(
x = df.index[-num_prediction:],
y = predicted,
mode='lines+markers',
name='predict',
line=dict(color='#2196f3', width=1)
))
fig.update_layout(template='plotly_dark', hovermode='x', spikedistance=-1, hoverlabel=dict(font_size=16))
fig.update_xaxes(showspikes=True)
fig.update_yaxes(showspikes=True)
fig.show()
df.drop(df.tail(10).index, inplace=True)
, il a montré le même mauvais résultat que moi.Réponses:
L'OP postule une conclusion intéressante. Permettez-moi de simplifier la question initiale comme suit.
Si le modèle est formé sur une série chronologique particulière, pourquoi le modèle ne peut-il pas reconstruire les données de séries chronologiques précédentes, sur lesquelles il a déjà été formé?
Eh bien, la réponse est intégrée dans la progression de la formation elle-même. Puisque
EarlyStopping
est utilisé ici pour éviter le sur-ajustement, le meilleur modèle est enregistré à l'epoch=5
endroit oùval_loss=0.0030
mentionné par l'OP. Dans ce cas, la perte de formation est égale0.0343
, c'est-à-dire que le RMSE de la formation est0.185
. Étant donné que l'ensemble de données est mis à l'échelle à l'aideMinMaxScalar
, nous devons annuler la mise à l'échelle de RMSE pour comprendre ce qui se passe.Les valeurs minimale et maximale de la séquence temporelle se trouvent être
2290
et3380
. Par conséquent, avoir0.185
comme RMSE de l'entraînement signifie que, même pour l'ensemble d'apprentissage, les valeurs prédites peuvent différer d'environ les valeurs de vérité au sol0.185*(3380-2290)
, c'est-à-dire les~200
unités en moyenne.Cela explique pourquoi il existe une grande différence lors de la prévision des données d'entraînement elles-mêmes à un pas de temps précédent.
Que dois-je faire pour émuler parfaitement les données d'entraînement?
J'ai posé cette question de moi-même. La réponse simple est de faire en sorte que la perte d'entraînement approche
0
, c'est-à-dire qu'elle s'adapte au modèle.Après un peu de formation, j'ai réalisé qu'un modèle avec seulement 1 couche LSTM qui a des
32
cellules n'est pas assez complexe pour reconstruire les données de formation. Par conséquent, j'ai ajouté une autre couche LSTM comme suit.Et le modèle est formé pour les
1000
époques sans y penserEarlyStopping
.À la fin de cette
1000
époque, nous avons une perte d'entraînement0.00047
qui est bien inférieure à la perte d'entraînement dans votre cas. Nous nous attendons donc à ce que le modèle reconstruise mieux les données d'entraînement. Voici le graphique de prédiction du 2 au 8 avril.Une note finale:
La formation sur une base de données particulière ne signifie pas nécessairement que le modèle devrait être capable de reconstruire parfaitement les données de formation. Surtout, lorsque les méthodes telles que l'arrêt précoce, la régularisation et l'abandon sont introduites pour éviter le surapprentissage, le modèle a tendance à être plus généralisable plutôt que de mémoriser les données d'entraînement.
la source
batch_size=1
est vraiment lent. Et oui, ilshuffle=False
aurait fallu plus de temps pour s'adapter car les données sont séquentielles.Vous voulez que le modèle apprenne la relation entre l'entrée et la sortie au lieu de la mémorisation. Si un modèle mémorise la sortie correcte pour chaque entrée, nous pouvons dire qu'elle est trop adaptée aux données d'apprentissage. Souvent, vous pouvez forcer le modèle à s'adapter en utilisant un petit sous-ensemble de données, donc si c'est le comportement que vous souhaitez voir, vous pouvez essayer cela.
la source
Suspect # 1 - Régularisation
Les réseaux de neurones sont parfaits pour sur-ajuster les données d'entraînement, en fait il y a une expérience remplaçant les étiquettes CIFAR10 (tâche de classification d'image) (valeurs y) par des étiquettes aléatoires sur l'ensemble de données d'entraînement et le réseau s'adapte aux étiquettes aléatoires, entraînant une perte presque nulle.
Alors pourquoi ça n'arrive pas tout le temps? régularisation .
la régularisation tente (grosso modo) de résoudre un problème plus difficile que le problème d'optimisation (la perte) que nous avons défini pour le modèle.
quelques méthodes de régularisation courantes dans les réseaux de neurones:
ces méthodes aident à réduire le surajustement et entraînent généralement une meilleure validation et des performances de test, mais entraînent une baisse des performances du train (ce qui n'a pas vraiment d'importance, comme expliqué dans le dernier paragraphe).
les performances des données de train ne sont généralement pas si importantes et pour cela nous utilisons l'ensemble de validation.
Suspect # 2 - Taille du modèle
vous utilisez une seule couche LSTM avec 32 unités. c'est assez petit. essayez d'augmenter la taille et même de mettre deux couches LSTM (ou une couche bidirectionnelle) et je suis sûr que le modèle et l'optimiseur satureront vos données tant que vous les laisserez - c'est-à-dire supprimer l'arrêt précoce, restore_last_weights et toute autre régularisation spécifiée ci-dessus.
Remarque sur la complexité du problème
essayer de prédire les prix futurs des actions simplement en regardant l'historique n'est pas une tâche facile, et même si le modèle peut (sur) s'adapter parfaitement à l'ensemble d'entraînement, il ne fera probablement rien d'utile sur l'ensemble de test ou dans le monde réel.
ML n'est pas de la magie noire, les échantillons x doivent être corrélés d'une manière ou d'une autre aux balises y, nous supposons généralement que (x, y) sont tirés d'une certaine distribution ensemble.
Une façon plus intuitive d'y penser, lorsque vous devez marquer une image manuellement pour la classe chien / chat - c'est assez simple. mais pouvez-vous "étiqueter" manuellement le cours de l'action en regardant uniquement l'historique de ce titre?
C'est une certaine intuition sur la difficulté de ce problème.
Remarque sur le sur-ajustement
Il ne faut pas rechercher des performances d'entraînement supérieures, il est presque inutile d'essayer d'ajuster les données d'entraînement, car nous essayons généralement de bien fonctionner avec un modèle sur de nouvelles données invisibles avec des propriétés similaires aux données du train. l'idée est d'essayer de généraliser et d'apprendre les propriétés des données et la corrélation avec la cible, c'est ça l'apprentissage :)
la source
Fondamentalement, si vous souhaitez obtenir de meilleurs résultats pour les données d'entraînement, la précision de votre entraînement doit être la plus élevée possible. Vous devriez utiliser un meilleur modèle en ce qui concerne les données dont vous disposez. Fondamentalement, vous devez vérifier si votre précision de formation à cet effet, quelle que soit la précision du test. Ceci est également appelé sur-ajustement, ce qui donne une meilleure précision dans les données d'entraînement plutôt que dans les données de test.
Un arrêt précoce peut être affecté pour ce scénario où la meilleure précision de test / validation est prise plutôt que la précision de la formation.
la source
La réponse courte:
Ensemble:
Intuition: Vous décrivez la priorité d'une grande précision dans les données d'entraînement. Cela décrit le sur-ajustement. Pour ce faire, définissez la taille du lot sur 1, les époques élevées et mélangez.
la source
Après avoir changé l'architecture du modèle et l'optimiseur en Adagrad, j'ai pu améliorer les résultats dans une certaine mesure.
La raison d'utiliser l'optimiseur Adagrad est la suivante:
Il adapte le taux d'apprentissage aux paramètres, en effectuant des mises à jour plus petites (c'est-à-dire des taux d'apprentissage faibles) pour les paramètres associés aux fonctionnalités fréquentes et des mises à jour plus importantes (c'est-à-dire des taux d'apprentissage élevés) pour les paramètres associés aux fonctionnalités peu fréquentes. Pour cette raison, il est bien adapté pour traiter des données éparses.
Veuillez vous référer au code ci-dessous:
La prédiction de stock est une tâche très difficile, donc plutôt que de s'en tenir à la prédiction d'un seul modèle, nous pouvons avoir plusieurs modèles travaillant ensemble pour faire une prédiction, puis en fonction du résultat maximum voté, prendre l'appel, similaire à une approche d'apprentissage d'ensemble. En outre, nous pouvons empiler quelques modèles comme:
Réseau de neurones à auto-encodage à rétroaction profonde pour réduire la dimension + Réseau de neurones récurrent profond + ARIMA + Régresseur de gradient de boosting extrême
Adaboost + Ensachage + Arbres supplémentaires + Boost de dégradé + Forêt aléatoire + XGB
Les agents d'apprentissage par renforcement se débrouillent plutôt bien dans la prédiction boursière comme:
Veuillez trouver un lien très ingénieux ici .
la source
Comme d'autres l'ont déjà dit, vous ne devriez pas en attendre grand-chose.
Néanmoins, j'ai trouvé ce qui suit dans votre code:
Vous réinstallez le détartreur à chaque fois pendant la formation et les tests. Vous devez enregistrer le sacler et transformer uniquement les données pendant les tests, sinon les résultats seront légèrement différents:
Set
shuffle=False
. Comme vous devez garder l'ordre de votre jeu de données.Set
batch_size=1
. Comme il sera moins sujet au surapprentissage et l'apprentissage sera plus bruyant et l'erreur moins moyennée.Définissez
epochs=50
ou plus.Avec les paramètres mentionnés ci-dessus, le modèle est atteint
loss: 0.0037 - val_loss: 3.7329e-04
.Vérifiez les exemples de prédiction suivants:
Du 17/04/2020 -> 23/04/2020:
Du 02/04/2020 -> 08/04/2020:
Du 25/03/2020 -> 31/03/2020:
la source
C'est insuffisant et pour améliorer cela, je pense que vous devez ajouter des neurones dans vos couches cachées. !! Un autre point est d'essayer la fonction d'activation «relu». Sigmoid ne donne pas de bons résultats. Vous devez également définir «softmax» dans votre couche de sortie.!
la source
Regardez ce que vous faites:
la source