English Русский 中文 Español 日本語 Português
preview
Neuronale Netze im Trading: Zeitreihenprognose mittels adaptiver Modenzerlegung (Abschließender Teil)

Neuronale Netze im Trading: Zeitreihenprognose mittels adaptiver Modenzerlegung (Abschließender Teil)

MetaTrader 5Handelssysteme |
12 1
Dmitriy Gizlyk
Dmitriy Gizlyk

Einführung

Im vorherigen Artikel haben wir das ACEFormer-Framework vorgestellt, ein Zeitreihenprognosemodell, das speziell an die Charakteristika von Finanzmärkten angepasst ist. Wir untersuchten die grundlegenden Prinzipien der probabilistischen Attention, die Algorithmen zu deren Implementierung auf Ebene der OpenCL-Kernel sowie Techniken zur Verbesserung der Recheneffizienz bei gleichzeitiger Wahrung der Prognosegenauigkeit. Ein ebenso wichtiger Aspekt blieb jedoch außerhalb des Rahmens dieser Diskussion: wie diese Mechanismen in das Hauptprogramm eingebunden werden und wie eine zuverlässige Kommunikation zwischen dem Rechenmodul und dem Handelsalgorithmus hergestellt wird.

In diesem Artikel setzen wir die Implementierung der von den Autoren des ACEFormer-Frameworks vorgeschlagenen Ansätze fort. Unser Hauptaugenmerk liegt auf der programmseitigen Implementierung der entsprechenden Algorithmen. Bevor wir jedoch zur technischen Implementierung übergehen, rekapitulieren wir zunächst kurz die Kernkonzepte und Stärken von ACEFormer. Im Zentrum des Frameworks steht der ACEEMD (Alias Complete Ensemble Empirical Mode Decomposition with Adaptive Noise) Algorithmus, der darauf ausgelegt ist, Rauschen aus Finanzzeitreihen zu entfernen. ACEEMD mildert wirksam den Randeffekt, während die wesentlichen Wendepunkte im Chart erhalten bleiben, ohne dass kritische Informationen bei der Glättung verloren gehen. Besondere Aufmerksamkeit gilt der ersten intrinsischen Modenfunktion (IMF), deren explizite Entfernung dazu beiträgt, hochfrequentes Rauschen zu eliminieren, ohne das Nutzsignal übermäßig zu unterdrücken.

Das Ergebnis dieser Stufe ist eine adaptive modale Repräsentation der Zeitreihe, die als Eingabe für das Destillationsmodul dient, welches auf einer Transformer-Architektur mit probabilistischer Attention basiert. Dieser Ansatz filtert nicht nur das Marktrauschen heraus, sondern ermöglicht es dem Modell auch, sich auf die wirklich informativen Bereiche historischer Daten zu konzentrieren, was die Prognosegenauigkeit unter Bedingungen hoher Marktvolatilität und Stochastizität verbessert.

Die verarbeiteten Repräsentationen werden anschließend durch einen konventionellen Self-Attention-Block geleitet, wodurch das Modell den globalen Kontext der ursprünglichen Sequenz erfassen und die Konsistenz über verschiedene Zeithorizonte hinweg verbessern kann. Durch die Kombination von lokalisierter probabilistischer Attention mit globaler Self-Attention ist das Modell in der Lage, sich gleichzeitig auf scharfe Marktumkehrungen und anhaltende Trends zu konzentrieren.

Die letzte Stufe der Architektur ist ein Vorhersagekopf oder eine abschließende Vorhersageschicht, die die höherstufige Merkmalsrepräsentation in eine spezifische numerische Prognose umwandelt.

ACEFormer kombiniert die Stärken zweier Ansätze: die Robustheit der empirischen Modenzerlegung und die Flexibilität tiefer Attention-Mechanismen. Gleichzeitig bleibt das Modell kompakt genug für den Echtzeiteinsatz in Benutzerterminals und erfordert keine übermäßigen Rechenressourcen. Dank seiner modularen Architektur und der Unterstützung für parallele Verarbeitung kann ACEFormer leicht an eine Vielzahl von Handelsstrategien angepasst werden, von kurzfristigen Momentum-Modellen bis hin zu Systemen für mittelfristigen Positionshandel.

Nachfolgend ist die Visualisierung des ACEFormer-Frameworks der Autoren dargestellt.



Konstruktion des probabilistischen Attention-Moduls

Nachdem die Implementierung der probabilistischen Attention-Mechanismen auf der OpenCL-Programmseite abgeschlossen ist, fahren wir nun mit der nächsten Stufe fort: der Entwicklung des entsprechenden übergeordneten Moduls auf der Ebene des Hauptprogramms. Um dies zu erreichen, erstellen wir ein spezialisiertes CNeuronMHProbAttention-Objekt, das den vollständigen probabilistischen Attention-Algorithmus kapselt.

Die neue Klasse erbt von CResidualConv, das bereits eine Architektur implementiert, die aus zwei aufeinanderfolgenden Faltungsschichten besteht, die durch residuale Verbindungen verknüpft sind. Dies ermöglicht es uns, uns ausschließlich auf die Implementierung der probabilistischen Attention-Logik zu konzentrieren, ohne die von der Elternklasse bereitgestellten FeedForward-Mechanismen zu verändern. Dadurch erreicht das Design einen hohen Grad an Modularität und erleichtert gleichzeitig die nahtlose Integration mit den anderen Komponenten des Modells.

Die Struktur der neuen Klasse ist unten dargestellt.

class CNeuronMHProbAttention :  public CResidualConv
  {
protected:
   uint                       iWindow;
   uint                       iWindowKey;
   uint                       iHeads;
   uint                       iUnits;
   uint                       iTopKQuerys;
   uint                       iRandomKeys;
   int                        ibScore;
   //---
   CNeuronConvOCL             cQKV;
   CNeuronBaseOCL             cQ;
   CNeuronBaseOCL             cKV;
   CNeuronBaseOCL             cRandomK;
   CNeuronBaseOCL             cMHAttentionOut;
   CNeuronConvOCL             cPooling;
   CNeuronTransposeOCL        cTranspose[2];
   CNeuronConvOCL             cScaling;
   //---
   virtual bool               RandomKeys(CBufferFloat* indexes, int random, int units, int heads);
   virtual bool               QueryImportance(void);
   virtual bool               TopKIndexes(void);
   //---
   virtual bool               AttentionOut(void);
   virtual bool               AttentionInsideGradients(void);
   //---
   virtual bool               feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool               updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool               calcInputGradients(CNeuronBaseOCL *prevLayer) override;

public:
                              CNeuronMHProbAttention(void) {};
                             ~CNeuronMHProbAttention(void) {};
   //---
   virtual bool               Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, 
                                   uint window, uint window_key, uint heads, uint units_count,
                                   ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int                Type(void)   const   {  return defNeuronMHProbAttention;   }
   //---
   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 Struktur von CNeuronMHProbAttention spiegelt die schrittweise Implementierung des probabilistischen Attention-Mechanismus wider, beginnend mit der zufälligen Schlüsselgenerierung und der Schätzung der Query-Wichtigkeit bis hin zur Gradienten-Backpropagation. Jede Funktionskomponente ist als separates Modul implementiert, was eine klare Aufgabentrennung ermöglicht und eine fein abgestimmte Anpassung erlaubt.

Aus architektonischer Sicht ist eines der bestimmenden Merkmale von CNeuronMHProbAttention, dass alle internen Objekte statisch deklariert sind. Dieses Design vereinfacht nicht nur die Speicherverwaltung, sondern macht auch die dynamische Ressourcenzuweisung bei der Erstellung einer Instanz der Klasse überflüssig. Folglich bleiben sowohl der Konstruktor als auch der Destruktor leer, was die Zuverlässigkeit und Vorhersehbarkeit der Objekterstellung und -zerstörung verbessert.

Die gesamte erforderliche Initialisierung interner Objekte und Variablen ist in der Init-Methode zentralisiert, die effektiv als Klassenkonstruktor fungiert. Sie ist für das Zusammenfügen aller Komponenten, die Konfiguration ihrer Parameter und die Zuweisung der erforderlichen Ressourcen verantwortlich.

bool CNeuronMHProbAttention::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                  uint window, uint window_key, uint heads, uint units_count,
                                  ENUM_OPTIMIZATION optimization_type, uint batch
                                 )
  {
   if(!CResidualConv::Init(numOutputs, myIndex, open_cl, window, window, units_count, optimization_type, batch))
      return false;

Da die neue Klasse von CResidualConv abgeleitet ist, das eine residuale Faltungsarchitektur implementiert, beginnt der Initialisierungsvorgang mit dem Aufruf der entsprechenden Methode der Elternklasse. Dies stellt sofort die zugrunde liegende Infrastruktur bereit, einschließlich der Faltungsschichten, der residualen Verbindungen und der Pufferinitialisierung. So vermeiden wir eine erneute Implementierung und können uns ganz auf die spezifische Attention-Logik konzentrieren.

Der nächste Schritt besteht darin, die Schlüsselparameter zu initialisieren, die das Verhalten des Attention-Mechanismus definieren.

iWindow = window;
iWindowKey = MathMax(5, window_key);
iHeads = MathMax(1, heads);
iUnits = units_count;
iTopKQuerys = int(MathMin(5 * MathMax(MathLog(iUnits),1), iUnits));
iRandomKeys = int(MathMin(5 * MathMax(MathLog(iUnits),1), iUnits));

Zusätzlich zu den bekannten Parametern werden zwei neue Variablen eingeführt, die spezifisch für die probabilistische Attention sind:

  • iTopKQuerys – die Anzahl der informativsten queries, die für die weitere Verarbeitung ausgewählt wurden;
  • iRandomKeys – die Anzahl der zufällig abgetasteten keys, die während des query-Auswahlprozesses verwendet werden.

Um die Skalierbarkeit zu gewährleisten, werden die Werte beider Parameter als logarithmische Funktionen der Gesamtlänge der Eingabesequenz berechnet.

Sobald die Objektparameter gespeichert wurden, fährt das Framework mit der sequenziellen Konstruktion aller internen Komponenten fort, die für die Implementierung des Attention-Mechanismus verantwortlich sind. Jedes Objekt wird individuell in einer streng definierten Reihenfolge initialisiert. Die erste zu initialisierende Komponente ist cQKV, eine konvolutionelle Schicht, die gleichzeitig die drei Entitäten generiert: Q (Query), K (Key) und V (Value). Diese Schicht erzeugt dichte Merkmalsrepräsentationen für jeden Attention-Head und kodiert sie gleichzeitig unter Verwendung der TANH-Aktivierungsfunktion.

int index = 0;
if(!cQKV.Init(0, index, OpenCL, iWindow, iWindow, 3 * iWindowKey * iHeads, iUnits, optimization, iBatch))
   return false;
cQKV.SetActivationFunction(TANH);

Es ist erwähnenswert, dass die zuvor entwickelten Kernels, die probabilistische Attention implementieren, einen dedizierten Puffer für den Query-Tensor verwenden. Daher besteht der nächste Schritt darin, zwei zusätzliche Objekte zu erstellen, die diese Entitäten trennen.

index++;
if(!cQ.Init(0, index, OpenCL, cQKV.Neurons() / 3, optimization, iBatch))
   return false;
index++;
if(!cKV.Init(0, index, OpenCL, 2 * cQ.Neurons(), optimization, iBatch))
   return false;

Das folgende Objekt speichert die zufällig ausgewählten Key-Indizes.

index++;
if(!cRandomK.Init(0, index, OpenCL, iHeads * MathMax(iRandomKeys, iTopKQuerys), optimization, iBatch))
   return false;

Beachten Sie, dass die Größe dieser Schicht durch den größeren von zwei Werten bestimmt wird: die Anzahl der abgetasteten Keys und die Anzahl der wichtigsten Queries. Die Idee ist, eine ausreichende Speicherkapazität für alle Indizes zu gewährleisten, die während der Berechnung erforderlich sein könnten. Da wir nicht im Voraus bestimmen können, welche Menge größer sein wird – iRandomKeys für zufällige Keys oder iTopKQuerys für Queries –, weisen wir Speicher basierend auf dem Maximum der beiden zu. Der resultierende Wert wird dann mit der Anzahl der Attention-Heads (iHeads) multipliziert, da jeder Head unabhängig arbeitet und daher seinen eigenen Satz an Indizes benötigt.

Als Ergebnis wird ein einzelnes Objekt cRandomK verwendet, um sowohl die zufällig abgetasteten Key-Indizes als auch die informativsten Queries zu speichern. Dies vereinfacht die interne Struktur des Moduls, reduziert die Gesamtzahl der Objekte und vereinfacht die Speicherverwaltung.

Die nächste Komponente speichert die Ausgaben der Multi-Head-Attention für die informativsten Queries.

index++;
if(!cMHAttentionOut.Init(0, index, OpenCL, iTopKQuerys * iHeads * iWindowKey, optimization, iBatch))
   return false;

Darauf folgt die adaptive Aggregationsschicht, die die Ausgaben aller Attention-Heads zu einer einheitlichen Repräsentation kombiniert.

index++;
if(!cPooling.Init(0, index, OpenCL, iHeads * iWindowKey, iHeads * iWindowKey, iWindow, iTopKQuerys,
                                                                              optimization, iBatch))
   return false;
cPooling.SetActivationFunction(TANH);

In diesem Stadium hat der Algorithmus bereits die Ausgabe des probabilistischen Attention-Mechanismus erzeugt. Ein wichtiger architektonischer Aspekt sollte jedoch hervorgehoben werden: Diese Ausgaben entsprechen nur den ausgewählten Top-K Queries. Folglich hat der resultierende Tensor eine wesentlich kleinere zeitliche Dimension als die ursprüngliche Eingabesequenz.

Dies erzeugt eine architektonische Diskrepanz. Der probabilistische Attention-Block erzeugt eine komprimierte Repräsentation, während die konventionelle Self-Attention-Architektur die ursprüngliche Sequenzlänge beibehält. Die Aufrechterhaltung dieser dimensionalen Konsistenz ist für die korrekte Anwendung von residual-Verbindungen unerlässlich, die ein stabiles Training und eine effektive Gradientenpropagation gewährleisten.

Um diese Diskrepanz zu beheben, verfolgen wir die folgende Strategie:

  • Zuerst wird die Attention-Ausgabematrix transponiert, sodass ihr Layout für die Faltung entlang der Merkmalsdimension geeignet ist.

index++;
if(!cTranspose[0].Init(0, index, OpenCL, iTopKQuerys, iWindow, optimization, iBatch))
   return false;

  • Als Nächstes wird die Faltungsschicht cScaling angewendet, um die Ausgabe auf die ursprüngliche Sequenzlänge zurückzuführen. Infolgedessen erhält der Tensor wieder dieselbe Dimensionalität wie die ursprüngliche Eingabe. Bemerkenswert ist, dass diese Hochskalierung unabhängig für jedes Merkmal durchgeführt wird, wodurch jeder Zeitschritt rekonstruiert werden kann, während die globale Attention-Struktur berücksichtigt wird.
index++;
if(!cScaling.Init(0, index, OpenCL, iTopKQuerys, iTopKQuerys, iUnits, iWindow, optimization, iBatch))
   return false;
cScaling.SetActivationFunction(None);
  • Schließlich werden die Daten durch Anwendung einer zweiten Transpositionsoperation in ihr ursprüngliches Layout zurückversetzt.
if(!cTranspose[1].Init(0, index, OpenCL, iWindow, iUnits, optimization, iBatch))
   return false;

Diese Abfolge von Operationen bietet maximale Kompatibilität zwischen dem stochastischen probabilistischen Attention-Mechanismus und den architektonischen Anforderungen der konventionellen Self-Attention. Das Modell bewahrt seine strukturelle Integrität und behält gleichzeitig die für ein stabiles Training erforderliche Recheneffizienz und Flexibilität bei.

Besondere Aufmerksamkeit gilt dem ibScore-Puffer, der die während der Backpropagation benötigten attention scores speichert. Dieser Puffer wird ausschließlich innerhalb des OpenCL-Kontexts zugewiesen.

   ibScore = OpenCL.AddBuffer(sizeof(float) * iTopKQuerys * iUnits * iHeads, CL_MEM_READ_WRITE);
   if(ibScore < 0)
      return false;
//---
   return true;
  }

Nachdem alle internen Objekte initialisiert wurden, schließt die Methode mit der Rückgabe eines logischen Erfolgsstatus ab.

Die nächste wichtige Phase ist die Implementierung des FeedForward-Algorithmus. Vor der Verarbeitung des Hauptdatenstroms ist jedoch ein geringer Vorbereitungsaufwand erforderlich.

Wie bei den meisten Modulen, die in dieser Serie entwickelt wurden, reihen mehrere Methoden einfach OpenCL-Kernel-Ausführungen in die Warteschlange ein. Da ihre zugrunde liegenden Algorithmen bereits in früheren Artikeln besprochen wurden, ist es wenig sinnvoll, sie hier zu wiederholen. Stattdessen konzentrieren wir uns auf den Algorithmus der Key-Stichprobenziehung, der in der RandomKeys-Methode implementiert ist.

Das Ziel des probabilistischen Attention-Mechanismus ist es, die Anzahl der Keys zu reduzieren, die bei der Identifizierung der informativsten Queries untersucht werden müssen. Dies senkt die Rechenkosten und führt gleichzeitig eine stochastische Komponente ein, die dazu beiträgt, Overfitting zu verringern und die Generalisierungsfähigkeit des Modells zu verbessern.

Die RandomKeys-Methode erhält einen Zeiger auf den indexes-Puffer, der mit den abgetasteten Key-Indizes gefüllt werden soll. Die Parameter random, units und heads geben jeweils die Anzahl der Zufallsstichproben, die Gesamtzahl der verfügbaren Keys und die Anzahl der Attention-Heads an.

bool CNeuronMHProbAttention::RandomKeys(CBufferFloat *indexes, int random, int units, int heads)
  {
   if(!indexes || random > units ||
      indexes.Total() < (random * heads)
     )
      return false;

Die Methode beginnt mit der Validierung der Eingabeparameter. Wenn der Pufferzeiger null ist, die angeforderte Anzahl an Zufallsstichproben die Anzahl der verfügbaren Keys übersteigt oder der Puffer zu klein ist, gibt die Methode sofort false zurück.

Sobald die Validierungsphase erfolgreich abgeschlossen wurde, wird eine random × heads-Matrix erstellt, wobei jede Spalte einem einzelnen Attention-Head entspricht und jede Zeile eine zufällig ausgewählte Key-Position speichert.

matrix<float> ind = matrix<float>::Zeros(random, heads);

Im Folgenden sind zwei Szenarien möglich. Wenn keine Stichprobenziehung erforderlich ist (random == units), wird die Matrix einfach mit aufeinanderfolgenden Indizes gefüllt, was bedeutet, dass alle verfügbaren Keys verwendet werden.

if(random == units)
  {
   for(int r = 0; r < random; r++)
     {
      for(int c = 0; c < heads; c++)
         ind[r, c] = (float)r;
     }
  }

Wenn die Anzahl der ausgewählten Keys (random) kleiner ist als die Gesamtzahl der verfügbaren Werte (units), kann eine naive Zufallsstichprobenstrategie eine nicht repräsentative Stichprobe der verfügbaren Keys/Datenbereiche erzeugen. Das einfache Ziehen von Zufallsindizes aus dem gesamten Bereich kann zu doppelten Werten, lokalen Clustern oder umgekehrt zu Lücken führen, in denen bestimmte Regionen überhaupt nicht repräsentiert sind. Infolgedessen kann das Modell wichtige Teile der Eingabesequenz möglicherweise nicht erfassen, was sich negativ auf den Trainingsprozess auswirkt.

Um dieses Problem zu vermeiden, verwendet unsere Implementierung eine gleichmäßig geschichtete Stichprobe. Der gesamte Indexbereich wird zunächst in gleich große Intervalle unterteilt.

else
  {
   double step = double(units) / random;

Das Ergebnis kann ein Bruchwert sein, was völlig akzeptabel ist, da die Stichprobengröße selten ein exakter Teiler der Sequenzlänge ist.

Für jedes stratum (Bereichssegment) wird innerhalb seiner Grenzen ein Wert nach dem Zufallsprinzip ausgewählt.

 for(int r = 0; r < random; r++)
   {
    for(int c = 0; c < heads; c++)
       ind[r, c] = float(int((r + MathRand() / 32767.0) * step));
   }
}

Dadurch erhält jeder Attention-Head seinen eigenen, unabhängig generierten Satz von Indizes, während alle diese Sätze zusammen die gesamte Sequenz abdecken, ohne eine Verzerrung oder übermäßige Konzentration in bestimmten Regionen aufzuweisen.

Sobald die Stichprobenziehung abgeschlossen ist, wird die Stichprobe in den indexes-Puffer geschrieben, von wo aus sie anschließend in den OpenCL-Kernels verwendet wird.

   if(!indexes.AssignArray(ind) ||
      !indexes.BufferWrite())
      return false;
//---
   return true;
  }

Dieser Ansatz bietet mehrere wesentliche Vorteile. Erstens garantiert es eine gleichmäßige Abdeckung des gesamten Merkmalsraums. Zweitens verhindert es, dass Indizes außerhalb des gültigen Bereichs liegen, während die Wahrscheinlichkeit doppelter Stichproben minimiert wird. Folglich wird die abgetastete Teilmenge wesentlich repräsentativer und bietet dem Modell eine vollständigere und ausgewogenere Sicht auf die ursprünglichen Daten. Dies ist besonders wichtig bei der Arbeit mit Finanzzeitreihen, bei denen Perioden geringer Volatilität genauso informativ sein können wie Episoden schneller Marktbewegungen.

Nach Abschluss der Vorbereitungsphase können wir zum Kern der Implementierung übergehen – der feedForward-Methode, die den vollständigen probabilistischen Attention-Algorithmus ausführt.

bool CNeuronMHProbAttention::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cQKV.FeedForward(NeuronOCL))
      return false;

Der erste Vorgang ruft die FeedForward-Methode der cQKV-Faltungsschicht auf. Wie der Name schon sagt, erzeugt diese Schicht einen verketteten Tensor, der die Repräsentationen von Query, Key und Value enthält. Die Ausgabe ist ein einzelner Tensor, in dem die drei Komponenten sequenziell gespeichert sind.

Für die nachfolgenden Stufen des Algorithmus müssen diese Repräsentationen jedoch in zwei Tensoren aufgeteilt werden: einen mit den Queries (Q) und einen weiteren mit den Keys und Values (K und V). Dies erfolgt durch die Verwendung der DeConcat-Methode, die die entsprechenden Teile des kombinierten QKV-Tensors extrahiert und sie auf cQ und cKV überträgt.

if(!DeConcat(cQ.getOutput(), cKV.getOutput(), cQKV.getOutput(), iWindowKey * iHeads,
                                                    2 * iWindowKey * iHeads, iUnits))
   return false;

Der zuvor beschriebene Algorithmus der Key-Stichprobenziehung wird dann durch den Aufruf der RandomKeys-Methode ausgeführt.

if(!RandomKeys(cRandomK.getOutput(), iRandomKeys, iUnits, iHeads))
   return false;

Die nächste Stufe ruft die Methoden QueryImportance und TopKIndexes auf. Diese Methoden stellen Kernels in die Warteschlange, die für die Bewertung der Query-Wichtigkeit und die Auswahl der informativsten zuständig sind.

if(!QueryImportance() || !TopKIndexes())
   return false;

Der Kern des Algorithmus folgt in der AttentionOut-Methode. Sie stellt den Attention-Kernel in die Warteschlange, der für die Berechnung der Attention nur für die ausgewählten Queries zuständig ist.

if(!AttentionOut())
   return false;

Die resultierenden Ausgaben aller Attention-Heads werden dann durch die cPooling-Faltungsschicht aggregiert. Diese Schicht wendet auch die TANH-Aktivierungsfunktion an, was die Ausdruckskraft des Ausgangssignals erhöht.

if(!cPooling.FeedForward(cMHAttentionOut.AsObject()))
   return false;

Die aggregierten Ausgaben werden anschließend hochskaliert, um den Tensor wieder auf die Dimensionalität der ursprünglichen Eingabesequenz zu bringen, wodurch Residualverbindungen korrekt angewendet werden können.

if(!cTranspose[0].FeedForward(cPooling.AsObject()))
   return false;
if(!cScaling.FeedForward(cTranspose[0].AsObject()))
   return false;
if(!cTranspose[1].FeedForward(cScaling.AsObject()))
   return false;

Der letzte Vorgang innerhalb des Attention-Blocks ruft die SumAndNormalize-Methode auf, die die hochskalierte Repräsentation mit der ursprünglichen Eingabe kombiniert, indem die Residualverbindung hinzugefügt und eine Normalisierung durchgeführt wird. Dies gewährleistet eine stabile Gradientenfortpflanzung und beschleunigt die Konvergenz während des Trainings.

if(!SumAndNormilize(cTranspose[1].getOutput(), NeuronOCL.getOutput(), cTranspose[1].getOutput(),
                    iWindow, true, 0, 0, 0, 1))
   return false;

Schließlich wird der resultierende Tensor an die entsprechende Methode der Elternklasse übergeben, welche die Vorwärtspropagierungsphase abschließt und den Ausgabetensor zurückgibt, der bereit ist, vom nächsten Modul des neuronalen Netzwerks verarbeitet zu werden.

 return CResidualConv::feedForward(cTranspose[1].AsObject());
}

Als Ergebnis ist der gesamte probabilistische Attention-Mechanismus im Objekt CNeuronMHProbAttention gekapselt. Dieses Design bietet eine hochgradig modulare, wiederverwendbare und leicht skalierbare Implementierung.

Sobald der Vorwärtsdurchlauf abgeschlossen ist, tritt der Trainingsprozess in seine kritischste Phase ein: die Backpropagation des Optimierungsfehlergradienten zur Aktualisierung der Modellparameter. Während dieser Phase wird der Beitrag jeder Komponente zum Gesamtverlust berechnet, wonach die Netzwerkgewichte entsprechend angepasst werden. Im Fall des probabilistischen Attention-Mechanismus, der durch die Klasse CNeuronMHProbAttention implementiert wird, wird die Backpropagation erheblich komplexer, da Gradienten nur durch die Teilmenge der ausgewählten Queries propagiert werden. Dies erfordert eine sorgfältige Organisation des Prozesses und eine strikte Einhaltung der Berechnungslogik.

bool CNeuronMHProbAttention::calcInputGradients(CNeuronBaseOCL *prevLayer)
  {
   if(!prevLayer)
      return false;

Die Methode calcInputGradients beginnt mit einer grundlegenden Validierung des Zeigers auf die vorhergehende Schicht, der als einer der Parameter der Methode empfangen wird. Ohne eine gültige Referenz auf die vorherige Schicht kann der Fehlergradient nicht weiter durch das Netzwerk propagiert werden.

Der nächste Schritt ist eine wichtige vorbereitende Operation: Der Gradientenpuffer für Query wird explizit gelöscht.

if(!cQ.getGradient().Fill(0))
   return false;

Diese Operation ist zwingend erforderlich, da der Attention-Algorithmus nur eine Teilmenge der Queries verarbeitet – die relevantesten, die während des Vorwärtsdurchlaufs ausgewählt wurden. Die verbleibenden Elemente der Gradientenmatrix können noch veraltete oder undefinierte Werte enthalten, die nicht mehr gültig sind und nachfolgende Parameteraktualisierungen beschädigen könnten. Daher wird die gesamte Matrix gelöscht, bevor die korrekten Gradientenwerte zu akkumulieren beginnen.

Als Nächstes rufen wir die entsprechende Methode der Elternklasse auf. Diese Operation propagiert den Fehler durch den FeedForward-Block. Somit wird dieser Teil des Backpropagation-Durchlaufs an die bereits bewährte Logik delegiert.

if(!CResidualConv::calcInputGradients(cTranspose[1].AsObject()))
   return false;

Weitere Schritte propagieren die Fehlergradienten nacheinander durch jeden internen Block des probabilistischen Attention-Mechanismus, wobei die Struktur des Vorwärtsdurchlaufs exakt gespiegelt, jedoch in umgekehrter Reihenfolge, durchlaufen wird. Die Gradienten durchlaufen zuerst den Skalierungsblock.

if(!cScaling.calcHiddenGradients(cTranspose[1].AsObject()))
   return false;
if(!cTranspose[0].calcHiddenGradients(cScaling.AsObject()))
   return false;

Anschließend werden sie durch die Multi-Head-Attention-Aggregationsschicht propagiert.

if(!cPooling.calcHiddenGradients(cTranspose[0].AsObject()))
   return false;
if(!cMHAttentionOut.calcHiddenGradients(cPooling.AsObject()))
   return false;

Diese Phasen sind für die genaue Rekonstruktion der Gradienten unerlässlich, da der Vorwärtsdurchlauf mehrere Transformationen der Datenform und -struktur beinhaltete. Nun müssen diese Operationen in der richtigen Reihenfolge umgekehrt werden.

Besondere Aufmerksamkeit gilt der Methode AttentionInsideGradients, die die Gradientenfortpflanzung innerhalb des probabilistischen Attention-Mechanismus selbst durchführt. Hier wird der Fehler sorgfältig durch die Teilmenge der ausgewählten Queries fortgepflanzt. Es ist die empfindlichste Komponente des gesamten Backpropagation-Durchlaufs: Sie stellt sicher, dass die Gewichtsaktualisierungen präzise und mathematisch konsistent bleiben, obwohl nur ein Bruchteil der ursprünglichen Informationen am Prozess teilnimmt.

if(!AttentionInsideGradients())
   return false;

Die nächste Stufe führt die Gradienten zusammen, indem die Query-, Key- und Value-Gradienten zu einem einzigen QKV-Tensor kombiniert werden, wodurch die ursprüngliche Darstellung wiederhergestellt wird, die während des FeedForward-Durchlaufs erzeugt wurde. Wenn eine Aktivierungsfunktion im cQKV-Block angewendet wurde, werden die Gradienten durch Berechnung der Ableitung dieser Funktion korrigiert. Dies berücksichtigt ihren Einfluss auf die fortgepflanzten Signale und bewahrt die mathematische Korrektheit des gesamten Prozesses.

if(!Concat(cQ.getGradient(), cKV.getGradient(), cQKV.getGradient(), iWindowKey * iHeads,
                                                        2 * iWindowKey * iHeads, iUnits))
   return false;
if(cQKV.Activation() != None)
   if(!DeActivation(cQKV.getOutput(), cQKV.getGradient(), cQKV.getGradient(), cQKV.Activation()))
      return false;

Die in dieser Phase erhaltenen Gradienten werden dann an die vorhergehende Schicht weitergeleitet.

if(!prevLayer.calcHiddenGradients(cQKV.AsObject()))
   return false;

Die einzige verbleibende Komponente ist der Fehlergradient, der durch die Residualverbindung fließt. Zuerst wird dieser Gradient unter Verwendung der Ableitung der Aktivierungsfunktion korrigiert, die auf die ursprüngliche Eingangsschicht angewendet wurde. Die Gradienten aus den beiden Zweigen werden dann addiert.

   if(prevLayer.Activation() != None)
      if(!DeActivation(prevLayer.getOutput(), cTranspose[1].getGradient(), cTranspose[1].getGradient(),
                                                                               prevLayer.Activation()))
         return false;
   if(!SumAndNormilize(cTranspose[1].getGradient(), prevLayer.getGradient(), prevLayer.getGradient(),
                       iWindow, false, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

Als Ergebnis implementiert die Methode calcInputGradients den vollständigen Backpropagation-Algorithmus für das probabilistische Attention-Modul. Sie bewahrt die Genauigkeit und Konsistenz jeder Gradiententransformation und ermöglicht es, den probabilistischen Attention-Mechanismus in komplexe neuronale Netzwerkarchitekturen zu integrieren, ohne das Risiko eines Informationsverlusts oder einer Verzerrung des Trainingssignals.

Die letzte Phase des Trainingsprozesses umfasst die Aktualisierung der Modellparameter und wird durch die Methode updateInputWeights implementiert. Da sich alle trainierbaren Parameter in internen Objekten befinden, besteht die Implementierung einfach darin, die entsprechenden Aktualisierungsmethoden dieser Komponenten nacheinander aufzurufen. Die Implementierung ist unkompliziert, daher wird diese Methode in diesem Artikel nicht im Detail besprochen. Der vollständige Quellcode des probabilistischen Attention-Moduls, einschließlich der Methode updateInputWeights, ist in den Anhängen enthalten.


Modellarchitektur

Zum Abschluss der Übersicht über die grundlegenden Implementierungskomponenten lohnt es sich, die Gesamtarchitektur des Trainingssystems zu untersuchen. Wie in einigen unserer früheren Arbeiten übernehmen wir ein hierarchisches Lern-Framework, das auf dem Actor–Director–Critic-Paradigma basiert. Dieser Ansatz ermöglicht eine klare Trennung zwischen Verarbeitung des Umgebungszustands, Entscheidungsfindung und Aktionsbewertung, was im Kontext komplexer und hochdynamischer Finanzmärkte besonders wichtig ist.

Im Rahmen dieses Frameworks werden vier Modelle trainiert. Das erste — der Environment State Encoder — spielt die zentrale Rolle. Es ist dafür verantwortlich, eine tiefgehende Analyse der Marktsituation durchzuführen und deren kompakte, aber dennoch höchst informative latente Repräsentation zu konstruieren. Dieses Modell verwendet die vom ACEFormer-Framework vorgeschlagenen Techniken, um zukünftige Umgebungszustände über einen festgelegten Planungshorizont vorherzusagen. Als Ergebnis erzeugt es eine stabile und dynamisch aussagekräftige Marktrepräsentation, die anschließend als Grundlage für die Aktionen des Agenten dient.

Das zweite Modell — der Actor — arbeitet gleichzeitig mit zwei Informationsquellen. Die erste besteht aus dem aktuellen Kontozustand und offenen Positionen, während die zweite die vom Encoder generierte latente Umgebungsrepräsentation ist. Der Actor wird darauf trainiert, Aktionen auszuwählen, die am besten zu den aktuellen Marktbedingungen passen, und seine Entscheidungen werden anschließend von den verbleibenden zwei Modellen bewertet.

Die Modelle Director und Critic bewerten die vom Actor ausgewählten Aktionen. Der Director führt einen strikten binären Filtermechanismus ein, der hilft, strategisch ungünstige Entscheidungen zu eliminieren, während der Critic eine weichere, quantitative Bewertung liefert, indem er den erwarteten Nutzen der ausgewählten Aktionen schätzt. Zusammen ermöglichen diese beiden Komponenten dem Agenten, eine robuste und adaptive Handelsstrategie zu entwickeln.

In diesem Artikel konzentrieren wir uns auf die Architektur des Environment State Encoder, da dies die Komponente ist, die die Schlüsselelemente des ACEFormer-Frameworks enthält. Die Architekturen der übrigen Modelle folgen im Allgemeinen den Prinzipien, die in unseren früheren Veröffentlichungen beschrieben wurden.

Wie üblich besteht die Eingangsschicht aus einer vollständig verbundenen Schicht ausreichender Größe.

//--- Encoder
   encoder.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Es ist erwähnenswert, dass wir im Gegensatz zur ursprünglichen ACEFormer-Implementierung keine Nullwerte für die prognostizierten Elemente anhängen.

Die erste Stufe der Datenverarbeitung wird von einem dedizierten Modul zur Merkmalsvorverarbeitung durchgeführt. Dieses Modul kombiniert Normalisierung mit stochastischer Rauschinjektion und einer konvolutionellen Transformation der Merkmalsdimensionalität.

Der Prozess beginnt mit der Normalisierung der Eingabedaten. Dies beseitigt Skalenunterschiede zwischen einzelnen Merkmalen und stabilisiert den Trainingsprozess. Um die Generalisierungsfähigkeit und Robustheit des Modells gegenüber lokalen Datenschwankungen weiter zu verbessern, wird während der Normalisierung eine kontrollierte Menge an zufälligem Rauschen eingeführt. Dies fungiert als Regularisierungsmechanismus, indem es die Variabilität realer Marktbedingungen simuliert und es dem Modell ermöglicht, sich besser an instabile oder zuvor nicht gesehene Szenarien anzupassen.

Die normalisierten und rauschverstärkten Daten werden dann durch eine konvolutive Schicht geleitet, die die Merkmalsdimensionalität in das von den nachfolgenden Netzwerkkomponenten benötigte Format transformiert. Dieser Vorgang reduziert gleichzeitig redundante Informationen und extrahiert die relevantesten räumlichen Beziehungen zwischen Merkmalen innerhalb des spezifizierten zeitlichen Fensters.

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormWithNoise;
   descr.count = prev_count;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   prev_count = descr.count = HistoryBars;
   descr.window = BarDescr;
   descr.step = BarDescr;
   int prev_out = descr.window_out = NSkills;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Nach der Vorverarbeitung werden die Daten transponiert, um von der zeitlichen Analyse zur unabhängigen Merkmalsanalyse zu wechseln. Dies ermöglicht es dem Attention-Mechanismus, aussagekräftige Abhängigkeiten innerhalb jedes Merkmals zu identifizieren, was die Repräsentationen und den Trainingsprozess verbessert.

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTransposeOCL;
   descr.count = prev_count;
   prev_count = descr.window = prev_out;
   prev_out = descr.count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Als Nächstes gehen wir zum Destillationsblock über. In der Encoder-Architektur implementiert er die zentrale Idee des ACEFormer-Frameworks: die Extraktion der informativsten Merkmale und die Aggregation relevanter Informationen. Im Kern befindet sich das probabilistische Attention-Modul, das die Eingabe zunächst verarbeitet, indem es sich auf die signifikantesten Merkmale konzentriert. Dies ermöglicht es dem Modell, sich auf die informativsten Komponenten des ursprünglichen Signals zu konzentrieren.

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMHProbAttention;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.step = 4;
   descr.window_out = 32;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Unmittelbar nach dem Attention-Modul folgt eine konvolutive Schicht, die die zeitliche Dimension der Merkmale reduziert. Ihr Zweck ist es, die Informationen zu komprimieren und dabei ihre wesentlichen Merkmale zu bewahren.

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   prev_out = descr.count = (prev_out + 1) / 2;
   descr.window = 2;
   descr.step = 2;
   int filt=descr.window_out = 5;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Darauf folgt ein Aggregationsmodul, das ähnlich wie eine max-pooling-Schicht arbeitet, die stärksten Aktivierungen innerhalb jedes Fensters auswählt und die dominanten Merkmale bewahrt.

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronProofOCL;
   descr.count = prev_count*prev_out;
   descr.window = filt;
   descr.step = filt;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Schließlich stabilisiert eine Normalisierungsschicht die Merkmalsverteilung und beschleunigt den Trainingsprozess.

//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count * prev_out;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Unsere Implementierung verwendet drei aufeinanderfolgende Destillationsblöcke, von denen jeder die Dimensionalität der einzelnen Merkmalszeitreihen unabhängig reduziert.

//--- layer 8
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMHProbAttention;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.step = 4;
   descr.window_out = 32;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 9
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   prev_out = descr.count = (prev_out + 1) / 2;
   descr.window = 2;
   descr.step = 2 ;
   filt=descr.window_out = 3;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 10
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronProofOCL;
   descr.count = prev_count*prev_out;
   descr.window = filt;
   descr.step = filt;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 11
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count * prev_out;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 12
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMHProbAttention;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.step = 4;
   descr.window_out = 32;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 13
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   prev_out = descr.count = (prev_out + 1) / 2;
   descr.window = 2;
   descr.step = 2 ;
   filt=descr.window_out = 3;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 14
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronProofOCL;
   descr.count = prev_count*prev_out;
   descr.window = filt;
   descr.step = filt;
   descr.layers = prev_count;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 15
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count * prev_out;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Somit reduziert der Destillationsblock nicht nur die Dimensionalität, er extrahiert und konzentriert die Essenz der ursprünglichen Daten und erzeugt eine reichhaltige latente Repräsentation, die robust gegenüber Rauschen ist.

Die Encoder-Architektur integriert dann einen zweischichtigen Self-Attention-Block, dessen Zweck es ist, Abhängigkeiten zwischen Merkmalen innerhalb der komprimierten latenten Repräsentation zu erfassen. Dies hilft dabei, verborgene Beziehungen zu entdecken und Informationen über verschiedene Merkmalsdimensionen hinweg zu synchronisieren.

//--- layer 16
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronDMHAttention;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.step = 4;
   descr.layers = 2;
   descr.window_out = 32;
   descr.batch = 1e4;
   descr.optimization = ADAM;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Infolgedessen lernt das Modell, die Daten nicht bloß als eine Sammlung unabhängiger Beobachtungen wahrzunehmen, sondern als ein kohärentes System interagierender Komponenten.

Um jede einzelne Merkmalssequenz unabhängig vorherzusagen, verwendet der Encoder zwei aufeinanderfolgende konvolutive Schichten. Dieses Design transformiert die verallgemeinerte latente Repräsentation effizient in numerische Vorhersagen für jedes Zielmerkmal.

Die erste Faltungsschicht verwendet eine erweiterte Anzahl an Filtern (das Vierfache der Anzahl der prognostizierten Parameter) und wendet die SoftPlus-Aktivierungsfunktion an. Dies erzeugt eine glatte Transformation positiver Werte, die die Trainingsstabilität verbessert und gleichzeitig Rauschen unterdrückt.

//--- layer 17
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 1;
   descr.window = prev_out;
   descr.step = prev_out;
   prev_out = descr.window_out = 4 * NForecast;
   descr.layers = prev_count;
   descr.activation = SoftPlus;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Die zweite Schicht schließt den Dekodierungsprozess ab, indem sie die Anzahl der Ausgaben auf genau die Anzahl der vorhergesagten Parameter (NForecast) reduziert und die TANH-Aktivierungsfunktion anwendet. Dies trägt dazu bei, die Vorhersagen innerhalb eines kontrollierten Wertebereichs zu halten, was besonders bei der Arbeit mit normalisierten Daten wichtig ist.

//--- layer 18
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = 1;
   descr.window = prev_out;
   descr.step = prev_out;
   prev_out = descr.window_out = NForecast;
   descr.layers = prev_count;
   descr.activation = TANH;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Diese Architektur ist einfach, aber effektiv. Sie bietet ausreichende Flexibilität für die Erstellung genauer Prognosen bei gleichzeitiger Wahrung der Unabhängigkeit einzelner Merkmalsvorhersagen – eine entscheidende Eigenschaft bei der Modellierung von Finanzzeitreihen, bei denen jede Variable einzigartige Informationen enthalten kann.

In der letzten Phase des Encoders werden die vorhergesagten Werte zurück in den ursprünglichen Datenraum transformiert, wodurch die Modellausgaben interpretiert werden können.

Die erste Operation ist eine Tensor-Transponierung. Sie stellt die zeitliche Dimension in ihrer ursprünglichen Ausrichtung wieder her und ordnet die Prognosewerte wieder den entsprechenden Zeitschritten zu.

//--- layer 19
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronTransposeOCL;
   descr.count = prev_count;
   prev_count=descr.window = prev_out;
   prev_out=descr.count;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Als Nächstes projiziert eine Faltungsschicht die Merkmalsdimensionalität zurück in das Format der ursprünglichen Eingabedaten. Im Wesentlichen handelt es sich hierbei um eine Projektion: Das Modell komprimiert oder erweitert die latente Repräsentation, sodass sie mit dem Raum kompatibel wird, in dem die ursprünglichen Daten gebildet wurden.

//--- layer 20
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = prev_count;
   descr.window = prev_out;
   descr.step = prev_out;
   prev_out = descr.window_out = BarDescr;
   descr.layers = 1;
   descr.activation = TANH;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Schließlich wandelt die inverse Normalisierung die Vorhersagen von der internen normalisierten Repräsentation des Modells zurück in reale Werte um, die von Benutzern interpretiert und direkt bei Handelsentscheidungen eingesetzt werden können.

//--- layer 21
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronRevInDenormOCL;
   descr.count = prev_count * prev_out;
   descr.layers = 1;
   descr.activation = None;
   if(!encoder.Add(descr))
     {
      delete descr;
      return false;
     }

Zusammen überbrücken diese Operationen die Lücke zwischen der internen Repräsentation des Modells und dem realen Markt, indem sie abstrakte latente Vorhersagen in umsetzbare Prognosen umwandeln.

Die vollständigen Architekturen aller trainierbaren Modelle sind in den Anhängen bereitgestellt.



Tests

Ein erheblicher Arbeitsaufwand wurde der Anpassung und Implementierung des ACEFormer-Frameworks in der MQL5-Umgebung gewidmet. Die Kernkomponenten des Frameworks wurden nun in die Architektur der trainierbaren Modelle integriert. Der nächste Schritt besteht darin, die resultierenden Modelle anhand realer historischer Daten zu bewerten.

Der Trainingsdatensatz wurde durch Ausführen des MetaTrader 5 Strategy Tester mit zufälligen Agentenstrategien auf einminütigen EURUSD-Preisdaten für das gesamte Jahr 2024 generiert. Dieser Ansatz setzt die Modelle einer breiten Palette von Marktszenarien aus und fördert die Generalisierbarkeit des gelernten Verhaltens.

Das Training wurde in zwei Phasen durchgeführt. Die erste Phase ist ein Offline-Training ohne Aktualisierung des Datensatzes, bis sich die Optimierungsfehler stabilisiert hatten. Dazu haben wir den Expert Advisor Study.mq5 auf dem Chart ausgeführt. Die zweite Phase nutzte ein Online-Training im Strategietester unter Verwendung des Expert Advisors StudyOnline.mq5, wodurch die Modelle unter Bedingungen feinabgestimmt werden konnten, die dem realen Handel sehr nahe kommen.

Um die Ergebnisse objektiv zu bewerten, wurden die trainierten Modelle an historischen Out-of-Sample-Daten von Januar bis März 2025 getestet. Dies eliminiert Overfitting und unterstreicht den praktischen Wert der erzielten Ergebnisse.

Alle anderen Umgebungsparameter und technischen Indikatoren blieben während des gesamten Trainings und Tests unverändert, wodurch sichergestellt wurde, dass die beobachteten Ergebnisse die Qualität der erlernten Strategie widerspiegeln.

Die Testergebnisse sind unten dargestellt.

Insgesamt war das Modell über den Testzeitraum profitabel und führte 13 Trades aus. Etwas mehr als die Hälfte dieser Trades war profitabel. Es ist jedoch anzumerken, dass 13 Trades über einen dreimonatigen Bewertungszeitraum eine relativ geringe Handelsaktivität darstellen.

Eine mögliche Erklärung ist die Art des probabilistischen Attention-Mechanismus selbst. Durch die Auswahl nur der informativsten Merkmale und Queries erzielt das Modell eine bessere Generalisierung, könnte aber weniger empfindlich gegenüber schwächeren Handelssignalen werden.

Ein weiterer beitragender Faktor könnte die begrenzte Repräsentativität des Trainingsdatensatzes sein. Das Modell ist während des Trainings möglicherweise einfach nicht auf eine ausreichend vielfältige Reihe von Marktbedingungen gestoßen, um in ähnlichen Situationen während des Bewertungszeitraums sicher zu agieren. Die Erweiterung sowohl des Umfangs als auch der Vielfalt der Trainingsdaten könnte die Verhaltensflexibilität des Agenten verbessern.


Schlussfolgerung

In diesem Artikel haben wir das ACEFormer-Framework untersucht, das einen effektiven Ansatz zur Extraktion informativer Merkmale aus Zeitreihen und zur Konstruktion kompakter latenter Repräsentationen bietet. Seine Architektur kombiniert probabilistische Attention, Merkmalsdestillation und tiefe Sequenztransformation, wodurch es besonders gut für die Analyse verrauschter und hochvolatiler Finanzdaten geeignet ist.

Aus praktischer Sicht haben wir unsere eigene Implementierung der wichtigsten ACEFormer-Komponenten unter Verwendung von MQL5 vorgestellt und sie in eine trainierbare Actor–Director–Critic-Architektur integriert. Besonderes Augenmerk wurde auf das Design des Environment State Encoder gelegt, der die vom Framework vorgeschlagenen Merkmalsverarbeitungstechniken implementiert.

Die experimentellen Ergebnisse waren ermutigend: Das Modell erzielte eine positive Rendite im Out-of-Sample-Testzeitraum, was die allgemeine Tragfähigkeit der vorgeschlagenen Architektur bestätigt. Gleichzeitig zeigten die Experimente eine relativ geringe Handelsaktivität, was auf die Merkmale der probabilistischen Attention und die begrenzte Vielfalt des Trainingsdatensatzes zurückzuführen sein könnte. Diese Beobachtungen geben eine klare Richtung für zukünftige Forschung vor.


Referenzen


In diesem Artikel verwendete Programme

#NameTypBeschreibung
1Research.mq5Expert AdvisorExpert Advisor für die Datenerfassung
2ResearchRealORL.mq5
Expert Advisor
Expert Advisor für die Datenerfassung mit der Methode Real-ORL
3Study.mq5Expert AdvisorExpert Advisor für das Offline-Modelltraining
4StudyOnline.mq5
Expert Advisor
Expert Advisor für das Online-Modelltraining
5Test.mq5Expert AdvisorExpert Advisor für Modelltests
6Trajectory.mqhKlassenbibliothekStruktur der Beschreibung des Systemzustands und der Modellarchitektur
7NeuroNet.mqhKlassenbibliothekEine Bibliothek von Klassen zur Erstellung neuronaler Netze
8NeuroNet.clCodebibliothekOpenCL-Programmcode

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18041

Beigefügte Dateien |
MQL5.zip (2720.51 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Aleksander
Aleksander | 8 Mai 2025 in 11:26
Beziehen sich die 13 Trades auf jeweils ein Währungspaar? Wenn man 10 Paare in die Analyse einbezieht, wie viele Trades ergibt das dann?
Von der Grundstufe bis zur Mittelstufe: Objektereignisse (IV) Von der Grundstufe bis zur Mittelstufe: Objektereignisse (IV)
In diesem Artikel werden wir das vollenden, was im vorigen begonnen wurde: eine vollständig interaktive Möglichkeit, Objekte direkt im Chart in ihrer Größe zu verändern. Obwohl viele meinen, dass die Erstellung von so etwas wesentlich tiefere MQL5-Kenntnisse erfordern würde, werden Sie sehen, dass wir mit einfachen Konzepten und grundlegendem Wissen eine Möglichkeit implementieren können, direkt im Chart mit Objekten zu arbeiten. Dies führt zu einem sehr interessanten und durchaus überzeugenden Ergebnis.
Von der Grundstufe bis zur Mittelstufe: Objektereignisse (III) Von der Grundstufe bis zur Mittelstufe: Objektereignisse (III)
In diesem Artikel schaffen wir die Grundlage für die Themen der nächsten Veröffentlichung. Wir werden uns auch ansehen, wie sich ein OBJ_LABEL-Objekt zum Bearbeiten und Verschieben vollständig interaktiv machen lässt. Mit anderen Worten: Wir können sowohl den Text als auch die Position des OBJ_LABEL-Objekts ändern, ohne das Dialogfenster „Objekteigenschaften“ zu öffnen.
Neuronale Netze im Trading: Verallgemeinerung von Zeitreihen ohne datenspezifische Abhängigkeit (Mamba4Cast) Neuronale Netze im Trading: Verallgemeinerung von Zeitreihen ohne datenspezifische Abhängigkeit (Mamba4Cast)
In diesem Artikel stellen wir das Mamba4Cast-Framework vor und betrachten eine seiner Schlüsselkomponenten genauer: die zeitstempelbasierte Positionskodierung. Der Artikel zeigt, wie das Time-Embedding unter Berücksichtigung der Kalenderstruktur der Daten gebildet wird.
Neuronale Netze im Trading: Zeitreihenprognose mittels adaptiver Modenzerlegung (ACEFormer) Neuronale Netze im Trading: Zeitreihenprognose mittels adaptiver Modenzerlegung (ACEFormer)
Wir laden Sie ein, die ACEFormer-Architektur zu erkunden – eine moderne Lösung, die die Effektivität probabilistischer Aufmerksamkeit mit adaptiver Zeitreihenzerlegung kombiniert. Dieser Artikel richtet sich an alle, die ein Gleichgewicht zwischen Berechnungseffizienz und Prognosegenauigkeit auf den Finanzmärkten suchen.