English Русский 中文 Español Deutsch Português
preview
適応型社会行動最適化(ASBO):Schwefel、ボックス=ミュラー法

適応型社会行動最適化(ASBO):Schwefel、ボックス=ミュラー法

MetaTrader 5 | 11 2月 2025, 08:33
59 0
Andrey Dik
Andrey Dik
内容
  1. はじめに
  2. アルゴリズム手法の実装


1. はじめに

生物の集団行動は、驚くべき知恵を示します。魚の群れから人間社会に至るまで、協力と連携は生存と繁栄の鍵となっています。では、この社会構造の原則を活用し、複雑な問題を効率的かつ正確に解決できる最適化アルゴリズムを生み出すことができるとしたらどうでしょうか。

自然界には、生存率や革新の可能性を高めるために、生物が社会を形成し、協調する例が数多く見られます。動物界や人間社会、さらにはその他の生命体において観察されるこの現象は、進化生物学者や社会哲学者にとって興味深い研究対象となっています。こうした社会の仕組みを研究することで、特定の目標を達成するために社会がどのように機能するかをシミュレートする計算モデルが開発されました。粒子群最適化(PSO)や蟻コロニー最適化(ACO)などのモデルは、最適化問題の解決において集団行動の効率性を実証しています。

本記事では、社会構造の概念とそれがグループの意思決定プロセスに与える影響を検討します。また、大域的最適化を実現するために応用可能な、社会における社会的行動と相互作用の原則に基づく数学モデルも紹介します。このモデルはASBO(適応型社会行動最適化、Adaptive Social Behavior Optimization)と呼ばれ、リーダーシップ、近隣関係、自己組織化など、環境がグループの意思決定に与える影響を考慮しています。本アルゴリズムはManojo Kumar Singh によって提案され、2013年にAswatha Kumar M.らが編集した「Proceedings of ICAdC, AISC 174」に掲載されました。

社会の構造と影響力のモデル

  • 社会:共通の行動や特徴によって結びついた、相互に関連する生物の集団である
  • 社会で生活することの利点:獲物を狩る機会の増加、繁殖の機会、捕食者からの保護、革新の促進
  • 社会における個人の発達に影響を与える主な要因:リーダーシップ、近隣関係、自尊心
  • 提案されたASBOモデル:動的リーダーシップ、動的論理的近隣関係、自己評価に基づく

ASBOアルゴリズムの主な原則

  • 解の集合:各解はD次元空間内のベクトルである
  • 各解について、大域的最良解、個体最良解、近傍の中心の3つのベクトルを計算する
  • 新しい解の位置を、適応影響比を使用して計算する
  • 影響比率は、自己適応型突然変異戦略を使用して適応される


2. アルゴリズム手法の実装

アルゴリズムの詳細に入る前に、その基本概念を理解することが重要です。 この概念はSchwefel法に関連しており、これは自己適応型突然変異の手法の一つです。Schwefel法は、進化的アルゴリズムなどの最適化アルゴリズムで使用される手法であり、以下のような特徴を持ちます。

1. 突然変異パラメータの自己適応

  • 各個体(解)は、それぞれ独自の突然変異パラメータ(例:突然変異のステップサイズ)を持つ
  • 突然変異パラメータも、解とともに進化する
  • したがって、突然変異のステップサイズは、各個体の関数空間(ランドスケープ)に適応する

2. ガウス突然変異

  • 新しい解の生成にはガウス(正規)分布を使用する
  • 突然変異の平均値は前の解と等しく、標準偏差は突然変異パラメータによって決まる

3. 解と突然変異パラメータの関係

  • 突然変異パラメータ(ステップサイズ)は、解の目的関数(適合度)の値に依存する
  • 適合度の高い解ほど突然変異のステップサイズは小さくなり、逆に適合度の低い解ほどステップサイズが大きくなる

Schwefelの基本的な考え方は、突然変異パラメータを適応的に調整することで、アルゴリズムが解空間をより効率的に探索できるようにすることです。特に、探索の後半では、より精密な解の調整が必要になるため、この手法は大きな効果を発揮します。

以下の例では、Schwefelの概念を利用して取引戦略のパラメータ最適化をおこないます。メソッドの詳細な動作については後述します。

例として、OnInit初期化中にランダム解の初期集団が作成される、最も単純な仮想エキスパートアドバイザー(EA)を取り上げます。OnTickでは進化プロセスの1つのステップが完了します。

a. 集団内の各個体の適合度を評価する
b. 適合度に基づいて集団を並び替える
c. 最も適合度の高い個体を除き、すべての個体に突然変異を適用する
d.世代カウンタを増加させる

このプロセスを指定された世代数に達するまで繰り返し、最適化を実行します。最適化が完了すると、最良解が出力されます。

// Inputs
input int    PopulationSize = 50;   // Population size
input int    Generations = 100;     // Number of generations
input double InitialStepSize = 1.0; // Initial step size

// Structure for storing an individual
struct Individual
{
    double genes [3];  // Strategy parameters
    double stepSizes [3];  // Step sizes for each parameter
    double fitness;   // Fitness
};

// Global variables
Individual population [];
int generation = 0;

// Initialization function
int OnInit ()
{
  ArrayResize (population, PopulationSize);
  InitializePopulation ();
  return (INIT_SUCCEEDED);
}

// Main loop function
datetime lastOptimizationTime = 0;

void OnTick ()
{
  datetime currentTime = TimeCurrent ();

  // Check if a day has passed since the last optimization
  if (currentTime - lastOptimizationTime >= 86400) // 86400 seconds in a day
  {
    Optimize ();
    lastOptimizationTime = currentTime;
  }

  // Here is the code for trading with the current optimal parameters
  TradingLogic ();
}

void Optimize ()
{
  // Optimization code (current OnTick content)
}

void TradingLogic ()
{
  // Implementing trading logic using optimized parameters
}

// Initialize the population
void InitializePopulation ()
{
  for (int i = 0; i < PopulationSize; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      population [i].genes [j] = MathRand () / 32767.0 * 100;  // Random values from 0 to 100
      population [i].stepSizes [j] = InitialStepSize;
    }
  }
}

// Population fitness assessment
void EvaluatePopulation ()
{
  for (int i = 0; i < PopulationSize; i++)
  {
    population [i].fitness = CalculateFitness (population [i]);
  }
}

// Calculate the fitness of an individual (this is where you need to implement your objective function)
double CalculateFitness (Individual &ind)
{
  // Example of a simple objective function
  return -(MathPow (ind.genes [0] - 50, 2) + MathPow (ind.genes [1] - 50, 2) + MathPow (ind.genes [2] - 50, 2));
}

// Sort the population in descending order of fitness
void SortPopulation ()
{
  ArraySort (population, WHOLE_ARRAY, 0, MODE_DESCEND);
}

// Population mutation according to Schwefel's concept
void Mutate ()
{
  for (int i = 1; i < PopulationSize; i++)  // Start from 1 to keep the best solution
  {
    for (int j = 0; j < 3; j++)
    {
      // Step size mutation
      population [i].stepSizes [j] *= MathExp (0.2 * MathRandom () - 0.1);

      // Gene mutation
      population [i].genes [j] += population [i].stepSizes [j] * NormalRandom ();

      // Limiting gene values
      population [i].genes [j] = MathMax (0, MathMin (100, population [i].genes [j]));
    }
  }
}

// Auxiliary function for displaying information about an individual
void PrintIndividual (Individual &ind)
{
  Print ("Genes: ", ind.genes [0], ", ", ind.genes [1], ", ", ind.genes [2]);
  Print ("Step sizes: ", ind.stepSizes [0], ", ", ind.stepSizes [1], ", ", ind.stepSizes [2]);
  Print ("Fitness: ", ind.fitness);
}

メソッドを部分ごとに見てみましょう。

1. 構造体と入力

まず、アルゴリズムの入力と、集団内の単一の解を表すIndividual構造体を定義します。各個体には、遺伝子(戦略パラメータ)、突然変異のステップサイズ、および適応度値があります。

input int PopulationSize = 50;   // Population size
input int Generations = 100;     // Number of generations
input double InitialStepSize = 1.0; // Initial step size

struct Individual
{
  double genes[3];  // Strategy parameters
  double stepSizes[3];  // Step sizes for each parameter
  double fitness;   // Fitness
};

2. 初期化

OnInit()関数で、集団を作成して初期化します。InitializePopulation()関数は、集団をランダムな遺伝子値で埋め、初期ステップサイズを設定します。

int OnInit ()
{
  ArrayResize (population, PopulationSize);
  InitializePopulation ();
  return (INIT_SUCCEEDED);
}

void InitializePopulation ()
{
  for (int i = 0; i < PopulationSize; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      population [i].genes [j] = MathRand () / 32767.0 * 100;
      population [i].stepSizes [j] = InitialStepSize;
    }
  }
}

3. メインループ

最適化プロセスはOnTick()関数で管理されます。集団を評価し、分類し、突然変異を実行し、次の世代に進みます。

datetime lastOptimizationTime = 0;

void OnTick ()
{
  datetime currentTime = TimeCurrent ();

  // Check if a day has passed since the last optimization
  if (currentTime - lastOptimizationTime >= 86400) // 86400 seconds in a day
  {
    Optimize ();
    lastOptimizationTime = currentTime;
  }

  // Here is the code for trading with the current optimal parameters
  TradingLogic ();
}

void Optimize ()
{
  // Optimization code (current OnTick content)
}

void TradingLogic ()
{
  // Implementing trading logic using optimized parameters
}

4. 集団評価と選別

これらの関数は、各個体の適応度を評価し、適応度の降順で集団を並べ替えます。この例では、CalculateFitness()関数は単純ですが、実際の使用では、取引戦略を評価するための目的関数が必要です。

void EvaluatePopulation ()
{
  for (int i = 0; i < PopulationSize; i++)
  {
    population [i].fitness = CalculateFitness (population [i]);
  }
}

double CalculateFitness (Individual &ind)
{
  return -(MathPow (ind.genes [0] - 50, 2) + MathPow (ind.genes [1] - 50, 2) + MathPow (ind.genes [2] - 50, 2));
}

void SortPopulation ()
{
  ArraySort (population, WHOLE_ARRAY, 0, MODE_DESCEND);
}

5. 突然変異

これは、Schwefelの概念を実装する重要な部分です。すべての個体(最高の個体を除く)に対して、次をおこないます。

  • 乱数の指数を掛けてステップサイズを変更する
  • 正規分布する乱数にステップサイズを掛けたものを追加して、遺伝子を変異させる
  • 遺伝子の値を[0, 100]の範囲に制限する

パラメータ最適化のためのSchwefelの概念を基本的に実装します。実際のアプリケーションでは、ターゲット関数を特定の取引戦略に適合させる必要があります。

void Mutate ()
{
  for (int i = 1; i < PopulationSize; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      population [i].stepSizes [j] *= MathExp (0.2 * MathRandom () - 0.1);
      population [i].genes [j] += population [i].stepSizes [j] * NormalRandom ();
      population [i].genes [j] = MathMax (0, MathMin (100, population [i].genes [j]));
    }
  }
}

また、NormalRandom()関数の実装にも注目すべきです。これは、Schwefelの適応型突然変異の概念の一部であり、正規(ガウス)分布で乱数を生成するボックス=ミュラー法を実装しています。この関数を部分ごとにみてみましょう。

1. 均一に分布した数値の生成:[0, 1]の区間に均一に分布する2つの独立した乱数u1u2を生成します。

double u1 = u.RNDfromCI(0, 1);
double u2 = u.RNDfromCI(0, 1); 

2. 正規分布への変換:ボックス-ミュラー変換方程式は、均一に分布する数を正規分布する数に変換します。

return MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2);

これは、単一の正規分布する数値を生成するボックス=ミュラー変換の実装の半分であることに注意することが重要です。完全な変換により、正規分布する2つの数値が生成されます。

z0 = MathSqrt(-2 * MathLog(u1)) * MathCos(2 * M_PI * u2);
z1 = MathSqrt(-2 * MathLog(u1)) * MathSin(2 * M_PI * u2);

ここでの実装では、余弦のみを使用し、正規分布に従う単一の数値を生成します。1回の呼び出しで必要な数値が1つだけであれば、これでまったく問題ありません。両方の数値が必要な場合は、正弦計算を追加することで対応できます。
この実装は効率的であり、進化アルゴリズムや確率的最適化などのさまざまなアプリケーションにおいて、正規分布に従う乱数を生成する方法として広く使用されています。

生成された数値には以下の特性があります。

1. 分布:正規分布(ガウス分布)
2. 平均値:0
3. 標準偏差:1

生成される数値の範囲:理論的には、正規分布はマイナスの無限大からプラスの無限大までの範囲の数値を生成できます。実際には、以下のようになります。

  • 68%[-1, 1]の範囲内
  • 95%[-2, 2]の範囲内
  • 99.7%[-3, 3]の範囲内

非常に稀に、[-4, 4]の範囲外の数字が存在する場合もあります。

ボックス=ミュラーは、正規分布に従う乱数を生成するために使用される手法であり、Schwefelの概念に基づいたアルゴリズムにおける自己適応型突然変異の実装において重要な役割を果たします。この分布を利用することで、小さな変化がより頻繁に発生するようになり、場合によっては大きな突然変異も発生可能となるため、解空間の探索を効率的に進めることができます。それでは、ボックス=ミュラー法を実際にテストし、その動作を評価してみましょう。

NormalRandom()関数をテストするスクリプトを実装してみましょう。

#property version   "1.00"
#property script_show_inputs

input int NumSamples = 10000; // Amount of generated numbers

double NormalRandom ()
{
  double u1 = (double)MathRand () / 32767.0;
  double u2 = (double)MathRand () / 32767.0;
  return MathSqrt (-2 * MathLog (u1)) * MathCos (2 * M_PI * u2);
}

void OnStart ()
{
  double sum = 0;
  double sumSquared = 0;
  double min = DBL_MAX;
  double max = DBL_MIN;

  int histogram [];
  ArrayResize (histogram, 20);
  ArrayInitialize (histogram, 0);

  // Random number generation and analysis
  for (int i = 0; i < NumSamples; i++)
  {
    double value = NormalRandom ();
    sum += value;
    sumSquared += value * value;

    if (value < min) min = value;
    if (value > max) max = value;

    // Filling the histogram
    int index = (int)((value + 4) / 0.4); // Split the range [-4, 4] into 20 intervals
    if (index >= 0 && index < 20) histogram [index]++;
  }

  // Calculate statistics
  double mean = sum / NumSamples;
  double variance = (sumSquared - sum * sum / NumSamples) / (NumSamples - 1);
  double stdDev = MathSqrt (variance);

  // Display results
  Print ("Statistics for ", NumSamples, " generated numbers:");
  Print ("Average value: ", mean);
  Print ("Standard deviation: ", stdDev);
  Print ("Minimum value: ", min);
  Print ("Maximum value: ", max);

  // Display the histogram
  Print ("Distribution histogram:");
  for (int i = 0; i < 20; i++)
  {
    string bar = "";
    for (int j = 0; j < histogram [i] * 50 / NumSamples; j++) bar += "*";
    PrintFormat ("[%.1f, %.1f): %s", -4 + i * 0.4, -3.6 + i * 0.4, bar);
  }
}

テストスクリプトは次のことを実行します。

1. NormalRandom()関数を定義する
2. 指定された数の乱数を生成する(デフォルトは10,000)
3. 基本的な統計特性(平均、標準偏差、最小値、最大値)を計算する
4. 範囲[-4, 4]を20の間隔に分割して分布ヒストグラムを作成する
5. MetaTrader端末ログに結果を表示する

それではスクリプトをテストしてみましょう。20,000個の値を取得します。以下は、ボックス=ミュラー変換法をテストするために実行されているスクリプトの出力です。

2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Statistics for 20,000 generated numbers:
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Average value: -0.003037802901958332
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Standard deviation:0.9977477093538349
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Minimum value: -3.865371560675546
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Maximum value:3.4797509297243994
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    Distribution histogram:
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-4.0, -3.6):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-3.6, -3.2):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-3.2, -2.8):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-2.8, -2.4):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-2.4, -2.0):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-2.0, -1.6): *
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-1.6, -1.2): **
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-1.2, -0.8): ****
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-0.8, -0.4): ******
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [-0.4, 0.0): *******
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [0.0, 0.4): *******
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [0.4, 0.8): ******
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [0.8, 1.2): ****
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [1.2, 1.6): ***
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [1.6, 2.0): *
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [2.0, 2.4):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [2.4, 2.8):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [2.8, 3.2):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [3.2, 3.6):
2024.07.12 13:11:05.437    checkRND (.US500Cash,M5)    [3.6, 4.0):

出力から、このメソッドが正しく機能していることが明らかです。標準偏差はほぼ1に等しく、平均値は0であり、範囲は間隔[-4;4]に対応しています。

次に、突然変異の適応パラメータを計算し、関数を記述します。

//——————————————————————————————————————————————————————————————————————————————
void AdaptiveMutation (double &Cg, double &Cs, double &Cn)
{
  Cg *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom());
  Cs *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom());
  Cn *= MathExp (tau_prime * NormalRandom() + tau * NormalRandom());
}
//——————————————————————————————————————————————————————————————————————————————

CgCsCn適応パラメータは、Schwefelによって提案された概念に基づく自己適応型突然変異戦略を使用して計算されます。これらのパラメータは以下の式で計算されます。

1. 初期化

  • N個の解の集団を用意する。各解はベクトルのペア(pi, σi)として表される。ここで、i ∈ {0, 1, 2}は3つのパラメータCgCsCnに対応する。
  • π成分の初期値は、想定される解空間内で一様分布に従いランダムに選択される。
  • σiの初期値は固定値に設定される。

2. 子孫の生成

  • すべての親(pi, σi)に対して、次の式に従って子孫(pi', σi')が生成される。
σ'i (j) = σi (j) * exp (τ' * N (0,1) + τ * Nj (0,1))
p'i (j) = pi (j) + σi (j) * N (0,1)

ここで、pi (j)p'i (j)σi (j)σ'i (j)はそれぞれpip'iσiσ'iベクトルのj番目の成分である。

  • N (0,1):平均0、標準偏差1の正規分布から取得された乱数
  • Nj (0,1):平均0、標準偏差1の正規分布から取得された乱数。ここで、jはカウンタである。
  • ττ'はそれぞれ(√(2√n))^-1(√(2n))^-1に設定されるスケーリング係数であり、nは問題の次元である。

したがって、CgCsCn各適応パラメータは、この自己適応戦略に従って変化し、最適化プロセス中に動的に調整できるようになります。

以下の出力結果からわかるように、CgCsCnの比率の取得値は、個々のケースでは大きくなりすぎます。これは、戦略パラメータσを更新するための式で、新しい値が以前の値に掛け算されて得られるために発生します。これにより、パラメータをターゲット関数の複雑な表面に適合させることができますが、同時に不安定性や値の急激な変化が生じる可能性があります。

CgCsCnがどのような値を取るか見てみましょう。

1.3300705071425474 0.0019262948596068497 0.00015329292900983978
1.9508542383574192 0.000148608606140360157007.656113084095
52.13323602205895 1167.5425054524449 0.0008421503214593158
1.0183156975753507 0.13486291437434697 0.18290674582014257
0.00003239513683361894 61.366694225534175 45.11710564761292
0.0004785111900713668 0.4798661298436033 0.007932035893070274
2712198854.63250.00003936758705232012325.9282730206205
0.0016658142882911 22123.8635822767061.6844067196638965
2.0422888701555126 0.007999762224016285 0.02460292446531715
7192.665454927090.000007671729921045711 0.3705939923291289
0.0073279981653727785 3237957.25443397231.6750241266497745e-07
550.743392136872113.512466529311943 223762.44571145615
34.571961515974785 0.000008292503593679501 0.008122937723317175
0.000002128739177639208 63.17654973794633128927.83801094144
866.7293481660888 1260.08203897183261.8496629497788273
0.000008459817609364248 25.623751292511788 0.0013478840638847347
27.956286711833616 0.0006967869388129299 0.0006885039945210606
66.692887212623947449.76869262452 8934.079392419668
0.15058617433681198 0.003114981958516487 7.703748428996011e-07
0.22147618633450808 265.4903003920267 315.20318731505455
0.00000158737784835800561134.6304274682934 0.7883024873065534

Schwefel法による自己適応突然変異の結果としてCgCsおよびCnが非常に大きな値をとる場合は、これらの値を制御および制限するための対策を講じる必要がある場合があります。これはアルゴリズムの安定性と効率を維持するために重要です。比率の数値を制限するために使用できるアプローチはいくつかあります。

1. 値を制限する
CgCsCnの上限と下限を設定します。次は例です。 

void LimitParameters (double &param, double minValue, double maxValue)
{
  param = MathMax (minValue, MathMin (param, maxValue));
}

// Usage:
LimitParameters (Cg, 0.0, 2.0);
LimitParameters (Cs, 0.0, 2.0);
LimitParameters (Cn, 0.0, 2.0); 

2. 正規化
突然変異後のパラメータ値を正規化して、その合計が常に1になるようにします。

void NormalizeParameters (double &Cg, double &Cs, double &Cn)
{
  double sum = Cg + Cs + Cn;
  if (sum > 0)
  {
    Cg /= sum;
    Cs /= sum;
    Cn /= sum;
  }
  else
  {
    // If sum is 0, set equal values
    Cg = Cs = Cn = 1.0 / 3.0;
  }
}

3. 対数スケーリング
大きな値を滑らかにするために対数スケーリングを適用します。

double ScaleParameter (double param)
{
  if (param == 0) return 0;

  double sign = (param > 0) ? 1 : -1;
  return sign * MathLog (1 + MathAbs (param));
}

4. 突然変異ステップの適応スケーリング
パラメータが大きくなりすぎる場合は、突然変異のステップサイズを小さくします。

 void AdaptMutationStep(double &stepSize, double paramValue)
{
  if(MathAbs(paramValue) > threshold)
  {
    stepSize *= 0.9; // Reduce the step size
  }
}

5. 定期的なリセット
パラメータを定期的に初期値または母集団平均にリセットします。

void ResetParameters(int generationCount)
{
  if(generationCount % resetInterval == 0)
  {
    Cg = initialCg;
    Cs = initialCs;
    Cn = initialCn;
  }
}

6. 指数関数の使用
指数関数を適用してパラメータの増加を制限します。

double LimitGrowth(double param)
{
  return 2.0 / (1.0 + MathExp(-param)) - 1.0; // Limit values in [-1, 1]
}

7. 監視と適応
パラメータ値を監視し、許容限度を頻繁に超える場合は、突然変異戦略を調整します。

void MonitorAndAdapt(double &param, int &outOfBoundsCount)
{
  if(MathAbs(param) > maxAllowedValue)
  {
    outOfBoundsCount++;
    if(outOfBoundsCount > threshold)
    {
      // Adaptation of the mutation strategy
      AdjustMutationStrategy();
    }
  }
}

また、パラメータを過度に制約すると、解空間を探索するアルゴリズムの能力が低下する可能性があることに留意することも重要です。そのため、制御と柔軟性の適切なバランスを見つける必要があります。これらの方法は最適化愛好家によるさらなる研究に応用できますが、私は記事で説明した独自のGaussDistribution関数を使用しました。

//——————————————————————————————————————————————————————————————————————————————
void C_AO_ASBO::AdaptiveMutation (S_ASBO_Agent &ag)
{
  ag.Cg *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8));
  ag.Cs *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8));
  ag.Cn *= MathExp (tau_prime * u.GaussDistribution (0, -1, 1, 1) + tau * u.GaussDistribution (0, -1, 1, 8));
}
//——————————————————————————————————————————————————————————————————————————————

以下の出力結果からわかるように、CgCsCnの比率の取得値を確認すると、私の関数を適用した場合、ボックス=ミュラー法を使用した場合に比べて、大きな値が発生する頻度が大幅に減少しています。

0.025582051880112085 0.6207719272290446 0.005335225840354781
0.9810075068811726 0.16583946164135704 0.01016969794039794
0.006133031813953609 17.700790930206647 0.3745475117676483
1.4547663270710334 0.3537259667123157 0.08834618264409702
0.11125695415944291 0.28183794982955684 0.09051405673590024
0.06340035225180855 0.16270375413207716 0.36885953030567936
0.008575136469231127 2.5881627332149053 0.11237602809318312
0.00001436227841400286 0.02323530434501054 10.360403964016376
0.936476760121053 0.017321731852758693 0.40372788912091845
0.009288586536835293 0.0000072639468670123115 15.463139841665908
0.15092489031689466 0.02160456749606 0.011008504295160867
0.0100807047494077 0.4592091893288436 0.0343285901385665
0.010014652012224212 0.0014577046664934706 0.006484475820059919
0.0002654495048564554 0.0005018788250576451 1.8639207859646574
5.972802450172414 0.10070170017416721 0.9226557559293936
0.011441827382547332 14.599641192191408 0.00007257778906744059
0.7249805357484554 0.000004301248511125035 0.2718776654314797
5.019113547774523 0.11351424171113386 0.02129848352762841
0.023978285994614518 0.041738711812672386 1.0247944259605422
0.0036842456260203237 12.869472963773408 1.5167205157941646
0.4529181577133935 0.0000625576761842319 30.751931508050227
0.5555092369559338 0.9606330180338433 0.39426099685543164
0.036106836251057275 2.57811344513881 0.042016638784243526
3.502119772985753128.02639287135680.9925745499516576
279.22360611021950.6837013166327449 0.01615639677602729
0.09687457825904996 0.3812813151133578 0.5272720937749686

Schwefelの概念と比率の適応値を理解したところで、次にアルゴリズムにおける最近傍の決定方法を見ていきましょう。最近傍点Ncの座標を求めるために、以下の手法を使用します。
1. 集団内の各個体について、最近傍が決定される。
2. 最近傍とは、対象の個体と目的関数(適合度)の値が最も近い3つの個体を指す。
3. 対象の個体と3つの最近傍個体によって形成されるグループの中心座標を求める。この中心座標は、3つの最近傍個体の座標の算術平均として計算される。

このように、Nc座標は単に3つの個体の座標をランダムに選ぶのではなく、目的関数の値に基づいてグループの中心として計算されます。これにより、個体の周囲の環境に関する情報を活用し、次の位置をより合理的に決定することが可能になります。

ここで重要なのは、最近傍は地理的な距離ではなく、目的関数の値の近さによって決定されるという点です。これは、社会構造における「論理的な近接性」に対応し、単なる物理的な距離ではありません。

//——————————————————————————————————————————————————————————————————————————————
void C_AO_ASBO::FindNeighborCenter (const S_ASBO_Agent &ag, double &center [])
{
// Create arrays for indices and fitness differences
  int indices [];
  double differences [];
  ArrayResize (indices, popSize - 1);
  ArrayResize (differences, popSize - 1);

// Fill the arrays
  int count = 0;
  for (int i = 0; i < popSize; i++)
  {
    if (&a [i] != &ag)  // Exclude the current agent
    {
      indices [count] = i;
      differences [count] = MathAbs (a [i].fitness - ag.fitness);
      count++;
    }
  }

// Sort arrays by fitness difference (bubble sort)
  for (int i = 0; i < count - 1; i++)
  {
    for (int j = 0; j < count - i - 1; j++)
    {
      if (differences [j] > differences [j + 1])
      {
        // Exchange of differences
        double tempDiff = differences [j];
        differences [j] = differences [j + 1];
        differences [j + 1] = tempDiff;

        // Exchange of indices
        int tempIndex = indices [j];
        indices [j] = indices [j + 1];
        indices [j + 1] = tempIndex;
      }
    }
  }

// Initialize the center
  ArrayInitialize (center, 0.0);

// Calculate the center based on the three nearest neighbors
  for (int j = 0; j < coords; j++)
  {
    for (int k = 0; k < 3; k++)
    {
      int nearestIndex = indices [k];
      center [j] += a [nearestIndex].c [j];
    }
    center [j] /= 3;
  }
}
//——————————————————————————————————————————————————————————————————————————————

以下がメソッドの説明です。

  • 目的関数(適合度)値の近さに基づいて最近傍を決定する
  • 3つの最近傍個体を使用する
  • これら3つの近傍個体の座標の算術平均を求め、グループの中心を計算する

この関数を分析してみましょう。

1. 並び替え方向
並び替えは適応度の差の昇順でおこなわれます。

現在の要素が次の要素よりも大きい場合は、それらの位置が入れ替わります。したがって、並び替え後、differences配列は最小値から最大値の順に並び替えされ、indices配列内の対応するインデックスはこの並び替えを反映します。

2. この関数は以下の手順を実行します。

  • 現在のエージェントを考慮から除外する
  • 現在のエージェントと他のすべてのエージェント間の適応度の差を計算する
  • この差によってエージェントを並べ替える
  • 3つの最近傍個体(適応度の差が最も小さいもの)を選択する
  • これら3つの最近傍個体の座標に基づいてグループの中心を計算する
void C_AO_ASBO::FindNeighborCenter(int ind, S_ASBO_Agent &ag[], double &center[])
{
  int indices[];
  double differences[];
  ArrayResize(indices, popSize - 1);
  ArrayResize(differences, popSize - 1);

  int count = 0;
  for (int i = 0; i < popSize; i++)
  {
    if (i != ind)
    {
      indices[count] = i;
      differences[count] = MathAbs(ag[ind].f - ag[i].f);
      count++;
    }
  }

// Sort by fitness difference ascending
  for (int i = 0; i < count - 1; i++)
  {
    for (int j = 0; j < count - i - 1; j++)
    {
      if (differences[j] > differences[j + 1])
      {
        double tempDiff = differences[j];
        differences[j] = differences[j + 1];
        differences[j + 1] = tempDiff;

        int tempIndex = indices[j];
        indices[j] = indices[j + 1];
        indices[j + 1] = tempIndex;
      }
    }
  }

  ArrayInitialize(center, 0.0);

  int neighborsCount = MathMin(3, count);  // Protection against the case when there are less than 3 agents
  for (int j = 0; j < coords; j++)
  {
    for (int k = 0; k < neighborsCount; k++)
    {
      int nearestIndex = indices[k];
      center[j] += ag[nearestIndex].c[j];
    }
    center[j] /= neighborsCount;
  }
}

このバージョンの関数はエラーに対してより堅牢であり、集団内のエージェントの数が少ない(3未満)ケースを正しく処理します。アルゴリズムの基本的な論理的手法について理解できたので、次はアルゴリズム自体の構造の検討に移ります。ASBOアルゴリズムは、次の主なステップで構成されます。

1. 初期化

  • 初期集団を定義し、各解は明示的に表現(エンコード形式ではない)される
  • 各解の目的関数の値を計算し、その適合度を決定する
  • 最も適合度の高い解が、その時点での大域的リーダーとして宣言される
  • 各解について、次に適合度の高い最近傍のグループを決定する

2. パラメータの適応的変異

  • 各解に対して、Cg(大域的リーダーの影響)、Cs(個体の最良解の影響)、Cn(近傍グループの中心の影響)の3つの適応パラメータのベクトルを決定する
  • 自己適応型Schwefel突然変異戦略を用いて、これらのパラメータを更新する

3. 決定ステータスの更新

  • 各解の位置の変化をCgCsCnパラメータの現在の値を用いて計算する
  • 新しい解の位置は、現在の位置にこの変化を加えて決定される

4. 2段階プロセス

  • 段階1:M個の独立した集団を作成し、それぞれ一定回数の反復でASBOアルゴリズムを適用する。最終的な集団から各解の適合度とパラメータ値を保存する
  • 段階2:すべての最終集団から最適な適合度を持つ解を選択し、そのパラメータを用いて新しい集団を形成する。この新しい集団にASBOアルゴリズムの基本ロジックを適用し、最終的な解を求める。

それでは、ASBOアルゴリズムの主な特徴に焦点をあててみましょう。

  • 自己適応型突然変異戦略を適用し、パラメータを動的に更新する
  • リーダーシップと近隣の影響の概念を用いて社会的行動をモデル化する
  • 解の多様性を維持し、収束を高速化する2段階のプロセスを採用する


本記事では、Schwefelの概念、ボックス=ミュラー法(正規分布)、自己適応型突然変異率の使用、適応度値に基づく最近傍決定について検討しました。ASBOの構造についても触れました。次の記事では、人工進化の2段階プロセスを詳細に分析し、アルゴリズムの完成、テスト関数を用いた評価、効率性の検証をおこないます。


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

添付されたファイル |
多通貨エキスパートアドバイザーの開発(第15回):実際の取引のためのEAの準備 多通貨エキスパートアドバイザーの開発(第15回):実際の取引のためのEAの準備
既製のエキスパートアドバイザー(EA)の完成に徐々に近づくにつれ、取引戦略のテスト段階では二次的に思える問題にも注意を払う必要があります。これらの問題は、実際の取引に移行する際に重要となります。
人工電界アルゴリズム(AEFA) 人工電界アルゴリズム(AEFA)
この記事では、クーロンの静電気力の法則に触発された人工電界アルゴリズム(AEFA: Artificial Electric Field Algorithm)を紹介します。このアルゴリズムは、荷電粒子とその相互作用を利用して複雑な最適化問題を解決するために電気現象をシミュレートします。AEFAは、自然法則に基づいた他のアルゴリズムと比較して、独自の特性を示します。
取引におけるカオス理論(第1回):金融市場における導入と応用、リアプノフ指数 取引におけるカオス理論(第1回):金融市場における導入と応用、リアプノフ指数
カオス理論は金融市場に適用できるでしょうか。この記事では、従来のカオス理論とカオスシステムがビル・ウィリアムズが提案した市場のカオスの概念とどのように異なるかについて考察します。
取引におけるニューラルネットワーク:時空間ニューラルネットワーク(STNN) 取引におけるニューラルネットワーク:時空間ニューラルネットワーク(STNN)
この記事では、時空間変換を活用し、今後の価格変動を効果的に予測する手法について解説します。STNNの数値予測精度を向上させるために、データの重要な側面をより適切に考慮できる連続アテンションメカニズムが提案されています。