English Deutsch
preview
データサイエンスと機械学習(第22回):オートエンコーダニューラルネットワークを活用してノイズからシグナルへと移行することで、よりスマートな取引を実現する

データサイエンスと機械学習(第22回):オートエンコーダニューラルネットワークを活用してノイズからシグナルへと移行することで、よりスマートな取引を実現する

MetaTrader 5インディケータ | 31 5月 2024, 10:41
328 0
Omega J Msigwa
Omega J Msigwa

オートエンコーダとは何か

オートエンコーダは教師なし人工ニューラルネットワークです。最も単純な形では、オートエンコーダは、入力データを低次元に圧縮し、このデータの低次元表現を使用して元の入力を再作成するという2つのことを試みるニューラルネットワークです。

オートエンコーダにぼやけた猫の画像が渡されたとします。この画像は圧縮され、解凍されて元の状態に戻り、その過程でノイズやぼやけたピクセルの一部が失われ、最終的に鮮明な猫の画像になります。

ぼかした猫とぼかさない猫の画像

この記事では、オートエンコーダニューラルネットワークを金融分野でどのように使用し、市場のノイズを除去して取引機会を発見できるようにするかを見ていきます。

この記事は、ONNXPCAニューラルネットワーク全般についての基本的な理解があれば簡単に読めます。

オートエンコーダは2つの部分で構成されます。

  1. エンコーダ:入力データを低次元の潜在表現に圧縮し、本質的な特徴を捉える
  2. デコーダ:潜在表現を受け取り、元の入力データをできるだけ正確に復元しようとする

オートエンコーダの利点

  • FX取引データの圧縮表現を学習できるため、次元削減タスクに有用であり、高次元データセットにおける特徴抽出、データ圧縮、可視化などのタスクに役立ちます。
  • 入力データの再構成を試みることで、オートエンコーダは本質的な特徴を学習し、ノイズや無関係な情報を除去します。これらの学習された特徴は、分類や異常検知のような他の機械学習タスクに有益です。
  • 教師なしなので、人の手を借りずに取引データの隠れたパターンを発見することができます。
  • オートエンコーダから学習された潜在表現は、他のモデルの事前学習された特徴として使用することができ、その性能を向上させる可能性があります。


オートエンコーダの構成

オートエンコーダを解剖し、それらが何で構成され、何が特別なのかを観察してみましょう。

オートエンコーダの中核には、3つの部分からなる人工ニューラルネットワークがあります。

  1. エンコーダ
  2. 埋め込みベクトル/潜在層
  3. デコーダ

シンプルなオートエンコーダアーキテクチャ

ニューラルネットワークの左側はエンコーダと呼ばれ、元の入力データを低次元表現に変換します。

ニューラルネットワークの中央部分は潜在層または埋め込みベクトルと呼ばれ、入力データを低次元データに圧縮する役割を担います。この層には、エンコーダとデコーダの両方よりもニューロンが少ないことが予想されます。

このニューラルネットワークの右の部分はデコーダと呼ばれ、エンコーダの出力を使用して元の入力を再現するのが仕事です。

これは、デコーダがエンコーダから返された低次元のデータから高次元のデータを再現しようとするからです。家の写真を見て家を建てようとするようなものです。

1Dハウスと3Dの比較

このため、このプロセス全体が機能するための鍵となる、情報損失が発生します。デコーダが不完全な情報を持つようにし、ネットワーク全体を訓練することで、構築誤差を最小にします。訓練の間、エンコーダとデコーダは、構築誤差を最小にするために協力せざるを得ません。

構築誤差とは、再現しようとしたデータと元の入力データとの差のことです。

もしエンコーダとデコーダの間に情報損失がなければ、ネットワークは単に入力を1倍することを学習して完全な再構成を得るので、オートエンコーダが役に立たなくなります。ある程度の誤差を持つエンコーダを持つことは、この機械学習技術にとって非常に重要であり、モデルを過剰適合させないようにします

エンコーダもデコーダも、上に示したオートエンコーダアーキテクチャの画像に見られるように、単一の層に限定されるものではありません。以下のPythonコードに、エンコーダとデコーダの両方の層のニューロンを格納するためのhidden_dimsという名前のリストがあるように、複数の層を含むことができます。

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

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)

以下は、オートエンコーダのアーキテクチャの様子です。

12,10 オートエンコーダアーキテクチャ

Autoencoderクラスでは、エンコーダとデコーダの両方でReLU (Rectified Linear Unit)が使用されているのを見ました。この活性化関数は、ほとんどのオートエンコーダで広く使用されています。これには重要な理由があります。

ReLUは計算効率が高く、消失勾配を回避し、取引データに通常見られるスパース表現を学習することができます。GELUやLeaky ReLUのようなReLUの他のバリエーションは、金融データを扱う際に役立ちます

シグモイドや双曲線正接(TANH)のような他の一般的な活性化関数は有用ですが、取引データに使用する前に、それらの長所と短所を理解する必要があります。

シグモイド

  • 長所:出力が0と1の間(ピクセル強度を表す)である必要がある画像再構成によく使用される
  • 短所:特にディープアーキテクチャでは、バックプロパゲーション中に勾配が消失する可能性があるため、金融データには不向きな場合がある
    シグモイドをオートエンコーダに適用すると、ネットワークは局所的な極小値に向かって振動し続け、収束に失敗しなかった
    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(双曲線正接)

  • 長所:出力は-1から1の間で、シグモイドに似ているが、勾配が急で、収束が速くなる可能性がある
  • 短所:非常に深いネットワークでは、勾配の消失に悩まされることがある

これらのシグモイド活性化関数やTANH活性化関数、その他この種の関数は、入力データを可能な限り正確に再構成するために、デコーダの出力層で使用するのが最も効果的です。この文脈では、オートエンコーダの出力は元の入力に似ていなければなりません。入力データは、前処理によって[0, 1]または[-1, 1]の範囲に正規化されることが多いので、出力値をこの範囲にスケーリングするためにシグモイド活性化関数が一般的に使用されます。

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


MinMaxScalerが友達

オートエンコーダはコード化するのも導入するのも簡単ですが、うまく機能させるためには適切な情報とツールを与える必要があります。なぜ活性化関数の選択がこのニューラルネットワークにとって重要なのか、そしてスケーリング技術も重要なのか、先ほど見てきたとおりです。

ReLU活性化関数を使用しているので、ゼロ以下の値が与えられたときはゼロを返し、そうでないときは与えられた値を返します(x = 0 when x<=0 else x = x)。

Standard-Scalerを使用すると、平均を引くことによってデータを中心に置き、単位分散になるようにスケーリングします。これは、標準化の際に、大きな正の値を持つ外れ値を非常に負の値(潜在的には-1)に押しやる可能性があります。外れ値から標準化された値が-1になった場合、この負の値が渡されると、エンコーダのReLU活性化関数は、その特定の特徴に対して常に0を出力します。

これは死んだReLUニューロン呼ばれる現象につながり、エンコーダの一部のニューロンは、このような負の入力値のために決して活性化されません。これらの瀕死のReLUニューロンは本質的に非アクティブになり、エンコードプロセスに寄与しないため、エンコーダでの学習を妨げる可能性があります。取引データ内のほとんどの外れ値またはスパイクは、ほとんど平坦であると予測されます。Standard-scalerが使用された下の画像を参照してください。

  StandardScalerオートエンコーダ

この問題への対処

データを0から1の間の特定の範囲にスケーリングするMinMaxScalerなど、他の正規化テクニックを試すことで、ReLU問題の原因となる-1値の生成を回避できる可能性があります。ただし、MinMaxScalerの限界を考慮すると、RobustScalerを考慮することもできます。これはStandardScalerよりも外れ値の影響を受けにくく、ReLUの活性化に対してより良いスケーリングを提供できる可能性があります。

また、標準のReLUの代わりにLeaky ReLU(x <= 0の場合はleaky_relu = 0.01x、x > 0の場合はrelu = x)を使用することも検討してください。Leaky ReLUは、負の入力に対しても小さな非ゼロ勾配を許容し、死んだReLU問題を緩和します。


オートエンコーダの訓練

さて、オートエンコーダの基本について簡単に説明したところで、オートエンコーダを訓練し、それを取引にどのように役立てることができるかを見てみましょう。

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)

エンコーダには[256, 128, 64]、デコーダには[64,128, 256]という複雑なニューラルネットワークアーキテクチャを採用し、潜在層には32のニューロンを配置しました。

このように複雑なニューラルネットワークでは、訓練データを過剰適合する可能性が高くなります。これは単なる例なので、ご自由により単純なアーキテクチャから始めてください。 

出力

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

損失と反復のグラフ

損失対反復プロット

データをオートエンコーダに渡し、その結果を観察してみましょう。

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)

出力

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]]

終値を視覚化することにしました。

終値とオートエンコード価格

オートエンコーダを通過した新しいデータには、ノイズフィルタがかかっており、チャートを見るだけで外れ値を検出するのは簡単であると結論づけられます。オートエンコーダが機能することを確認したところで、オートエンコーダの適用と、それをMQL5ベースのプログラムで最終的にどのように使用することができるかについて説明しましょう。


オートエンコーダの適用

オートエンコーダは、次元削減、特徴学習、異常検出、推薦システム、画像ノイズ除去など、工学、医療分野、エンターテインメントなど、さまざまな分野や産業で使用されています。

次元削減

オートエンコーダは、高次元のデータを低次元の潜在空間に圧縮することに優れています。これは、多数の特徴を含むデータセットを扱う場合に特に有用で、よりコンパクトな表現で本質的な特徴を捉えることができます。

  • 処理する特徴の数を減らすことで、後続の機械学習タスクの計算効率を向上させます。 
  • 学習した潜在空間に主成分分析(PCA)のような次元削減技術を適用できるようにすることで、高次元データの可視化を強化します。

このタスクを達成するためには、ニューラルネットワークのエンコーダ部分だけを使用する必要があります。 

Autoencoderクラスが開始された直後に呼び出されるはずのbuild関数を追加して、Autoencoderクラスを修正する必要があります。このメソッドは、入力データの形状に基づいて動的に層を作成するのに便利で、形状が判明するまで層の作成を遅らせることができます。

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

クラス関数の呼び出し方法も少し変更する必要があります。先に述べたように、ニューラルネットワークモデルをコンパイルして訓練する前にbuild関数を呼び出すことになっています。クラスメソッドを呼び出す順番は重要です。

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())

これでbuild関数が整ったので、オートエンコーダがエラーなく正常に訓練された後、ようやくエンコーダとデコーダのニューラルネットワークを別々に取り出すことができます。

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)

エンコーダができれば、情報を渡して潜在層(空間)を通過した結果行列を得ることができます。

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()

列数encoded_data.shape[1]をPCA成分に割り当てることで、各特徴の説明分散を測定し、データ次元を縮小するためにPCAに適用する最適な成分数を理解するのに役立つスクリープロットを描くことができます。

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スクリープロット

累積説明分散を見ると、説明された分散の比率がほとんど1、いくつかの構成要素では1に近いことがわかります。これは、多くの情報を失うことなく、大幅な次元削減を達成できる可能性があることを意味しています。
スクリープロットは、ほぼ2成分でエルボーポイントを示しています。これは約0.9989の全分散を説明し、これは私たちのデータを削減するのに最適な成分数です。1つの軸にプロットしたとき、成分間に大きな違いは見られなかったので、1成分でもうまくいくはずです。

次にPCAクラスが呼び出されるときは、2つの成分を得るために2の値を適用して呼び出されなければなりません。

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

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

結果

pca reduced data.shape:  (10000, 2)

潜在層からの全32成分を1つの軸にプロットすることにしました。チャート上でほとんど同じに見えた他の特徴と非常に異なっていたのは1つの特徴だけでした。これにより、この削減されたデータ内のいくつかの成分が意味をなすことが明らかになります。

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")

成分対インデックスのプロット:

PCA成分プロット

オートエンコーダの潜在空間にPCAを適用することで、元の高次元データに直接PCAを適用するのに比べて、削減プロセスをより細かく制御できるようになり、プロセス中のデータ内の不要なノイズの削減にも役立ちます。

部屋の中の象

説明した例では、すべての入力データの次元を削減しましたが、予測モデルでPCA後に削減されたデータを適用する場合には理想的ではない可能性があります。その場合は、独立変数にのみPCAを適用する必要があります。


ただし、MetaTrader 5の取引データからノイズを減らすために作成したこのオートエンコーダを別のオートエンコーダアプリケーションとして使用する前に、ONNX形式に保存する必要があります。


オートエンコーダモデルをONNX形式に保存する

すでにエンコーダとデコーダの両方を抽出してから、次元削減のために適用しているので、ONNX形式で保存するのは簡単でしょう。両方を別々に保存するので、まずはエンコーダモデルから始めましょう。

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)

ONNX用のinput_signatureは、TensorFlowとONNXの最新バージョンでエラーを回避するのに役立ちます。これは、MetaTrader 5でこの形式のモデルを読み込む際に、.onnxファイルの入力名を明確にするのに役立つからです。

デコーダモデルの保存

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)

ONNX統合の課題を克服する」稿で、私はPythonとMQL5プログラミング言語の両方で利用可能な同じ次元削減とスケーリング技術を精密に統合するという問題に取り組みましたが、スケーリングの問題を軽減する簡単な解決策を見つけました。

スケーラーの保存

PythonとMQL5で同じスケーラーを使用することは非常に重要です。 これがどれほど重要なことなのかは、いくら強調しても足りません。

Python

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

MinMaxScaler情報配列をシンプルなバイナリファイルに保存し、MetaTrader 5指標に含めることができます。MQL5\Filesフォルダ下に保存しました。

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[];


取引データからノイズを減らす

画像からノイズを除去するなどのさまざまな局面で見られるように、オートエンコーダはデータからノイズを除去することができます。終値と新しい終値の画像を見ると、オートエンコードされた終値のノイズが少ないことは明らかです。オートエンコーダによって提供される新しいOHLCのローソク足を描くのに役立つ指標を作成しましょう。

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

MQL5で読み込まれたONNXモデルを、Pythonで使用しているかのように簡単に使用できるようにするために、Autoencoderクラスを作成する必要があります。

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
  };

CAutoEncoderONNXクラスをそのままモデルごとにインスタンス化します。

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

モデルを初期化します。

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);
  }

モデルから予測を得るために、生データをエンコーダに渡し、その結果をデコーダに渡して最終的な出力を得ます。思い出してください。Pythonでは、call関数の中で2つの別々のモデルを次々に渡していました。

Python

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

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

これをMQL5で実際に見てみましょう。

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);
  }

指標のプロット

ローソク足のようなオートエンコーダ同時線

私の観察によると、オートエンコーダによって作られたローソク足の実体サイズはほとんど同じであり、下値と上値の差は大きく、すべてのローソク足でほとんど同じです。

ほとんどのローソク足が赤色の弱気相場で、ごく少数のローソク足が灰色の強気相場です。

この指標をチャート上にうまく表示させるには、強気ローソク足と弱気ローソク足の両方について、ローソク足の下値と上値の間を埋めればよいです。

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];
   }

指標のプロット

オートエンコードされた新しいバー

指標に、市場の実際の始値と終値に基づいて、強気ローソク足と弱気ローソク足を区別するオプションを与えることができます。

MQL5 (AutoEncoder Indicator.mq5)

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

指標のプロット

オートエンコードされたOHLC EURUSDカラーローソク足

また、元のローソク足を隠して、オートエンコーダで作られた新しいローソク足だけにするオプションもあります。

オートエンコーダ指標show bars=false


オートエンコーダの欠点

オートエンコーダは、他の機械学習モデルと同様、独自の課題を伴います。

  1. 不完全なデータ再構成
    オートエンコーダは、データを圧縮した後に再作成しようとします。時には、それらがうまく機能せず、元データの再構成にエラーが生じることもありまる。これは、元のデータを非常に正確に再現する必要がある場合に問題となります。

  2. 理解するのが難しい
    オートエンコーダが生成する圧縮データ形式は、解釈が難しい場合があります。オートエンコーダがどのような特徴を捉えているのかが明確でないことが多く、モデルがどのように機能するのかを説明するのが難しいです。

  3. ノイズに敏感
    オートエンコーダは、データの主要なパターンを強調することを目的としているが、ノイズや外れ値に苦労することがあります。その結果、再構成がうまくいかなかったり、特徴が偏ってしまったりする可能性があり、理想的とは言えません。

  4. 次元のボトルネック
    オートエンコーダの中間層(データが圧縮される部分)は、時に小さすぎることがあります。十分な寸法がなければ、やるべきことのために重要な情報をすべて把握できないかもしれません。この層の適切なサイズを選ぶことが鍵で、何を達成しようとしているかによります。

  5. 訓練に費用がかかる
    ディープオートエンコーダの訓練、特に大規模なデータセットの訓練には、多くの計算能力が必要となります。リソースや時間が限られている場合は、この点に留意する必要があります。

  6. すべての作業に適しているわけではない
    オートエンコーダは、分類や回帰のような、入力データを直接扱う方が効果的なタスクには最適な選択ではないかもしれません。

  7. 過剰適合のリスク
    単純な問題に対して複雑なモデルを使用すると、過剰適合を引き起こす可能性があります。つまり、モデルは学習データをうまく学習しすぎるが、新しい未知のデータではパフォーマンスが低下してしまうのです。



最後に

オートエンコーダは、外国為替市場のノイズを削減する優れたツールです。指標に見られるように、ノイズの少ないローソク足が得られましたが、それでも市場を反映しています。元のローソク足よりも良い場合もあれば、悪い場合もあります。これらの新しいローソク足により、市場の異なる視点が得られます。

パターンからシグナルを抽出し、取引戦略を構築することで、新しいローソク足を自由に探求してください。

では。

以下は添付ファイルの表です。 

ファイル 説明と使用法
Include\MatrixExtend.mqh
行列操作のための追加関数
Include\preprocessing.mqh
生の入力データを機械学習モデルの使用に適するように前処理するためのライブラリ
Indicators\AutoEncoder Indicator.mq5 メインの指標ファイル(説明されたオートエンコーダを展開し、結果予測にロウソク足を描画する)
Include\Autoencoder-onnx.mqh  機械学習モデルをONNX形式で読み込み、結果を解釈するためのライブラリ
Files\...  これらのファイルをMQL5Filesフォルダに保存
autoencoders.ipynb すべてのPythonコードを実行するためのPython Jupyterノートブック 




MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14760

添付されたファイル |
Code_6_Files.zip (689.5 KB)
ONNX統合の課題を克服する ONNX統合の課題を克服する
ONNXは、異なるプラットフォーム間で複雑なAIコードを統合するための素晴らしいツールです。ただし、この素晴らしいツールを最大限に活用するためにはいくつかの課題に対処する必要があります。この記事では、読者が直面する可能性のある一般的な問題と、それを軽減する方法について説明します。
フェアバリューギャップ(FVG)/不均衡取引方法をステップバイステップで学ぶ:スマートマネーコンセプトのアプローチ フェアバリューギャップ(FVG)/不均衡取引方法をステップバイステップで学ぶ:スマートマネーコンセプトのアプローチ
フェアバリューギャップ(FVG)取引戦略に基づいて、MQL5で自動売買アルゴリズムを作成して実装するためのステップバイステップのガイドです。初心者にも経験豊富なトレーダーにも役立つエキスパートアドバイザー(EA)の作成に関する詳細なチュートリアルです。
知っておくべきMQL5ウィザードのテクニック(第16回):固有ベクトルによる主成分分析 知っておくべきMQL5ウィザードのテクニック(第16回):固有ベクトルによる主成分分析
データ分析における次元削減技術である主成分分析について、固有値とベクトルを用いてどのように実装できるかを考察します。いつものように、MQL5ウィザードで使用可能なExpertSignalクラスのプロトタイプの開発を目指します。
知っておくべきMQL5ウィザードのテクニック(第15回):ニュートンの多項式を用いたサポートベクトルマシン 知っておくべきMQL5ウィザードのテクニック(第15回):ニュートンの多項式を用いたサポートベクトルマシン
サポートベクトルマシンは、データの次元を増やす効果を調べることで、あらかじめ定義されたクラスに基づいてデータを分類します。これは教師あり学習法で、多次元のデータを扱う可能性を考えるとかなり複雑です。この記事では、2次元データの非常に基本的な実装であるニュートンの多項式が、価格とアクションを分類する際にどのように効率的に実行できるかを検討します。