Prédire le prix des actions avec LSTM

L’apprentissage automatique a trouvé ses applications dans de nombreux domaines intéressants au cours de ces années. Apprivoiser le marché boursier est l’un d’entre eux. J’envisageais de m’y essayer depuis un certain temps déjà ; principalement pour solidifier ma connaissance pratique des LSTM. Et finalement, j’ai terminé le projet et assez excité de partager mon expérience.

Motivation et public cible

Je vais écrire sur mon expérience sur une série de blogs. Le but de cette série n’est pas d’expliquer les bases des concepts de LSTM ou d’apprentissage automatique. Par conséquent, je vais supposer que le lecteur a commencé son voyage avec l’apprentissage automatique et a les bases comme Python, la familiarité avec SkLearn, Keras, LSTM, etc. La raison en est qu’il existe déjà d’excellents articles sur des sujets tels que « Comment fonctionnent les LSTM », rédigés par des personnes beaucoup plus qualifiées pour expliquer les mathématiques. Mais je partagerai des liens vers de tels articles chaque fois que j’aurai l’impression que des connaissances de base font défaut. Il existe de nombreux articles qui expliquent comment prédire les cours de la bourse à partir d’un ensemble de données, mais la plupart des auteurs ne révèlent/expliquent pas comment ils sont parvenus à cette configuration particulière d’un réseau neuronal ou comment ils ont sélectionné cet ensemble particulier d’hyperparamètres. Le but réel de cet article est donc de partager ces étapes, mes erreurs et certaines étapes que j’ai trouvées très utiles. En tant que tel, cet article n’est pas limité au problème de prédiction du prix des actions.

Voici les choses que nous allons examiner :

  1. Lire et analyser les données. (Pandas)
  2. Normaliser les données. (SkLearn)
  3. Conversion des données en séries temporelles et problème d’apprentissage supervisé.
  4. Création de modèle (Keras)
  5. Réglage fin du modèle (dans le prochain article)
  6. Entraînement, prédiction et visualisation du résultat.
  7. Tips &outils que j’ai trouvé très utiles (dernier article de la série)

Veuillez noter que ce premier article parle des étapes de prétraitement et des terminologies des LSTM. Si vous êtes assez confiant dans ces étapes, vous pouvez passer à l’article suivant.

Commençons !

Lecture et analyse des données

J’utiliserai les données historiques du cours des actions de GE pour ce post. Vous pouvez trouver les données dans mon site kaggle ici. Je ne me souviens pas de la source des données puisque je les avais téléchargées il y a longtemps. Nous pouvons lire les données dans le cadre comme indiqué ci-dessous :

df_ge = pd.read_csv(os.path.join(INPUT_PATH, "us.ge.txt"), engine='python')
df_ge.tail()

Comme vous pouvez le voir, il y a environ 14060 éléments, chacun représentant les attributs boursiers d’un jour pour la société. Voyons comment cela se présente sur un graphique :

from matplotlib import pyplot as pltplt.figure()
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.plot(df_ge)
plt.title('GE stock price history')
plt.ylabel('Price (USD)')
plt.xlabel('Days')
plt.legend(, loc='upper left')
plt.show()

Il semble que les prix – Open, Close, Low, High – ne varient pas trop les uns des autres, sauf pour de légères baisses occasionnelles du prix Low.

Maintenant, vérifions le tracé du volume :

plt.figure()
plt.plot(df_ge)
plt.title('GE stock volume history')
plt.ylabel('Volume')
plt.xlabel('Days')
plt.show()

Huh. Avez-vous vu quelque chose d’intéressant ? Il y a une forte augmentation du nombre de transactions autour du 12000ème jour sur la ligne de temps, ce qui coïncide avec la chute soudaine du prix des actions. Peut-être pouvons-nous revenir à cette date particulière et déterrer de vieux articles de presse pour trouver ce qui l’a causée.

Maintenant, voyons si nous avons des valeurs nulles/Nan à craindre. Il s’avère que nous n’avons pas de valeurs nulles. Super!

print("checking if any null values are present\n", df_ge.isna().sum())

Normalisation des données

Les données ne sont pas normalisées et la plage de chaque colonne varie, en particulier le volume. Normaliser les données aide l’algorithme à converger, c’est-à-dire à trouver le minimum local/global de manière efficace. Je vais utiliser MinMaxScaler de Sci-kit Learn. Mais avant cela, nous devons diviser l’ensemble de données en ensembles de données de formation et de test. Aussi je vais convertir le DataFrame en ndarray dans le processus.

Conversion des données en séries temporelles et problème d’apprentissage supervisé

Ceci est assez important et quelque peu délicat. C’est là que les connaissances du LSTM sont nécessaires. Je donnerais une brève description des concepts clés qui sont nécessaires ici, mais je recommande fortement de lire le blog d’Andre karpathy ici, qui est considéré comme l’une des meilleures ressources sur LSTM là-bas et ceci. Ou vous pouvez également regarder la vidéo d’Andrew Ng (qui mentionne d’ailleurs le blog d’Andre aussi).

LSTMs consomment l’entrée dans le format ; un tableau à 3 dimensions.

  • Batch Size dit combien d’échantillons d’entrée voulez-vous que votre réseau neuronal voit avant de mettre à jour les poids. Disons donc que vous avez 100 échantillons (ensemble de données d’entrée) et que vous voulez mettre à jour les poids chaque fois que votre NN a vu une entrée. Dans ce cas, la taille du lot sera de 1 et le nombre total de lots sera de 100. De même, si vous voulez que votre réseau mette à jour les poids après avoir vu tous les échantillons, la taille du lot sera de 100 et le nombre de lots sera de 1. Il s’avère que l’utilisation d’une très petite taille de lot réduit la vitesse de formation et, d’autre part, l’utilisation d’une trop grande taille de lot (comme l’ensemble des données) réduit la capacité du modèle à généraliser à différentes données et consomme également plus de mémoire. Mais il faut moins d’étapes pour trouver les minima de votre fonction objective. Vous devez donc essayer différentes valeurs sur vos données et trouver le point idéal. C’est un sujet assez vaste. Nous verrons comment les rechercher de manière un peu plus intelligente dans le prochain article.
  • Les pas de temps définissent combien d’unités en arrière dans le temps vous voulez que votre réseau voit. Par exemple, si vous travaillez sur un problème de prédiction de caractères où vous avez un corpus de texte à entraîner et que vous décidez de nourrir votre réseau de 6 caractères à la fois. Alors votre pas de temps est de 6. Dans notre cas, nous allons utiliser 60 comme pas de temps c’est-à-dire que nous allons regarder dans 2 mois de données pour prédire le prix des prochains jours. Plus sur ce sujet plus tard.
  • Features est le nombre d’attributs utilisés pour représenter chaque pas de temps. Considérons l’exemple de prédiction de caractères ci-dessus, et supposons que vous utilisez un vecteur codé à un coup de taille 100 pour représenter chaque caractère. Alors la taille des caractéristiques est ici de 100.

Maintenant que nous avons quelque peu éclairci les terminologies, convertissons nos données de stock dans un format approprié. Supposons, pour simplifier, que nous choisissions 3 comme pas de temps (nous voulons que notre réseau se base sur 3 jours de données pour prédire le prix du 4ème jour), alors nous formerions notre ensemble de données comme ceci :

Les échantillons 0 à 2 seraient notre première entrée et le prix de clôture de l’échantillon 3 serait sa valeur de sortie correspondante ; tous deux entourés d’un rectangle vert. De même, les échantillons 1 à 3 seront notre deuxième entrée et le prix de clôture de l’échantillon 4 sera la valeur de sortie correspondante, représentée par un rectangle bleu. Et ainsi de suite. Jusqu’à présent, nous avons donc une matrice de forme (3, 5), 3 étant le pas de temps et 5 le nombre de caractéristiques. Maintenant, pensez à combien de telles paires d’entrée-sortie sont possibles dans l’image ci-dessus ? 4.

Mélangez également la taille du lot avec ceci. Supposons que nous choisissions une taille de lot de 2. Alors la paire d’entrée-sortie 1 (rectangle vert) et la paire 2 (rectangle bleu) constitueraient le premier lot. Et ainsi de suite. Voici le bout de code python pour faire cela:

‘y_col_index’ est l’index de votre colonne de sortie. Maintenant, supposons qu’après avoir converti les données en format d’apprentissage supervisé, comme indiqué ci-dessus, vous avez 41 échantillons dans votre ensemble de données de formation, mais votre taille de lot est de 20, alors vous devrez rogner votre ensemble de formation pour supprimer les échantillons impairs laissés de côté. Je vais chercher une meilleure façon de contourner cela, mais pour l’instant c’est ce que j’ai fait:

Maintenant en utilisant les fonctions ci-dessus formons nos ensembles de données de formation, de validation et de test

x_t, y_t = build_timeseries(x_train, 3)
x_t = trim_dataset(x_t, BATCH_SIZE)
y_t = trim_dataset(y_t, BATCH_SIZE)
x_temp, y_temp = build_timeseries(x_test, 3)
x_val, x_test_t = np.split(trim_dataset(x_temp, BATCH_SIZE),2)
y_val, y_test_t = np.split(trim_dataset(y_temp, BATCH_SIZE),2)

Maintenant que nos données sont prêtes, nous pouvons nous concentrer sur la construction du modèle.

Création du modèle

Nous utiliserons LSTM pour cette tâche, qui est une variation du réseau neuronal récurrent. La création d’un modèle LSTM est aussi simple que ceci :

Maintenant que vous avez votre modèle compilé et prêt à être formé, formez-le comme indiqué ci-dessous. Si vous vous demandez quelles valeurs utiliser pour les paramètres comme les époques, la taille du lot, etc, ne vous inquiétez pas, nous verrons comment les déterminer dans le prochain article.

L’entraînement de ce modèle (avec des hyperparamètres finement réglés) a donné la meilleure erreur de 3,27e-4 et la meilleure erreur de validation de 3,7e-4. Voici à quoi ressemblait la perte de formation par rapport à la perte de validation :

Erreur de formation par rapport à l’erreur de validation

Voici à quoi ressemblait la prédiction avec le modèle ci-dessus :

Prédiction vs données réelles

J’ai constaté que cette configuration pour le LSTM fonctionne le mieux parmi toutes les combinaisons que j’ai essayées (pour cet ensemble de données), et j’en ai essayé plus de 100 ! La question est donc la suivante : comment trouver l’architecture parfaite (ou, dans la plupart des cas, proche de la perfection) pour votre réseau neuronal ? Cela nous amène à notre prochaine et importante section, qui sera poursuivie dans le prochain article.

Vous pouvez trouver tous les programmes complets sur mon profil Github ici.

NOTE : Une humble demande aux lecteurs – Vous êtes tous les bienvenus pour vous connecter avec moi sur LinkedIn ou Twitter, mais si vous avez une question concernant mes blogs, veuillez la poster dans la section des commentaires du blog respectif au lieu de la messagerie personnelle, de sorte que si quelqu’un d’autre a la même question, il la trouverait ici même, et je n’aurais pas à l’expliquer individuellement. Cependant, vous êtes toujours les bienvenus pour m’envoyer des questions sans rapport avec les blogs ou des questions techniques générales, à moi personnellement. Merci 🙂

MISE À JOUR 13/4/19

  1. Il est venu à ma connaissance, depuis que j’ai écrit cet article, que mon modèle utilisé pour ce blog peut avoir été surajusté. Bien que je ne l’ai pas confirmé, c’est probable. Donc, s’il vous plaît, soyez prudent lorsque vous implémentez ceci dans vos projets. Vous pourriez essayer des choses comme moins d’époques, un réseau plus petit, plus de dropout etc.
  2. J’ai utilisé l’activation Sigmoïde pour la dernière couche qui peut souffrir de la limitation de ne pas être capable de prédire un prix supérieur au prix ‘max’ dans le jeu de données. Vous pourriez essayer l’activation ‘linéaire’ pour la dernière couche afin de résoudre ce problème.
  3. Correction d’une coquille dans la section « conversion des données en séries temporelles ».

Merci aux lecteurs d’avoir porté ces éléments à mon attention.

MISE À JOUR 21/1/2020

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.