Neuronale Netze im Trading: Generalisierung von Zeitreihen ohne Abhängigkeit von spezifischen Daten (Kernmodule des Modells)
Einführung
Im vorherigen Artikel haben wir das Mamba4Cast-Framework und seine Kernkomponenten vorgestellt: das SSM-Modul und den Mechanismus der Prior-data Fitted Networks (PFNs). Dieses Framework bildet eine solide Grundlage für Zeitreihenprognosen und hat das Potenzial, eine leistungsstarke Ergänzung für das analytische Toolkit eines Traders zu werden.
Mamba4Cast wurde nicht für eine lange Anpassungsphase bei jeder neuen Zeitreihe entwickelt, sondern für den sofortigen Einsatz. Dank des Konzepts des Zero-Shot-Forecastings kann das Modell hochwertige Prognosen auf realen Daten ohne zusätzliches Training oder Hyperparameter-Tuning erstellen. Infolgedessen müssen Trader nicht mehr tagelang nach der optimalen Konfiguration suchen.
Die hohe Ausführungsgeschwindigkeit des Modells beruht auf der linearen Rechenkomplexität seiner SSM-Module. Im Gegensatz zu Transformer-Architekturen, deren Rechenaufwand quadratisch mit der Sequenzlänge wächst, wird jeder Verarbeitungsschritt in Mamba4Cast in konstanter Zeit ausgeführt. Dies ermöglicht eine nahezu sofortige Inferenz selbst bei sehr langen Sequenzen bei minimaler Latenz. Im Handel, wo die Ausführungsgeschwindigkeit über den Ausgang eines Trades entscheiden kann, ist dieser Vorteil kaum zu überschätzen.
Darüber hinaus generiert Mamba4Cast den gesamten Prognosehorizont auf einmal, anstatt ihn Schritt für Schritt zu erstellen. Dieses Design vermeidet die für autoregressive Modelle typische Fehlerakkumulation und liefert stabilere zukünftige Trajektorien. Handelsstrategien erhalten sofort einen vollständigen Ausblick, was fundiertere und besser informierte Entscheidungen ermöglicht.
Ebenso bedeutend ist die Trainingsmethodik, die auf synthetischen Szenarien basiert. Das Modell wurde mit Millionen künstlich generierter Zeitreihen trainiert. Dies ermöglicht es Mamba4Cast, eine verallgemeinerte Intuition zu entwickeln und unter verschiedensten Bedingungen zuverlässig zu arbeiten. Dieser Ansatz verbessert die Robustheit gegenüber Rauschen und abrupten Marktveränderungen. Folglich verringert dies die Wahrscheinlichkeit unerwarteter Ausfälle in Produktivumgebungen.
Trotz der Leistungsfähigkeit seiner zugrunde liegenden Mechanismen bleibt Mamba4Cast recheneffizient. Laut Experimenten der Autoren des Frameworks erreicht es eine Genauigkeit, die mit modernen, auf Transformern basierenden Foundation-Modellen vergleichbar ist, während es wesentlich weniger Rechenressourcen benötigt. Dies macht es selbst auf ressourcenbeschränkter Infrastruktur praktikabel und für die direkte Integration in Handelsterminals geeignet, ohne dass High-End-GPU-Cluster erforderlich sind.
Genau diese Kombination aus sofortiger Bereitstellung, außergewöhnlicher Inferenzgeschwindigkeit, ganzheitlicher Prognose, Robustheit gegenüber verrauschten Daten und Recheneffizienz macht Mamba4Cast zu einem wirklich bahnbrechenden Framework für Zeitreihenprognosen.
Nachfolgend ist die Visualisierung des Mamba4Cast-Frameworks der Autoren dargestellt.

Im praktischen Teil des vorherigen Artikels schlossen wir mit der Implementierung des Objekts zur zeitlichen Kodierung ab – einer Schlüsselkomponente, die für die Bereitstellung von Positionskodierungen der Eingabedaten innerhalb der Zeitsequenz verantwortlich ist. Diese Komponente vervollständigte die Initialisierungspipeline des Modells und wurde zu einem wesentlichen Bestandteil der architektonischen Aufbereitung des Eingangssignals. Ohne sie wäre das Framework nicht in der Lage, zeitliche Abhängigkeiten korrekt zu erfassen.
Heute knüpfen wir an diesem Punkt an.
Datenvorverarbeitungsmodul
Die Entwicklungslogik ist unkompliziert: Bevor Berechnungen durchgeführt, Preisbewegungen prognostiziert oder Handelssignale generiert werden, müssen die Eingabedaten zunächst ordnungsgemäß aufbereitet werden. Wie bei jedem System für maschinelles Lernen hängt die Leistung von Mamba4Cast direkt von der Qualität des eingehenden Datenstroms ab. Wenn das Modell verrauschte Eingaben, inkonsistente Skalierungen oder fragmentierte Strukturen erhält, kann selbst die fortschrittlichste Architektur dies nicht ausgleichen. Aus diesem Grund liegt unser Fokus nun auf dem Datenvorverarbeitungsmodul.
Dieses Modul ist weit mehr als eine Hilfsstufe. Es dient als Brücke zwischen rohen Marktdaten und den strukturierten Eingaben, die das Modell effektiv interpretieren kann. Diese Stufe führt Normalisierung, Skalierung, Fensterkonstruktion, Maskengenerierung und – am wichtigsten – Anreicherung der Daten mit zusätzlichem Kontext durch. Zusammen bereiten diese Operationen die Informationen für die Verarbeitung durch das Kernmodell vor und bilden die Grundlage für alle nachfolgenden Prognosen.
Unser Ziel ist es nicht nur, Preiskurse und Indikatorwerte zu laden. Wir müssen sie in eine einheitliche Skala umwandeln, die Grenzen gültiger Fenster identifizieren, Masken für nicht verfügbare Werte generieren und alle Feature-Kanäle zeitlich synchronisieren. Erst nach diesen Schritten können wir die Daten an den Encoder weitergeben und erwarten, dass sie korrekt interpretiert werden.
Im praktischen Teil des vorherigen Artikels haben wir die Implementierung des Objekts zur zeitlichen Kodierung abgeschlossen – eine der Schlüsselkomponenten der Datenvorverarbeitung und ein wichtiges Element der Mamba4Cast-Architektur. Diese Komponente ermöglicht es dem Modell, eine Abfolge von Marktereignissen nicht als abstrakte Ansammlung von Zahlen wahrzunehmen, sondern als strukturierte Information mit einer klar definierten Reihenfolge und einem zeitlichen Rhythmus.
Um diesen Ansatz zu implementieren, werden wir eine spezialisierte Klasse CMamba4CastEmbedding entwickeln, die eine zentrale Rolle innerhalb der Vorverarbeitungsphase einnimmt. Ihre Struktur wird im Folgenden dargestellt.
class CMamba4CastEmbeding : public CNeuronBaseOCL { protected: CNeuronConvOCL cProjection; CNeuronBatchNormOCL cNorm; CNeuronTSPositionEncoder cProjectionWithTE; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override { return false; } virtual bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override; public: CMamba4CastEmbeding(void) {}; ~CMamba4CastEmbeding(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_out, uint units_count, uint &periods[], ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) override const { return defMamba4CastEmbeding; } //--- virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual void SetOpenCL(COpenCLMy *obj) override; };
Die Grundidee besteht darin, den Strom der Ausgangsinformationen in eine strukturierte und informationsreiche Darstellung umzuwandeln, die für die nachfolgenden Prognosestufen geeignet ist. Um dies zu erreichen, folgt die Klasse einem modularen Design und kombiniert mehrere spezialisierte Module, von denen jedes für eine spezifische Datentransformation verantwortlich ist.
Es ist erwähnenswert, dass alle internen Objekte der Klasse CMamba4CastEmbedding als static deklariert sind. Folglich bleiben sowohl der Konstruktor als auch der Destruktor leer, da keine dynamische Speicherverwaltung erforderlich ist – die Objekte werden automatisch erstellt und zerstört. Diese architektonische Entscheidung vereinfacht die Verwaltung des Objektlebenszyklus, minimiert das Risiko von Speicherlecks und eliminiert potenzielle Fehler durch die dynamische Speicherzuweisung, was in Hochleistungs-Handelssystemen besonders wichtig ist.
Die Initialisierung aller internen Module erfolgt in der Methode Init, deren Parameter die Haupteigenschaften des zu erstellenden Objekts definieren.
bool CMamba4CastEmbeding::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint window, uint window_out, uint units_count, uint &periods[], ENUM_OPTIMIZATION optimization_type, uint batch) { if(periods.Size() <= 0) return false; int freqs = (int(window_out / 2 + 2 * periods.Size()) - 1) / int(2 * periods.Size()); if(freqs <= 0) return false;
Die Methode beginnt mit der Überprüfung, ob das Array der analysierten Zeitreihenperioden Daten enthält. Diese Validierung ist notwendig, da die zeitliche Kodierung mindestens eine Periode erfordert, um korrekt berechnet zu werden. Unmittelbar danach berechnet die Methode den Parameter freqs, der die Anzahl der jeder Periode zugewiesenen Frequenzharmonischen angibt.
Die Logik hinter diesem Parameter verdient eine detailliertere Erklärung. Einer der Initialisierungsparameter (window_out) definiert die Einbettungsdimension für einen einzelnen Zeitschritt. Wie bereits besprochen, muss jedes zeitliche Datensegment in zwei komplementären Formen dargestellt werden: erstens als reiner Wert unabhängig von der Zeit und zweitens in Kombination mit zeitlichen Merkmalen, die durch harmonische Komponenten kodiert sind. Die endgültige Einbettung kombiniert beide Darstellungen innerhalb eines einzigen Vektors. Für diese Darstellung steht jedoch nur ein fest vorgegebener Speicherumfang zur Verfügung. Daher kann nur die Hälfte der benutzerdefinierten Einbettungsgröße für die zeitliche Kodierung verwendet werden. Die verbleibende Hälfte ist für die Standardprojektion der Eingabedaten reserviert.
Betrachten wir nun, wie diese zeitliche Hälfte auf die Frequenzkomponenten verteilt wird. Die Autoren von Mamba4Cast verwendeten einen Ansatz, der der in Transformer-Architekturen verwendeten Positionskodierung ähnelt: Für jede spezifizierte Periode werden sowohl Sinus- als auch Kosinusfunktionen generiert, um die Phase und Frequenz der Schwingungen zu beschreiben. Folglich erfordert jede Frequenzkomponente zwei harmonische Funktionen – einen Sinus und einen Kosinus. Daher muss die Gesamtzahl der Frequenzkomponenten mindestens doppelt so hoch sein wie die Anzahl der im Eingabearray spezifizierten Perioden.
Um Verwirrung zu vermeiden, verwenden wir eine einfache Formel, die jedoch sorgfältige Aufmerksamkeit erfordert: Wir teilen die Hälfte von window_out durch die doppelte Anzahl der spezifizierten Perioden. Der resultierende Wert bestimmt, wie viele Frequenzkomponenten jeder Periode zugewiesen werden können. Dieser Wert muss immer größer als null sein. Andernfalls wäre das Modell nicht in der Lage, auch nur ein einziges harmonisches Paar zu erzeugen, was eine zeitliche Kodierung unmöglich machen würde.
In der Praxis bedeutet dies, dass bei der Auswahl der Einbettungsgröße für einen einzelnen Zeitschritt das Ziel nicht einfach darin bestehen sollte, die Modellkapazität zu erhöhen, sondern sicherzustellen, dass jede analysierte Periode durch mindestens ein harmonisches Paar repräsentiert wird.
Wir rufen dann die entsprechende Initialisierungsmethode der Basisklasse auf, in der alle geerbten Objekte und Schnittstellen initialisiert werden.
if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window_out * units_count, optimization_type, batch)) return false;
Nach deren erfolgreicher Ausführung fahren wir mit der sequenziellen Initialisierung der internen Module unserer Klasse fort. Die erste Komponente ist cProjection, die konvolutive Projektionsschicht, die für die Abbildung der Eingabedaten in eine kompakte Darstellung verantwortlich ist.
int index = 0; if(!cProjection.Init(0, index, OpenCL, window, window, window_out - 2 * freqs * periods.Size(), units_count, 1, optimization, iBatch)) return false; cProjection.SetActivationFunction(TANH);
Dieses Modul erzeugt Einbettungen der ursprünglichen Daten, ohne zeitliche Informationen einzubeziehen. Wir verwenden die TANH-Aktivierungsfunktion, um Nichtlinearität einzuführen, wodurch das Netzwerk aussagekräftige Muster, die in den rohen numerischen Daten verborgen sind, hervorheben und gleichzeitig den Einfluss von Ausreißern reduzieren kann.
Die Batch-Normalisierungsschicht (cNorm) passt diese projizierten Merkmale anschließend an, eliminiert Verzerrungen in der Verteilung und verbessert die Rechenstabilität.
index++; if(!cNorm.Init(0, index, OpenCL, cProjection.Neurons(), iBatch, optimization)) return false; cNorm.SetActivationFunction(None);
Die Erzeugung von Einbettungen, die mit zeitlichen Informationen angereichert sind, wird vom Modul für zeitliche Kodierung (cProjectionWithTE) übernommen, was besonders wichtig für die Erfassung saisonaler Trends und dynamischer Veränderungen im Marktverhalten ist.
index++; if(!cProjectionWithTE.Init(0, index, OpenCL, window, units_count, periods, freqs, optimization, iBatch)) return false; SetActivationFunction(None); //--- return true; }
In der Framework-Logik garantiert dieser sequenzielle Initialisierungsprozess, dass jedes Submodul eine konsistente und kompatible Konfiguration erhält, was die effiziente Integration zeitlicher Informationen mit den ursprünglichen Eingabedaten ermöglicht.
Eine der wichtigsten Phasen des CMamba4CastEmbedding-Moduls ist die Ausführung der feedForward-Methode, die die Vorwärtsausbreitung der Daten durch jede Komponente der Vorverarbeitungsarchitektur durchführt. In dieser Phase wird das Fundament für die nachfolgenden Module gelegt, sodass diese nicht mit Rohdaten arbeiten, sondern mit sorgfältig aufbereiteten Repräsentationen, die bereits sowohl die strukturellen Merkmale des ursprünglichen Signals als auch dessen zeitlichen Kontext enthalten.
bool CMamba4CastEmbeding::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { if(!cProjection.FeedForward(NeuronOCL)) return false;
Der Prozess beginnt mit der Weitergabe der Eingabedaten an cProjection. Dieser kompakte Faltungsblock projiziert das ursprüngliche Signal in einen latenten Merkmalsraum. Dieser Schritt ermöglicht es dem System, sich sofort auf die informativsten Muster und signifikanten Änderungen im Eingabestrom zu konzentrieren.
Die extrahierten Merkmale werden dann an cNorm weitergeleitet. Diese Normalisierungsschicht ist dafür verantwortlich, die Aktivierungen auf einen stabilen Bereich zu skalieren und gleichzeitig Spitzen, Ausreißer und Verteilungsverschiebungen zu eliminieren.
if(!cNorm.FeedForward(cProjection.AsObject())) return false;
Ohne Normalisierung leiden tiefe neuronale Architekturen häufig unter explodierenden oder verschwindenden Gradienten. Hier fungiert die Normalisierung als Stabilisierungsmechanismus, der ein konsistentes Modellverhalten sowohl während des Trainings als auch bei der Inferenz fördert.
Das besondere Merkmal der Mamba4Cast-Architektur liegt jedoch in ihrem zweiten Verarbeitungspfad. Parallel dazu werden die ursprünglichen Eingabedaten an cProjectionWithTE weitergeleitet, das für die zeitliche Kodierung zuständige Modul.
if(!cProjectionWithTE.FeedForward(NeuronOCL, SecondInput)) return false;
Im Gegensatz zur anfänglichen Projektionsphase, die ausschließlich auf Faltungsoperationen beruht, erhält dieses Modul auch eine zweite Eingabe, SecondInput. Über diesen Kanal werden vorberechnete Zeitmarkierungen für jeden Zeitschritt in die Berechnung eingeführt. Dies ermöglicht es dem Modul, nicht nur die Merkmalswerte selbst zu analysieren, sondern sie auch in ihrem präzisen zeitlichen Kontext zu interpretieren, was die Empfindlichkeit des Modells gegenüber saisonalen, zyklischen und phasenbezogenen Marktdynamiken verbessert.
Die letzte Phase des Prozesses ist die Fusionsoperation. Die von den beiden Verarbeitungspfaden erzeugten Repräsentationen werden zu einem einzigen Tensor verkettet.
if(!Concat(cNorm.getOutput(), cProjectionWithTE.getOutput(), Output, cProjection.GetFilters(), cProjectionWithTE.GetWindowOut(), cProjection.GetUnits())) return false; //--- return true; }
Das resultierende Ergebnis ist eine dichte, mehrstufige Repräsentation jedes Zeitschritts, die sowohl die ursprünglichen Merkmalsinformationen als auch eine angereicherte zeitliche Struktur kombiniert. Diese umfassende Repräsentation der Zeitsequenz dient als Grundlage für alle nachfolgenden Operationen, die vom Modell durchgeführt werden.
Wie allgemein bekannt ist, reicht die Vorwärtsausbreitung allein nicht aus, um ein neuronales Netz zu trainieren. Damit sich das Modell an die Daten anpassen kann, muss es auch eine korrekte Backpropagation durchführen. Dieser Teil wird von der Methode calcInputGradients ausgeführt. Im Wesentlichen dient er als interner Fehleranalysemechanismus des Modells, der aktiviert wird, nachdem eine Vorhersage generiert wurde, um zu bestimmen, wo Fehler aufgetreten sind und wie die Modellparameter angepasst werden sollten.
bool CMamba4CastEmbeding::calcInputGradients(CNeuronBaseOCL *NeuronOCL) { if(!NeuronOCL) return false;
Die Methode prüft zunächst, welches Objekt beteiligt ist. Es ist wichtig, sicherzustellen, dass jede Komponente, die bei Gradientenberechnungen verwendet wird, korrekt initialisiert wurde. Nach dieser Prüfung wird die Ausführung mit der Methode DeConcat fortgesetzt, die den zuvor verketteten Gradientenvektor in zwei unabhängige Informationsströme trennt.
if(!DeConcat(cNorm.getGradient(), cProjectionWithTE.getGradient(), Gradient, cProjection.GetFilters(), cProjectionWithTE.GetWindowOut(), cProjection.GetUnits())) return false;
Erinnern Sie sich daran, dass während des Feed-Forward-Durchlaufs die normalisierte Merkmalsrepräsentation und die zeitlichen Einbettungen zu einem einzigen Tensor zusammengeführt wurden? Während der Backpropagation müssen diese Ströme jedoch wieder getrennt werden, damit jeder Verarbeitungszweig unabhängig trainiert werden kann.
Nachdem die Gradienten getrennt wurden, beginnt der Hauptprozess der Backpropagation, bei dem die Fehlergradienten durch beide Informationsströme zurück zu den ursprünglichen Eingabedaten propagiert werden. Zuerst führen wir Operationen mit dem Zweig durch, der keine zeitlichen Informationen enthält.
if(!cProjection.calcHiddenGradients(cNorm.AsObject())) return false; if(!NeuronOCL.calcHiddenGradients(cProjection.AsObject())) return false;
Die Berechnung wird mit dem Zweig für den zeitlichen Kontext fortgesetzt. Vor der Propagierung der Gradienten ist es jedoch erforderlich, die zuvor berechneten Daten zu bewahren. Dafür leitet die Methode den Zeiger vorübergehend auf den Gradientenpuffer um, der dem ursprünglichen Eingabeobjekt zugeordnet ist. Danach werden die Gradientenberechnungen durchgeführt.
CBufferFloat *temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(NeuronOCL.getPrevOutput(), false) || !NeuronOCL.calcHiddenGradients(cProjectionWithTE.AsObject()) || !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, cProjection.GetWindow(), false, 0, 0, 0, 1) || !NeuronOCL.SetGradient(temp, false) ) return false; //--- return true; }
Der letzte Schritt besteht darin, die Gradienten aus beiden Informationsströmen zu summieren. Danach werden alle Pufferzeiger in ihren ursprünglichen Zustand zurückversetzt, und die Methode endet, indem sie das boolesche Ergebnis des Vorgangs an das aufrufende Programm zurückgibt.
Das Ergebnis dieses sorgfältig konzipierten Verfahrens ist die korrekte Verteilung der Fehlergradienten im gesamten Modul, was für ausgeglichene Gewichtsaktualisierungen sorgt.
Die Optimierung der trainierbaren Parameter in jedem Teilmodul wird von der Methode updateInputWeights durchgeführt. Gewichtsaktualisierungen werden durch sequenzielles Aufrufen der entsprechenden Aktualisierungsmethoden interner Objekte durchgeführt. Dieses schrittweise Verfahren spiegelt das Prinzip der isolierten Gewichtsverwaltung wider, bietet eine fein abgestimmte Kontrolle über den Trainingsprozess und ermöglicht es, Änderungen unabhängig von der Gesamtarchitektur des Systems vorzunehmen. Wenn eine Phase des Aktualisierungsprozesses fehlschlägt, gibt die Methode sofort einen Fehler zurück und bewahrt dadurch die Konsistenz des Modellzustands.
Der Code der Methode updateInputWeights ist in den Anhängen zum Artikel verfügbar. Sie enthalten auch den vollständigen Code der Klasse CMamba4CastEmbedding und alle ihre Methoden.
Zusammenfassend lässt sich sagen, dass die Klasse CMamba4CastEmbedding weit mehr als eine Sammlung von Datentransformationsalgorithmen ist. Es handelt sich um ein sorgfältig entwickeltes Modul, bei dem jede Funktion eng mit der Gesamtlogik des Mamba4Cast-Frameworks integriert ist. Es bietet eine qualitativ hochwertige Vorverarbeitung der Eingabedaten, wodurch das Modell in der Lage ist, aussagekräftige Merkmale effizient zu extrahieren, zeitliche Dynamiken zu erfassen und durch ordnungsgemäß propagierte Fehlergradienten effektiv zu lernen.
Encoder
Die nächste Stufe unserer Implementierung ist die Konstruktion des Encoders, der von den Autoren des Mamba4Cast-Frameworks vorgeschlagen wurde. Diese Komponente spielt eine entscheidende Rolle bei der Extraktion und Strukturierung von Merkmalen für die anschließende Prognose. Ihre Architektur basiert auf einem Stapel von Faltungsschichten mit unterschiedlichen Kerngrößen. Jede Faltungsschicht konzentriert sich auf die Erfassung spezifischer Merkmale der Eingabedaten, wonach deren Ausgaben verkettet und normalisiert werden, bevor sie an das Mamba-Modul weitergeleitet werden. Die Anzahl der Wiederholungen dieses Blocks bestimmt die Tiefe der Merkmalsextraktion, wodurch sich das Framework an Aufgaben unterschiedlicher Komplexität anpassen kann.
Von besonderem Interesse ist hier der Stapel von Faltungsschichten. Ein Objekt für Faltungsschichten ist in unserer Bibliothek schon lange verfügbar, und seine Verwendung bereitet keine Schwierigkeiten. Beim herkömmlichen Ansatz wird jedoch jede Faltungsschicht als separates Objekt instanziiert und sequenziell verarbeitet. Wenn Faltungskerne unterschiedlicher Größe verwendet werden, wächst die Anzahl solcher Objekte erheblich, während die sequenzielle Ausführung die Rechenkomplexität des Modells und folglich die Trainingszeit erheblich erhöht.
Um diese Einschränkung zu beheben, haben wir ein spezielles Objekt entwickelt, das in der Lage ist, mehrere Faltungsschichten parallel zu verarbeiten. Ein solches Objekt kann Berechnungen gleichzeitig in mehreren Ausführungsströmen durchführen, was die Ausführungszeit drastisch reduziert. Intern verteilt es die Arbeitslast, indem es dasselbe Eingangssignal an mehrere Faltungsfilter mit unterschiedlichen Kerngrößen weiterleitet und dann deren Ausgaben zu einem einzigen Tensor zusammenfügt. Dieser Ansatz reduziert nicht nur die Anzahl der Zwischenobjekte, sondern nutzt auch die Rechenressourcen wesentlich effizienter, insbesondere bei der Ausführung auf modernen GPUs mit mehreren Threads.
Das Konzept an sich ist nicht völlig neu. Zuvor haben wir die Klasse CNeuronMultiWindowsConvOCL implementiert, die Faltungsoperationen mit unterschiedlichen Kerngrößen unterstützte. Das Design ging jedoch von einem separaten Eingabepuffer für jedes Faltungsfenster aus. Im vorliegenden Fall stehen wir vor einer anderen Herausforderung: Derselbe Eingabestrom muss gleichzeitig mit mehreren Faltungsfenstern verarbeitet werden.
Die Schwierigkeit liegt darin, dass eine Änderung der Kernelgröße auch die Anzahl der möglichen Faltungsoperationen ändert. Wenn das Faltungsfenster größer wird, nimmt die Anzahl der Positionen ab, an denen die Faltung angewendet werden kann. Folglich werden die Rechenlasten, die mit unterschiedlichen Kernelgrößen verbunden sind, unausgeglichen. Dies erschwert eine effiziente parallele Ausführung.
Um dieses Problem zu lösen, haben wir Zero-Padding eingeführt, bei dem Nullen sowohl am Anfang als auch am Ende des Eingabevektors hinzugefügt werden. Diese Technik erweitert den Vektor so, dass unabhängig von der Größe des Faltungsfensters dieselbe Anzahl an Operationsthreads verwendet werden kann. Infolgedessen wird die Anzahl der Faltungsoperationen ausschließlich durch die Faltungsschrittweite bestimmt, die für alle Kernelgrößen identisch bleibt. Dies gleicht die Arbeitslast über parallele Threads hinweg aus und vereinfacht die parallele Verarbeitung erheblich.
Die implementierte Kernlogik von OpenCL befindet sich in einem kompakten, aber leistungsstarken FeedForwardMultWinConvWPad-Kernel, der denselben Eingabestrom gleichzeitig durch mehrere Faltungsfenster unterschiedlicher Länge verarbeitet.
__kernel void FeedForwardMultWinConvWPad(__global const float *matrix_w, __global const float *matrix_i, __global float *matrix_o, __global const int *windows_in, const int inputs, const int step, const int window_out, const int activation ) { const size_t id = get_global_id(0); const size_t id_w = get_global_id(1); const size_t v = get_global_id(2); const size_t outputs = get_global_size(0); const size_t windows_total = get_global_size(1);
Jeder Ausführungs-Thread wird durch drei Koordinaten identifiziert:
- id – der Index des Ausgabetensorelements;
- id_w – der Faltungsfensterindex;
- v – die Kennung des univariaten Eingabestroms.
Diese Organisation ermöglicht es, mehrere Faltungsfenster, Positionen und Eingabesequenzen parallel unter Verwendung unterschiedlicher Betriebsparameter zu verarbeiten.
Im ersten Schritt bestimmt der Kernel die Länge des aktuellen Faltungsfensters window_in, indem er sie mithilfe der aktuellen Fensterkennung aus dem windows_in-Array abruft.
int window_in = windows_in[id_w];
Als Nächstes berechnet er den Start-Offset des Faltungsfensters im Eingabepuffer entsprechend dem aktuellen Ausgabetensorindex. Der Offset wird so berechnet, dass die Mitte des Faltungsfensters mit der Position id übereinstimmt. Da die Fenstergrößen variieren, wird der Ausdruck (window_in + 1) / 2 verwendet, um eine korrekte Zentrierung sicherzustellen.
int window_in = windows_in[id_w]; int mid_win = (window_in + 1) / 2; int shift_in = id * step - mid_win; int shift_in_var = v * inputs;
Die Variable shift_in_var wird dann verwendet, um die entsprechende Eingabesequenz innerhalb des globalen Eingabepuffers zu adressieren.
Im nächsten Schritt wird shift_weight berechnet, also der Offset im globalen Array der trainierbaren Parameter. Da die Gewichte für jedes Faltungsfenster unter Berücksichtigung ihrer Länge und des Bias-Koeffizienten nacheinander gespeichert werden, ist es notwendig, die Größen aller Gewichtungsblöcke zu summieren, die dem aktuellen Fenster id_w vorausgehen. Dies ermöglicht es uns, genau zu bestimmen, wo die Gewichte für die aktuelle Faltung beginnen.
int shift_weight = 0; for(int w = 0; w < id_w; w++) shift_weight += (windows_in[w] + 1) * window_out;
Anschließend wird eine verschachtelte Schleife über jeden Ausgabekanal w_out gestartet, der die Einbettungsdimension im nach der Faltung generierten Ergebnispuffer darstellt.
for(int w_out = 0; w_out < window_out; w_out++) { float sum = matrix_w[shift_weight + window_in]; //--- for(int w = 0; w < window_in; w++) if((shift_in + w) >= 0 && (shift_in + w) < inputs) sum += IsNaNOrInf(matrix_i[shift_in_var + shift_in + w] * matrix_w[shift_weight + w], 0); //--- int shift_out = (v * outputs + id) * window_out + w_out; matrix_o[shift_out] = Activation(sum, activation); shift_weight += window_in + 1; } }
Für jeden Ausgabekanal wird die Variable sum initialisiert, die zuerst den Bias-Wert (das letzte Element des Gewichtungsblocks) annimmt. Die Produkte der Eingabewerte und ihrer entsprechenden Gewichte werden dann zu diesem Wert addiert. Während dieses Prozesses prüft der Algorithmus explizit, ob der aktuelle Eingabeindex außerhalb des gültigen Eingabebereichs liegt. Wenn dies der Fall ist, wird die Operation übersprungen, was dem Anwenden von Null-Padding entspricht.
Sobald ein Durchlauf durch das Fenster abgeschlossen ist, wird das Ergebnis durch die Aktivierungsfunktion verarbeitet. Der Aktivierungstyp wird durch den Parameter activation spezifiziert, während die Aktivierungsfunktion über den Hilfs-Wrapper Activation angewendet wird. Der endgültige Wert wird dann unter Berücksichtigung aller erforderlichen Offsets in den globalen Ausgabepuffer matrix_o geschrieben.
Schließlich rückt der Zeiger shift_weight um die Größe des aktuellen Fensters plus ein zusätzliches Element für den bias vor und positioniert ihn so bei den Gewichten des nächsten Filters. Somit verarbeitet jeder Ausführungsthread nacheinander alle Ausgabekanäle, während die Berechnungen über alle Faltungsfenster und alle Eingabesequenzen hinweg vollständig parallel bleiben.
Der resultierende Algorithmus ist nicht nur flexibel, sondern auch außergewöhnlich effizient. Faltungen auf mehreren Skalen werden gleichzeitig auf die Daten angewendet, was eine umfassende multiskalare Darstellung der Eingabe liefert. Null-Padding eliminiert die Notwendigkeit für manuelles Zuschneiden von Sequenzen oder Nachbearbeitung zur Angleichung der Einbettungsdimensionen. Alles wird dynamisch während der GPU-Ausführung durchgeführt.
Diese Implementierung demonstriert ein wichtiges Designprinzip: Hohe Leistung wird nicht durch Vereinfachung des Algorithmus erreicht, sondern durch sorgfältige Partitionierung der Berechnung, Ausnutzung paralleler Ausführung und präzise Orchestrierung jeder Stufe der Datentransformations-Pipeline.
Die nächste wichtige Stufe ist die Berechnung von Fehlergradienten in Bezug auf die ursprünglichen Eingabedaten. Im Gegensatz zum Vorwärtsdurchlauf stellt diese Phase ein wesentlich anspruchsvolleres Problem dar. Anstatt einfach eine Reihe von Filtern anzuwenden, müssen wir den Beitrag jedes Filters rückwärts propagieren, um den Gradienten auf Eingabeebene zu rekonstruieren, während wir Offsets korrekt handhaben und die Tensor-Konsistenz wahren.
Dieser Algorithmus ist im OpenCL-Kernel CalcHiddenGradientMultWinConvWPad implementiert. Sein Zweck ist es, Fehlergradienten über Faltungsfenster unterschiedlicher Länge und über alle Ausgabekanäle hinweg zu aggregieren. Mit anderen Worten, er rekonstruiert den Fehler, der zurück zur ursprünglichen Eingabesequenz propagiert wurde, während er sowohl die Aktivierungsnichtlinearität als auch die Faltungsfilterstruktur berücksichtigt.
__kernel void CalcHiddenGradientMultWinConvWPad(__global const float *matrix_w, __global const float *matrix_i, __global float *matrix_ig, __global const float *matrix_og, __global const int *windows_in, const int outputs, const int step, const int window_out, const int activation ) { const size_t id_x = get_global_id(0); const size_t id_loc = get_local_id(1); const size_t id_win = id_loc / window_out; const size_t id_f = id_loc % window_out; const size_t v = get_global_id(2); const size_t inputs = get_global_size(0); const size_t size_loc = get_local_size(1); const size_t windows_total = size_loc / window_out;
Jeder Thread innerhalb des Kernels berechnet ein Element des Gradienten auf Eingabeebene:
- id_x – der Index der Position innerhalb der Eingabesequenz;
- id_loc – der lokale Arbeitsgruppenbezeichner, der in id_win (Fensterindex) und id_f (Filterindex innerhalb des Ausgabepuffers) zerlegt wird;
- v – der Bezeichner der Eingabesequenz.
Der erste Schritt berechnet shift_weight, was die exakte Startposition des Gewichtsblocks bestimmt, der dem aktuellen Faltungsfenster und Kanal entspricht. Wie zuvor iteriert der Algorithmus durch alle vorangegangenen Fenster und akkumuliert deren Größen, bis er die korrekte Position innerhalb des globalen Gewichts-Arrays matrix_w erreicht.
__local float temp[LOCAL_ARRAY_SIZE]; const uint ls = min((uint)size_loc, (uint)LOCAL_ARRAY_SIZE); //--- int window_in = windows_in[id_win]; int shift_weight = id_f * (window_in + 1); for(int w = 0; w < id_win; w++) shift_weight += (windows_in[w] + 1) * window_out;
Als Nächstes bestimmt er den Bereich des Ausgabepuffers, an dem das aktuelle Eingabeelement beteiligt gewesen sein könnte – shift_out. Dieser Wert ist nach unten durch null begrenzt und stellt die frühestmögliche Ausgabeposition dar, deren Faltungsfenster das aktuelle Eingabeelement id_x noch einschließen könnte.
int shift_out = max((int)((id_x - window_in) / step), 0);
Doch das reicht nicht aus – dann beginnt ein Zyklus durch alle möglichen Ausgaben out, an denen unser Element bei einem Vorwärtsdurchlauf beteiligt sein könnte.
float grad = 0; int mid_win = (window_in + 1) / 2; for(int out = shift_out; out < outputs; out++) { int shift_in = out * step - mid_win; if(shift_in > id_x) break; int shift_w = id_x - shift_in; if(shift_w >= window_in) continue; int shift_g = ((v * outputs + out) * windows_total + id_win) * window_out + id_f; grad += IsNaNOrInf(matrix_w[shift_w + shift_weight] * matrix_og[shift_g], 0); }
Bei jeder Iteration prüft der Kernel, ob id_x zum aktuellen Faltungsfenster gehört. Wenn dies der Fall ist, berechnet er den lokalen Versatz shift_w innerhalb des Fensters zusammen mit der entsprechenden Ausgabegradientenposition shift_g. Die Multiplikation des entsprechenden Filtergewichts mit dem Ausgabegradienten matrix_og ergibt den Beitrag dieser Faltung zum Eingabegradienten. Diese Beiträge werden in der Variablen grad akkumuliert.
An diesem Punkt hält jeder Ausführungsthread nur einen partiellen Gradienten. Mehrere Threads innerhalb derselben Arbeitsgruppe können jedoch derselben Eingabeposition id_x entsprechen, während sie unterschiedliche Filter oder Ausgabekanäle verarbeiten. Daher wird ein Zwischen-Array im lokalen Speicher eingeführt – temp, und der Summationszyklus mit Synchronisationsbarrieren wird gestartet. Jeder Thread speichert zunächst sein Teilergebnis in temp.
for(int i = 0; i < size_loc; i += ls) { if(i <= id_loc && (i + ls) > id_loc) temp[id_loc % ls] = (i == 0 ? 0 : temp[id_loc % ls]) + grad; barrier(CLK_LOCAL_MEM_FENCE); }
Anschließend wird das Array mittels paralleler Reduktionsfaltung in Zweierpotenzen summiert, wobei der endgültige Gradient über alle Kanäle und Fenster in temp[0] verbleibt.
uint count = ls; do { count = (count + 1) / 2; if(id_loc < count && (id_loc + count) < ls) { temp[id_loc] += temp[id_loc + count]; temp[id_loc + count] = 0; } barrier(CLK_LOCAL_MEM_FENCE); } while(count > 1);
Schließlich schreibt nur der Thread mit id_loc == 0 den resultierenden Gradienten in den globalen Eingabegradienten-Puffer matrix_ig. Bevor dies geschieht, ruft er die Deactivation-Funktion auf, die die Ableitung der Aktivierungsfunktion auf den entsprechenden Eingabewert matrix_i anwendet. Dieser Schritt ist wesentlich: Der Gradient muss gemäß der Aktivierungsfunktion korrigiert werden; andernfalls würde der Lernprozess inkorrekt werden.
if(id_loc == 0) matrix_ig[v * inputs + id_x] = Deactivation(temp[0], matrix_i[v * inputs + id_x], activation); }
Dies ist eine der rechenintensivsten Phasen der Backpropagation. Durch die Aufteilung der Berechnungen in Fenster und Kanäle sowie die Nutzung von lokalem Speicher wird auch bei einer großen Anzahl von Parametern eine hohe Effizienz erreicht. In Kombination mit dem Feed-Forward-Durchlauf macht dieser Schritt unseren Algorithmus nicht nur modular und skalierbar, sondern auch fähig, sehr komplexe Abhängigkeiten in Finanzzeitreihen ohne Genauigkeits- oder Leistungsverlust zu verarbeiten.
Der letzte Schritt in dieser konvolutionellen Symphonie ist die Gewichtsoptimierung. In dieser Phase werden die berechneten Gradienten in tatsächliche Parameteraktualisierungen umgewandelt. Dies wird mithilfe des Adam-Optimierungsalgorithmus erreicht, der erweitert wurde, um Faltungsfenster unterschiedlicher Größe und Zero-Padding zu berücksichtigen. Die gesamte Logik ist im UpdateWeightsMultWinConvAdamWPad-Kernel gekapselt.
__kernel void UpdateWeightsMultWinConvAdamWPad(__global float *matrix_w, __global const float *matrix_og, __global const float *matrix_i, __global float *matrix_m, __global float *matrix_v, __global const int *windows_in, const int windows_total, const int window_out, const int inputs, const int step, const int outputs, const float l, const float b1, const float b2 ) { const size_t i = get_global_id(0); // weight shift const size_t v = get_local_id(1); // variable const size_t variables = get_local_size(1);
Jeder Thread in diesem Kernel ist für die Aktualisierung eines spezifischen Parameters i verantwortlich – sei es ein Filterkoeffizient oder ein Bias. Die Variable v identifiziert die Eingabesequenz, während variables die Gesamtzahl der Sequenzen angibt. Zusammen ermöglichen sie es, Gradienten über alle Eingabesequenzen hinweg zu akkumulieren, was zu stabileren Parameteraktualisierungen führt.
Zu Beginn der Ausführung bestimmt jeder Thread, welcher Filter, welcher Ausgabekanal und welches spezifische Gewicht seinem Parameterindex i entsprechen. Dies wird durch Iteration über alle Faltungsfenster erreicht, wobei der kumulative Offset shift_before beibehalten wird, der die Position jedes Fensters innerhalb des linearen Arrays trainierbarer Parameter verfolgt.
__local float temp[LOCAL_ARRAY_SIZE]; const uint ls = min((uint)variables, (uint)LOCAL_ARRAY_SIZE); //--- int step_out = window_out * windows_total; //--- int shift_before = 0; int window = 0; int number_w = 0; for(int w = 0; w < windows_total; w++) { int win = windows_in[w]; if(shift_before <= i && (win + 1)*window_out > (i - shift_before)) { window = win; number_w = w; } else shift_before += (win + 1) * window_out; }
Sobald das Fenster, das i entspricht, identifiziert wurde, bestimmen wir:
- window – die Breite der Filtergröße;
- number_w – Fensterindex;
- id_f – Filterindex;
- shift_in – Versatz relativ zum Anfang des Fensters (wenn er der Fenstergröße entspricht, dann ist es Bias);
- bias – ein logisches Flag, das bestimmt, ob das analysierte Element ein Bias-Parameter ist.
int shift_in = (i - shift_before) % (window + 1); int shift_in_var = v * inputs; bool bias = (shift_in == window); int mid_win = (window + 1) / 2; int id_f = (i - shift_before) / (window + 1); int shift_out = number_w * window_out + id_f; int shift_out_var = v * outputs * step_out;
Für gewöhnliche Filterkoeffizienten iteriert der Kernel über jede out-Ausgabeposition. An jeder Position wird überprüft, ob das entsprechende Eingabeelement innerhalb des gültigen Eingabebereichs liegt. Wenn dies der Fall ist, führt er die Standard-Gradientenberechnung durch, indem er den Ausgabegradienten matrix_og mit der entsprechenden Eingabeaktivierung matrix_i multipliziert und die Produkte in grad akkumuliert.
float grad = 0; if(!bias) { for(int out = 0; out < outputs; out++) { int in = out * step - mid_win + shift_in; if(in >= inputs) break; if(in < 0) continue; //--- grad += IsNaNOrInf(matrix_og[shift_out_var + shift_out + out * step_out] * matrix_i[shift_in_var + in], 0); } } else { for(int out = 0; out < outputs; out++) grad += IsNaNOrInf(matrix_og[shift_out_var + shift_out + out * step_out], 0); }
Für Bias-Parameter sind die Eingabeaktivierungen nicht erforderlich. Stattdessen akkumuliert der Kernel einfach die Ausgabegradienten über alle Positionen hinweg.
Die nächste Stufe verwendet erneut eine lokale Akkumulation durch das temp-Array. Dessen Zweck ist es, Gradienten über alle Eingabesequenzen hinweg zu aggregieren und gleichzeitig den Einfluss einzelner Ausreißer zu reduzieren. Wie zuvor wird ein paralleler Reduktionsbaum in Kombination mit Synchronisationsbarrieren verwendet, um die Summierung innerhalb jeder Arbeitsgruppe effizient durchzuführen. Nur der Thread mit v == 0 (der erste) führt die endgültige Gewichtsaktualisierung durch.
//--- sum for(int s = 0; s < (int)variables; s += ls) { if(v >= s && v < (s + ls)) temp[v % ls] = (s == 0 ? 0 : temp[v % ls]) + grad; barrier(CLK_LOCAL_MEM_FENCE); } //--- uint count = ls; do { count = (count + 1) / 2; if(v < count && (v + count) < ls) { temp[v] += temp[v + count]; temp[v + count] = 0; } barrier(CLK_LOCAL_MEM_FENCE); } while(count > 1);
An diesem Punkt kommt der Adam-Optimierer ins Spiel:
- Die erste Momentenschätzung mt wird mittels exponentieller Glättung mit dem Koeffizienten b1 aktualisiert.
- Die zweite Momentenschätzung vt, die die Gradientenvarianz darstellt, wird mit dem Koeffizienten b2 aktualisiert.
- Das Gewicht wird aktualisiert, indem mt / sqrt(vt) normalisiert und das Ergebnis mit der Lernrate l skaliert wird.
- Alle Werte werden mittels clamp begrenzt, um numerische Instabilität, explodierende Gewichte und Division durch Null zu verhindern.
if(v == 0) { grad = temp[0]; float mt = IsNaNOrInf(clamp(b1 * matrix_m[i] + (1 - b1) * grad, -1.0e5f, 1.0e5f), 0); float vt = IsNaNOrInf(clamp(b2 * matrix_v[i] + (1 - b2) * pow(grad, 2), 1.0e-6f, 1.0e6f), 1.0e-6f); float weight = clamp(matrix_w[i] + IsNaNOrInf(l * mt / sqrt(vt), 0), -MAX_WEIGHT, MAX_WEIGHT); matrix_w[i] = weight; matrix_m[i] = mt; matrix_v[i] = vt; } }
Schließlich werden die aktualisierten Parameterwerte in den globalen Speicher zurückgeschrieben.
Zusammen mit den zuvor beschriebenen Feed-Forward- und Backpropagation-Stufen macht dieser Mechanismus den Faltungsblock vollständig in sich geschlossen, vollständig differenzierbar und für die Integration in Deep-Neural-Network-Architekturen geeignet. Noch wichtiger ist, dass er hochgradig skalierbar ist und sich leicht an eine beliebige Anzahl von Faltungsfenstern und Ausgabekanälen anpassen lässt. Dies verleiht dem Modell eine außergewöhnliche Flexibilität bei der Arbeit mit realen, nichtlinearen und nichtstationären Zeitreihen.
Innerhalb der Hauptanwendung ist die gesamte Funktionalität der Multi-Window-Konvolution mit Zero-Padding in der Klasse CNeuronMultiWindowsConvWPadOCL gekapselt. Dieses Modul erbt von der Basisklasse für Faltungsschichten CNeuronConvOCL und dient als Schnittstelle, die für die Planung aller oben beschriebenen OpenCL-Kernels verantwortlich ist.
Die Klassenstruktur ist unten dargestellt.
class CNeuronMultiWindowsConvWPadOCL : public CNeuronConvOCL { protected: int aiWindows[]; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL); virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL); virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL); public: CNeuronMultiWindowsConvWPadOCL(void) { activation = SoftPlus; iWindow = -1; } ~CNeuronMultiWindowsConvWPadOCL(void) {}; virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint &windows[], uint step, uint window_out, uint units_count, uint variables, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) const { return defNeuronMultiWindowsConvWPadOCL; } //--- methods for working with files virtual bool Save(int const file_handle); virtual bool Load(int const file_handle); //--- virtual void SetOpenCL(COpenCLMy *obj); //--- virtual uint GetWindow(void) const { return aiWindows[0]; } virtual uint GetWindowsSize(void) const { return aiWindows.Size(); } virtual uint GetWindowOut(void) const { return iWindowOut; } virtual uint GetUnits(void) const { return Neurons()/(iVariables*GetWindowsSize()*iWindowOut); } };
Diese Klasse abstrahiert die gesamte Komplexität der Interaktion mit OpenCL und ermöglicht es, den Multi-Window-Konvolutionsblock nahtlos in die gesamte Modellarchitektur zu integrieren. Der vollständige Quellcode der Klasse sowie die Implementierung aller ihrer Methoden sind im Anhang für Leser enthalten, die die Implementierung im Detail studieren möchten.
Leider hat jeder Artikel praktische Grenzen, und wir haben nun den verfügbaren Platz erreicht. Die Effizienz der implementierten Techniken wird im nächsten Artikel bewertet.
Schlussfolgerung
In diesem Artikel haben wir die Implementierung der von den Autoren des Mamba4Cast-Frameworks vorgeschlagenen Konzepte fortgesetzt und uns dabei auf zwei Eckpfeiler konzentriert: zeitbewusste Eingabe-Embeddings und Multi-Window-Konvolution mit Zero-Padding. Die Klasse CMamba4CastEmbedding hat gezeigt, wie Roh-Feature-Projektionen und harmonische zeitliche Kodierungen in einem einzigen Modul kombiniert werden können. Unterdessen haben die OpenCL-Kernels FeedForwardMultWinConvWPad, CalcHiddenGradientMultWinConvWPad und UpdateWeightsMultWinConvAdamWPad veranschaulicht, wie mehrere Faltungsfenster effizient parallel verarbeitet werden können, während die vollständige Differenzierbarkeit während des gesamten Trainings erhalten bleibt.
Unsere Aufmerksamkeit galt nicht nur der Leistung. Sorgfältiges Offset-Management, eine ausgewogene Arbeitslastverteilung über Ausführungsthreads hinweg und eine effiziente Gradientenaggregation durch lokale Reduktionen machten den Faltungsblock zu einer wirklich skalierbaren Komponente. Gleichzeitig bleibt die Gesamtlogik transparent: Die Klasse CNeuronMultiWindowsConvWPadOCL kapselt alle OpenCL-spezifischen Implementierungsdetails, wodurch die vorgeschlagene Lösung einfach in jedes Modul integriert werden kann.
Im nächsten Artikel werden wir alle entwickelten Komponenten zu einem vollständigen Modell zusammenfügen, es mit realen historischen Marktdaten trainieren und die Wirksamkeit der vorgeschlagenen Techniken in der Praxis bewerten.
Verwandte Links
- Mamba4Cast: Efficient Zero-Shot Time Series Forecasting with State Space Models
- Andere Artikel dieser Serie
In diesem Artikel verwendete Programme
| # | Name | Typ | Spezifikationen |
|---|---|---|---|
| 1 | Research.mq5 | Expert Advisor | Expert Advisor für die Erfassung von Datensätzen |
| 2 | ResearchRealORL.mq5 | Expert Advisor | Expert Advisor für die Erfassung von Datensätzen mit der Methode Real-ORL |
| 3 | Study.mq5 | Expert Advisor | Expert Advisor für das Offline-Modelltraining |
| 4 | StudyOnline.mq5 | Expert Advisor | Expert Advisor für das Online-Modelltraining |
| 5 | Test.mq5 | Expert Advisor | Expert Advisor für Modelltests |
| 6 | Trajectory.mqh | Klassenbibliothek | Struktur der Beschreibung des Systemzustands und der Modellarchitektur |
| 7 | NeuroNet.mqh | Klassenbibliothek | Eine Bibliothek von Klassen zur Erstellung eines neuronalen Netzes |
| 8 | NeuroNet.cl | Bibliothek | OpenCL-Programmcode |
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18138
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.
Marktsimulation: Positionsansicht (IV)
Implementierung einer Schaltung für Quantum Reservoir Computing (QRC)
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Algorithmus für künstliche Atome (A3)
- 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.