English Русский Español Deutsch 日本語 Português
preview
协方差矩阵自适应演化策略(CMA-ES)

协方差矩阵自适应演化策略(CMA-ES)

MetaTrader 5交易 |
109 0
Andrey Dik
Andrey Dik

目录

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


概述

优化领域有很多算法,但我们正在寻找最强大的算法来解决我们交易机器人的优化问题。CMA-ES(协方差矩阵自适应演化策略)是数学严谨性与生物学直觉相结合的罕见例子之一,它创造了一种算法,该算法不仅能解决优化问题,还能学习理解问题的结构。

CMA-ES 的历史始于 20 世纪 90 年代末的德国研究实验室,当时尼古劳斯·汉森(Nikolaus Hansen)和安德烈亚斯·奥斯特迈尔(Andreas Ostermeier)提出了一个根本性问题:有没有可能设计一种优化算法,它不仅能简单地搜索解决方案,还能根据问题的具体结构进行调整?传统的进化算法在父代个体周围的球形区域内生成后代,这种方法对于简单函数效果良好,但对于复杂且条件数较差的病态问题则效果不佳。让我们一起来看一下这个有趣的算法。


算法的实现

想象一下在一个形状不规则的岛屿上寻找宝藏。通常的做法是向各个方向均匀搜索,就像岛屿是圆形的那样。CMA-ES 逐渐了解岛屿的形状,并逐渐将其搜索方向转向找到宝藏的可能性更高的方向。此外,它还能记住成功的路线,并利用这一记忆来规划未来的搜索路径。

CMA-ES 基于一个看似简单的方程: x_k ~ N(m, σ²C) 。然而,这种简洁的背后隐藏着深厚的数学结构。这里的每个符号都承载着关于搜索状态的重要信息:m 是当前对最优解位置的最佳猜测, σ 是我们愿意冒险偏离已知解的程度度量,而 C 是一个协方差矩阵,它编码了我们对函数几何特性的理解。我们唯一可以合理引入的改动是用幂律分布替换正态分布,这意味着实现将遵循一个修改后的方程:x_k ~ PowerDist(m, σ²C)。 这一修改改变了对搜索空间的探索方式(更大的‘跳跃’),但保留了算法的基本自适应性质。

协方差矩阵 C 是该算法的真正核心。它起初是一个不起眼的单位矩阵,代表一种球形分布。但每一次迭代都会有所演变,在快速改进的方向上不断延伸,而在进展缓慢的地方则有所收缩。逐渐地,球体变成一个椭圆,然后变成一个细长的椭球体,理想情况下,其方向与待优化函数的轮廓相一致。

CMA-ES 的主要创新之处在于演化路径的概念。这是算法的一种遗传记忆,它不仅记得成功点在哪里,还记得算法是如何找到这些点的。进化路径累积了关于连续成功步骤的信息,形成了一个指向最有前景搜索区域的方向向量。第二条进化路径执行着更为精细的功能:它控制着步长。如果算法的连续步骤之间存在相关性,即每个后续步骤都延续了前一个步骤的方向,那么步长就会增加 — 算法“感觉”它正在朝着正确的方向前进。如果步骤是随机的且不相关,则步长减小 — 或许算法已经接近最优解,需要更仔细地进行搜索。

在 CMA-ES 中,进化的生物学隐喻背后隐藏着一条严谨的数学原理 — 最大似然估计。该算法会不断地问自己:哪些分布参数使得观察到的成功点最有可能出现?这将进化优化从一种启发式方法转变为一种基于统计的方法。

C 协方差矩阵的每次更新都包含两个部分:基于进化路径的秩一更新和使用来自整个选定种群的信息的秩 μ更新。秩一更新提供了稳定性和捕捉长期趋势的能力,而秩μ更新则允许快速适应当前一代的新信息。

在 CMA-ES 的背景下,使用了一个修改后的 Heaviside 函数,该函数在该算法的停滞检测机制中起着重要作用。该函数将进化路径的长度与预期长度进行比较;如果路径过短,则表明存在“随机摇摆”的情况。 停用条件(hsig = 0):算法随机“游荡”,步长相互抵消,则步长可能过大。在停滞期,秩一更新的影响会减弱,我们会更多地依赖来自整体群体的信息。 激活条件(hsig = 1): 算法朝着某个方向发展,连续的步骤相互关联,那么步长就足以适应当前情况。

CMA-ES 最深刻的特性之一是它对搜索空间变换的不变性。该算法同样有效地求解函数 f(x) 和函数 f(Ax + b),其中 A 是任意非奇异矩阵,b 是任意移位向量。这意味着 CMA-ES 与坐标系的选择无关。这种不变性并非偶然;它是使用最大似然原则和调整协方差矩阵的直接结果。该算法能自动为问题检测出一个自然坐标系,其中坐标轴与函数变化的主要方向一致。

理论上的优雅必须兼顾实际可用性。CMA-ES 需要 O(n²) 内存来存储协方差矩阵,需要 O(n³) 计算来进行特征值分解,这使得该算法适用于维度高达数百个变量的问题。对于大维度情况,已经开发出了专门的改进方法:sep-CMA-ES 仅限于对角协方差矩阵,VkD-CMA 使用可变维度,而 LM-CMA 则应用了有限记忆原理。这些方法的实现目前不在本文讨论范围内,但后续文章中可能会再次探讨。

cmaes 算法

图1.CMA-ES 算法示意图

该图展示了算法经过几代演变的过程 — 三个算法状态的快照(第 1 代、第 5 代和第 15 代),展示了种群如何逐渐收敛到最优解。

协方差矩阵的调整 — 蓝色椭圆显示了当代数变化时分布形状的变化: 1-圆形(单位矩阵),5-拉长和旋转,15-根据局部几何形状进行精细调整。
算法组成: 红点代表整个种群(λ 个后代),绿点代表最佳解(μ 个父代),黑点 m 代表种群均值,虚线圆圈代表目标函数的等值线。

关键公式如下,并附有关于幂律修正的说明。该图清晰地展示了算法如何根据待优化函数的形态调整其搜索策略,逐步缩小搜索空间并逼近最优解。

让我们来编写伪代码。

初始化

  1. 设置算法参数:
    • 种群规模(lambda)= 50
    • 父母数量 (mu) = 25
    • 秩-1 更新的学习率(c1)= 0.01
    • 秩-μ 更新的学习率(cμ)= 0.8
    • 步长阻尼 = 0.6
    • 初始步长(sigma)= 0.3
  2. 计算重组权重:
    • 对于每个 μ 个父代,计算其权重为 log(μ + 0.5) - log(i + 1)
    • 将权重归一化,使其总和等于 1。
    • 计算有效选择质量 μ_eff(由重组权重决定的等效规模)
  3. 初始化策略参数:
    • 计算进化路径(cs,cc)的学习率
    • 计算标准正态向量(chiN)的期望范数
    • 确定特征值分解的区间
  4. 初始化数据结构:
    • C 协方差矩阵 = 单位矩阵
    • 特征向量矩阵 B = 单位矩阵
    • 特征值向量 D = 单位向量
    • pc 和 ps 演化路径 = 零向量
    • 总体均值 m = 搜索空间中的随机点
  5. 创建初始种群:
    • 在搜索空间中生成 lambda 随机点

演化的基本循环

重复此过程直至达到停止条件:

第 1 步:产生后代 

  1. 对于每个 lambda 后代:
    • 从标准正态分布生成随机向量 z
    • 应用变换:y = B × D × z
    • 创建后代:x_k = m + σ × y
    • 对 x_k 应用搜索空间约束

第 2 步:评估和更新 

  1. 评估所有后代的适应度
  2. 对种群进行排序:
    • 按适应度降序排列后代
    • 如有必要,更新已找到的最佳解决方案。
  3. 更新种群均值:
    • 存储先前的平均值 m_old
    • 计算新的均值,即 μ 个最佳后代的加权和:m_new = Σ(w_i × x_i),其中 i 从 1 到 μ
  4. 更新进化路径:
    • 计算均值偏移:y_w = (m_new - m_old) / σ
    • 利用特征值分解计算 C^(-1/2) × y_w
    • 更新演化路径,步长为:ps = (1 - cs) × ps + √(cs × (2 - cs) × μ_eff) × C^(-1/2) × y_w
    • 检查停滞条件(Heaviside函数)
    • 更新协方差矩阵的演化路径:pc = (1 - cc) × pc + stagnation_indicator × √(cc × (2 - cc) × μ_eff) × y_w
  5. 更新协方差矩阵:
    • 根据向量 pc 的外积准备秩-1 更新
    • 根据最佳子代偏差的加权和,准备秩-μ 更新
    • 更新 C:C = (1 - c1 - cμ) × C + c1 × (pc × pc^T) + cμ × Σ(w_i × y_i × y_i^T)
    • 确保矩阵对称性
  6. 更新步长:
    • 基于 ps 路径长度计算自适应系数
    • 更新 σ:σ = σ × exp((cs/damps) × (||ps||/chiN - 1))
    • 为了保证数值稳定性,应将 σ 限制在合理的范围内。
  7. 特征值分解(如有必要):
    • 如果自上次分解以来已经经过了足够多的迭代:
      • 对 C = B × D² × B^T 进行雅可比分解
      • 按降序排列特征值和特征向量
      • 确保矩阵的正定性
  8. 检查并修正协方差矩阵:
    • 定期检查 C 正定性
    • 如有必要,在对角线上增加一个较小的数值。
    • 确保矩阵对称性

现在,让我们编写从 C_AO 类派生的 C_AO_CMAES,它将实现 CMA-ES 优化算法。

公有成员与方法:

  • SetParams() - 从以下位置设置 CMA-ES 参数值 参数数组。
  • Init() - 初始化算法。接受每个变量的最小值、最大值和步长,以及轮数。
  • Moving() -实现算法的主循环,负责生成新的解决方案。
  • Revision() - 评估新解决方案并更新算法状态。

私有字段:

  • CMA-ES 参数 - 变量 包含 CMA-ES 算法的具体参数:父代数量 (mu)、步长 (sigma)、学习率 (learningRateC1, learningRateCMu)、阻尼因子(stepSizeDamping)以及其他算法运行所必需的参数。
  • CMA-ES数据结构- 数组用于存储重组权重、covMatrix 协方差矩阵、B 特征向量、D 特征值、进化路径(pc,ps)以及其他辅助数据。mu_eff、counteval、eigeneval、chiN、eigenInterval 值用于控制和管理算法进度。
  • 用于性能优化的变量旨在加快算法运行过程中的计算速度,如学习率和阻尼。
  • 辅助数组 在执行各种操作时,用作向量和矩阵的临时存储。
  • 辅助方法执行 CMA-ES 算法的各个步骤,例如初始化分布、更新分布、计算特征分解、对种群进行排序、计算权重、更新均值、检查正定性以及确保正定性。

总的来说, C_AO_CMAES 该类封装了 CMA-ES 算法的逻辑,提供了初始化、参数调整和执行优化的接口。它包含了实现算法所需的参数、数据数组和方法。公有与私有成员的划分有助于控制对类内部组件的访问。

//————————————————————————————————————————————————————————————————————
class C_AO_CMAES : public C_AO
{
  public: //----------------------------------------------------------
  ~C_AO_CMAES () { }
  C_AO_CMAES ()
  {
    ao_name = "CMAES";
    ao_desc = "Covariance Matrix Adaptation Evolution Strategy";
    ao_link = "https://www.mql5.com/en/articles/18227";

    // Default parameters
    popSize         = 50;          // Default population size (lambda)
    mu              = 25;          // Number of parents (half of the population)
    learningRateC1  = 0.01;        // Learning rate for rank-1 update
    learningRateCMu = 0.8;         // Learning rate for rank-μ update
    stepSizeDamping = 0.6;         // Damping for step size

    // Create and initialize the parameters array
    ArrayResize (params, 5);
    params [0].name = "popSize";         params [0].val = popSize;
    params [1].name = "mu";              params [1].val = mu;
    params [2].name = "learningRateC1";  params [2].val = learningRateC1;
    params [3].name = "learningRateCMu"; params [3].val = learningRateCMu;
    params [4].name = "stepSizeDamping"; params [4].val = stepSizeDamping;
  }

  void SetParams ()
  {
    popSize         = (int)params [0].val;
    mu              = (int)params [1].val;
    learningRateC1  = params      [2].val;
    learningRateCMu = params      [3].val;
    stepSizeDamping = params      [4].val;
  }

  bool Init (const double &rangeMinP  [],  // minimum values
             const double &rangeMaxP  [],  // maximum values
             const double &rangeStepP [],  // step size
             const int     epochsP = 0);

  void Moving   ();
  void Revision ();

  //------------------------------------------------------------------
  private: //---------------------------------------------------------
  // CMA-ES specific parameters
  int mu;                  // Number of parents (selected points)
  double sigma;            // Step size
  double learningRateC1;   // Learning rate for rank-1 update
  double learningRateCMu;  // Learning rate for rank-μ update
  double stepSizeDamping;  // Damping factor for step size update

  // CMA-ES specific data structures
  double weights   [];      // Recombination weights
  double covMatrix [];      // Covariance matrix (stored as a one-dimensional array)
  double B  [];             // C eigenvectors
  double D  [];             // C eigenvalues (square roots)
  double pc [];             // Evolution path for C
  double ps [];             // Evolution path for step-size control
  double mu_eff;            // Effective selection mass
  int    counteval;         // Counter of function evaluations since the last decomposition
  int    eigeneval;         // Generation counter when decomposition was performed
  double chiN;              // Expected norm N(0,I)
  int    eigenInterval;     // Interval for eigenvalue decomposition

  // Variables for performance optimization
  double cs;                // Learning rate for the sigma path
  double cc;                // Learning rate for rank-1 path 
  double damps;             // Damping for sigma
  double hsig_threshold;    // Threshold for the Heaviside function

  // Auxiliary arrays
  double y_vec     [];         // Mutation vector
  double arindex   [];         // Array of indices for sorting
  double arfitness [];         // Fitness array for sorting
  double temp_vec  [];         // Temporary vector for matrix operations
  double invsqrtC_times_yw []; // Temporary storage for C^(-1/2) * y_w

  // Caching variables for Box-Muller
  double cached_normal;
  bool   has_cached;

  // Auxiliary methods
  void   InitDistribution          ();
  void   UpdateDistribution        ();
  void   ComputeEigendecomposition ();
  double GetChiN                   ();
  void   SortPopulation            ();
  void   ComputeWeights            ();
  void   UpdateMean                ();
  bool   CheckPositiveDefinite     ();
  void   EnforcePositiveDefinite   ();
};
//————————————————————————————————————————————————————————————————————

C_AO_CMAES 类的 Init 方法初始化 CMA-ES 算法。它以最小和最大参数值、步长和迭代次数作为输入。首先,调用 StandardInit 方法执行标准初始化。has_cached 标志初始化为 “false”,表示 Box-Muller 随机数生成没有缓存值。接下来,将 sigma 步长初始化为初始值 0.3(搜索范围的 30%)。调用 ComputeWeights 方法来计算重组权重,调用 GetChiN 方法来计算期望范数 N(0, I)。

cs、cc、damps 和 hsig_threshold 参数是根据 mu_eff(有效质量)和坐标数(coords)计算的。这些参数用于在运行过程中调整 CMA-ES 策略。计算并设置 eigenInterval,以确定执行特征值分解的频率。此参数用于优化性能。

内存被分配给 CMA-ES 特有的数组,例如 covMatrix 协方差矩阵、B 特征向量、D 特征值、pc 和 ps 演化路径、y_vec、arindex、arfitness、temp_vec 和 invsqrtC_times_yw 时间向量。仅当需要更改问题维度时,才会重新创建数组。

pc 和 ps 演化路径数组初始化为零,covMatrix 协方差矩阵和 B 特征向量矩阵初始化为单位矩阵,D 特征值数组初始化为 1。调用 InitDistribution 方法来初始化初始分布。然后重置 counteval 和 eigeneval 函数的评估计数器。“revision” 标志设置为 “true” 表示应重新计算适应度值。“true” 表示初始化成功。

总之,Init 方法执行 CMA-ES 算法工作所需的所有初始化操作,包括设置参数、为数组分配内存和初始化初始值。 

//————————————————————————————————————————————————————————————————————
bool C_AO_CMAES::Init (const double &rangeMinP [],  // minimum values
                       const double &rangeMaxP [],  // maximum values
                       const double &rangeStepP [], // step size
                       const int     epochsP = 0)   // number of epochs
{
  if (!StandardInit (rangeMinP, rangeMaxP, rangeStepP)) return false;

  //------------------------------------------------------------------
  // Initialize Box-Muller caching
  has_cached = false;

  // Initialize CMA-ES specific variables
  sigma          = 0.3;  // Initial step size (30% of search range)

  // Calculate the effective mass of the variance selection
  ComputeWeights ();

  // Expected norm N(0,I)
  chiN = GetChiN ();

  // Calculate and save strategy parameters
  cs = (mu_eff + 2.0) / (coords + mu_eff + 5.0);
  cc = (4.0 + mu_eff / coords) / (coords + 4.0 + 2.0 * mu_eff / coords);
  damps = 1.0 + 2.0 * MathMax (0.0, MathSqrt ((mu_eff - 1.0) / (coords + 1.0)) - 1.0) + cs;
  hsig_threshold = 1.4 + 2.0 / (coords + 1.0);

  // Set the eigenvalue decomposition interval - tuning for performance
  eigenInterval = (int)(coords / (10.0 * MathSqrt (learningRateC1 + learningRateCMu)));
  eigenInterval = MathMax (1, eigenInterval);

  // Allocate arrays only once
  ArrayResize (covMatrix, coords * coords);
  ArrayResize (B, coords * coords);
  ArrayResize (D, coords);
  ArrayResize (pc, coords);
  ArrayResize (ps, coords);
  ArrayResize (y_vec, coords);
  ArrayResize (arindex, popSize);
  ArrayResize (arfitness, popSize);
  ArrayResize (temp_vec, coords);
  ArrayResize (invsqrtC_times_yw, coords);

  // Initialize evolution paths with zeros
  ArrayInitialize (pc, 0);
  ArrayInitialize (ps, 0);

  // Fast initialization of the identity covariance matrix and decomposition
  ArrayInitialize (covMatrix, 0.0);
  ArrayInitialize (B, 0.0);

  for (int i = 0; i < coords; i++)
  {
    covMatrix [i * coords + i] = 1.0;
    B [i * coords + i] = 1.0;
    D [i] = 1.0;
  }

  // Initialize initial distribution
  InitDistribution ();

  // Reset calculation counters
  counteval = 0;
  eigeneval = 0;

  // Forced fitness recalculation
  revision = true;

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

C_AO_CMAES 类中的 Moving 方法负责在种群中生成新的后代(解)。 变换 y = B * D * z:在遍历种群的循环中,有一个嵌套循环,该循环遍历每个个体的所有坐标。计算每个坐标 i 处的 y_vec [i] 值。这是通过矩阵乘法实现的。实际上,它是将 z 个随机数的向量乘以矩阵 “B * D”,其中 B 是协方差矩阵的特征向量,D 是协方差矩阵的特征值,z 是使用 u.PowerDistribution 函数生成的随机数。 

后代生成:计算每个 k 个后代的每个 i 坐标的 (a [k].c [i]) 值。这是通过将缩放向量 y_vec[i] 添加到当前均值 cB[i](当前分布的中心)来实现的。缩放是通过将 y_vec[i] 乘以 'sigma' 步长来实现的。因此,a[k].c[i] = cB[i] + sigma * y_vec[i]。

u.SeInDiSp 函数确保子代的坐标值保持在 rangeMin[i]、rangeMax[i] 和 rangeStep[i] 定义的有效范围内。它可以对值进行裁剪,使其在边界处反弹,并将其量化到最接近的有效步长值。生成所有后代后,“revision” 标志设置为 “true”,并且应该重新计算生成的后代的适应度值。

因此,该方法 根据当前分布生成新的解集( cB 平均值和协方差矩阵,表示为B  D) 和 sigma 步长。它使用随机数在当前均值附近产生微小变化,以探索搜索空间。这 SeInDiSp 函数确保新解保持在允许的范围内,以及 “revision” 标志。 以便随后重新评估这些新解的适应度。

//————————————————————————————————————————————————————————————————————
void C_AO_CMAES::Moving ()
{
  // Generate new lambda descendants
  for (int k = 0; k < popSize; k++)
  {

    // Apply the transformation y = B*D*z
    for (int i = 0; i < coords; i++)
    {
      y_vec [i] = 0.0;
      for (int j = 0; j < coords; j++)
      {
        y_vec [i] += B [i * coords + j] * D [j] * u.PowerDistribution (0.0, -8, 8, 20);
      }

      // Generate a descendant: x_k = m + σ * y
      a [k].c [i] = cB [i] + sigma * y_vec [i];
      a [k].c [i] = u.SeInDiSp (a [k].c [i], rangeMin [i], rangeMax [i], rangeStep [i]);
    }
  }

  // Fitness recalculation mark
  revision = true;
}
//————————————————————————————————————————————————————————————————————

C_AO_CMAES 类的 Revision 方法负责在评估新种群的适应度函数后更新算法参数。首先,该方法检查 “revision” 标志的值。然后,调用 SortPopulation() 方法,对当前种群进行排序,使最好的个体位于顶部。UpdateDistribution() 方法根据种群中最佳个体的信息来更新 CMA-ES 分布的参数(策略向量的 cB 均值、协方差矩阵、sigma 步长、进化路径)。分布更新是 CMA-ES 的一个关键步骤,因为它允许算法根据获得的结果调整其搜索策略。 

因此,Revision 方法代表了 CMA-ES 的主要适应周期。它对种群进行排序,根据最优个体更新分布参数,并增加计算计数器的数值。 

//————————————————————————————————————————————————————————————————————
void C_AO_CMAES::Revision ()
{
  if (!revision) return;

  revision = false;

  // Sort the population by fitness
  SortPopulation ();

  // Update distribution parameters based on selected individuals
  UpdateDistribution ();

  // Update the calculation counter
  counteval++;
}
//————————————————————————————————————————————————————————————————————

C_AO_CMAES 类的 InitDistribution 方法旨在初始化解决方案搜索的初始分布。该方法为搜索空间中的每个坐标确定一个初始均值(cB)。对于每个坐标 i,使用 u.RNDfromCI() 在 rangeMin[i] 和 rangeMax[i] 之间随机设置 cB[i] 的值。外层循环遍历整个种群(popSize 个个体)。在这个循环内部,有一个内循环,用于遍历每个个体的所有坐标。为每个后代生成坐标。首先,它们从给定的范围 RNDfromCI 中随机选择,然后使用 SeInDiSp 四舍五入到采样步长。

u.RNDfromCI 函数生成给定区间内均匀分布的随机数。u.SeInDiSp 函数确保每个个体的生成坐标都位于由定义的可接受范围内。 rangeMin[i]、rangeMax[i] 和 rangeStep [i].

该方法使用在搜索空间中均匀分布的随机解来初始化初始种群。分布中心(cB)也是随机选择的。这为 CMA-ES 的后续迭代提供了一个起点,算法将调整搜索策略以找到最优解。

//+------------------------------------------------------------------+
//| Initialize search distribution                                   |
//+------------------------------------------------------------------+
void C_AO_CMAES::InitDistribution ()
{
  // Set the initial mean to the center of the search space
  for (int i = 0; i < coords; i++)
  {
    cB [i] = u.RNDfromCI (rangeMin [i], rangeMax [i]);
  }

  for (int k = 0; k < popSize; k++)
  {
    for (int i = 0; i < coords; i++)
    {
      // Generate a uniformly distributed point
      a [k].c [i] = u.RNDfromCI (rangeMin [i], rangeMax [i]);
      a [k].c [i] = u.SeInDiSp (a [k].c [i], rangeMin [i], rangeMax [i], rangeStep [i]);
    }
  }
}
//————————————————————————————————————————————————————————————————————

GetChiN 方法计算从 N (0,I) 中抽取的随机向量的期望范数,给定坐标数 ( coords )。 。结果根据搜索空间的维度进行近似计算。

//+------------------------------------------------------------------+
//| Calculate the expected norm N(0,I)                               |
//+------------------------------------------------------------------+
double C_AO_CMAES::GetChiN ()
{
  double n = (double)coords;
  return MathSqrt (n) * (1.0 - 1.0 / (4.0 * n) + 1.0 / (21.0 * n * n));
}
//————————————————————————————————————————————————————————————————————

SortPopulation  旨在根据目标函数值(适应度)对一组解决方案进行排序,以确定最佳解决方案。首先,该方法将种群中每个个体的适应度值复制到 arfitness 辅助数组中。同时创建了 arindex 数组,该数组最初包含个体的索引。这是必要的,以便在按适应度排序后,能够恢复排序后的适应度与种群中原始个体之间的对应关系。

采用插入排序算法。它遍历 arfitness 数组,对于每个元素,将其插入到数组已排序部分的正确位置,并将较大的元素向右移动。值得注意的是,排序是按降序进行的(在 “while” 循环中 arfitness [j] < tempFitness),目标是最大化目标函数(fitness)。与 arfitness 一起,arindex 也同步排序,以跟踪个体原始索引的位置。

排序后,该方法检查最佳个体的适应度是否优于当前最佳解,如果是,则更新 fB,并将最佳解替换为种群中最佳个体的坐标。因此,该方法不仅根据适应度对种群进行排序,还能记录当前找到的最佳解。

//+------------------------------------------------------------------+
//| Sort the population by fitness                                   |
//+------------------------------------------------------------------+
void C_AO_CMAES::SortPopulation ()
{
  // Copy fitness values and indices
  for (int i = 0; i < popSize; i++)
  {
    arindex [i] = i;
    arfitness [i] = a [i].f;
  }

  for (int i = 1; i < popSize; i++)
  {
    double tempFitness = arfitness [i];
    double tempIndex = arindex [i];
    int j = i - 1;

    // Sort in descending order (for maximization)
    while (j >= 0 && arfitness [j] < tempFitness)
    {
      arfitness [j + 1] = arfitness [j];
      arindex [j + 1] = arindex [j];
      j--;
    }

    arfitness [j + 1] = tempFitness;
    arindex [j + 1] = tempIndex;
  }

  // Update the best solution if necessary
  if (arfitness [0] > fB)
  {
    fB = arfitness [0];
    int bestIdx = (int)arindex [0];
    ArrayCopy (cB, a [bestIdx].c, 0, 0, coords);
  }
}
//————————————————————————————————————————————————————————————————————

UpdateMean 方法通过加权重组最优个体来更新群体均值(cB)。对于每个坐标,计算一个新的平均值,该平均值是来自最优个体的加权坐标值之和,其中权重在 weights 数组中指定。

//+------------------------------------------------------------------+
//| Update the mean using weighted recombination                     |
//+------------------------------------------------------------------+
void C_AO_CMAES::UpdateMean ()
{
  // Weighted recombination: m^(g+1) = Σ w_i * x_{i:λ}^(g+1)
  for (int j = 0; j < coords; j++)
  {
    double meanSum = 0.0;
    for (int i = 0; i < mu; i++)
    {
      int idx = (int)arindex [i];
      meanSum += weights [i] * a [idx].c [j];
    }
    cB [j] = meanSum;
  }
}
//————————————————————————————————————————————————————————————————————

ComputeWeights 方法用于计算加权重组的权重,按大小 mu 分配权重数组,并计算 log(mu+0.5) 以用于权重公式。对于从 0 到 mu-1 的每个 i,它将 log(mu + 0.5) 与 log(i+1) 之间的差值赋给一个权重,将这些权重相加,该方法将所有权重除以该和,使和等于 1,然后计算权重的平方和,计算 mu_eff — 使用权重的效率,这对于调整算法的速度和方差非常重要。

该方法考虑了最佳解的贡献,为重组提供了最优权重。

//+------------------------------------------------------------------+
//| Calculate weighted recombination weights                         |
//+------------------------------------------------------------------+
void C_AO_CMAES::ComputeWeights ()
{
  // Allocate the array of weights
  ArrayResize (weights, mu);

  // Pre-calculate log(mu + 0.5)
  double log_mu_plus_half = MathLog (mu + 0.5);

  // Calculate positive weights
  double sum = 0.0;
  for (int i = 0; i < mu; i++)
  {
    weights [i] = log_mu_plus_half - MathLog (i + 1);
    sum += weights [i];
  }

  // Normalize weights
  double sum_weights = 0.0;
  double sum_squares = 0.0;
  for (int i = 0; i < mu; i++)
  {
    weights [i] /= sum;
    sum_weights += weights [i];
    sum_squares += weights [i] * weights [i];
  }

  // Calculate the effective mass of the variance selection
  mu_eff = sum_weights * sum_weights / sum_squares;
}
//————————————————————————————————————————————————————————————————————

UpdateDistribution 方法更新分布参数,即 CMA-ES 算法中的协方差矩阵(covMatrix)和步长(sigma)。如果经过足够多的世代(counteval - eigeneval > eigenInterval),则计算特征值分解,并将当前均值(cB)存储在 oldMean 临时数组中。该方法调用 UpdateMean 来计算新的均值,计算新均值与旧均值之差除以 sigma,并将 y_w 乘以 C^(-1/2),使用 B 和 D 分解将其分解为几个步骤,并将结果存储在 invsqrtC_times_yw 中。

然后,该方法使用 invsqrtC_times_yw 更新 ps 步长的演化路径,根据 ps 范数和预期长度确定进展是否被认为是“停滞的”,然后使用 y_w 和 hsig 进展指标更新 pc 协方差矩阵的演化路径。该方法首先计算 c1a 秩一更新矩阵,然后基于个体与旧均值之差除以 sigma,使用 “weights” 计算 cmu 秩μ更新矩阵,使用秩一和秩 μ更新来更新 covMatrix 协方差矩阵,并在进展停滞时调整 c1。定期调用 EnforcePositiveDefinite 函数,以确保协方差矩阵为正定矩阵。sigma 步长根据 ps 范数进行更新,并且为了数值稳定性,其值被限制在 min_sigma 和 max_sigma 之间。

一般来说, UpdateDistribution 根据搜索历史(进化路径)和种群数据调整分布参数(协方差矩阵和步长),以适应目标函数的地形。

//+------------------------------------------------------------------+ 
//| Update distribution parameters                                   |
//+------------------------------------------------------------------+
void C_AO_CMAES::UpdateDistribution ()
{
  // Check the necessity of eigenvalue decomposition
  if (counteval - eigeneval > eigenInterval)
  {
    ComputeEigendecomposition ();
    eigeneval = counteval;
  }

  // Preserve the old average
  double oldMean [];
  ArrayResize (oldMean, coords);
  ArrayCopy (oldMean, cB, 0, 0, coords);

  // Update average
  UpdateMean ();

  // Calculate the mean shift
  double y_w [];
  ArrayResize (y_w, coords);
  for (int j = 0; j < coords; j++)
  {
    y_w [j] = (cB [j] - oldMean [j]) / sigma;
  }

  // Calculate C^(-1/2) * y_w
  // Step 1: B^T * y_w
  ArrayInitialize (temp_vec, 0.0);
  for (int i = 0; i < coords; i++)
  {
    for (int j = 0; j < coords; j++)
    {
      temp_vec [i] += B [j * coords + i] * y_w [j];
    }
  }

  // Step 2: D^(-1) * (B^T * y_w)
  for (int i = 0; i < coords; i++)
  {
    temp_vec [i] /= D [i];
  }

  // Step 3: B * D^(-1) * B^T * y_w
  ArrayInitialize (invsqrtC_times_yw, 0.0);
  for (int i = 0; i < coords; i++)
  {
    for (int j = 0; j < coords; j++)
    {
      invsqrtC_times_yw [i] += B [i * coords + j] * temp_vec [j];
    }
  }

  // Update the evolution path for sigma
  double norm_ps_squared = 0.0;
  for (int i = 0; i < coords; i++)
  {
    ps [i] = (1.0 - cs) * ps [i] + MathSqrt (cs * (2.0 - cs) * mu_eff) * invsqrtC_times_yw [i];
    norm_ps_squared += ps [i] * ps [i];
  }

  // Heaviside function
  double norm_ps = MathSqrt (norm_ps_squared);
  double expected_length = MathSqrt (1.0 - MathPow (1.0 - cs, 2.0 * counteval)) * chiN;
  bool hsig = norm_ps / expected_length < hsig_threshold;

  // Update the evolution path for C
  double delta_hsig = hsig ? 1.0 : 0.0;
  for (int i = 0; i < coords; i++)
  {
    pc [i] = (1.0 - cc) * pc [i] + delta_hsig * MathSqrt (cc * (2.0 - cc) * mu_eff) * y_w [i];
  }

  // Prepare rank-1 update
  double c1a [];
  ArrayResize (c1a, coords * coords);
  for (int i = 0; i < coords; i++)
  {
    for (int j = 0; j <= i; j++)
    {
      c1a [i * coords + j] = c1a [j * coords + i] = pc [i] * pc [j];
    }
  }

  // Prepare rank-μ update
  double cmu [];
  ArrayResize (cmu, coords * coords);
  ArrayInitialize (cmu, 0.0);

  for (int k = 0; k < mu; k++)
  {
    int idx = (int)arindex [k];

    // Calculate y_i = (x_i - m_old) / sigma
    for (int i = 0; i < coords; i++)
    {
      temp_vec [i] = (a [idx].c [i] - oldMean [i]) / sigma;
    }

    // Add the weighted outer product
    for (int i = 0; i < coords; i++)
    {
      for (int j = 0; j <= i; j++)
      {
        double update = weights [k] * temp_vec [i] * temp_vec [j];
        cmu [i * coords + j] += update;
        if (i != j) cmu [j * coords + i] += update;
      }
    }
  }

  // Update C the covariance matrix
  double c1 = learningRateC1;
  double cmu_rate = learningRateCMu;

  // Adjust c1 if hsig is 'false' (progress stalled)
  if (!hsig)
  {
    c1 *= (1.0 - (1.0 - delta_hsig) * cc * (2.0 - cc));
  }

  double one_minus_c1_cmu = 1.0 - c1 - cmu_rate;

  // Update C with rank-1 and rank-μ updates
  for (int i = 0; i < coords; i++)
  {
    for (int j = 0; j <= i; j++)
    {
      covMatrix [i * coords + j] = one_minus_c1_cmu * covMatrix [i * coords + j] +
                                   c1 * c1a [i * coords + j] +
                                   cmu_rate * cmu [i * coords + j];

      // Maintain symmetry
      if (i != j)
      {
        covMatrix [j * coords + i] = covMatrix [i * coords + j];
      }
    }
  }

  // Ensure positive definiteness
  if (counteval % (10 * eigenInterval) == 0)
  {
    EnforcePositiveDefinite ();
  }

  //  Update the sigma step size
  double exponent = (cs / damps) * (norm_ps / chiN - 1.0);
  sigma *= MathExp (exponent);

  // Limit sigma for numerical stability
  double min_sigma = 1e-16;
  double max_eigenvalue = D [0]; // D sorted in descending order
  double max_sigma = 1e4 * MathMax (1.0, MathSqrt (max_eigenvalue));

  if (sigma < min_sigma) sigma = min_sigma;
  else
    if (sigma > max_sigma) sigma = max_sigma;
}
//————————————————————————————————————————————————————————————————————

ComputeEigendecomposition 执行分解 使用改进的雅可比方法将 covMatrix 对称协方差分解为特征值和特征向量,该方法创建 covMatrix 的副本,因此不会修改原始数据。最初,B 被设置为单位矩阵,代表初始基。它会执行最大迭代次数,或者直到非对角线上的非零元素小于 “tolerance” 为止。 

求最大非对角线元素:找出对角线以外绝对值最大的元素,选择该元素进行旋转。接下来,如果最大值小于 “tolerance”,则中断循环。该方法计算 phi 以执行雅可比旋转,通过应用旋转来修改 C_copy 的元素,并根据旋转更新 B 矩阵的列(特征向量)。迭代结束后,该方法求出对角元素的平方根(确保最小正性为 1e-14),并将特征值和相应的特征向量按降序排列,以供后续使用。因此,该方法能够准确计算协方差矩阵的特征值和特征向量。

//+------------------------------------------------------------------+
//| Calculate the eigenvalue decomposition using the Jacobi method   |
//+------------------------------------------------------------------+
void C_AO_CMAES::ComputeEigendecomposition ()
{
  // Create a copy of the covariance matrix for decomposition
  double C_copy [];
  ArrayResize (C_copy, coords * coords);
  ArrayCopy (C_copy, covMatrix);

  // Initialize B as the identity matrix
  for (int i = 0; i < coords; i++)
  {
    for (int j = 0; j < coords; j++)
    {
      B [i * coords + j] = (i == j) ? 1.0 : 0.0;
    }
  }

  // Improved Jacobi eigenvalue decomposition
  int max_iterations = 10; //50 * coords;
  double tolerance   = 0.01; //1e-14 * coords * coords;

  for (int iter = 0; iter < max_iterations; iter++)
  {
    // Find the largest off-diagonal element
    double max_val = 0.0;
    int p = 0, q = 1;

    for (int i = 0; i < coords - 1; i++)
    {
      for (int j = i + 1; j < coords; j++)
      {
        double val = MathAbs (C_copy [i * coords + j]);
        if (val > max_val)
        {
          max_val = val;
          p = i;
          q = j;
        }
      }
    }

    // Check convergence
    if (max_val < tolerance) break;

    // Calculate the rotation angle
    double app = C_copy [p * coords + p];
    double aqq = C_copy [q * coords + q];
    double apq = C_copy [p * coords + q];

    double phi = 0.5 * MathArctan (2.0 * apq / (aqq - app + 1e-14));
    double c = MathCos (phi);
    double s = MathSin (phi);

    // Update the matrix elements
    double app_new = c * c * app - 2 * c * s * apq + s * s * aqq;
    double aqq_new = s * s * app + 2 * c * s * apq + c * c * aqq;

    C_copy [p * coords + p] = app_new;
    C_copy [q * coords + q] = aqq_new;
    C_copy [p * coords + q] = C_copy [q * coords + p] = 0.0;

    // Update other elements in p and q rows/columns
    for (int i = 0; i < coords; i++)
    {
      if (i != p && i != q)
      {
        double aip = C_copy [i * coords + p];
        double aiq = C_copy [i * coords + q];
        C_copy [i * coords + p] = C_copy [p * coords + i] = c * aip - s * aiq;
        C_copy [i * coords + q] = C_copy [q * coords + i] = s * aip + c * aiq;
      }
    }

    // Update eigenvectors
    for (int i = 0; i < coords; i++)
    {
      double bip = B [i * coords + p];
      double biq = B [i * coords + q];
      B [i * coords + p] = c * bip - s * biq;
      B [i * coords + q] = s * bip + c * biq;
    }
  }

  // Extract eigenvalues and ensure positivity
  double min_eigenvalue = 1e-14;
  for (int i = 0; i < coords; i++)
  {
    D [i] = MathSqrt (MathMax (min_eigenvalue, C_copy [i * coords + i]));
  }

  // Sort eigenvalues and vectors in descending order
  for (int i = 0; i < coords - 1; i++)
  {
    int max_idx = i;
    for (int j = i + 1; j < coords; j++)
    {
      if (D [j] > D [max_idx]) max_idx = j;
    }

    if (max_idx != i)
    {
      // Exchange eigenvalues
      double temp = D [i];
      D [i] = D [max_idx];
      D [max_idx] = temp;

      // Exchange eigenvectors
      for (int k = 0; k < coords; k++)
      {
        temp = B [k * coords + i];
        B [k * coords + i] = B [k * coords + max_idx];
        B [k * coords + max_idx] = temp;
      }
    }
  }
}
//————————————————————————————————————————————————————————————————————

CheckPositiveDefinite  方法检查协方差矩阵是否为正定矩阵。它快速检查对角线元素的正性,并比较最小特征值(来自 D 数组按降序排列)包含一个较小的正数 1e-14 。如果两个检查都通过,则该方法返回  'true'。

//+------------------------------------------------------------------+
//| Check the positive definiteness of the covariance matrix         |
//+------------------------------------------------------------------+
bool C_AO_CMAES::CheckPositiveDefinite ()
{
  // Quick check: all diagonal elements should be positive
  for (int i = 0; i < coords; i++)
  {
    if (covMatrix [i * coords + i] <= 0) return false;
  }

  // Check if the minimum eigenvalue is positive
  double min_eigenvalue = D [coords - 1]; // D is sorted in descending order
  return min_eigenvalue > 1e-14;
}
//————————————————————————————————————————————————————————————————————

EnforcePositiveDefinite 方法确保 covMatrix 协方差矩阵的正定性。它执行以下几个步骤:找到对角线上的最小元素,并将缺失的“校正”值添加到对角线元素,使最小值等于 1e-10;通过对每个非对角线元素与其转置求平均值来最大化矩阵的对称性。

如果矩阵仍然不是正定的,则使用 ComputeEigendecomposition 进行特征值分解,将所有小于 √1e-10 的特征值替换为 √1e-10,以确保正定性。然后,该方法使用 B 特征向量和修正后的 D 特征值重新计算 covMatrix,以获得修正后的正定矩阵。这种方法确保协方差矩阵变为正定矩阵,适合在 CMA-ES 算法中进行进一步计算。

//+------------------------------------------------------------------+
//| Ensuring positive definiteness of the covariance matrix          |
//+------------------------------------------------------------------+
void C_AO_CMAES::EnforcePositiveDefinite ()
{
  // Method 1: Adding a small value to the diagonal
  double min_diag = 1e308; // A very large number
  for (int i = 0; i < coords; i++)
  {
    if (covMatrix [i * coords + i] < min_diag)
    {
      min_diag = covMatrix [i * coords + i];
    }
  }

  if (min_diag < 1e-10)
  {
    double correction = 1e-10 - min_diag;
    for (int i = 0; i < coords; i++)
    {
      covMatrix [i * coords + i] += correction;
    }
  }

  // Method 2: Ensuring symmetry
  for (int i = 0; i < coords; i++)
  {
    for (int j = i + 1; j < coords; j++)
    {
      double avg = (covMatrix [i * coords + j] + covMatrix [j * coords + i]) * 0.5;
      covMatrix [i * coords + j] = covMatrix [j * coords + i] = avg;
    }
  }

  // If still not positive definite, perform the factorization and fix
  if (!CheckPositiveDefinite ())
  {
    ComputeEigendecomposition ();

    double min_eigenvalue = 1e-10;
    for (int i = 0; i < coords; i++)
    {
      if (D [i] < MathSqrt (min_eigenvalue))
      {
        D [i] = MathSqrt (min_eigenvalue);
      }
    }

    // Reconstruct C = B * D^2 * B^T
    ArrayInitialize (covMatrix, 0.0);
    for (int i = 0; i < coords; i++)
    {
      for (int j = 0; j <= i; j++)
      {
        double sum = 0.0;
        for (int k = 0; k < coords; k++)
        {
          sum += B [i * coords + k] * D [k] * D [k] * B [j * coords + k];
        }
        covMatrix [i * coords + j] = covMatrix [j * coords + i] = sum;
      }
    }
  }
}
//————————————————————————————————————————————————————————————————————


测试结果

现在我们终于可以查看结果了。显而易见,该算法能够很好地快速处理中等规模的问题,并且在某些设置下,也能处理低维问题,但最困难的多维问题执行起来需要不成比例的时间,因此实际上被排除在结果之外。为什么会发生这种情况?传统上,MQL5 中的矩阵计算是一个严重的计算问题。在手动实现矩阵运算时,该算法在高维问题上的表现不尽如人意,这极大地限制了其在实际中的应用。然而,随着内置矩阵处理类的出现,情况发生了巨大变化。现在,对于所有计算密集型任务,使用平台自带的矩阵运算实现至关重要。

CMAES|Covariance Matrix Adaptation Evolution Strategy|50.0|25.0|0.01|0.8|1.0|
=============================
5 Hilly's; Func runs:10000; result:0.7625797883550075
25 Hilly's;Func runs:10000; result:0.7208874560706138
500 Hilly's; Func runs:10000; result:0.0
=============================
5 Forest's; Func runs:10000; result:0.8205636421348295
25 Forest's; Func runs:10000; result:0.7961602627346933
500 Forest's; Func runs:10000; result:0.0
=============================
5 Megacity's; Func runs:10000; result:0.7584615384615383
25 Megacity's; Func runs:10000; result:0.49076923076923074
500 Megacity's;Func runs:10000; result:0.0
=============================
All score:4.34942 (48.33%)

在可视化结果中,我们可以看到该算法既能处理“大幅跳跃”,又能聚焦于函数的极值区域,并对其进行深入探索。

Hilly

Hilly测试函数上的 CMA-ES

​Forest

Forest 测试函数上的 CMA-ES

Megacity

Megacity 测试函数上的 CMA-ES

根据结果,CMA-ES 算法在最强大的基于种群的优化算法排名中位列第 38。

#
AO
描述
Hilly
Hilly
Final
Forest
Forest
Final
Megacity (discrete)
Megacity
Final
Final
结果
% 的
最大限度
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)
1 ANS 跨邻域搜索 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 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 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
22 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
23 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
24 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
25 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
26 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
27 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
28 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
29 (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
30 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
31 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
32 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
33 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
34 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
35 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
36 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
37 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
38 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
39 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
40 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
41 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
42 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
43 ADAMm 自适应矩估计 M 0.88635 0.44766 0.26613 1.60014 0.84497 0.38493 0.16889 1.39880 0.66154 0.27046 0.10594 1.03794 4.037 44.85
44 CGO 混沌游戏优化 0.57256 0.37158 0.32018 1.26432 0.61176 0.61931 0.62161 1.85267 0.37538 0.21923 0.19028 0.78490 3.902 43.35
45 CROm 珊瑚礁优化 M 0.78512 0.46032 0.25958 1.50502 0.86688 0.35297 0.16267 1.38252 0.63231 0.26738 0.10734 1.00703 3.895 43.27
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


总结

CMA-ES 能够根据目标函数的局部几何特性调整搜索策略,这一能力使其在处理具有未知结构的复杂、病态问题时几乎不可或缺。幂律分布的重尾使得该算法能够进行“长跳跃”,这对于逃离局部最优解至关重要。该算法的计算复杂度为 O(n²) 内存和 O(n³) 时间,严重限制了其适用性。对于高维问题(n > 100),资源投入强度与所获得的收益不成比例。多维函数的运行时间增长如此之快,以至于对于较大的 n 值,该算法几乎无法应用。

CMA-ES 适用于噪声函数,能够处理不连续的目标函数,在多极值景观上表现有效,而这种多样性的秘诀在于该算法的基本理念:它不对函数结构做出假设,而是对其进行细致探索,并根据积累的经验调整其搜索策略。CMA-ES 重新定义了进化计算中的一类方法,它表明生物启发式算法可以具有严格的数学基础,并且算法适应不仅仅是参数调整,而是学习问题结构的基本原则。

该算法对于中等规模的问题而言非常出色,它是一款专业工具,在其细分领域中看起来是一个值得考虑的替代方案。该算法兼具数学上的优雅与实际中的高效,但由于其计算上的局限性,使用时需谨慎。在这种情况下,MQL5 语言开发人员最近引入了快速的 MQL5 矩阵操作来计算矩阵;在此算法实现中,使用了传统的顺序计算。

tab

图2.根据相应测试的算法颜色分级

图表​

图 3.算法测试结果直方图(范围从 0 到 100,越高越好,其中 100 是可能的最大理论结果,存档中有一个用于计算评级表的脚本)

CMAES 的优点和缺点:

优点:

  1. 对中等维度的函数具有良好的收敛性。

缺点:

  1. 低维函数的结果分散。
  2. 大尺度函数的关键资源密集度。

这篇文章附有一个归档,其中包含当前版本的算法代码。文章作者不对规范算法描述的绝对准确性负责。为提高搜索能力,已对其中多项功能进行了修改。文章中提出的结论和判断都是基于实验结果。


本文中用到的程序

# 名称 类型 描述
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_CMAES.mq5
脚本 CMAES 测试平台

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

附加的文件 |
CMAES.zip (230.9 KB)
MQL5中的自优化智能交易系统(第十部分):矩阵分解 MQL5中的自优化智能交易系统(第十部分):矩阵分解
分解是一种用于挖掘数据属性规律的数学方法。当我们将分解应用于以行列形式组织的海量市场数据时,能够从中挖掘市场的模式与特征。分解是一种强大的工具,本文将展示如何在MetaTrader 5终端中通过MQL5 API使用该方法,从而更深入地洞察市场数据。
无需真实期权的期权交易(第二部分):实盘中的应用 无需真实期权的期权交易(第二部分):实盘中的应用
本文探讨了简单的期权策略及其在MQL5中的实现。我们将开发一个基础 EA,并逐步对其进行升级,使其变得更加复杂。
在 MQL5 中实现保本机制(第 2 部分):基于 ATR 和风险回报比的保本机制 在 MQL5 中实现保本机制(第 2 部分):基于 ATR 和风险回报比的保本机制
本文将完成 MQL5 中基于 ATR 和风险回报比(RRR)的保本机制实现,并从零开始开发一个类,使得在无需重新输入参数的情况下即可轻松切换保本模式。为评估每种保本类型的有效性,我们将进行多次回测,并在算法交易的背景下分析它们各自的优缺点。
利用机器学习研究分形市场结构导论 利用机器学习研究分形市场结构导论
本文尝试从自相似分形结构的角度来研究金融时间序列。鉴于我们有众多类比证实市场报价可作为自相似分形来考虑,这使我们能够思考此类结构的预测视野。