English Русский 中文 Español Deutsch Português
preview
ALGLIBライブラリの最適化手法(第1回):

ALGLIBライブラリの最適化手法(第1回):

MetaTrader 5テスター |
35 1
Andrey Dik
Andrey Dik

内容

  1. はじめに
  2. ALGLIBライブラリの最適化方法


はじめに

MetaTrader 5端末の標準配布には、数値解析における強力なツールであるALGLIBライブラリが含まれており、取引システム開発者にとって有用な機能を提供しています。ALGLIBライブラリは、以下の多岐にわたる数値解析手法を提供しています。

  • 線形代数:連立一次方程式の解法、固有値・固有ベクトルの計算、行列分解など
  • 最適化:一次元および多次元の最適化手法
  • 補間と近似:多項式補間およびスプライン補間、最小二乗法による関数の近似
  • 数値積分および微分:台形法やシンプソン法などの積分手法、数値微分
  • 微分方程式の数値解法:常微分方程式とその数値的な解法
  • 統計手法:パラメータ推定、回帰分析、乱数生成
  • フーリエ解析:高速フーリエ変換(FFT)

ALGLIBにおける決定論的最適化手法は、勾配降下法のさまざまな変種に基づいており、解析的および数値的アプローチの両方を使用することができます。本記事では、特にトレーダーの実務的な課題に最も適している数値的手法に焦点を当てます。

金融分野の多くの問題は、価格データが個別の点として表されるため、本質的に離散的であることが多いことは重要なポイントです。したがって、特に勾配の数値的表現を使用する手法に注目することになります。ユーザー自身が勾配を計算する必要はなく、この作業は最適化手法が自動的におこなってくれます。

ALGLIBの最適化手法を使いこなすのは簡単ではありません。というのも、関数名の命名規則やアクセス順に一貫性がなく、情報が整理されていないためです。本記事の主な目的は、ライブラリに関する情報のギャップを埋め、シンプルな使用例を通して理解を深めることです。まずは、これらの手法がどのような目的で使われるのかを把握するために、基本的な用語と概念を整理しましょう。

最適化とは、与えられた条件や制約のもとで最良の解を見つけるプロセスを指します。解を探索するということは、少なくとも2つ以上の選択肢が存在し、その中から最も優れたものを選ぶ必要があることを意味します。

最適化問題とは、ある条件を満たす可能な解の集合の中から、最大または最小の値を与える解を見つける問題です。最適化問題の主な構成要素は以下の通りです。

  1. 目的関数(適合度関数):最大化または最小化すべき対象の関数。たとえば、利益の最大化やコストの最小化など(利益やコストが最適化基準)。
  2. 変数:最適な結果を得るために調整されるパラメータ。
  3. 制約条件:最適解を探索する際に満たすべき条件。

目的関数は、ユーザーが任意に定義できる関数であり、入力(最適化対象)を受け取って、最適化基準となる値を返します。たとえば、過去のデータに基づいて取引システムをテストする関数が目的関数になりうる場合、その関数のパラメータは取引システムの設定であり、出力値はそのシステムのパフォーマンス指標となります。

制約の種類

  1. 境界制約(ボックス制約):変数の値に対する制限。たとえば、「x」は1〜3の範囲にある必要がある。
  2. 線形等式制約:変数を含む式が特定の値と等しくなる必要がある条件。例:(x + y = 5)
  3. 線形不等式制約:変数を含む式が特定の値より以上または以下である必要がある条件。例:(x - y ≥ 1)

本記事の例では、境界制約のみを取り扱います。この記事では、ALGLIBの最適化手法を効果的かつ簡単に使いこなすためのテクニックと、シンプルな使用例を紹介します。

使用する例としては、最大化すべき目的関数として、滑らかな単峰関数(逆放物線)を用います。この関数は、引数の数にかかわらず、定義域は[-10, 10]であり、関数値は常に[0, 1]の範囲に収まります。

//——————————————————————————————————————————————————————————————————————————————
//Paraboloid, F(Xn) ∈ [0.0; 1.0], X ∈ [-10.0; 10.0], maximization
double ObjectiveFunction (double &x [])
{
  double sum = 0.0;

  for (int i = 0; i < ArraySize (x); i++)
  {
    if (x [i] < -10.0 || x [i] > 10.0) return 0.0;
    sum += (-x [i] * x [i] + 100.0) * 0.01;
  }
  
  return sum /= ArraySize (x);
}
//——————————————————————————————————————————————————————————————————————————————


BLEIC (Boundary, Linear Equality-Inequality Constraints)

BLEIC (Boundary, Linear Equality-Inequality Constraints)は、等式および不等式の制約を含む最適化問題を解くために使用される手法群の名称です。この名前は、制約条件をアクティブ(有効)と非アクティブ(無効)に分類するアプローチに由来しています。この手法では、等式および不等式制約を含む問題を、等式のみで構成された一連のサブ問題に変換して解決します。現在の点においてアクティブな不等式制約は等式として扱い、非アクティブな制約は一時的に無視されます(ただし、常に監視は続けられます)。簡単に言えば、現在の解候補(点)は実行可能領域内を移動しながら、制約条件の境界に「張り付いたり離れたり」するような動きをします。

1. 何をするのか:BLEICは、次のような制約を考慮しながら、ある関数の最小値または最大値を求めます(例:利益の最大化、コストの最小化)。

  • 変数の範囲制限(境界条件):たとえば、価格は負にならない
  • 等式制約:たとえば、ポートフォリオの比率の合計が100%でなければならない
  • 不等式制約:たとえば、リスクは特定の水準を超えてはならない

2. どのように動作するのか:BLEICは限られたメモリ(メモリ効率)を使って動作します。つまり、すべての中間計算結果を保存するのではなく、重要な情報のみを保持しながら、以下のように一歩ずつ解に近づいていきます。

  • 現在の状況を評価
  • 移動すべき方向を決定
  • その方向へ一歩進む
  • 制約条件を満たしているか確認
  • 必要に応じて移動方向を調整

3.特徴

  • 関数が滑らかである必要がある(急激なジャンプがない)
  • 大域的最小値ではなく、局所最小値を求める手法
  • 初期値(出発点)に敏感

簡単な例え:家を建てる場所を土地の中から探していると想像してください。以下のような制約があります。家の広さは決められている(等式制約)、境界線からXメートル以上離す必要がある(不等式制約)、敷地内に収める必要がある(境界条件)、最も眺めが良い場所に建てたい(目的関数)。BLEICは、これらの制約を守りながら、敷地内で理想的な位置を少しずつ移動しながら探し出すイメージです。この手法の詳細や次に紹介するアルゴリズムについては、ライブラリ作成者のページをご覧ください。

BLEICをはじめとするALGLIBライブラリの最適化手法を使用するには、ライブラリのファイルをプロジェクトに含める必要があります。ただし、このライブラリはMetaTrader 5端末に標準搭載されているため、ユーザーが追加でインストールする必要はありません。

#include <Math\Alglib\alglib.mqh>

ALGLIB手法を効果的に活用するためのスクリプトを作成してみましょう。以下に、ALGLIB手法を使用する際に典型的な主なステップを示します。同一のコードブロックは、視認性を高めるため適切に色分けして表示しています。

1.目的関数の実行回数、最適化するパラメータの範囲、およびそのステップ幅など、問題の境界条件を定義します。ALGLIB手法を使用するには、最適化パラメータ「x」の初期値を設定する必要があります(ALGLIB手法は決定論的であり、結果は初期値に大きく依存するため、ここでは問題パラメータの範囲内でランダムな初期値を生成します)。また、パラメータ間のスケール差への感度が高いため、スケール「s」も設定します(ここでは一律で「1」を指定します)。

2. アルゴリズムの動作に必要なオブジェクトを宣言します

3.アルゴリズムの外部パラメータ(設定)を指定します

4.最適化対象となるパラメータの範囲とステップ幅、そしてアルゴリズムの外部パラメータをメソッドに渡し、アルゴリズムを初期化します

5. 最適化を実行します

6. 最適化結果を取得し、今後の利用に備えます

ユーザーが最適化プロセスに介入したり、任意のタイミングで中断する方法は用意されていません。メソッドはすべての処理を独立して実行し、その過程で目的関数を内部的に呼び出します。アルゴリズムは目的関数を任意の回数呼び出す可能性があり(BLEICメソッドにはこの停止条件を指定する方法がありません)、ユーザー側で最大許容回数を自ら制御し、強制的に停止コマンドを渡すことで対処する必要があります。

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Create and initialize arrays for range bounds---------------------
  double rangeMin [], rangeMax [], rangeStep;
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);

  for (int i = 0; i < params; i++)
  {
    rangeMin  [i] = -10;
    rangeMax  [i] =  10;
  }
  rangeStep =  DBL_EPSILON;

  double x [];
  double s [];
  ArrayResize (x, params);
  ArrayResize (s, params);
  ArrayInitialize (s, 1);

  // Generate random initial parameter values in given ranges----
  for (int i = 0; i < params; i++)
  {
    x [i] = rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0);
  }

  // Create objects for optimization------------------------------------------
  C_OptimizedFunction  fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject              obj;
  CNDimensional_Rep    frep;
  CMinBLEICReportShell rep;

  // Set the parameters of the BLEIC optimization algorithm---------------------------
  double diffStep = 0.00001;
  double epsg     = 1e-16;
  double epsf     = 1e-16;
  double epsi     = 0.00001;

  CAlglib::MinBLEICCreateF      (x, diffStep, fFunc.state);
  CAlglib::MinBLEICSetBC        (fFunc.state, rangeMin, rangeMax);
  CAlglib::MinBLEICSetInnerCond (fFunc.state, epsg, epsf, rangeStep);
  CAlglib::MinBLEICSetOuterCond (fFunc.state, rangeStep, epsi);
  CAlglib::MinBLEICOptimize     (fFunc.state, fFunc, frep, 0, obj);
  CAlglib::MinBLEICResults      (fFunc.state, x, rep);

  // Output of optimization results-----------------------------------------------
  Print ("BLEIC, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

メソッドは目的関数をユーザープログラムからではなく自ら呼び出すため、目的関数の呼び出しをALGLIBの親クラス(メソッドごとに異なる)を継承したクラス内にラップする必要があります。ラッパークラスはC_OptimizedFunctionとして宣言し、以下のメソッドをクラス内に定義します。

1. Func:派生クラスでオーバーライドされる仮想メソッドです。
2. Init:クラスパラメータを初期化します。このメソッド内では以下の処理をおこないます。

  • 目的関数の実行回数や、これまでに発見された最良の関数値に関連する変数の初期化をおこないます。
  • c配列とcB配列は座標を保持するために予約されます。

 変数

  • state:BLEICメソッド専用のCMinBLEICStateShell型のオブジェクトで、アルゴリズムの静的メソッド呼び出し時や停止メソッド呼び出し時に使用されます。
  • numberLaunches:現在の実行回数。目的関数が制御不能に長時間実行されるのを防ぐために必要です。
  • maxNumbLaunchesAllowed:許容される最大の実行回数。
  • fB:発見された目的関数の最良値。
  • c []:現在の座標を保持する配列。
  • cB []:最良探索座標を保存するための配列。
//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_Func
class C_OptimizedFunction : public CNDimensional_Func
{
  public:
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized--------
  virtual void Func (CRowDouble &x, double &func, CObject &obj);

  // Initialization of optimization parameters---------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinBLEICStateShell state;          // State 
  int                 numberLaunches; // Launch counter 

  double fB;                          // Best found value of the objective function (maximum)
  double cB [];                       // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                       // Array for storing current coordinates
  int    maxNumbLaunchesAllowed;      // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

C_OptimizedFunctionFuncメソッドは、ユーザーが定義した目的関数へのアクセスを実行するために用意されています。このメソッドは次の引数を取ります。xベクトル:最適化メソッドが提案する、問題のパラメータの一つの候補解、func:計算された目的関数の値を返すための変数、obj:本メソッドの目的においては明確な用途は不明ですが、おそらく追加情報の受け渡しを可能にするために予約されていると考えられます。メソッドの主な処理手順は以下の通りです。

1. numberLaunchesカウンタがインクリメントされます。このカウンタはFuncメソッドの呼び出し回数を記録するために使用されます。
2. 実行回数がmaxNumbLaunchesAllowedを超えた場合、funcDBL_MAXを設定します。ALGLIBのメソッドは関数の最小化を目的としているため、この値は最悪の解を意味します。その後、MinBLEICRequestTermination関数を呼び出してBLEICメソッドに最適化の停止を通知します。
3. 次にループ処理で、xベクトルの値をc配列にコピーします。これは、ユーザー定義の目的関数にこれらの値を渡して使用するために必要です。
4. ObjectiveFunction関数が呼び出され、現在のc配列の値に対する目的関数の値を計算します。結果はffValに保存され、funcにはffVal(負の値)が設定されます。これは、最適化対象が最大化すべき反転した放物面であり、BLEICメソッドは最小化をおこなうため、値を反転する必要があるためです。
5. ffValの現在の値が、これまでに記録された最良値fBを上回る場合、fBを更新し、cB配列に現在のcの状態をコピーします。これにより、目的関数の最良値とそれに対応するパラメータを記録・参照できるようになります。

Func関数は、カスタム目的関数の呼び出しを実装し、呼び出し回数の管理や最良結果の更新、設定された制限回数を超えた場合の停止処理を実行するものです。

//——————————————————————————————————————————————————————————————————————————————
// Implementation of the function to be optimized
void C_OptimizedFunction::Func (CRowDouble &x, double &func, CObject &obj)
{
  // Increase the function launch counter and limitation control----------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    func = DBL_MAX;
    CAlglib::MinBLEICRequestTermination (state);
    return;
  }
  
  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  func = -ffVal;

  // Update the best solution found--------------------------------------
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

BLEICアルゴリズムを用いて放物面関数を最適化するテストスクリプトを実行した結果、以下の出力が得られます。

BLEIC, best result:0.6861786206145579, number of function launches:84022

MinBLEICRequestTerminationメソッドを使って最適化の停止を要求したにもかかわらず、アルゴリズムは処理を継続し、目的関数にさらに74,022回アクセスしようとし、許容回数である10,000回を大きく超えました。

次に、BLEICを制限せず、その裁量に任せて実行してみましょう。結果は次のとおりです。

BLEIC, best result:1.0, number of function launches:72017

ご覧のとおり、BLEICは放物面関数に対して完全に収束することができますが、この場合、目的関数の実行に必要な回数を事前に見積もることはできません。今後、より本格的なテストと結果の分析をおこなっていきます。

アルゴリズムにおいては、微分ステップが重要です。たとえば、0.00001の代わりに非常に小さいステップ(例:1e-16)を使用した場合、アルゴリズムは途中で早期停止し、事実上スタックしてしまいます。その際の出力結果は次のとおりです:

BLEIC, best result:0.6615878186651468, number of function launches:4002


L-BFGS (Limited-memory Broyden–Fletcher–Goldfarb–Shanno)

L-BFGS (Limited-memory Broyden–Fletcher–Goldfarb–Shanno)アルゴリズムは、変数の数が1,000を超える大規模問題を解くために特化した効率的な最適化手法です。これは準ニュートン法の一種であり、関数の曲率に関する情報を保存するために限定されたメモリを使用することで、完全なヘッセ行列を明示的に保存・計算する必要を回避しています。

このアルゴリズムの原理は、直近のM組の値と勾配を用いて、最適化対象の関数の2次モデルを構築・更新することにあります。通常、Mは3から10程度の適度な数値であり、計算コストをO(N·M)に大幅に抑えられます。各反復では、現在の点における勾配を計算し、保存されたベクトルを使って探索方向を決定し、線形探索によってステップ長を決定します。準ニュートン法のステップが関数値や勾配の十分な減少をもたらさなかった場合は、方向の再調整がおこなわれます。

L-BFGSの主な特徴は、近似ヘッセ行列が正定値であることにより、関数の曲率に関わらず準ニュートン法の方向が常に下降方向になることが保証されている点です。

以下は、L-BFGSの基本的な動作説明です。

1. 主な考え方:関数の最小値を徐々に「降りて」見つけようとしながら、関数の簡略化(2次)モデルを構築し、それを絶えず改善していきます。

2. 具体的にどうやるか?最後のMステップ(通常は3~10ステップ)を記憶し、各ステップで以下の2つを保存します。

  • どこにいたか(位置)
  • どこへ進めるか(勾配)

この情報に基づき、関数の曲率(ヘッセ行列)を近似し、その近似を使って次のステップを決定します。

3. 特長:常に「下りる」(関数値の減少方向へ進む)、メモリを節約(最後のMステップのみ保存)、かつ高速(計算コストは問題サイズ×Mに比例)。

4. 実例:霧の中、山を歩いて下っているところを想像してください。

  • 現在地点での下り方向しか分からない
  • 最近の数歩と斜面の変化を覚えている
  • その情報で次に進むべき最適な方向を予測する

5. 制約

  • 非常に複雑な「地形」では動作が遅くなる可能性がある
  • 性能向上のために追加設定が必要になる場合がある

BLEIC法とは異なり、L-BFGSは目的関数の呼び出し回数の制限を直接設定できますが、最適化パラメータの境界条件を指定することはできません。以下の例ではMの値を「1」に設定していますが、他の値を使ってもアルゴリズムの性能や挙動に目立った変化はありませんでした。

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Create and initialize arrays for search range bounds--------------
  double rangeMin [], rangeMax [], rangeStep;
  ArrayResize (rangeMin,  params);
  ArrayResize (rangeMax,  params);

  for (int i = 0; i < params; i++)
  {
    rangeMin  [i] = -10;
    rangeMax  [i] =  10;
  }
  rangeStep =  DBL_EPSILON;

  double x [];
  double s [];
  ArrayResize (x, params);
  ArrayResize (s, params);
  ArrayInitialize (s, 1);

  // Generate random initial parameter values in given ranges-----
  for (int i = 0; i < params; i++)
  {
    x [i] = rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0);
  }

  // Create objects for optimization-------------------------------------------
  C_OptimizedFunction  fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject              obj;
  CNDimensional_Rep    frep;
  CMinLBFGSReportShell rep;

  // Set the parameters of the L-BFGS optimization algorithm---------------------------
  double diffStep = 0.00001;
  double epsg     = 1e-16;
  double epsf     = 1e-16;

  CAlglib::MinLBFGSCreateF  (1, x, diffStep, fFunc.state);
  CAlglib::MinLBFGSSetCond  (fFunc.state, epsg, epsf, rangeStep, numbTestFuncRuns);
  CAlglib::MinLBFGSSetScale (fFunc.state, s);
  CAlglib::MinLBFGSOptimize (fFunc.state, fFunc, frep, 0, obj);
  CAlglib::MinLBFGSResults  (fFunc.state, x, rep);

  //----------------------------------------------------------------------------
  Print ("L-BFGS, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

L-BFGSでは、state変数はCMinLBFGSStateShell型です。

//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_Func
class C_OptimizedFunction : public CNDimensional_Func
{
  public: //--------------------------------------------------------------------
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized---------
  virtual void Func (CRowDouble &x, double &func, CObject &obj);

  // Initialization of optimization parameters----------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinLBFGSStateShell state;          // State 
  int                 numberLaunches; // Launch counter

  double fB;                          // Best found value of the objective function (maximum)
  double cB [];                       // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                       // Array for storing current coordinates
  int    maxNumbLaunchesAllowed;      // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

MinLBFGSRequestTermination関数を使用して最適化プロセスの停止を要求します。

//——————————————————————————————————————————————————————————————————————————————
// Implementation of the function to be optimized
void C_OptimizedFunction::Func (CRowDouble &x, double &func, CObject &obj)
{
  //Increase the function launch counter and limitation control-------------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    func = DBL_MAX;
    CAlglib::MinLBFGSRequestTermination (state);
    return;
  }

  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  func = -ffVal;

  // Update the best solution found--------------------------------------
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

L-BFGSアルゴリズムを用いて放物面関数を最適化するテストスクリプトを実行したところ、次の結果が出力されました。

L-BFGS, best result:0.6743844728276278, number of function launches:24006

目的関数の最大実行回数に関するパラメータは機能していない可能性が高く、実際にはその2倍以上の回数が実行されました。

次に、実行回数に制限を設けずに最適化を実行させてみます。

L-BFGS, best result:1.0, number of function launches:52013

BLEICと同様に、L-BFGSも放物面関数に完全に収束する能力を持っていますが、実行回数は制御不能になります。次のアルゴリズムのレビューでは、この点を考慮しないと本当に深刻な問題になり得ることを示します。

L-BFGSでも微分ステップは重要です。たとえば、ステップサイズを1e-16のように非常に小さく設定すると、アルゴリズムは途中で行き詰まり、次のような結果で早期に停止します。

L-BFGS, best result:0.6746423814003036, number of function launches:4001


NS(箱制約・線形制約・非線形制約付き非滑らか非凸最適化)

NS(箱制約・線形制約・非線形制約付き非滑らか非凸最適化)は、目的関数が滑らかでも凸でもない問題を解決するために設計された非滑らか・非凸最適化アルゴリズムです。つまり、関数には急激な変化や角、その他の特徴が含まれている可能性があります。このような問題の主な特徴は、目的関数に不連続性や急激な変化が含まれており、勾配に基づく手法では解析が困難になる点です。

このアルゴリズムの原理には勾配推定が含まれており、現在の解の周囲にある複数のランダムな点で勾配を推定する「勾配サンプリング法」が用いられます。これにより、関数特有の問題を回避することができます。得られた勾配の推定値に基づき、限定された二次計画問題(QP問題)が構築されます。この解により、現在の解を改善するために進むべき方向が決定されます。アルゴリズムは反復的に動作し、各反復ごとに計算された勾配とQP問題の解に基づいて現在の解を更新します。

この最適化の説明を簡単に言い換えると、次のようになります。

1. 非滑らか(NONSMOOTH)最適化

  • 関数に切れ目や「裂け目」がある場合がある
  • 連続微分可能性は求められない
  • 急激な変化やジャンプを含む可能性がある

2. 非凸(NONCONVEX)

  • 関数には複数の局所的な最小値や最大値が存在する可能性がある
  • 関数の「地形」には「山」や「谷」がある可能性がある

3. 制約の種類(CONSTRAINTS)箱制約・線形制約・非線形制約付き非滑らか非凸最適化(上記のようなもの)

この手法の特徴は、AGS(Adaptive Gradient Sampling )ソルバー用のパラメータを指定・設定する必要があることです。AGSソルバーは、境界・線形・非線形の制約がある非滑らか問題の解決を目的としています。AGSソルバーには、制約処理の専用機構、変数のスケーリング機能(スケーリングが難しい問題に対応)、および数値微分の組み込みサポートなど、いくつかの重要な機能が含まれています。

AGSソルバーの最も重要な制限は、高次元問題には適していないという点です。AGSの1ステップでは、ランダムに選ばれた点で約2·N回の勾配評価が必要になります(これに対してL-BFGSでは1ステップあたりO(1)の評価で済みます)。通常、収束にはO(N)回の反復が必要となるため、最適化セッション全体ではO(N²)の勾配推定が必要になります。

前述の手法と異なり、NSでは問題の最適化対象の初期値と境界条件の変数にdouble型ではなくCRowDouble型の使用が求められます。さらに、AGSソルバー用の各種パラメータも指定する必要があります。

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  // Initialization of optimization parameters---------------------------------------
  int numbTestFuncRuns = 10000;
  int params           = 1000;

  // Additionally, you need to specify --------------
  CRowDouble rangeMin, rangeMax;
  rangeMin.Resize (params);
  rangeMax.Resize (params);
  double rangeStep;

  for (int i = 0; i < params; i++)
  {
    rangeMin.Set (i, -10);
    rangeMax.Set (i,  10);
  }
  rangeStep = DBL_EPSILON;

  CRowDouble x, s; 
  x.Resize (params);
  s.Resize (params);
  s.Fill (1);

  // Generate random initial parameter values in given ranges----
  for (int i = 0; i < params; i++)
  {
    x.Set (i, rangeMin [i] + ((rangeMax [i] - rangeMin [i]) * rand () / 32767.0));
  }

  // Create objects for optimization------------------------------------------
  C_OptimizedFunction fFunc; fFunc.Init (params, numbTestFuncRuns);
  CObject             obj;
  CNDimensional_Rep   frep;
  CMinNSReport        rep;
  
  // Set the parameters of the NS optimization algorithm------------------------------
  double diffStep = 0.00001;
  double radius   = 0.8;
  double rho      = 50.0;

  CAlglib::MinNSCreateF    (x, diffStep, fFunc.state);
  CAlglib::MinNSSetBC      (fFunc.state, rangeMin, rangeMax);
  CAlglib::MinNSSetScale   (fFunc.state, s);
  CAlglib::MinNSSetCond    (fFunc.state, rangeStep, numbTestFuncRuns);

  CAlglib::MinNSSetAlgoAGS (fFunc.state, radius, rho);

  CAlglib::MinNSOptimize   (fFunc.state, fFunc, frep, obj);
  CAlglib::MinNSResults    (fFunc.state, x, rep);

  // Output of optimization results-----------------------------------------------
  Print ("NS, best result: ", fFunc.fB, ", number of function launches: ", fFunc.numberLaunches);
}
//——————————————————————————————————————————————————————————————————————————————

NSメソッドを使用する場合、ラッパークラスは別の親クラスCNDimensional_FVecを継承して作成する必要があります。また、仮想メソッドもFVecに変更する必要があります。このメソッドの特筆すべき点は、目的関数の値としてDBL_MAXを返すことができないという点です。前述の最適化手法とは異なり、このような値を返すとエラーで終了してしまいます。そのため、最適化中に最悪の解を追跡するための追加フィールド(fW)をクラスに設ける必要があります。

//——————————————————————————————————————————————————————————————————————————————
// Class for function optimization, inherits from CNDimensional_FVec
class C_OptimizedFunction : public CNDimensional_FVec
{
  public: //--------------------------------------------------------------------
  C_OptimizedFunction (void) { }
  ~C_OptimizedFunction (void) { }

  // A virtual function to contain the function being optimized--------
  virtual void FVec (CRowDouble &x, CRowDouble &fi, CObject &obj);

  // Initialization of optimization parameters---------------------------------------
  void Init (int coords,
             int maxNumberLaunchesAllowed)
  {
    numberLaunches         = 0;
    maxNumbLaunchesAllowed = maxNumberLaunchesAllowed;
    fB = -DBL_MAX;
    fW =  DBL_MAX;

    ArrayResize (c,  coords);
    ArrayResize (cB, coords);
  }

  //----------------------------------------------------------------------------
  CMinNSState state;             // State 
  int         numberLaunches;    // Launch counter

  double fB;                     // Best found value of the objective function (maximum)
  double fW;                     // Worst found value of the objective function (maximum)
  double cB [];                  // Coordinates of the point with the best function value

  private: //-------------------------------------------------------------------
  double c  [];                  // Array for storing current coordinates
  int    maxNumbLaunchesAllowed; // Maximum number of function calls allowed
};
//——————————————————————————————————————————————————————————————————————————————

誤った操作はで表示されます。その代わりに、最適化中に見つかった最悪の解(符号を反転させた値。なぜならこの手法は最小化を行うため)を返すようにします。そしてもちろん、メソッドの停止呼び出しもMinNSRequestTerminationに変更する必要があります。

//——————————————————————————————————————————————————————————————————————————————
void C_OptimizedFunction::FVec (CRowDouble &x, CRowDouble &fi, CObject &obj)
{
  // Increase the function launch counter and limitation control----------------
  numberLaunches++;
  if (numberLaunches >= maxNumbLaunchesAllowed)
  {
    //fi.Set (0, DBL_MAX);  //Cannot return DBL_MAX value
    fi.Set (0, -fW);
    CAlglib::MinNSRequestTermination (state);
    return;
  }

  // Copy input coordinates to internal array-------------------------
  for (int i = 0; i < x.Size (); i++) c [i] = x [i];

  // Calculate objective function value----------------------------------------
  double ffVal = ObjectiveFunction (c);
  fi.Set (0, -ffVal);

  // Update the best and worst solutions found-----------------------------
  if (ffVal < fW) fW = ffVal;
  if (ffVal > fB)
  {
    fB = ffVal;
    ArrayCopy (cB, c);
  }
}
//——————————————————————————————————————————————————————————————————————————————

NSアルゴリズムを用いて放物面関数の最適化をおこなうテストスクリプトを実行したところ、次の結果が出力されました。

NS, best result:0.6605212238333136, number of function launches:1006503

NSでも目的関数の最大実行回数のパラメータは機能していないようです。実際には100万回以上の実行がおこなわれました。

次に、実行回数の制限を設けずにNSを動かしてみます。残念ながら、スクリプトの実行完了を待てず、チャートを閉じて強制的に停止させました。

No result

NSにおいても微分のステップは重要です。非常に小さいステップ(例えば1e-16)を使うと、アルゴリズムは早期に停止し、割り当てられた目的関数の実行回数を使い切ることなく、以下のように停止してしまいます。

NS, best result:0.6784901840822722, number of function launches:96378


結論

本記事では、ALGLIBライブラリに含まれる最適化手法について学びました。これらの手法の主要な特徴を理解することで、単に最適化を行うだけでなく、目的関数の呼び出し回数が制御不能になるといった望ましくない状況を回避する助けにもなります。

次回のALGLIB最適化手法に関する最終記事では、さらに3つの手法を詳しく取り上げます。今回紹介したすべての手法を実際にテストし、実務での強みと弱みを明らかにするとともに、研究の総括をおこないます。また、複雑なテスト問題における各アルゴリズムの特徴的な動作を視覚的に示すことで、それぞれの手法が異なる最適化課題にどのように対応しているかをより深く理解できるようにします。

記事の本文には、ALGLIBの手法を実際に動かすための完全なスクリプトコードを掲載しています。さらに、記事と共に配布するアーカイブには、今回解説した手法を使ってすぐに取引戦略やその他のタスクの最適化を始められるように必要なすべての資料を収録しています。つまり、本記事の目的である「手法のシンプルでわかりやすい使用例の提示」は達成されています。

最後に、ALGLIBライブラリのメソッド利用に関する知見を提供してくださったEvgeniy Chernish氏に、心より感謝の意を表します。

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

# 名前 種類 詳細
1 Simple test ALGLIB BLEIC.mq5
スクリプト
BLEIC動作テスト用スクリプト
2 Simple test ALGLIB LBFGS.mq5
スクリプト
L-BFGS動作テスト用スクリプト
3 Simple test ALGLIB NS.mq5
スクリプト
NS動作テスト用スクリプト

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
Rorschach
Rorschach | 25 10月 2024 において 18:03
ありがとう。
ALGLIBライブラリの最適化手法(第2回): ALGLIBライブラリの最適化手法(第2回):
この記事では、ALGLIBライブラリにおける残りの最適化手法の検討を続けていきます。特に、複雑な多次元関数でのテストに重点を置きます。これにより、各アルゴリズムの効率性を評価できるだけでなく、さまざまな条件下における強みと弱みを明らかにすることができます。
多通貨エキスパートアドバイザーの開発(第19回):Pythonで実装されたステージの作成 多通貨エキスパートアドバイザーの開発(第19回):Pythonで実装されたステージの作成
これまでは、標準のストラテジーテスター内で最適化タスクを順に自動実行することだけを考えてきました。しかし、もしそれらの実行の合間に、別の手段で得られたデータを処理したいとしたらどうなるでしょうか。ここでは、Pythonで記述されたプログラムによって新たな最適化ステージを作成する機能の追加を試みます。
取引におけるニューラルネットワーク:対照パターンTransformer(最終回) 取引におけるニューラルネットワーク:対照パターンTransformer(最終回)
本連載の前回の記事では、Atom-Motif Contrastive Transformer (AMCT)フレームワークについて取り上げました。これは、対照学習を用いて、基本要素から複雑な構造に至るまでのあらゆるレベルで重要なパターンを発見することを目的とした手法です。この記事では、MQL5を用いたAMCTアプローチの実装を引き続き解説していきます。
リプレイシステムの開発(第71回):正しい時間を知る(IV) リプレイシステムの開発(第71回):正しい時間を知る(IV)
この記事では、前回の記事で紹介したリプレイ/シミュレーションサービスに関連する実装方法について見ていきます。人生の多くのことと同様に、問題は必ず発生するものです。そして今回も例外ではありませんでした。本記事では、引き続き改善をおこなっていきます。ここで提示されるコンテンツは、教育目的のみに使用されることを意図しています。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを利用することは避けてください。