English Русский 中文 Español 日本語 Português
preview
Bewältigung der Herausforderungen bei der ONNX-Integration

Bewältigung der Herausforderungen bei der ONNX-Integration

MetaTrader 5Handelssysteme | 4 Juni 2024, 12:33
155 0
Omega J Msigwa
Omega J Msigwa

Einführung

ONNX (Open Neural Network Exchange) revolutioniert die Art und Weise, wie wir anspruchsvolle KI-basierte mql5-Programme erstellen. Diese neue Technologie von MetaTrader 5 ist der Weg nach vorne zu einem maschinellem Lernen. Es verspricht viel. Jedoch kommt ONNX mit ein paar Herausforderungen, die einem Kopfschmerzen bereiten werden, wenn man keine Ahnung hat, wie sie zu lösen sind.

Wenn Sie eine einfache KI-Technik wie ein neuronales Feedforward-Netz einsetzen, ist der Einsatzprozess vielleicht nicht so problematisch, aber da die meisten realen Projekte viel komplexer sind, müssen Sie viele Dinge tun, wie z. B. Zeitreihendaten extrahieren, die großen Daten vorverarbeiten und umwandeln, um ihre Dimensionen zu reduzieren, ganz zu schweigen davon, dass Sie mehrere Modelle in einem großen Projekt verwenden müssen, wenn Sie ONNX-Modelle einsetzen - in solchen Situationen kann der Einsatz von ONNX kompliziert werden.

ONNX ist ein autarkes Tool, das lediglich die Möglichkeit bietet, ein KI-Modell zu speichern. Es wird nicht alles mitgeliefert, was notwendig ist, um die trainierten Modelle auf der anderen Seite laufen zu lassen. Es liegt an Ihnen, herauszufinden, wie Sie Ihre endgültigen ONNX-Modelle einsetzen wollen. In diesem Artikel werden wir die drei Herausforderungen erörtern, nämlich die Skalierung und Normalisierung der Daten, die Einführung der Dimensionenreduzierung in das Modell und die Überwindung der Herausforderung, ONNX-Modelle für Zeitreihenvorhersagen einzusetzen.

onnx modelle mql5

Dieser Artikel setzt voraus, dass Sie ein grundlegendes Verständnis von maschinellem Lernen und KI-Theorie haben und dass Sie zumindest ein- oder zweimal versucht haben, ONNX-Modelle in mql5 zu verwenden.


Bewältigung der Herausforderungen bei der Datenvorverarbeitung

Im Kontext des maschinellen Lernens bezieht sich die Datenverarbeitung auf den Prozess der Umwandlung der Werte von Merkmalen in Ihrem Datensatz in einen bestimmten Bereich. Diese Transformation zielt darauf ab, eine konsistentere Darstellung der Daten für Ihr maschinelles Lernmodell zu erreichen. Der Skalierungsprozess ist aus mehreren Gründen sehr wichtig;

Es verbessert die Leistung von Modellen für maschinelles Lernen: Viele Algorithmen des maschinellen Lernens, insbesondere abstandsbasierte Algorithmen wie K-Nearest Neighbors (KNN) und Support Vector Machines (SVM), basieren auf der Berechnung von Abständen zwischen Datenpunkten. Wenn Merkmale sehr unterschiedliche Maßstäbe haben (z. B. ein Merkmal in Tausendsteln, ein anderes in Zehnteln), dominieren die Merkmale mit den größeren Maßstäben die Abstandsberechnungen, was zu einer suboptimalen Leistung führt. Durch die Skalierung werden alle Merkmale in einen ähnlichen Bereich gebracht, sodass sich das Modell auf die tatsächlichen Beziehungen zwischen den Datenpunkten konzentrieren kann.

Schnellere Konvergenz der Ausbildung: Auf Gradientenabstieg basierende Optimierungsalgorithmen, die häufig in neuronalen Netzen und anderen Modellen eingesetzt werden, nähern sich der optimalen Lösung schrittweise auf der Grundlage der Gradienten der Verlustfunktion. Wenn Merkmale unterschiedliche Maßstäbe haben, können die Gradienten auch sehr unterschiedliche Größenordnungen haben, was es dem Optimierer erschwert, das Minimum effizient zu finden. Durch die Skalierung erhalten die Gradienten einen gleichmäßigeren Bereich, was zu einer schnelleren Konvergenz führt.

Das gewährleistet die Stabilität der numerischen Operationen: Einige Algorithmen des maschinellen Lernens beinhalten Berechnungen, die bei Merkmalen mit sehr unterschiedlichen Größenordnungen instabil werden können. Die Skalierung hilft, diese numerischen Probleme zu vermeiden und stellt sicher, dass das Modell die Berechnungen genau durchführt.


Gemeinsame Skalierungstechniken:

  • Normalisierung (Min-Max-Skalierung): Bei dieser Technik werden die Merkmale in einem bestimmten Bereich skaliert (oft 0 bis 1 oder -1 bis 1).
  • Standardisierung (Z-Score-Normalisierung): Bei dieser Technik werden die Daten zentriert, indem der Mittelwert von jedem Merkmal abgezogen und dann durch die Standardabweichung dividiert wird.

So wichtig dieser Normalisierungsprozess auch ist, es gibt nicht viele Quellen im Internet, die erklären, wie man ihn richtig durchführt. Dieselbe Skalierungstechnik und dieselben Parameter, die für die Trainingsdaten verwendet wurden, müssen auch für die Testdaten und den Einsatz des Modells verwendet werden.

Mit der gleichen Analogie der Skalierung: Stellen Sie sich vor, Sie haben ein Merkmal, das „Einkommen“ in Ihren Trainingsdaten darstellt. Der Skalierer lernt die minimalen und maximalen Einkommenswerte (oder Mittelwert und Standardabweichung für die Standardisierung) während des Trainings. Wenn Sie einen anderen Skalierer für die Testdaten verwenden, kann es sein, dass er auf Einkommenswerte stößt, die außerhalb des Bereichs liegen, den er beim Training gesehen hat. Dies kann zu unerwarteten Skalierungen führen und Inkonsistenzen zwischen Trainings- und Testdaten hervorrufen.

Verwendung der gleichen Parameter für die Analogie des Skalierers: Stellen Sie sich ein Lineal vor, mit dem Sie die Höhe messen. Wenn Sie für Training und Test ein anderes Lineal mit unterschiedlichen Einheiten (Zoll oder Zentimeter) verwenden, sind Ihre Messungen nicht vergleichbar. In ähnlicher Weise stört die Verwendung unterschiedlicher Skalierer für Trainings- und Testdaten den Bezugsrahmen, den das Modell während des Trainings gelernt hat.

Im Wesentlichen wird durch die Verwendung desselben Skalierers sichergestellt, dass das Modell sowohl beim Training als auch beim Testen konsistent Daten sieht, was zu zuverlässigeren und besser interpretierbaren Ergebnissen führt.

Sie können die Skalierungstechniken des Python-Moduls Scikit-learn.preprocessing verwenden. Solange Sie das ONNX-Modell in derselben Python-Sprache erstellen und bereitstellen, ist alles in Ordnung.
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import numpy as np

# Example data
data = np.array([[1000, 2], [500, 1], [2000, 4], [800, 3]])

# Create a MinMaxScaler object
scaler_minmax = MinMaxScaler()

# Fit the scaler on the training data (learn min/max values)
scaler_minmax.fit(data)

# Transform the data using the fitted scaler
data_scaled_minmax = scaler_minmax.transform(data)

print("Original data:\n", data)
print("\nMin Max Scaled data:\n", data_scaled_minmax)

Schwierig wird es jedoch, wenn Sie das trainierte Modell in der Sprache MQL5 verwenden wollen. Obwohl es verschiedene Möglichkeiten in Python gibt, den Skalierer zu speichern, ist es eine Herausforderung sein, ihn im Meta-Editor zu extrahieren, da Python seine eigene Art hat, Objekte zu speichern und den Prozess einfacher gestaltet als andere Programmiersprachen. Am besten wäre es, die Daten in MQL5 vorzuverarbeiten, den Skalierer zu speichern und die skalierten Daten in einer CSV-Datei zu speichern, die wir mit Python-Code lesen werden.

Nachfolgend finden Sie einen Plan für die Vorverarbeitung der Daten:

  1. Sammle die Daten vom Markt und skalieren sie
  2. Speichere den Skalierer 
  3. Speicher die skalierten Daten in einer CSV-Datei


01: Sammle die Daten vom Markt und skalieren sie

Wir werden die Eröffnungs-, Höchst-, Tiefst- und Schlusskurse für 1000 Bars eines täglichen Zeitrahmens sammeln und dann ein Mustererkennungsproblem schaffen, indem wir das Aufwärts-Muster immer dann zuordnen, wenn der Preis über dem Eröffnungswert geschlossen hat, andernfalls ist es ein Abwärts-Muster. Indem wir das LSTM-KI-Modell auf diese Muster trainieren, versuchen wir, es dazu zu bringen, zu verstehen, was zu diesen Mustern beiträgt, sodass es, sobald es gut trainiert ist, in der Lage sein kann, uns das Handelssignal zu liefern.

Innerhalb des ONNX-Skripts Daten sammeln:

Wir beginnen mit den Bibliotheken, die wir brauchen:

#include <MALE5\preprocessing.mqh> //This library contains the normalization techniques for machine learning
#include <MALE5\MatrixExtend.mqh>

StandardizationScaler scaler; //We want to use z-normalization/standardization technique for this project

Dann müssen wir die Preisinformationen sammeln.

input int data_size = 10000; //number of bars to collect for our dataset

MqlRates rates[];
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- vector.CopyRates is lacking we are going to copy rates in normal way

   ArraySetAsSeries(rates, true);    
   if (CopyRates(Symbol(), PERIOD_D1, 1, data_size, rates)<-1)
     {
       printf("Failed to collect data Err=%d",GetLastError());
       return;
     }
   

   matrix OHLC(data_size, 4);
   for (int i=0; i<data_size; i++) //Get OHLC values and save them to a matrix
     {
       OHLC[i][0] = rates[i].open;
       OHLC[i][1] = rates[i].high;
       OHLC[i][2] = rates[i].low;
       
        if (rates[i].close>rates[i].open)
          OHLC[i][3] = 1; //Buy signal
        else if (rates[i].close<rates[i].open)
          OHLC[i][3] = 0; //sell signal
     }

//---
  }

Erinnern Sie sich! Die Skalierung gilt für die unabhängigen Variablen, weshalb wir die Datenmatrix in die x- und y-Matrix bzw. den Vektor aufteilen, um die x-Matrix zu erhalten, mit der wir die Spalten skalieren können.

   matrix x;
   vector y;
   MatrixExtend::XandYSplitMatrices(OHLC, x, y);  //WE split the data into x and y | The last column in the matrix will be assigned to the y vector 
     
//--- Standardize the data
   
   x = scaler.fit_transform(x);   


02: Sichern des Skalierers

Wie bereits erwähnt, müssen wir den Skalierer zur späteren Verwendung speichern.

   if (!scaler.save(Symbol()+"-SCALER"))
      return;

Nachdem dieses Codefragment ausgeführt wurde, wird ein Ordner mit Binärdateien erstellt. Diese beiden Dateien enthalten die Parameter für den Standardisierungs-Skalierer. Wir werden später sehen, wie Sie diese Parameter verwenden können, um die gespeicherte Skalierungsinstanz zu laden.

eurusd scaler


03: Speichern der skalierten Daten in einer CSV-Datei

Zu guter Letzt müssen wir die skalierten Daten in einer CSV-Datei speichern, die wir später im Python-Code verwenden können.
   OHLC = MatrixExtend::concatenate(x, y); //We apped the y column to the scaled x matrix, this is the opposite of XandYsplitMatrices function
   if (!MatrixExtend::WriteCsv(Symbol()+"-OHLSignal.csv",OHLC,"open,high,low,signal",false,8))       
    {
     DebugBreak();
     return;
    }

Das Ergebnis:



Überwindung von Herausforderungen bei Zeitreihendaten

Es gibt einige Studien, die darauf hindeuten, dass Deep-Learning-Modelle wie GRU, LSTM und RNN aufgrund ihrer Fähigkeit, Muster über einen bestimmten Zeitraum hinweg zu verstehen, bessere Vorhersagen für den Aktienmarkt treffen können als andere Modelle. Die meisten algorithmischen Händler in der Data-Science-Community sind auf diese speziellen Modelle eingestellt, so auch ich.

Es stellt sich heraus, dass Sie einige zusätzliche Codezeilen schreiben müssen, um die Daten so vorzubereiten, dass sie für Zeitreihenvorhersagen mit diesen Modellen geeignet sind.

Wenn Sie schon einmal mit Zeitreihenmodellen gearbeitet haben, haben Sie wahrscheinlich eine ähnliche Funktion oder einen ähnlichen Code wie diesen gesehen:

def get_sequential_data(data, time_step):
    if dataset.empty is True:
      print("Failed to create sequences from an empty dataset")
      return

    Y = data.iloc[:, -1].to_numpy() # get the last column from the dataset and assign it to y numpy 1D array
    X = data.iloc[:, :-1].to_numpy() # Get all the columns from data array except the last column, assign them to x numpy 2D array

    X_reshaped = []
    Y_reshaped = []

    for i in range(len(Y) - time_step + 1):
        X_reshaped.append(X[i:i + time_step])
        Y_reshaped.append(Y[i + time_step - 1])

    return np.array(X_reshaped), np.array(Y_reshaped)

Diese Funktion ist für Zeitreihenmodelle wie das LSTM sehr wichtig, da sie die Datenvorbereitung übernimmt:

  • Aufteilung der Daten in Sequenzen einer festen Größe (time_step).
  • Trennung von Merkmalen (Informationen aus der Vergangenheit) und Zielen (vorhergesagter Wert).
  • Umformung der Daten in ein für LSTM-Modelle geeignetes Format.

Diese Datenaufbereitung hilft dabei, dem LSTM-Modell die wichtigsten Informationen strukturiert zur Verfügung zu stellen, was zu einem schnelleren Training, einer besseren Speicherverwaltung und einer potenziell höheren Vorhersagegenauigkeit führt.

Während LSTMs Sequenzen verarbeiten können, handelt es sich bei Echtzeitdaten um einen kontinuierlichen Informationsstrom. Sie müssen immer noch ein Zeitfenster mit Daten aus der Vergangenheit festlegen, die das Modell bei seinen Vorhersagen berücksichtigen soll. Daher ist diese Funktion nicht nur für das Training und die Tests, sondern auch für Echtzeit-Vorhersagen erforderlich. Die y-Felder brauchen wir nicht, aber wir brauchen den Code für die Umformung des x-Feldes. Wir werden in MetaTrader 5 Vorhersagen in Echtzeit machen, richtig? Wir müssen eine ähnliche Funktion wie diese in mql5 erstellen. 

Zuvor wollen wir die Dimensionen der Numpy-Arrays x und y überprüfen, die von der Funktion get_sequential_data zurückgegeben werden, wenn der Zeitschrittwert 7 beträgt.

X_reshaped, Y_reshaped = get_sequential_data(dataset, step_size)

print(f"x_shape{X_reshaped.shape} y_shape{Y_reshaped.shape}")

Ausgaben:

x_shape(9994, 7, 3) y_shape(9994,)

Das zurückgegebene x-Array ist ein 3D-Array, anders gesagt, ein Tensor, während die zurückgegebenen y-Daten eine 1 dim. Matrix, d.h. ein Vektor sind. Dies müssen wir bei der Erstellung einer ähnlichen Funktion in mql5 berücksichtigen.

Lassen Sie uns nun eine einfache Klasse mit dem Namen CTSDataProcessor erstellen:

class CTSDataProcessor 
  {
CTensors *tensor_memory[]; //Tensor objects may be hard to track in memory once we return them from a function, this keeps track of them
bool xandysplit;

public:
                     CTSDataProcessor (void);
                    ~CTSDataProcessor (void);
                    
                     CTensors *extract_timeseries_data(const matrix<double> &x, const int time_step); //for real time predictions
                     CTensors *extract_timeseries_data(const matrix<double> &MATRIX, vector &y, const int time_step); //for training and testing purposes 
  };

Die beiden Funktionen mit ähnlichem Namen extract_timeseries_data leisten ähnliche Arbeit, mit der Ausnahme, dass die eine den y-Vektor nicht zurückgibt; er wird für Echtzeitvorhersagen verwendet.

CTSDataProcessor ::CTSDataProcessor (void)
 {
   xandysplit = true; //by default obtain the y vector also
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTensors *CTSDataProcessor ::extract_timeseries_data(const matrix<double> &x,const int time_step)
 {
  CTensors *timeseries_tensor;
  timeseries_tensor = new CTensors(0);
  ArrayResize(tensor_memory, 1);
  tensor_memory[0] = timeseries_tensor;
  
  xandysplit = false; //In this function we do not obtain the y vector
  
  vector y;
  timeseries_tensor = extract_timeseries_data(x, y, time_step);
  
  xandysplit = true; //restore the original condition
   
  return timeseries_tensor;
 }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CTensors *CTSDataProcessor ::extract_timeseries_data(const matrix &MATRIX, vector &y,const int time_step)
 {
  CTensors *timeseries_tensor;
  timeseries_tensor = new CTensors(0);
  ArrayResize(tensor_memory, 1);
  tensor_memory[0] = timeseries_tensor;
  
  matrix<double> time_series_data = {};
  matrix x = {}; //store the x variables converted to timeseries
  vector y_original = {};
  y.Init(0);
  
  if (xandysplit) //if we are required to obtain the y vector also split the given matrix into x and y
     if (!MatrixExtend::XandYSplitMatrices(MATRIX, x, y_original))
       {
         printf("%s failed to split the x and y matrices in order to make a tensor",__FUNCTION__);
         return timeseries_tensor;
       }
  
  x = xandysplit ? x : MATRIX; 
  
  for (ulong sample=0; sample<x.Rows(); sample++) //Go throught all the samples
    {
      matrix<double> time_series_matrix = {};
      vector<double> timeseries_y(1);
      
      for (ulong time_step_index=0; time_step_index<(ulong)time_step; time_step_index++)
        {
            if (sample + time_step_index >= x.Rows())
                break;
             
             time_series_matrix = MatrixExtend::concatenate(time_series_matrix, x.Row(sample+time_step_index), 0);
             
             if (xandysplit)
               timeseries_y[0] = y_original[sample+time_step_index]; //The last value in the column is assumed to be a y value so it gets added to the y vector
        }
      
      if (time_series_matrix.Rows()<(ulong)time_step)
        continue;
        
        timeseries_tensor.Append(time_series_matrix);
         
        if (xandysplit)
         y = MatrixExtend::concatenate(y, timeseries_y);
    }
   
   return timeseries_tensor;
 }

Lassen Sie uns nun in einem Expert Advisor namens ONNX challenges EA versuchen, diese Funktionen zu verwenden, um die Zeitreihendaten zu extrahieren:

#include <Timeseries Deep Learning\tsdataprocessor.mqh>

input int time_step_ = 7;
//it is very important the time step value matches the one used during training in  a python script


CTSDataProcessor ts_dataprocessor;
CTensors *ts_data_tensor;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (!onnx.Init(lstm_model))
     return INIT_FAILED;
     
   string headers;  
   matrix data = MatrixExtend::ReadCsv("EURUSD-OHLSignal.csv",headers); //let us open the same data so that we don't get confused along the way
   
   matrix x;
   vector y;
      
   ts_data_tensor = ts_dataprocessor.extract_timeseries_data(data, y, time_step_);
   
   printf("x_shape %s y_shape (%d,)",ts_data_tensor.shape(),y.Size());
 }

Ausgaben:

GD      0       07:21:14.710    ONNX challenges EA (EURUSD,H1)  Warning: CTensors::shape assumes all matrices in the tensor have the same size
IG      0       07:21:14.710    ONNX challenges EA (EURUSD,H1)  x_shape (9994, 7, 3) y_shape (9994,)

Großartig, wir haben die gleichen Abmessungen wie im Python-Code,

Der Zweck von ONNX ist es, ein maschinelles Lernmodell, das in einer Sprache erstellt wurde, so zu gestalten, dass es gut funktioniert, wenn nicht sogar in der anderen Sprache. Das bedeutet, wenn ich ein Modell in Python erstelle und es dort laufen lasse, sollte die Genauigkeit und Präzision, die es mir liefert, fast dieselbe sein, die in einer anderen Sprache, in diesem Fall in MQL5, geliefert wird, wenn dieselben Daten ohne Konvertierung verwendet wurden.

Wenn dies der Fall ist, müssen Sie, bevor Sie das ONNX-Modell in MQL5 verwenden, überprüfen, ob Sie alles richtig gemacht haben, indem Sie das Modell mit denselben Daten auf beiden Plattformen testen, um zu sehen, ob es dieselbe Genauigkeit bietet. Lassen Sie uns dieses Modell testen.

Ich habe das LSTM-Modell mit 10 Neuronen in der Eingabeschicht und der einzigen versteckten Schicht im Netzwerk erstellt und den Adam-Optimierer für den Lernfortschritt eingesetzt.

from keras.optimizers import Adam
from keras.callbacks import EarlyStopping

learning_rate = 1e-3
patience = 5 #if this number of epochs validation loss is unchanged stop the process


model = Sequential()

model.add(LSTM(units=10, input_shape=(step_size, dataset.shape[1]-1))) #Input layer
model.add(Dense(units=10, activation='relu', kernel_initializer='he_uniform'))
model.add(Dropout(0.3))
model.add(Dense(units=len(classes_in_data), activation = 'softmax')) #last layer outputs = classes in data

model.compile(optimizer=Adam(learning_rate=learning_rate), loss="binary_crossentropy", metrics=['accuracy'])

Ausgaben:

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm (LSTM)                 (None, 10)                560       
                                                                 
 dense (Dense)               (None, 10)                110       
                                                                 
 dropout (Dropout)           (None, 10)                0         
                                                                 
 dense_1 (Dense)             (None, 2)                 22        
                                                                 
=================================================================
Total params: 692 (2.70 KB)
Trainable params: 692 (2.70 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________

Ich habe das Modell für 100 Epochen trainiert, wobei die Geduld auf 5 Epochen und batch_size = 64 gesetzt wurde.

from keras.utils import to_categorical

y_train = to_categorical(y_train, num_classes=len(classes_in_data)) #ONE-HOT encoding
y_test = to_categorical(y_test, num_classes=len(classes_in_data)) #ONE-HOT encoding

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

history = model.fit(x_train, y_train, epochs = 100 , validation_data = (x_test,y_test), callbacks=[early_stopping], batch_size=64, verbose=2)

Das LSTM-Modell konvergierte bei der 77. Epoche mit dem Verlust = 0,3000 und einer Genauigkeit von: 0.8876.

Epoch 75/100
110/110 - 1s - loss: 0.3076 - accuracy: 0.8856 - val_loss: 0.2702 - val_accuracy: 0.8983 - 628ms/epoch - 6ms/step
Epoch 76/100
110/110 - 1s - loss: 0.2968 - accuracy: 0.8856 - val_loss: 0.2611 - val_accuracy: 0.9060 - 651ms/epoch - 6ms/step
Epoch 77/100
110/110 - 1s - loss: 0.3000 - accuracy: 0.8876 - val_loss: 0.2634 - val_accuracy: 0.9063 - 714ms/epoch - 6ms/step

loss vs iterations graph


Schließlich habe ich das Modell mit dem gesamten Datensatz getestet;

X_reshaped, Y_reshaped = get_sequential_data(dataset, step_size)

predictions = model.predict(X_reshaped)

predictions = classes_in_data[np.argmax(predictions, axis=1)]  # Find class with highest probability | converting predicted probabilities to classes

from sklearn.metrics import accuracy_score

print("LSTM model accuracy: ", accuracy_score(Y_reshaped, predictions))

Das Ergebnis war wie folgt:

313/313 [==============================] - 2s 3ms/step

LSTM model accuracy:  0.9179507704622774

Wir müssen diesen Genauigkeitswert oder etwas in der Nähe davon erwarten, wenn wir dieses LSTM-Modell verwenden, das

in MQL5 in ONNX gespeichert wurde. inp_model_name war model.eurusd.D1.onnx.

output_path = inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

Lassen Sie uns dieses Modell in unseren Expert Advisor einfügen.

#include <Timeseries Deep Learning\onnx.mqh>
#include <Timeseries Deep Learning\tsdataprocessor.mqh>
#include <MALE5\metrics.mqh>

#resource "\\Files\\model.eurusd.D1.onnx" as uchar lstm_model[]

input int time_step_ = 7;
//it is very important the time step value matches the one used during training in  a python script

CONNX onnx;
CTSDataProcessor ts_dataprocessor;
CTensors *ts_data_tensor;

In der Bibliothek onnx.mqh gibt es nichts anderes als eine ONNX-Klasse, die das ONNX-Modell initialisiert und Funktionen zur Erstellung von Vorhersagen enthält.

class CONNX
  {
protected:

   bool initialized;
   long onnx_handle;
   void PrintTypeInfo(const long num,const string layer,const OnnxTypeInfo& type_info);
   long inputs[], outputs[];
   
   void replace(long &arr[]) { for (uint i=0; i<arr.Size(); i++) if (arr[i] <= -1) arr[i] = UNDEFINED_REPLACE; }
   string ConvertTime(double seconds);
   
public:
                     CONNX(void);
                    ~CONNX(void);
                     
                     bool Init(const uchar &onnx_buff[], ulong flags=ONNX_DEFAULT); //Initislized ONNX model from a resource uchar array with default flag
                     bool Init(string onnx_filename, uint flags=ONNX_DEFAULT); //Initializes the ONNX model from a .onnx filename given

                     virtual int predict_bin(const matrix &x, const vector &classes_in_data); //Returns the predictions for the current given matrix, this function is for real-time prediction
                     virtual vector predict_bin(CTensors &timeseries_tensor, const vector &classes_in_data); //gives out the vector for all the predictions | useful function for testing only
                     virtual vector predict_proba(const matrix &x); //Gives out the predictions for the current given matrix | this function is for realtime predictions
  };

Schließlich habe ich ein geladenes LSTM-Modell in ONNX challenges EA ausgeführt:

int OnInit()
  {
   if (!onnx.Init(lstm_model))
     return INIT_FAILED;
     
   string headers;  
   matrix data = MatrixExtend::ReadCsv("EURUSD-OHLSignal.csv",headers); //let us open the same data so that we don't get confused along the way
   
   matrix x;
   vector y;
      
   ts_data_tensor = ts_dataprocessor.extract_timeseries_data(data, y, time_step_);
      
   vector classes_in_data = MatrixExtend::Unique(y); //Get the classes in the data
      
   vector preds = onnx.predict_bin(ts_data_tensor, classes_in_data);
   
   Print("LSTM Model Accuracy: ",Metrics::accuracy_score(y, preds));
   
//---
   return(INIT_SUCCEEDED);
  }

Das Ergebnis ist nachstehend aufgeführt:

2024.04.14 07:44:16.667 ONNX challenges EA (EURUSD,H1)  LSTM Model Accuracy: 0.9179507704622774

Großartig! Wir haben den gleichen Genauigkeitswert wie im Python-Code mit einer Genauigkeit von signifikanten Zahlen. Das zeigt uns, dass wir alles richtig gemacht haben.

Nun wollen wir dieses Modell nutzen, um Vorhersagen in Echtzeit zu treffen, bevor wir fortfahren können:

Innerhalb von ONNX fordert REALTIME EA heraus.

Da wir Vorhersagen auf Echtzeit-Datensätzen machen werden, im Gegensatz zu dem, was wir vorher gemacht haben, wo wir die CSV-Datei mit normalisierten Daten zum Testen verwendet haben, müssen wir dieses Mal den Skalierer laden, den wir einmal gespeichert haben, und ihn jedes Mal auf die neuen Daten anwenden, bevor wir die Daten in unser LSTM-Modell im ONNX-Format eingeben.

#resource "\\Files\\model.eurusd.D1.onnx" as uchar lstm_model[]
#resource "\\Files\\EURUSD-SCALER\\mean.bin" as double standardization_scaler_mean[];
#resource "\\Files\\EURUSD-SCALER\\std.bin" as double standardization_scaler_std[];

Gleich nach dem Laden des ONNX-Modells als Ressource müssen wir die von uns gespeicherten Binärdateien mean und std einfügen.

Dieses Mal rufen wir den Standardisierungs-Skalierer mit einem Zeiger auf, da wir ihn mit den gespeicherten Skalierungswerten instanziieren werden.

#include <Timeseries Deep Learning\onnx.mqh>
#include <Timeseries Deep Learning\tsdataprocessor.mqh>
#include <MALE5\preprocessing.mqh>

#resource "\\Files\\model.eurusd.D1.onnx" as uchar lstm_model[]
#resource "\\Files\\EURUSD-SCALER\\mean.bin" as double standardization_scaler_mean[];
#resource "\\Files\\EURUSD-SCALER\\std.bin" as double standardization_scaler_std[];

input int time_step_ = 7;
//it is very important the time step value matches the one used during training in  a python script

CONNX onnx;
StandardizationScaler *scaler;
CTSDataProcessor ts_dataprocessor;
CTensors *ts_data_tensor;

MqlRates rates[];
vector classes_ = {0,1};
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (!onnx.Init(lstm_model))
     return INIT_FAILED;
   
   scaler = new StandardizationScaler(standardization_scaler_mean, standardization_scaler_std); //laoding the saved scaler
   
//---
   return(INIT_SUCCEEDED);
  }

So normalisieren Sie alle neuen Eingabedaten:

void OnTick()
  { 
   if (CopyRates(Symbol(), PERIOD_D1, 1, time_step_, rates)<-1)
     {
       printf("Failed to collect data Err=%d",GetLastError());
       return;
     }
   
   matrix data(time_step_, 3);
   for (int i=0; i<time_step_; i++) //Get the independent values and save them to a matrix
     {
       data[i][0] = rates[i].open;
       data[i][1] = rates[i].high;
       data[i][2] = rates[i].low;
     }
   
   ts_data_tensor = ts_dataprocessor.extract_timeseries_data(data, time_step_);  //process the new data into timeseries 
   
   data = ts_data_tensor.Get(0); //This tensor contains only one matrix for the recent latest bars thats why we find it at the index 0
   data = scaler.transform(data); //Transform the new data 
   
   int signal = onnx.predict_bin(data, classes_);
   
   Comment("LSTM trade signal: ",signal);
  }

Schließlich ließ ich den EA im Strategietester ohne Fehler laufen, die Vorhersagen wurden erfolgreich im Chart angezeigt.

LSTM-Signale im Chart


Überwinden der herausfordernden Dimensionenreduzierung

Wie bereits erwähnt, ist für die Lösung von Problemen im realen Leben mit maschinellen Lernmodellen mehr erforderlich als ein KI-Modellcode, um die Aufgabe zu bewältigen. Zu den nützlichen Werkzeugen, die Datenwissenschaftler in der Regel in ihrem Toolkit mit sich führen, gehören Algorithmen zur Dimensionenreduktion wie PCA, LDA, NMF, Truncated SVD und viele mehr. Trotz ihrer Nachteile haben die Algorithmen zur Dimensionenreduzierung auch ihre Vorteile: 


Vorteile der Dimensionenreduzierung:

Verbesserte Modellleistung: Hochdimensionale Daten können zum „Fluch der Dimensionen“ führen, bei dem Modelle aufgrund des großen Merkmalsraums nur schwer effektiv lernen können. PCA reduziert die Komplexität und kann die Leistung verschiedener Algorithmen des maschinellen Lernens, einschließlich Klassifizierung, Regression und Clustering, verbessern.

Schnellere Ausbildung und Bearbeitung: Das Trainieren von Modellen des maschinellen Lernens auf hochdimensionalen Daten kann sehr rechenintensiv sein. PCA reduziert die Anzahl der Merkmale, was zu schnelleren Trainingszeiten und potenziell geringeren Anforderungen an die Rechenleistung führt.

Reduzierte Überanpassung: Eine hohe Dimensionalität kann das Risiko einer Überanpassung erhöhen, bei der sich das Modell zwar die Trainingsdaten merkt, aber nicht gut auf ungesehene Daten verallgemeinern kann. Die PCA hilft, dieses Risiko zu mindern, indem sie sich auf die informativsten Merkmale konzentriert.

Genau wie bei den Skalierungstechniken ist es cool, wenn Sie eine Dimensionenreduktionstechnik wie die Principal Component Analysis (PCA) von Scikit-Learn verwenden. Allerdings werden Sie es schwer haben, Wege zu finden, diese PCA in MQL5 zu verwenden, wo die meiste Arbeit erledigt wird, einschließlich des Handels auf der Grundlage von allem, was Sie gebaut haben. 

Im ONNX-Skript für das Datensammeln müssen wir die PCA hinzufügen.

#include <MALE5\Dimensionality Reduction\PCA.mqh>

CPCA *pca;

Wir wollen die PCA-Technik hinzufügen, um x Variablen zu normalisieren, bevor der Normalisierungsprozess stattfindet.

   MatrixExtend::XandYSplitMatrices(OHLC, x, y);  //WE split the data into x and y | The last column in the matrix will be assigned to the y vector 

//--- Reduce data dimension

   pca = new CPCA(2); //reduce the data to have two columns
   x = pca.fit_transform(x);
   if (!pca.save(Symbol()+"-PCA"))
     return

Dadurch wird ein Unterordner unter dem Ordner MQL5\Files erstellt. Dieser Ordner enthält Binärdateien mit Informationen für die PCA.

pca-Dateien onnx

Der neue CSV-Datensatz mit PCA hat nun zwei unabhängige Variablen, wie im PCA-Konstruktor angewiesen, um zwei Komponenten aus den Originaldaten zu bilden.

pca-Datensatz

Um Verwirrung zu vermeiden, können wir eine boolesche Bedingung erstellen, um zu prüfen, ob die Bedingung für PCA vom Nutzer zugelassen ist, da das Speichern von PCA-Daten in einer CSV-Datei unterschiedlich sein könnte. Außerdem müssen wir möglicherweise den Namen einer CSV-Datei ändern und PCA in den Namen aufnehmen, damit wir den Unterschied zwischen den CSV-Dateien der Datensätze erkennen können.

Innerhalb des ONNX-Skripts Daten sammeln.

input bool use_pca = true;

MqlRates rates[];
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- vector.CopyRates is lacking we are going to copy rates in normal way
... some code

//---
  
   matrix x;
   vector y;
   MatrixExtend::XandYSplitMatrices(OHLC, x, y);  //WE split the data into x and y | The last column in the matrix will be assigned to the y vector 

//--- Reduce data dimension
     
     if (use_pca)
      { 
         pca = new CPCA(2); //reduce the data to have two columns
         x = pca.fit_transform(x);
         if (!pca.save(Symbol()+"-PCA"))
           return;
      }
      
//--- Standardize the data  
     ...rest of the code
   
   if (CheckPointer(pca)!=POINTER_INVALID)
      delete pca;
  }

Wir müssen auch ähnliche Änderungen am Haupt-EA namens ONNX challenges REALTIME vornehmen.

//.... other imports 

#include <MALE5\Dimensionality Reduction\PCA.mqh>

CPCA *pca;

#resource "\\Files\\model.eurusd.D1.onnx" as uchar lstm_model_data[]
#resource "\\Files\\model.eurusd.D1.PCA.onnx" as uchar lstm_model_pca[]

#resource "\\Files\\EURUSD-SCALER\\mean.bin" as double standardization_scaler_mean[];
#resource "\\Files\\EURUSD-SCALER\\std.bin" as double standardization_scaler_std[];

#resource "\\Files\\EURUSD-PCA-SCALER\\mean.bin" as double standardization_pca_scaler_mean[];
#resource "\\Files\\EURUSD-PCA-SCALER\\std.bin" as double standardization_pca_scaler_std[];

#resource "\\Files\\EURUSD-PCA\\components-matrix.bin" as double pca_comp_matrix[];
#resource "\\Files\\EURUSD-PCA\\mean.bin" as double pca_mean[];


input int time_step_ = 7;
input bool use_pca = true;

//it is very important the time step value matches the one used during training in  a python script

CONNX onnx;
StandardizationScaler *scaler;

// ......

MqlRates rates[];
vector classes_ = {0,1};
int prev_bars = 0;
MqlTick ticks;
double min_lot = 0;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
   if (use_pca)
    {
    if (!onnx.Init(lstm_model_pca))
      return INIT_FAILED;
    }
   else
     {
       if (!onnx.Init(lstm_model_data))
         return INIT_FAILED;
     }
   
   if (use_pca)   
    {
      scaler = new StandardizationScaler(standardization_pca_scaler_mean, standardization_pca_scaler_std); //loading the saved scaler applied to PCA data
      pca = new CPCA(pca_mean, pca_comp_matrix);
    }  
   else
      scaler = new StandardizationScaler(standardization_scaler_mean, standardization_scaler_std); //laoding the saved scaler
    
//---
   
   m_trade.SetExpertMagicNumber(MAGIC_NUMBER);
   m_trade.SetDeviationInPoints(100);
   m_trade.SetTypeFillingBySymbol(Symbol());
   m_trade.SetMarginMode();
   
   min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   // ... collecting data code 
 ...
   
   ts_data_tensor = ts_dataprocessor.extract_timeseries_data(data, time_step_);  //process the new data into timeseries 
   
   data = ts_data_tensor.Get(0); //This tensor contains only one matrix for the recent latest bars thats why we find it at the index 0
   
   if (use_pca)
    data = pca.transform(data);
    
   data = scaler.transform(data); //Transform the new data 
   
   int signal = onnx.predict_bin(data, classes_);
   
   Comment("LSTM trade signal: ",signal);

  }

Haben Sie die Veränderungen bemerkt? Es gibt zwei Modelle im Expert Advisor, ein LSTM-Modell wurde auf einem regulären Datensatz trainiert und das andere mit dem Wort PCA im Namen wurde auf den Daten trainiert, die mit PCA bearbeitet wurden, da die Daten, die mit PCA bearbeitet wurden, unterschiedliche Dimensionen haben können, verglichen mit den Daten, die nicht bearbeitet wurden und die immer ähnliche Dimensionen wie die Originaldaten haben werden, Dieser Unterschied macht es wichtig, auch unterschiedliche Skalierer für jedes Modell zu haben

Nachdem wir nun Platz für ein neues Modell geschaffen haben, das mit PCA gefüllt werden soll, gehen wir zurück zu unserem Python-Skript und nehmen einige Änderungen vor. Es sind nur noch wenige Änderungen vorzunehmen: Der Name der CSV-Datei und der endgültige Name der ONNX-Datei:

csv_file = "EURUSD-OHLSignalPCA.csv"
step_size = 7
inp_model_name = "model.eurusd.D1.PCA.onnx"

Dieses Mal konvergierte das Modell in der 17. Epoche:

110/110 - 1s - loss: 0.6920 - accuracy: 0.5215 - val_loss: 0.6921 - val_accuracy: 0.5168 - 658ms/epoch - 6ms/step
Epoch 15/100
110/110 - 1s - loss: 0.6918 - accuracy: 0.5197 - val_loss: 0.6921 - val_accuracy: 0.5175 - 656ms/epoch - 6ms/step
Epoch 16/100
110/110 - 1s - loss: 0.6919 - accuracy: 0.5167 - val_loss: 0.6921 - val_accuracy: 0.5178 - 627ms/epoch - 6ms/step
Epoch 17/100
110/110 - 1s - loss: 0.6919 - accuracy: 0.5248 - val_loss: 0.6920 - val_accuracy: 0.5222 - 596ms/epoch - 5ms/step

Konvergiert mit einem ausreichenden Genauigkeit von 52,48%, etwas, das in der Regel so geschieht und nicht in der Nähe von 89% ohne PCA. Jetzt lassen Sie uns eine einfache Strategie realisieren, die Positionen auf der Grundlage von gegebenen Signale öffnen kann:

Die Handelslogik ist einfach: Prüfe, dass es keine offene Position in der Richtung gibt, und eröffne eine in dieser Richtung unter Kontrolle eines Signalwechsels. Wenn es ein neues Signal gibt, schließe die Position des entsprechenden Typs und eröffne eine Position in der entgegengesetzter Richtung.

void OnTick()
  {
//---
   
   if (!MQLInfoInteger(MQL_TESTER)) //if we are live trading consider new bar event
      if (!isnewBar(PERIOD_CURRENT))
        return;
      
//.... some code to collect data
... 
    
   data = scaler.transform(data); //Transform the new data 
   
   int signal = onnx.predict_bin(data, classes_);
   
   Comment("LSTM trade signal: ",signal);

//--- Open trades based on Signals
   
   SymbolInfoTick(Symbol(), ticks);
   if (signal==1) 
    {
      if (!PosExists(POSITION_TYPE_BUY))
        m_trade.Buy(min_lot,Symbol(), ticks.ask);
      else
       {
         PosClose(POSITION_TYPE_BUY); 
         PosClose(POSITION_TYPE_SELL); 
       } 
    }
   else
     {
      if (!PosExists(POSITION_TYPE_SELL))
        m_trade.Sell(min_lot,Symbol(), ticks.bid);
      else
       {
          PosClose(POSITION_TYPE_SELL); 
          PosClose(POSITION_TYPE_BUY); 
       }
     } 
  }

Ich habe das Modell „Nur Öffnungspreise“ im 12-Stunden-Zeitrahmen getestet, da der tägliche Zeitrahmen eine Menge Fehler bei der Marktschließung verursacht. Im Folgenden finden Sie die Ergebnisse, wenn das LSTM-Modell mit PCA angewendet wurde:

PCA lstm strategy tester

Ohne PCA:

lstm no PCA tester



Abschließende Überlegungen

ONNX ist ein großartiges Tool, aber wir müssen bei seiner Verwendung über den Tellerrand hinausschauen. Durch die Möglichkeit, Code für maschinelles Lernen zwischen verschiedenen Plattformen auszutauschen, erspart es uns eine Menge Arbeit und Kopfzerbrechen, die entstehen können, wenn man sich entscheidet, diese anspruchsvollen Deep-Learning- und KI-Modelle in der Sprache mql5 zu implementieren, aber man muss trotzdem noch etwas Arbeit leisten, um am Ende ein zuverlässiges und funktionierendes Programm zu haben.

Peace out.

Weitere Informationen zu allen in diesem Beitrag enthaltenen Dateien und mehr finden Sie in diesem GitHub Repo.

Anhänge: 

Datei Beschreibung und Verwendung
MatrixExtend.mqh
Verfügt über zusätzliche Funktionen für Matrixmanipulationen.
metrics.mqh
Enthält Funktionen und Code zur Messung der Leistung von ML-Modellen.
preprocessing.mqh
Die Bibliothek für die Vorverarbeitung von rohen Eingabedaten, um sie für die Verwendung von Modellen des maschinellen Lernens geeignet zu machen.
plots.mqh
Bibliothek zum Zeichnen von Vektoren und Matrizen.
Timeseries Deep Learning\onnx.mqh
Diese Bibliothek besteht aus der ONNX-Klasse, die dafür verantwortlich ist, .onnx-Dateien zu lesen und die geladenen Dateien für Vorhersagen zu verwenden.
Tensors.mqh
Eine Bibliothek mit Tensoren, algebraischen 3D-Matrizen-Objekten, programmiert in einfacher MQL5-Sprache.
Timeseries Deep Learning\tsdataprocessor.mqh
Eine Bibliothek mit einer Klasse, die Funktionen zur Umwandlung von Rohdaten in Daten enthält, die für Zeitreihenvorhersagen geeignet sind.
Dimensionality Reduction\base.mqh
Eine Datei mit den für die Dimensionenreduktion erforderlichen Funktionen. 
Dimensionality Reduction\PCA.mqh
Bibliothek der Hauptkomponentenanalyse (PCA).
Python\onnx_timeseries.ipynb  Ein Python-Jupyter-Notizbuch, das den gesamten in diesem Beitrag verwendeten Python-Code enthält. 
Python\requirements.txt  Eine Textdatei mit allen Python-Abhängigkeiten, die für die Ausführung des Python-Codes erforderlich sind.


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

Beigefügte Dateien |
Code_7_Files.zip (81.91 KB)
MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 16): Hauptkomponentenanalyse mit Eigenvektoren MQL5-Assistent-Techniken, die Sie kennen sollten (Teil 16): Hauptkomponentenanalyse mit Eigenvektoren
Die Hauptkomponentenanalyse, ein Verfahren zur Verringerung der Dimensionalität in der Datenanalyse, wird in diesem Artikel untersucht, und es wird gezeigt, wie sie mit Eigenwerten und Vektoren umgesetzt werden kann. Wie immer streben wir die Entwicklung eines Prototyps einer Experten-Signal-Klasse an, die im MQL5-Assistenten verwendet werden kann.
Nutzerdefinierte Indikatoren (Teil 1): Eine schrittweise Einführung in die Entwicklung von einfachen nutzerdefinierten Indikatoren in MQL5 Nutzerdefinierte Indikatoren (Teil 1): Eine schrittweise Einführung in die Entwicklung von einfachen nutzerdefinierten Indikatoren in MQL5
Erfahren Sie, wie Sie mit MQL5 nutzerdefinierte Indikatoren erstellen können. Dieser Einführungsartikel führt Sie durch die Grundlagen des Aufbaus einfacher nutzerdefinierter Indikatoren und demonstriert einen praktischen Ansatz zur Codierung verschiedener nutzerdefinierter Indikatoren für alle MQL5-Programmierer, die neu in diesem interessanten Thema sind.
Datenwissenschaft und ML (Teil 22): Nutzung von Autoencodern Neuronaler Netze für intelligentere Trades durch den Übergang vom Rauschen zum Signal Datenwissenschaft und ML (Teil 22): Nutzung von Autoencodern Neuronaler Netze für intelligentere Trades durch den Übergang vom Rauschen zum Signal
In der schnelllebigen Welt der Finanzmärkte ist es für den erfolgreichen Handel entscheidend, aussagekräftige Signale vom Rauschen zu unterscheiden. Durch den Einsatz hochentwickelter neuronaler Netzwerkarchitekturen sind Autocoder hervorragend in der Lage, verborgene Muster in Marktdaten aufzudecken und verrauschte Daten in verwertbare Erkenntnisse umzuwandeln. In diesem Artikel gehen wir der Frage nach, wie Autoencoders die Handelspraktiken revolutionieren und Händlern ein leistungsfähiges Werkzeug an die Hand geben, um die Entscheidungsfindung zu verbessern und sich auf den dynamischen Märkten von heute einen Wettbewerbsvorteil zu verschaffen.
Lernen Sie Schritt für Schritt, wie Sie die Fair Value Gap (FVG)/Ungleichgewichte handeln können: Ein Ansatz des Smart Money-Konzepts Lernen Sie Schritt für Schritt, wie Sie die Fair Value Gap (FVG)/Ungleichgewichte handeln können: Ein Ansatz des Smart Money-Konzepts
Eine Schritt-für-Schritt-Anleitung zur Erstellung und Implementierung eines automatisierten Handelsalgorithmus in MQL5 auf der Grundlage der Handelsstrategie Fair Value Gap (FVG). Eine detaillierte Anleitung zur Erstellung eines Expertenberaters, die sowohl für Anfänger als auch für erfahrene Händler nützlich sein kann.