
知っておくべきMQL5ウィザードのテクニック(第22回):条件付きGAN
はじめに
敵対的生成ネットワーク(cGAN)はGANの一種で、生成ネットワークの入力データの種類をカスタマイズできます。共有されたリンクから、またこのテーマについて読むとわかるように、GANは生成器と識別器の一対のニューラルネットワークです。両者は互いに訓練されるか、または互いに訓練し合います。生成器はターゲット出力の生成能力が向上し、一方、識別器は生成器からのデータ (別名、偽データ) を識別するように訓練されます。
この応用は、通常、画像分析で使用されます。画像分析では、生成器ネットワークを使用して画像を作成し、識別器ネットワークは、入力として与えられた画像が生成器ネットワークによって作成されたものか、実際のものかを特定します。相互訓練は、識別器生成器の画像と実際の画像を交互に入力することでおこなわれ、他のネットワークと同様に、バックプロパゲーションによって識別器の重みが適切に調整されます。一方、生成器は、非条件または典型的な設定では、ランダムな入力データが与えられ、これに関係なく、可能な限り現実的な画像を生成することになっています。
条件付きGANの設定(cGAN)では、ランダムなデータではなく、ある種のデータを入力として生成ネットワークに与えるという若干の変更を加えます。これは、識別器に入力するデータが対になっていたり、2つの部分に分かれていたりする場合に有効で、識別器ネットワークの目的は、入力された対のデータが有効かでっち上げかを判別することです。
GANとcGANの応用の多くは画像認識や画像処理であるようですが、この記事では、金融時系列予測のための非常にシンプルなモデルがどのように構築できるかを探ります。タイトルにあるように、GANの代わりにcGANを採用します。この2つの違いを説明するために、下の2つの図を考えてみましょう。
どちらの画像も、生成器ネットワークの出力がテストや検証のために識別器ネットワークに供給される設定を指しています。GANは敵対的なもので、生成器は識別器を欺くのがうまくなるように訓練され、識別器は実データまたは非生成器のネットワークデータから生成器の出力を識別するのがうまくなるように訓練されます。しかし、この2つの設定の主な違いは、GANの場合、生成器ネットワークがランダムな入力データを受け取り、それを使用して、識別器が実際のデータと見分けがつかないようなデータを作り出すことです。金融時系列予測における私たちの目的からすれば、このような方法の適用と使用は限定的であるに違いません。
ただしcGANの場合、図の中でノイズと呼ばれているものは、本質的に独立したデータ、あるいは生成器がラベルを生成しようとしているデータである(私たちの適応の場合)。しかし、識別器ネットワークは、ノイズ(または独立データ)とそれぞれのラベルのペアリングを受け取り、このペアリングが実データによるものか、独立データに割り当てられたラベルが生成器によるものかを判別しようとします。
金融時系列予測におけるcGANの利点とは何でしょうか。よく言われているように、証拠は実践次第なので、いつものようにこの記事の最後のほうでいくつかテストをおこないますが、画像認識においては、GAN は計算コストのせいでCNNや ViT(英語)ほどうまく機能していないにもかかわらず、確かにある程度の影響力を持っています。しかし、画像合成や補強に関しては、より優れていると言われています。
環境設定
cGANモデルを構築するために、この記事で紹介した多層パーセプトロンネットワークを基本クラスとして使用します。この基本クラスは、cGANを稼働させるために必要なすべての「ツールとライブラリ」を表しています。なぜなら、生成器ネットワークと識別器ネットワークは、単に多層パーセプトロンのインスタンスのように扱われるからです。この基本クラスは、フィードフォワードメソッドForward()とバックプロパゲーション関数Backward()の2つの主要な関数を持っているだけです。もちろん、ネットワークの設定を受け取るクラスコンストラクタや、学習した重みをファイルとして保存するためのハウスキーピングメソッド、さらに学習目標の設定やフィードフォワード結果の読み込みをおこなう関数もあります。
このcGANでは、典型的な多層パーセプトロンの基本クラスを使用していますが、生成ネットワークのバックプロパゲーションや学習方法には、GAN特有の変更を加える必要があります。損失生成器は以下の式から計算できます。
−log(D(G(z∣y)∣y))
ここで
- D()は識別器出力関数
- G()は生成器出力関数
- zは独立データ
- yは従属データ、ラベル、予測データ
つまり、この損失生成器の値は、通常、出力サイズに応じてベクトル形式となり、バックプロパゲーションを開始する際に、各前方パスからの誤差値に対する重み付けとして機能します。ネットワーククラスでは次のように変更します。
//+------------------------------------------------------------------+ //| Backward pass through the neural network to update weights | //| and biases using gradient descent | //+------------------------------------------------------------------+ void Cgan::Backward(vector<double> &DiscriminatorOutput, double LearningRate = 0.05) { if(target.Size() != output.Size()) { printf(__FUNCSIG__ + " Target & output size should match. "); return; } if(ArraySize(weights) != hidden_layers + 1) { printf(__FUNCSIG__ + " weights matrix array size should be: " + IntegerToString(hidden_layers + 1)); return; } ... // Update output layer weights and biases vector _output_error = -1.0*MathLog(DiscriminatorOutput)*(target - output);//solo modification for GAN Back(_output_error, LearningRate); }
私たちのBackward()関数はオーバーロードされるようになり、一方のバリアントは通常、入力として識別器の出力を取り、これらのオーバーロードされた関数の両方は、本質的に古いバックプロパゲーション関数で持っていたコードのほとんどを持つBack()関数を呼び出し、重複を減らすためにここに導入されています。ただし、この重み付けは、生成器を訓練する際に、単に次の終値の変化を予測するのがうまくなるだけでなく、生成器のデータが本物であると識別器を欺くのが「うまく」なることを保証します。一方、識別器は、生成器データと実データを区別するのが得意になろうとすることで、「逆方向」の訓練をおこなっています。
対照的に、この設定をサードパーティのアプリに実装する場合、PythonでTensorFlowを使用して同様のネットワークを定義するには、各層を個別のコマンドまたはコード行で追加する必要があります。このPythonオプションは、もちろん、私たちの基本クラスが提供しない、より多くのカスタマイズを提供しますが、MQL5環境でcGANを実行するためのプロトタイピングツールとしては、好ましい選択ではありません。言うまでもなく、Pythonとそのニューラルネットワークライブラリを使用するには、訓練結果をMQL5に書き出せるようなONNXなどのアダプター、または同等のカスタム実装が必要です。これらには、モデルが開発中に一度訓練され、その後展開されるように設計されている場合や、オフライン(展開されていない)時に定期的に訓練されるように設計されている場合に確かに利点があります。
ニューラルネットワークの訓練をライブでおこなう必要があったり、配備中におこなう必要があったりするシナリオでは、Pythonとの間の多くの「アダプター」は扱いにくくなる可能性があります。
カスタムシグナルクラスの設計
本連載を通して見てきたように、シグナルクラスは、初期化、検証、市場状況の評価のための標準的な関数を備えています。さらに、カスタム指標であろうと、MQL5ライブラリ内ですでに利用可能な典型的な指標の組み合わせであろうと、シグナルをカスタマイズするための関数を無制限に追加することができます。多層パーセプトロンをベースとしたcGANを構築するため、まず、パーセプトロン基本クラスを使用した前回の記事 で採用したものと同様の追加の関数から始めます。
これらはGetOutput()、Setoutput()、Norm()関数となります。ここでの役割は、前回の記事で説明したものと非常に似ています。つまり、get関数は市場の状況を決定するアンカー関数となり、set関数は以前と同様、各トレーニング パスの後にネットワークの重みを書き込むために使用でき、norm関数はフィードフォワードの前に入力データを正規化する重要な役割を果たします。
cGANカスタムシグナルクラスには、新たに3つの関数が追加されます。これらは生成器ネットワークの処理を識別器ネットワークから分離するためのものです。
生成ネットワークのアーキテクチャーは、入力層、隠れ層5層、出力層1層の計7層からなるものとして任意に選択されます。これを適切に判断するには、前述の記事 で取り上げたニューラル アーキテクチャ検索を使用しますが、ここでの目的上、これらの仮定はcGANを示すのに十分です。これらのネットワーク設定はsettings配列で定義され、GENと命名したネットワーククラスのインスタンスを初期化するために使用します。
この生成ネットワークは、入力として終値の事前変化を持ち、出力として同じく終値の単一予測変化を持ちます。これは、すでに参照した記事でニューラルアーキテクチャーの検索を見たときの実装とあまり変わりません。出力予測は、入力となる4つの変化に続く終値の変化となります。
つまり、これら4つの事前変化と予測値の組み合わせが、後で見る識別器ネットワークの入力データとなります。これは後で確認します。今回使用するネットワークベースクラスは、固定されたsoftplusによって活性化をおこないます。完全なソースが提供されているので、読者は自分の設定に適したものに簡単にカスタマイズすることができます。したがって、シグナルクラスで調整可能なパラメータは、学習率、学習エポック数、訓練データセットサイズのみとなります。これらにはそれぞれm_learning_rate、m_epochs、m_train_setという名前が割り当てられています。出力取得関数の中で、このようにしてネットワークの入力データを読み込み、フィードフォワードし、新しいバーごとにネットワークを訓練します。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSignalCGAN::GetOutput(double &GenOut, bool &DisOut) { GenOut = 0.0; DisOut = false; for(int i = m_epochs; i >= 0; i--) { for(int ii = m_train_set; ii >= 0; ii--) { vector _in, _out; vector _in_new, _out_new, _in_old, _out_old; _in_new.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_INPUTS); _in_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1 + 1, __GEN_INPUTS); _in = Norm(_in_new, _in_old); GEN.Set(_in); GEN.Forward(); if(ii > 0)// train { _out_new.CopyRates(m_symbol.Name(), m_period, 8, ii, __GEN_OUTPUTS); _out_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_OUTPUTS); _out = Norm(_out_new, _out_old); ... } else if(ii == 0 && i == 0) { ... } } } }
私たちのGANは条件付きです。なぜなら、生成器ネットワークへの入力はランダムではなく、識別器ネットワークへの入力は生成器への入力とその出力を捉える2重のものだからです。したがって、識別器ネットワークの役割は、入力データが5つの連続した終値の変化からなる実時間系列シーケンスから得られたものか、あるいは生成器ネットワークのデータ入力とその出力のペアであるかを判断することです。つまり、入力データが「本物」か「偽物」かをそれぞれ判断するのです。
これは、識別器ネットワークの出力が非常に単純なブールであることを意味します。入力データはすべて市場からのもの(true)か、生成器が部分的に作り出したもの(false)のどちらかです。これをそれぞれ1と0で表し、訓練後のテストでは0.0から1.0の間の浮動小数点数が返されます。そこで、識別器ネットワークを訓練するには、実際の終値の変化を5つのデータポイント(5つの連続した変化)として交互に入力し、さらに別の5つの終値の変化(そのうち 4つだけが実際のもので、5番目は生成器ネットワークの予測)を入力します。実データの訓練の一部は、以下にコードを示すR関数によって処理されます。
//+------------------------------------------------------------------+ //| Process Real Data in Discriminator | //+------------------------------------------------------------------+ void CSignalCGAN::R(vector &IN, vector &OUT) { vector _out_r, _out_real, _in_real; _out_r.Copy(OUT); _in_real.Copy(IN); Sum(_in_real, _out_r); DIS.Set(_in_real); DIS.Forward(); _out_real.Resize(__DIS_OUTPUTS); _out_real.Fill(1.0); DIS.Get(_out_real); DIS.Backward(m_learning_rate); }
また、偽データを訓練するためのF関数のコードもここに示します。
//+------------------------------------------------------------------+ //| Process Fake Data in Discriminator | //+------------------------------------------------------------------+ void CSignalCGAN::F(vector &IN, vector &OUT) { vector _out_f, _out_fake, _in_fake; _out_f.Copy(OUT); _in_fake.Copy(IN); Sum(_in_fake, _out_f); DIS.Set(_in_fake); DIS.Forward(); _out_fake.Resize(__DIS_OUTPUTS); _out_fake.Fill(0.0); DIS.Get(_out_fake); DIS.Backward(m_learning_rate); }
これら2つの関数は、以下のように出力関数の取得内で呼び出されます。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSignalCGAN::GetOutput(double &GenOut, bool &DisOut) { GenOut = 0.0; DisOut = false; for(int i = m_epochs; i >= 0; i--) { for(int ii = m_train_set; ii >= 0; ii--) { ... if(ii > 0)// train { _out_new.CopyRates(m_symbol.Name(), m_period, 8, ii, __GEN_OUTPUTS); _out_old.CopyRates(m_symbol.Name(), m_period, 8, ii + 1, __GEN_OUTPUTS); _out = Norm(_out_new, _out_old); // int _dis_sort = MathRand()%2; if(_dis_sort == 0) { F(_in, GEN.output); GEN.Get(_out); GEN.Backward(DIS.output, m_learning_rate); R(_in, _out); } else if(_dis_sort == 1) { R(_in, _out); GEN.Get(_out); GEN.Backward(DIS.output, m_learning_rate); F(_in, GEN.output); } } else if(ii == 0 && i == 0) { GenOut = GEN.output[0]; DisOut = (((DIS.output[0] >= 0.5 && GenOut >= 0.5)||(DIS.output[0] < 0.5 && GenOut < 0.5)) ? true : false); } } } }
Sum関数を使用して、4つの終値の変化を、実際のデータを取得する場合は次の終値の変化と、偽データを取得する場合は生成器の予測と対にします。そのため、その後の訓練によって、パーセプトロンに期待されるように、生成器は市場状況の評価に使える予測を立てるのがうまくなります。しかし、それでは識別器の訓練努力をどうするのでしょうか。
まず、前述したように、訓練は生成器ネットワークの重みをシャープにするのに役立ちます。というのも、生成器ネットワークをバックプロパゲーションする際に使用する損失値を調整するために、損失生成器の重みを使用するからです。第二に、ネットワークが訓練され、配備された後も、識別器は生成器の予測を検証するために使用することができます。もし、生成器によるものだということがわからないのであれば、生成器ネットワークがうまく機能していることになります。
cGANとMQL5シグナルクラスの統合
シグナルクラスの中でこれを機能させるには、買い条件関数と売り条件関数をコード化して、2つのものを返すGetOutput()関数を呼び出す必要があります。終値の推定変化は、double変数GenOutとbool変数DisOutによって捕捉され、終値予測のこの変化が識別ネットワークを欺くことができたかどうかを示します。読者は、GANの最も一般的な使用法である画像生成において典型的なケースであるように、市場条件を決定する際に生成器出力のみを使用する設定を自由に試すことができます。しかし、識別器ネットワークがこれらの予報を確認することは、コンディションを評価する上で、より安全なステップとして機能します。
ネットワークの入力値はすべて-1.0から+1.0の範囲になるように正規化されており、同じように、ほとんどの場合、出力も同じような範囲になると予想されます。つまり、この生成器は、終値の予想変化率を示しているのです。これらはパーセンテージなので、100倍すれば100を超えない値を得ることができます。この値の符号がプラスかマイナスかで、それぞれが買いか売りかを判断します。そこで、条件を処理し、買い条件関数と売り条件関数から期待されるような0~100の範囲の整数出力を得るために、買い条件関数を以下のように用意します。
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalCGAN::LongCondition(void) { int result = 0; double _gen_out = 0.0; bool _dis_out = false; GetOutput(_gen_out, _dis_out); _gen_out *= 100.0; if(_dis_out && _gen_out > 50.0) { result = int(_gen_out); } //printf(__FUNCSIG__ + " generator output is: %.5f, which is backed by discriminator as: %s", _gen_out, string(_dis_out)); return(result); }
もちろん、予測パーセンテージがマイナスでなければゼロでないということと、この値が100を掛けた後の予測パーセンテージの絶対額であるということを除いては、売り条件は非常によく似ています。
テストと検証
MQL5ウィザードで組み立てたEAでテスト実行をおこなうと(このためのガイドラインはこちらおよび こちら)を使用してテストしてみると、1回の実行で次のような結果が得られました。
これらの実行を得るためにシグナルを処理する際に、識別器ネットワークを訓練する順序をランダム化します。つまり、最初に実際のデータで訓練する場合もあれば、最初に偽のデータで訓練する場合もあります。このテストでは、テストの順番を厳密にすることで識別器ネットワークが偏るため、買いだけ、売りだけというように常に一方に傾くことはありません。また、前述のように、一般的なGANの使用では識別ネットワークの検証は必要ありませんが、これは、より注意深くするためにここで採用することを選択したものにすぎません。
この検証の追加により、私たちの結果は各テスト実行で簡単に再現できるものではありません。特に、私たちのネットワークアーキテクチャは、5つの隠れ層のみを使用し、各層のサイズがわずか5であることから、非常に小さいです。識別ネットワークからのこの検証チェックでより一貫性のある結果を目標とする場合は、5~25個の隠し層を持つネットワークを訓練する必要があります。各隠し層のサイズはおそらく100未満にはなりません。層数よりも層サイズが、より信頼性の高いネットワーク結果を生み出す重要な要因になる傾向があります。
しかし、もしこの識別器ネットワークの検証を取りやめれば、このネットワークは、パフォーマンスに多少の不調はあるにせよ、より不安定なテスト結果をもたらさないはずです。妥協案のひとつは、識別器ネットワークの検証をオンにするかどうかをユーザーが選択できるように、追加の入力パラメータを追加することでしょう。
結論
まとめると、条件付き敵対的生成ネットワーク(cGAN)をカスタムシグナルクラスに開発し、MQL5ウィザードのおかげでエキスパートアドバイザー(EA)に組み込めることを見てきました。cGANは、生成器ネットワークを訓練するときに非ランダムデータを使用するという点でGANの修正版であり、識別器データの入力データは、このケースでは、すでに示したように、この生成器ネットワーク入力データと生成器出力データの組み合わせでした。訓練中のニューラルネットワークは重みを学習するので、訓練プロセスが完了するたびに、ネットワークの重みを記録する規定を設け、使用することは良い習慣です。この記事のために実施したテスト実行では、このようなメリットを考慮したり検討したりはしていません。
加えて、異なるネットワーク訓練方式を採用することの潜在的なメリットとトレードオフについては、まだ調査していません。例えば、この記事では、新しいバーごとにネットワークを訓練していますが、これは、変化する可能性のある市場環境に対して、ネットワークと取引システムの柔軟性と適応性を高めることを意図しています。しかし、これに対する反論であり、おそらく信頼できる議論は、新しいバーごとに常にネットワークを訓練することによって、不必要にノイズを訓練しているということです。対照的に、たとえば、市場の重要な「長期的」側面のみが訓練データポイントとして使用されるように、訓練を6か月に1回実行する体制は、より持続可能な結果をもたらす可能性があります。
さらに、常に重要な前提条件の質問である ニューラルアーキテクチャ検索(英語)については、主な主題ではなかったため「省略」されていますが、ネットワークに精通している人なら誰でも、これがニューラルネットワークのパフォーマンスに非常に敏感な側面であり、ネットワークを訓練して最終的に展開する前に、ある程度の注意が必要であることを知っているでしょう。つまり、これらの3つの重要なファセットは、重要であるにもかかわらず、適切に扱われていません。つまり、このcGANクラスを取引に値すると見なす前に、これらを出発点として利用して、このcGANクラスを開発および推進することが読者に推奨されます。いつものように、これは投資アドバイスではなく、この記事で共有されているすべてのアイデアについて、さらに利用する前に読者側で独自の注意を払うことが期待されます。頑張りましょう。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15029





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索