English 日本語
preview
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

MetaTrader 5Indikatoren | 5 Juni 2024, 10:56
109 0
Omega J Msigwa
Omega J Msigwa

Was sind Autoencoder?

Autoencoder sind unüberwachte künstliche neuronale Netze. In seiner einfachsten Form ist ein Autoencoder ein neuronales Netz, das zwei Dinge versucht. Es komprimiert die Eingabedaten in eine niedrigere Dimension und versucht dann, diese niedrigere Darstellung der Daten zu verwenden, um die ursprüngliche Eingabe wiederherzustellen.

Angenommen, dem Autoencoder wird ein unscharfes Katzenbild übergeben. Dieses Bild wird komprimiert und wieder in seinen ursprünglichen Zustand dekomprimiert, wobei einige der verrauschten/unscharfen Pixel verloren gehen, um am Ende ein klares Bild einer Katze zu erhalten.

Unscharfe Katze vs. unscharfes Katzenbild

In diesem Artikel werden wir sehen, wie wir ein neuronales Autoencoder-Netzwerk im Finanzbereich einsetzen können, um das Marktrauschen zu beseitigen, damit wir Handelschancen entdecken können.

Dieser Artikel ist einfach zu lesen, wenn Sie ein grundlegendes Verständnis von ONNX, PCA und einem künstlichen neuronalen Netz im Allgemeinen haben.

Ein Autoencoder besteht aus zwei Teilen:

  1. Ein Encoder nimmt die Eingabedaten und komprimiert sie in eine niedriger dimensionale latente Darstellung, die die wesentlichen Merkmale erfasst.
  2. Ein Decoder empfängt die latente Darstellung und versucht, die ursprünglichen Eingabedaten so genau wie möglich zu rekonstruieren.

Vorteile von Autoencodern:

  • Sie können für Aufgaben der Dimensionenreduktion nützlich sein, da sie eine komprimierte Darstellung der Forex-Handelsdaten lernen können, was für Aufgaben wie Merkmalsextraktion, Datenkompression und Visualisierung in hochdimensionalen Datensätzen nützlich ist.
  • Durch den Versuch, die Eingabedaten zu rekonstruieren, lernt der Autoencoder die wesentlichen Merkmale und entfernt Rauschen oder irrelevante Informationen. Diese gelernten Merkmale können für andere Aufgaben des maschinellen Lernens wie Klassifizierung oder Erkennung von Anomalien von Nutzen sein.
  • Da sie unbeaufsichtigt sind, können sie verborgene Muster in den Handelsdaten ohne menschliche Interaktion entdecken.
  • Die von einem Autoencoder gelernte latente Repräsentation kann als vortrainierte Merkmale für andere Modelle verwendet werden, was deren Leistung verbessern kann.


Woraus sind sie zusammengesetzt?

Nehmen wir die Autoencoder auseinander und sehen wir uns an, woraus sie bestehen und was sie besonders macht.

Das Herzstück eines Autoencoders ist ein künstliches neuronales Netz, das aus drei Teilen besteht.

  1. Der Encoder
  2. Der Einbettungsvektor/die latente Schicht
  3. Der Decoder

einfache Autoencoder-Architektur

Der linke Teil des neuronalen Netzes wird als Encoder bezeichnet. Seine Aufgabe ist es, die ursprünglichen Eingabedaten in eine niedrigere dimensionale Darstellung umzuwandeln.

Der mittlere Teil des neuronalen Netzes wird als latente Schicht oder Einbettungsvektor bezeichnet. Seine Aufgabe besteht darin, die Eingabedaten in weniger dimensionalen Daten zu komprimieren.

Der rechte Teil dieses neuronalen Netzes wird als Decoder bezeichnet. Seine Aufgabe ist es, die ursprüngliche Eingabe mit Hilfe der Ausgabe des Encoders wiederherzustellen.

Dies ist faszinierend, weil der Decoder versucht, aus den vom Encoder zurückgegebenen niederdimensionalen Daten höherdimensionale Daten zu erzeugen. Das ist so, als würde man versuchen, ein Haus zu bauen, indem man sich ein Bild von einem Haus ansieht.

1D-Haus vs. 3D

Dadurch wird ein Informationsverlust erzwungen, der für das Funktionieren des gesamten Prozesses entscheidend ist. Indem man dafür sorgt, dass der Decoder unvollkommene Informationen hat und das gesamte Netzwerk trainiert, um den Konstruktionsfehler zu minimieren. Während des Trainings sind der Encoder und der Decoder gezwungen, zusammenzuarbeiten, um den Konstruktionsfehler zu minimieren.

Der Konstruktionsfehler ist die Differenz zwischen der versuchten Nachbildung und den ursprünglichen Eingabedaten.

Wenn wir keinen Informationsverlust zwischen dem Encoder und dem Decoder haben, dann würde das Netzwerk einfach lernen, die Eingabe mit eins zu multiplizieren und eine perfekte Rekonstruktion zu erhalten, was den Autoencoder nutzlos macht. Einen Encoder mit einem gewissen Grad an Fehlern zu haben, ist entscheidend für diese Technik des maschinellen Lernens. Stellen Sie sicher, dass Sie Ihr Modell nicht überanpassen.

Sowohl Kodierer als auch Dekodierer sind nicht auf eine einzige Schicht beschränkt, wie in der oben gezeigten Autoencoder-Architektur zu sehen ist. Sie kann mehrere Schichten enthalten, wie im folgenden Python-Code zu sehen ist, in dem wir eine Liste mit dem Namen hidden_dims zum Speichern der Neuronen der Kodierer- und Dekodierer-Schichten haben.

Python:

class Autoencoder(Model):
  def __init__(self, input_dim, latent_dim, hidden_dims=[]):
    super(Autoencoder, self).__init__()

    self.encoder = tf.keras.Sequential()
    # Add hidden layers to the encoder (if any)
    for dim in hidden_dims:
      self.encoder.add(layers.Dense(dim, activation='relu'))
      self.encoder.add(layers.Dropout(0.5))

    # Define the latent layer
    self.encoder.add(layers.Dense(latent_dim, activation='relu'))

    # Decoder ( mirrored structure )
    self.decoder = tf.keras.Sequential()
    # Add hidden layers to the decoder (in reverse order)
    for dim in hidden_dims[::-1]:
      self.decoder.add(layers.Dense(dim, activation='relu'))
      self.decoder.add(layers.Dropout(0.5))

    # Define the output layer
    self.decoder.add(layers.Dense(input_dim, activation='sigmoid'))  #the output layer with dimensions matching the original input data

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

Aufrufen der Klasse Autoencoder:

Python:

input_dim = dataset.shape[1]  # number of columns in the data
latent_dim = 5  # Dimension of latent layer
hidden_dims = [12, 10]

autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)

Nachfolgend ist die Architektur des Autoencoders dargestellt:

12,10 Autoencoder-Architektur

In der Autoencoder-Klasse haben Sie die Verwendung von RELU (Rectified Linear Unit) sowohl im Encoder als auch im Decoder gesehen. Diese Aktivierungsfunktion wird in den meisten Autoencodern verwendet, und das hat einen wichtigen Grund.

RELU ist rechnerisch effizient, vermeidet verschwindende Gradienten und kann spärliche Darstellungen erlernen, die normalerweise in den Handelsdaten zu finden sind. Andere Varianten von RELU wie GELU und Leaky RELU können bei der Arbeit mit Finanzdaten hilfreich sein.

Andere populäre Aktivierungsfunktionen wie Sigmoid und Hyperbolic Tangent (TANH) können nützlich sein, aber man muss ihre Vor- und Nachteile verstehen, bevor man sie im Datenhandel verwendet.

Sigmoid:

  • Vorteile: Wird häufig zur Bildrekonstruktion verwendet, wenn die Ausgabe zwischen 0 und 1 liegen muss (was die Pixelintensität darstellt).
  • Nachteile Für Finanzdaten ist es möglicherweise nicht ideal, da es während der Backpropagation zu verschwindenden Gradienten kommen kann, insbesondere bei tiefen Architekturen.
    Bei der Anwendung von Sigmoid auf den Autoencoder konnte das Netzwerk nicht konvergieren, da es ständig in Richtung der lokalen Minima oszillierte:
    Epoch 1/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - loss: 0.4001 - val_loss: 0.3753
    Epoch 2/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3733 - val_loss: 0.3745
    Epoch 3/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3724 - val_loss: 0.3746
    Epoch 4/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3758 - val_loss: 0.3746
    Epoch 5/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3692 - val_loss: 0.3745
    Epoch 6/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3747 - val_loss: 0.3746
    Epoch 7/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3716 - val_loss: 0.3746
    Epoch 8/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3740 - val_loss: 0.3745
    Epoch 9/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3698 - val_loss: 0.3745
    Epoch 10/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3713 - val_loss: 0.3745
    Epoch 11/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3726 - val_loss: 0.3745
    Epoch 12/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3739 - val_loss: 0.3745
    Epoch 13/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3725 - val_loss: 0.3746
    Epoch 14/50
    110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.3749 - val_loss: 0.3746

Tanh (Hyperbolischer Tangens):

  • Vorteile: Die Ausgaben liegen zwischen -1 und 1, ähnlich wie bei Sigmoid, aber mit steileren Gradienten, was zu einer schnelleren Konvergenz führen kann.
  • Nachteile Bei sehr tiefen Netzen kann es immer noch zu verschwindenden Gradienten kommen.

Diese Sigmoid- und TANH-Aktivierungsfunktionen und andere ihrer Art funktionieren am besten, wenn sie in der Ausgabeschicht des Decoders verwendet werden, um die Eingabedaten so genau wie möglich zu rekonstruieren. In diesem Zusammenhang sollte die Ausgabe des Autoencoders der ursprünglichen Eingabe ähneln. Da die Eingabedaten je nach Vorverarbeitung häufig auf den Bereich [0, 1] oder [-1, 1] normiert sind, wird die Sigmoid-Aktivierungsfunktion üblicherweise zur Skalierung der Ausgabewerte auf diesen Bereich verwendet.

Python:

# Define the output layer
self.decoder.add(layers.Dense(input_dim, activation='sigmoid'))  #the output layer of the decoder with dimensions matching the original input data


Ein Min-Max Skalierer ist Ihr Freund

Autoencoder sind einfach zu kodieren und zu implementieren, allerdings müssen sie mit den richtigen Informationen und Werkzeugen ausgestattet werden, damit sie gut funktionieren. Wie wir gerade gesehen haben, ist die Wahl der Aktivierungsfunktion für diesen Typ von neuronalem Netz entscheidend, ebenso wie die Skalierungstechnik.

Da wir die RELU-Aktivierungsfunktion verwenden, die den Wert Null zurückgibt, wenn ihr ein Wert kleiner oder gleich Null gegeben wird, andernfalls gibt sie den gegebenen Wert zurück, d. h. (x = 0, wenn x<=0, sonst x = x).

Wenn Sie Standard-Skalierer verwenden, werden die Daten durch Subtraktion des Mittelwerts zentriert und auf eine Einheitsvarianz skaliert. Dies kann dazu führen, dass Ausreißer mit großen positiven Werten bei der Standardisierung auf sehr negative Werte (möglicherweise -1) gedrückt werden. Wenn ein normierter Wert eines Ausreißers -1 wird, gibt die RELU-Aktivierung im Messgerät bei Überschreiten dieses negativen Wertes immer 0 für dieses spezifische Merkmal aus.

Dies kann zu einem Phänomen führen, das als Absterben von „RELU-Neuronen“ bezeichnet wird, bei dem einige Neuronen im Encoder aufgrund dieser negativen Eingangswerte nie aktiviert werden. Diese absterbenden RELU-Neuronen können das Lernen im Encoder behindern, da sie im Wesentlichen inaktiv werden und keinen Beitrag zum Enkodierungsprozess leisten. Die meisten Ausreißer oder Spitzen in den Handelsdaten werden meist flach vorhergesagt; siehe das folgende Bild, in dem der Standard-Skalierer verwendet wurde.

  StandardScaler-Autoencoder

Um dieses Problem anzugehen:

Probieren Sie andere Normalisierungstechniken aus, wie z. B. den Min-Max-Skalierer, der die Daten auf einen bestimmten Bereich zwischen 0 und 1 skaliert und so möglicherweise die Erstellung von -1-Werten vermeidet, die RELU-Probleme verursachen. In Anbetracht der Einschränkungen des Min-Max-Skalierers können Sie jedoch auch den Robust-Skalierer verwenden, der weniger empfindlich auf Ausreißer reagiert als der Standard-Skalierer und möglicherweise eine bessere Skalierung für RELU-Aktivierungen bietet.

Außerdem sollten Sie erwägen, Leaky RELU (leaky_relu = 0,01x für x <= 0, relu = x für x > 0) anstelle von Standard-RELU zu verwenden. Leaky RELU ermöglicht auch bei negativen Eingaben einen kleinen Gradienten ungleich Null, wodurch das Problem des sterbenden RELU gemildert wird.


Training des Autoencoders

Nachdem wir nun kurz die Grundlagen eines Autoencoders erörtert haben, wollen wir einen Autoencoder trainieren und sehen, wie wir ihn für den Handel nutzen können.

Python:

import sklearn
from sklearn.model_selection import train_test_split
from keras import optimizers
from keras.callbacks import EarlyStopping

x_train, x_test = train_test_split(dataset, test_size=0.3, random_state=42) #train test the data

# Normalizing the input data 

scaler = sklearn.preprocessing.MinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)

print(f"x_train {x_train.shape}.dtype({x_train.dtype}) x_test {x_test.shape}.dtype({x_test.dtype})")

# compile the autoencoder

input_dim = dataset.shape[1]
latent_dim = 32  # Dimension of latent space
hidden_dims = [256, 128, 64]

autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)

optimizer = optimizers.Adam(learning_rate=1e-5)
autoencoder.compile(optimizer=optimizer, loss=losses.MeanSquaredError())

early_stopping = EarlyStopping(monitor='val_loss', patience = 5, restore_best_weights=True) //stop the training process if 5 epochs have no change in loss
history = autoencoder.fit(x_train, x_train, epochs=50, shuffle=True, callbacks=[early_stopping], validation_data=(x_test, x_test), batch_size=64, verbose=1)

Ich habe mich für eine komplexe neuronale Netzwerkarchitektur [256, 128, 64] für den Kodierer entschieden, eine umgekehrte Anordnung von [64, 128, 256] wird auf den Dekodierer angewendet, während 32 Neuronen in der latenten Schicht vorhanden sind.

Bei einem so komplexen neuronalen Netz ist die Wahrscheinlichkeit größer, dass es sich zu stark an die Trainingsdaten anpasst. Es steht Ihnen frei, mit einfacheren Architekturen zu beginnen - dies ist nur ein Beispiel

Ausgaben:

x_train (7000, 4).dtype(float64) x_test (3000, 4).dtype(float64)
Epoch 1/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - loss: 0.0669 - val_loss: 0.0636
Epoch 2/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0648 - val_loss: 0.0608
Epoch 3/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 0.0624 - val_loss: 0.0550

....
....
....

Epoch 46/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.2096e-04 - val_loss: 1.0195e-04
Epoch 47/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.0758e-04 - val_loss: 9.7759e-05
Epoch 48/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - loss: 1.0923e-04 - val_loss: 9.4798e-05
Epoch 49/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - loss: 1.0243e-04 - val_loss: 9.0442e-05
Epoch 50/50
110/110 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - loss: 1.0222e-04 - val_loss: 8.7384e-05

Diagramm von Verlust vs. Iteration:

Diagramm von Verlust vs. Iteration

Übergeben wir die Daten an den Autoencoder und beobachten wir das Ergebnis;

Python:

original_norm_data = scaler.transform(dataset)

new_data = autoencoder.call(original_norm_data)

new_data = scaler.inverse_transform(new_data) #return data to the original form 

print("original data\n",dataset,"\nnew data\n",new_data)

Ausgaben:

original data
 [[1.06507 1.06633 1.06497 1.06538]
 [1.06628 1.06685 1.06463 1.06508]
 [1.06771 1.06797 1.06599 1.06627]
 ...
 [0.99941 0.99996 0.9991  0.99916]
 [0.99687 0.99999 0.99646 0.99941]
 [0.99536 0.99724 0.99444 0.99687]] 
new data
 [[1.06612682 1.06676685 1.06537819 1.06605109]
 [1.06617137 1.06679912 1.06541834 1.06609218]
 [1.06742607 1.06804771 1.06668032 1.06736937]
 ...
 [0.99906356 1.00121275 0.9980908  0.99980352]
 [0.998204   1.00034005 0.9972261  0.99893805]
 [0.99581326 0.99789913 0.99494114 0.99651365]]

Ich beschloss, die Schlusskurse zu visualisieren:

Schlusskurse vs. automatisch kodierte Preise

Wir können daraus schließen, dass die neuen Daten, die den Autoencoder durchlaufen haben, ein gewisses Maß an gefiltertem Rauschen aufweisen, und es ist einfach, die Ausreißer zu erkennen, indem man sich das Diagramm ansieht. Jetzt, da wir sicher sind, dass es funktioniert, wollen wir uns mit Autoencoder-Anwendungen beschäftigen und damit, wie wir sie endlich in unseren MQL5-basierten Programmen einsetzen können.


Anwendungen von Autoencodern

Autoencoder werden in verschiedenen Bereichen und Branchen eingesetzt, z. B. im Ingenieurwesen, in der Medizin, in der Unterhaltungsbranche und in vielen anderen Bereichen zur Dimensionenreduzierung, zum Lernen von Merkmalen, zur Erkennung von Anomalien, in Empfehlungssystemen und zur Bildentrauschung.

Dimensionenreduktion

Autoencoder zeichnen sich durch die Komprimierung hochdimensionaler Daten in einen niedrigeren latenten Raum aus. Dies ist besonders wertvoll, wenn es um Datensätze mit einer großen Anzahl von Merkmalen geht, da sie die wesentlichen Merkmale in einer kompakteren Darstellung erfassen, die dann verwendet werden kann:

  • Verbesserung der Berechnungseffizienz bei nachfolgenden Aufgaben des maschinellen Lernens durch Verringerung der Anzahl der zu verarbeitenden Merkmale. 
  • Verbessern Sie die Visualisierung hochdimensionaler Daten, indem Sie die Anwendung von Dimensionenreduktionstechniken wie der Hauptkomponentenanalyse (PCA) auf den erlernten latenten Raum ermöglichen.

Um diese Aufgabe zu erfüllen, müssen wir nur den Encoderteil unseres neuronalen Netzes verwenden. 

Wir müssen die Autoencoder-Klasse ändern, indem wir die Build-Funktion hinzufügen, die kurz nach dem Start der Autoencoder-Klasse aufgerufen werden soll. Diese Methode ist nützlich für die dynamische Erstellung von Ebenen auf der Grundlage der Form der Eingabedaten und ermöglicht es Ihnen, die Erstellung von Ebenen zu verzögern, bis ihre Form bekannt ist.

Python:

class Autoencoder(Model):
  def __init__(self, input_dim, latent_dim, hidden_dims=[]):
    super(Autoencoder, self).__init__()
    self.hidden_dims = hidden_dims
    self.input_dim = input_dim
    
    # Encoder
    self.encoder = tf.keras.Sequential(name='encoder') #give the encoder Sequential layer name=encoder
    # Decoder ( mirrored structure )
    self.decoder = tf.keras.Sequential(name='decoder') #give the decoder Sequential layer name=decoder
    
  def build(self):

    # Add hidden layers to the encoder (if any)
    for dim in hidden_dims:
      self.encoder.add(layers.Dense(dim, activation='relu'))
      self.encoder.add(layers.Dropout(0.5))

    # Define the latent layer
    self.encoder.add(layers.Dense(latent_dim, activation='relu'))
        
    # Add hidden layers to the decoder (in reverse order)
    for dim in hidden_dims[::-1]:
      self.decoder.add(layers.Dense(dim, activation='relu'))
      self.decoder.add(layers.Dropout(0.5))

    # Define the output layer
    self.decoder.add(layers.Dense(self.input_dim, activation='sigmoid'))  #the output layer with dimensions matching the original input data

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

Wir müssen auch die Art und Weise, wie wir unsere Klassenfunktionen aufrufen, ein wenig ändern. Wie bereits erwähnt, sollen wir die Build-Funktion aufrufen, bevor wir unser neuronales Netzwerkmodell kompilieren und trainieren. Die Reihenfolge des Aufrufs der Klassenmethoden ist wichtig!

Python:

# Instantiate the autoencoder and build the model
autoencoder = Autoencoder(input_dim, latent_dim, hidden_dims)
autoencoder.build()

optimizer = optimizers.Adam(learning_rate=1e-5)
autoencoder.compile(optimizer=optimizer, loss=losses.MeanSquaredError())

Nachdem wir nun die Build-Funktion eingerichtet haben, können wir schließlich sowohl das Encoder- als auch das Decoder-Netz separat extrahieren, sobald der Autoencoder erfolgreich und fehlerfrei trainiert wurde.

Python:

# Extract Encoder
encoder_input = autoencoder.encoder.layers[0].input
encoder_output = autoencoder.encoder.get_layer(index=-1).output # the layer at index -1 is the last layer

# Define the encoder model
encoder_model = tf.keras.Model(inputs=encoder_input, outputs=encoder_output)

# Extract Decoder
decoder_input = autoencoder.decoder.layers[0].input
decoder_output = autoencoder.decoder.get_layer(index=-1).output # the layer at index -1 is the last layer

# Define the decoder model
decoder_model = tf.keras.Model(inputs=decoder_input, outputs=decoder_output)

Sobald wir den Encoder haben, können wir die Informationen weitergeben und die Ergebnismatrix erhalten, die durch die latente Schicht (Raum) geleitet wird.

Python:

from sklearn.decomposition import PCA

# Fit & transform the encoded data 
encoded_data = encoder_model.predict(original_norm_data)
print("decoded data.shape: ",encoded_data.shape)

# Create PCA object
pca = PCA(n_components=encoded_data.shape[1])

reduced_data = pca.fit_transform(encoded_data)
print("pca reduced data.shape: ",reduced_data.shape)

print("explained var:\n",np.cumsum(pca.explained_variance_ratio_))

# Plotting the scree plot
plt.figure(figsize=(10, 6))
plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance')
plt.title('Scree Plot')
plt.grid(True)
plt.show()

Indem wir den PCA-Komponenten die Anzahl der Spalten encoded_data.shape[1] zuweisen, können wir die erklärte Varianz jedes Merkmals messen und eine Bildschirmdarstellung zeichnen, die uns helfen kann, die beste Anzahl von Komponenten für die PCA zu ermitteln, um die Datendimension zu verringern.

313/313 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step
decoded data.shape:  (10000, 32)
pca reduced data.shape:  (10000, 32)
explained var:
 [0.99623495 0.9989214  0.99982804 0.9999363  0.99996614 0.9999872
 0.99999297 0.9999953  0.9999972  0.9999982  0.9999987  0.9999991
 0.9999994  0.9999996  0.9999997  0.9999998  0.99999994 1.
 1.         1.         1.         1.         1.         1.
 1.         1.         1.         1.         1.         1.
 1.         1.        ]

PCA-Kreuzungsdiagramm

Betrachtet man die kumulierte erklärte Varianz, so stellt man fest, dass das Verhältnis der erklärten Varianz bei den meisten Komponenten nahe bei 1 und bei einigen Komponenten bei 1 liegt. Dies bedeutet, dass Sie möglicherweise eine erhebliche Dimensionenreduzierung erreichen können, ohne viel Information zu verlieren.
Der Screeplot zeigt, dass der Knickpunkt bei fast 2 Komponenten liegt, was etwa 0,9989 Gesamtvarianz erklärt, dies ist die beste Anzahl von Komponenten, um unsere Daten zu reduzieren. Selbst 1 Komponente sollte gut funktionieren, da ich keinen großen Unterschied zwischen den Komponenten erkennen konnte, als ich sie auf einer Achse auftrug.

Wenn die PCA-Klasse das nächste Mal aufgerufen wird, sollte sie mit dem Wert 2 aufgerufen werden, um 2 Komponenten zu erhalten.

# Create PCA object
pca = PCA(n_components=2)

reduced_data = pca.fit_transform(encoded_data)
print("pca reduced data.shape: ",reduced_data.shape)

Das Ergebnis:

pca reduced data.shape:  (10000, 2)

Ich beschloss, alle 32 Komponenten der latenten Schicht auf einer Achse darzustellen. Nur ein Merkmal unterschied sich deutlich von den anderen, die auf dem Diagramm fast gleich aussahen, was zur Klärung einiger Komponenten in diesen reduzierten Daten beiträgt.

bar = [count+1 for count in range(reduced_data.shape[0])]

plt.figure(figsize = (7,10))
for col in range(reduced_data.shape[1]):
    plt.plot(bar,  reduced_data[:, col],label=f'feature {col}')
    
plt.xlabel("index")
plt.ylabel("feature")
plt.title("PCA encoded features")
plt.legend()
plt.savefig("pca-encoded features")

Komponenten vs. Index-Plot:

Darstellung der PCA-Komponenten

Die Anwendung von PCA auf den latenten Raum des Autoencoders bietet eine bessere Kontrolle über den Reduktionsprozess als die direkte Anwendung von PCA auf die hochdimensionalen Originaldaten, ganz zu schweigen davon, dass es hilft, unnötiges Rauschen in den Daten während des Prozesses zu reduzieren.

Ein Elefant im Raum:

In dem besprochenen Beispiel haben wir die Dimension aller Eingabedaten reduziert. Dies ist möglicherweise nicht ideal, wenn Sie die nach der PCA reduzierten Daten in Vorhersagemodellen anwenden wollen; in diesem Fall müssen Sie die PCA nur auf unabhängige Variablen anwenden.


Aber bevor wir diesen Autoencoder, den wir erstellt haben, um das Rauschen aus den Handelsdaten in MetaTrader 5 zu reduzieren, als weitere Autoencoder-Anwendung verwenden können, müssen wir ihn im ONNX-Format speichern.


Speichern des Autoencoder-Modells im ONNX-Format

Wir haben bereits sowohl den Encoder als auch den Decoder extrahiert, bevor wir sie für die Dimensionenreduktion verwendet haben. Die Konvertierung und Speicherung im ONNX-Format sollte einfach sein. Beginnen wir mit dem Gebermodell, da wir beide getrennt speichern werden.

Python:

import tf2onnx
import onnx
import os

output_path = os.path.join('/kaggle/working/',"encoder.eurusd.h1.onnx")

# saving the encoder for MetaTrader 5

input_signature = [tf.TensorSpec(encoder_input.shape, tf.float16, name='x_inputs')] #onnx input signature
# Use from_function for tf functions
onnx_model, _ = tf2onnx.convert.from_keras(encoder_model, input_signature, opset=13)
onnx.save(onnx_model, output_path)

Die input_signature für ONNX hilft, Fehler mit den neuesten Versionen von TensorFlow und ONNX zu vermeiden, da sie hilft, die Eingabenamen für unsere .onnx Datei zu klären, wenn ein Modell dieses Formats in MetaTrader 5 geladen wird.

Speichern des Decodermodells:

Python:

# saving the decoder

output_path = os.path.join('/kaggle/working/',"decoder.eurusd.h1.onnx")

input_signature = [tf.TensorSpec(decoder_input.shape, tf.float16, name='decoder_inputs')] #onnx input signature

onnx_model, _ = tf2onnx.convert.from_keras(decoder_model, input_signature, opset=13) #conver keras model to onnx
onnx.save(onnx_model, output_path)

In dem Artikel Overcoming ONNX Integration Challenges habe ich mich mit dem Problem der Integration derselben Dimensionenreduzierungs- und Skalierungstechniken beschäftigt, die sowohl für Python als auch für die Programmiersprache mql5 verfügbar sind.

Speichern des Skalierers:

Die Verwendung desselben Skalierers in Python und mql5 ist entscheidend. Ich kann nicht genug betonen, wie wichtig das ist.

Python:

scaler.data_min_.tofile("minmax_min.bin")
scaler.data_max_.tofile("minmax_max.bin")

Wir speichern die Min-Max-Skalierer-Informationsarrays in einfachen Binärdateien, die wir in unseren MetaTrader 5-Indikator einbinden können. Nachdem Sie sie im Ordner MQL5Files gespeichert haben.

MQL5 (AutoEncoder Indicator.mq5):

//Load both the encoder_model and the decoder_model
#resource "\\Files\\encoder.eurusd.h1.onnx" as uchar encoder_onnx[];
#resource "\\Files\\decoder.eurusd.h1.onnx" as uchar decoder_onnx[];

// Load the MinMax scaler also
#resource "\\Files\\minmax_min.bin" as double min_values[];
#resource "\\Files\\minmax_max.bin" as double max_values[];


Reduzierung des Rauschens in den Handelsdaten

Der Autoencoder kann Rauschen aus den Daten entfernen, wie es in verschiedenen Aspekten, wie z. B. der Entfernung von Rauschen aus Bildern, zu sehen ist, müssen wir dies noch in den Finanzdaten beweisen. Wenn man sich das Bild der Schlusskurse und der neuen Schlusskurse ansieht, wird deutlich, dass die automatisch kodierten Schlusskurse weniger verrauscht sind. Lassen Sie uns einen Indikator erstellen, der uns hilft, die Kerzen für den neuen OHLC des Autoencoders zu zeichnen.

MQL5 (AutoEncoder Indicator.mq5):

#property indicator_chart_window
#property indicator_plots 1
#property indicator_buffers 5

input bool show_bars = true;
input bool show_bullish_bearish = false;

//--- plot Candle
#property indicator_label1  "autoencoded open; high; low; close"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrRed, clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

Wir müssen eine Autoencoder-Klasse erstellen, um die Verwendung der geladenen ONNX-Modelle in MQL5 zu erleichtern, als ob wir sie in Python verwenden würden.

MQL5(Autoencoder-onnx.mqh):

class CAutoEncoderONNX
  {
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; }
   
public:
                     CAutoEncoderONNX(void);
                    ~CAutoEncoderONNX(void);
                     
                     bool Init(const uchar &onnx_buff[], ulong flags=ONNX_DEFAULT); //load the onnx model from a resource uchar array
                     bool Init(string onnx_filename, uint flags=ONNX_DEFAULT); //load the onnx model from a .onnx file 
                     
                     matrix predict(const matrix &x); //passing inputs for either the encoder or the decoder to the outputs in matrix form
                     vector predict(const vector &x); //passing inputs for either the encoder or the decoder to the outputs in matrix form
  };

Instanziierung der Klasse CAutoEncoderONNX für jedes Modell separat, wie sie sind:

MQL5 (AutoEncoder Indicator.mq5):

#include <Autoencoder-onnx.mqh>
#include <MALE5\preprocessing.mqh>

CAutoEncoderONNX encoder_model; //for the encoder model
CAutoEncoderONNX decoder_model; //for the decoder model
MinMaxScaler *scaler; //Python-like MinMax scaler

Initialisierung der Modelle:

MQL5 (AutoEncoder Indicator.mq5):

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   if (!encoder_model.Init(encoder_onnx)) //initializing the encoder
     return INIT_FAILED;
   
   if (!decoder_model.Init(decoder_onnx)) //initializing the decoder
     return INIT_FAILED;
   
   scaler = new MinMaxScaler(min_values, max_values); //Load the Minmax scaler saved in python
     
//---
   return(INIT_SUCCEEDED);
  }

Um die Vorhersagen des Modells zu erhalten, werden wir die Rohdaten an den Encoder weiterleiten und dann das Ergebnis an den Decoder für die endgültige Ausgabe weiterleiten. Erinnern Sie sich! In Python hatten wir zwei separate Modelle, die nacheinander in der Aufruffunktion übergeben wurden.

Python:

class Autoencoder(Model):
...
...

  def call(self, x):
    encoded = self.encoder(x)
    decoded = self.decoder(encoded)
    return decoded

Sehen wir uns dies in mql5 an:

MQL5 (AutoEncoder Indicator.mq5):

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   
   int start = prev_calculated;
   if(start>=rates_total)
      start = rates_total-1;
   
   
   vector encoded_data = {}, decoded_data = {};
   for(int i = start; i<rates_total; i++)
     {
        vector x_inputs = {open[i], high[i], low[i], close[i]};
        
        x_inputs = scaler.transform(x_inputs); //Normalize the input data, important!
        encoded_data = encoder_model.predict(x_inputs); //encode the data
        decoded_data = decoder_model.predict(encoded_data); //decode the data
        
        decoded_data = scaler.inverse_transform(decoded_data); //return data to its original state  
          
        open_candle[i]= decoded_data[0];
        high_candle[i]= decoded_data[1];
        low_candle[i]=  decoded_data[2];
        close_candle[i]=decoded_data[3];
        
        // Set upper and lower body colors based on the gradient
        
        if (close_candle[i]>open_candle[i])
         {
           color_buffer[i] = 1.0; //Draw gray for bullish candle
         }
        else
         {
          color_buffer[i] = 0.0; //draw red when there was a bearish candle
         }
                         
        if (MQLInfoInteger(MQL_DEBUG))
         Comment(StringFormat("plotting [%d/%d] OPEN[%.5f] HIGH[%.5f] LOW[%.5f] CLOSE[%.5f]",i,rates_total,open_candle[i],high_candle[i],low_candle[i],close_candle[i]));
     }
     
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Indikator-Darstellung:

autoencoder doji als Kerzen

Nach meiner Beobachtung haben die vom Autoencoder erstellten Kerzen fast die gleiche Körpergröße, und der Unterschied zwischen dem niedrigeren und dem höheren Preis ist hoch und für alle Kerzen fast gleich.

Die meisten Kerzen sind, in rot, also abwärts im Markt, und nur sehr wenige Kerzen sind aufwärts und in grau Farbe.

Damit dieser Indikator auf dem Diagramm gut erscheint, können wir den Raum zwischen dem unteren und dem oberen Kurs der Kerze füllen. Sowohl für Aufwärts- wie auch für Abwärts-Kerzen.

MQL5 (AutoEncoder Indicator.mq5):

  if (close_candle[i]>open_candle[i])
   {
     color_buffer[i] = 1.0; //Draw gray for bullish candle

     close_candle[i] = high_candle[i];
     open_candle[i] = low_candle[i];
   }
  else
   {
     color_buffer[i] = 0.0; //draw red when there was a bearish candle
    
     close_candle[i] = low_candle[i];
     open_candle[i] = high_candle[i];
   }

Indikator-Darstellung:

automatisch codierte neue Balken

Wir können unserem Indikator die Option geben, zwischen Auf- und Abwärts-Kerzen zu unterscheiden, basierend auf den aktuellen Eröffnungs- und Schlusskursen des Marktes.

MQL5 (AutoEncoder Indicator.mq5):

if (show_bullish_bearish)
 {
  if (close[i]>open[i])
   color_buffer[i] = 1.0;
  else
    color_buffer[i] = 0.0;
 }

Indikator-Darstellung:

autocodierte OHLC EURUSD-Farbkerzen

Wir haben auch die Möglichkeit, die Originalkerzen auszublenden und nur die neuen, mit dem Autoencoder erstellten Kerzen zu verwenden.

autoencoder indicator show bars=false


Nachteile von Autoencodern

Autoencoder haben wie alle Modelle des maschinellen Lernens ihre eigenen Herausforderungen:

  1. Unvollkommene Daten-Rekonstruktion
    Autoencoder versuchen, die Daten nach der Komprimierung wiederherzustellen. Manchmal leisten sie keine gute Arbeit, was zu Fehlern bei der Rekonstruktion der ursprünglichen Daten führt. Dies ist ein Problem, wenn Sie eine sehr genaue Nachbildung der Originaldaten benötigen.

  2. Schwer zu verstehen
    Die von Autoencodern erzeugten komprimierten Datenformate können schwierig zu interpretieren sein. Es ist oft nicht klar, welche Merkmale der Daten der Autoencoder erfasst hat, was es schwierig macht, die Funktionsweise des Modells zu erklären.

  3. Empfindlich gegen Rauschen
    Autoencoder zielen darauf ab, die Hauptmuster in den Daten hervorzuheben, haben aber möglicherweise Probleme mit Rauschen und Ausreißern. Dies kann zu einer schlechten Rekonstruktion und verzerrten Merkmalen führen, was nicht ideal ist.

  4. Engpass bei der Dimensionalität
    Die mittlere Schicht eines Autoencoders, in der die Daten komprimiert werden, kann manchmal zu klein sein. Wenn es nicht ausreichend dimensioniert ist, kann es sein, dass es nicht alle wichtigen Informationen für das, was Sie tun müssen, erfasst. Die Wahl der richtigen Größe für diese Schicht ist entscheidend und hängt davon ab, was Sie erreichen wollen.

  5. Aufwendig zu trainieren
    Das Training von Deep Autoencodern, insbesondere bei großen Datensätzen, kann viel Rechenleistung erfordern. Dies ist wichtig, wenn Sie nur über begrenzte Mittel oder Zeit verfügen.

  6. Nicht für alle Aufgaben geeignet
    Autoencoder sind möglicherweise nicht die beste Wahl für Aufgaben wie Klassifizierung oder Regression, bei denen eine direkte Arbeit mit den Eingabedaten effektiver sein könnte.

  7. Risiko der Überanpassung
    Die Verwendung komplexer Modelle für einfache Probleme kann zu einer Überanpassung führen, bei der das Modell die Trainingsdaten zu gut lernt, aber bei neuen, ungesehenen Daten schlecht abschneidet.



Abschließende Überlegungen

Autoencoder können ein großartiges Werkzeug sein, um das Rauschen auf dem Forex-Markt zu reduzieren, wie im Indikator zu sehen ist. Am Ende haben wir weniger verrauschte Kerzen, die immer noch den Markt widerspiegeln, sie könnten entweder besser oder schlechter sein als die ursprünglichen Kerzen, Diese neuen Kerzen geben uns eine andere Perspektive auf den Markt.

Sie können die neuen Kerzen erforschen, indem Sie Signale aus den Mustern extrahieren und darauf Handelsstrategien aufbauen.

Peace out.

Tabelle der Anhänge: 

Datei Beschreibung und Verwendung
Include\MatrixExtend.mqh
Verfügt über zusätzliche Funktionen für Matrixmanipulationen.
Include\ preprocessing.mqh
Die Bibliothek für die Vorverarbeitung von rohen Eingabedaten, um sie für die Verwendung von Machine-Learning-Modellen geeignet zu machen.
Indicators\ AutoEncoder Indicator.mq5 Die Hauptindikator-Datei. Es setzt den besprochenen Autoencoder ein und zieht Kerzen auf die Ergebnisvorhersagen.
Include\ Autoencoder-onnx.mqh  Eine Bibliothek zum Laden eines Machine-Learning-Modells im ONNX-Format und zur Interpretation der Ergebnisse.
Files\...  Speichern Sie diese Dateien im Ordner MQL5\Files
autoencoders.ipynb Python Jupyter-Notebook zur Ausführung des gesamten besprochenen Python-Codes 




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

Beigefügte Dateien |
Code_6_Files.zip (689.5 KB)
Wie man ein volatilitätsbasiertes Handelssystem (Chaikin Volatility - CHV) aufbaut und optimiert Wie man ein volatilitätsbasiertes Handelssystem (Chaikin Volatility - CHV) aufbaut und optimiert
In diesem Artikel werden wir einen weiteren, volatilitätsbasierten Indikator namens Chaikin Volatility vorstellen. Wir werden verstehen, wie man einen nutzerdefinierten Indikator erstellt, nachdem wir herausgefunden haben, wie er verwendet und aufgebaut werden kann. Wir werden einige einfache Strategien vorstellen, die verwendet werden können, und sie dann testen, um zu verstehen, welche davon besser sein kann.
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.
Aufbau eines Modells von Kerzen, Trend und Nebenbedingungen (Teil 2): Zusammenführung integrierter Indikatoren Aufbau eines Modells von Kerzen, Trend und Nebenbedingungen (Teil 2): Zusammenführung integrierter Indikatoren
In diesem Artikel geht es darum, die Vorteile der im MetaTrader 5 integrierten Indikatoren zu nutzen, um Signale abseits eines Trends zu erkennen. In Fortführung des vorherigen Artikels werden wir untersuchen, wie wir unsere Idee mit Hilfe von MQL5-Code in das endgültige Programm übertragen können.
Bewältigung der Herausforderungen bei der ONNX-Integration Bewältigung der Herausforderungen bei der ONNX-Integration
ONNX ist ein großartiges Werkzeug für die Integration von komplexem KI-Code zwischen verschiedenen Plattformen. Es ist ein großartiges Werkzeug, das einige Herausforderungen mit sich bringt, die man angehen muss, um das Beste daraus zu machen.