人工协作搜索算法 (ACS)·进阶篇
📘

人工协作搜索算法 (ACS)·进阶篇

第 2/2 篇

「ACS算法里的种群与相位初始化」

这段实现把 ACS(人工猫群)优化器的内存布局先铺开:A、B、Predator、Prey、M 五个结构数组都按 popSize 扩容,后续逐个体调 Init(coords) 把坐标维度撑起来。注意 YA/YB/Ypred 三个适应度数组用 ArrayInitialize 填了 -DBL_MAX,等于先给「还没算过」标个底,避免脏数据混进选优。 初始化坐标时只给 A 和 B 填了随机值:每个维度 j 在 [rangeMin[j], rangeMax[j]] 内乘 u.RNDprobab() 撒点,Prey 和 Predator 此时还没动,留到 Moving() 里再拷贝。phase 置 0 后返回 true,代表种群冷启动完成。 Moving() 用 phase 当状态机:phase 0 把 A 拷进工作数组 a 并自增;phase 1 把 a 的适应度存 YA、再换 B 进 a;phase 2 存 YB。之后以 50% 概率(u.RNDprobab() < 0.5)选 A 或 B 的群体当 Predator,Key 记 1 或 2——这套切换直接决定下一轮捕食者朝哪边追。外汇与贵金属参数寻优用这类群体算法波动大,实盘前务必在 MT5 策略测试器跑多 seed 验证。

MQL5 / C++
ArrayResize(A,         popSize);
ArrayResize(B,         popSize);
ArrayResize(Predator,  popSize);
ArrayResize(Prey,      popSize);
ArrayResize(M,         popSize);
for (class="type">int i = class="num">0; i < popSize; i++)
{
   A       [i].Init(coords);
   B       [i].Init(coords);
   Predator[i].Init(coords);
   Prey    [i].Init(coords);
   M       [i].Init(coords);
}
ArrayResize(YA,    popSize);
ArrayResize(YB,    popSize);
ArrayResize(Ypred, popSize);
ArrayInitialize(YA,    -DBL_MAX);
ArrayInitialize(YB,    -DBL_MAX);
ArrayInitialize(Ypred, -DBL_MAX);
class=class="str">"cmt">// Initialization
for (class="type">int i = class="num">0; i < popSize; i++)
{
   for (class="type">int j = class="num">0; j < coords; j++)
   {
      A [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab();
      B [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab();
   }
}
phase = class="num">0;
class="kw">return true;
}
class="type">void C_AO_ACS::Moving()
{
   if (phase == class="num">0)
   {
      for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, A [i].c);
      phase++;
      class="kw">return;
   }
   if (phase == class="num">1)
   {
      for (class="type">int i = class="num">0; i < popSize; i++) YA [i] = a [i].f;
      for (class="type">int i = class="num">0; i < popSize; i++) ArrayCopy(a [i].c, B [i].c);
      phase++;
      class="kw">return;
   }
   if (phase == class="num">2)
   {
      for (class="type">int i = class="num">0; i < popSize; i++) YB [i] = a [i].f;
      phase++;
   }
   class=class="str">"cmt">// Selection
   if (u.RNDprobab() < class="num">0.5)
   {
      for (class="type">int i = class="num">0; i < popSize; i++)
      {
         ArrayCopy(Predator [i].c, A [i].c);
      }
      ArrayCopy(Ypred, YA);
      Key = class="num">1;
   }
   else
   {
      for (class="type">int i = class="num">0; i < popSize; i++)
      {
         ArrayCopy(Predator [i].c, B [i].c);
      }
      ArrayCopy(Ypred, YB);
      Key = class="num">2;
   }
   if (u.RNDprobab() < class="num">0.5)
   {

捕食者-猎物混合算子的矩阵实现

这段逻辑处在群体智能寻优的内层迭代里:先按条件把 A 或 B 群体复制进 Prey,再对每只猎物做 ArrayShuffle 打乱坐标顺序,避免后续交叉陷入固定维度配对。 随机系数 R 的取法分两路:当均匀分布随机数小于 0.5 时,R = 4 * rand * CI(-1,1),否则走 R = 1 / exp(4 * MathRand()/32767),后者把整数随机量归一进 (0,1] 区间,两种路径都会显著影响变异步长。 二进制矩阵 M 先全置 1,再以 bioProbab 概率翻 0 或重写 1;若某行 sum 等于 coords(全 1),随机挑一列置 0,保证每行至少留一个交叉缺口。 真正落点的循环里,a[i].c[j] = Predator[j] + R*(Prey[j]-Predator[j]) 做连续变异;当 M 对应位 >0 时回退成 Predator 原值(交叉),越界则重投到 [rangeMin,rangeMax] 内。开 MT5 把 bioProbab 从 0.1 调到 0.4,能直观看到 M 稀疏度变化与收敛轨迹抖动。

MQL5 / C++
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayCopy(Prey [i].c, A [i].c);
  }
}
else
{
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayCopy(Prey [i].c, B [i].c);
  }
}
  class=class="str">"cmt">// Permutation of Prey
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    ArrayShuffle(Prey [i].c);
  }
  class="type">class="kw">double R;
  if (u.RNDprobab() < class="num">0.5)
  {
    R = class="num">4 * u.RNDprobab() * u.RNDfromCI(-class="num">1.0, class="num">1.0);
  }
  else R = class="num">1 / MathExp(class="num">4 * MathRand() / class="num">32767.0);
  class=class="str">"cmt">// Fill binary matrix M with 1s
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      M [i].c [j] = class="num">1;
    }
  }
  class=class="str">"cmt">// Additional operations with matrix M
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">0;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      if (u.RNDprobab() < bioProbab)
      {
        M [i].c [j] = class="num">1;
      }
      else
      {
        M [i].c [j] = class="num">0;
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">int sum = class="num">0;
    for (class="type">int c = class="num">0; c < coords; c++) sum += M [i].c [c];
    if (sum == coords)
    {
      class="type">int j = MathRand() % coords;
      M [i].c [j] = class="num">0;
    }
  }
  class=class="str">"cmt">// Mutation
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {
      a [i].c [j] = Predator [i].c [j] + R * (Prey [i].c [j] - Predator [i].c [j]);
      class=class="str">"cmt">// Crossover
      if (M [i].c [j] > class="num">0)
      {
        a [i].c [j] = Predator [i].c [j];
      }
      class=class="str">"cmt">// Boundary control
      if (a [i].c [j] < rangeMin [j] || a [i].c [j] > rangeMax [j])
      {
        a [i].c [j] = rangeMin [j] + (rangeMax [j] - rangeMin [j]) * u.RNDprobab();
      }
    }
  }
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    for (class="type">int j = class="num">0; j < coords; j++)
    {

◍ 进化迭代里的捕食者留存与乱序

Revision 函数在 phase 小于 3 时直接 return,意味着前两个阶段不更新选择结果,这是为了避免初始种群未稳定就过早固化最优解。 遍历种群时,若当前个体适应度 a[i].f 大于历史预测值 Ypred[i],就把该个体的参数数组拷贝进 Predator[i].c 并更新 Ypred[i],相当于只保留变得更优的捕食者。 Key 等于 1 时把 Predator 拷给 A 系并记录 YA,否则拷给 B 系记录 YB,两套记忆池交替承载搜索轨迹。随后对 Ypred 排序并反转,取 Ypred[0] 为本次最优,若优于全局 fB 则回写 cB 与对应个体。 ArrayShuffle 用 MathRand 做从尾到头的 Fisher–Yates 交换,ArraySize 拿到长度后每层随机抽一个前置下标互换,给后续交叉提供无序性。外汇与贵金属杠杆高,这类参数搜索仅作策略原型验证,实盘前须在 MT5 用历史数据回测观察过拟合倾向。

MQL5 / C++
  a [i].c [j] = u.SeInDiSp(a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]);
  }
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ACS::Revision()
{
  if (phase < class="num">3) class="kw">return;
  class=class="str">"cmt">// Selection update
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    class="type">class="kw">double d = a [i].f;
    if (d > Ypred [i])
    {
      ArrayCopy(Predator [i].c, a [i].c);
      Ypred [i] = d;
    }
  }
  if (Key == class="num">1)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      ArrayCopy(A [i].c, Predator [i].c);
    }
    ArrayCopy(YA, Ypred);
  }
  else
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      ArrayCopy(B [i].c, Predator [i].c);
    }
    ArrayCopy(YB, Ypred);
  }
  ArraySort(Ypred);
  ArrayReverse(Ypred, class="num">0, WHOLE_ARRAY);
  class="type">class="kw">double Ybest = Ypred [class="num">0];
  class="type">int Ibest = ArrayMaximum(Ypred);
  if (Ybest > fB)
  {
    fB = Ybest;
    ArrayCopy(a [Ibest].c, Predator [Ibest].c);
    ArrayCopy(cB, Predator [Ibest].c);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ACS::ArrayShuffle(class="type">class="kw">double &arr [])
{
  class="type">int n = ArraySize(arr);
  for (class="type">int i = n - class="num">1; i > class="num">0; i--)
  {
    class="type">int j = MathRand() % (i + class="num">1);
    class="type">class="kw">double tmp = arr [i];
    arr [i] = arr [j];
    arr [j] = tmp;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————

「极小种群下反而更稳的协同搜索」

人工协同搜索(ACS)最反直觉的一点是:种群缩到极小,综合得分不降反升。在适应度函数各跑 10,000 次的设定下,种群 10 个个体时 All score 为 4.49701(49.97%),降到 3 个个体变成 4.97110(55.23%),仅留 1 个个体时进一步到 5.22555(58.06%)。 这背后的机制是算法内部用了五个子种群,个体间跨群交互维持了多样性,没有出现“瓶颈”效应。可视化里看不到聚类,智能体在平坦区(如 Forest、Megacity 的大片低变区)也保持混乱游走,避开了局部陷阱。 横向排位上,ACS 在 23 个对比算法里列第 8,总得分 5.226、折算 58.06%。细分看,Forest 函数上 5/25/500 规模得分分别 1.00000 / 0.88861 / 0.22413,是它最亮眼的项;Megacity 离散函数得分偏弱,500 规模仅 0.13322。外汇或贵金属模型若引用此类优化器做参数寻优,需注意极小种群可能倾向过拟合平坦目标面,实盘属高风险验证。 想自己复现,可直接把下面这段日志结构当校验基准:种群=1、协作系数=0.9、单函数 10,000 次时,Hilly 三项应为 0.75547 / 0.74744 / 0.30407。

MQL5 / C++
ACS|Artificial Cooperative Search|class="num">1.0|class="num">0.9| ============================= class="num">5 Hilly&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.7554725186591347 class="num">25 Hilly&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.7474431551529281 class="num">500 Hilly&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.3040669213089683 ============================= class="num">5 Forest&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.9999999999993218 class="num">25 Forest&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.888607840003743 class="num">500 Forest&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.2241289484506152 ============================= class="num">5 Megacity&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.6907692307692308 class="num">25 Megacity&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.4818461538461539 class="num">500 Megacity&class="macro">#x27;s; Func runs: class="num">10000; result: class="num">0.1332153846153859 ============================= All score: class="num">5.22555 (class="num">58.06%)

优化器横向跑分:从萤火虫到类电磁

上面这张表把 24 到 40 号共 17 种参数优化算法在同一测试环境下的原始指标摊开了。NMm(Nelder-Mead)和 FAm(萤火虫算法)标注了 M,表示其某列结果带有约束修正,其余未标 M 的算法按无约束处理。 看综合加权值(倒数第二列),NMm 拿到 3.233 最高,FAm 3.048 次之,GSA 2.911 排第三;而 EM(类电磁算法)只有 1.786,CSS(电荷系统搜索)1.842,落在末端。换算成百分制(末列),NMm 35.92、FAm 33.87,EM 仅 19.85。 对外汇与贵金属 EA 调参来说,这组数字说明:传统直接搜索类(NMm)和群智能类(FAm、GSA)在收敛质量上可能明显优于末段算法。但外汇/贵金属市场高杠杆、高波动,回测优势不等于实盘胜率,切换优化器前建议在 MT5 策略测试器里用同一品种周期复跑验证。 标 M 的算法在部分子项(如第 4、8 列)数值偏高,可能是约束处理把边界解也算了进来,实盘若遇报价跳空,这类解可能直接失效。

◍ 记住这一条就够了

ACS 把五个单一个体种群丢进互相洗牌与二进制变异的回路里,实测在种群总规模仅 5 的情况下,颜色渐变图里仍有结果 ≥0.99 的白色高亮区,说明小种群抗退化不是嘴上说说。 它外部参数只有一个,跨类型函数收敛表现稳,但在低维函数上离散度偏大,跑之前得先想清楚维度适配。 真要上手,去 GitHub 拉 Population-optimization-algorithms-MQL5 的 ACS.zip(26.11 KB),MT5 里挂上作者给的评级脚本,自己跑一遍直方图,比看任何结论都实在。

常见问题

先用低差异序列铺种群初始位置,相位用反正弦映射打散,能避开聚堆;种群数低于20时建议降维度跑。
把猎物矩阵按列归一、捕食者向量外扩成行,再做逐元素加权;核对两者列数等于变量数即可排错。
可以,小布能按你给的变量范围自动初始化种群、执行混合算子并输出每代留存曲线,你只调参数看结果。
极小种群降低了维度干扰,协同搜索更易聚焦;建议变量多于15个时反而主动控到10~15个个体。
在标准测试函数上ACS中后期超越萤火虫约12%~18%,类比电磁慢但全局更不易陷局部,按精度需求选。