English Русский 中文 Español Deutsch Português
preview
ニューラルネットワークが簡単に(第23部):転移学習用ツールの構築

ニューラルネットワークが簡単に(第23部):転移学習用ツールの構築

MetaTrader 5トレーディングシステム | 15 11月 2022, 09:28
232 0
Dmitriy Gizlyk
Dmitriy Gizlyk

内容


はじめに

引き続き人工知能の世界に没頭していきます。本日は、転移学習技術についてご紹介します。この技術についてはすでにさまざまな記事で言及していますが、使用したことはありません。一方、これはニューラルネットワークの開発効率を高め、訓練のコストを削減する強力なツールです。


1.転移学習の目的

転移学習とは何でしょうか。そしてなぜ必要なのでしょうか。転移学習は、1つの問題を解決するために訓練されたモデルの知識を新しい問題を解決するための基礎として再利用する機械学習手法です。もちろん、新しい問題を解決するために、モデルは新しいデータで事前に追加訓練されています。一般に、適切に選択されたドナーモデルを使用すると、同様のモデルを最初から訓練するよりも、追加の訓練がはるかに高速に実行され、より良い結果が得られます。

ドナーモデル全体またはその一部を使用することが可能です。

このテクノロジーに似ているのは、クラスタリングとデータ圧縮の結果を使用して、ニューラルネットワークのソースデータを前処理した場合です。この場合、事前訓練済みのモデル全体を使用しましたが、新しい問題を解決するためのモデルを構築するとき、ドナーモデルの追加の訓練は実行しませんでした。「生の」ソースデータを前処理するためにのみ使用し、このデータを使用して新しいモデルを訓練しました。

オートエンコーダの研究を始めたとき、モデルの訓練後に転移学習を使用する可能性についてもお話しましたが、この場合、完全なオートエンコーダを完全にドナーモデルとして使用することはできません。元のデータを圧縮し、圧縮された表現から復元するようにオートエンコーダを訓練したためです。したがって、オートエンコーダ全体をドナーモデルとして使用しても意味がありません。データの前処理については、エンコーダのみを使用する方がはるかに効率的です。この場合、同じ量の情報を処理するために必要な訓練可能な重みが少なくなるため、モデル全体が小さくなり、追加の層の効率が高くなります。

ただし、転移学習の使用は、教師なし学習の結果に限定されません。ニューラル層を1つでも追加または削除する必要があったときに、モデルの訓練をどれだけ最初からやり直したかを思い出してください。この場合、ニューラル層の一部を再利用できました。

この技術には別の応用分野があります。フェージング勾配の問題により、深いモデルを完全に訓練することはほぼ不可能です。転移学習を使用すると、ニューラル層をブロックで訓練し、モデルのサイズを徐々に大きくすることができます。

もちろん、このテクノロジーには他にも多くの用途が考えられます。次に、それを使えるようにするツールを考えてみましょう。


2.ツールの作成

まず、作成するツールの目的を決めましょう。初めに、訓練済みのモデルを保存する方法に戻ります。それらはすべて単一のバイナリファイルに保存されます。各モデルオブジェクトには、独自の厳密なデータ記録構造があります。したがって、エディターでファイルからデータの一部を単純に削除することは困難です。訓練済みのモデル全体をファイルから読み込み、必要な操作を実行して、新しいモデルを新しいファイルに保存するか、以前のモデルを上書きする必要があります。ドナーモデルは、訓練された問題を解決するためにさらに使用できるため、新しいファイルの方が適しています。

また、私達のニューラルネットワークは、訓練されたデータでのみ適切に機能します。まったく新しいデータでは、結果が予測できない場合があります。これは、個々のニューラル層にも当てはまります。したがって、転移学習では、入力データ層から始まる一連のニューラル層のみを使用できます。モデルの途中または最後からブロックを抽出することはできません。つまり、ドナーモデル全体またはその最初の層のいくつかを使用できます。そして、いくつかの異なるニューラル層を追加して、新しいモデルを保存します。

同時に、訓練モードと運用中の両方で、新しいモデルの完全な機能を確保する必要があります。もちろん、モデルは最初に訓練する必要があります。

以下にご注目ください。ドナーモデルのニューラル層は、その重みを保持します。また、モデルの事前訓練段階で得たすべての知識を保持します。モデルが初期化されたときと同様に、新しいニューラル層はランダムな重みを受け取ります。以前と同じように新しいモデルの訓練を開始すると、新しい層の訓練とともに、以前に訓練したニューラル層のバランスが崩れます。したがって、最初にドナーモデルのニューラル層の訓練を阻止する必要があります。こうすれば、新しい層のみの訓練を保証できます。


2.1 設計

ソースドナーモデルを使用するプログラムだけが必要なわけではありません。何らかの方法でそれを処理し、新しいファイルに再保存する必要があります。コピーされた層の数とモデルアーキテクチャは常に個別です。したがって、ユーザーが各モデルを個別に迅速かつ便利に構成できるようなツールが必要です。つまり、便利なユーザーインターフェイスを備えたツールが必要です。よって、UIデザインから始めます。

3つの透明なブロックが見えます。最初のブロックでは、ドナーモデルを扱います。ここでは、訓練済みモデルを含むファイルを選択する機能が必要です。ファイルからモデルを読み込んだ後、ツールは読み込んだモデルのアーキテクチャの説明を提供する必要があります。どのモデルが読み込まれ、どのニューラル層がコピーされるかをユーザーが理解する必要があるためです。また、コピーされた層の数についてツールに通知します。前述のように、ソースデータ層から順にニューラル層をコピーします。

2番目のブロックでは、ニューラル層が追加されます。ここでは、作成中のニューラル層に関する情報を入力するためのフィールドを作成します。プログラムコードでは、各ニューラル層を1つずつ順番に記述し、新しいモデルのアーキテクチャに追加します。

3番目のブロックには、作成されたモデルの全体的なアーキテクチャが表示され、ファイルを指定して保存することができます。ツールの設計例を以下に示します。

ツール設計

ツールの設計とその実装は、デモンストレーションのみを目的として提示されており、ニーズに合わせていつでも変更できます。


2.2 ユーザーインターフェイスの実装 

これで、設計の実装に進むことができます。このために、CAppDialogダイアログアプリケーション基本クラスを継承する新しいクラスCNetCreatorPanelを作成しましょう。

パネル内の各コントロールは、個別のオブジェクトとして作成されます。したがって、新しいクラスで非常に多くのオブジェクトを宣言します。便宜上、それらをブロックに分割します。

最初のブロックには、事前訓練済みモデルの視覚化に関連するオブジェクトが含まれます。

  • m_edPTModel - 事前訓練済みモデルのファイル名を指定するための要素
  • m_edPTModelLayers - 事前訓練済みモデルのニューラル層の総数の表示
  • m_spPTModelLayers - 新しいモデルにコピーされるニューラル層の数
  • m_lstPTMode - 事前訓練済みモデルのアーキテクチャの表示
class CNetCreatorPanel : protected CAppDialog
  {
protected:
   //--- pre-trained model
   CEdit             m_edPTModel;
   CEdit             m_edPTModelLayers;
   CSpinEdit         m_spPTModelLayers;
   CListView         m_lstPTModel;
   CNetModify        m_Model;   
   CArrayObj*        m_arPTModelDescription;

また、事前に訓練されたモデルで動作するようにオブジェクトを宣言します。

  • m_Model - 事前訓練済みモデルのオブジェクト
  • m_arPTModelDescription - 事前訓練済みモデルのアーキテクチャの説明を含む動的配列

次の2つの瞬間に注意してください。モデルアーキテクチャ記述の動的配列を除き、すべてのオブジェクトは静的として宣言されます。静的オブジェクトを使用すると、メモリ操作をシステムに転送できます。静的オブジェクトは含まれているオブジェクトと一緒に作成および削除され、プログラマーによる追加作業を必要としないためです。ただし、この方法では、クラスの構造内でのみオブジェクトを作成できます。アーキテクチャの説明は、事前訓練済みのモデルから取得されます。したがって、このオブジェクトは動的ポインタを介して宣言されました。

そして2つ目の瞬間です。事前訓練済みのモデルオブジェクトを宣言するためにCNetModifyクラスを使用しましたが、以前はニューラルネットワークモデル用のCNetクラスを作成しました。ニューラルネットワークに追加機能が必要だからです。これを実装するには、CNetクラスから派生した新しいクラスCNetModifyを作成しますが、この部分には、ツールの機能を説明するときに戻ります。

次のブロックには、作成される新しいニューラル層を記述するためのオブジェクトが含まれています。オブジェクトは、ニューラル層アーキテクチャを記述するCLayerDescriptionクラスの要素と一致しています。そのため、各要素を詳細に調べることはしません。ただし、新しいニューラル層を追加し、作成したものを削除するための2つのボタンの作成について言及したいと思います。追加されたニューラル層のみを削除できます。コピーされたニューラル層の数を制御するために、前のブロックの要素を使用します。

   //--- add layers
   CComboBox         m_cbNewNeuronType;
   CEdit             m_edCount;
   CEdit             m_edWindow;
   CEdit             m_edWindowOut;
   CEdit             m_edStep;
   CEdit             m_edLayers;
   CEdit             m_edBatch;
   CEdit             m_edProbability;
   CComboBox         m_cbActivation;
   CComboBox         m_cbOptimization;
   CButton           m_btAddLayer;
   CButton           m_btDeleteLayer;

新しいモデルのオブジェクトの最後のブロックには、3つの要素しか含まれていません。これらは、モデルの一般的なアーキテクチャを表示するためのオブジェクト、新しいモデルを保存するためのボタン、および追加するニューラル層のアーキテクチャを記述する動的配列です。この場合、追加されるニューラル層のアーキテクチャを記述する動的配列の静的オブジェクトm_arAddLayersを作成しました。ニューラル層のアーキテクチャはツール内で作成されます。このオブジェクトは静的として作成することもできます。

   //--- new model
   CListView         m_lstNewModel;
   CButton           m_btSave;
   CArrayObj         m_arAddLayers;

クラスのpublicメソッドの基本的なリストを使用します。これらには、クラスコンストラクタとデストラクタ、パネル作成メソッド、およびイベントハンドラが含まれます。

親クラスの3つのメソッドがオーバーライドされました。これは、public継承によって回避できた可能性があります。

public:
                     CNetCreatorPanel();
                    ~CNetCreatorPanel();
   //--- main application dialog creation and destroy
   virtual bool      Create(const long chart, const string name, const int subwin, const int x1, const int y1);
   //--- chart event handler
   virtual bool      OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
 
   virtual void      Destroy(const int reason = REASON_PROGRAM) override { CAppDialog::Destroy(reason); }
   bool              Run(void) { return CAppDialog::Run();}
   void              ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
     {               CAppDialog::ChartEvent(id, lparam, dparam, sparam); }
  };

静的オブジェクトを使用するため、クラスのコンストラクタとデストラクタは実質的に空です。

インターフェイス要素の作成と配置に関連する作業の主要部分は、ダイアログウィンドウ作成メソッドCreateで実装されますが、メソッドの説明に移る前に、少し準備作業をおこないましょう。

まず、インターフェイスの内部空間を適切に編成するのに役立つ定数の数を定義する必要があります。完全なリストは添付ファイルに記載されています。

入力要素に加えて、インターフェイスには多数のテキストラベルが含まれていますが、それらのオブジェクトを宣言していないことに注目してください。これは、クラスの構造を単純化するために意図的におこなわれています。それらは視覚化のためにのみ必要なので、ツールの機能を作成するためには使用されません。ただし、これらのオブジェクトを作成する必要があります。このようなオブジェクトを作成する手順は、一部のデータを除いて繰り返されます。これには、オブジェクトのテキストとその場所が含まれる場合があります。コードを構造化するために、そのようなラベルを作成するための別のCreateLabelメソッドを作成します。

メソッドのパラメータでは、オブジェクト識別子、ラベルのテキスト、およびパネル上の座標を渡します。

メソッド本体では、まず新しいラベルオブジェクトを作成し、操作結果を確認します。次に、チャート上にオブジェクトを作成し、それに必要なコンテンツを渡し、作成したオブジェクトポインタをインターフェイスオブジェクトのコレクションを含む動的配列に追加します。

private変数にポインタを持つ新しいオブジェクトを作成しました。メソッド操作の実行中に、各操作の結果を確認し、エラーが発生した場合は、作成されたオブジェクトを削除します。ただし、メソッドを終了した後、プログラムが閉じられたときにさらに削除するために、作成されたオブジェクトへのポインタをクラスに残しません。これは、作成されたオブジェクトへのポインタをダイアログボックスオブジェクトのコレクションに渡したためです。このオブジェクトのすべての機能は、親クラスに既に実装されています。この機能には、プログラムを閉じたときのコレクションのすべてのオブジェクトの削除が含まれます。今のところ、ポインタをコレクションに渡したっきりにできます。

bool CNetCreatorPanel::CreateLabel(const int id, const string text, const int x1, const int y1, const int x2, const int y2)
  {
   CLabel *tmp_label = new CLabel();
   if(!tmp_label)
      return false;
   if(!tmp_label.Create(m_chart_id, StringFormat("%s%d", LABEL_NAME, id), m_subwin, x1, y1, x2, y2))
     {
      delete tmp_label;
      return false;
     }
   if(!tmp_label.Text(text))
     {
      delete tmp_label;
      return false;
     }
   if(!Add(tmp_label))
     {
      delete tmp_label;
      return false;
     }
//---
   return true;
  }

同様に、入力オブジェクトを作成するメソッドを作成しますが、新しいオブジェクトを作成する代わりに、クラスで以前に作成したオブジェクトを使用します。関連するポインタは、メソッドパラメータで渡されます。

bool CNetCreatorPanel::CreateEdit(const int id,
                                  CEdit& object,
                                  const int x1,
                                  const int y1,
                                  const int x2,
                                  const int y2,
                                  bool read_only)
  {
   if(!object.Create(m_chart_id, StringFormat("%s%d", EDIT_NAME, id), m_subwin, x1, y1, x2, y2))
      return false;
   if(!object.TextAlign(ALIGN_RIGHT))
      return false;
   if(!object.ReadOnly(read_only))
      return false;
   if(!Add(object))
      return false;
//---
   return true;
  }

さらに、列挙型と定数を使用して、作成されたニューラル層のアーキテクチャを記述します。ユーザーがそのような要素に誤った値を入力するのを避けるために、特別なコントロールを作成しましょう。ユーザーは、提案されたリストから1つの要素のみを選択できるようになります。そのような要素がいくつか必要です。ニューラル層のタイプを示す要素を作成することから始めましょう。この機能は、CreateComboBoxTypeメソッドで実装されます。このメソッドは特定の要素を作成するように設計されているため、パラメータでオブジェクトへのポインタを渡す必要はありません。ここでは、作成する要素の座標のみを指定する必要があります。

メソッド本体では、チャート上の指定された座標に要素を作成し、結果を確認します。

次に、要素にテキストの説明と数値IDを入力する必要があります。ニューラル層タイプの識別子をIDとして使用できますが、文字による説明がありません。したがって、数値識別子をテキスト記述に変換するには、別のLayerTypeToStringメソッドを作成します。そのアルゴリズムは非常に単純です。添付ファイルでご覧ください。ここでは、各タイプのニューラル層に対してのみこのメソッドを呼び出します。

メソッドの最後に、オブジェクトポインタをインターフェイスオブジェクトのコレクションに追加します。

動的オブジェクトと静的オブジェクトの両方をコレクションに追加することにご注意ください。これは、コレクション機能が、プログラム完了後のオブジェクトの削除に対する制御よりもはるかに広いためです。同時に、コレクション要素は、チャート上のオブジェクトの座標の決定とイベントの処理に関与します。指定されたコレクションの一般的な目的は、すべてのオブジェクトを単一の生物全体として機能させることです。

bool CNetCreatorPanel::CreateComboBoxType(const int x1, const int y1, const int x2, const int y2)
  {
   if(!m_cbNewNeuronType.Create(m_chart_id, "cbNewNeuronType", m_subwin, x1, y1, x2, y2))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronBaseOCL), defNeuronBaseOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronConvOCL), defNeuronConvOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronProofOCL), defNeuronProofOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronLSTMOCL), defNeuronLSTMOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronAttentionOCL), defNeuronAttentionOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronMHAttentionOCL), defNeuronMHAttentionOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronMLMHAttentionOCL), defNeuronMLMHAttentionOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronDropoutOCL), defNeuronDropoutOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronBatchNormOCL), defNeuronBatchNormOCL))
      return false;
   if(!m_cbNewNeuronType.ItemAdd(LayerTypeToString(defNeuronVAEOCL), defNeuronVAEOCL))
      return false;
   if(!Add(m_cbNewNeuronType))
      return false;
//---
   return true;
  }

同様に、活性化関数の列挙とパラメータ最適化メソッドのオブジェクトを作成します。列挙をテキスト形式に変換するには、標準のEnumToString関数を使用します。したがって、ループ内のリストに要素を追加できます。メソッドの完全なコードは添付ファイルにあります。

これで準備作業が完了し、ユーザーインターフェイスの作成に進むことができます。この機能はCreateメソッドで実行されます。パラメータでは、チャート上のパネルの右上隅の位置の座標を受け取るだけですが、オブジェクトを作成するには、パネルの寸法も必要です。便利な操作と将来の変更(必要な場合)を可能にするために、定義済みの定数を使用してパネルのサイズを設定します。パネルは、親クラスの同様のメソッドで作成されます。これは、メソッド本体で最初に呼び出されます。

bool CNetCreatorPanel::Create(const long chart, const string name, const int subwin, const int x1, const int y1)
  {
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x1 + PANEL_WIDTH, y1 + PANEL_HEIGHT))
      return false;

次に、作成したパネルにインターフェイスオブジェクトを追加します。オブジェクトは左上隅から順番に追加されます。新しい各オブジェクトの座標は、前のオブジェクトの座標にリンクされます。このアプローチにより、オブジェクトを平らな構造に構築できます。

上記のロジックに従って、事前訓練済みのモデルワークグループオブジェクトの作成を開始しましょう。1つ目はグループラベルです。ラベルを作成するには、ラベルの座標を決定し、以前に作成したCreateLabelメソッドを呼び出します。ラベルテキストと座標がこのメソッドに渡されます。一意のラベルIDを追加することを忘れないでください。

   int lx1 = INDENT_LEFT;
   int ly1 = INDENT_TOP;
   int lx2 = lx1 + LIST_WIDTH;
   int ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(0, "PreTrained model", lx1, ly1, lx2, ly2))
      return false;

次に、事前訓練済みのモデルでファイルの名前を選択するために使用される入力フィールドを作成します。これをおこなうには、作成されたオブジェクトの座標を垂直方向にシフトし、水平方向の座標は変更しません。したがって、2つのオブジェクトは厳密に互いの下に配置されます。

ユーザーがファイル名を手動で指定することはできません。代わりに、ユーザーに既存のファイルからファイルを選択するように求めます。このアクションの機能については、少し後で説明します。ここでは、ファイル名フィールドを読み取り専用にします。オブジェクトは、以前に作成したCreateEditメソッドを呼び出すことによって作成されます。フィールドを作成したら、情報メッセージを追加します。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateEdit(0, m_edPTModel, lx1, ly1, lx2, ly2, true))
      return false;
   if(!m_edPTModel.Text("Select file"))
      return false;

その下で、訓練済みモデルのニューラルフィールドの数を指定します。これをおこなうには、ニューラル層の数のテキストラベルと入力フィールド(この場合は出力)を作成します。このフィールドも読み取り専用になります。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(1, "Layers Total", lx1, ly1, lx1 + EDIT_WIDTH, ly2))
      return false;
//---
   if(!CreateEdit(1, m_edPTModelLayers, lx2 - EDIT_WIDTH, ly1, lx2, ly2, true))
      return false;
   if(!m_edPTModelLayers.Text("0"))
      return false;

同様に、コピーするニューラル層の数を入力するためのラベルとフィールドを作成します。ここでは、ユーザーがニューラル層の数を選択するのを制限するメカニズムを実装する必要があります。0未満またはモデル内のニューラル層の総数よりも大きくてはなりません。これは、CSpinEditクラスオブジェクトのインスタンスを使用して簡単に実行できます。このクラスを使用すると、有効な値の範囲を指定できます。残りはすでにクラスに実装されています。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(2, "Transfer Layers", lx1, ly1, lx1 + EDIT_WIDTH, ly2))
      return false;
//---
   if(!m_spPTModelLayers.Create(m_chart_id, "spPTMCopyLayers", m_subwin, lx2 - 100, ly1, lx2, ly2))
      return false;
   m_spPTModelLayers.MinValue(0);
   m_spPTModelLayers.MaxValue(0);
   m_spPTModelLayers.Value(0);
   if(!Add(m_spPTModelLayers))
      return false;

次に、事前訓練済みのモデルアーキテクチャの説明を含むウィンドウのみを表示する必要があります。これまでは、作成されたオブジェクトの座標を常に1レベル下にシフトしていたことに注意してください。この場合、上の境界のみを前のオブジェクトから下に移動しました。オブジェクトの下の境界線は、ウィンドウの高さからのインデントに設定されています。したがって、オブジェクトをウィンドウのサイズに引き延ばし、作成されたインターフェイスの下部に滑らかなエッジを取得します。

   lx1 = INDENT_LEFT;
   lx2 = lx1 + LIST_WIDTH;
   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ClientAreaHeight() - INDENT_BOTTOM;
   if(!m_lstPTModel.Create(m_chart_id, "lstPTModel", m_subwin, lx1, ly1, lx2, ly2))
      return false;
   if(!m_lstPTModel.VScrolled(true))
      return false;
   if(!Add(m_lstPTModel))
      return false;

これにより、事前訓練済みのモデルブロックでの操作が完了し、オブジェクトの2番目のブロックに進み、追加されたニューラル層のアーキテクチャを記述します。ブロックオブジェクトも上から下に作成されます。新しいオブジェクトの座標を定義するときは、座標を水平方向にシフトし、ウィンドウの上端からのインデントのレベルで上部境界線を定義します。

   lx1 = lx2 + CONTROLS_GAP_X;
   lx2 = lx1 + ADDS_WIDTH;
   ly1 = INDENT_TOP;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(3, "Add layer", lx1, ly1, lx2, ly2))
      return false;

以下では、インデント距離で、作成するニューラル層のタイプを選択するためのコンボボックスを作成します。これは、以前に作成したメソッドを使用しておこないます。このオブジェクトの幅は、ブロック全体の幅に等しくなります。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateComboBoxType(lx1, ly1, lx2, ly2))
      return false;

これに続いて、作成されたニューラル層のアーキテクチャを記述する要素が続きます。CLayerDescriptionニューラル層アーキテクチャ記述クラスの各要素に対して、要素の名前を含むテキストラベルと値入力フィールドの2つのオブジェクトを作成します。インターフェイスパネルの要素を厳密な順序で配置するには、テキストラベルをブロックの左側に、入力フィールドをブロックの右側に配置します。すべての入力フィールドのサイズは同じになります。このアプローチは一種のテーブルを作成します。

現在、9つの要素すべてに同一のコードを提供することはしません。以下は、テーブルから2つの行を作成するコードの例です。そのコードは添付ファイルにあります。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(4, "Neurons", lx1, ly1, lx1 + EDIT_WIDTH, ly2))
      return false;
//---
   if(!CreateEdit(2, m_edCount, lx2 - EDIT_WIDTH, ly1, lx2, ly2, false))
      return false;
   if(!m_edCount.Text((string)DEFAULT_NEURONS))
      return false;

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + EDIT_HEIGHT;
   if(!CreateLabel(5, "Activation", lx1, ly1, lx1 + EDIT_WIDTH, ly2))
      return false;
//---
   if(!CreateComboBoxActivation(lx2 - EDIT_WIDTH, ly1, lx2, ly2))
      return false;

追加されたニューラル層のアーキテクチャを記述する要素を作成したら、ニューラル層を追加および削除するための2つのボタンを追加しましょう。ボタン間のブロックの幅を半分に分割して、ボタンを一列に並べます。

   ly1 = ly2 + CONTROLS_GAP_Y;
   ly2 = ly1 + BUTTON_HEIGHT;
   if(!m_btAddLayer.Create(m_chart_id, "btAddLayer", m_subwin, lx1, ly1, lx1 + ADDS_WIDTH / 2, ly2))
      return false;
   if(!m_btAddLayer.Text("ADD LAYER"))
      return false;
   m_btAddLayer.Locking(false);
   if(!Add(m_btAddLayer))
      return false;
//---
   if(!m_btDeleteLayer.Create(m_chart_id, "btDeleteLayer", m_subwin, lx2 - ADDS_WIDTH / 2, ly1, lx2, ly2))
      return false;
   if(!m_btDeleteLayer.Text("DELETE"))
      return false;
   m_btDeleteLayer.Locking(false);
   if(!Add(m_btDeleteLayer))
      return false;

作成中のモデルの完全なアーキテクチャを説明する3番目の最後のブロックに移りましょう。ここには、上記で使用したすべてのメソッドがあります。

すべての要素を作成したら、trueでメソッドを終了します。すべてのメソッドとクラスの完全なコードは、以下の添付ファイルにあります。

これで、インターフェイスの要素の配置が完了したので、エキスパートアドバイザー(EA)に追加できます。しかし、この形では、銘柄チャートの美しい絵になるだけです。次に、必要な機能をフォームに実装する必要があります。


2.3 ツール機能の実装

引き続きツールの作成に取り組みます。次のステップはインターフェイスに必要な機能を提供することです。先に進む前に、ツールに必要なアルゴリズムに戻りましょう。

  1. まず、訓練済みのモデルが保存されているファイルを開く。これをおこなうには、ユーザーがオブジェクトをクリックしてファイルを選択する。ユーザーが指定された拡張子を持つ既存のファイルを選択するダイアログボックスが開く。
  2. ユーザーがファイルを選択すると、ツールは指定されたファイルからモデルを読み込み、読み込まれたモデルに関する情報(ニューロン層のタイプと数、各層のニューロン数)を表示する。
  3. デフォルトで読み込まれたモデルに関する情報の出力とともに、そのすべてのニューラル層が新しいモデルにコピーされるように設定される。それらに関する情報は、作成されたモデルの記述ブロックにもコピーされる。
  4. ユーザーは、コピーされたニューラル層の数を手動で変更できる。コピーされるニューラル層の数の変更と同時に、作成されるモデルのアーキテクチャに変更を加える。これは、作成されたモデルのアーキテクチャを記述するブロックに反映される。
  5. コピーされたニューラル層の数を選択した後、ユーザーは新しいニューラル層のタイプとアーキテクチャを手動で指定し、[ADD LAYER]ボタンを押して作成したモデルに追加する。
  6. 誤ってニューラル層がモデルに追加された場合、ユーザーはモデルアーキテクチャを記述するブロックでそのようなニューラル層を選択し、[DELETE]ボタンを押して削除する。削除できるのは、追加されたニューラル層のみ。ドナーモデルの層を削除するには、ツールを使用して、コピーされたニューラル層の数を変更する必要がある。
  7. 作成したニューラルネットワークのアーキテクチャを作成したら、ユーザーは[SAVE MODEL]ボタンを押す。ダイアログボックスが開き、ユーザーは既存のファイルを選択するか、新しいファイルの名前を指定する。

ツールを使用するのは論理的なシナリオのように思えますが、実装するにはいくらかの努力が必要です。まず、保存されたモデルに関する情報を取得する機能が必要です。以前は、読み込まれたモデルに関する情報をユーザーに提供していませんでした。この機能を実装するには、ニューラルネットワーククラスを変更する必要があります。ただし、この機能はモデル自体の操作には影響しないため、以前に作成したCNetニューラルネットワークモデルクラスの直接の後継となる新しいCNetModifyクラスに追加します。

新しいクラスでは新しいオブジェクトを作成しません。したがって、クラスコンストラクタとデストラクタは空のままになります。LayersTotalメソッドは、モデル内のニューラル層の数を返します。配列のサイズを返すだけなので、アルゴリズムに複雑なことは何もありません。そのコードは添付ファイルにあります。

class CNetModify :  public CNet
  {
public:
                     CNetModify(void) {};
                    ~CNetModify(void) {};
   //---
   uint              LayersTotal(void);
   CArrayObj*        GetLayersDiscriptions(void);
  };

使用されているニューラルネットワークに関する情報を取得するためのGetLayersDiscriptionsメソッドについて少し説明しましょう。このメソッドを実行した結果、モデルコンストラクタメソッドのパラメータで渡されたモデルの説明と同様に、ニューラルネットワークアーキテクチャの説明を含む動的配列を受け取る必要があります。このプロセスを整理する複雑さは、ニューラル層のハイパーパラメータを取得するためのメソッドを以前に作成していないという事実に関連しています。したがって、対応するメソッドをニューラル層クラスに追加する必要があります。まず、GetLayerInfoメソッドをCNeuronBaseOCLニューラル層基本クラスに追加します。

新しいメソッドにはパラメータが含まれておらず、実行後、CLayerDescriptionニューラル層記述オブジェクトが返されます。メソッド本体では、最初にニューラル層記述オブジェクトのインスタンスを作成します。次に、現在のニューラル層のハイパーパラメータを入力します。その後、メソッドを終了し、作成したオブジェクトポインタを呼び出しプログラムに返します。

CLayerDescription* CNeuronBaseOCL::GetLayerInfo(void)
  {
   CLayerDescription* result = new CLayerDescription();
   if(!result)
      return result;
//---
   result.type = Type();
   result.count = Output.Total();
   result.optimization = optimization;
   result.activation = activation;
   result.batch = (int)(optimization == LS ? iBatch : 1);
   result.layers = 1;
//---
   return result;
  }

ニューラル層の基本クラスにメソッドを追加することで、そのすべての子孫にメソッドを追加しました。したがって、すべてのニューラル層はこのメソッドを持っています。これで、任意のニューラル層から同様の情報を取得できます。このデータで十分な場合は、ニューラル層での作業を終了し、モデル情報の収集方法に進むことができます。

ただし、各ニューラル層に特定の情報が必要な場合は、すべてのニューラル層でこのメソッドをオーバーライドする必要があります。以下は、サブサンプリング層でのメソッドオーバーライドの例です。これにより、分析されたウィンドウサイズとその移動ステップに関するデータを取得できます。メソッド本体で、最初に親クラスのメソッドを呼び出して、基になるハイパーパラメータを取得します。次に、結果のニューラル層記述オブジェクトに特定のパラメータを追加します。その後、ニューラル層の記述オブジェクトへのポインタを呼び出しプログラムに返すことにより、メソッドを終了します。

CLayerDescription* CNeuronProofOCL::GetLayerInfo(void)
  {
   CLayerDescription *result = CNeuronBaseOCL::GetLayerInfo();
   if(!result)
      return result;
   result.window = (int)iWindow;
   result.step = (int)iStep;
//---
   return result;
  }

前述のすべてのタイプのニューラル層に対する同様のメソッドは、以下の添付ファイルで利用できます。

これで、各ニューラル層のハイパーパラメータに関する情報を取得できます。この情報は、共通の構造に組み合わせることができます。CNetModify::GetLayersDiscriptionsメソッドに戻り、その中に動的配列を作成して、ニューラル層記述オブジェクトへのポインタを格納しましょう。

次に、すべてのニューラル層を通るループを作成します。ループ本体では、上記で作成したメソッドを呼び出して、各ニューラル層からアーキテクチャ記述オブジェクトを要求します。取得したオブジェクトは動的配列に追加されます。

ループのすべての反復を実行すると、完全に読み込まれたモデルアーキテクチャの説明を含む動的配列が作成されます。メソッドの完了後、呼び出し元プログラムに返します。

CArrayObj* CNetModify::GetLayersDiscriptions(void)
  {
   CArrayObj* result = new CArrayObj();
   for(uint i = 0; i < LayersTotal(); i++)
     {
      CLayer* layer = layers.At(i);
      if(!layer)
         break;
      CNeuronBaseOCL* neuron = layer.At(0);
      if(!neuron)
         break;
      if(!result.Add(neuron.GetLayerInfo()))
         break;
     }
//---
   return result;
  }

この段階で、以前に作成されたモデルのアーキテクチャの記述を取得する可能性を実装しました。次に、事前訓練済みのモデルをユーザー指定のファイルから読み込むメソッドの実装に移ることができます。この機能を実装するには、CNetCreatorPanel::LoadModelメソッドを作成します。このメソッドは、モデルを読み込むファイルの名前をパラメータで受け取ります。

メソッド本体では、まず指定されたファイルからモデルを読み込みます。モデルのLoadメソッドを呼び出す前に、パラメータの値は確認しません。これは、すべてのコントロールがloadメソッドで実装されているためです。動作結果のみを確認します。モデル読み込みエラーの場合、読み込まれたモデル記述ブロックにエラー情報を出力します。

bool CNetCreatorPanel::LoadModel(string file_name)
  {
   float error, undefine, forecast;
   datetime time;
   ResetLastError();
   if(!m_Model.Load(file_name, error, undefine, forecast, time, false))
     {
      m_lstPTModel.ItemsClear();
      m_lstPTModel.ItemAdd("Error of load model", 0);
      m_lstPTModel.ItemAdd(file_name, 1);
      int err = GetLastError();
      if(err == 0)
         m_lstPTModel.ItemAdd("The file is damaged");
      else
         m_lstPTModel.ItemAdd(StringFormat("error id: %d", GetLastError()), 2);
      m_edPTModel.Text("Select file");
      return false;
     }

モデルの読み込みに成功したら、読み込まれたファイルの名前とニューラル層の数をインターフェイスの対応する要素に表示します。

以前に読み込んたモデルの説明があれば削除します。次に、読み込まれたモデルのアーキテクチャに関する情報を収集するためのメソッドを呼び出します。

   m_edPTModel.Text(file_name);
   m_edPTModelLayers.Text((string)m_Model.LayersTotal());
   if(!!m_arPTModelDescription)
      delete m_arPTModelDescription;
   m_arPTModelDescription = m_Model.GetLayersDiscriptions();

読み込まれたモデルに関する情報を受信した後、ループを作成します。ループの本体では、受信した情報をインターフェイスの対応するブロックに出力します。

   m_lstPTModel.ItemsClear();
   int total = m_arPTModelDescription.Total();
   for(int i = 0; i < total; i++)
     {
      CLayerDescription* temp = m_arPTModelDescription.At(i);
      if(!temp)
         return false;
      //---
      string item = StringFormat("%s (units %d)", LayerTypeToString(temp.type), temp.count);
      if(!m_lstPTModel.AddItem(item, i))
         return false;
     }

メソッドの最後で、コピーされたニューラル層の許容数の値の範囲を、読み込まれたモデルの合計サイズに変更します。読み込まれたモデル全体をコピーするようツールに指示します。その後、メソッドを終了します。

   m_spPTModelLayers.MaxValue(total);
   m_spPTModelLayers.Value(total);
//---
   return true;
  }

ご覧のとおり、上記のメソッドは、パラメータを介して、呼び出し元のプログラムからデータをロードするファイルの名前を受け取ります。ユーザーがモデルファイルを選択できるようにする必要があります。

別のOpenPreTrainedModelメソッドを作成しましょう。このメソッドの本体では、ファイルダイアログボックスのインターフェイスを既に実装している標準のFileSelectDialog関数のみを呼び出します。関数呼び出し時に、必要なファイル拡張子と、既存のファイルのみを指定できることを示すFSD_FILE_MUST_EXISTフラグを指定します。

特定のフラグを使用すると、この関数で複数のファイルを選択できます。したがって、実行の結果として、FileSelectDialogは選択されたファイルの数を返します。ファイルの名前は、関数がパラメータで受け取るポインタである配列に含まれています。

したがって、ユーザーがファイルを選択すると、その名前がパラメータで上記のメソッドに渡されます。それ以外の場合は、ユーザーがデータを読み込むファイルを選択する必要があることを求めるメッセージが生成されます。

bool CNetCreatorPanel::OpenPreTrainedModel(void)
  {
   string filenames[];
   if(FileSelectDialog("Select a file to load data", NULL,
                       "Neuron Net (*.nnw)|*.nnw|All files (*.*)|*.*",
                       FSD_FILE_MUST_EXIST, filenames, NULL) > 0)
     {
      if(!LoadModel(filenames[0]))
         return false;
     }
   else
      m_edPTModel.Text("Files not selected");
//---
   return true;
  }

徐々に前進し、すでにインターフェイスの視覚化を作成しています。また、ファイルを選択して事前訓練済みモデルを読み込む一連のメソッドも作成しました。しかし、これまでのところ、これら2つのプログラムブロックは1つの有機的なプログラムに結合されていません。データ読み込みメソッドは、読み込まれたデータモデルに関する情報をパネルに表示します。でも今のところ、一方通行です。プログラムがユーザーのアクションと情報に対するユーザーの反応に関する情報を受け取る方法を指定する必要があります。

これをおこなうには、イベントハンドラを使用します。CAppDialog子クラスでは、このメカニズムはマクロ置換によって実装されます。この目的のために、EVENT_MAP_BEGINマクロで始まりEVENT_MAP_ENDマクロで終わるマクロのブロックがプログラムコードで作成されます。それらの間には、さまざまなイベントに対応する多数のマクロがあります。この場合、数値識別子によるイベント処理を意味するON_EVENTマクロを使用します。ファイル名オブジェクトでマウスクリックイベントを処理するには、マクロ本体でON_CLICKイベント、m_edPTModelオブジェクトポインタ、およびOpenPreTrainedModelイベントが発生したときに呼び出されるメソッドの名前を指定します。したがって、ファイル名入力ボックスに対応するm_edPTModelオブジェクトでマウスボタンが押されると、プログラムはOpenPreTrainedModelメソッドを呼び出し、事前訓練済みモデル読み込みメソッドのチェーンを開始します。

EVENT_MAP_BEGIN(CNetCreatorPanel)
ON_EVENT(ON_CLICK, m_edPTModel, OpenPreTrainedModel)
ON_EVENT(ON_CLICK, m_btAddLayer, OnClickAddButton)
ON_EVENT(ON_CLICK, m_btDeleteLayer, OnClickDeleteButton)
ON_EVENT(ON_CLICK, m_btSave, OnClickSaveButton)
ON_EVENT(ON_CHANGE, m_spPTModelLayers, ChangeNumberOfLayers)
ON_EVENT(ON_CHANGE, m_lstPTModel, OnChangeListPTModel)
EVENT_MAP_END(CAppDialog)

同様に、それらによって呼び出される他のイベントとメソッドについて説明しましょう。

  • OnClickAddButton - [ADD LAYER]ボタンのクリックイベント
  • OnClickDeleteButton — [DELETE]ボタンのクリックイベントを処理するメソッド
  • OnClickSaveButton —[SAVE MODEL]ボタンのクリックを処理するメソッド
  • ChangeNumberOfLayers — コピーするニューラル層の数を変更するイベントを処理するメソッド
  • OnChangeListPTModel — モデルアーキテクチャの説明リンクのニューラル層でのマウスクリックを処理するメソッド

すべてのクラスとそのメソッドの完全なコードは、添付ファイルにあります。新しいモデルを解決するメソッドを考えてみましょう。その実装はかなり複雑であり、CNetModifyニューラルネットワークモデルクラスで追加のメソッドを作成する必要があります。

このメソッドのアルゴリズムは、条件付きで3つのブロックに分割できます。

  • 事前訓練済みモデルからニューラル層をコピーする
  • モデルに新しいニューラル層を追加する
  • モデルをファイルに保存する

現時点では、ニューラルネットワーククラスには最後のポイントのみが実装されています。ニューラル層を別のモデルからコピーしたり、新しいニューラル層を既存のモデルに追加したりする方法はありません。

ポイントごとに行きましょう。まず、ニューラル層をコピーするためのメカニズムを作成します。ニューラル層のアーキテクチャに応じて、さまざまな数のオブジェクトを含めることができます。ただし、さまざまなパラメータ最適化方法を使用して、すべてのタイプのニューラル層をコピーできる普遍のアルゴリズムが必要です。訓練済みモデルのコピーには、アーキテクチャだけでなく、すべての重みも転送されます。ここで、質問です。各ニューラル層のすべての要素をコピーする必要があるのはなぜでしょうか。ニューラル層の必要なオブジェクトにポインタを単純にコピーできないのはなぜでしょうか。ポインタを使用することで、プログラムコードのさまざまな部分から同じオブジェクトにアクセスできます。このプロパティを使用します。2つのメソッドを作成しましょう。モデル構造内の番号によって、ニューラル層オブジェクトへのポインタが返されます。2つ目は、ニューラル層オブジェクトへのポインタをモデルアーキテクチャに追加します。

CLayer* CNetModify::GetLayer(uint layer)
  {
   if(!layers || LayersTotal() <= layer)
      return NULL;
//---
   return layers.At(layer);
  }

bool CNetModify::AddLayer(CLayer *new_layer)
  {
   if(!new_layer)
      return false;
   if(!layers)
     {
      layers = new CArrayLayer();
      if(!layers)
         return false;
     }
//---
   return layers.Add(new_layer);
  }

連続するニューラル層のブロックをコピーしてから、シーケンスを維持しながらポインタを新しいモデルに転送することにより、そのようなニューラル層間のすべての関係を保存します。

これが最初のポイントでした。先に進みます。モデルのコンストラクタは、アーキテクチャの記述に従って新しいモデルを作成できます。モデルにニューラル層を追加するとき、ニューラル層の同様の記述を作成しました。モデルがすでにその方法を知っている新しい層を追加するだけでよいようですが、問題は、コピーされたニューラル層と新しく作成されたニューラル層との間のブリッジがないということです。

ニューラル層のアーキテクチャによると、あるニューラル層の重みは別のニューラル層の要素に直接関係しています。したがって、フィードフォワードおよびバックワードモードで機能するモデルを維持するには、この接続を構築する必要があります。CNeuronBaseOCLニューラル層基本クラスの初期化メソッドを見ると、そのパラメータの中で、後続のニューラル層のニューロン数に気付くことができます。このパラメータは、作成される重み行列のサイズと、パラメータの最適化で使用される関連するバッファを決定します。

最初に、クラスにCNeuronBaseOCLメソッドを追加します。このメソッドは、後続の層CNeuronBaseOCL::numOutputsで指定されたニューロン数に従って重み行列を調整します

メソッドのパラメータでは、次の層のニューロン数とパラメータ最適化メソッドを渡します。

メソッド本体では、パラメータで受け取った後続のニューラル層の要素数を確認し、必要に応じて適切なサイズの重み行列を作成します。新しく追加されたニューラル層を参照するため、ランダムな重みで埋めます。入力された行列の場合、OpenCLコンテキストでバッファを作成し、行列の内容をそこに渡します。

クラスメソッドは、データをファイルに保存する前にコンテキストからデータを読み込もうとするため、OpenCLコンテキストにデータを渡す必要があります。エラーが発生した場合、モデルの保存が中止され、falseの結果が返されます。もちろん、ニューラル層クラスのメソッドを変更することもできます。しかし、それにかかる手間は、情報をOpenCLコンテキストに転送したり戻したりするためのコストを上回っていると思います。

bool CNeuronBaseOCL::numOutputs(const uint outputs, ENUM_OPTIMIZATION optimization_type)
  {
   if(outputs > 0)
     {
      if(CheckPointer(Weights) == POINTER_INVALID)
        {
         Weights = new CBufferFloat();
         if(CheckPointer(Weights) == POINTER_INVALID)
            return false;
        }
      Weights.BufferFree();
      Weights.Clear();
      int count = (int)((Output.Total() + 1) * outputs);
      if(!Weights.Reserve(count))
         return false;
      float k = (float)(1 / sqrt(Output.Total() + 1));
      for(int i = 0; i < count; i++)
        {
         if(!Weights.Add((2 * GenerateWeight()*k - k)*WeightsMultiplier))
            return false;
        }
      if(!Weights.BufferCreate(OpenCL))
         return false;

重み行列を作成したら、重み最適化プロセスで使用されるデータバッファを作成します。

重みと関連するバッファの行列が必要ない場合は、それらを不要なものとして削除します。その後、メソッドを終了します。

メソッドの完全なコードは、以下の添付ファイルにあります。

ここで、CNetModifyクラスに戻り、与えられたAddLayersの説明に従ってニューラル層を追加するメソッドを作成しましょう。メソッドパラメータで、追加されるニューラル層のアーキテクチャの記述と共に動的配列へのポインタを渡します。メソッド本体ではすぐに、受信したデータを確認します。受信したポインタは有効で、少なくとも1つのニューラル層の説明が含まれている必要があります。

bool CNetModify::AddLayers(CArrayObj *new_layers)
  {
   if(!new_layers || new_layers.Total() <= 0)
      return false;
//---
   if(!layers || LayersTotal() <= 0)
     {
      Create(new_layers);
      return true;
     }

次に、モデルに存在するニューラル層の数を確認します。存在しない場合は、単に親クラスのコンストラクタを呼び出して、指定されたアーキテクチャで新しいモデルが作成されます。

ニューラル層を既存のモデルに追加する場合は、最初にローカル変数を宣言します。

   CLayerDescription *desc = NULL, *next = NULL;
   CLayer *temp;
   int outputs;

次に、少し準備作業をおこない、上記で作成したメソッドを呼び出して2つのニューラル層を結合します。

   int shift = (int)LayersTotal() - 1;
   CLayer* last_layer = layers.At(shift);
   if(!last_layer)
      return false;
//---
   CNeuronBaseOCL* neuron = last_layer.At(0);
   if(!neuron)
      return false;
//---
   desc = neuron.GetLayerInfo();
   next = new_layers.At(0);
   outputs = (next == NULL || (next.type != defNeuron && next.type != defNeuronBaseOCL) ? 0 : next.count);
   if(!neuron.numOutputs(outputs, next.optimization))
      return false;
   delete desc;

さらに、親クラスのコンストラクタと同様に、モデルアーキテクチャ記述の動的配列をループし、すべてのニューラル層を順次追加します。このブロックのコードは、親クラスコンストラクタのコードを完全に繰り返すものなので、この記事では取り上げません。すべてのメソッドとクラスの完全なコードは、以下の添付ファイルにあります。

ツールのCNetCreatorPanelクラスに戻り、モデル保存ボタンの押下イベントを処理するメソッドを作成しましょう。これにより、新しいモデルを作成する上記のメソッドが1つのシーケンスに結合されます。

OnClickSaveButtonメソッドの開始時に、モデルを保存するファイルを指定するようにユーザーに促します。これをおこなうには、既におなじみのFileSelectDialog関数を使用します。今回は、ファイルが書き込み用に作成されていることを示すフラグを変更します。また、デフォルトのファイル名を指定します。

bool CNetCreatorPanel::OnClickSaveButton(void)
  {
   string filenames[];
   if(FileSelectDialog("Select files to save", NULL,
                       "Neuron Net (*.nnw)|*.nnw|All files (*.*)|*.*",
                       FSD_WRITE_FILE, filenames, "NewModel.nnw") <= 0)
     {
      Print("File not selected");
      return false;
     }

次に、ニューラルネットワーククラスの新しいインスタンスを作成し、操作の結果を確認します。

   string file_name = filenames[0];
   if(StringLen(file_name) - StringLen(EXTENSION) > StringFind(file_name, EXTENSION))
      file_name += EXTENSION;
   CNetModify* new_model = new CNetModify();
   if(!new_model)
      return false;

新しいモデルを正常に作成したら、ループを実装して、必要な数のニューラル層をコピーします。コピーされたすべてのニューラル層について、学習フラグをfalseに切り替える必要があります。したがって、後続の訓練のプロセスでこれらの層の重みを更新するプロセスを無効にします。後に、文字通り1つのメソッドを呼び出すことで、モデルのすべてのニューラル層のこのフラグをプログラムで変更できます。

   int total = m_spPTModelLayers.Value();
   bool result = true;
   for(int i = 0; i < total && result; i++)
     {
      CLayer* temp = m_Model.GetLayer((uint)i);
      if(!temp)
        {
         result = false;
         break;
        }
      CNeuronBaseOCL* neuron = temp.At(0);
      neuron.TrainMode(false);
      if(!new_model.AddLayer(temp))
         result = false;
     }

ニューラル層のコピーの繰り返しが完了したら、上記のメソッドを呼び出してニューラル層を追加し、新しいモデルの作成を完了します。

   new_model.SetOpenCL(m_Model.GetOpenCL());
   if(result && m_arAddLayers.Total() > 0)
      if(!new_model.AddLayers(GetPointer(m_arAddLayers)))
         result = false;

その後、作成したモデルを保存するだけです。

   if(result && !new_model.Save(file_name, 1.0e37f, 100, 0, 0, false))
      result = false;
//---
   if(!!new_model)
      delete new_model;
   LoadModel(m_edPTModel.Text());
//---
   return result;
  }

モデルを保存した後、訓練は別のプログラムで実行されるため、モデルを削除できます。

モデルを削除すると、コピーされたニューラル層も削除されることにご注意ください。データを新しいモデルにコピーせず、ポインタのみを渡したためです。すでに使用されているモデルに基づいて別のモデルを作成する場合は、それを再度読み込む必要があります。不要なルーチンを避けるために、モデルを再度読み込むためのメソッドを呼び出します。その後初めてメソッドを終了します。

これで、クラスコードでの作業は終了です。次に来るのはテストです。


3.テスト

作成したツールをテストするために、NetCreator.mq5 EAを作成しましょう。EAコードは非常に単純で、上記で作成したCNetCreatorPanelクラスの接続のみが含まれています。実際、クラスのEAへの統合は3つのポイントで実行されます。OnInit関数でのモデルの初期化と起動、OnDeinit関数でのクラスの破棄、OnChartEventメソッドでのクラスへのイベントの受け渡しです。すべての統合ポイントのコードを以下に示します。

#include "NetCreatorPanel.mqh"
CNetCreatorPanel Panel;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!Panel.Create(0, "NetCreator", 0, 50, 50))
      return INIT_FAILED;
   if(!Panel.Run())
      return INIT_FAILED;
//---
   return(INIT_SUCCEEDED);
  }

void OnDeinit(const int reason)
  {
//---
   Panel.Destroy(reason);
  }

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id == CHARTEVENT_OBJECT_CLICK)
      Sleep(0);
   Panel.ChartEvent(id, lparam, dparam, sparam);
  }

実際のテストでは、モデル間のニューラル層の転送と、新しい層を追加する可能性に関する期待が確認されました。さらに、このツールを使用すると、まったく新しいモデルを作成できます。これにより、作成したモデルの記述をプログラムコードからそれることができます。  


結論

この記事では、ニューラル層の一部をモデル間で転送するためのツールを作成しました。また、任意のアーキテクチャの任意の数の新しい層を追加することもできます。以前に訓練したモデルで実験して、アーキテクチャの変更がモデルの生産性にどのように影響するかを確認するようお勧めします。

1つのモデルにさまざまなアーキテクチャを組み合わせるなど多くの実験をおこなって、モデルのアーキテクチャを変更することができます。同時に、結果とソースデータ層のアーキテクチャを保持する場合、完全に新しいモデルアーキテクチャを既存のEAに「配置」することができます。その後、モデルを訓練し、アーキテクチャの影響とモデルのエラーを比較します。


参考文献リスト

  1. ニューラルネットワークが簡単に(第20部):オートエンコーダ
  2. ニューラルネットワークが簡単に(第21部):変分オートエンコーダ(VAE)
  3. ニューラルネットワークが簡単に(第22部):回帰モデルの教師なし学習

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

# 名前 タイプ 詳細
1 NetCreator.mq5 EA   モデル構築ツール
2 NetCreatotPanel.mqh クラスライブラリ ツールを作成するためのクラスライブラリ
3 NeuroNet.mqh クラスライブラリ ニューラルネットワークを作成するためのクラスのライブラリ
4 NeuroNet.cl コードベース OpenCLプログラムコードライブラリ


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

添付されたファイル |
MQL5.zip (71.47 KB)
データサイエンスと機械学習—ニューラルネットワーク(第01回):フィードフォワードニューラルネットワークの解明 データサイエンスと機械学習—ニューラルネットワーク(第01回):フィードフォワードニューラルネットワークの解明
ニューラルネットワークの背後にある操作全体は、多くの人に気に入られていますが、ほとんどの人に理解されていません。この記事では、フィードフォワード型の多層知覚の密室の背後にあるすべてを平易な言葉で説明しようとします。
一からの取引エキスパートアドバイザーの開発(第27部):未来に向かって(II) 一からの取引エキスパートアドバイザーの開発(第27部):未来に向かって(II)
チャート上直接の発注システムをより完全にしましょう。この記事では、発注システムを修正する方法、またはより直感的にする方法を示します。
DoEasy-コントロール(第14部):グラフィック要素に名前を付けるための新しいアルゴリズム。TabControl WinFormsオブジェクトへの作業の継続 DoEasy-コントロール(第14部):グラフィック要素に名前を付けるための新しいアルゴリズム。TabControl WinFormsオブジェクトへの作業の継続
この記事では、カスタムグラフィックを構築するためのすべてのグラフィック要素に名前を付けるための新しいアルゴリズムを作成し、TabControl WinFormsオブジェクトの開発を継続する予定です。
勢力指数による取引システムの設計方法を学ぶ 勢力指数による取引システムの設計方法を学ぶ
最も人気のあるテクニカル指標によって取引システムを設計する方法についての連載の新しい記事へようこそ。今回は、新しく、勢力指数(Force Index)テクニカル指標と、この指標を使った取引システムの作り方についてご紹介します。