English Русский Español Deutsch 日本語 Português
preview
射箭算法(Archery Algorithm, AA)

射箭算法(Archery Algorithm, AA)

MetaTrader 5测试者 |
759 2
Andrey Dik
Andrey Dik

内容

  1. 概述
  2. 算法实现
  3. 测试结果


概述

在任务复杂度日益攀升、资源愈发紧缺的当下,优化举措不仅已成为一种必然需求,更在现代社会中演变成一门实实在在的算法艺术。如何在众多可能的解决方案中找到最佳的一个?如何降低成本、提高效率并实现最大利润?这些问题涉及众多领域的专家——从经济学到工程学,从社会系统到生态学。在解决优化问题之前,重要的是通过识别关键变量和数学关系来正确建模问题,从而真实地反映现实情况。优化在金融和交易中被广泛应用,它不仅有助于开发新的投资策略,还可以改进现有的策略。然而,尽管优化方法具有普遍性,但它们却被有条件地分为两类:确定性方法和随机方法。

像梯度下降这样的确定性方法通过使用数学导数来寻找最优解,提供严谨且可预测的解决方案,能够有效地模拟不同场景。然而,一旦问题变得非线性或多变量,它们的有效性可能会急剧下降。在此情况下,随机方法可以派上用场,它们依赖于随机过程,在复杂条件下能够找出可接受的解决方案,这在波动的市场中特别有效。

确定性方法和随机方法的结合在现代优化方法中发挥着关键作用。通过结合这两种方法,分析师可以创建更灵活且适应性强的模型,这些模型既能考虑稳定条件,也能应对变化条件。这不仅能够提高预测的质量,还能最小化风险,这对于成功的投资管理至关重要。

在本文中,我将介绍一种新的解决优化问题的方法——射箭算法(简称AA)。该算法由Fatemeh Ahmadi Zeidabadi及其同事开发,并于2022年2月发表。这种受射箭启发的方法通过基于随机选择的元素更新搜索空间中群体成员的位置,生成准最优解。我们将对标准目标函数进行测试,以评估AA的效率,并将获得的结果与我们已知的其他算法进行比较。通过深入细节,我们将揭示这种创新方法如何改变我们对优化的思维方式,并为解决复杂问题开辟新的视野。


算法实现

射箭算法(AA)是一种全新的随机优化方法,旨在寻找优化问题的最优解,其灵感来源于射箭者瞄准目标的行为。AA模拟了向目标射箭的过程。群体中的每个成员代表优化问题的一个潜在解,它们在搜索空间中的位置会根据随机选择的“目标”成员的表现进行更新,这类似于射箭者根据他们想要击中的位置调整瞄准方向。

群体以矩阵的形式表示,其中每一行对应一个成员(解),每一列对应问题的一个维度。这样可以根据目标函数值对决策进行结构化的评估和更新。每个成员的表现通过目标函数进行评估,该函数量化了找到的解的质量。结果存储在一个向量中,这使得算法可以比较不同解的效率。

目标被划分为若干区域,其宽度对应种群成员的生产力。计算概率函数以根据目标函数值确定每个成员被选中的概率,表现更优的“射箭者”被选中的概率更高。基于累积概率随机选择群体中的一个成员,模拟射箭者选择目标的过程。这一选择会影响其他成员位置的更新方式。该算法使用特定的方程在搜索空间中更新每个“射箭者”的位置。更新取决于所选“射箭者”的目标函数值是否优于当前值。这一过程涉及随机性,以探索搜索空间。AA以迭代的方式工作,直到达到停止条件(最大迭代次数)为止。在此过程中,算法会跟踪找到的最优解。

上述AA算法的原始版本将矩阵描述为群体,向量描述为群体的成员。然而,文本中并未指出对矩阵进行操作的具体动作。实际上,该算法包括了与大多数先前讨论的算法中类似的对搜索智能体的标准操作。

值得注意的是,“目标被划分为若干区域,其宽度对应于群体成员的生产力”这句话暗示了使用轮盘赌法进行选择。在此情况下,选择一个区域的概率与其宽度成正比。

通过这种方式,可以用更简单描述的解释许多复杂的概念,从而简化想法的实现。

因此,射箭算法是一种基于群体的优化方法,它使用射箭原理来指导寻找最优解的过程。它将随机性与正态分布相结合,以探索和利用搜索空间。算法的关键组成部分:

1. 群体成员(射箭者)
2. 概率向量和累积概率向量
3. 遗传机制(原始版本中不存在)
4. 位置更新机制
5. 训练强度参数(I)

首先,让我们展示该算法的伪代码:

初始化
创建一个包含popSize个智能体的群体
对于每个智能体:
在搜索范围内随机初始化一个位置
初始化前一个位置和适应度

主循环:
直到达到停止条件:
对于群体中的每个i智能体:
计算概率向量P和累积概率向量C
            
对于每个c坐标:
使用累积概率选择k射箭者
                
                If (random_number < inheritance_probability):
                    new_position [c] = k_archer_position [c]
                Otherwise:
                    I = rounding (1 + random_number_from_0_to_1)  // Training intensity parameter
                    random_gaussian = generate_gaussian_number (mean = 0, min =-1, max = 1)
                    
                    If (k_archer_fitness > i_agent_fitness):
                        new_position [c] = previous_position [c] + random_gaussian * (k_archer_position [c] - I * previous_position [c])
                    Otherwise:
                        new_position [c] = previous_position [c] + random_gaussian * (previous_position [c] - I * k_archer_position [c])
                
将new_position[c]限制在搜索范围内
            
更新i智能体的位置
        
评估所有智能体的适应度
更新全局最优解
对于每个智能体:
如果新的适应度优于之前的适应度:
更新前一个位置和适应度

返回找到的最优解

代码的实现特征:

1. 算法采用概率方法选择学习对象(射箭者)。
2. 遗传机制允许智能体以一定的概率直接复制成功射箭者的位置。
3. 在更新位置时,使用高斯分布为射箭者的“学习过程”引入随机性。
4. 算法存储智能体之前的最优位置,使其能够“记住”良好的决策。
5. 实现中将包含一个机制,以确保新位置在允许的搜索范围内。
6. 所描述的训练强度参数(I)将用于调节当前位置对新位置的影响程度。

参数I (训练强度)是一个随机变量,可以取值1或2。其定义如下:I = 将(1 + 0到1之间随机生成的数字)四舍五入到最接近的整数。这意味着I有一半的概率等于1,也有一半的概率等于2。参数I在算法中的作用:

1. 当I = 1时,算法进行较小的位置调整。
2. 当I = 2时,算法可以对位置进行更大幅度的调整。

让我们继续讨论算法代码。描述“射箭者”结构——S_AA_Agent。它代表优化算法中的一个智能体,包含了解空间中的一组坐标,并包含有关其适应度函数效率的信息。 

  • cPrev [] — 该数组存储智能体的前一个坐标。
  • fPrev — 该变量存储智能体的前一个适应度值。   

Init 方法允许我们通过设置其坐标的初始值和适应度来准备智能体以进行工作。接下来,将fPrev的值设置为“double”类型的最小可能值,因为适应度尚未计算。

//——————————————————————————————————————————————————————————————————————————————
struct S_AA_Agent
{
    double cPrev []; // previous coordinates
    double fPrev;    // previous fitness

    void Init (int coords)
    {
      ArrayResize (cPrev, coords);
      fPrev = -DBL_MAX;
    }
};
//——————————————————————————————————————————————————————————————————————————————

让我们来看看实现算法本身的C_AO_AAm类,它继承自C_AO类。 

  • popSize - 种群大小。
  • inhProbab - 从其他射箭者继承特征的概率。

然后,初始化参数值为2的params数组,该数组存储了算法的参数:种群大小和继承概率。

  • SetParams - 该方法根据存储在params数组中的值设置参数。它提取popSizeinhProbab的值,并将它们转换为适当的数据类型。
  • Init - 该方法通过接受最小和最大搜索边界、搜索步长以及迭代次数来初始化算法。
  • MovingRevision - 这两个方法负责智能体在解空间中的移动逻辑以及它们的修订(更新)。 

S_AA_Agent agent [] - 用于执行优化的智能体数组。

C_AO_AAm类实现优化算法,而SetParamsInitMovingRevision则负责在算法运行期间管理其配置和行为。 

//——————————————————————————————————————————————————————————————————————————————
class C_AO_AAm : public C_AO
{
  public: //--------------------------------------------------------------------
  ~C_AO_AAm () { }
  C_AO_AAm ()
  {
    ao_name = "AAm";
    ao_desc = "Archery Algorithm M";
    ao_link = "https://www.mql5.com/en/articles/15782";

    popSize   = 50;    // population size
    inhProbab = 0.3;

    ArrayResize (params, 2);

    params [0].name = "popSize";   params [0].val = popSize;
    params [1].name = "inhProbab"; params [1].val = inhProbab;
  }

  void SetParams ()
  {
    popSize   = (int)params [0].val;
    inhProbab = params      [1].val;
  }

  bool Init (const double &rangeMinP  [], // minimum search range
             const double &rangeMaxP  [], // maximum search range
             const double &rangeStepP [], // step search
             const int     epochsP = 0);  // number of epochs

  void Moving ();
  void Revision ();

  //----------------------------------------------------------------------------
  double  inhProbab; //probability of inheritance

  S_AA_Agent agent [];

  private: //-------------------------------------------------------------------
};
//——————————————————————————————————————————————————————————————————————————————

Init 方法在 C_AO_AAm 类中负责初始化优化算法。它接受四个参数:最小和最大搜索边界的数组、搜索步长以及默认值为0的迭代次数。

  • 如果标准初始化成功,该方法会调整 agent 数组的大小,使其与指定的 popSize 种群大小相匹配。这使我们能够创建所需数量的智能体,将其用于算法中。
  • for 循环中,使用 Init方法初始化数组中的每个智能体,该方法为每个智能体指定初始坐标。

最后,该方法返回 true,表示算法初始化成功完成。因此,Init方法通过设置必要的参数并创建将参与优化的智能体,确保算法为运行做好了准备。

//——————————————————————————————————————————————————————————————————————————————
bool C_AO_AAm::Init (const double &rangeMinP  [],
                     const double &rangeMaxP  [],
                     const double &rangeStepP [],
                     const int     epochsP = 0)
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //----------------------------------------------------------------------------
  ArrayResize (agent, popSize);
  for (int i = 0; i < popSize; i++) agent [i].Init (coords);

  return true;
}
//——————————————————————————————————————————————————————————————————————————————

Moving方法在C_AO_AAm类中负责根据智能体的当前位置和它们正在优化的函数值,在解空间中移动智能体。让我们将其分解为几个部分:

  • 如果该方法是首次调用(revision等于false),则在指定的rangeMinrangeMax边界内,为每个智能体的各个坐标初始化一个随机值。
  • 然后,使用SeInDiSp方法调整该值,以确保其符合指定的步长。

之后,将revision标识设置为true,并完成该方法。

  • 接下来创建两个数组:P用于存储概率,C用于存储累积概率。
  • 找到智能体适应度函数值中最差的 F_worst值,以对适应度函数值进行归一化。
  • 然后计算每个智能体的概率,并对其进行归一化,使其总和为1。
  • 根据P概率计算C累积概率。
  • 对于每个智能体的各个坐标,基于累积概率选择一个伙伴射箭者(即另一个智能体)。
  • 如果随机值小于指定的inhProbab继承概率,则智能体接受所选智能体的坐标(以给定的概率确保特征的继承)。
  • 否则,智能体根据一个方程更新其位置,该方程考虑了当前位置、随机值以及伙伴射箭者的位置。
  • 最后,使用SeInDiSp方法调整新的坐标值。

Moving方法实现了智能体在解空间中的移动,考虑了它们的当前位置和函数值,并使用概率方法选择移动方向和更新位置。

//——————————————————————————————————————————————————————————————————————————————
void C_AO_AAm::Moving ()
{
  //----------------------------------------------------------------------------
  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;
  }

  //-------------------------------------------------------------------------
  // Calculate probability vector P and cumulative probability C
  double P [], C [];
  ArrayResize (P, popSize);
  ArrayResize (C, popSize);
  double F_worst = DBL_MAX;
  double sum = 0;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < F_worst) F_worst = a [i].f;
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] =  a [i].f - F_worst;
    sum += P [i];
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] /= sum;
    C [i] = (i == 0) ? P [i] : C [i - 1] + P [i];
  }

  double x;

  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      // Select archer (k) using cumulative probability
      int k = 0;
      double r = u.RNDprobab ();
      while (k < popSize - 1 && C [k] < r) k++;

      if (u.RNDbool () < inhProbab)
      {
        x = a [k].c [c];
      }
      else
      {
        // Update position using Eq. (5) and (6)
        double I   = MathRound (1 + u.RNDprobab ());
        double rnd = u.GaussDistribution (0, -1, 1, 8);

        if (a [k].f > a [i].f)
        {
          x = agent [i].cPrev [c] + rnd * (a [k].c [c] - I * agent [i].cPrev [c]);
        }
        else
        {
          x = agent [i].cPrev [c] + rnd * (agent [i].cPrev [c] - I * a [k].c [c]);
        }
      }

      a [i].c [c] = u.SeInDiSp (x, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

在C_AO_AAm类中,Revision方法负责更新种群中表现最优个体的相关信息。该方法执行以下操作:

  • ind变量初始化为-1。它将用于存储具有最佳函数值的智能体的索引。
  • for循环遍历popSize种群中的所有智能体,如果当前智能体的函数值 a [i].f超过了当前最佳函数值fB
    • fB更新为新的更好的值a [i].f
    • 并将该智能体的索引存储在变量ind中。
循环完成后,如果ind不等于-1,则调用ArrayCopy函数。它将最佳智能体的坐标从数组a复制到数组 cB。第二个for循环也会遍历种群中的所有智能体:

  • 如果当前智能体的函数值a [i].f超过了其之前的适应度函数值agent [i].fPrev
    • 则更新智能体的fPrev的前一个值。
    • 并使用ArrayCopy将智能体的当前坐标复制到cPrev数组。

Revision方法的作用是更新有关全局最优解的信息,以及更新智能体的最佳位置。

//——————————————————————————————————————————————————————————————————————————————
void C_AO_AAm::Revision ()
{
  //----------------------------------------------------------------------------
  int ind = -1;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > fB)
    {
      fB = a [i].f;
      ind = i;
    }
  }

  if (ind != -1) ArrayCopy (cB, a [ind].c, 0, 0, WHOLE_ARRAY);

  //----------------------------------------------------------------------------
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f > agent [i].fPrev)
    {
      agent [i].fPrev = a [i].f;
      ArrayCopy (agent [i].cPrev, a [i].c, 0, 0, WHOLE_ARRAY);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————


测试结果

我对算法进行了些许修改。原始算法并不提供射箭者之间的直接信息交换。信息交换是通过正态分布间接地通过坐标交互发生的,因此我认为有必要添加这种信息的交换。为此,我增加了一个附加的inhProbab算法,以给定的概率实现这种交换。

if (u.RNDbool () < inhProbab)
{
  x = a [k].c [c];
}

以下呈现的结果对应于作者最初设想算法的原始版本。

AA|Archery Algorithm|50.0|
=============================
5 Hilly's; Func runs: 10000; result: 0.6699547926310098
25 Hilly's; Func runs: 10000; result: 0.37356238340164605
500 Hilly's; Func runs: 10000; result: 0.257542163368952
=============================
5 Forest's; Func runs: 10000; result: 0.38166669771790607
25 Forest's; Func runs: 10000; result: 0.199300365268835
500 Forest's; Func runs: 10000; result: 0.15337954055780398
=============================
5 Megacity's; Func runs: 10000; result: 0.4076923076923077
25 Megacity's; Func runs: 10000; result: 0.17907692307692308
500 Megacity's; Func runs: 10000; result: 0.10004615384615476
=============================
总分: 2.72222 (30.25%)

该算法在测试中的得分占比为30.25%,但经过我的修改后,算法的性能提高了超过13%。以下是修改版本的结果:

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9353194829441194
25 Hilly's; Func runs: 10000; result: 0.6798262991897616
500 Hilly's; Func runs: 10000; result: 0.2596620178276653
=============================
5 Forest's; Func runs: 10000; result: 0.5735062785421186
25 Forest's; Func runs: 10000; result: 0.22007188891556378
500 Forest's; Func runs: 10000; result: 0.1486980566819649
=============================
5 Megacity's; Func runs: 10000; result: 0.6307692307692309
25 Megacity's; Func runs: 10000; result: 0.344
500 Megacity's; Func runs: 10000; result: 0.10193846153846249
=============================
总分: 3.89379 (43.26%)

因此,我选择了经过修改的算法,并将其添加到了排名表中。算法的可视化结果如下。我认为它相当不错。当然,结果存在一定的分散性,不但不重要,而且仅仅出现在坐标数量较少的函数中。

Hilly值

AAm在Hilly测试函数上

Forest值

AAm在Forest测试函数上

Megacity

AAm在Megacity测试函数上

根据运行结果,经过修改的算法版本排名第26位。

# AO 说明 Hilly值Hilly最终值 Forest值Forest最终值 Megacity (离散)Megacity最终值 最终结果 最大百分比
10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)10 p (5 F)50 p (25 F)1000 p (500 F)
1ANS跨邻域搜索0.949480.847760.438572.235811.000000.923340.399882.323230.709230.634770.230911.574916.13468.15
2CLA密码锁算法0.953450.871070.375902.200420.989420.917090.316422.222940.796920.693850.193031.683806.10767.86
3AMOm动物迁徙优化M0.903580.843170.462842.209590.990010.924360.465982.380340.567690.591320.237731.396755.98766.52
4(P+O)ES(P+O) 进化策略0.922560.881010.400212.203790.977500.874900.319452.171850.673850.629850.186341.490035.86665.17
5CTA彗星尾算法0.953460.863190.277702.094350.997940.857400.339492.194840.887690.564310.105121.557125.84664.96
6SDSm随机扩散搜索 M0.930660.854450.394762.179880.999830.892440.196192.088460.723330.611000.106701.441035.70963.44
7ESG社会群体的进化0.999060.796540.350562.146161.000000.828630.131021.959650.823330.553000.047251.423585.52961.44
8SIA模拟各向同性退火0.957840.842640.414652.215130.982390.795860.205071.983320.686670.493000.090531.270205.46960.76
9ACS人工协同搜索0.755470.747440.304071.806981.000000.888610.224132.112740.690770.481850.133221.305835.22658.06
10ASO无序社会优化0.848720.746460.314651.909830.961480.791500.238031.991010.570770.540620.166141.277525.17857.54
11TSEA龟壳演化算法0.967980.644800.296721.909490.994490.619810.227081.841390.690770.426460.135981.253225.00455.60
12DE差分进化0.950440.616740.303081.870260.953170.788960.166521.908650.786670.360330.029531.176534.95555.06
13CRO化学反应优化0.946290.661120.298531.905930.879060.584220.211461.674730.758460.426460.126861.311784.89254.36
14BSA鸟群算法0.893060.649000.262501.804550.924200.711210.249391.884790.693850.326150.100121.120124.80953.44
15HS和声搜索0.865090.687820.325271.878180.999990.680020.095901.775920.620000.422670.054581.097254.75152.79
16SSG树苗播种和生长0.778390.649250.395431.823080.859730.624670.174291.658690.646670.441330.105981.193984.67651.95
17BCOm细菌趋化性优化算法M0.759530.622680.314831.697040.893780.613390.225421.732590.653850.420920.144351.219124.64951.65
18(PO)ES(PO) 进化策略0.790250.626470.429351.846060.876160.609430.195911.681510.590000.379330.113221.082554.61051.22
19TSm禁忌搜索M0.877950.614310.291041.783300.928850.518440.190541.637830.610770.382150.121571.114494.53650.40
20BSO头脑风暴优化0.937360.576160.296881.810410.931310.558660.235371.725340.552310.290770.119140.962224.49849.98
21WOAm鲸鱼优化算法M0.845210.562980.262631.670810.931000.522780.163651.617430.663080.411380.113571.188034.47649.74
22AEFA人工电场算法0.877000.617530.252351.746880.927290.726980.180641.834900.666150.116310.095080.877544.45949.55
23ACOm蚁群优化 M0.881900.661270.303771.846930.858730.586800.150511.596040.596670.373330.024720.994724.43849.31
24BFO-GA细菌觅食优化 - ga0.891500.551110.315291.757900.969820.396120.063051.428990.726670.275000.035251.036924.22446.93
25ABHA人工蜂巢算法0.841310.542270.263041.646630.878580.477790.171811.528180.509230.338770.103970.951974.12745.85
26AAm射箭算法M0.935320.679830.259661.874810.573510.220070.148700.942280.630770.344000.101941.076713.89443.26
27ASBO适应性社会行为优化0.763310.492530.326191.582020.795460.400350.260971.456770.264620.171690.182000.618313.65740.63
28MEC思维进化计算0.695330.533760.326611.555690.724640.330360.071981.126980.525000.220000.041980.786983.47038.55
29IWO入侵杂草优化0.726790.522560.331231.580580.707560.339550.074841.121960.423330.230670.046170.700173.40337.81
30Micro-AIS微型人工免疫系统0.795470.519220.308611.623300.729560.368790.093981.192330.376670.158670.028020.563353.37937.54
31COAm布谷鸟优化算法 M0.758200.486520.313691.558410.740540.280510.055991.077040.505000.174670.033800.713473.34937.21
32SDOm螺旋动力学优化 M0.746010.446230.296871.489120.702040.346780.109441.158260.428330.167670.036630.632633.28036.44
33NMmNelder-Mead方法 M0.738070.505980.313421.557470.636740.283020.082211.001970.446670.186670.040280.673623.23335.92
34FAm萤火虫算法 M0.586340.472280.322761.381380.684670.374390.109081.168140.286670.164670.047220.498553.04833.87
35GSA引力搜索算法0.647570.491970.300621.440160.539620.363530.099451.002600.326670.122000.019170.467832.91132.34
36BFO细菌觅食优化0.611710.432700.313181.357590.544100.215110.056760.815970.421670.138000.031950.591622.76530.72
37ABC人工蜂群0.633770.424020.308921.366710.551030.218740.056230.826000.340000.142000.031020.513022.70630.06
38BA蝙蝠算法0.597610.459110.352421.409150.403210.193130.071750.668100.210000.101000.035170.346172.42326.93
39AAA人工藻类算法0.500070.320400.255251.075720.370210.222840.167850.760890.278460.148000.097550.524022.36126.23
40SA模拟退火0.557870.421770.315491.295130.349980.152590.050230.552800.311670.100330.028830.440832.28925.43
41IWDm智能水滴 M0.545010.378970.301241.225220.461040.147040.043690.651770.258330.097000.023080.378422.25525.06
42PSO粒子群优化0.597260.369230.299281.265770.372370.163240.070100.605720.256670.080000.021570.358232.23024.77
43Boids算法虚拟生物算法0.433400.305810.254250.993460.357180.201600.157080.715860.278460.142770.098340.519572.22924.77
44MA猴群算法0.591070.426810.318161.336040.311380.140690.066120.518190.228330.085670.027900.341902.19624.40
45SFL混合蛙跳算法0.539250.358160.298091.195510.371410.114270.040510.526180.271670.086670.024020.382352.10423.38



总结

我呈现了算法的两个版本:原始版本和经过修改的版本,后者只进行了微小的改动,但却显著提高了性能。本文清楚地表明,即使是算法逻辑的微小调整,也能在各种任务中显著提高效率。同时,这也表明复杂的描述可能会让人难以理解算法的工作原理,从而阻碍其改进。相反,用简单的语言表达复杂的概念,可以为更高效的解决方案铺平道路。

标签

图1. 根据相关测试,将算法的颜色等级大于或等于0.99的结果以白色突出显示。

图表

图例2. 算法测试结果的直方图(评分范围为0到100,越高越好,其中100为理论上的最高可能得分,档案中附有计算排名表的脚本)


文章即将完成并准备发表时,我突然有了一个想法,决定进行测试。如果按照作者关于目标和射箭者使用“轮盘赌”方法进行选择的逻辑,我们是否可以将目标本身的大小与找到的解决方案的质量成反比地调整呢?如果解决方案质量较高,那么应该对其进行细化,并探索其周围区域。反之,如果结果并不显著,则需要扩大搜索范围,以识别新的、潜在的有希望的区域。

目标

图例3. 射中目标的箭矢数量与目标本身的质量成正比,而目标的大小与它们的质量成反比 

让我们看一下这段代码,其增加了目标大小与质量成反比的思路。

void C_AO_AAm::Moving ()
{
  //----------------------------------------------------------------------------
  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;
  }

  //-------------------------------------------------------------------------
  // Calculate probability vector P and cumulative probability C
  double P [], C [];
  ArrayResize (P, popSize);
  ArrayResize (C, popSize);
  double F_worst = DBL_MAX; // a[ArrayMaximum(a, WHOLE_ARRAY, 0, popSize)].f;
  double sum = 0;

  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < F_worst) F_worst = a [i].f;
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] =  a [i].f - F_worst; ////F_worst - a[i].f;
    sum += P [i];
  }

  for (int i = 0; i < popSize; i++)
  {
    P [i] /= sum;
    C [i] = (i == 0) ? P [i] : C [i - 1] + P [i];
  }

  double x;
  
  double maxFF = fB;
  double minFF = DBL_MAX;
  double prob1;
  double prob2;
  
  for (int i = 0; i < popSize; i++)
  {
    if (a [i].f < minFF) minFF = a [i].f;
  } 

  for (int i = 0; i < popSize; i++)
  {
    for (int c = 0; c < coords; c++)
    {
      // Select archer (k) using cumulative probability
      int k = 0;
      double r = u.RNDprobab ();
      while (k < popSize - 1 && C [k] < r) k++;

      if (u.RNDbool () < inhProbab)
      {
        x = a [k].c [c];
      }
      else
      {
        
        // Update position using Eq. (5) and (6)
        //double I   = MathRound (1 + u.RNDprobab ());
        double rnd = u.GaussDistribution (0, -1, 1, 8);
        /*
        if (a [k].f > a [i].f)
        {
          x = agent [i].cPrev [c] + rnd * (a [k].c [c] - I * agent [i].cPrev [c]);
        }
        else
        {
          x = agent [i].cPrev [c] + rnd * (agent [i].cPrev [c] - I * a [k].c [c]);
        }
        */
        
        prob1 = u.Scale (a [i].f, minFF, maxFF, 0, 1);
        prob2 = u.Scale (a [k].f, minFF, maxFF, 0, 1);
        
        x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (1 - prob1 - prob2);  
        
      }

      a [i].c [c] = u.SeInDiSp (x, rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
//—

1. 原始版本中被注释的部分使用了if-else条件结构来确定如何更新智能体的位置。此逻辑已经被移除,并替换为一种新的计算方式。

2. 三条新的代码行:

prob1 = u.Scale(a[i].f, minFF, maxFF, 0, 1);
prob2 = u.Scale(a[k].f, minFF, maxFF, 0, 1);

x = agent[i].cPrev[c] + rnd * (a[k].c[c] - agent[i].cPrev[c]) * (1 - prob1 - prob2);

由此引入了一种计算更新位置的新方法:

a) 通过Scale函数计算出两个概率值(prob1和prob2),该函数根据最小适应度值minFF和最大适应度值maxFF,将智能体i和k的适应度值归一化到0到1的范围内。

b) 然后使用这些概率计算新的x位置。其使用了agent [i].cPrev [c]智能体的i前一个位置、a [k].c [c]所选射箭者k的位置以及rnd随机因子。

c) 当前的移动行为受到两个智能体适应度值之和与1的差值影响。作为一个缩放因子,允许目标根据所选射箭者的适应度成反比地扩展或收缩。射箭者越不熟练,箭的分布就越广,但击中目标的概率分布仍然遵循正态分布。

现在我们来看一下结果:

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9174358826544864
25 Hilly's; Func runs: 10000; result: 0.7087620527831496
500 Hilly's; Func runs: 10000; result: 0.42160091427958263
=============================
5 Forest's; Func runs: 10000; result: 0.9252690259821034
25 Forest's; Func runs: 10000; result: 0.7580206359203926
500 Forest's; Func runs: 10000; result: 0.353277934084795
=============================
5 Megacity's; Func runs: 10000; result: 0.6738461538461538
25 Megacity's; Func runs: 10000; result: 0.552
500 Megacity's; Func runs: 10000; result: 0.23738461538461658
=============================
总分: 5.54760 (61.64%)

算法的性能显著提升。在以下可视化结果中,我们可以看到算法的稳定收敛以及对函数曲面重要区域的识别。

Hilly2

AAm在Hilly测试函数上

让我们再做一个小实验。上面的结果是从1中减去射箭者概率之和从而得到的。

//x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (1 - prob1 - prob2); 
 x = agent [i].cPrev [c] + rnd * (a [k].c [c] - agent [i].cPrev [c]) * (2 - prob1 - prob2);  

主要的变化是从2中减去概率之和,而不是从1中减去。让我们看看这样一个简单的操作如何影响算法的行为:

  • 在之前的版本中,如果两个射箭者的适应度都很高,这个操作结果可能是负数,从而在新射箭者的坐标中产生“突变”效应。
  • 在新版本中,乘数将是一个介于0到2之间的值。

这一变化使得智能体在移动时加大幅度,更积极地探索解空间,因为智能体在每次位置更新时会迈出更大的步伐。

因此,正如算法结果的打印输出所示,这一改变在中等维度的函数上提高了算法的收敛性,但也导致了在高维函数(用黄色标记)上的性能下降,尽管总体上算法得到了更高的最终得分。

AAm|Archery Algorithm M|50.0|0.3|
=============================
5 Hilly's; Func runs: 10000; result: 0.9053229410164233
25 Hilly's; Func runs: 10000; result: 0.8259118221071665
500 Hilly's; Func runs: 10000; result: 0.2631661675236262
=============================
5 Forest's; Func runs: 10000; result: 0.9714247249319152
25 Forest's; Func runs: 10000; result: 0.9091052022399436
500 Forest's; Func runs: 10000; result: 0.2847632249786224
=============================
5 Megacity's; Func runs: 10000; result: 0.7169230769230768
25 Megacity's; Func runs: 10000; result: 0.6378461538461538
500 Megacity's; Func runs: 10000; result: 0.10473846153846252
=============================
总分: 5.61920 (62.44%)

之前的版本看起来更实用,将其保留作为AAm算法修改版的主要版本。我将再次展示带有热力分级的排名表。AAm现在占据了当之无愧的第7位。该算法可以被描述为非常平衡(在不同维度的函数上收敛良好),可以推荐其用于解决不同的问题。

Tab2

图例 4. 根据相关测试,将算法的颜色等级大于或等于0.99的结果以白色突出显示。

AAm的优缺点:

优点:

  1. 速度相当快。
  2. 自适应性。
  3. 只有一个外部参数。
  4. 良好的收敛性。
  5. 良好的扩展性。
  6. 实现简单(尽管作者的描述较复杂)。

缺点:

  1. 在低维函数上易陷入局部最优。

进一步在评分表中新增算法会导致表格的可读性下降。因此,我决定将参与评分的算法数量限制为45种,并将竞赛改为“淘汰制”模式。为便于读者以直观且流畅的方式访问所有相关文章,我制作了一份HTML文件,其中包含按评分排序的已评审算法列表。该文件已在文章中存档了一段时间,首次打开它的读者还会发现一个小彩蛋。”

文章附有一个包含当前版本算法代码的归档文件。本文作者对标准算法描述的绝对准确性不承担责任。为提升搜索能力,已经对其中的许多算法进行了修改。文章中表述的结论和论断都是基于实验的结果。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/15782

附加的文件 |
AAm.zip (35.89 KB)
最近评论 | 前往讨论 (2)
Ilya Melamed
Ilya Melamed | 13 9月 2024 在 18:11
感谢您的研究。但作为 mql5 上 Expert Advisors 的一名普通程序员(我不是数学家),我有一个非常简单的问题。这个问题对您来说可能很愚蠢,我在此先向您道歉。但是,您的研究对优化 EA 有什么帮助?您能举个例子吗?比方说,我们有一个新的 EA,我们想优化它,....?谢谢。
Andrey Dik
Andrey Dik | 14 9月 2024 在 18:23
Ilya Melamed 优化 EA 有什么帮助?您能举个例子吗?比方说,我们有一个新的 EA,我们想优化它,....?谢谢。

感谢您对我工作的关注和提出的好问题。

应用优化算法的场景有很多,只要您想在可能的解决方案中获得最佳方案。

例如,您可以将其应用于 EA 的自我优化,如此处 所述。

或者可以将其作为内部测试人员优化管理的一部分,如此处 所述。

量化风险管理方法:应用 VaR 模型优化多货币投资组合(使用 Python 和 MetaTrader 5) 量化风险管理方法:应用 VaR 模型优化多货币投资组合(使用 Python 和 MetaTrader 5)
本文探讨了价值风险(VaR)模型在多货币投资组合优化中的潜力。借助 Python 的强大功能和 MetaTrader 5 的功能,我们展示了如何实施 VaR 分析,以实现高效的资金分配和头寸管理。从理论基础到实际实施,文章涵盖了将 VaR——这一最稳健的风险计算系统之一——应用于算法交易的方方面面。
高效处理指标的便捷方法 高效处理指标的便捷方法
在本文中,我将介绍如何制作一个简单的面板,以便直接从图表中更改指标设置,以及需要对指标进行哪些更改以连接该面板。本文面向 MQL5 的新手用户。
在Python和MQL5中应用局部特征选择 在Python和MQL5中应用局部特征选择
本文探讨了Narges Armanfard等人在论文《数据分类的局部特征选择》中介绍的一种特征选择算法。该算法使用Python实现,用于构建二元分类器模型,这些模型可以与MetaTrader 5应用程序集成以进行推理。
开发回放系统(第 61 部分):玩转服务(二) 开发回放系统(第 61 部分):玩转服务(二)
在本文中,我们将研究使回放/模拟系统更高效、更安全地运行的修改。我也不会对那些想要充分利用这些类的人置之不理。此外,我们将探讨 MQL5 中的一个特定问题,即在使用类时降低代码性能,并解释如何解决它。