Vorhersage von Aktienkursen mit LSTM

Maschinelles Lernen hat in den letzten Jahren in vielen interessanten Bereichen seine Anwendung gefunden. Die Zähmung des Aktienmarktes ist eines davon. Ich hatte schon seit geraumer Zeit mit dem Gedanken gespielt, es einmal zu versuchen, vor allem um meine Kenntnisse über LSTMs zu festigen. Nun habe ich das Projekt abgeschlossen und freue mich, meine Erfahrungen mit anderen zu teilen.

Motivation und Zielgruppe

Ich werde in einer Reihe von Blogs über meine Erfahrungen schreiben. Der Zweck dieser Serie besteht nicht darin, die Grundlagen von LSTM oder Konzepten des maschinellen Lernens zu erklären. Daher gehe ich davon aus, dass der Leser seine Reise mit Machine Learning begonnen hat und über die Grundlagen wie Python, Vertrautheit mit SkLearn, Keras, LSTM usw. verfügt. Der Grund dafür ist, dass es bereits hervorragende Artikel zu Themen wie „Wie funktionieren LSTMs?“ von Leuten gibt, die viel qualifizierter sind, die Mathematik dahinter zu erklären. Aber ich werde Links zu solchen Artikeln weitergeben, wenn ich das Gefühl habe, dass Hintergrundwissen fehlt. Es gibt zwar viele Artikel, in denen beschrieben wird, wie man anhand eines Datensatzes Aktienkurse vorhersagen kann, aber die meisten Autoren erklären nicht, wie sie zu einer bestimmten Konfiguration eines Neuronalen Netzes gekommen sind oder wie sie einen bestimmten Satz von Hyperparametern ausgewählt haben. Der eigentliche Zweck dieses Artikels besteht also darin, solche Schritte, meine Fehler und einige Schritte, die ich sehr hilfreich fand, mit anderen zu teilen. Daher ist dieser Artikel nicht auf das Problem der Aktienkursvorhersage beschränkt.

Hier sind die Dinge, die wir uns ansehen werden:

  1. Lesen und Analysieren von Daten. (Pandas)
  2. Normalisieren der Daten. (SkLearn)
  3. Daten in Zeitreihen und überwachte Lernprobleme umwandeln.
  4. Modell erstellen (Keras)
  5. Feinabstimmung des Modells (im nächsten Artikel)
  6. Training, Vorhersage und Visualisierung der Ergebnisse.
  7. Tipps & Tools, die ich sehr hilfreich fand (letzter Artikel der Serie)

Bitte beachten Sie, dass dieser erste Artikel über die Vorverarbeitungsschritte und die Terminologie von LSTM spricht. Wenn Sie mit diesen Schritten vertraut sind, können Sie den nächsten Artikel überspringen.

Lassen Sie uns beginnen!

Lesen und Analysieren der Daten

Ich werde für diesen Beitrag die historischen Aktienkursdaten von GE verwenden. Sie können die Daten auf meiner Kaggle-Seite hier finden. Ich erinnere mich nicht mehr an die Quelle der Daten, da ich sie vor langer Zeit heruntergeladen hatte. Wir können die Daten wie folgt einlesen:

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

Wie Sie sehen können, gibt es etwa 14060 Elemente, die jeweils einen Börsentag für das Unternehmen darstellen. Schauen wir mal, wie es auf einem Diagramm aussieht :

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()

Es scheint, als ob die Preise – Open, Close, Low, High – nicht allzu sehr voneinander abweichen, abgesehen von gelegentlichen leichten Rückgängen beim Low-Preis.

Nun schauen wir uns mal den Plot für das Volumen an:

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

Huh. Haben Sie etwas Interessantes gesehen? Es gibt einen ziemlichen Anstieg der Anzahl der Transaktionen um den 12000. Tag auf der Zeitachse, der zufällig mit dem plötzlichen Rückgang des Aktienkurses zusammenfällt. Vielleicht können wir zu diesem Datum zurückgehen und alte Nachrichtenartikel ausgraben, um die Ursache dafür herauszufinden.

Nun wollen wir sehen, ob wir irgendwelche Null/Nan-Werte haben, um die wir uns Sorgen machen müssen. Wie sich herausstellt, haben wir keine Nullwerte. Großartig!

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

Normalisierung der Daten

Die Daten sind nicht normalisiert und der Bereich für jede Spalte variiert, insbesondere das Volumen. Die Normalisierung der Daten hilft dem Algorithmus bei der Konvergenz, d.h. bei der effizienten Suche nach dem lokalen/globalen Minimum. Ich werde MinMaxScaler aus Sci-kit Learn verwenden. Aber vorher müssen wir den Datensatz in Trainings- und Testdatensätze aufteilen. Dabei werde ich auch den DataFrame in ndarray umwandeln.

Daten in Zeitreihen umwandeln und Problem des überwachten Lernens

Dies ist ziemlich wichtig und etwas knifflig. Hier wird das Wissen von LSTM benötigt. Ich würde eine kurze Beschreibung der Schlüsselkonzepte geben, die hier benötigt werden, aber ich empfehle dringend, Andre Karpathys Blog hier zu lesen, der als eine der besten Ressourcen über LSTM da draußen gilt und dies. Sie können sich auch das Video von Andrew Ng ansehen (in dem übrigens auch Andres Blog erwähnt wird).

LSTMs verarbeiten Eingaben im Format eines dreidimensionalen Arrays.

  • Die Stapelgröße gibt an, wie viele Stichproben der Eingaben Ihr Neuronales Netz sehen soll, bevor es die Gewichte aktualisiert. Angenommen, Sie haben 100 Stichproben (Eingabedatensatz) und möchten die Gewichte jedes Mal aktualisieren, wenn Ihr NN eine Eingabe gesehen hat. In diesem Fall wäre die Stapelgröße 1 und die Gesamtzahl der Stapel wäre 100. Wenn Sie möchten, dass Ihr Netz die Gewichte aktualisiert, nachdem es alle Stichproben gesehen hat, wäre die Stapelgröße 100 und die Anzahl der Stapel 1. Es stellt sich heraus, dass die Verwendung einer sehr kleinen Stapelgröße die Geschwindigkeit des Trainings reduziert und andererseits die Verwendung einer zu großen Stapelgröße (wie der gesamte Datensatz) die Fähigkeit des Modells zur Generalisierung auf verschiedene Daten reduziert und auch mehr Speicher verbraucht. Es sind jedoch weniger Schritte erforderlich, um die Minima für Ihre Zielfunktion zu finden. Sie müssen also verschiedene Werte für Ihre Daten ausprobieren und den Sweet Spot finden. Das ist ein ziemlich großes Thema. Im nächsten Artikel werden wir sehen, wie man auf etwas intelligentere Weise danach suchen kann.
  • Zeitschritte definieren, wie viele Einheiten in der Zeit zurück Sie Ihr Netzwerk sehen wollen. Wenn Sie zum Beispiel an einem Problem der Zeichenvorhersage arbeiten, bei dem Sie einen Textkorpus zum Trainieren haben, und Sie beschließen, Ihr Netz mit jeweils 6 Zeichen zu füttern, dann ist Ihr Zeitschritt 6. Dann ist Ihr Zeitschritt 6. In unserem Fall werden wir 60 als Zeitschritt verwenden, d.h. wir werden 2 Monate an Daten betrachten, um den Preis des nächsten Tages vorherzusagen. Mehr dazu später.
  • Merkmale ist die Anzahl der Attribute, die zur Darstellung jedes Zeitschritts verwendet werden. Betrachten wir das obige Beispiel der Zeichenvorhersage und nehmen wir an, dass Sie einen kodierten Ein-Hot-Vektor der Größe 100 zur Darstellung jedes Zeichens verwenden. Dann ist die Merkmalsgröße hier 100.

Nun, da wir die Terminologie einigermaßen geklärt haben, wollen wir unsere Bestandsdaten in ein geeignetes Format konvertieren. Nehmen wir der Einfachheit halber an, dass wir 3 als Zeitschritt gewählt haben (wir wollen, dass unser Netzwerk auf 3 Tage Daten zurückblickt, um den Preis am vierten Tag vorherzusagen), dann würden wir unseren Datensatz wie folgt bilden:

Die Stichproben 0 bis 2 wären unsere erste Eingabe und der Schlusskurs von Stichprobe 3 wäre der entsprechende Ausgabewert; beide sind von einem grünen Rechteck umschlossen. In ähnlicher Weise wären die Stichproben 1 bis 3 unsere zweite Eingabe und der Schlusskurs von Stichprobe 4 wäre der entsprechende Ausgabewert; beide werden durch ein blaues Rechteck dargestellt. Und so weiter. Bis jetzt haben wir also eine Matrix der Form (3, 5), wobei 3 für den Zeitschritt und 5 für die Anzahl der Merkmale steht. Überlegen Sie nun, wie viele solcher Eingabe-Ausgabe-Paare im obigen Bild möglich sind? 4.

Mischen Sie auch die Stapelgröße dazu. Nehmen wir an, wir wählen eine Stapelgröße von 2. Dann würden das Eingabe-Ausgabe-Paar 1 (grünes Rechteck) und das Paar 2 (blaues Rechteck) den ersten Stapel bilden. Und so weiter. Hier ist der Python-Codeausschnitt, um dies zu tun:

‚y_col_index‘ ist der Index Ihrer Ausgabespalte. Nehmen wir nun an, dass Sie nach der Konvertierung der Daten in das Format für überwachtes Lernen, wie oben gezeigt, 41 Stichproben in Ihrem Trainingsdatensatz haben, Ihre Stapelgröße aber 20 beträgt, dann müssen Sie Ihren Trainingsdatensatz trimmen, um die ungeraden Stichproben zu entfernen. Ich werde nach einem besseren Weg suchen, um dies zu umgehen, aber im Moment habe ich folgendes getan:

Nutzen wir nun die oben genannten Funktionen, um unsere Trainings-, Validierungs- und Testdatensätze zu bilden

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)

Nun, da unsere Daten fertig sind, können wir uns auf den Aufbau des Modells konzentrieren.

Erstellen des Modells

Wir werden für diese Aufgabe das LSTM verwenden, das eine Variante des rekurrenten neuronalen Netzes ist. Die Erstellung eines LSTM-Modells ist ganz einfach:

Nun, da Sie Ihr Modell kompiliert haben und bereit sind, es zu trainieren, trainieren Sie es wie unten gezeigt. Wenn Sie sich fragen, welche Werte Sie für Parameter wie Epochen, Stapelgröße usw. verwenden sollen, keine Sorge, wir werden im nächsten Artikel sehen, wie Sie diese herausfinden können.

Das Trainieren dieses Modells (mit fein abgestimmten Hyperparametern) ergab den besten Fehler von 3,27e-4 und den besten Validierungsfehler von 3,7e-4. So sah der Trainingsverlust gegenüber dem Validierungsverlust aus:

Trainingsfehler gegenüber Validierungsfehler

So sah die Vorhersage mit dem obigen Modell aus:

Vorhersage vs. reale Daten

Ich habe herausgefunden, dass diese Konfiguration für LSTM am besten von allen Kombinationen funktioniert, die ich (für diesen Datensatz) ausprobiert habe, und ich habe mehr als 100 ausprobiert! Die Frage ist also: Wie findet man die perfekte (oder in fast allen Fällen nahezu perfekte) Architektur für sein neuronales Netz? Dies führt uns zu unserem nächsten und wichtigen Abschnitt, der im nächsten Artikel fortgesetzt wird.

Sie können alle vollständigen Programme auf meinem Github-Profil hier finden.

HINWEIS: Eine bescheidene Bitte an die Leser – Sie alle sind herzlich eingeladen, mit mir auf LinkedIn oder Twitter in Kontakt zu treten, aber wenn Sie eine Frage zu meinen Blogs haben, posten Sie sie bitte im Kommentarbereich des jeweiligen Blogs statt in einer persönlichen Nachricht, so dass, wenn jemand anderes die gleiche Frage hat, er sie hier selbst finden würde und ich sie nicht einzeln erklären müsste. Sie können mir aber gerne auch Anfragen, die nichts mit Blogs oder allgemeinen technischen Fragen zu tun haben, persönlich schicken. Danke 🙂

UPDATE 13/4/19

  1. Seit ich diesen Artikel geschrieben habe, habe ich erfahren, dass mein Modell, das ich für diesen Blog verwendet habe, möglicherweise überangepasst wurde. Ich habe es zwar nicht bestätigt, aber es ist wahrscheinlich. Seien Sie also bitte vorsichtig, wenn Sie dieses Modell in Ihren Projekten einsetzen. Sie könnten Dinge ausprobieren wie weniger Epochen, ein kleineres Netzwerk, mehr Aussetzer usw.
  2. Ich habe die Sigmoid-Aktivierung für die letzte Schicht verwendet, die möglicherweise unter der Einschränkung leidet, dass sie nicht in der Lage ist, einen Preis vorherzusagen, der größer ist als der „maximale“ Preis im Datensatz. Sie könnten die „lineare“ Aktivierung für die letzte Schicht ausprobieren, um dieses Problem zu lösen.
  3. Ich habe einen Tippfehler im Abschnitt „Konvertierung von Daten in Zeitreihen“ behoben.

Danke an die Leser, die mich auf diese Probleme aufmerksam gemacht haben.

UPDATE 21/1/2020

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.