English Русский 中文 Español Português
preview
取引におけるニューラルネットワーク:多変量時系列予測のためのLSTMの最適化(DA-CG-LSTM)

取引におけるニューラルネットワーク:多変量時系列予測のためのLSTMの最適化(DA-CG-LSTM)

MetaTrader 5トレーディングシステム |
25 2
Dmitriy Gizlyk
Dmitriy Gizlyk

はじめに

金融市場は、単なる画面上の数字の集合ではありません。そこは、あらゆるティック、ローソク足、取引量の変化が、人間の感情、期待、不安、希望を反映する動的な環境です。この市場のリズムを理解し、価格がどの方向へ動くのかを予測することは、トレーダーが絶えず挑戦してきた課題です。

本記事が注目するのは、多変量時系列データです。これは市場データを表現する古典的な形式であり、時間経過に伴う資産価格、取引量、テクニカル指標、ニュースの流れなどが含まれます。これらはいずれも分析・モデル化が可能であり、最終的には予測に活用できるデータです。

これまで金融業界では、主にARIMA、SARIMAなどの古典的な時系列モデルが利用されてきました。これらのモデルは実用性が高く、解釈しやすいうえ、大規模な計算リソースを必要としません。特に市場環境が安定している場合、季節性や線形的な依存関係を扱う上で一定の成果を上げてきました。しかし、金融市場は非定常的です。ニュースは市場参加者の期待に影響を与え、投資家心理は数秒で変化し、アルゴリズム取引は相互作用による共振効果を生み出します。その結果、市場には複雑で非線形、そして時にはカオス的な関係性が形成されます。従来型モデルは大まかな方向性を示すことはできますが、細かな変化を捉えることには限界があります。

ディープラーニングの登場は、この状況を大きく変えました。リカレントニューラルネットワーク(RNN)は、過去の時系列的な動きをモデルに反映できるようにしました。しかし、万能な解決策にはなりませんでした。最大の問題は、いわゆる「短期記憶問題」です。つまり、RNNは限られた時間範囲の情報しか処理できません。入力シーケンスが長くなるにつれて、時系列の初期部分に含まれる重要な情報を急速に失ってしまいます。

この問題を解決するため、LSTM(Long Short-Term Memory:長短期記憶)やGRU(Gated Recurrent Unit:ゲート付きリカレントユニット)といった改良型アーキテクチャが開発されました。これらのモデルはニューラルネットワークに記憶を与え、長期間にわたって重要な情報を保持できるようにしました。

しかし、これらのアーキテクチャにも限界があります。長期的な依存関係を捉える能力を持つ一方で、入力データの品質に大きく依存します。特に、短期的な市場急変が重要な意味を持つ状況では十分に対応できない場合があります。短期的な変化は、長期的な市場コンテキストに大きな変化を伴わない限り、モデルの長期記憶には十分に保持されないことがあります。

この問題を解決するために導入されたのがアテンション機構です。アテンション機構は、現在の時点からどれだけ離れているかに関係なく、時系列の中で最も重要な部分にモデルが選択的に注目できるようにしました。LSTMとは異なり、アテンション機構ではすべての時間ステップを順番に処理する必要がなく、重要な情報へ直接集中できます。その結果、特に複雑な多変量時系列データにおいて、ニューラルネットワークが長期的な依存関係を捉える能力が大幅に向上しました。

しかし、このアプローチにも弱点があります。アテンションベースのモデルは長期的な依存関係の把握には優れていますが、市場にとって極めて重要な短期シグナルを見落とす傾向があります。金融市場では遅延は許容されません。たった1つのニュース速報が、価格を急騰または急落させる可能性があります。モデルがこうしたイベントに迅速に反応できなければ、適切なタイミングでポジションを調整する機会を失います。

論文「A Dual-Staged Attention Based Conversion-Gated Long Short Term Memory for Multivariable Time Series Prediction」の著者らは、これら2つのアプローチの長所を組み合わせることを試み、新たなフレームワークであるDual-Staged Attention Conversion-Gated LSTM (DA-CG-LSTM)を提案しました。このモデルは、二段階のアテンション機構を採用しています。第1段階では、入力特徴量と時間区間の重要度を評価します。その後、専用のCG-LSTMブロックでデータを処理し、第2段階で時間的依存関係を再評価します。これにより、重要なシグナルを強調しながら、関連性の低い情報を抑制します。

さらに、このモデルの大きな特徴として、慎重に設計された活性化関数があります。これにより、長期的な情報保持能力と、短期的な市場変動への応答性の両方が向上しています。


DA-CG-LSTMアルゴリズム

DA-CG-LSTMフレームワークは、多変量時系列データから意味のあるパターンを抽出することを目的として設計されています。そのアーキテクチャは、2層のアテンション機構と、Conversion-Gated LSTM (CG-LSTM)と呼ばれる改良型リカレントブロックを組み合わせています。これにより、本モデルは動的な多変量プロセスの予測に特に有効となります。

処理は、多変量時系列シーケンスをモデルに入力するところから始まります。このシーケンスは行列X = [x1, x2, ..., xT] ∈ RT*nで表現されます。ここで、各行x tは時刻tにおけるn個の特徴量からなるベクトルです。しかし、モデルはこの情報をそのまま機械的に処理するわけではありません。まず、どの特徴量が重要であり、どの時間ステップが意味を持つのかを学習します。これが第1段階である「入力アテンション」です。

まずモデルは、各時刻における特徴量を分析します。そして、時刻tにおける各特徴量xktの重要度スコアを、以下の式に基づいて計算します。

ここで、Webeは、各入力特徴量ベクトルxtに適用される線形層の学習可能な重みとバイアスを表します。この線形層は、元のデータをより情報量の多い潜在表現へ変換し、各特徴量の関連性を評価できる形にします。また、学習可能なベクトルveは、解釈可能なアテンションマスクとして機能し、スカラー形式の重要度スコアを計算するために使用されます。この設計により、モデルは各特徴量が予測にどの程度貢献しているかを柔軟に評価できます。これは、ノイズを含む高次元時系列データを扱う場合に特に有効です。

算出された重要度係数は、SoftMax関数によって正規化されます。これにより、各特徴量に対するスコアが確率分布へ変換されます。

その後、この係数を用いて元の入力ベクトルxtを調整します。つまり、各特徴量は推定された重要度に応じて重み付けされます。

しかし、ここで終わりではありません。次の段階では、モデルは時間軸上の各位置の重要度を評価し、どの時点の情報を重点的に処理すべきかを判断します。同様の構造が適用されます。

その結果、得られるシーケンスは時間的な重み付けを施した表現になります。

つまり、リカレント処理が始まる前の段階ですでに、モデルは自ら重要と判断した情報へ注意を集中させています。

次に、このシーケンスは改良型リカレントブロックであるCG-LSTMへ入力されます。ここから処理の第2段階が始まります。標準的なLSTMアーキテクチャでは、著者らは入力ゲートと忘却ゲートに関連する活性化関数を再設計しました。この変更の目的は、短期的な市場変動に伴う急激なスパイクへの感度を高めると同時に、長期的な情報保持能力を強化することです。

従来のLSTMブロックでは、入力ゲートはシグモイド関数を使用して、どの情報を保持すべきかを判断します。しかし、シグモイド関数には飽和問題があります。入力値が極端に小さい、または大きい場合、微分値がゼロに近づき、学習効率が低下します。DA-CG-LSTMフレームワークでは、この問題を解決するため、シグモイド関数と双曲線正接関数(tanh)を組み合わせた新しい関数を提案しています。

この設計により、学習初期段階における飽和を防ぎながら、微細ではあるものの重要な変動への感度を維持できます。金融時系列データでは、この能力が特に重要です。たとえば、突然の取引量増加やボラティリティの急上昇は、市場レジームの変化を示している可能性があります。このようなパターンを早期に検出できれば、予測において大きな優位性を得られます。

CG-LSTMでは忘却ゲートも変更されています。標準的なLSTMアーキテクチャとは異なり、シグモイド関数と変換された双曲線正接関数を組み合わせた関数を使用します。

この関数には特徴的な性質があります。その微分値は0から約2.89までの範囲を持ち、データ分散効果を生み出します。実際には、この性質により、モデルは不要または古くなった情報をより積極的に破棄し、最新の変化へ集中できるようになります。これは金融市場において非常に重要です。過去の出来事は急速に関連性を失い、成功する予測には現在のシグナルへの迅速な反応が求められるためです。

CG-LSTMブロックの出力はシーケンス(h1, h2, ..., hT)となります。ここで、各htは短期情報と長期情報の両方を含んでいます。しかし、情報を保持するだけでは十分ではありません。重要なのは、その情報を正しく呼び出すことです。その役割を担うのが2番目のアテンション層である時間アテンションです。

この段階で、モデルは自身の記憶を再評価します。現在の時点に対して、各隠れ状態hjがどれほど重要であるかを計算します。その値はSoftMaxによって正規化され、コンテキストベクトルが構築されます。このコンテキストベクトルは、時系列全体の履歴を凝縮した本質的表現であり、その後、2番目のCG-LSTMブロックによって処理されます。

現在の隠れ状態htと拡張されたコンテキストctを組み合わせることで、モデルは最終的な予測値を生成します。

ここで、fは通常、全結合層またはモデルの出力層に相当するコンポーネントです。

DA-CG-LSTMアーキテクチャは、単に高性能なだけでなく、妥当な判断を下せる設計になっています。このモデルは、すべての情報を記憶するわけではありません。重要な情報を選択的に取り込みます。また、あらゆるノイズに反応するのではなく、一時的な変動と意味のあるパターンを区別することを学習します。この能力は、金融予測において特に重要です。システムは、過去に観測されたものと類似したシグナルを識別し、その重要度を評価した上で、予測を調整できます。

この意味で、DA-CG-LSTMは、構造化された情報分析を通じて学習し、進化し、判断を導く動的・適応型システムとして機能します。その強みは、数学的な定式化だけでなく、以下の明確な概念にもあります。

注意+記憶+解釈=有意義な予測

著者によるDA-CG-LSTMフレームワークの可視化を以下に示します。

DA-CG-LSTMフレームワークの独自の可視化



MQL5での実装

DA-CG-LSTMフレームワークの理論的側面を検討したので、実践的な内容に移り、提案された手法をMQL5を用いて独自に実装した内容を紹介します。

まず、著者による以下の図に示されている改良型CG-LSTMブロックの構築から始めます。

重要な点として、従来のLSTMブロックと同様に、入力テンソルには前の時間ステップで生成された隠れ状態が結合されます。その結果得られるテンソルを基に、3つのゲートと新しいコンテキスト表現という4つの要素が生成されます。これらの要素は、それぞれ独立した線形層によって生成されます。ただし、これらの線形層の出力に異なる活性化関数を適用することで、非線形な挙動が導入されます。

4つの要素それぞれに異なる活性化関数を使用する場合、本来であれば4つの全結合層を順番に実行する必要があります。しかし、これは最適な方法とは言えません。本実装では、処理の並列性を最大限に高めることを重視しています。なぜなら、高い並列性はモデルの学習速度だけでなく、実環境における意思決定処理の高速化にもつながるためです。

従来型LSTMブロックを実装した際には、フィードフォワード処理全体を単一のカーネル内にまとめました。各要素の値は、ワークグループ内のスレッドによって同時並列的に計算され、中間値はローカルメモリを介して共有されます。この方式は非常に高い効率を示しました。しかし、DA-CG-LSTMフレームワークの著者が提案した、より複雑な活性化関数の組み合わせを使用する場合、状況は異なります。途中計算の結果を保持するための追加データバッファが必要となり、アルゴリズムの構造が大幅に複雑になります。

そこで今回の実装では、別のアプローチを採用しました。フィードフォワード処理を以下の2段階に分割します。最初の段階では、活性化関数を適用せずに4つのエンティティすべてが生成されます。これは、完全結合層を使用することによって、または出力テンソルのサイズが生成されるエンティティ数に比例するような畳み込み層を使用することによって実現できます。多次元入力データを扱う場合、畳み込み層がより適しています。これは、個々の単変量系列に対して独立した処理を実行できるためです。

第2段階では、必要な活性化関数を適用し、ブロック内部の処理を実行することで、実際のCG-LSTM計算をおこないます。

当然ながら、まずOpenCLコンテキスト内で第2段階の実装から開始します。

OpenCLプログラムの変更


最初に、CSLSTM_FeedForwardカーネル内に、CG-LSTMブロックの実装におけるフィードフォワード処理を実装します。このカーネルは、3つのデータバッファへのポインタのみを受け取ります。

これらのバッファのうち1つには入力データ(concatenated)が格納されています。このデータには、活性化関数を適用する前の4つの構成要素の値が保存されています。グローバルメモリアクセスに伴うレイテンシを低減し、読み取り性能を向上させるため、値はfloat4ベクトル型を使用して格納されています。これにより、4つの連続した要素を1回のメモリアクセスで取得できます。このデータ配置により、メモリ帯域幅をより効率的に利用でき、大量の入力データを処理する際に特に計算速度を向上させることができます。

残りの2つのバッファは、コンテキスト値および出力値を格納するために使用されます。

__kernel void CSLSTM_FeedForward(__global const float4* __attribute__((aligned(16))) concatenated,
                                 __global float *memory,
                                 __global float *output)
  {
   uint id = (uint)get_global_id(0);
   uint total = (uint)get_global_size(0);       // hidden size
   uint idv = (uint)get_global_id(1);
   uint total_v = (uint)get_global_size(1);     // variables

このカーネルは、明示的なワークグループを構成せずに、2次元の実行空間内で動作します。第1次元はセルの隠れ状態のサイズに対応し、第2次元は入力データに含まれる単変量系列の数を表します。カーネル本体では、タスク空間のすべての次元でスレッドを識別します。

この情報を使用して、データバッファ内の適切なオフセットを計算し、対応する入力データのブロックを直ちにローカル変数へ読み込みます。

uint shift = id + total * idv;
float4 concat = concatenated[shift];

次に、必要な活性化関数をすべてのエンティティに適用します。

float fg = 1 - Activation(1 - 1 / pow(Activation(concat.s0, ActFunc_SIGMOID), 2), ActFunc_TANH);
float ig = Activation(Activation(concat.s1, ActFunc_SIGMOID), ActFunc_TANH);
float nc = Activation(concat.s2, ActFunc_TANH);
float og = Activation(concat.s3, ActFunc_SIGMOID);

その後、コンテキスト値および隠れ状態の両方を更新します。

float mem = IsNaNOrInf(memory[shift] * fg + ig * nc, 0);
float out = IsNaNOrInf(og * Activation(mem, ActFunc_TANH), 0);

得られた値を対応するグローバルバッファに書き戻して、カーネル処理を終了します。

 memory[shift] = mem;
 output[shift] = out;
} 

得られたカーネルコードは、主に活性化関数を選択する役割を担うヘルパーメソッドを使用しているため、コンパクトで読みやすい構成になっています。このアプローチにより、カーネル自体のロジックが簡素化されるだけでなく、コードのモジュール性および拡張性も向上します。さらに、計算結果を検証する仕組みを組み込むことで、計算パイプライン全体の信頼性を高めることができます。

フィードフォワードカーネルの実装を完了した後、逆伝播処理の実装に進みます。フィードフォワード処理の実行中には、学習可能なパラメータが使用されていないことは容易に確認できます。すべての学習可能なパラメータは、第1段階で使用されるニューラル層に配置されています。そのため、逆伝播処理の実装では、参加する各構成要素に対して誤差勾配を正しく分配する処理のみが必要となります。これらの処理は、CSLSTM_CalcHiddenGradientカーネル内で実行されます。

勾配分配カーネルのパラメータには、適切なデータバッファが追加されますが、元の実行空間は維持されます。

__kernel void CSLSTM_CalcHiddenGradient(__global const float4* __attribute__((aligned(16))) concatenated,
                                        __global float4* __attribute__((aligned(16))) grad_concat,
                                        __global const float* memory,
                                        __global const float* grad_output
                                       )
  {
   uint id = get_global_id(0);
   uint total = get_global_size(0);
   uint idv = get_global_id(1);
   uint shift = id + total * idv;

カーネル本体では、すべての次元における現在のスレッドを識別し、対応するデータバッファ内のオフセットを直ちに計算します。

なお、DA-CG-LSTMフレームワークで提案されている複雑な活性化関数の導関数を計算するためには、フィードフォワード処理中に意図的に保存されなかった複数の中間値が必要になります。これらの値を保存しておく場合、相当量のメモリリソースが必要になることは明らかです。さらに、グローバルデータバッファへのアクセスは高コストな処理です。しかし、行列計算の大部分は第1段階の内部ニューラル層に委譲されているため、活性化前のエンティティ値から必要な値を迅速に再計算することが可能です。

これを実現するために、活性化前のデータをローカル変数へ読み込み、関数を再計算することで、中間結果をローカルに保存します。 

   float4 concat = concatenated[shift];                    // Pre-activation values for all 4 gates
// --- Forward reconstruction of gates ---
   float fg_s = Activation(concat.s0, ActFunc_SIGMOID);
   float fg = 1.0f - Activation(1.0f - 1.0f / pow(fg_s, 2), ActFunc_TANH);  // Forget gate (ft)
   float ig_s = Activation(concat.s1, ActFunc_SIGMOID);
   float ig = Activation(ig_s, ActFunc_TANH);              // Input gate (it)
   float nc = Activation(concat.s2, ActFunc_TANH);         // New content (ct~)
   float og = Activation(concat.s3, ActFunc_SIGMOID);      // Output gate (ot)
   float mem = memory[shift];                              // New memory state (ct)
   float mem_t = Activation(mem, ActFunc_TANH);            // tanh(ct)

この段階では、前の時間ステップにおけるメモリ状態も再構築します。

// --- Reconstruct previous memory state (t-1) ---
   float prev_mem = IsNaNOrInf((mem - ig * nc) / fg, 0);

準備計算を完了した後、勾配分配処理を直接実行します。まず、グローバルバッファから出力勾配を読み込み、ローカル変数へ格納します。

// --- Gradients computation ---
   float out_g = grad_output[shift];

次に、対応する活性化関数の導関数を用いて、この勾配を出力ゲートおよびコンテキストメモリへ分配します。

float og_g = Deactivation(out_g * mem_t, og, ActFunc_SIGMOID);
float mem_g = Deactivation(out_g * og, mem_t, ActFunc_TANH);

次に、コンテキストメモリの勾配を、新しいコンテキスト射影と入力ゲートの間に伝播させます。

float nc_g = Deactivation(mem_g * ig, nc, ActFunc_TANH);
float ig_g = Deactivation(Deactivation(mem_g * nc, ig, ActFunc_TANH), ig_s, ActFunc_SIGMOID);

特に注意すべき点は、それぞれの活性化関数の導関数を含む段階です。これらの導関数は、入力ゲートの勾配を更新する際に順番に適用されます。

最後に、活性化関数を適用する前の忘却ゲート値まで誤差勾配を逆伝播させます。前述したように、DA-CG-LSTMフレームワークの著者らは、この構成要素に対して比較的複雑な活性化関数を提案しています。そのため、勾配伝播は複数の段階に分けて実行する必要があります。

まず、コンテキストメモリの勾配とその以前の値に基づいて、忘却ゲートの誤差を計算します。

// ∂L/∂fg = ∂L/∂ct * mem_(t-1)
   float fg_g = mem_g * prev_mem;

得られた値は、その後、双曲線正接関数の導関数および複雑な内部式の導関数を用いて調整されます。

// Derivative of the complex forget gate:
// f(z) = 1 - tanh(1 - 1 / σ(z)^2)
   float fg_s_g = 2 / pow(fg_s, 3) * Deactivation(-fg_g, fg, ActFunc_TANH);
   fg_g = Deactivation(fg_s_g, fg_s, ActFunc_SIGMOID);

最後に、シグモイド関数の導関数を適用します。

得られた値は、グローバルデータバッファ内の対応する位置に保存されます。

// --- Write back gradients ---
   grad_concat[shift] = (float4)(fg_g, ig_g, nc_g, og_g);
  }

これで、OpenCL側の実装作業は完了です。完全なソースコードは添付ファイルに記載されています。

CG-LSTMオブジェクトの作成


次に、メインプログラムに移ります。ここでは、CG-LSTMブロックの動作管理を担当する新しいオブジェクトCNeuronCGLSTMOCLを作成します。新しいオブジェクトの構造を以下に示します。

class CNeuronCGLSTMOCL : public CNeuronBaseOCL
  {
protected:
   CNeuronBaseOCL    cConcatenateInputs;
   CNeuronConvOCL    cProjection;
   //---
   virtual bool      CSLSTM_feedForward(void);
   virtual bool      CSLSTM_CalcHiddenGradient(void);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronCGLSTMOCL(void) {};
                    ~CNeuronCGLSTMOCL(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint numNeurons,
                          ENUM_OPTIMIZATION optimization_type, uint batch) override;
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint count, uint window, uint variables, 
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual int       Type(void) override  const    {  return defNeuronCGLSTMOCL; }
   virtual bool      Clear(void) override;
   virtual CBufferFloat *getLSTMWeights(void) { return cProjection.GetWeightsConv(); }
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
  };

新しいクラス構造には、既によく知られている一連のオーバーライドされたメソッドと、2つの内部オブジェクトが含まれています。これらのオブジェクトの機能については、クラスメソッドの実装を進める中で明らかになります。すべての内部オブジェクトはクラスメンバとして直接宣言されているため、クラスのコンストラクタおよびデストラクタは空のままにすることができます。宣言されたオブジェクトおよび継承されたオブジェクトの初期化は、Initメソッド内で実行されます。

初期化メソッドは、作成されるオブジェクトのアーキテクチャを一意に定義する複数の定数を受け取ります。

bool CNeuronCGLSTMOCL::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                            uint count, uint window, uint variables,
                            ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, count * variables, optimization_type, batch))
      return false;
   SetActivationFunction(None);

これらの値の一部は、同名の親クラスのメソッドへ直ちに渡されます。このメソッドでは、すでにパラメータの検証および継承されたオブジェクトの初期化が処理されています。

ここで、新しく作成されたオブジェクトには活性化関数が存在しないことを明示的に指定している点に注意してください。

親クラスの初期化が正常に完了した後、宣言されたオブジェクトの動作準備を進めます。この場合、対象となるオブジェクトは2つだけです。最初のオブジェクトは完全結合層であり、入力データと過去のフィードフォワード処理で生成された隠れ状態から構成される連結テンソルを格納するために使用されます。

if(!cConcatenateInputs.Init(0, 0, OpenCL, (count + window) * variables, optimization, iBatch))
   return false;
cConcatenateInputs.SetActivationFunction(None);

ここでも、活性化関数が存在しないことを明示的に示します。

2番目のオブジェクトは、連結されたテンソルを4つのエンティティに投影する役割を担う畳み込み層です。

if(!cProjection.Init(0, 1, OpenCL, count + window, count + window, count * 4, 1, variables, optimization, iBatch))
   return false;
cProjection.SetActivationFunction(None);

前述したように、この層も活性化関数を使用せずに動作します。そのため、この設定を明示的に指定します。

特に注意すべき点は、畳み込み層の初期化処理です。この層を作成する際には、系列長を1に明示的に設定します。一見すると、この設定は制限的に見えるかもしれません。しかし、この設計上の選択は、意図的なアーキテクチャ上の決定を反映したものです。同時に、並列処理される単変量(独立)系列の数も指定します。

このアプローチにより、各単変量系列は、それぞれ独自の学習可能なパラメータ、すなわち固有の重み行列を保持できます。その結果、各系列を独立して解析および学習することが可能になります。それぞれの系列は自身のコンテキスト内で学習し、他の系列とパラメータを共有することなく、固有のパターンや規則性に応答します。これにより、入力データに対して、より表現力が高く、適応性に優れ、構造的に柔軟な表現を得ることができます。特に、異なる時間的サブ系列が異なる意味的役割を持つ場合や、それぞれが個別の市場要因を表現するようなタスクにおいて、この特性は重要になります。

このようなパラメータ分離は、学習時にも重要な役割を果たします。第一に、チャネル間の相互干渉を低減し、支配的なパターンへの過学習を抑制します。第二に、各単変量系列は自身のデータ特性に集中して学習することができます。このような差別化された学習により、モデルは特定のタスクに対する精度を向上させるだけでなく、変化する市場環境に対しても大幅に高い頑健性を持つようになります。

さらに、独立して学習される単変量フィルタは、より優れた汎化性能を促進します。モデルはデータの単なる記憶に依存しにくくなり、より一般的なパターンを抽出しやすくなります。これは金融時系列データにおいて特に重要です。金融データには、特殊で再現性の低いイベントが含まれる可能性があるためです。学習プロセスを複数の独立した分岐へ分解することで、モデルは未知の状況においても典型的な市場シグナルを認識できるようになります。

最後に、リカレントモデルの主要な特徴の1つは、直前のフィードフォワード処理で得られたデータに依存する点です。そのため、初期化処理を完了する前に、すべてのデータバッファをクリアします。

   if(!Clear())
      return false;
//---
   return true;
  }

次の段階では、フォワードパス処理を実装します。この処理はfeedForwardメソッド内に実装します。この部分は比較的簡単です。このメソッドは入力データオブジェクトへのポインタを受け取り、まず有効性を検証します。

bool CNeuronCGLSTMOCL::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

次に、射影層のパラメータから、入力テンソルと隠れ状態の両方の次元を抽出します。

int hidden = (int)cProjection.GetFilters() / 4;
int inputs = (int)cProjection.GetWindow() - hidden;
int variables = (int)cProjection.GetVariables();

次に、入力データと、各単変量シーケンスに対する前回の順伝播処理の結果を連結します。

if(!Concat(NeuronOCL.getOutput(), getOutput(), cConcatenateInputs.getOutput(), inputs, hidden, variables))
   return false;

得られた値は4つのエンティティに投影されます。

if(!cProjection.FeedForward(cConcatenateInputs.AsObject()))
   return false;

この時点で残っている処理は、あらかじめ作成したフィードフォワードカーネルCSLSTM_feedForwardの実行をキューに追加するラッパーメソッドを呼び出すことだけです。

 return CSLSTM_feedForward();
}

カーネルを実行キューに配置するためのメソッドは、これまでの記事で説明したものと同じアーキテクチャに従っているため、ここではその内部アルゴリズムの詳細な説明は省略します。

フィードフォワード処理の実装を完了した後、逆伝播処理へ進みます。ご存じのように、この処理は2つの段階で構成されています。すなわち、誤差勾配の分配と、学習可能なパラメータの最適化です。

今回の場合、学習可能なパラメータは連結データの射影層内にのみ存在します。そのため、パラメータの最適化処理は、射影層に対応する最適化メソッドを呼び出すだけで完了します。

bool CNeuronCGLSTMOCL::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   return cProjection.UpdateInputWeights(cConcatenateInputs.AsObject());
  }

calcInputGradientsに実装されている、参加する各構成要素へ誤差勾配を分配するアルゴリズムは、やや複雑になります。このメソッドは、入力データオブジェクトへのポインタを受け取ります。これはフォワードパスで使用された同一オブジェクトです。しかし今回は、入力データがモデルの最終出力に与える寄与に基づいて、そこへ誤差勾配を逆伝播させる必要があります。

bool CNeuronCGLSTMOCL::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

メソッド本体では、まず受け取ったポインタの有効性を直ちに確認します。この確認処理の必要性については、これまでの記事で繰り返し説明してきました。

次に、フィードフォワードメソッドの場合と同様に、入力データおよび隠れ状態の次元を取得します。

int hidden = (int)cProjection.GetFilters() / 4;
int inputs = (int)cProjection.GetWindow() - hidden;
int variables = (int)cProjection.GetVariables();

まず、対応するカーネルを実行キューへ配置するラッパーメソッドを呼び出し、エンティティ間で誤差勾配を分配します。

if(!CSLSTM_CalcHiddenGradient())
   return false;

次に、連結入力テンソルへ勾配を逆伝播させます。

if(!cConcatenateInputs.calcHiddenGradients(cProjection.AsObject()))
   return false;

逆連結処理を使用して、元の入力データに対応する誤差勾配を抽出します。

if(!DeConcat(NeuronOCL.getGradient(), getPrevOutput(), cConcatenateInputs.getGradient(),
                                                             inputs, hidden, variables))
   return false;

注目すべき点として、内部オブジェクトの初期化時には、意図的に活性化関数を無効化しています。しかし、これは入力データ自体に活性化関数を使用することを排除するものではありません。そのため、入力データに活性化関数が関連付けられているかを確認し、必要に応じて対応する活性化関数の導関数を用いて、得られた勾配を調整します。

   if(NeuronOCL.Activation() != None)
      if(!DeActivation(NeuronOCL.getOutput(), NeuronOCL.getGradient(), NeuronOCL.getGradient(),
                                                                       NeuronOCL.Activation()))
         return false;
//---
   return true;
  }

その後、処理結果の論理値を呼び出し元へ返すことで、メソッドを完了します。

これにより、新しいCNeuronCGLSTMOCLクラスのメソッドを構築するために使用されたアルゴリズムの説明は終了します。このクラスおよびすべてのメソッドの完全なソースコードは、添付ファイルで提供されています。

気付かないうちに、私たちは本記事で扱える実装範囲の実践的な限界にほぼ到達しました。しかし、この研究を論理的に完成させるためには、さらなる継続が必要です。今回はここで一区切りとします。続編へ続きます。そして次回も、同様に充実した内容になる予定です。 



結論

本記事では、DA-CG-LSTMフレームワークの理論的基盤について検討しました。従来のモデルとは異なり、このアーキテクチャにはCG-LSTMおよびデュアルアテンション機構を含む複数の革新的な仕組みが組み込まれています。これらの機構は、データから依存関係をより深く、より正確に抽出することを可能にします。これらの構成要素により、モデルは複雑な時間的関係を効果的に処理すると同時に、長期的および短期的なパターンの両方を捉えることができます。

実践的なセクションでは、MQL5を用いたCG-LSTMブロックの独自実装について紹介しました。しかし、この作業はまだ完了しておらず、次回の記事では引き続き開発を進め、プロジェクトを論理的な完成へと導きます。



参照文献


この記事で使用されているプログラム

# 名前 種類 詳細
1 Research.mq5 EA サンプル収集用EA
2 ResearchRealORL.mq5
EA
Real-ORL法を用いたサンプル収集用EA
3 Study.mq5 EA オフラインモデル学習用EA
4 StudyOnline.mq5
EA
オンラインモデル学習用EA
5 Test.mq5 EA モデルテスト用EA
6 Trajectory.mqh クラスライブラリ システム状態とモデルアーキテクチャ記述構造
7 NeuroNet.mqh クラスライブラリ ニューラルネットワークを作成するためのクラスのライブラリ
7 NeuroNet.cl コードライブラリ OpenCLプログラムコード

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/17901

添付されたファイル |
MQL5.zip (2687.97 KB)
最後のコメント | ディスカッションに移動 (2)
Vladimir Sanin
Vladimir Sanin | 26 4月 2025 において 08:26
こんにちは。NeuroNet.mqh、NeuroNet.cl、Trajectory.mqhといったライブラリはどこで入手できますか?
また、モデルの正確なパラメータ(入力データのサイズ、ニューロンの数、最適化アルゴリズム)はどのようなものですか?
Dmitriy Gizlyk
Dmitriy Gizlyk | 28 4月 2025 において 12:02
Владимир #:
こんにちは。NeuroNet.mqh、NeuroNet.cl、Trajectory.mqhといったライブラリはどこで入手できますか?
また、モデルの正確なパラメータ(入力データのサイズ、ニューロンの数、最適化アルゴリズム)はどのようなものですか?

ウラジーミルさん、こんにちは。

NeuroNet.* ライブラリはすべて「MQL5\Experts\NeuroNet_DNG\NeuroNet.*」フォルダに、Trajectory.mqh は「MQL5\Experts\DACGLSTM\Trajectory.mqh」フォルダに含まれています。

学習モデルの詳細な説明については、次回の記事でご紹介する予定です。

EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
取引におけるニューラルネットワーク:Actor-Director-Critic(最終回) 取引におけるニューラルネットワーク:Actor-Director-Critic(最終回)
Actor-Director-Criticフレームワークは、古典的なエージェント学習アーキテクチャを発展させたものです。本記事では、その実装と金融市場環境への適応に関する実践的な経験を紹介します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
取引におけるニューラルネットワーク:Actor-Director-Critic 取引におけるニューラルネットワーク:Actor-Director-Critic
階層型学習とマルチコンポーネントアーキテクチャを組み合わせたActor-Director-Critic (ADC)フレームワークについて詳しく解説します。DirectorがActorの行動を分類する仕組みが、トレードにおける意思決定の最適化にどのように寄与し、金融市場という不確実性の高い環境においてモデルの頑健性をどのように向上させるのかを詳しく解説します。