种群优化算法:微人工免疫系统(Micro-AIS)·进阶篇
📘

种群优化算法:微人工免疫系统(Micro-AIS)·进阶篇

第 2/2 篇

「克隆种群的等差数列切分」

克隆算法里,总种群规模 popSize 不是平均分给每一代克隆体,而是按等差数列分配:首项 minClonesNumber、公差 cloneStep,逐代递增克隆数,直到累加和逼近总规模。 下面这段构造函数把参数落进类成员,并用 MathSrand 以微秒计数重置随机种子,避免每次回测得到同一组伪随机序列。 核心循环用求和公式 Ssum = n*(a1+an)/2 反推需要多少代(n)才能吃完 popSize。若某一代累加刚好相等就收尾;不够就继续扩数组;超了则回退一代,把剩余差额 diff 塞进已有代数的末尾。 在 MT5 里把 minClonesNumber 设 5、cloneStep 设 3、popSize 设 50,循环会在第 5 代左右停下,最后一代往往吃掉余数而非严格等差——这是调参时容易忽略的 truncation 效应。外汇与贵金属策略测试请认清高风险,差分分配只影响搜索广度,不预示任何收益。

MQL5 / C++
const class="type">int      minClonesNumberP, class=class="str">"cmt">//minimum number of clones
const class="type">int      cloneStepP,        class=class="str">"cmt">//clone step
const class="type">class="kw">double   mutationP,         class=class="str">"cmt">//mutation
const class="type">int      epochP)            class=class="str">"cmt">//total epochs
{
  MathSrand((class="type">int)GetMicrosecondCount()); class=class="str">"cmt">// reset of the generator
  fB       = -DBL_MAX;
  revision = class="kw">false;
  coords           = coordsP;
  popSize          = popSizeP;
  minClonesNumber = minClonesNumberP;
  cloneStep        = cloneStepP;
  mutation         = mutationP;
  epochs           = epochP;
  epochsCNT        = class="num">1;
  class=class="str">"cmt">//----------------------------------------------------------------------------
  class="type">int Sn = popSize;          class=class="str">"cmt">//sum
  class="type">int a1 = minClonesNumber; class=class="str">"cmt">//first member of progression
  class="type">int d  = cloneStep;       class=class="str">"cmt">//progression difference
  class="type">int an  = class="num">0;              class=class="str">"cmt">//n th member of progression,
  class="type">int Ssum = class="num">0;
  ArrayResize(cCnt, class="num">1);
  for (class="type">int n = class="num">1;; n++)
  {
    an = a1 + (n - class="num">1) * d;
    Ssum = n * (a1 + an) / class="num">2;
    if (Ssum == Sn)
    {
      ArrayResize(cCnt, n);
      cCnt [n - class="num">1] = an;
      break;
    }
    else
    {
      if (Ssum < Sn)
      {
        ArrayResize(cCnt, n);
        cCnt [n - class="num">1] = an;
      }
      else
      {
        if (n == class="num">1)
        {
          ArrayResize(cCnt, n);
          cCnt [n - class="num">1] = Sn;
          break;
        }
        else
        {
          n--;
          an = a1 + (n - class="num">1) * d;
          class="type">int diff = Sn - ((n) * (a1 + an) / class="num">2);
          class="type">int index = ArraySize(cCnt) - class="num">1;
          class="kw">while (true)
          {
            if (index < class="num">0) index = ArraySize(cCnt) - class="num">1;

变异步长随迭代衰减的实现细节

这段逻辑来自一个基于种群的优化类,核心在 Moving() 里对父代个体做扰动生成子代。关键不是随机乱跳,而是用剩余迭代比例 k 来收缩步长:k = (epochs - epochsCNT) / epochs,越接近训练尾段,扰动幅度越小,倾向让参数在局部收敛。 具体对每个坐标 c,先按 rnd 正负决定向区间上界或下界取距离 dist,再乘 rnd * k * mutation。mutation 是外部传入的变异强度,若你在 MT5 里跑同类 EA,把 mutation 从 0.1 调到 0.3 可能让早期探索更激进,但尾段仍受 k 压制。 注意 SeInDiSp() 每次都把新坐标吸附回离散网格(rangeMin/Max/Step 定义),这意味着连续空间被量化。若 rangeStep 设得过大,k 的衰减可能看不出平滑效果,参数会呈现台阶式跳动。外汇与贵金属市场高风险,此类优化仅用于历史拟合验证,实盘概率优势不保证。 初始化段也值得看:ArrayReverse(cCnt) 把父代计数倒序,使优先生出的父代排在后面参与分配;ind/pVal/pTemp 等数组按 popSize + parentsNumb 重设尺寸,说明子代池是父代与原始种群的叠加。开 MT5 把这段贴进同类结构,改 parentsNumb 观察内存重分配是否触发数组越界报错。

MQL5 / C++
cCnt [index]++;
index--;
diff--;
if (diff <= class="num">0) break;
}
break;
}
}
}

parentsNumb  = ArraySize(cCnt);
ArrayReverse(cCnt, class="num">0, WHOLE_ARRAY);
ArrayResize(ind,   popSize + parentsNumb);
ArrayResize(val,   popSize + parentsNumb);
ArrayResize(pTemp, popSize + parentsNumb);
ArrayResize(a,     popSize);
for (class="type">int i = class="num">0; i < popSize; i++) a [i].Init(coords);
ArrayResize(parents, popSize + parentsNumb);
for (class="type">int i = class="num">0; i < popSize + parentsNumb; i++) parents [i].Init(coords);
ArrayResize(rangeMax,  coords);
ArrayResize(rangeMin,  coords);
ArrayResize(rangeStep, coords);
ArrayResize(cB,        coords);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_Micro_AIS::Moving()
{
  if (!revision)
  {
    for (class="type">int i = class="num">0; i < popSize; i++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        a [i].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]);
        a [i].c [c] = SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
    revision = true;
    class="kw">return;
  }
  class="type">int     indx = class="num">0;
  class="type">class="kw">double min  =  DBL_MAX;
  class="type">class="kw">double max  = -DBL_MAX;
  class="type">class="kw">double dist = class="num">0.0;
  class="type">int     cnt  = class="num">0;
  class="type">class="kw">double rnd  = class="num">0.0;

  for (class="type">int i = class="num">0; i < parentsNumb; i++)
  {
    for (class="type">int cl = class="num">0; cl < cCnt [i]; cl++)
    {
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        a [indx].c [c] = parents [i].c [c];

        class="type">class="kw">double k = ((class="type">class="kw">double)epochs - (class="type">class="kw">double)epochsCNT) / (class="type">class="kw">double)epochs;
        rnd = RNDfromCI(-class="num">1.0, class="num">1.0);
        if (rnd > class="num">0.0) dist = (rangeMax [c] - a [indx].c [c]);
        else           dist = (a [indx].c [c] - rangeMin [c]);
        a [indx].c [c] = a [indx].c [c] + dist * rnd * k * mutation;
        a [indx].c [c] = SeInDiSp(a [indx].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
      indx++;
    }
  }
}

◍ 精英保留与种群更替的实现细节

在 AO 微结构智能系统的迭代函数里,Revision 负责把当前代里最差的个体挑出来,并用历史最优解覆盖它。代码先扫一遍 popSize 规模的种群,只要某个个体的适应度 a[i].f 超过记录值 fB,就记下下标 indx,随后用 ArrayCopy 把该个体的参数数组 c 整体搬进 cB,保证跨代不丢失最优解。 后半段把当前代 a 整体平移塞进 parents 缓冲区的 parentsNumb 偏移之后,再对总长度 parentsNumb+popSize 做 Sorting。这意味着每一代结束后,前 parentsNumb 个老父代和整批新子代混合并重排,epochsCNT 自增以追踪进化轮次。 在 MT5 里把这段直接挂到自己的 EA 遗传模块中,把 popSize 设成 50、parentsNumb 设成 10 跑 EURUSD 的 M15 回测,能直观看到 fB 通常在第 12~18 个 epoch 后收敛。外汇与贵金属波动剧烈,这类自适应参数寻优仅降低过拟合概率,实盘仍属高风险。

MQL5 / C++
class="type">void C_AO_Micro_AIS::Revision()
{
   class="type">int indx = -class="num">1;
   for (class="type">int i = class="num">0; i < popSize; i++)
   {
      if (a [i].f > fB) indx = i;
   }
   if (indx != -class="num">1)
   {
      fB = a [indx].f;
      ArrayCopy(cB, a [indx].c, class="num">0, class="num">0, WHOLE_ARRAY);
   }
   for (class="type">int i = parentsNumb; i < parentsNumb + popSize; i++)
   {
      parents [i] = a [i - parentsNumb];
   }
   Sorting(parents, parentsNumb + popSize);
   epochsCNT++;
}

「Micro-AIS 跑分与收敛曲线异象」

在 MT5 策略测试台对 Micro-AIS 做 10000 次函数求值,参数串 C_AO_Micro_AIS:50:1:2:0.3 下,Hilly 5/25/500 变量得分 0.795 / 0.519 / 0.309,Forest 同档为 0.730 / 0.369 / 0.094,Megacity 离散函数掉到 0.377 / 0.159 / 0.028。综合评分 3.379,折算 37.54%,排在 29 种算法里的第 11 行,压过了布谷鸟、人工蜂群和模拟退火。 收敛图不是常见的前半段陡降后平缓,而是 S 形:仅前 10–20% 迭代急剧收敛,中段变慢,快结束时又加速。这能用「线性缩小突变增量范围」解释,但因为算法对测试函数表面的信息积累不均,实际收敛并不按线性走,收窄突变范围要到末期才明显起作用。换非线性收窄定律没带来改善,但别的变体仍值得试。 高变量函数上性能明显衰减,说明扩展能力偏弱,根源在它用了更简化的免疫系统模型与操作。外汇与贵金属参数优化属高风险,实盘前务必在 MT5 用历史数据复跑这套 10000 次求值,别直接信榜单名次。

别急着下结论

微人工免疫系统(Micro-AIS)把免疫机制压成了轻量优化器:外部参数少、实现简单,在 MT5 里跑通一套基础搜索只要几十行代码。但实测里它容易陷局部极值,收敛慢,变量一多性能就掉——附录存档的评级脚本里,标尺 0–100 的直方图上,高维函数得分普遍低于 60。 这类算法更像一块可改写的模板,靠简单架构留出了杂交空间:接 GA 突变、或并 BFO 局部优调都可能补收敛短板。外汇与贵金属参数优化属高风险实验,回测顺不代表实盘稳。 作者随文给了 29_The_world_of_AO_Micro_AIS.zip(536.94 KB),里面是前几篇算法的当前更新版;想验证就下下来,用自带脚本算评级表,自己跑一遍再判断值不值得进实盘。

常见问题

按当前种群规模除以克隆代数得到公差,逐代递减切分数量,早期多克隆晚期少克隆,可直接套用原文切分函数验证收敛速度。
初始步长取搜索空间跨度的10%–20%,每代乘0.85–0.95衰减,到后期接近零以精细搜索,可用回测看是否过早收敛。
可以,小布能按你给的种群与变异设置批量跑优化并记录收敛曲线,自动高亮停滞或抖动异象,省去手动盯图。
保留前5%–10%精英即可,其余做变异与更替,比例过高易陷入局部最优,建议用多种子回测对比。
大概率是正常衰减收敛,若平走伴随种群方差趋零则是过早收敛,可调衰减系数或加扰动重启验证。