English 日本語
preview
Deep Learning GRU model with Python to ONNX  with EA, and GRU vs LSTM models

Deep Learning GRU model with Python to ONNX with EA, and GRU vs LSTM models

MetaTrader 5Tester | 21 Mai 2024, 12:47
109 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Einführung

Dies ist die Fortsetzung von „Deep Learning, Vorhersage und Aufträge mit Python, dem MetaTrader5 Python-Paket und ONNX-Modelldatei“, aber Sie können diesen Artikel auch ohne Kenntnis des vorherigen fortsetzen. Alles wird erklärt werden. Alles, was wir verwenden werden, ist in diesem Artikel enthalten. In diesem Abschnitt werden wir Sie durch den gesamten Prozess führen, der in der Erstellung eines Expert Advisors (EA) für den Handel und anschließende Tests gipfelt.

Maschinelles Lernen ist ein Teilbereich der künstlichen Intelligenz (KI), der sich auf die Entwicklung von Algorithmen und statistischen Modellen konzentriert, die es Computern ermöglichen, Aufgaben auszuführen, ohne explizit programmiert zu werden. Das Hauptziel des maschinellen Lernens besteht darin, Computer in die Lage zu versetzen, aus Daten zu lernen und ihre Leistung mit der Zeit zu verbessern.

Wie Modelle funktionieren

Nutzen wir die Grundprinzipien, die der Funktionsweise und Anwendung von Modellen des maschinellen Lernens zugrunde liegen. Auch wenn dies für diejenigen, die bereits Erfahrung mit statistischer Modellierung oder maschinellem Lernen haben, elementar erscheinen mag, seien Sie versichert, dass wir schnell zur Entwicklung anspruchsvoller und robuster Modelle übergehen werden.

Wir werden uns zunächst auf ein Modell konzentrieren, das als Entscheidungsbaum bekannt ist. Es gibt zwar kompliziertere Modelle, die eine höhere Vorhersagegenauigkeit bieten, aber Entscheidungsbäume dienen aufgrund ihrer Einfachheit und ihrer grundlegenden Rolle bei der Konstruktion einiger der fortschrittlichsten Modelle im Bereich der Datenwissenschaft als zugänglicher Einstiegspunkt.

Um die Dinge zu vereinfachen, beginnen wir mit der rudimentärsten Form eines Entscheidungsbaums.

Baum

Hier kategorisieren wir Häuser in nur zwei verschiedene Gruppen. Der erwartete Preis für jedes geeignete Haus wird aus dem historischen Durchschnittspreis von Häusern der gleichen Kategorie abgeleitet.

Anhand von Daten wird die optimale Methode zur Einteilung von Häusern in diese beiden Gruppen ermittelt und anschließend der erwartete Preis für jede Gruppe bestimmt. Dieser entscheidende Schritt, bei dem das Modell Muster aus den Daten erfasst, wird als Anpassung oder Training des Modells bezeichnet. Der zu diesem Zweck verwendete Datensatz wird als Trainingsdaten bezeichnet.

Die Feinheiten der Modellanpassung, einschließlich der Entscheidungen über die Datensegmentierung, sind hinreichend komplex und werden später noch ausführlicher behandelt. Sobald das Modell angepasst ist, kann es auf neue Daten angewandt werden, um die Preise für weitere Wohnungen zu prognostizieren.


Verbesserung des Entscheidungsbaums

Welcher der beiden unten dargestellten Entscheidungsbäume wird mit größerer Wahrscheinlichkeit aus dem Prozess der Anpassung der Trainingsdaten für Immobilien hervorgehen?

Zwei Entscheidungsbäume

Der Entscheidungsbaum auf der linken Seite (Entscheidungsbaum 1) entspricht wahrscheinlich eher der Realität, da er die Korrelation zwischen der Anzahl der Schlafzimmer und den höheren Verkaufspreisen für Häuser widerspiegelt. Sein größter Nachteil ist jedoch, dass er viele andere Faktoren, die sich auf die Hauspreise auswirken, nicht berücksichtigt, wie z. B. die Anzahl der Badezimmer, die Grundstücksgröße, die Lage usw.

Um ein breiteres Spektrum von Faktoren zu berücksichtigen, kann man einen Baum mit zusätzlichen „Splits“ verwenden, der als „tieferer“ Baum bezeichnet wird. Ein Entscheidungsbaum, der die Gesamtgröße des Grundstücks eines jeden Hauses berücksichtigt, könnte beispielsweise so aussehen:

Baum 3

Um den Preis eines Hauses zu schätzen, folgen Sie den Zweigen des Entscheidungsbaums, wobei Sie immer den Weg wählen, der den spezifischen Merkmalen des Hauses entspricht. Der voraussichtliche Preis für das Haus steht am Ende des Baumes. Dieser besondere Punkt, an dem eine Vorhersage getroffen wird, wird als „Blatt“ bezeichnet.

Die Unterteilungen und Werte an diesen Blättern werden von den Daten beeinflusst, was Sie dazu veranlasst, sich den Datensatz anzusehen, mit dem Sie arbeiten.


Pandas für Daten verwenden

In der Anfangsphase eines jeden Projekts zum maschinellen Lernen müssen Sie sich mit dem Datensatz vertraut machen. Hier erweist sich die Pandas-Bibliothek als unverzichtbar. Datenwissenschaftler verwenden in der Regel Pandas als ihr primäres Werkzeug zur Untersuchung und Verarbeitung von Daten. Im Code wird es gewöhnlich als „pd“ abgekürzt.

import pandas as pd

Auswahl der Daten für die Modellierung

Der Datensatz besteht aus einer überwältigenden Anzahl von Variablen, die es schwierig machen, ihn zu verstehen oder gar klar darzustellen. Wie können wir diese riesige Menge an Daten in eine überschaubare Form bringen, um sie besser zu verstehen?

Unser erster Ansatz besteht darin, eine Teilmenge von Variablen auf der Grundlage von Intuition auszuwählen. Im weiteren Verlauf werden wir statistische Techniken einführen, die eine automatische Priorisierung der Variablen ermöglichen.

Um die auszuwählenden Variablen oder Spalten zu ermitteln, müssen wir zunächst eine umfassende Liste aller Spalten des Datensatzes prüfen.

Wir haben diese Daten importiert, und zwar hiermit:

mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)


Erstellung des Modells

Die beste Ressource für die Erstellung Ihrer Modelle ist die Bibliothek scikit-learn, die im Code oft als sklearn abgekürzt wird. Scikit-learn ist die bevorzugte Wahl für die Modellierung von Daten, die typischerweise in DataFrames gespeichert werden.

Im Folgenden werden die wichtigsten Schritte zur Erstellung und Verwendung eines Modells beschrieben:

  • Definition: Bestimmung der Art des Modells, das erstellt werden soll. Handelt es sich um einen Entscheidungsbaum, oder soll ein anderes Modell gewählt werden? Es werden auch spezifische Parameter für den ausgewählten Modelltyp defriniert.
  • Anpassen: Diese Phase ist der Kern der Modellierung, in der das Modell aus den bereitgestellten Daten lernt und Muster erfasst. Dabei wird das Modell mit Ihrem Datensatz trainiert.
  • Vorhersage: So einfach es auch klingt, in diesem Schritt wird Ihr trainiertes Modell verwendet, um Vorhersagen für neue oder ungesehene Daten zu treffen. Das Modell verallgemeinert, was es gelernt hat, um fundierte Vorhersagen zu treffen.
  • Auswerten: Bewertung der Genauigkeit der Vorhersagen des Modells. In diesem entscheidenden Schritt wird die Ausgabe des Modells mit den tatsächlichen Ergebnissen verglichen, sodass man seine Leistung und Zuverlässigkeit beurteilen können.

Mit scikit-learn bieten diese Schritte einen strukturierten Rahmen für die effiziente Erstellung, das Training und die Auswertung von Modellen, die auf die verschiedenen Daten zugeschnitten sind, die typischerweise in DataFrames zu finden sind.


Die Gated Recurrent Unit (GRU)

Wikipedia sagt dazu:

Gated recurrent units (GRUs) verwenden ein Gatter- (Gating-) Mechanismus in , der 2014 von Kyunghyun Cho eingeführt wurde. GRUs ähneln einem (LSTM) mit einem Gatter-Mechanismus zum Eingeben oder Vergessen bestimmter Merkmale, verfügen aber nicht über einen Kontextvektor oder ein Ausgangsgatter, was zu weniger Parametern als LSTM führt. Die Leistung von GRU bei bestimmten Aufgaben der Modellierung polyphoner Musik, der Modellierung von Sprachsignalen und der Verarbeitung natürlicher Sprache wurde als ähnlich zu der von LSTM befunden. Die GRUs zeigten, dass Gatter im Allgemeinen tatsächlich hilfreich ist, und das Team von Bengio kam zu keinem konkreten Ergebnis, welche der beiden Gatter-Einheiten besser ist.

GRU, ein Akronym für Gated Recurrent Unit, ist eine Variante der Architektur rekurrenter, neuronaler Netze (RNN), die dem LSTM (Long Short-Term Memory) ähnelt.

Ähnlich wie LSTM ist GRU für die Modellierung sequenzieller Daten konzipiert und ermöglicht die selektive Beibehaltung oder Auslassung von Informationen über die Zeit. GRU zeichnet sich im Vergleich zu LSTM durch eine schlankere Architektur mit weniger Parametern aus. Diese Eigenschaft erhöht die Leichtigkeit des Trainings und die Effizienz der Berechnungen.

Der Hauptunterschied zwischen GRU und LSTM liegt im Umgang mit dem Zustand der Speicherzellen. Beim LSTM unterscheidet sich der Zustand der Speicherzelle vom verborgenen Zustand und wird durch drei Gatter aktualisiert: das Eingangsgatter, das Ausgangsgatter und das Vergessensgatter. Umgekehrt ersetzt GRU den Zustand der Speicherzelle durch einen „Kandidaten-Aktivierungsvektor“, der über zwei Gatter aktualisiert wird: das Reset-Gatter und das Update-Gatter.

Zusammenfassend lässt sich sagen, dass GRU eine bevorzugte Alternative zu LSTM für die sequentielle Datenmodellierung darstellt, insbesondere in Szenarien, in denen Rechenbeschränkungen bestehen oder eine einfachere Architektur bevorzugt wird.


Wie GRU operiert:

Ähnlich wie andere rekurrente neuronale Netzarchitekturen verarbeitet GRU sequentielle Daten Element für Element und passt ihren verborgenen Zustand auf der Grundlage der aktuellen Eingabe und des vorangegangenen verborgenen Zustands an. Bei jedem Zeitschritt berechnet GRU einen „Kandidaten-Aktivierungsvektor“, der Informationen aus der Eingabe und dem vorherigen verborgenen Zustand zusammenfasst. Dieser Vektor aktualisiert dann den verborgenen Zustand für den nächsten Zeitschritt.


Der Kandidaten-Aktivierungsvektor wird mit zwei Gattern berechnet: dem Reset-Gatter und dem Update-Gatter. Das Reset-Gatter bestimmt den Grad des Vergessens des vorherigen versteckten Zustands, während das Update-Gatter die Integration des Kandidaten-Aktivierungsvektors in den neuen versteckten Zustand beeinflusst.

Dies ist das Modell (GRU), das wir für diesen Artikel wählen werden.

model.add(Dense(128, activation='relu', input_shape=(inp_history_size,1), kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dropout(0.05))
model.add(Dense(1, activation='linear'))

Zunächst wählen wir die Eingabe in Features und die Zielvariable aus:

if 'Close' in data.columns:
    data['target'] = data['Close']
else:
    data['target'] = data.iloc[:, 0]

# Extract OHLC columns
x_features = data[[0]]
# Target variable
y_target = data['target']

Wir unterteilen die Daten in Trainings- und Testsätze:

x_train, x_test, y_train, y_test = train_test_split(x_features, y_target, test_size=0.2, shuffle=False)

In diesem Fall beträgt die Testgröße 20 %, in der Regel wird eine Testgröße von weniger als 30 % gewählt (um eine Überanpassung zu vermeiden).

Sequentielle Modellinitialisierung:

model = Sequential()

Mit dieser Zeile wird ein leeres sequentielles Modell erstellt, mit dem Sie schrittweise Schichten hinzufügen können.

Hinzufügen der verdichteten (dense) Schichten:

model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))

Die verdichtete Schicht (dense layer) ist eine vollständig verbundene Schicht in einem neuronalen Netz.

Die Zahlen in den Klammern geben die Anzahl der Neuronen in jeder Schicht an. Die erste Schicht besteht also aus 128 Neuronen, die zweite aus 256, die dritte aus 128 und die vierte aus 64.

Die Aktivierungsfunktion „relu“ (Rectified Linear Unit) wird verwendet, um nach jeder Schicht eine Nichtlinearität einzuführen, die dem Modell hilft, komplexe Muster zu lernen.

Der Parameter input_shape wird nur in der ersten Schicht angegeben und definiert die Form der Eingabedaten. In diesem Fall entspricht sie der Anzahl der Merkmale in den Eingabedaten.

kernel_regularizer=l2(k_reg) wendet eine L2-Regularisierung auf die Gewichte der Schicht an und hilft, eine Überanpassung zu verhindern, indem große Gewichtswerte bestraft werden.

Ausgangsschicht:

model.add(Dense(1, activation='linear'))

  • Die letzte Schicht besteht aus einem einzigen Neuron, was typisch für eine Regressionsaufgabe ist (Vorhersage eines kontinuierlichen Wertes).
  • Es wird die „lineare“ Aktivierungsfunktion verwendet, was bedeutet, dass die Ausgabe eine lineare Kombination der Eingaben ohne weitere Transformation ist.
  • Zusammenfassend lässt sich sagen, dass dieses Modell aus mehreren verdichteten Schichten mit gleichgerichteten linearen Aktivierungsfunktionen besteht, gefolgt von einer linearen Ausgabeschicht. Zur Regularisierung wird die L2-Regularisierung auf die Gewichte angewendet. Diese Architektur wird in der Regel für Regressionsaufgaben verwendet, bei denen es darum geht, einen numerischen Wert vorherzusagen.

Wir kompilieren nun das Modell

# Compile the model[]
model.compile(optimizer='adam', loss='mean_squared_error')

Optimierer:

Der Optimierer ist eine entscheidende Komponente des Trainingsprozesses. Er bestimmt, wie die Gewichte des Modells während des Trainings aktualisiert werden, um die Verlustfunktion zu minimieren. adam' ist ein beliebter Optimierungsalgorithmus, der für seine Effizienz beim Training neuronaler Netze bekannt ist. Es passt die Lernraten für jeden Parameter individuell an und eignet sich daher für ein breites Spektrum von Problemen.

Verlustfunktion:

Der Verlustparameter definiert das Ziel, das das Modell beim Training zu minimieren versucht. In diesem Fall wird „mean_squared_error“ als Verlustfunktion verwendet. Der mittlere, quadratische Fehler (MSE) ist eine gängige Wahl für Regressionsprobleme, bei denen das Ziel darin besteht, die mittlere quadratische Differenz zwischen den vorhergesagten Werten und den tatsächlichen Werten zu minimieren. Sie eignet sich für Probleme, bei denen die Ausgabe ein kontinuierlicher Wert ist. MAE berechnet den Durchschnitt der absoluten Differenzen zwischen den vorhergesagten Werten und den tatsächlichen Werten.

mae und mse

Alle Fehler werden unabhängig von ihrer Richtung mit der gleichen Bedeutung behandelt. Ein niedriger MAE-Wert bedeutet auch eine bessere Modellleistung.

Zusammenfassend lässt sich sagen, dass die Anweisung model.compile das neuronale Netzmodell für das Training konfiguriert. Sie gibt den Optimierer („adam') für die Aktualisierung der Gewichte während des Trainings und die Verlustfunktion („mean_squared_error') an, die das Modell minimieren soll. Dieser Kompilierungsschritt ist eine notwendige Vorstufe zum Training des Modells mit Daten.

Und wir trainieren das zuvor definierte neuronale Netzmodell.

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2, verbose=1)
Trainingsdaten:

X_train_scaled: Dies sind die Eingabedaten für das Training, die vermutlich skaliert oder vorverarbeitet wurden, um numerische Stabilität zu gewährleisten.

y_train: Dies sind die entsprechenden Zielwerte oder Labels für die Trainingsdaten.

Konfiguration der Ausbildung:

epochs=int(epoch): Dieser Parameter gibt an, wie oft der gesamte Trainingsdatensatz vorwärts und rückwärts durch das neuronale Netz geleitet wird.

int(epoch) gibt an, dass die Anzahl der Epochen durch die Variable epoch bestimmt wird.

batch_size=256: Während jeder Epoche werden die Trainingsdaten in Stapel aufgeteilt, und die Gewichte des Modells werden nach Verarbeitung jedes Stapels aktualisiert. Hier besteht jeder Stapel aus 256 Datenpunkten.

Validierungsdaten:

validation_split=0.2: Dieser Parameter legt fest, dass 20 % der Trainingsdaten als Validierungssatz verwendet werden. Die Leistung des Modells auf dieser Menge wird während des Trainings überwacht, aber nicht zur Aktualisierung der Gewichte verwendet.

Informationsumfang:
verbose=1: Dieser Parameter steuert die Ausführlichkeit der Trainingsausgabe. Ein Wert von 1 bedeutet, dass der Trainingsfortschritt in der Konsole angezeigt wird.

Während des Trainingsprozesses lernt das Modell, Vorhersagen zu treffen, indem es seine Gewichte auf der Grundlage der bereitgestellten Eingabedaten ( X_train_scaled ) und Zielwerte ( y_train ) anpasst. Der Validierungssplit hilft bei der Bewertung der Leistung des Modells auf ungesehenen Daten, und der Trainingsfortschritt wird auf der Grundlage der Einstellung von verbose angezeigt.


Offenlegen der Lineareinheit

Wenn wir uns mit neuronalen Netzen beschäftigen, beginnen wir mit dem Grundbaustein: dem einzelnen Neuron. Schematisch sieht ein Neuron, auch als Einheit bezeichnet, so aus, wenn es mit einem einzigen Eingang konfiguriert ist:

y = x*w + b

Offenlegung der Mechanik der Lineareinheit


Lassen Sie uns die Feinheiten der Kernkomponente eines neuronalen Netzes erforschen: das einzelne Neuron. Visualisiert wird ein Neuron mit einem einzigen Eingang x wie folgt dargestellt:

Die Eingabe, die mit x bezeichnet ist, bildet eine Verbindung zum Neuron, und diese Verbindung hat ein Gewicht, das mit w bezeichnet ist, das ihr zugeordnet ist. Wenn Informationen über diese Verbindung laufen, wird der Wert mit dem der Verbindung zugewiesenen Gewicht multipliziert. Im Falle der Eingabe x ist das, was das Neuron schließlich erreicht, das Produkt w * x. Durch die Anpassung dieser Gewichte „lernt“ ein neuronales Netz mit der Zeit.

Nun führen wir b ein, eine besondere Form der Gewichtung, die wir Bias nennen. Im Gegensatz zu anderen Gewichten sind mit dem Bias keine Eingangsdaten verbunden. Stattdessen wird ein Wert von 1 in den Graphen eingefügt, um sicherzustellen, dass der Wert, der das Neuron erreicht, einfach b ist (da 1 * b gleich b ist). Durch die Einführung des Bias wird das Neuron in die Lage versetzt, seine Ausgabe unabhängig von seinen Eingaben zu ändern.

Y = X*W + b*1


Umfassender Umgang mit mehreren Inputs

Und wenn wir weitere Faktoren einbeziehen wollen? Machen Sie sich keine Sorgen, denn die Lösung ist ganz einfach. Durch die Erweiterung unseres Modells können wir dem Neuron nahtlos zusätzliche Eingangsverbindungen hinzufügen, die jeweils einem bestimmten Merkmal entsprechen.

Zur Ableitung des Ergebnisses führen wir ein einfaches Verfahren durch. Jede Eingabe wird mit dem entsprechenden Verbindungsgewicht multipliziert und die Ergebnisse werden üppig zusammengeführt. Das Ergebnis ist eine ganzheitliche Darstellung, bei der das Neuron geschickt mehrere Eingaben verarbeitet, wodurch das Modell nuancierter wird und das komplizierte Zusammenspiel verschiedener Merkmale widerspiegelt. Mit dieser Methode kann unser neuronales Netz ein breiteres Spektrum an Informationen erfassen, was seine Fähigkeit, Muster umfassend zu erkennen, verbessert.

y = w0*x0 + w1*x1 + w2*x2

Mathematisch ausgedrückt, lässt sich die Funktionsweise dieses Neurons durch die folgende Formel kurz und bündig beschreiben:

y = w 0 ⋅ x 0 + w 1 ⋅ x 1 + w 2 ⋅ x 2 + b =  y=w0​⋅x0​+w1​⋅x1​+w2​⋅x2​+b

wobei:

  • y steht für die Ausgabe des Neurons.
  • w 0, w 1, w 2 ​ bezeichnen die mit den jeweiligen Eingängen x 0 , x 1 , x 2 verbundenen Gewichte​.
  • b steht für den Bias-Term.

Diese mit zwei Eingängen ausgestattete Lineareinheit besitzt die Fähigkeit, eine Ebene in einem dreidimensionalen Raum zu modellieren. Wenn die Anzahl der Eingaben zwei übersteigt, passt das Gerät die Hyperebenen an — mehrdimensionalen Oberflächen, die die Beziehungen zwischen mehreren Eingabemerkmalen auf komplizierte Weise erfassen. Diese Flexibilität ermöglicht es dem neuronalen Netz, komplexe Muster in Daten zu erkennen und zu verstehen, die über einfache lineare Beziehungen hinausgehen.

Lineare Einheiten in Keras

Die Erstellung eines neuronalen Netzwerks in Keras wird nahtlos durch „keras.Sequential()“ erreicht. Dieses Dienstprogramm baut ein neuronales Netz durch das Stapeln von Schichten auf und bietet so einen unkomplizierten Ansatz für die Modellerstellung. Die Schichten kapseln das Wesen der Netzarchitektur ein, und unter ihnen ist die „verdichtete“ Schicht besonders geeignet für die Konstruktion von Modellen, die mit den zuvor untersuchten vergleichbar sind.

model = Sequential()

Im Folgenden werden wir uns mit den Feinheiten der verdichteten Schicht befassen und ihre Fähigkeiten und Rolle beim Aufbau robuster und ausdrucksstarker, neuronaler Netzarchitekturen aufdecken.


Tiefe neuronale Netze

Wir können die Tiefe und die Ausdrucksfähigkeit unseres Netzes durch die Integration von verborgenen Schichten erhöhen. Diese verborgenen Schichten spielen eine entscheidende Rolle bei der Entschlüsselung komplizierter Beziehungen innerhalb der Daten und ermöglichen es Ihrem neuronalen Netzwerk, komplexe Muster zu erkennen und zu erfassen. Wir erhöhen die Komplexität unseres Modells, indem wir strategisch verborgene Schichten hinzufügen und es so in die Lage versetzen, differenzierte Merkmale zu erlernen und darzustellen, um umfassendere und genauere Vorhersagen zu treffen.

Erforschung der Konstruktion komplexer, neuronaler Netze

Wir machen uns nun auf den Weg, um neuronale Netze zu konstruieren, die in der Lage sind, die komplizierten Beziehungen zu erfassen, die die Fähigkeiten tiefer neuronaler Netze ausmachen.

Im Mittelpunkt unseres Ansatzes steht das Konzept der Modularität — eine Strategie, bei der ein komplexes Netzwerk aus elementaren, funktionalen Einheiten zusammengesetzt wird. Nachdem wir uns zuvor damit befasst haben, wie eine lineare Einheit eine lineare Funktion berechnet, konzentrieren wir uns nun auf die Fusion und Anpassung dieser einzelnen Einheiten. Indem wir diese grundlegenden Komponenten strategisch kombinieren und modifizieren, erschließen wir das Potenzial, kompliziertere und vielschichtigere Beziehungen in komplexen Datensätzen zu modellieren und zu verstehen. Dies dient als Ausgangspunkt für die Entwicklung neuronaler Netze, die in der Lage sind, die nuancierten Muster, die den Bereich des Deep Learning ausmachen, geschickt zu navigieren und zu verstehen.

Enthüllte Schichten

In der komplizierten Architektur neuronaler Netze sind die Neuronen systematisch in Schichten organisiert. Eine bemerkenswerte Konfiguration, die sich herauskristallisiert, ist die verdichtete Schicht (dense layer) — eine Konsolidierung von linearen Einheiten, die einen gemeinsamen Satz von Eingängen teilen.

Diese Anordnung ermöglicht eine leistungsfähige und vernetzte Struktur, die es den Neuronen innerhalb der Schicht ermöglicht, Informationen gemeinsam zu verarbeiten und zu interpretieren. Wenn wir uns mit den Feinheiten der Schichten befassen, sticht die verdichtete Schicht als grundlegendes Konstrukt hervor, das veranschaulicht, wie Neuronen gemeinsam zur Fähigkeit des Netzwerks beitragen können, komplexe Beziehungen innerhalb der Daten zu verstehen und zu lernen.

Eingabe, verdichtete Ausgabe 


Verschiedene Schichten in Keras

In der Welt von Keras umfasst ein „Layer“, eine Schicht also, eine bemerkenswert vielseitige Einheit. Im Wesentlichen handelt es sich dabei um jede Form der Datentransformation. Zahlreiche Schichten, z. B. Faltungsschichten und rekurrente Schichten, nutzen Neuronen, um Daten umzuwandeln, und unterscheiden sich vor allem durch die komplizierten Muster der Verbindungen, die sie herstellen. Umgekehrt dienen andere Schichten Zwecken, die vom Feature-Engineering bis hin zur elementaren Arithmetik reichen, was das breite Spektrum an Transformationen zeigt, die innerhalb des modularen Rahmens eines neuronalen Netzes durchgeführt werden können. Die Vielfalt der Schichten unterstreicht die Anpassungsfähigkeit und die weitreichenden Fähigkeiten, die zu den vielfältigen Architekturen neuronaler Netze beitragen.

Neuronale Netze mit Aktivierungsfunktionen verstärken

Überraschenderweise übertrifft die Einbindung von zwei verdichtete Schichten ohne dazwischenliegende Elemente nicht die Wirksamkeit einer einzelnen dichten Schicht. Verdichtete, isolierte Schichten beschränken uns im Bereich der linearen Strukturen, unfähig, die Grenzen von Linien und Flächen zu überschreiten. Um aus dieser Linearität auszubrechen, führen wir ein entscheidendes Element ein: die Nichtlinearität. Dieser zentrale Bestandteil wird durch Aktivierungsfunktionen verkörpert.

Aktivierungsfunktionen dienen als transformative Kraft, die Nichtlinearität in das neuronale Netz einbringt. Sie bieten das wesentliche Werkzeug, um über lineare Beschränkungen hinaus zu navigieren und ermöglichen es dem Modell, komplizierte Muster und Beziehungen in den Daten zu erkennen. Im Wesentlichen sind Aktivierungsfunktionen die Katalysatoren, die neuronale Netze in Bereiche der Komplexität vorantreiben und ihre Fähigkeit freisetzen, die nuancierten Merkmale in verschiedenen Datensätzen zu erfassen.

Wenn wir die Gleichrichterfunktion mit einer Lineareinheit verschmelzen, entsteht eine beeindruckende Einheit, die als gleichgerichtete Lineareinheit (rectified linear unit, ReLU) bekannt ist. Im allgemeinen Sprachgebrauch wird die Gleichrichterfunktion aus diesem Grund oft als „ReLU-Funktion“ bezeichnet. Die Anwendung der ReLU-Aktivierung auf eine Lineareinheit verwandelt die Ausgabe in max(0, w * x + b), eine Darstellung, die in einem Diagramm wie folgt veranschaulicht werden kann:

w*x + b

Strategische Schichtung mit verdichteten Netzen

Mit der neu gewonnenen Nichtlinearität können wir nun die Möglichkeiten des Stapelns von Schichten (layer stacking) erkunden und herausfinden, wie wir damit komplexe Datentransformationen durchführen können.

Eingabe, verborgen & Ausgabe

Verborgene Schichten in neuronalen Netzen freilegen

Die der Ausgabeschicht vorgelagerten Schichten werden oft als „verborgene Schichten“ (hidden layers) bezeichnet, da ihre Ausgaben der direkten Beobachtung verborgen bleiben.

Man beachte, dass die letzte (Ausgangs-) Schicht die Form einer linearen Einheit ohne Aktivierungsfunktion annimmt. Diese architektonische Wahl entspricht Aufgaben mit Regressionscharakter, bei denen es darum geht, einen numerischen Wert vorherzusagen. Bei Aufgaben wie der Klassifizierung kann es jedoch erforderlich sein, eine Aktivierungsfunktion in die Ausgabeschicht einzubauen, um den Anforderungen der jeweiligen Aufgabe besser gerecht zu werden.


Konstruktion von Sequenzialmodellen

Das sequenzielle Modell, wie es bisher verwendet wurde, verknüpft nahtlos eine Reihe von Schichten in einer sequenziellen Weise — von der ersten bis zur letzten Schicht. In dieser strukturellen Orchestrierung dient die erste Schicht als Empfänger des Inputs, während die letzte Schicht schließlich den begehrten Output erzeugt. Dieser sequenzielle Aufbau entspricht dem in der obigen Abbildung dargestellten Modell:

model = keras.Sequential([
    # the hidden ReLU layers
    layers.Dense(units=4, activation='relu', input_shape=[2]),
    layers.Dense(units=3, activation='relu'),
    # the linear output layer 
    layers.Dense(units=1),
])


Sorgen Sie für eine kohärente Schichtung, indem Sie alle Ebenen zusammen in einer Liste darstellen, ähnlich wie bei [Ebene, Ebene, Ebene, ...], anstatt sie einzeln aufzulisten. Um eine Aktivierungsfunktion nahtlos in eine Schicht einzubinden, geben Sie einfach ihren Namen im Aktivierungsargument an. Dieser optimierte Ansatz gewährleistet eine übersichtliche und organisierte Darstellung der Architektur Ihres neuronalen Netzes.

Auswählen der Anzahl von Einheiten in einer verdichteten Schicht

Die Entscheidung über die Anzahl der Einheiten in einer verdichteten Schicht von layers.Dense (z. B. layers.Dense(units=4, ...) ) hängt von den einzigartigen Merkmalen Ihres Problems und der Komplexität der Muster ab, die Sie in Ihren Daten aufdecken wollen. Berücksichtigen Sie die folgenden Faktoren:

Problemkomplexität:

  • Für einfachere Probleme mit weniger komplizierten Beziehungen in den Daten könnte eine kleinere Anzahl von Einheiten, wie 4, ein geeigneter Ausgangspunkt sein.
  • In komplexeren Szenarien, die durch nuancierte und vielschichtige Beziehungen gekennzeichnet sind, ist die Entscheidung für eine größere Anzahl von Einheiten oft von Vorteil.

Größe der Daten:

  • Die Größe des Datensatzes spielt eine Rolle; größere Datensätze können eine größere Anzahl von Einheiten enthalten, aus denen das Modell lernen kann.
  • Kleinere Datensätze erfordern einen vorsichtigeren Ansatz, um eine Überanpassung zu vermeiden und zu verhindern, dass das Modell das Rauschen erlernt.

Modell Kapazität:

  • Die Anzahl der Einheiten beeinflusst die Fähigkeit des Modells, komplexe Muster zu erfassen, wobei eine höhere Anzahl im Allgemeinen die Ausdruckskraft erhöht.
  • Vorsicht ist geboten, um eine Überparametrisierung zu vermeiden, insbesondere bei begrenzten Daten, da dies zu einer Überanpassung führen könnte.

Experimente:

  • Experimentieren Sie mit verschiedenen Konfigurationen, beginnen Sie mit einer bescheidenen Anzahl von Einheiten, trainieren Sie das Modell und verfeinern Sie es auf der Grundlage von Leistungsmetriken und Beobachtungen.
  • Techniken wie die Kreuzvalidierung geben Aufschluss über die Verallgemeinerungsleistung des Modells über verschiedene Datenuntergruppen hinweg.

Denken Sie daran, dass die Wahl der Einheiten nicht allgemein festgelegt ist und unter Umständen einige Versuche und Irrtümer erfordert. Die Überwachung der Leistung des Modells anhand eines Validierungssatzes und die iterative Anpassung der Architektur sind ein wichtiger Teil des Modellentwicklungsprozesses.

Wir wählen dies:

model = Sequential()
model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(1, activation='linear'))

Erste Schicht (Eingabeschicht):

128 Einheiten: Eine relativ hohe Anzahl von 128 Einheiten in der ersten Schicht ermöglicht es dem Modell, vielfältige und komplexe Muster in den Eingabedaten zu erfassen. Dies kann bei der Extraktion komplexer Merkmale in der Anfangsphase des Netzes von Vorteil sein. Aktivierung „relu“: Die Rectified Linear Unit (ReLU)-Aktivierung führt Nichtlinearität ein und ermöglicht es dem Modell, aus komplexen Beziehungen und Mustern zu lernen. Regularisierung (L2): Der L2-Regularisierungsterm ( kernel_regularizer=l2(k_reg) ) hilft, eine Überanpassung zu verhindern, indem er große Gewichte in der Schicht bestraft.

Zweite und dritte Schicht:

256 und 128 Einheiten: Durch die Beibehaltung einer höheren Anzahl von Einheiten in den nachfolgenden Schichten (256 und 128) kann das Modell weiterhin komplexe Informationen erfassen und verarbeiten. Die schrittweise Verringerung der Anzahl der Einheiten trägt zur Schaffung einer Hierarchie der Merkmale bei. Aktivierung „relu“: Die Aktivierung durch ReLU bleibt bestehen und fördert die Nichtlinearität in jeder Schicht. Regularisierung (L2): Die konsequente Anwendung der L2-Regularisierung über alle Schichten hinweg hilft, eine Überanpassung zu verhindern.

Vierte Schicht: 

64 Einheiten: Eine Verringerung der Einheiten verfeinert die Darstellung von Merkmalen weiter und trägt dazu bei, wesentliche Informationen herauszudestillieren und dabei ein Gleichgewicht zwischen Komplexität und Einfachheit zu wahren. Aktivierung „relu“: Die ReLU-Aktivierung bleibt bestehen und gewährleistet die Erhaltung der nichtlinearen Eigenschaften. Regularisierung (L2): Der Regularisierungsterm wird konsequent auf die Stabilität angewandt.

Fünfte Schicht (Ausgabeschicht):

1 Einheit: Die letzte Schicht mit einer einzigen Einheit eignet sich gut für Regressionsaufgaben, bei denen es darum geht, einen kontinuierlichen numerischen Wert vorherzusagen.  „lineare“ Aktivierung: Die lineare Aktivierung ist für die Regression geeignet, da das Modell den vorhergesagten Wert ohne zusätzliche Transformation direkt ausgibt.

Insgesamt scheint die gewählte Architektur auf eine Regressionsaufgabe zugeschnitten zu sein, mit einem durchdachten Gleichgewicht zwischen Ausdrucksvermögen und Regularisierung zur Vermeidung einer Überanpassung. Die schrittweise Verringerung der Anzahl der Einheiten erleichtert die Extraktion von hierarchischen Merkmalen. Dieses Design deutet auf ein umfassendes Verständnis der Komplexität der Aufgabe und auf das Bemühen hin, ein Modell zu entwickeln, das sich gut auf ungesehene Daten verallgemeinern lässt.

Zusammenfassend lässt sich sagen, dass die allmähliche Verringerung der Anzahl der Einheiten in den verborgenen Schichten zusammen mit den spezifischen Entscheidungen für jede Schicht auf ein Design hinweist, das darauf abzielt, hierarchische und abstrakte Darstellungen der Eingabedaten zu erfassen. Die Architektur scheint ein Gleichgewicht zwischen der Komplexität des Modells und der Notwendigkeit, eine Überanpassung zu vermeiden, herzustellen, und die Wahl der Einheiten ist auf die Art der vorliegenden Regressionsaufgabe abgestimmt. Die spezifischen Zahlen könnten durch Experimente und Abstimmung auf der Grundlage der Leistung des Modells anhand von Validierungsdaten ermittelt worden sein.


Kompilieren des Modells

# Compile the model[]
model.compile(optimizer='adam', loss='mean_squared_error')

Wir haben uns bereits mit dem Aufbau vollständig verbundener Netze unter Verwendung von Stapeln verdichteter Schichten befasst. In der Anfangsphase werden die Gewichte des Netzes nach dem Zufallsprinzip festgelegt, was bedeutet, dass das Netz keine Vorkenntnisse besitzt. Jetzt konzentrieren wir uns auf den Prozess des Trainings eines neuronalen Netzes und enträtseln das Wesen des Lernens dieser Netze.

Wie bei maschinellen Lernverfahren üblich, beginnen wir mit einem kuratierten Satz von Trainingsdaten. Jedes Beispiel in diesem Datensatz umfasst Merkmale (Inputs) und ein vorhergesagtes Ziel (Output). Der springende Punkt beim Training des Netzes ist die Anpassung seiner Gewichte, um die Eingangsmerkmale in genaue Vorhersagen für die Zielausgabe umzuwandeln.

Das erfolgreiche Training eines Netzes für eine solche Aufgabe setzt voraus, dass seine Gewichte bis zu einem gewissen Grad die Beziehung zwischen diesen Merkmalen und dem Ziel, wie sie sich in den Trainingsdaten manifestiert, einschließen.

Neben den Trainingsdaten kommen zwei weitere wichtige Komponenten ins Spiel:

  1. Eine „Verlustfunktion“, die die Effizienz der Vorhersagen des Netzes misst.
  2. Ein „Optimierer“, der das Netz anweist, seine Gewichte iterativ anzupassen, um die Leistung zu verbessern.

Je weiter wir uns in den Trainingsprozess hineinwagen, desto wichtiger wird das Verständnis für die Feinheiten dieser Komponenten, um die Fähigkeit eines neuronalen Netzwerks zur Generalisierung und zur Erstellung genauer Vorhersagen für ungesehene Daten zu fördern.

Die Verlustfunktion

Wir haben uns zwar mit dem architektonischen Entwurf eines Netzes befasst, aber der entscheidende Aspekt, ein Netz über das spezifische Problem, das es lösen soll, zu instruieren, muss noch erforscht werden. Diese Verantwortung liegt bei der Verlustfunktion.

Im Wesentlichen quantifiziert die Verlustfunktion den Unterschied zwischen dem tatsächlichen Wert des Ziels und dem vom Modell vorhergesagten Wert. Sie dient als Maßstab für die Bewertung, wie gut das Modell seine Vorhersagen mit den tatsächlichen Ergebnissen in Einklang bringt.

Eine häufig verwendete Verlustfunktion bei Regressionsproblemen ist der mittlere absolute Fehler (MAE). Im Zusammenhang mit jeder Vorhersage, die als y_pred bezeichnet wird, bewertet der MAE die Differenz zum wahren Ziel, y_true, durch Berechnung der absoluten Differenz, abs(y_true - y_pred).

Der kumulative MAE-Verlust für einen Datensatz wird als Mittelwert all dieser absoluten Unterschiede berechnet. Diese Metrik liefert ein umfassendes Maß für die durchschnittliche Größe der Vorhersagefehler und leitet das Modell dazu an, die Gesamtdiskrepanz zwischen seinen Vorhersagen und den wahren Zielen zu minimieren.

mae

Der mittlere absolute Fehler stellt den durchschnittlichen Abstand zwischen der angepassten Kurve und den tatsächlichen Datenpunkten dar.

Neben der MAE sind alternative Verlustfunktionen, die häufig bei Regressionsproblemen auftreten, der mittlere, quadratische Fehler (MSE) und der Huber-Verlust, die beide in Keras verfügbar sind.

Während des gesamten Trainingsprozesses stützt sich das Modell auf die Verlustfunktion als Orientierungshilfe, um die optimalen Werte für seine Gewichte zu bestimmen — mit dem Ziel, den geringstmöglichen Verlust zu erzielen. Im Wesentlichen vermittelt die Verlustfunktion das Ziel des Netzes und leitet es zum Lernen und zur Verfeinerung seiner Parameter an, um die Vorhersagegenauigkeit zu verbessern.

Der Optimierer - Stochastischer Gradientenabstieg

Nachdem das Problem, das das Netz lösen soll, definiert wurde, ist der nächste entscheidende Schritt die Festlegung des Lösungsweges. Diese Aufgabe übernimmt der Optimierer — ein Algorithmus zur Feinabstimmung der Gewichte mit dem Ziel, den Verlust zu minimieren.

Im Bereich des Deep Learning fallen die meisten Optimierungsalgorithmen unter den Begriff des stochastischen Gradientenabstiegs. Dabei handelt es sich um iterative Algorithmen, die ein Netz schrittweise trainieren. Jeder Trainingsschritt folgt dieser Abfolge:

  1. Nimm einige Trainingsdaten und gib sie in das Netz ein, um Vorhersagen zu erstellen.
  2. Bewerte den Verlust durch den Vergleich der Vorhersagen mit den tatsächlichen Werten.
  3. Passe die Gewichte in einer Richtung an, die den Verlust verringern.

Dieser Vorgang wird so lange wiederholt, bis der gewünschte Grad der Verlustreduzierung erreicht ist oder bis eine weitere Reduzierung unpraktisch wird. Im Wesentlichen führt der Optimierer das Netz durch die Feinheiten der Gewichtsanpassung und lenkt es auf die Konfiguration, die den Verlust minimiert und die Vorhersagegenauigkeit erhöht.

Jeder Satz von Trainingsdaten, der in jeder Iteration abgetastet wird, wird als Minibatch bezeichnet und oft einfach nur als „batch“. Andererseits wird ein vollständiger Durchlauf durch die Trainingsdaten als Epoche bezeichnet. Die angegebene Anzahl der Epochen bestimmt, wie oft das Netz jedes Trainingsbeispiel verarbeitet.

Lernrate und Chargengröße

Die Linie wird bei jeder Charge nur geringfügig umgestellt und nicht komplett überarbeitet. Das Ausmaß dieser Verschiebungen wird durch die Lernrate bestimmt. Eine geringere Lernrate bedeutet, dass das Netz mehr Minipartien ausgesetzt werden muss, bevor sich seine Gewichte auf ihre optimalen Werte einstellen.

Die Lernrate und die Größe der Minibatches sind die beiden wichtigsten Parameter, die den Verlauf des SGD-Trainings beeinflussen. Ihr Zusammenspiel kann sehr komplex sein, und die optimale Auswahl ist nicht immer offensichtlich.

Glücklicherweise ist für die meisten Aufgaben eine erschöpfende Suche nach optimalen Hyperparametern nicht zwingend erforderlich, um zufriedenstellende Ergebnisse zu erzielen. Adam, ein SGD-Algorithmus mit einer adaptiven Lernrate, macht eine umfangreiche Parametereinstellung überflüssig. Seine Selbstoptimierung macht ihn zu einem hervorragenden Allzweckoptimierer, der für eine Vielzahl von Problemen geeignet ist.

Für dieses Beispiel haben wir ADAM als SGD und MSE als Verlust gewählt.

model.compile(optimizer='adam', loss='mean_squared_error')

Beim Anpassen,

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2, verbose=1)

werden wir etwas Ähnliches sehen:

44241/44241 [==============================] - 247s 6ms/step - loss: 0.0021 - val_loss: 8.0975e-04
Epoch 2/30
44241/44241 [==============================] - 247s 6ms/step - loss: 2.3062e-04 - val_loss: 0.0010
Epoch 3/30
44241/44241 [==============================] - 288s 7ms/step - loss: 2.3019e-04 - val_loss: 8.5903e-04
Epoch 4/30
44241/44241 [==============================] - 248s 6ms/step - loss: 2.3003e-04 - val_loss: 7.6378e-04
Epoch 5/30
44241/44241 [==============================] - 257s 6ms/step - loss: 2.2993e-04 - val_loss: 9.5630e-04
Epoch 6/30
44241/44241 [==============================] - 247s 6ms/step - loss: 2.2988e-04 - val_loss: 7.3110e-04
Epoch 7/30
44241/44241 [==============================] - 224s 5ms/step - loss: 2.2985e-04 - val_loss: 8.7191e-04


Überanpassung und Unteranpassung

Keras speichert die Trainings- und Validierungsverluste während der Epochen, in denen das Modell trainiert wird. Wir werden uns mit der Interpretation dieser Lernkurven befassen und untersuchen, wie man sie zur Verbesserung der Modellentwicklung nutzen kann. Insbesondere werden wir die Lernkurven analysieren, um Anzeichen von Überanpassung und Unteranpassung zu erkennen, und einige Strategien zur Lösung dieser Probleme untersuchen.

Lernkurven interpretieren:

Wenn man die Informationen in den Trainingsdaten betrachtet, können sie in zwei Komponenten unterteilt werden: Signal und Rauschen. Das Signal ist der Teil, der verallgemeinert und unserem Modell hilft, Vorhersagen für neue Daten zu treffen. Andererseits umfasst Rauschen zufällige Fluktuationen, die sich aus realen Daten ergeben, und nicht-informative Muster, die nicht zu den Vorhersagefähigkeiten des Modells beitragen. Es ist wichtig, diesen Unterschied zu erkennen und zu verstehen.

Während des Modelltrainings versuchen wir, Gewichte oder Parameter auszuwählen, die den Verlust in einer Trainingsmenge minimieren. Für eine umfassende Bewertung der Leistung eines Modells ist es jedoch unabdingbar, es anhand eines neuen Datensatzes – der Validierungsdaten – zu beurteilen.

Eine effektive Interpretation dieser Kurven (wenn sie gezeichnet werden) ist für das erfolgreiche Training von Deep-Learning-Modellen unerlässlich.

Lernkurve

Nun verringert sich der Trainingsverlust, wenn das Modell entweder ein Signal oder ein Rauschen erfasst. Der Validierungsverlust nimmt jedoch nur dann ab, wenn das Modell ein Signal lernt, da jegliches Rauschen, das aus dem Trainingssatz gewonnen wird, nicht auf neue Daten verallgemeinert werden kann. Wenn das Modell also ein Signal lernt, sinken beide Kurven, während beim Lernen von Rauschen eine Lücke zwischen ihnen entsteht. Das Ausmaß dieser Lücke zeigt an, wie stark das Modell verrauscht ist.

Über_Unter_Anpassung



Im Idealfall würden wir versuchen, Modelle zu entwickeln, die nur das Signal und nichts vom Rauschen lernen. Die Erreichung dieses Idealzustands ist jedoch praktisch unwahrscheinlich. Stattdessen navigieren wir durch einen Kompromiss. Wir können das Modell dazu ermutigen, mehr Signale auf Kosten von mehr Rauschen zu lernen. Solange diese Abwägung zu unseren Gunsten ausfällt, wird der Validierungsverlust weiter abnehmen. Es kommt jedoch ein Punkt, an dem die Abwägung ungünstig wird, die Kosten den Nutzen überwiegen und der Validierungsverlust zunimmt.


Dieser Kompromiss verdeutlicht zwei potenzielle Herausforderungen beim Modelltraining: unzureichendes Signal oder übermäßiges Rauschen. Eine unzureichende Anpassung der Trainingsmenge liegt vor, wenn der Verlust nicht minimiert wird, weil das Modell nicht genügend Signale gelernt hat. Andererseits kommt es zu einer Überanpassung der Trainingsmenge, wenn der Verlust nicht minimiert wird, weil das Modell zu viel Rauschen absorbiert hat. Der Schlüssel zum Training von Deep-Learning-Modellen liegt darin, die optimale Balance zwischen diesen beiden Szenarien zu finden.

Das andere Diagramm sieht nun wie folgt aus:

Überanpassung und Unteranpassung

Modell Kapazität:

Die Kapazität eines Modells bezeichnet seine Fähigkeit, komplizierte Muster zu erfassen und zu begreifen. Im Zusammenhang mit neuronalen Netzen wird dies vor allem durch die Anzahl der Neuronen und deren Vernetzung beeinflusst. Wenn es den Anschein hat, dass Ihr Netz die Komplexität der Daten nur unzureichend erfasst (Unteranpassung), sollten Sie seine Kapazität verbessern.

Die Kapazität eines Netzes kann entweder durch Verbreiterung (Hinzufügen weiterer Einheiten zu bestehenden Schichten) oder durch Vertiefung (Einbeziehung weiterer Schichten) erhöht werden. Breitere Netze zeichnen sich durch das Erlernen linearer Beziehungen aus, während tiefere Netze eher dazu neigen, nichtlineare Muster zu erfassen. Die Wahl zwischen beiden hängt von der Art des Datensatzes ab.

Frühzeitige Beendigung:

Wie bereits erwähnt, kann der Validierungsverlust steigen, wenn ein Modell während des Trainings übermäßig viel Rauschen einbezieht. Um dieses Problem zu umgehen, können wir ein Frühstoppverfahren anwenden, bei dem wir den Trainingsprozess unterbrechen, sobald erkennbar ist, dass der Validierungsverlust nicht mehr abnimmt. Durch dieses proaktive Eingreifen wird eine Überanpassung verhindert und sichergestellt, dass das Modell gut auf neue Daten verallgemeinert werden kann.

Sobald wir einen Anstieg des Validierungsverlustes feststellen, können wir die Gewichte auf den Punkt zurücksetzen, an dem das Minimum erreicht wurde. Diese Vorsichtsmaßnahme garantiert, dass das Modell nicht im Lern von Rauschen verharrt und somit eine Überanpassung verhindert wird.

Die Implementierung des Trainings mit einem frühzeitigem Stopp mindert auch das Risiko eines vorzeitigen Abbruchs des Trainingsprozesses, bevor das Netz das Signal vollständig erfasst hat. Neben der Verhinderung einer Überanpassung durch zu langes Training wirkt ein frühzeitiger Abbruch als Schutz vor einer Unteranpassung durch zu kurze Trainingsdauer. Konfigurieren Sie einfach Ihre Trainingsepochen auf eine ausreichend große Anzahl (mehr als erforderlich), und das frühe Stoppen wird die Beendigung auf der Grundlage von Validierungsverlusttrends verwalten.

Integration des frühzeitigen Stopps:

In Keras wird das frühe Stoppen in unser Training durch einen Callback erreicht. Ein Callback ist im Wesentlichen eine Funktion, die in regelmäßigen Abständen während des Trainingsprozesses des Netzes ausgeführt wird. Der Rückruf zum vorzeitigen Stoppen wird nach jeder Epoche ausgelöst. Keras bietet zwar eine Reihe von vordefinierten Callbacks, ermöglicht aber auch die Erstellung von nutzerdefinierten Callbacks, um spezifische Anforderungen zu erfüllen.

Das ist unsere Wahl:

from tensorflow import keras
from tensorflow.keras import layers, callbacks

early_stopping = callbacks.EarlyStopping(
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=20, # how many epochs to wait before stopping
    restore_best_weights=True,
)

# Train the model
model.fit(X_train_scaled, y_train, epochs=int(epoch), batch_size=256, validation_split=0.2,callbacks=[early_stopping], verbose=1)


Außerdem haben wir mehr Einheiten und eine weitere versteckte Schicht hinzugefügt (das Modell ist in der .py-Datei nach der Optimierung komplexer)

model.add(Dense(128, activation='relu', input_shape=(X_train.shape[1],), kernel_regularizer=l2(k_reg)))
model.add(Dense(256, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(128, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(64, activation='relu', kernel_regularizer=l2(k_reg)))
model.add(Dense(1, activation='linear'))

Diese Parameter vermitteln die folgende Anweisung: „Wenn es keine Verbesserung des Validierungsverlustes von mindestens 0,001 gegenüber den vorangegangenen 20 Epochen gibt, beende das Training und behalte das bisher beste Modell.“ Die Feststellung, ob der Validierungsverlust aufgrund von Überanpassung oder bloßer zufälliger Chargenvariation zunimmt, kann manchmal schwierig sein. Mit den angegebenen Parametern können wir bestimmte Toleranzen festlegen, die dem System vorgeben, wann es den Trainingsprozess stoppen soll.

Wir setzen die Anzahl der Epochen zunächst auf 300, in der Hoffnung, dass der Trainingsprozess früher beendet wird.

Umgang mit fehlenden Werten

Verschiedene Umstände können dazu führen, dass in einem Datensatz fehlende Werte vorhanden sind.

Bei der Arbeit mit Bibliotheken für maschinelles Lernen wie scikit-learn führt der Versuch, ein Modell mit Daten zu konstruieren, die fehlende Werte enthalten, normalerweise zu einem Fehler. Daher müssen Sie eine der folgenden Strategien anwenden, um dieses Problem zu lösen.

Drei Herangehensweisen

  1. Stromlinienförmige Lösung (drop nan): Eliminiere Spalten mit fehlenden Werten. Ein unkomplizierter Ansatz besteht darin, Spalten mit fehlenden Werten zu verwerfen:
df2 = df2.dropna()

Wenn jedoch ein erheblicher Teil der Werte in den verworfenen Spalten fehlt, führt die Wahl dieses Ansatzes dazu, dass das Modell den Zugang zu einer beträchtlichen Menge potenziell wertvoller Informationen einbüßt. Zur Veranschaulichung: Stellen Sie sich einen Datensatz mit 10.000 Zeilen vor, bei dem in einer entscheidenden Spalte nur ein Eintrag fehlt. Die Anwendung dieser Strategie würde bedeuten, dass die gesamte Spalte entfernt werden müsste.

2) Eine verbesserte Alternative: Anrechnung

Bei der Unterstellung (imputation) werden die fehlenden Werte mit bestimmten numerischen Werten aufgefüllt. Wir könnten uns zum Beispiel dafür entscheiden, den Mittelwert in jeder Spalte einzutragen.

Auch wenn der unterstellte Wert in den meisten Fällen nicht ganz genau ist, führt diese Methode im Allgemeinen zu genaueren Modellen, als wenn die Zeile ganz weggelassen wird.


3) Fortschreitende Imputationstechniken

Die Anrechnung ist der herkömmliche Ansatz, der sich oft als wirksam erweist. Dennoch können die unterstellten Werte systematisch von ihren wahren Werten (die im Datensatz nicht verfügbar sind) abweichen. Alternativ dazu könnten Zeilen mit fehlenden Werten unterschiedliche Merkmale aufweisen. In solchen Fällen kann eine Verfeinerung des Modells zur Berücksichtigung der Originalität fehlender Werte die Vorhersagegenauigkeit verbessern.

In dieser Methodik setzen wir die Unterstellung fehlender Werte wie zuvor beschrieben fort. Zusätzlich führen wir für jede Spalte mit fehlenden Einträgen im ursprünglichen Datensatz eine neue Spalte ein, in der die Positionen der unterstellten Einträge angegeben sind.

Diese Technik kann zwar in bestimmten Szenarien die Ergebnisse erheblich verbessern, aber ihre Wirksamkeit kann variieren, und in einigen Fällen kann sie keine Verbesserung bringen.

ONNX-Modell ausgeben

1 Laden der Daten.

Da wir nun ein grundlegendes Verständnis der .py-Datei haben, die wir zum Trainieren des Modells erstellt haben, können wir mit dem Trainieren beginnen. 

Wir sollten unsere Pfade hier schreiben:

# get rates
eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)

# create dataframe
df = pd.DataFrame(eurusd_rates)

So sieht der Code am Ende aus ( GRU_create_model.py ):

Wenn wir trainieren, erhalten wir diese Ergebnisse:

Mean Squared Error: 0.0031695919830203693

Mean Absolute Error: 0.05063149001883482

R2 Score: 0.9263800140852619
Baseline MSE: 0.0430534174061265
Baseline MAE: 0.18048216851868318
Baseline R2 Score: 0.0

So heißt es in diesem Artikel:  „Forex exchange rate forecasting using deep recurrent neural networks“, die Ergebnisse von GRU und LTSM sind ähnlich. 

Artikel

Tabelle


Nachdem wir ONNX_GRU.py ausgeführt haben, erhalten wir ein ONNX-Modell im selben Ordner, in dem sich auch die übertragende Python-Datei (ONNX_GRU.py) befindet. Dieses ONNX-Modell sollte im Ordner MQL5 Files gespeichert werden, um es vom EA aus aufrufen zu können.

Auf diese Weise wird der EA dem Artikel hinzugefügt.


Jetzt können wir das Modell mit dem Strategietester testen oder handeln.


LSTM vs. GRU oben


Vergleich zwischen GRU und LSTM

Die LSTM-Zelle hält einen Zellzustand aufrecht, aus dem sie liest und in den sie schreibt. Es umfasst vier Gatter, die die Prozesse des Lesens, Schreibens und Ausgebens von Werten in den und aus dem Zellzustand steuern, abhängig von den Eingangs- und Zellzustandswerten. Das anfängliche Gatter gibt vor, welche Informationen der verborgene Zustand vergessen soll. Das nachfolgende Gatter ist für die Identifizierung des zu schreibenden Segments des Zellzustands verantwortlich. Das dritte Gatter bestimmt den zu schreibenden Inhalt. Das letzte Gatter schließlich ruft Informationen aus dem Zellzustand ab, um einen Ausgang zu erzeugen.

LSTM


Die GRU-Zelle hat Ähnlichkeiten mit der LSTM-Zelle, weist jedoch einige wichtige Unterschiede auf. Erstens fehlt ein verborgener Zustand, da die Funktionalität des verborgenen Zustands im LSTM-Zellendesign durch den Zellzustand übernommen wird. Anschließend werden die Prozesse der Entscheidung, was der Zellzustand vergisst und in welchen Teil des Zellzustands geschrieben wird, in einem einzigen Tor zusammengefasst. Es wird dann nur der Teil des Zellzustands eingeschrieben, der gelöscht wurde. Schließlich dient der gesamte Zellzustand als Ausgang, abweichend von der LSTM-Zelle, die selektiv aus dem Zellzustand liest, um einen Ausgang zu erzeugen. Diese kollektiven Änderungen führen zu einem einfacheren Design mit weniger Parametern im Vergleich zum LSTM. Die Verringerung der Parameter kann jedoch möglicherweise zu einer Verringerung der Aussagekraft führen.

GRU


Experimenteller Vergleich

GRU

# Split the data into training and testing sets
x_train, x_test, y_train, y_test = train_test_split(x_features, y_target, test_size=0.2, shuffle=False)


# Standardize the features StandardScaler()
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(x_train)
X_test_scaled = scaler.transform(x_test)

scaler_y = StandardScaler()
y_train_scaled = scaler_y.fit_transform(np.array(y_train).reshape(-1, 1))
y_test_scaled = scaler_y.transform(np.array(y_test).reshape(-1, 1))

# Define parameters

learning_rate = 0.001
dropout_rate = 0.5
batch_size = 1024
layer_1 = 256
epochs = 1000
k_reg = 0.001
patience = 10
factor = 0.5
n_splits = 5  # Number of K-fold Splits
window_size = days  # Ajusta esto según tus necesidades

def create_windows(data, window_size):
    return [data[i:i + window_size] for i in range(len(data) - window_size + 1)]

custom_optimizer = Adam(learning_rate=learning_rate)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=factor, patience=patience, min_lr=1e-26)

def build_model(input_shape, k_reg):
    model = Sequential()
    
    layer_sizes = [ 512,1024,512, 256, 128, 64]
    model.add(Dense(layer_1, kernel_regularizer=l2(k_reg), input_shape=input_shape))
    for size in layer_sizes:
        model.add(Dense(size, kernel_regularizer=l2(k_reg)))
        model.add(BatchNormalization())
        model.add(Activation('relu'))
        model.add(Dropout(dropout_rate))

    model.add(Dense(1, activation='linear'))
    model.add(BatchNormalization())
    model.compile(optimizer=custom_optimizer, loss='mse', metrics=[rmse()])
    
    return model



# Define EarlyStopping callback
early_stopping = EarlyStopping(monitor='val_loss', patience=patience, restore_best_weights=True)

# KFold Cross Validation
kfold = KFold(n_splits=n_splits, shuffle=True, random_state=42)
history = []
loss_per_epoch = []
val_loss_per_epoch = []

for train, val in kfold.split(X_train_scaled, y_train_scaled):
    x_train_fold, x_val_fold = X_train_scaled[train], X_train_scaled[val]
    y_train_fold, y_val_fold = y_train_scaled[train], y_train_scaled[val]
    
    # Flatten the input data
    x_train_fold_flat = x_train_fold.flatten()
    x_val_fold_flat = x_val_fold.flatten()

    # Create windows for training and validation
    x_train_windows = create_windows(x_train_fold_flat, window_size)
    x_val_windows = create_windows(x_val_fold_flat, window_size)

    # Rebuild the model
    model = build_model((window_size, 1), k_reg)

    # Create a new optimizer
    custom_optimizer = Adam(learning_rate=learning_rate)
    
    # Recompile the model
    model.compile(optimizer=custom_optimizer, loss='mse', metrics=[rmse()])
    
    hist = model.fit(
        np.array(x_train_windows), y_train_fold[window_size - 1:],
        epochs=epochs,
        validation_data=(np.array(x_val_windows), y_val_fold[window_size - 1:]),
        batch_size=batch_size,
        callbacks=[reduce_lr, early_stopping]
    )
    history.append(hist)
    loss_per_epoch.append(hist.history['loss'])
    val_loss_per_epoch.append(hist.history['val_loss'])




mean_loss_per_epoch = [np.mean(loss) for loss in loss_per_epoch]
val_mean_loss_per_epoch = [np.mean(val_loss) for val_loss in val_loss_per_epoch]

print("mean_loss_per_epoch", mean_loss_per_epoch)
print("unique_min_val_loss_per_epoch", val_loss_per_epoch)

# Create a DataFrame to display the mean loss values
epoch_df = pd.DataFrame({
    'Epoch': range(1, len(mean_loss_per_epoch) + 1),
    'Train Loss': mean_loss_per_epoch,
    'Validation Loss': val_loss_per_epoch
})



LSTM

model = Sequential()
model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,1)))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100, return_sequences = True))
model.add(Dropout(0.3))
model.add(LSTM(100, return_sequences = False))
model.add(Dropout(0.3))
model.add(Dense(units=1, activation = 'sigmoid'))
model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])


Schiebefenster-Auswertung


Ich habe Ihnen eine .py-Datei zum Vergleich von LSTM und GRU sowie eine cross validation.py-Datei zur Verfügung gestellt.

Außerdem habe ich eine einfache GRU-.py-Datei zur Erstellung von ONNX-Modellen hinterlassen

Mit dem einfachen Modell GRU simple erhalten wir folgende Ergebnisse für Januar 2024

Backtesting

Grafik


Schlussfolgerung und zukünftige Arbeit

Dieser Vergleich ist von entscheidender Bedeutung für die Entscheidung, welches Modell zu verwenden ist, oder wir können sogar in Erwägung ziehen, beide in gestapelter oder überlagerter Form zu verwenden. Dieser Ansatz ermöglicht es uns, trotz der inhärenten Unterschiede bei den Losgrößen und Schichtkonfigurationen wesentliche Informationen aus den von uns verwendeten Modellen zu extrahieren. Wenn sie, wie ich glaube, zu ähnlichen Ergebnissen kommen, ist GRU viel schneller.

Im Rahmen zukünftiger Arbeiten wäre es von Vorteil, verschiedene Kernel und rekurrente Initialisierer zu untersuchen, die auf jeden Zelltyp zugeschnitten sind, um die Leistung zu verbessern.

Ein guter Ansatz für den Handel mit ONNX-Modellen wäre die Integration beider Modelle in denselben EA, lesen Sie bitte diesen Artikel: Ein Beispiel für die Zusammenstellung von ONNX-Modellen in mql5.


Schlussfolgerung

Modelle wie GRU sind in der Lage, gute Ergebnisse zu erzielen, und sehen robust aus. Ich hoffe, dass Ihnen dieser Artikel ebenso viel Spaß gemacht hat wie mir die Erstellung des Artikels. Wir haben auch den Vergleich zwischen GRU- und LSTM-Modellen gesehen, und wir können diesen .py-Code verwenden, um zu wissen, wann die Epochen gestoppt werden sollen (unter Berücksichtigung der Anzahl der Eingabedaten).


Haftungsausschluss

Die Wertentwicklung in der Vergangenheit lässt keine Rückschlüsse auf zukünftige Ergebnisse zu.



Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/14113

Modified Grid-Hedge EA in MQL5 (Part III): Optimizing Simple Hedge Strategy (I) Modified Grid-Hedge EA in MQL5 (Part III): Optimizing Simple Hedge Strategy (I)
In this third part, we revisit the Simple Hedge and Simple Grid Expert Advisors (EAs) developed earlier. Our focus shifts to refining the Simple Hedge EA through mathematical analysis and a brute force approach, aiming for optimal strategy usage. This article delves deep into the mathematical optimization of the strategy, setting the stage for future exploration of coding-based optimization in later installments.
Advanced Variables and Data Types in MQL5 Advanced Variables and Data Types in MQL5
Variables and data types are very important topics not only in MQL5 programming but also in any programming language. MQL5 variables and data types can be categorized as simple and advanced ones. In this article, we will identify and learn about advanced ones because we already mentioned simple ones in a previous article.
Verständnis von Programmierparadigmen (Teil 2): Ein objektorientierter Ansatz für die Entwicklung eines Price Action Expert Advisors Verständnis von Programmierparadigmen (Teil 2): Ein objektorientierter Ansatz für die Entwicklung eines Price Action Expert Advisors
Lernen Sie das objektorientierte Programmierparadigma und seine Anwendung im MQL5-Code kennen. Dieser zweite Artikel geht tiefer auf die Besonderheiten der objektorientierten Programmierung ein und bietet anhand eines praktischen Beispiels praktische Erfahrungen. Sie lernen, wie Sie unseren früher entwickelten prozeduralen Price Action Expert Advisor mit dem EMA-Indikator und Kursdaten der Kerzen in objektorientierten Code umwandeln können.
The Disagreement Problem: Diving Deeper into The Complexity Explainability in AI The Disagreement Problem: Diving Deeper into The Complexity Explainability in AI
Dive into the heart of Artificial Intelligence's enigma as we navigate the tumultuous waters of explainability. In a realm where models conceal their inner workings, our exploration unveils the "disagreement problem" that echoes through the corridors of machine learning.