Extremal Optimization (EO)アルゴリズム
内容
はじめに
多くの実世界の問題、特に取引に関する問題は、複数の局所的な極値、不連続性、非微分可能な領域を持つ複雑な離散目的関数のランドスケープによって特徴付けられています。そのため、古典的な勾配ベースの手法を適用することはできません。このような問題を解決するために、多くのメタヒューリスティック最適化アルゴリズムが開発されています。それぞれの手法には、探索空間における探索と活用のバランスを取る上で、それぞれ異なる長所と短所があります。
極値最適化(EO, Extremal Optimization)は、Bak-Sneppenモデルに着想を得たメタヒューリスティック最適化アルゴリズムです。このアルゴリズムは、自己組織化臨界性(self-organized criticality)という概念に基づいて、Stefan BoettcherとAllon Percusによって1999年に開発されました。自己組織化臨界性とは、複雑なシステムが自然に臨界状態へと進化し、その過程でさまざまな規模の雪崩のような変化が発生するという考え方です。その後、EOの集団ベースの変種が開発され、反復的に集団全体を更新することで、連続最適化問題にも適用できるようになりました。
アルゴリズムの実装
Bak-Sneppenモデルは、生態系の進化を説明し、自己組織化臨界性という現象を示すために開発されました。これは、局所的な相互作用がどのようにして全体的な臨界イベントにつながるのかを示す、単純な共進化モデルです。
Bak-Sneppenモデルの原理
生態系と種
- N個の種が、鎖状または格子状に配置されている
- 各種iには、適応度値fi ∈ [0,1]が割り当てられる
- 適応度は、その種が環境にどの程度適応しているかを表す
進化のダイナミクス
各時間ステップで: 1. 適応度が最小の種を見つける:fmin = min{fi} 2. この種とその近傍の適応度を、新しいランダムな値に置き換える 3. このプロセスを繰り返す自己組織化臨界
- システムは、自ら臨界状態へと組織化される
- 閾値はfc ≈ 0.67
- 適応度がfcより低い適応度を持つ種は、雪崩のようなイベントによって絶滅する
- 雪崩の規模は冪乗則に従う
外部から制御されなくても、長期間ほとんど変化が起こらない停滞状態と、突然発生するさまざまな規模の雪崩のような変化が交互に現れます。このように、システム自身が臨界状態へと組織化されていきます。変化の規模は冪乗則に従い、雪崩のサイズについては、P(s) ~ s^(-τ)と表されます。これは、種の存続期間などが、特徴的な尺度を持たない冪乗則に従うことを意味します。また、ある種に生じた変化は、その隣接する種にも影響を与えます。このような局所的な相互作用から全体的なダイナミクスが生まれることが、Bak-Sneppenモデルの重要な特徴です。
BoettcherとPercusは、Bak-Sneppenモデルの原理を応用して最適化アルゴリズムを開発しました。通常の最適化では「最も良い要素をさらに改善する」ことを考えますが、EOでは逆に、最も悪い要素を改善します。最も悪い要素に注目するという考え方は直感に反するように見えますが、局所最適解から抜け出すためには効果的です。Bak-Sneppenモデルでは、「断続平衡」と呼ばれる現象が見られます。つまり、長期間にわたってほとんど変化しない状態が続いた後、突然、大きな変化が発生します。EOでは、この原理を利用します。悪い要素だけを変更していくと、ある要素を変更した結果として、その解が大幅に改善されることがあります。しかし、その一方で、別の要素が相対的に「悪い要素」になる場合があります。
極値最適化は、もともと生態系モデルで研究されていた自己組織化臨界性の原理を最適化問題に応用したものです。この手法の重要な特徴は、複雑なシステムが自然に臨界状態へ自己組織化する傾向を利用することで、探索空間における探索と活用のバランスを自動的に取ることを目指している点です。ここでは、この主張が実際の問題に対してどの程度有効であるのかをテストします。それでは、EOアルゴリズムの擬似コードを見ていきましょう。
初期化:
- popSize個のエージェントから集団を作成する
- パラメータを初期化する
- エージェントとコンポーネントの順位付け用配列を作成する
最初の実行(集団の初期化):
- 集団内の各エージェントについて:
- 各コンポーネント(座標)について:
- 許容範囲内のランダムな値を割り当てる
- 必要に応じて離散化を適用する
- 各コンポーネント(座標)について:
基本最適化ループ
集団内の各エージェントに対して、以下を実行します。
- エージェントランキング
- エージェント全員とその適応度のリストを作成する
- エージェントを適応度順に並べ替える(最大化のため、適応度が低いものから順に並べる)
- ターゲットエージェントを選択する
- 冪乗則分布P(n) ∝ n^(-τ)を使用する
- この分布に従ってランクを選択する
- 選択されたランクを持つエージェントをターゲットとして定義する
- ターゲットエージェントのコンポーネントランキング
- 各コンポーネントについて、その適応度を計算する
- 適応度 = 1 -(既知の最良値からの正規化偏差)
- コンポーネントを適応度順に並べ替える(適応度の低いものから順に)
- 各コンポーネントについて、その適応度を計算する
- コンポーネントの選択と変異:
- 冪乗則分布を使用してコンポーネントのランクを選択する
- 選択したコンポーネントを新しいランダムな値に置き換える
- 境界を確認し、離散化を適用する
結果の更新
- 全集団を適応度順に並べ替える(適応度の高い個体を最初に配置)
- 改善が見つかった場合は、大域最適解を更新する
アルゴリズムをコードで実装してみましょう。ここでは、最も悪い要素を取り除くことで解を見つけるという最適化手法の原理に基づいて、EOアルゴリズムを実装するクラスを作成します。このクラスは、基本クラス C_AO を継承し、さらにアルゴリズム固有のパラメータとメソッドを備えています。コンストラクタではパラメータを初期化し、デストラクタではそれぞれのリソースを解放します。主なパラメータは以下の通りです。
- popSize:集団のサイズであり、集団内のエージェント数を表す
- tau:要素を選択する確率を決定する冪乗則分布のパラメータ
- greedyStart:貪欲初期化に用いるエージェントの割合
- eliteUpdate:各反復において更新に参加するエージェントの割合
- SetParams:配列から現在のパラメータ値を取得し、それらを適用する
- Init:パラメータの範囲とエポック数を考慮して、集団を初期化する
- Moving :集団の状態を更新しながら、アルゴリズムを1ステップ実行する
- Revision:評価値を更新する、または次の段階に進むための準備をする
- RankedComponent:エージェントの各コンポーネントを、その適応度に基づいて順位付けして保存するための構造体
- RankedAgent:エージェント自身を、全体的な適応度に基づいて順位付けして保存するための構造体
- compRanksおよびagentRanks:ソートされたコンポーネントとエージェントを保存するための構造体配列
- tau、greedyStart、eliteUpdate:アルゴリズムの動作を制御するパラメータ
- ApplyExtremalOptimization:各要素の順位に基づいて要素を選択・更新するEOの基本的なメカニズムです。選択には、冪乗則分布が考慮されます。
- CalculateComponentFitness:エージェントの各コンポーネントの適応度を計算します。
- SelectRankByPowerLaw:冪乗則分布を使用して、コンポーネントまたはエージェントの順位を選択します。これにより、適応度の低い要素に重点を置くことができます。
このクラスは、極値最適化アルゴリズムを実問題へ適用するためのものです。とくに、集団内で適応度の低いコンポーネントを逐次更新しながら高品質な解を探索する問題に向いています。
//———————————————————————————————————————————————————————————————————— //--- Initialization bool C_AO_EO::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ ArrayResize (compRanks, coords); ArrayResize (agentRanks, popSize); return true; } //————————————————————————————————————————————————————————————————————
EOクラスの初期化メソッドは、その後の処理を実行するための初期条件を準備する役割を担います。このメソッドでは、変数の最小値と最大値の範囲、および変数を変更する際のステップ幅、さらに必要に応じてエポック数(反復回数)をパラメータとして受け取ります。
このメソッドでは、まず標準の初期化処理を呼び出します。この処理によって、基本的なパラメータが設定され、共通して使用されるデータ構造が準備されます。この処理に失敗した場合は、初期化を中止します。
初期化が正常に完了した場合、順位付けされたコンポーネントとエージェントを格納するために使用される配列のサイズを変更します。コンポーネント配列のサイズは、問題の座標数または変数の数に応じて設定されます。一方、エージェント配列のサイズは、指定された集団サイズに応じて設定されます。これらの処理が完了すると、メソッドは初期化が正常に成功したことを示す結果を返します。
//———————————————————————————————————————————————————————————————————— //--- Main loop of the algorithm void C_AO_EO::Moving () { // Initial population setup if (!revision) { int greedyCount = (int)(popSize * greedyStart); for (int i = 0; i < popSize; i++) { // Random initialization for the rest for (int c = 0; c < coords; c++) { a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; return; } // Apply Extremal Optimization --------------------------------- ApplyExtremalOptimization (); } //————————————————————————————————————————————————————————————————————
ApplyExtremalOptimizationメソッドは、エージェント集団を更新するためのEOアルゴリズムのメインループを実装します。その目的は、EOの原理に基づいてエージェントのコンポーネントを変更することで、解を改善することです。各反復で更新するエージェントの数は、eliteUpdateパラメータ(更新するエージェントの割合)と集団サイズを考慮して決定されます。まず、エージェントは適応度に基づいてソートされます。このアルゴリズムは最大化問題を想定しているため、エージェントは最も悪いものから最も良いものへと順位付けされます。その後、冪乗則確率分布を使用して、変更対象となるエージェントが選択されます。
選択されたエージェントについて、そのコンポーネントが順位付けされます。各コンポーネントには、そのコンポーネントがエージェント全体の適応度にどの程度貢献しているかを評価するための適応度が割り当てられます。コンポーネントは、その「適応度」に基づいて最も悪いものから最も良いものへと順位付けされます。そして、冪乗則確率分布を使用して、変更するコンポーネントを1つ選択します。これにより、順位の低い、つまり適応度の悪いコンポーネントが選択される可能性が高くなります。
選択されたエージェントのコンポーネントは、rangeMinとrangeMaxパラメータによって指定された許容範囲内のランダムな値に置き換えられます。その後、生成された値が指定された境界とステップ幅を満たしているかどうかを確認し、値の妥当性を検証します。この処理を、更新対象となる各エージェントについて繰り返します。これにより、適応度の低いコンポーネントを選択し、それらをランダムに置き換えることで解の適応度を改善するという、EOアルゴリズムの中核となるロジックが実装されます。
//———————————————————————————————————————————————————————————————————— //--- Apply Extremal Optimization void C_AO_EO::ApplyExtremalOptimization () { // Number of agents to update in this iteration int numUpdates = MathMax (1, (int)(popSize * eliteUpdate)); // Update selected agents based on EO principle //for (int update = 0; update < numUpdates; update++) for (int update = 0; update < popSize; update++) { // Step 1: Select an agent to modify // Use ranking by overall fitness int targetAgent; // Rank agents by fitness (from worst to best for maximization) for (int i = 0; i < popSize; i++) { agentRanks [i].idx = i; agentRanks [i].fitness = a [i].f; } // Sort (worst first to maximize) for (int i = 0; i < popSize - 1; i++) { for (int j = i + 1; j < popSize; j++) { if (agentRanks [i].fitness > agentRanks [j].fitness) { RankedAgent temp = agentRanks [i]; agentRanks [i] = agentRanks [j]; agentRanks [j] = temp; } } } // Select an agent according to the power-law distribution int rank = SelectRankByPowerLaw (popSize); targetAgent = agentRanks [rank].idx; // Step 2: Rank the components of the selected agent for (int c = 0; c < coords; c++) { compRanks [c].agentIdx = targetAgent; compRanks [c].componentIdx = c; compRanks [c].fitness = CalculateComponentFitness (targetAgent, c); } // Sort components (worst first) for (int i = 0; i < coords - 1; i++) { for (int j = i + 1; j < coords; j++) { if (compRanks [i].fitness > compRanks [j].fitness) { RankedComponent temp = compRanks [i]; compRanks [i] = compRanks [j]; compRanks [j] = temp; } } } // Step 3: Select a component to change according to P(n) ∝ n^(-τ) int compRank = SelectRankByPowerLaw (coords); int compIdx = compRanks [compRank].componentIdx; // Step 4: Replace the selected component with a new random value // This is the key principle of EO - unconditional replacement with random a [targetAgent].c [compIdx] = u.RNDfromCI (rangeMin [compIdx], rangeMax [compIdx]); // Check boundaries a [targetAgent].c [compIdx] = u.SeInDiSp (a [targetAgent].c [compIdx], rangeMin [compIdx], rangeMax [compIdx], rangeStep [compIdx]); } } //————————————————————————————————————————————————————————————————————
CalculateComponentFitnessメソッドは、エージェントの単一のコンポーネントに対する適応度を計算します。この関数は、各コンポーネントが解全体の適応度にどの程度貢献しているかを評価するため、極値最適化(EO)アルゴリズムにおいて重要な役割を果たします。このアルゴリズムでは、コンポーネントの「適応度」を、そのコンポーネントが解の適応度に与える相対的な貢献度として計算する、シンプルな指標を使用します。
まず、「適応度」を0に初期化します。次に、コンポーネント値の範囲(range)を計算します。この範囲が0より大きい場合、正規化された偏差を計算します。偏差は、エージェントのコンポーネント値と基準値cBの差の絶対値を計算し、それをコンポーネント値の範囲で正規化することで求めます。適応度は、1.0から「偏差」を差し引くことで算出されます。これにより、適応度は偏差に反比例するようになります。つまり、コンポーネントの値が目標値に近いほど偏差は小さくなり、その結果、「適応度」は高くなります。
このように、この関数は特定のコンポーネントに対する適応度スコアを返します。このスコアによって、現在のコンポーネント値が目標値にどの程度適応しているかを判断できます。この適応度は、その後のコンポーネントの順位付けに使用されます。
//———————————————————————————————————————————————————————————————————— //--- Calculate the fitness component double C_AO_EO::CalculateComponentFitness (int agentIdx, int componentIdx) { // For the general optimization problem we use a simple metric // λi = relative contribution of the component to the fitness double fitness = 0.0; double range = rangeMax [componentIdx] - rangeMin [componentIdx]; if (range > 0) { // Normalized deviation double deviation = MathAbs (a [agentIdx].c [componentIdx] - cB [componentIdx]) / range; fitness =1.0 - deviation;// Invert so that bigger = better } return fitness; } //————————————————————————————————————————————————————————————————————
SelectRankByPowerLawメソッドは、冪乗則確率分布に基づいて、リスト内の要素の位置(順位、rank)を選択する処理を実装します。このメソッドはEOアルゴリズムにおいて重要な役割を果たします。なぜなら、このメソッドによって、変更対象となるエージェントやコンポーネントが選択されるためです。このメソッドはmaxRankを入力として受け取り、要素が取り得る最大の順位を指定します。
このメソッドでは、冪乗則P(n) ∝ n^(-τ)に従うランダム値を生成するために、逆変換法の原理を使用します。ここで、nは要素の順位で、τ (tau)は、冪乗則分布の形状を決定するパラメータです。
tauの値に応じて、次の2つのケースが実装されます。
一般的なケース(τ ≠ 1.0)
- 確率を適切に正規化するための正規化係数normを計算します。
- u.RNDprobab()を使用して、範囲[0, 1)の乱数rを生成します。
- 逆変換法の公式を使用して、r、norm、およびtauに基づいてrankを計算します。
- 配列の範囲外アクセスを防ぐため、rankを[0, maxRank - 1]の範囲に制限します。
特殊なケース(τ = 1.0)
- τ=1.0の場合に対応する正規化係数normを計算します。
- 専用の公式を使用してrankを計算します。
- ランクは[0, maxRank - 1]の範囲内に制限されます。
最終的に、このメソッドは整数値のrankを返します。この値は、順位付けされたリスト内で選択された要素の位置を示します。これは確率的に選択されます。つまり、EOの原理に従って、順位の低い要素、すなわち適応度の低い(悪い)要素ほど選択される確率が高くなります。
//———————————————————————————————————————————————————————————————————— //--- Select a rank according to a power-law distribution int C_AO_EO::SelectRankByPowerLaw (int maxRank) { // P(n) ∝ n^(-τ), where n is a rank from 1 to maxRank // Use the inverse transformation method double r = u.RNDprobab (); if (tau != 1.0) { // General case: inverse transform for P(n) ∝ n^(-τ) double norm = (1.0 - MathPow (maxRank + 1.0, 1.0 - tau)) / (1.0 - tau); double x = r * norm; int rank = (int)MathPow ((1.0 - tau) * x + 1.0, 1.0 / (1.0 - tau)) - 1; if (rank >= maxRank) rank = maxRank - 1; if (rank < 0) rank = 0; return rank; } else { // Special case τ = 1: P(n) ∝ 1/n double norm = MathLog (maxRank + 1.0); int rank = (int)(MathExp (r * norm) - 1.0); if (rank >= maxRank) rank = maxRank - 1; if (rank < 0) rank = 0; return rank; } } //————————————————————————————————————————————————————————————————————
Revisionメソッドは、EOアルゴリズムの実行中に見つかった最良解に関する情報を更新するために使用されます。このメソッドの役割は、現在の最良解を特定し、その情報を保持するグローバル変数を更新することです。まず、ソートされたエージェントを一時的に保存するために、aTという一時配列を作成します。次に、エージェント集団aを、組み込みのu.Sorting関数を使用してソートします。このソートは最大化を目的としておこなわれます。つまり、適応度関数fの値が最も高いエージェント、すなわち最も優れたエージェントがa配列の先頭に配置されます。a配列は、エージェントに関する情報を格納する主要なデータ構造であり、各エージェントのcコンポーネントの値や、適応度関数fの値などが含まれています。
ソートが完了すると、最良エージェント、つまりa[0]の適応度関数fの値が、現在の最良値であるfBよりも大きいかどうかを確認します。fBには、これまでに見つかった最良解の適応度関数の値が保存されています。現在の最良エージェントが、それまでに記録されているグローバル最良解よりも優れている場合は、次の処理をおこないます。最良エージェントのコンポーネントa[0].cの値をcB配列にコピーします。cBには、最良解のコンポーネントが保存されていると考えられます。fBを、最良エージェントの適応度関数a[0].fの値に更新します。
このように、Revision関数は最良解の情報を更新し、そのコンポーネントと適応度関数の値を保持します。また、集団をソートすることで、最も優れた解が常に配列の先頭に配置されるようになります。そのため、その後の処理では、a[0]を参照することで現在の集団における最良エージェントを簡単に取得できます。
//———————————————————————————————————————————————————————————————————— //--- Update the best solutions void C_AO_EO::Revision () { // Sort the population for MAXIMIZATION static S_AO_Agent aT []; ArrayResize (aT, popSize); // Use the built-in sorting function u.Sorting (a, aT, popSize); // Update the global best solution if (a [0].f > fB) { ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY); fB = a [0].f; } } //————————————————————————————————————————————————————————————————————
テスト結果
テストをおこなった結果、このアルゴリズムは十分に良好な結果を示しませんでした。そのため、アルゴリズムの収束性能を改善することを目的として、元のすべてのメソッドを見直し、改良版のアルゴリズムを作成することにしました。EO|Extremal Optimization (Boettcher-Percus)|50.0|1.4|0.5|0.3|
=============================
5 Hilly's; Func runs:10000; result:0.5146369337195529
25 Hilly's; Func runs:10000; result:0.29089804555433085
500 Hilly's; Func runs:10000; result:0.25192095557138877
=============================
5 Forest's; Func runs:10000; result:0.367128650332966
25 Forest's; Func runs:10000; result:0.19477408852361866
500 Forest's; Func runs:10000; result:0.15367465708144543
=============================
5 Megacity's; Func runs:10000; result:0.2584615384615384
25 Megacity's; Func runs:10000; result:0.12707692307692314
500 Megacity's; Func runs:10000; result:0.09413846153846227
=============================
All score:2.25271 (25.03%)
以下は、この最適化手法についての私自身の解釈です。ここでは、基本となる最適化クラスを継承する新しいクラスを実装します。
コンストラクタでは、アルゴリズムの初期パラメータを設定します。これには、集団サイズ、改善(改良)対象となる最も悪い解の数、突然変異の確率、選択および突然変異に使用する分布の次数が含まれます。これらのパラメータは、後から変更やカスタマイズをおこなえるように、構造体の配列に保存します。
SetParamsメソッドでは、パラメータ配列に格納されている値に基づいて、クラス内部の変数を更新します。また、アルゴリズムの初期化、移動ステップ(状態の更新)、およびリビジョン(解の再評価や次の反復の準備)を実行するためのメソッドを宣言します。クラスのprivate部分には、現在のエポックと、アルゴリズムの実行における総エポック数を管理するための変数を保持します。さらに、集団内の特定の解のコンポーネントに対して突然変異を適用するメソッドを定義します。これは、今回実装するアルゴリズムの中心的な処理の一つです。
//———————————————————————————————————————————————————————————————————— class C_AO_EOm : public C_AO { public: //---------------------------------------------------------- ~C_AO_EOm () { } C_AO_EOm () { ao_name = "EOm"; ao_desc = "Extremal Optimization M"; ao_link = "https://www.mql5.com/ja/articles/18755"; popSize = 50; // Population size popRaising = 3; // Boost the worst agents mutationRate = 0.1; // Mutation probability powCh = 2.0; // Selection power-law exponent powMut = 8.0; // Mutation power-law exponent ArrayResize (params, 5); params [0].name = "popSize"; params [0].val = popSize; params [1].name = "popRaising"; params [1].val = popRaising; params [2].name = "mutationRate"; params [2].val = mutationRate; params [3].name = "powCh"; params [3].val = powCh; params [4].name = "powMut"; params [4].val = powMut; } void SetParams () { popSize = (int)params [0].val; popRaising = (int)params [1].val; mutationRate = params [2].val; powCh = params [3].val; powMut = params [4].val; } bool Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0); void Moving (); void Revision (); //------------------------------------------------------------------ int popRaising; // Boost the worst agents double mutationRate; // Mutation probability double powCh; // Selection power-law exponent double powMut; // Mutation power-law exponent private: //--------------------------------------------------------- int currentEpoch; // current epoch int totalEpochs; // total number of epochs void MutateComponent (int agentIdx, int componentIdx); }; //————————————————————————————————————————————————————————————————————
Initメソッドは、アルゴリズムの初期化を担当します。この関数は、最適化パラメータの最小値、最大値、およびステップ幅を格納した配列と、アルゴリズムの実行における総エポック数(サイクル数)を入力として受け取ります。
メソッド内部では、まずC_AO基底クラスのStandardInit関数を呼び出します。この関数では、最適化パラメータの範囲やステップ幅に関する基本設定、メモリの確保、その他の一般的な初期化処理がおこなわれます。
StandardInitが失敗した場合、Init関数も処理を中止し、falseを返します。StandardInitが正常に完了した場合、currentEpoch(現在のエポック)を0に初期化し、totalEpochs(総エポック数)を、パラメータepochsPから受け取った値に設定します。
最後に、初期化が正常に完了したことを示すため、メソッドはtrueを返します。このように、Initメソッドは、初期パラメータとエポックカウンタを設定することで、アルゴリズムを実行できる状態に準備します。
//———————————————————————————————————————————————————————————————————— //--- Initialization bool C_AO_EOm::Init (const double &rangeMinP [], const double &rangeMaxP [], const double &rangeStepP [], const int epochsP = 0) { if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false; //------------------------------------------------------------------ currentEpoch = 0; totalEpochs = epochsP; return true; } //————————————————————————————————————————————————————————————————————
Movingメソッドは、極値最適化アルゴリズムのメインサイクルを表します。このメソッドは、最適解を探索する過程で解の集団を進化させる役割を担います。 エポックの増加:currentEpochカウンタを1増加させます。 集団の初期化(最初のエポックのみ):revisionフラグ(解の再評価が必要かどうかを示すフラグ)がfalseの場合、つまりアルゴリズムの最初のサイクルでは、集団を初期化します。
集団内の各「エージェント」(解)について、各「座標」(解のパラメータ)ごとに、現在の座標に対して指定された範囲(rangeMin、rangeMax)内でランダムな値を生成します。SeInDiSpによる離散化を使用して、パラメータの値が指定されたステップ幅に対応するようにします。初期化が完了したら、revisionをtrueに設定し、このコードブロックが1回だけ実行されるようにします。その後、メソッドを終了します。
その後のエポックにおける極値最適化の適用:次のエポック以降では、極値最適化を適用します。まず、新しい(突然変異した)解を一時的に保存するために、aT配列を作成します。集団内の各エージェントについて、aT[i]構造体を初期化します。各解の座標について乱数を生成し、それをpowChの冪乗にします。この値を使用して「親(parent)」を選択します。
生成された乱数をスケーリングして、ind集団内の親のインデックスを選択します。次に、突然変異の種類を決定します。乱数がmutationRateより小さい場合(つまり、mutationRateの確率で突然変異が有効になる場合)は、PowerDistributionの冪乗則分布に従って、親の座標値に突然変異を適用します。それ以外の場合は、ランダムノイズを加えながら、最良解に向かう「指向性移動」をおこないます。新しい値は、親の座標値、ランダム値、およびcB[c](これまでに見つかった最良の座標)と親の座標値との差を加算して計算します。その後、SeInDiSpによる離散化を適用します。突然変異処理が完了すると、一時配列aTのすべての座標がメインの集団配列aにコピーされます。
このように、このメソッドは、各ステップ(エポック)で選択、ランダムな突然変異、および指向性探索のメカニズムを使用して解を変化させる進化サイクルを実装します。これにより、解の値を改善し、最終的に最適解を見つけることを目指します。
//———————————————————————————————————————————————————————————————————— //--- Main loop of the algorithm void C_AO_EOm::Moving () { currentEpoch++; // Initial population setup if (!revision) { for (int i = 0; i < popSize; i++) { for (int c = 0; c < coords; c++) { a [i].c [c] = u.RNDfromCI (rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp (a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } revision = true; return; } //Apply Extremal Optimization--------------------------------------- static S_AO_Agent aT []; ArrayResize (aT, popSize); for (int i = 0; i < popSize; i++) { aT [i].Init (coords); for (int c = 0; c < coords; c++) { double rnd = u.RNDprobab (); rnd = pow (rnd, powCh); int ind = (int)u.Scale (rnd, 0.0, 1.0, 0, popSize - 1); // Select the mutation type double mutType = u.RNDprobab (); if (mutType < mutationRate) { aT [i].c [c] = u.PowerDistribution (a [ind].c [c], rangeMin [c], rangeMax [c], powMut); } else { // Directed movement towards the better with noise aT [i].c [c] = a [ind].c [c] + u.RNDprobab () * (cB [c] - a [ind].c [c]); } // Check boundaries aT [i].c [c] = u.SeInDiSp (aT [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); } } for (int i = 0; i < popSize; i++) ArrayCopy (a [i].c, aT [i].c); } //————————————————————————————————————————————————————————————————————
このメソッドは、アルゴリズムの実行中に最良解に関する情報を更新する役割を担います。
集団のソート:集団のコピーを保存するために、aTという一時配列を作成します。集団aを目的関数の値に基づいてソートします。ソートにはu.Sortingメソッドを使用します。
大域最良解の更新:集団の最初の要素、つまり最良解の目的関数fの値が、現在の大域最良値fBよりも優れているかどうかを確認します。現在の解の方が優れている場合は、a[0].cの座標をcBにコピーし、より良い解のfの値でfBを更新します。
最悪解の更新:fWを、集団の最後の要素が持つ目的関数の値に更新します。
最悪解の改善:popRaising回実行されるループ内で、集団内の最悪解を改善します。popRaising個の最も悪いエージェント(ソートされたa配列の末尾に位置するエージェント)について、目的関数fの値を、現在の最悪値fWと現在の最良値fBの間にあるランダムな値として再計算します。これは、集団に多様性を持たせたり、最悪解をより良い解の方向へ移動させたりするためにおこなわれます。
集団の再ソート:u.Sortingメソッドを使用して、集団aを再度ソートします。これは、fの値が変更された後に、集団を再び正しい順序に戻すために必要です。
//———————————————————————————————————————————————————————————————————— //--- Update the best solutions void C_AO_EOm::Revision () { // Sort the population -------------------------------------------- static S_AO_Agent aT []; ArrayResize (aT, popSize); u.Sorting (a, aT, popSize); // Update the global best solution if (a [0].f > fB) { ArrayCopy (cB, a [0].c, 0, 0, WHOLE_ARRAY); fB = a [0].f; } fW = a [popSize - 1].f; //------------------------------------------------------------------ for (int i = 0; i < popRaising; i++) { a [popSize - 1 - i].f = u.RNDfromCI (fW, fB); } u.Sorting (a, aT, popSize); } //————————————————————————————————————————————————————————————————————
現在のアルゴリズムは、ある意味ではEOの考え方を受け継いでいるものの、変更が加えられています。特に、冪乗則分布を使用して、必ずしも悪いエージェントだけではなく、ドナー個体となるエージェントを選択するようになっています。
a [popSize - 1 - i].f = u.RNDfromCI (fW, fB);さらに、EOの説明には見られない機能を追加しました。このアルゴリズムでは、最悪のエージェントに対して、集団内の最悪値から最良値までの範囲からランダムに選んだ適応度を割り当てることで、実質的に最悪のエージェントを「復活」させます。 PowerDistributionによる突然変異は、「冪乗則分布に基づく突然変異」を独自に解釈したものです。 より良い方向への方向性のある動き(mutType >= mutationRateの場合)は、収束を加速させるために追加した仕組みです。
これで、改良版アルゴリズムの結果を確認できます。これらの結果を評価表に追加できます。
EOm|Extremal Optimization Mod|50.0|3.0|0.1|2.0|8.0|
=============================
5 Hilly's; Func runs:10000; result:0.7616651321732368
25 Hilly's; Func runs:10000; result:0.7724295992586084
500 Hilly's; Func runs:10000; result:0.3174703398632668
=============================
5 Forest's; Func runs:10000; result:0.9999936711143977
25 Forest's; Func runs:10000; result:0.7675163269928252
500 Forest's; Func runs:10000; result:0.23527203643380376
=============================
5 Megacity's; Func runs:10000; result:0.7476923076923077
25 Megacity's; Func runs:10000; result:0.5396923076923076
500 Megacity's; Func runs:10000; result:0.14249230769230903
=============================
All score:5.28422 (58.71%)
可視化の結果を見ると、低次元関数において結果のばらつきが大きいことが確認できます(緑の線)。

Hillyテスト関数のEOm

Forestテスト関数のEOm

Megacityテスト関数のEOm
テストの結果、EOmアルゴリズムはランキング表で12位となりました。
| # | AO | 説明 | Hilly | Hilly ファイナル | Forest | Forest ファイナル | Megacity(離散) | Megacity ファイナル | ファイナル 結果 | % MAX | ||||||
| 10p(5F) | 50p(25F) | 1000p(500F) | 10p(5F) | 50p(25F) | 1000p(500F) | 10p(5F) | 50p(25F) | 1000p(500F) | ||||||||
| 1 | ANS | across neighbourhood search | 0.94948 | 0.84776 | 0.43857 | 2.23581 | 1.00000 | 0.92334 | 0.39988 | 2.32323 | 0.70923 | 0.63477 | 0.23091 | 1.57491 | 6.134 | 68.15 |
| 2 | CLA | コードロックアルゴリズム(joo) | 0.95345 | 0.87107 | 0.37590 | 2.20042 | 0.98942 | 0.91709 | 0.31642 | 2.22294 | 0.79692 | 0.69385 | 0.19303 | 1.68380 | 6.107 | 67.86 |
| 3 | AMOm | 動物移動最適化m | 0.90358 | 0.84317 | 0.46284 | 2.20959 | 0.99001 | 0.92436 | 0.46598 | 2.38034 | 0.56769 | 0.59132 | 0.23773 | 1.39675 | 5.987 | 66.52 |
| 4 | (P+O)ES | (P+O)進化戦略 | 0.92256 | 0.88101 | 0.40021 | 2.20379 | 0.97750 | 0.87490 | 0.31945 | 2.17185 | 0.67385 | 0.62985 | 0.18634 | 1.49003 | 5.866 | 65.17 |
| 5 | CTA | 彗星の尾アルゴリズム(joo) | 0.95346 | 0.86319 | 0.27770 | 2.09435 | 0.99794 | 0.85740 | 0.33949 | 2.19484 | 0.88769 | 0.56431 | 0.10512 | 1.55712 | 5.846 | 64.96 |
| 6 | TETA | 時間進化移動アルゴリズム(joo) | 0.91362 | 0.82349 | 0.31990 | 2.05701 | 0.97096 | 0.89532 | 0.29324 | 2.15952 | 0.73462 | 0.68569 | 0.16021 | 1.58052 | 5.797 | 64.41 |
| 7 | SDSm | 確率的拡散探索M | 0.93066 | 0.85445 | 0.39476 | 2.17988 | 0.99983 | 0.89244 | 0.19619 | 2.08846 | 0.72333 | 0.61100 | 0.10670 | 1.44103 | 5.709 | 63.44 |
| 8 | BOAm | ビリヤード最適化アルゴリズムM | 0.95757 | 0.82599 | 0.25235 | 2.03590 | 1.00000 | 0.90036 | 0.30502 | 2.20538 | 0.73538 | 0.52523 | 0.09563 | 1.35625 | 5.598 | 62.19 |
| 9 | AAm | アーチェリーアルゴリズムM | 0.91744 | 0.70876 | 0.42160 | 2.04780 | 0.92527 | 0.75802 | 0.35328 | 2.03657 | 0.67385 | 0.55200 | 0.23738 | 1.46323 | 5.548 | 61.64 |
| 10 | ESG | 社会集団の進化(joo) | 0.99906 | 0.79654 | 0.35056 | 2.14616 | 1.00000 | 0.82863 | 0.13102 | 1.95965 | 0.82333 | 0.55300 | 0.04725 | 1.42358 | 5.529 | 61.44 |
| 11 | SIA | 等方的焼きなまし(joo) | 0.95784 | 0.84264 | 0.41465 | 2.21513 | 0.98239 | 0.79586 | 0.20507 | 1.98332 | 0.68667 | 0.49300 | 0.09053 | 1.27020 | 5.469 | 60.76 |
| 12 | EOm | 極値最適化M | 0.76166 | 0.77242 | 0.31747 | 1.85155 | 0.99999 | 0.76751 | 0.23527 | 2.00277 | 0.74769 | 0.53969 | 0.14249 | 1.42987 | 5.284 | 58.71 |
| 13 | BBO | 生物地理学に基づく最適化 | 0.94912 | 0.69456 | 0.35031 | 1.99399 | 0.93820 | 0.67365 | 0.25682 | 1.86867 | 0.74615 | 0.48277 | 0.17369 | 1.40261 | 5.265 | 58.50 |
| 14 | ACS | 人工協調探索 | 0.75547 | 0.74744 | 0.30407 | 1.80698 | 1.00000 | 0.88861 | 0.22413 | 2.11274 | 0.69077 | 0.48185 | 0.13322 | 1.30583 | 5.226 | 58.06 |
| 15 | DA | 弁証法的アルゴリズム | 0.86183 | 0.70033 | 0.33724 | 1.89940 | 0.98163 | 0.72772 | 0.28718 | 1.99653 | 0.70308 | 0.45292 | 0.16367 | 1.31967 | 5.216 | 57.95 |
| 16 | BHAm | ブラックホールアルゴリズムM | 0.75236 | 0.76675 | 0.34583 | 1.86493 | 0.93593 | 0.80152 | 0.27177 | 2.00923 | 0.65077 | 0.51646 | 0.15472 | 1.32195 | 5.196 | 57.73 |
| 17 | ASO | 無政府社会最適化 | 0.84872 | 0.74646 | 0.31465 | 1.90983 | 0.96148 | 0.79150 | 0.23803 | 1.99101 | 0.57077 | 0.54062 | 0.16614 | 1.27752 | 5.178 | 57.54 |
| 18 | RFO | ロイヤルフラッシュ最適化(joo) | 0.83361 | 0.73742 | 0.34629 | 1.91733 | 0.89424 | 0.73824 | 0.24098 | 1.87346 | 0.63154 | 0.50292 | 0.16421 | 1.29867 | 5.089 | 56.55 |
| 19 | AOSm | 原子軌道探索M | 0.80232 | 0.70449 | 0.31021 | 1.81702 | 0.85660 | 0.69451 | 0.21996 | 1.77107 | 0.74615 | 0.52862 | 0.14358 | 1.41835 | 5.006 | 55.63 |
| 20 | TSEA | 亀甲進化アルゴリズム(joo) | 0.96798 | 0.64480 | 0.29672 | 1.90949 | 0.99449 | 0.61981 | 0.22708 | 1.84139 | 0.69077 | 0.42646 | 0.13598 | 1.25322 | 5.004 | 55.60 |
| 21 | BSA | バックトラッキング探索アルゴリズム | 0.97309 | 0.54534 | 0.29098 | 1.80941 | 0.99999 | 0.58543 | 0.21747 | 1.80289 | 0.84769 | 0.36953 | 0.12978 | 1.34700 | 4.959 | 55.10 |
| 22 | DE | 差分進化 | 0.95044 | 0.61674 | 0.30308 | 1.87026 | 0.95317 | 0.78896 | 0.16652 | 1.90865 | 0.78667 | 0.36033 | 0.02953 | 1.17653 | 4.955 | 55.06 |
| 23 | SRA | レストラン経営達人アルゴリズム(joo) | 0.96883 | 0.63455 | 0.29217 | 1.89555 | 0.94637 | 0.55506 | 0.19124 | 1.69267 | 0.74923 | 0.44031 | 0.12526 | 1.31480 | 4.903 | 54.48 |
| 24 | CRO | 化学反応の最適化 | 0.94629 | 0.66112 | 0.29853 | 1.90593 | 0.87906 | 0.58422 | 0.21146 | 1.67473 | 0.75846 | 0.42646 | 0.12686 | 1.31178 | 4.892 | 54.36 |
| 25 | BIO | 血液型遺伝最適化(joo) | 0.81568 | 0.65336 | 0.30877 | 1.77781 | 0.89937 | 0.65319 | 0.21760 | 1.77016 | 0.67846 | 0.47631 | 0.13902 | 1.29378 | 4.842 | 53.80 |
| 26 | BSA | 鳥群アルゴリズム | 0.89306 | 0.64900 | 0.26250 | 1.80455 | 0.92420 | 0.71121 | 0.24939 | 1.88479 | 0.69385 | 0.32615 | 0.10012 | 1.12012 | 4.809 | 53.44 |
| 27 | DEA | イルカのエコーロケーションアルゴリズム | 0.75995 | 0.67572 | 0.34171 | 1.77738 | 0.89582 | 0.64223 | 0.23941 | 1.77746 | 0.61538 | 0.44031 | 0.15115 | 1.20684 | 4.762 | 52.91 |
| 28 | HS | ハーモニー検索 | 0.86509 | 0.68782 | 0.32527 | 1.87818 | 0.99999 | 0.68002 | 0.09590 | 1.77592 | 0.62000 | 0.42267 | 0.05458 | 1.09725 | 4.751 | 52.79 |
| 29 | SSG | 苗木の播種と育成 | 0.77839 | 0.64925 | 0.39543 | 1.82308 | 0.85973 | 0.62467 | 0.17429 | 1.65869 | 0.64667 | 0.44133 | 0.10598 | 1.19398 | 4.676 | 51.95 |
| 30 | BCOm | 細菌走化性最適化M | 0.75953 | 0.62268 | 0.31483 | 1.69704 | 0.89378 | 0.61339 | 0.22542 | 1.73259 | 0.65385 | 0.42092 | 0.14435 | 1.21912 | 4.649 | 51.65 |
| 31 | ABO | アフリカ水牛の最適化 | 0.83337 | 0.62247 | 0.29964 | 1.75548 | 0.92170 | 0.58618 | 0.19723 | 1.70511 | 0.61000 | 0.43154 | 0.13225 | 1.17378 | 4.634 | 51.49 |
| 32 | (PO)ES | (PO)進化戦略 | 0.79025 | 0.62647 | 0.42935 | 1.84606 | 0.87616 | 0.60943 | 0.19591 | 1.68151 | 0.59000 | 0.37933 | 0.11322 | 1.08255 | 4.610 | 51.22 |
| 33 | FBA | フラクタルベースのアルゴリズム | 0.79000 | 0.65134 | 0.28965 | 1.73099 | 0.87158 | 0.56823 | 0.18877 | 1.62858 | 0.61077 | 0.46062 | 0.12398 | 1.19537 | 4.555 | 50.61 |
| 34 | TSm | タブーサーチM | 0.87795 | 0.61431 | 0.29104 | 1.78330 | 0.92885 | 0.51844 | 0.19054 | 1.63783 | 0.61077 | 0.38215 | 0.12157 | 1.11449 | 4.536 | 50.40 |
| 35 | BSO | ブレインストーム最適化 | 0.93736 | 0.57616 | 0.29688 | 1.81041 | 0.93131 | 0.55866 | 0.23537 | 1.72534 | 0.55231 | 0.29077 | 0.11914 | 0.96222 | 4.498 | 49.98 |
| 36 | WOAm | 鯨最適化アルゴリズムM | 0.84521 | 0.56298 | 0.26263 | 1.67081 | 0.93100 | 0.52278 | 0.16365 | 1.61743 | 0.66308 | 0.41138 | 0.11357 | 1.18803 | 4.476 | 49.74 |
| 37 | AEFA | 人工電界アルゴリズム | 0.87700 | 0.61753 | 0.25235 | 1.74688 | 0.92729 | 0.72698 | 0.18064 | 1.83490 | 0.66615 | 0.11631 | 0.09508 | 0.87754 | 4.459 | 49.55 |
| 38 | AEO | 人工生態系ベースの最適化アルゴリズム | 0.91380 | 0.46713 | 0.26470 | 1.64563 | 0.90223 | 0.43705 | 0.21400 | 1.55327 | 0.66154 | 0.30800 | 0.28563 | 1.25517 | 4.454 | 49.49 |
| 39 | CAm | ラクダアルゴリズムM | 0.78684 | 0.56042 | 0.35133 | 1.69859 | 0.82772 | 0.56041 | 0.24336 | 1.63149 | 0.64846 | 0.33092 | 0.13418 | 1.11356 | 4.444 | 49.37 |
| 40 | ACOm | 蟻コロニー最適化M | 0.88190 | 0.66127 | 0.30377 | 1.84693 | 0.85873 | 0.58680 | 0.15051 | 1.59604 | 0.59667 | 0.37333 | 0.02472 | 0.99472 | 4.438 | 49.31 |
| 41 | CMAES | 共分散行列適応進化戦略 | 0.76258 | 0.72089 | 0.00000 | 1.48347 | 0.82056 | 0.79616 | 0.00000 | 1.61672 | 0.75846 | 0.49077 | 0.00000 | 1.24923 | 4.349 | 48.33 |
| 42 | BFO-GA | 細菌採食の最適化:Ga | 0.89150 | 0.55111 | 0.31529 | 1.75790 | 0.96982 | 0.39612 | 0.06305 | 1.42899 | 0.72667 | 0.27500 | 0.03525 | 1.03692 | 4.224 | 46.93 |
| 43 | SOA | シンプル最適化アルゴリズム | 0.91520 | 0.46976 | 0.27089 | 1.65585 | 0.89675 | 0.37401 | 0.16984 | 1.44060 | 0.69538 | 0.28031 | 0.10852 | 1.08422 | 4.181 | 46.45 |
| 44 | ABHA | 人工蜂の巣アルゴリズム | 0.84131 | 0.54227 | 0.26304 | 1.64663 | 0.87858 | 0.47779 | 0.17181 | 1.52818 | 0.50923 | 0.33877 | 0.10397 | 0.95197 | 4.127 | 45.85 |
| 45 | ACMO | 大気雲モデルの最適化 | 0.90321 | 0.48546 | 0.30403 | 1.69270 | 0.80268 | 0.37857 | 0.19178 | 1.37303 | 0.62308 | 0.24400 | 0.10795 | 0.97503 | 4.041 | 44.90 |
| RW | ランダムウォーク | 0.48754 | 0.32159 | 0.25781 | 1.06694 | 0.37554 | 0.21944 | 0.15877 | 0.75375 | 0.27969 | 0.14917 | 0.09847 | 0.52734 | 2.348 | 26.09 | |
まとめ
ここで紹介したEOmのコンパクトな改良版実装は、メタヒューリスティック最適化の分野における興味深い例です。理論的な原則から大きく離れることで、実用面でより良い結果が得られる場合があります。45種類の集団ベース最適化手法の中で12位にランクされたこのアルゴリズムは、実際には、元のEOから冪乗則分布という主要なアイデアだけを残したハイブリッド手法といえます。
最悪のエージェントを「復活」させるメカニズムにより、早期収束を防ぎ、集団の多様性を維持するとともに、探索のための追加の機会を生み出します。 また、計算構造を簡略化し、コンポーネント単位の評価を廃止することで、計算量を削減しています。解を直接操作することで、収束速度も向上します。
EOの改良版が成功したことは、メタヒューリスティックアルゴリズムを設計する上で重要な原則を示しています。それは、アルゴリズムの効率は、元のアイデアにどれだけ忠実であるかによって決まるのではなく、探索空間における探索と活用のバランスによって決まるということです。ここで紹介した実装は、極値最適化の古典的な原理から離れているにもかかわらず、高い収束速度、競争力のある結果、そして実装・設定の容易さを示しています。
そのため、このアルゴリズムは集団ベース最適化手法の有用な選択肢の一つとなります。特に、解の品質と計算コストのバランスが重要となる問題に適しています。

図1:対応するテストに応じたアルゴリズムのカラーグラデーション

図2:アルゴリズムテスト結果のヒストグラム(0から100のスケール、高いほど良い)100は理論上の最大値であり、アーカイブには評価表を計算するためのスクリプトがあります。
EOmのメリットとデメリット
長所
- 実装がシンプル
- 高速かつ効率的
- 離散問題で良好な結果
短所
- 低次元関数では結果のばらつきが大きい
- 「滑らかな」低次元問題における平均的な結果
この記事には、最新版のアルゴリズムコードを含むアーカイブが添付されています。記事の著者は、正統的なアルゴリズム定義の説明の絶対的な正確さについて責任を負いません。探索性能を向上させるために、それらの多くに変更が加えられています。記事に示された結論と判断は、実験結果に基づいています。
記事で使用されているプログラム
| # | 名前 | 種類 | 詳細 |
|---|---|---|---|
| 1 | #C_AO.mqh | インクルード | 集団最適化アルゴリズムの親クラス |
| 2 | #C_AO_enum.mqh | インクルード | 集団最適化アルゴリズムの列挙 |
| 3 | TestFunctions.mqh | インクルード | テスト関数のライブラリ |
| 4 | TestStandFunctions.mqh | インクルード | テストスタンド関数ライブラリ |
| 5 | Utilities.mqh | インクルード | 補助関数のライブラリ |
| 6 | CalculationTestResults.mqh | インクルード | 比較表の結果を計算するスクリプト |
| 7 | Testing AOs.mq5 | スクリプト | すべての集団最適化アルゴリズムの統一テストスタンド |
| 8 | Simple use of population optimization algorithms.mq5 | スクリプト | 可視化せずに集団最適化アルゴリズムを使用する簡単な例 |
| 9 | Test_AO_EOm.mq5 | スクリプト | EOmテストスタンド |
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/18755
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初級から中級まで:オブジェクトイベント(IV)
市場シミュレーション(第23回):ポジション表示(I)
市場シミュレーション(第24回):ポジション表示(II)
初級から中級まで:オブジェクトイベント(III)
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索