Neuronale Netze im Handel: Multi-Task-Lernen auf der Grundlage des ResNeXt-Modells
Einführung
Die rasante Entwicklung der künstlichen Intelligenz hat zur aktiven Integration von Deep-Learning-Methoden in die Datenanalyse geführt, auch im Finanzsektor. Finanzdaten zeichnen sich durch eine hohe Dimensionalität, Heterogenität und zeitliche Struktur aus, was die Anwendung herkömmlicher Verarbeitungsmethoden erschwert. Gleichzeitig hat Deep Learning eine hohe Effizienz bei der Analyse komplexer und unstrukturierter Daten bewiesen.
Unter den modernen Faltungsarchitekturen gibt es eine, die sich besonders hervorhebt: ResNeXt, eingeführt in „Aggregated Residual Transformations for Deep Neural Networks“. ResNeXt ist in der Lage, sowohl lokale als auch globale Abhängigkeiten zu erfassen und multidimensionale Daten effektiv zu verarbeiten, während die Rechenkomplexität durch gruppierte Faltungen reduziert wird.
Ein Schlüsselbereich der Finanzanalyse mit Deep Learning ist das Multi-Task-Lernen (MTL). Dieser Ansatz ermöglicht die gleichzeitige Lösung mehrerer verwandter Aufgaben und verbessert die Modellgenauigkeit und die Verallgemeinerungsfähigkeit. Im Gegensatz zu klassischen Ansätzen, bei denen jedes Modell eine einzelne Aufgabe löst, nutzt MTL gemeinsame Datendarstellungen, was das Modell robuster gegenüber Marktschwankungen macht und den Trainingsprozess verbessert. Dieser Ansatz ist besonders wertvoll für die Vorhersage von Markttrends, die Risikobewertung und die Bewertung von Vermögenswerten, da die Finanzmärkte dynamisch sind und von zahlreichen Faktoren beeinflusst werden.
Die Studie „Collaborative Optimization in Financial Data Mining Through Deep Learning and ResNeXt“ stellt einen Rahmen für die Integration der Architektur von ResNeXt in Multi-Task-Modelle vor. Diese Lösung eröffnet neue Möglichkeiten für die Verarbeitung von Zeitreihen, die Erkennung von räumlich-zeitlichen Mustern und die Erstellung genauer Prognosen. Die gruppierten Faltungen und Residualblöcke von ResNeXt beschleunigen das Training und verringern das Risiko, kritische Merkmale zu verlieren, was diese Methode besonders für die Finanzanalyse relevant macht.
Ein weiterer wesentlicher Vorteil des vorgeschlagenen Ansatzes ist die Automatisierung der Extraktion aussagekräftiger Merkmale aus den Rohdaten. Die herkömmliche Finanzanalyse erfordert oft eine umfangreiche Funktionsentwicklung, während tiefe neuronale Netze selbstständig wichtige Muster erkennen können. Dies ist besonders wichtig bei der Analyse multimodaler Finanzdaten, die mehrere Quellen wie Marktindikatoren, makroökonomische Berichte und Nachrichtenpublikationen umfassen. Die Flexibilität von MTL ermöglicht eine dynamische Anpassung der Aufgabengewichte und Verlustfunktionen, was die Anpassungsfähigkeit des Modells an Marktveränderungen und die Vorhersagegenauigkeit verbessert.
Die Architektur von ResNeXt
Die Architektur von ResNeXt basiert auf einem modularen Design und gruppierten Faltungen. Das Herzstück sind Faltungsblöcke mit Residualverbindungen, die zwei Grundprinzipien unterliegen:
- Wenn die Ausgabe-Merkmalskarten die gleiche Größe haben, verwenden die Blöcke identische Hyperparameter (Breite und Filtergrößen).
- Wenn die Größe der Merkmalskarte verringert wird, wird die Blockbreite proportional vergrößert.
Durch die Einhaltung dieser Prinzipien wird die Komplexität der Berechnungen über alle Modellebenen hinweg in etwa konstant gehalten, was den Entwurf vereinfacht. Es genügt, ein einziges Vorlagenmodul zu definieren, während andere Blöcke automatisch generiert werden, was eine Standardisierung, eine einfachere Abstimmung und eine optimierte Architekturanalyse gewährleistet.
Standardneuronen in künstlichen neuronalen Netzen führen eine gewichtete Summe von Eingaben durch, die primäre Operation in Faltungsschichten und vollständig verbundenen Schichten. Dieser Prozess kann in drei Stufen unterteilt werden: Aufspaltung, Umwandlung und Zusammenführung. ResNeXt führt einen flexibleren Ansatz ein, der es erlaubt, komplexere Transformationsfunktionen oder sogar Mini-Modelle selbst zu erstellen. Daraus ergibt sich das Network-in-Neuron-Konzept, das die Fähigkeiten der Architektur um eine neue Dimension erweitert: die Kardinalität. Im Gegensatz zu Breite oder Tiefe bestimmt die Kardinalität die Anzahl der unabhängigen, komplexen Transformationen innerhalb jedes Blocks. Experimente haben gezeigt, dass eine Erhöhung der Kardinalität effektiver sein kann als eine Erhöhung der Tiefe oder Breite, wodurch ein besseres Gleichgewicht zwischen Leistung und Recheneffizienz erreicht wird.
Alle ResNeXt-Blöcke haben eine einheitliche Engpass-Struktur. Sie besteht aus:
- einer ersten 1×1-Faltungsschicht, die die Dimensionalität der Merkmale reduziert,
- einer wichtigen 3×3-Faltungsschicht, die die Kerndatenverarbeitung durchführt,
- einer letzten 1×1-Faltungsschicht, die die ursprüngliche Dimensionalität wiederherstellt.
Dieser Entwurf reduziert die Rechenkomplexität bei gleichzeitiger Beibehaltung einer hohen Modellaussagekraft. Darüber hinaus bewahren die Residualverbindungen die Gradienten während des Trainings und verhindern so das Verschwinden der Gradienten, was ein Schlüsselfaktor für tiefe Netzwerke ist.
Eine wichtige Neuerung in ResNeXt ist die Verwendung von gruppierten Faltungen. Hier werden die Eingabedaten in mehrere unabhängige Gruppen aufgeteilt, die jeweils von separaten Faltungsfiltern verarbeitet werden, wobei die Ergebnisse anschließend aggregiert werden. Auf diese Weise werden die Modellparameter reduziert, ein hoher Netzdurchsatz beibehalten und die Recheneffizienz ohne nennenswerte Genauigkeitsverluste verbessert.
Um die Rechenkomplexität bei einer Änderung der Anzahl der Gruppen stabil zu halten, passt ResNeXt die Breite der Engpassblöcke an und steuert die Anzahl der Kanäle in den internen Schichten. Dies ermöglicht skalierbare Modelle ohne übermäßigen Berechnungsaufwand.
Das auf ResNeXt basierende Multi-Task-Learning-Framework stellt einen fortschrittlichen Ansatz für die Verarbeitung von Finanzdaten dar, der die gemeinsame Nutzung von Merkmalen und die kooperative Modellierung über verschiedene analytische Aufgaben hinweg ermöglicht. Es besteht aus drei strukturellen Schlüsselkomponenten:
- Modul zur Merkmalsextraktion,
- Gemeinsames Lernmodul,
- Aufgabenspezifische Ausgabeschichten.
Dieser Ansatz integriert effiziente Deep-Learning-Mechanismen mit finanziellen Zeitreihen, die eine hohe Vorhersagegenauigkeit bieten und das Modell an dynamische Marktbedingungen anpassen.
Das Modul zur Merkmalsextraktion basiert auf der ResNeXt-Architektur, die sowohl lokale als auch globale Merkmale von Finanzdaten effektiv erfasst. Bei mehrdimensionalen Finanzdaten ist der entscheidende Parameter die Anzahl der Gruppen im Modell. Es schafft ein Gleichgewicht zwischen detaillierter Merkmalsdarstellung und Rechenaufwand. Jede gruppierte Faltung in ResNeXT identifiziert bestimmte Muster in Kanalgruppen, die dann zu einer einheitlichen Darstellung zusammengefasst werden.
Nachdem sie nichtlineare Transformationsschichten durchlaufen haben, bilden die extrahierten Merkmale die Grundlage für das anschließende Multi-Task-Lernen und die aufgabenspezifische Anpassung. Das Modul für gemeinsames Lernen verwendet einen Mechanismus der Gewichtsverteilung, der gemeinsame Merkmale in aufgabenspezifische Räume projiziert. Auf diese Weise wird sichergestellt, dass das Modell für jede Aufgabe individuelle Repräsentationen erzeugen kann, wobei Interferenzen vermieden werden und gleichzeitig eine hohe Effizienz bei der gemeinsamen Nutzung von Merkmalen erreicht wird. Durch das Clustern von Aufgaben auf der Grundlage von Korrelationen wird der Mechanismus des gemeinsamen Lernens weiter verbessert.
Aufgabenspezifische Ausgabeschichten bestehen aus vollständig verknüpften Perceptrons, die spezielle Merkmale in den endgültigen Vorhersageraum projizieren. Die Ausgabeschichten können sich an die Art der jeweiligen Aufgabe anpassen. Bei Klassifizierungsaufgaben wird insbesondere der Kreuzentropieverlust verwendet, bei Regressionsaufgaben der mittlere quadratische Fehler (MSE). Beim Multi-Task-Lernen wird die endgültige Verlustfunktion als gewichtete Summe der Verluste der einzelnen Aufgaben dargestellt.
Die Ausbildung erfolgt in mehreren Stufen. Zunächst werden die Modelle für einzelne Aufgaben vortrainiert, um eine effektive Konvergenz der spezialisierten MLPs zu gewährleisten. Das Modell wird dann in der Multi-Task-Architektur feinabgestimmt, um die Gesamtleistung zu verbessern. Die Optimierung erfolgt mit dem Adam-Algorithmus mit dynamischer Anpassung der Lernrate.
Implementierung mit MQL5
Nachdem wir die theoretischen Aspekte des ResNeXt-basierten Multi-Task-Learning-Rahmens besprochen haben, implementieren wir unsere Interpretation mit MQL5. Wir beginnen mit der Konstruktion der grundlegenden Architekturblöcke von ResNeXt – den Engpassmodulen.
Engpassmodul
Das Engpassmodul besteht aus drei Faltungsschichten, von denen jede eine entscheidende Rolle bei der Verarbeitung der Rohdaten übernimmt. Die erste Schicht reduziert die Merkmalsdimensionalität und verringert damit den Rechenaufwand für die nachfolgende Verarbeitung.
Die zweite Faltungsschicht führt die Hauptfaltung durch und extrahiert komplexe Merkmale auf hoher Ebene, die für eine genaue Interpretation der Daten erforderlich sind. Es analysiert die Abhängigkeiten zwischen den Elementen und identifiziert Muster, die für spätere Phasen entscheidend sind. Dieser Ansatz ermöglicht es dem Modell, sich an nichtlineare Abhängigkeiten in den Finanzdaten anzupassen und so die Prognosegenauigkeit zu verbessern.
Die letzte Schicht stellt die ursprüngliche Dimensionalität des Tensors wieder her, wobei alle wichtigen Informationen erhalten bleiben. Die Dimensionalitätsreduktion entlang der zeitlichen Achse bei der früheren Merkmalsextraktion wird durch die Erweiterung des Merkmalsraums kompensiert, was den ResNeXt-Prinzipien entspricht.
Um das Training zu stabilisieren, wird nach jeder Faltungsschicht eine Batch-Normalisierung durchgeführt. Sie reduziert die interne Kovariatenverschiebung und beschleunigt die Konvergenz. Die ReLU-Aktivierung erhöht die Nichtlinearität des Modells und verbessert so seine Fähigkeit, komplexe Abhängigkeiten und die Qualität der Verallgemeinerung zu erfassen.
Die oben beschriebene Architektur ist im Objekt CNeuronResNeXtBottleneck implementiert, das die folgende Struktur aufweist.
class CNeuronResNeXtBottleneck : public CNeuronConvOCL { protected: CNeuronConvOCL cProjectionIn; CNeuronBatchNormOCL cNormalizeIn; CNeuronTransposeRCDOCL cTransposeIn; CNeuronConvOCL cFeatureExtraction; CNeuronBatchNormOCL cNormalizeFeature; CNeuronTransposeRCDOCL cTransposeOut; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtBottleneck(void){}; ~CNeuronResNeXtBottleneck(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtBottleneck; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
Als übergeordnete Klasse verwenden wir ein Faltungsschichtobjekt, das die Funktion der Wiederherstellung des Merkmalsraums übernimmt. Darüber hinaus enthält die Struktur eine Reihe interner Objekte, denen jeweils eine Schlüsselrolle in den von uns entwickelten Algorithmen zugewiesen ist. Ihre Funktionalität wird bei der Erstellung der Methoden der neuen Klasse näher erläutert.
Alle internen Objekte werden als statisch deklariert, sodass wir den Konstruktor und Destruktor der Klasse leer lassen können. Die Initialisierung dieser deklarierten und abgeleiteten Objekte wird in der Methode Init durchgeführt. Diese Methode erhält eine Reihe von Konstanten, die die Architektur des zu erstellenden Moduls eindeutig definieren.
bool CNeuronResNeXtBottleneck::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch) { int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1; if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, group_size * groups, group_size * groups, chanels_out, units_out, 1, optimization_type, batch)) return false;
Im Methodenkörper wird in der Regel die gleichnamige Methode der Elternklasse aufgerufen, die Initialisierungsalgorithmen für geerbte Objekte und Schnittstellen enthält. In diesem Fall fungiert die übergeordnete Klasse jedoch als letzte Faltungsschicht des Moduls. Seine Eingabe erhält Daten nach der Merkmalsextraktion, bei der sich die Dimensionalität des verarbeiteten Tensors geändert haben kann. Deshalb ermitteln wir zunächst die Sequenzlänge am Modulausgang und rufen erst dann die Methode der Elternklasse auf.
Nach erfolgreicher Initialisierung der abgeleiteten Objekte, gehen wir zu den neu deklarierten Objekten über. Die Arbeit beginnt mit dem Datenprojektionsblock. Die erste Faltungsschicht bereitet die Projektionen der Rohdaten für die erforderliche Anzahl von Arbeitsgruppen vor.
//--- Projection In uint index = 0; if(!cProjectionIn.Init(0, index, OpenCL, chanels_in, chanels_in, group_size * groups, units_count, 1, optimization, iBatch)) return false; index++; if(!cNormalizeIn.Init(0, index, OpenCL, cProjectionIn.Neurons(), iBatch, optimization)) return false; cNormalizeIn.SetActivationFunction(LReLU);
Diese Projektionen werden dann normalisiert und mit der Funktion LReLU aktiviert.
Beachten Sie, dass das Ergebnis dieser Operationen ein dreidimensionaler Tensor (Zeit, Gruppe, Dimension] ist. Um die unabhängige Verarbeitung einzelner Gruppen zu realisieren, verschieben wir die Dimension der Gruppenkennung mit Hilfe eines 3D-Tensortranspositionsobjekts an die erste Position.
index++; if(!cTransposeIn.Init(0, index, OpenCL, units_count, groups, group_size, optimization, iBatch)) return false; cTransposeIn.SetActivationFunction((ENUM_ACTIVATION)cNormalizeIn.Activation());
Als Nächstes folgt der Block zur Merkmalsextraktion. Hier verwenden wir eine Faltungsschicht, bei der die Anzahl der Gruppen die Anzahl der unabhängigen Sequenzen ist. Dadurch wird sichergestellt, dass die Werte der einzelnen Gruppen nicht „vermischt“ werden. Jede Gruppe verwendet ihre eigene Matrix von trainierbaren Parametern.
//--- Feature Extraction index++; if(!cFeatureExtraction.Init(0, index, OpenCL, group_size * window, group_size * step, group_size, units_out, groups, optimization, iBatch)) return false;
Außerdem ist zu beachten, dass die Methode Parameter für die Größe des Faltungsfensters und dessen Schritt entlang der zeitlichen Dimension erhält. Bei der Übergabe dieser Parameter an die Initialisierung der internen Faltungsschicht multiplizieren wir die entsprechenden Werte mit der Gruppengröße.
Nach der Faltungsschicht fügen wir eine Stapelnormalisierung mit der Aktivierungsfunktion LReLU hinzu.
index++; if(!cNormalizeFeature.Init(0, index, OpenCL, cFeatureExtraction.Neurons(), iBatch, optimization)) return false; cNormalizeFeature.SetActivationFunction(LReLU);
Der letzte Block der Rückwärtsprojektion in den Merkmalsraum besteht lediglich aus einem 3D-Tensor-Transpositionsobjekt, das die Gruppen zu einer einzigen Sequenz zusammenführt. Die eigentliche Datenprojektion wird, wie bereits erwähnt, mit den abgeleiteten Methoden der übergeordneten Klasse durchgeführt.
//--- Projection Out index++; if(!cTransposeOut.Init(0, index, OpenCL, groups, units_out, group_size, optimization, iBatch)) return false; cTransposeOut.SetActivationFunction((ENUM_ACTIVATION)cNormalizeFeature.Activation()); //--- return true; }
Jetzt müssen wir nur noch das logische Ergebnis der Operationen an den Aufrufer zurückgeben und die Objektinitialisierungsmethode abschließen.
Der nächste Schritt ist der Aufbau des Algorithmus für den Vorwärtsdurchlauf, der in der Methode feedForward implementiert ist.
bool CNeuronResNeXtBottleneck::feedForward(CNeuronBaseOCL *NeuronOCL) { //--- Projection In if(!cProjectionIn.FeedForward(NeuronOCL)) return false;
Die Methode erhält einen Zeiger auf das Rohdatenobjekt, der sofort an die gleichnamige Methode in der ersten internen Faltungsschicht des Datenprojektionsblocks übergeben wird. Die Gültigkeit des Zeigers wird nicht überprüft, da diese Überprüfung bereits in der internen Schicht erfolgt, sodass eine zusätzliche Prüfung überflüssig ist.
Die Ergebnisse der Projektion werden normalisiert und in separate Gruppendarstellungen übertragen.
if(!cNormalizeIn.FeedForward(cProjectionIn.AsObject())) return false; if(!cTransposeIn.FeedForward(cNormalizeIn.AsObject())) return false;
Im Block zur Merkmalsextraktion werden gruppierte Faltungsoperationen durchgeführt und die Ergebnisse normalisiert.
//--- Feature Extraction if(!cFeatureExtraction.FeedForward(cTransposeIn.AsObject())) return false; if(!cNormalizeFeature.FeedForward(cFeatureExtraction.AsObject())) return false;
Die aus den einzelnen Gruppen extrahierten Merkmale werden in eine einzige mehrdimensionale Sequenz zurückverwandelt und unter Verwendung der übergeordneten Klasse in den vorgesehenen Merkmalsraum projiziert.
//--- Projection Out if(!cTransposeOut.FeedForward(cNormalizeFeature.AsObject())) return false; return CNeuronConvOCL::feedForward(cTransposeOut.AsObject()); }
Das logische Ergebnis dieser Operationen wird an das aufrufende Programm zurückgegeben, und die Methode ist beendet.
Wie Sie sehen können, ist der Algorithmus des Vorwärtsdurchlaufs linear. Fehlergradienten pflanzen sich ebenfalls linear fort. Daher werden Methoden der Rückwärtsdurchläufe für eine eigenständige Studie herangezogen. Der vollständige Code für dieses Objekt und alle seine Methoden sind in der Anlage enthalten.
Modul für verbleibende Verbindungen
Die Architektur ResNeXt verfügt über Residualverbindungen für jedes Bottleneck-Modul, die eine effiziente Fehlergradientenfortpflanzung während der Backpropagation ermöglichen. Diese Verbindungen ermöglichen es dem Modell, zuvor extrahierte Merkmale wiederzuverwenden, was die Konvergenz verbessert und das Risiko des Verschwindens des Gradienten verringert. Dadurch kann das Modell bis zu einer größeren Tiefe trainiert werden, ohne dass sich der Rechenaufwand wesentlich erhöht.
Es ist wichtig zu beachten, dass der Ausgangstensor eines Bottleneck-Moduls ungefähr die gleiche Gesamtgröße beibehält, aber einzelne Dimensionen können sich ändern. Eine Verringerung der zeitlichen Schritte wird durch eine Erhöhung der Dimensionalität des Merkmalsraums kompensiert, wobei kritische Informationen erhalten bleiben und langfristige Abhängigkeiten erfasst werden. Ein separates Projektionsmodul sorgt für die korrekte Integration von Residualverbindungen, indem es die Eingabedaten auf die erforderlichen Dimensionen abbildet. Dadurch werden Fehlanpassungen vermieden und die Trainingsstabilität auch bei tiefen Architekturen beibehalten.
In unserer Implementierung haben wir dieses Modul als Objekt CNeuronResNeXtResidual erstellt, dessen Struktur unten dargestellt ist.
class CNeuronResNeXtResidual: public CNeuronConvOCL { protected: CNeuronTransposeOCL cTransposeIn; CNeuronConvOCL cProjectionTime; CNeuronBatchNormOCL cNormalizeTime; CNeuronTransposeOCL cTransposeOut; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtResidual(void){}; ~CNeuronResNeXtResidual(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint units_in, uint units_out, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtResidual; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
Bei der Entwicklung dieses Objekts verwendeten wir ähnliche Ansätze wie bei der Konstruktion von Bottleneck-Modulen, jedoch angepasst an die verschiedenen Dimensionen des Eingabetensors.
In der dargestellten Objektstruktur sind mehrere verschachtelte Objekte zu sehen, deren Funktionalität bei der Implementierung der neuen Klassenmethoden beschrieben wird. Alle internen Objekte werden statisch deklariert. Dadurch können wir den Konstruktor und Destruktor der Klasse leer lassen. Die Initialisierung aller Objekte, einschließlich der abgeleiteten, wird im Init-Modul durchgeführt, das eine Reihe von Konstanten erhält, die die Objektarchitektur definieren.
bool CNeuronResNeXtResidual::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint units_in, uint units_out, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronConvOCL::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_in, chanels_out, units_out, 1, optimization_type, batch)) return false;
Innerhalb der Methode rufen wir zunächst die gleichnamige Methode der übergeordneten Klasse auf und übergeben die erforderlichen Parameter. Wie bei den Bottleneck-Modulen verwenden wir eine Faltungsschicht als übergeordnete Klasse. Es übernimmt auch die Projektion der Daten in den neuen Merkmalsraum.
Nach erfolgreicher Initialisierung der abgeleiteten Objekte und Schnittstellen gehen wir zur Arbeit mit den neu deklarierten Objekten über. Um die Arbeit mit zeitlichen Dimensionen zu erleichtern, transponieren wir zunächst die Eingabedaten.
int index=0; if(!cTransposeIn.Init(0, index, OpenCL, units_in, chanels_in, optimization, iBatch)) return false;
Anschließend projiziert eine Faltungsschicht die einzelnen Einheitssequenzen auf die angegebene Dimensionalität.
index++; if(!cProjectionTime.Init(0, index, OpenCL, units_in, units_in, units_out, chanels_in, 1, optimization, iBatch)) return false;
Die Ergebnisse werden normalisiert, ähnlich wie beim Bottleneck-Modul. Es wird jedoch keine Aktivierungsfunktion angewendet, da das Residualmodul alle Informationen verlustfrei weitergeben muss.
index++; if(!cNormalizeTime.Init(0, index, OpenCL, cProjectionTime.Neurons(), iBatch, optimization)) return false;
Anschließend passen wir den Merkmalsraum an. Zu diesem Zweck führen wir eine inverse Transposition durch. Die Projektion wird dann über die übergeordnete Klasse abgewickelt.
index++; if(!cTransposeOut.Init(0, index, OpenCL, chanels_in, units_out, optimization, iBatch)) return false; //--- return true; }
Wir müssen nur noch das logische Ergebnis der Operationen an das aufrufende Programm zurückgeben und die Arbeit der Initialisierungsmethode für das neue Objekt abschließen.
Als Nächstes wird der Algorithmus für den Vorwärtsdurchlauf in der Methode feedForward konstruiert.
bool CNeuronResNeXtResidual::feedForward(CNeuronBaseOCL *NeuronOCL) { //--- Projection Timeline if(!cTransposeIn.FeedForward(NeuronOCL)) return false;
Die Methode erhält einen Zeiger auf das Objekt, das die Eingabedaten enthält. Dieser Zeiger wird an die interne Datenumsetzungsschicht weitergegeben, die die Daten in Einheitssequenzen umwandelt.
Anschließend passen wir die Dimensionalität der Einheitssequenzen mit Hilfe einer Faltungsschicht an die Zielgröße an.
if(!cProjectionTime.FeedForward(cTransposeIn.AsObject())) return false;
Die Ergebnisse sind normalisiert.
if(!cNormalizeTime.FeedForward(cProjectionTime.AsObject())) return false;
Abschließend wird eine inverse Transposition durchgeführt, und die Daten werden in den Merkmalsraum projiziert.
//--- Projection Chanels if(!cTransposeOut.FeedForward(cNormalizeTime.AsObject())) return false; return CNeuronConvOCL::feedForward(cTransposeOut.AsObject()); }
Die endgültige Projektion erfolgt anhand der übergeordneten Klasse. Das logische Ergebnis der Operationen wird dann an das aufrufende Programm zurückgegeben, womit das Verfahren des Vorwärtsdurchlaufs abgeschlossen ist.
Der Algorithmus des Vorwärtsdurchlaufs ist linear. Daher haben wir während der Backpropagation einen linearen Gradientenfluss. Die Methoden des Rückwärtsdurchlaufs werden also für eine unabhängige Studie überlassen, ähnlich wie das Objekt CNeuronResNeXtBottleneck. Der vollständige Code für diese Objekte und alle ihre Module ist im Anhang enthalten.
Der Block ResNeXt
Oben haben wir separate Objekte erstellt, die die beiden Informationsströme des ResNeXt-Rahmens darstellen. Nun ist es an der Zeit, diese Objekte in einer einzigen Struktur zusammenzufassen, um eine effizientere Datenverarbeitung zu ermöglichen. Zu diesem Zweck erstellen wir das Objekt CNeuronResNeXtBlock, das als Hauptblock für die nachfolgende Datenverarbeitung dienen wird. Die Struktur dieses Objekts wird im Folgenden dargestellt.
class CNeuronResNeXtBlock : public CNeuronBaseOCL { protected: uint iChanelsOut; CNeuronResNeXtBottleneck cBottleneck; CNeuronResNeXtResidual cResidual; CBufferFloat cBuffer; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CNeuronResNeXtBlock(void){}; ~CNeuronResNeXtBlock(void){}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defNeuronResNeXtBlock; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual CLayerDescription* GetLayerInfo(void) override; virtual void SetOpenCL(COpenCLMy *obj) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; };
In dieser Struktur finden wir vertraute Objekte und einen Standardsatz virtueller Methoden, die wir außer Kraft setzen müssen.
Alle internen Objekte werden als statisch deklariert, sodass wir den Konstruktor und Destruktor der Klasse leer lassen können. Die Initialisierung dieser deklarierten und abgeleiteten Objekte wird in der Methode Init durchgeführt. Seine Parameterstruktur wird vollständig vom Objekt CNeuronResNeXtBottleneck geerbt.
bool CNeuronResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint chanels_in, uint chanels_out, uint window, uint step, uint units_count, uint group_size, uint groups, ENUM_OPTIMIZATION optimization_type, uint batch) { int units_out = ((int)units_count - (int)window + (int)step - 1) / (int)step + 1; if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, units_out * chanels_out, optimization_type, batch)) return false;
Innerhalb des Methodenkörpers wird zunächst die Dimensionalität der Sequenz am Blockausgang bestimmt, dann werden die vom übergeordneten Objekt abgeleiteten Basisschnittstellen initialisiert.
Nach erfolgreicher Ausführung der Initialisierungsmethode der Elternklasse speichern wir die erforderlichen Parameter in den Variablen des Objekts.
iChanelsOut = chanels_out;
Wir initialisieren die internen Objekte der zuvor konstruierten Informationsströme.
int index = 0; if(!cBottleneck.Init(0, index, OpenCL, chanels_in, chanels_out, window, step, units_count, group_size, groups, optimization, iBatch)) return false; index++; if(!cResidual.Init(0, index, OpenCL, chanels_in, chanels_out, units_count, units_out, optimization, iBatch)) return false;
Am Blockausgang erwarten wir die Summe der Werte aus den beiden Informationsströmen. Daher kann der empfangene Fehlergradient vollständig an beide Datenströme weitergegeben werden. Um unnötiges Kopieren von Daten zu vermeiden, werden die Zeiger auf die entsprechenden Datenpuffer ausgetauscht.
if(!cResidual.SetGradient(cBottleneck.getGradient(), true)) return false; if(!SetGradient(cBottleneck.getGradient(), true)) return false; //--- return true; }
Schließlich geben wir ein boolesches Ergebnis an das aufrufende Programm zurück und schließen die Initialisierungsmethode ab.
Als Nächstes erstellen wir die Algorithmen der Vorwärtsdurchläufe in der Methode feedForward. Hier ist alles ganz einfach.
bool CNeuronResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL) { if(!cBottleneck.FeedForward(NeuronOCL)) return false; if(!cResidual.FeedForward(NeuronOCL)) return false;
Die Methode erhält einen Zeiger auf das Eingangsdatenobjekt, der sofort an die Methoden der beiden Informationsstromobjekte weitergegeben wird. Die Ergebnisse werden dann summiert und normalisiert.
if(!SumAndNormilize(cBottleneck.getOutput(), cResidual.getOutput(), Output, iChanelsOut, true, 0, 0, 0, 1)) return false; //--- result return true; }
Das logische Ergebnis der Operationen wird dann an das aufrufende Programm zurückgegeben, womit das Verfahren des Vorwärtsdurchlaufs abgeschlossen ist.
Obwohl die Struktur auf den ersten Blick einfach erscheinen mag, umfasst sie in Wirklichkeit zwei Informationsströme, was die Fehlergradientenverteilung komplexer macht. Der für diesen Prozess zuständige Algorithmus ist in der Methode calcInputGradients implementiert.
bool CNeuronResNeXtBlock::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Die Methode erhält einen Zeiger auf das während des Vorwärtsdurchlaufs verwendete Eingangsdatenobjekt. In diesem Fall muss der Fehlergradient entsprechend dem Einfluss der Eingabedaten auf die endgültige Modellausgabe propagiert werden. Daten können nur an ein gültiges Objekt übergeben werden. Bevor wir mit den Operationen fortfahren, überprüfen wir daher zunächst die Relevanz des empfangenen Zeigers.
Nach dieser Überprüfung wird der Fehlergradient durch den ersten Informationsstrom weitergegeben.
if(!NeuronOCL.calcHiddenGradients(cBottleneck.AsObject())) return false;
Vor der Weitergabe des Gradienten durch den zweiten Datenstrom müssen die zuvor gewonnenen Daten erhalten bleiben. Anstatt die Daten vollständig zu kopieren, verwenden wir einen Mechanismus zum Austausch von Zeigern. Der Zeiger auf den Fehlergradientenpuffer der Eingangsdaten wird in einer lokalen Variablen gespeichert.
CBufferFloat *temp = NeuronOCL.getGradient();
Als Nächstes wird geprüft, ob der Hilfspuffer mit den Abmessungen des Gradientenpuffers übereinstimmt. Falls erforderlich, anpassen.
if(cBuffer.GetOpenCL() != OpenCL || cBuffer.Total() != temp.Total()) { if(!cBuffer.BufferInitLike(temp)) return false; }
Sein Zeiger wird dann an das Eingangsdatenobjekt übergeben.
if(!NeuronOCL.SetGradient(GetPointer(cBuffer), false)) return false;
Jetzt kann der Fehlergradient sicher durch den zweiten Informationsstrom übertragen werden, ohne dass ein Datenverlust droht.
if(!NeuronOCL.calcHiddenGradients(cResidual.AsObject())) return false;
Die Werte der beiden Ströme werden addiert, und die Pufferzeiger werden in ihren ursprünglichen Zustand zurückversetzt.
if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1)) return false; if(!NeuronOCL.SetGradient(temp, false)) return false; //--- return true; }
Dann geben wir das logische Ergebnis der Operation an den Aufrufer zurück und schließen die Methode der Fehlergradientenverteilung ab.
Damit ist der Überblick über den algorithmischen Aufbau der ResNeXt-Blockobjektmethoden abgeschlossen. Der vollständige Code für dieses Objekt und alle seine Methoden sind in der Anlage enthalten.
Wir sind nun am Ende dieses Artikels angelangt, aber unsere Arbeit ist noch nicht abgeschlossen. Wir werden eine kurze Pause einlegen und im nächsten Artikel fortfahren.
Schlussfolgerung
In diesem Artikel wird ein auf der ResNeXt-Architektur basierendes Multi-Task-Learning-System vorgestellt, das für die Verarbeitung von Finanzdaten entwickelt wurde. Dieser Rahmen ermöglicht eine effiziente Merkmalsextraktion und -verarbeitung und optimiert Klassifizierungs- und Regressionsaufgaben in hochdimensionalen und zeitseriellen Datenumgebungen.
Im praktischen Teil haben wir die Hauptelemente der ResNeXt-Architektur aufgebaut. Im nächsten Artikel werden wir den Rahmen für das Multitasking-Lernen aufbauen und die Wirksamkeit der implementierten Ansätze anhand realer historischer Daten bewerten.
Referenzen
- Aggregated Residual Transformations for Deep Neural Networks
- Collaborative Optimization in Financial Data Mining Through Deep Learning and ResNeXt
- Andere Artikel dieser Serie
Programme, die im diesem Artikel verwendet werden
| # | Name | Typ | Beschreibung |
|---|---|---|---|
| 1 | Research.mq5 | Expert Advisor | Expert Advisor zum Sammeln von Beispielen |
| 2 | ResearchRealORL.mq5 | Expert Advisor | Expert Advisor für die Probenahme mit der Real-ORL-Methode |
| 3 | Study.mq5 | Expert Advisor | Modellausbildung Expert Advisor |
| 4 | Test.mq5 | Expert Advisor | Modellprüfung Expert Advisor |
| 5 | Trajectory.mqh | Klassenbibliothek | Struktur der Beschreibung des Systemzustands und der Modellarchitektur |
| 6 | NeuroNet.mqh | Klassenbibliothek | Eine Bibliothek von Klassen zur Erstellung eines neuronalen Netzes |
| 7 | NeuroNet.cl | Bibliothek | OpenCL-Programmcode |
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/17142
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Risikomanagement (Teil 2): Implementierung der Losberechnung in einer grafischen Schnittstelle
Risikomanagement (Teil 1): Grundlagen für den Aufbau einer Risikomanagement-Klasse
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Neuronale Netze im Handel: Speichererweitertes kontextbezogenes Lernen (MacroHFT) für Kryptowährungsmärkte
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Meiner Erfahrung nach teilen Händler, die etwas wirklich Nützliches weitergeben können, nie etwas mit.
Ja, sie wissen (wie ich seit 1998), dass eine funktionierende Strategie nach der Verbreitung schnell aufhört zu funktionieren.
Das ist der Grund, warum Forumsprogrammierer individuelle Lösungen teilen, während eine funktionierende (profitable) Strategie nie veröffentlicht wurde. Oder verkauft.
Ja, sie wissen (wie ich seit 1998), dass eine Strategie, die funktioniert, schnell aufhört zu funktionieren, sobald sie verbreitet wird.
Deshalb tauschen Forenprogrammierer individuelle Lösungen aus, und eine funktionierende (profitable) Strategie wurde nie veröffentlicht. Oder verkauft.
und die Notwendigkeit, Gelder zwischen Ländern zu transferieren, zählt nicht mehr?)
Wie kann man ein solches System sein?
Ein Handelsroboter wird immer funktionieren, wenn Sie bei einem Pullback kaufen, die Frage ist, wo ist der Pullback?
Ich habe die Übersetzung gesehen, ich bin definitiv nicht übersetzbar
Ich muss zugeben, ich war nicht schlau genug, um das Original zu verstehen.
"Ich habe die ganze Nacht mit mir selbst geredet, und sie haben mich nicht verstanden!" (Schwanezkij
Ja, sie wissen (wie ich seit 1998), dass eine Strategie, die funktioniert, schnell nicht mehr funktioniert, sobald sie verbreitet wird.
Das gilt für Börsen mit begrenzter Liquidität, nicht für den Forex, dort gibt es genug Liquidität für alle.
P.S. Ich habe mich an Mikhail erinnert, er hat ein System zur Absicherung an der Moskauer Börse, er hat es weitergegeben und es funktioniert, und es sollte auch in Zukunft funktionieren. Alles hängt vom persönlichen Kapital ab, und mit 100 Dollar ist dort nichts zu machen.
Hier sucht jeder nach einem System für hundert Pfund und einer Rentabilität von 10% pro Tag. Das ist der Grund, warum solche Ergebnisse der Suche.