种群优化算法:人工蜂群(ABC)·进阶篇
📘

种群优化算法:人工蜂群(ABC)·进阶篇

第 2/3 篇

「蜂群优化类的接口与私有骨架」

这套蜂群寻优逻辑在 MT5 里被封装成一个类,对外只暴露四个公共成员:最优坐标的蜜值 nB、初始化蜂巢的 InitHive、给蜜蜂分派任务的 TasksForBees,以及采集蜜值的 CollectingNectar。 InitHive 的入参直接决定寻优规模:coordinatesP 是待优化参数维度,beesNumberP 是蜂群总数,workerBeesNumberP 为工蜂数,areasNumberP 划分觅食区数量,collectionRadiusP 与 scoutAreaRadiusP 分别控制采集半径与侦察半径。调这些数之前,先在策略测试器里跑一遍默认维度,看 nB 收敛曲线再动刀。 私有侧才是真正干活的地方。BeeFlight 与 ScoutBeeFlight 接收坐标数组引用和单蜂结构体,做定向飞行与随机侦察;MarkUpAreas、SortingBees、SortingAreas 负责区域标记与两轮排序。SeInDiSp 做带步长的离散映射,RNDfromCI 在闭区间取随机,Scale 做区间线性变换且可反转——这三个是坐标换算的底层函数,改寻优边界必碰。 类内还压了一组临时数组:beesT、areasT 供排序用,indA/valA/indB 存索引与蜜值。外汇与贵金属波动剧烈、杠杆高风险大,这类启发式寻参仅降低过拟合概率,不保证样本外表现。

MQL5 / C++
  class="kw">public: class="type">class="kw">double nB;                    class=class="str">"cmt">//nectar of the best coordinates
  class="kw">public: class="type">void InitHive(const class="type">int     coordinatesP,     class=class="str">"cmt">//number of opt. parameters
                                                                 const class="type">int     beesNumberP,     class=class="str">"cmt">//bees number
                                                                 const class="type">int     workerBeesNumberP, class=class="str">"cmt">//worker bees number
                                                                 const class="type">int     areasNumberP,     class=class="str">"cmt">//areas number
                                                                 const class="type">class="kw">double collectionRadiusP, class=class="str">"cmt">//collection radius
                                                                 const class="type">class="kw">double scoutAreaRadiusP); class=class="str">"cmt">//scout area radius
  class="kw">public: class="type">void TasksForBees();
  class="kw">public: class="type">void CollectingNectar();
  class=class="str">"cmt">//============================================================================
  class="kw">private: class="type">void   BeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee);
  class="kw">private: class="type">void   ScoutBeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee);
  class="kw">private: class="type">void   MarkUpAreas();
  class="kw">private: class="type">void   SortingBees();
  class="kw">private: class="type">void   SortingAreas();
  class="kw">private: class="type">class="kw">double SeInDiSp(class="type">class="kw">double In, class="type">class="kw">double InMin, class="type">class="kw">double InMax, class="type">class="kw">double Step);
  class="kw">private: class="type">class="kw">double RNDfromCI(class="type">class="kw">double Min, class="type">class="kw">double Max);
  class="kw">private: class="type">class="kw">double Scale(class="type">class="kw">double In, class="type">class="kw">double InMIN, class="type">class="kw">double InMAX, class="type">class="kw">double OutMIN, class="type">class="kw">double OutMAX,  class="type">bool Revers);
  class="kw">private: class="type">int    coordinates;        class=class="str">"cmt">//coordinates number
  class="kw">private: class="type">int    beesNumber;         class=class="str">"cmt">//the number of all bees
  class="kw">private: class="type">int    workerBeesNumber;   class=class="str">"cmt">//worker bees number
  class="kw">private: class="type">int    areasNumber;        class=class="str">"cmt">//areas number
  class="kw">private: S_Bee  beesT          [];  class=class="str">"cmt">//temporary, for sorting
  class="kw">private: S_Area areasT         [];  class=class="str">"cmt">//temporary, for sorting
  class="kw">private: class="type">int    indA           [];  class=class="str">"cmt">//array for indexes when sorting
  class="kw">private: class="type">class="kw">double valA           [];  class=class="str">"cmt">//array for nectar values when sorting
  class="kw">private: class="type">int    indB           [];  class=class="str">"cmt">//array for indexes when sorting

◍ 蜂群算法的结构体与初始化落点

下面这段私有成员定义了 ABC(人工蜂群)优化器的空间描述:每个坐标点都带采集半径、侦察半径与超球面参数,排序时另开 valB 数组缓存花蜜值。 私有段里 hypersphereRadius 与 distHyperspCenters 决定多组超球面在参数空间中的相对位置,scouting 布尔量控制是否进入侦察态,beeDance 数组则记录各区域的摇摆舞信息。 InitHive 负责把外部传入的维度数、蜂数、工蜂数、区域数落进实例。注意 areasNumber 被强制下限为 3:若传入小于 3 则直接取 3,避免低维退化。 MathSrand(GetTickCount()) 用系统节拍数播种子,使每次 MT5 加载 EA 后的随机搜索路径不重复;nB 初始化为 -DBL_MAX 代表尚未记录最优花蜜。 循环里对 areas 与 areasT 各调一次 Init(coordinates),说明算法同时维护两套区域容器,可能用于交叉比对或温度式更新。

MQL5 / C++
  class="kw">private: class="type">class="kw">double valB           [];   class=class="str">"cmt">//array for nectar values when sorting
  class="kw">private: class="type">class="kw">double areasRadius [];   class=class="str">"cmt">//radius for each coordinate
  class="kw">private: class="type">class="kw">double scoutRadius [];   class=class="str">"cmt">//scout radius for each coordinate
  class="kw">private: class="type">class="kw">double collectionRadius;   class=class="str">"cmt">//collection radius
  class="kw">private: class="type">class="kw">double scoutAreaRadius;     class=class="str">"cmt">//scout area radius
  class="kw">private: class="type">class="kw">double hypersphereRadius;   class=class="str">"cmt">//hypersphere radius
  class="kw">private: class="type">class="kw">double distHyperspCenters; class=class="str">"cmt">//distance hyperspheres centers
  class="kw">private: class="type">class="kw">double scoutHyperspRadius; class=class="str">"cmt">//scout hypersphere radius
  class="kw">private: class="type">bool   scouting;           class=class="str">"cmt">//scouting flag
  class="kw">private: S_BeeDance beeDance [];    class=class="str">"cmt">//bee dance
};
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABC::InitHive(const class="type">int      coordinatesP,       class=class="str">"cmt">//number of opt. parameters
                         const class="type">int      beesNumberP,        class=class="str">"cmt">//bees number
                         const class="type">int      workerBeesNumberP,  class=class="str">"cmt">//worker bees number
                         const class="type">int      areasNumberP,       class=class="str">"cmt">//areas number
                         const class="type">class="kw">double   collectionRadiusP,  class=class="str">"cmt">//collection radius
                         const class="type">class="kw">double   scoutAreaRadiusP)   class=class="str">"cmt">//scout area radius
{
  MathSrand(GetTickCount());
  scouting = false;
  nB       = -DBL_MAX;
  coordinates        = coordinatesP;
  beesNumber         = beesNumberP;
  workerBeesNumber = workerBeesNumberP;
  areasNumber        = areasNumberP < class="num">3 ? class="num">3 : areasNumberP;
  collectionRadius = collectionRadiusP; class=class="str">"cmt">//collection radius
  scoutAreaRadius  = scoutAreaRadiusP;  class=class="str">"cmt">//scout area radius
  ArrayResize(areas,  areasNumber);
  ArrayResize(areasT, areasNumber);
  for (class="type">int i = class="num">0; i < areasNumber; i++)
  {
    areas  [i].Init(coordinates);
    areasT [i].Init(coordinates);
  }
  ArrayResize(beeDance, areasNumber);
  ArrayResize(rangeMax,   coordinates);
  ArrayResize(rangeMin,   coordinates);
  ArrayResize(rangeStep, coordinates);
  ArrayResize(cB,         coordinates);
  ArrayResize(indA, areasNumber);
  ArrayResize(valA, areasNumber);
  ArrayResize(areasRadius, coordinates);
  ArrayResize(scoutRadius, coordinates);
  for (class="type">int i = class="num">0; i < coordinates; i++)
  {

蜂群半径与采蜜阶段的状态切换

人工蜂群(ABC)优化里,超球面半径不是拍脑袋给的,而是按各维度的区间跨度加权算出来。下面这段先把每个维度的采集半径 areasRadius[i] 和侦察半径 scoutRadius[i] 定义为 (rangeMax[i]-rangeMin[i]) 乘以对应系数,再用平方和开方得到整体 hypersphereRadius,并令 distHyperspCenters 直接等于其两倍——这意味着相邻采集区中心间距默认不重叠。 collectingNectar 函数靠 scouting 布尔量切两阶段:非侦察期只做 SortingBees 并把前 areasNumber 只蜂的坐标与适应度写回各区域,随后 scouting 置真;侦察期则逐蜂比较,若 bees[b].n 优于所属区域旧值就更新区域最优,同时追踪全局最优 nB。注意这里用的是 > 严格比较,相等时不覆盖,可能让多峰面上的平局点保留旧中心。 非侦察状态下,所有蜂的坐标在 rangeMin~rangeMax 内随机生成后再用 SeInDiSp 对齐到离散步长网格。若你在 MT5 里把 rangeStep 设得过大,蜂群会退化成粗网格搜索,收敛速度可能明显变慢;外汇与贵金属参数优化属高风险操作,回测过拟合概率偏高,须用样本外数据复核。

MQL5 / C++
  areasRadius [i] = (rangeMax [i] - rangeMin [i]) * collectionRadius;
  scoutRadius [i] = (rangeMax [i] - rangeMin [i]) * scoutAreaRadius;
}
  class="type">class="kw">double sqr = class="num">0.0;
  for (class="type">int i = class="num">0; i < coordinates; i++) sqr += areasRadius [i] * areasRadius [i];
  hypersphereRadius  = MathSqrt(sqr) * collectionRadius;
  distHyperspCenters = hypersphereRadius * class="num">2.0;
  sqr = class="num">0.0;
  for (class="type">int i = class="num">0; i < coordinates; i++) sqr += scoutRadius [i] * scoutRadius [i];
  scoutHyperspRadius = MathSqrt(sqr) * scoutAreaRadius;
  ArrayResize(indB, beesNumber);
  ArrayResize(valB, beesNumber);
  ArrayResize(bees,  beesNumber);
  ArrayResize(beesT, beesNumber);
  for (class="type">int i = class="num">0; i < beesNumber; i++)
  {
    ArrayResize(bees  [i].c, coordinates);
    ArrayResize(beesT [i].c, coordinates);
    bees  [i].n = -DBL_MAX;
    beesT [i].n = -DBL_MAX;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABC::TasksForBees()
{
  if (!scouting)
  {
    nB = -DBL_MAX;
  }
  MarkUpAreas();
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABC::CollectingNectar()
{
  if (!scouting)
  {
    SortingBees();
    for (class="type">int a = class="num">0; a <areasNumber; a++)
    {
      ArrayCopy(areas [a].cB, bees [a].c, class="num">0, class="num">0, WHOLE_ARRAY);
      areas [a].n = bees [a].n;
    }
    scouting = true;
  }
  else
  {
    class=class="str">"cmt">//transfer the nectar to the hive---------------------------------------------
    for (class="type">int b = class="num">0; b < beesNumber; b++)
    {
      if (bees [b].n > areas [bees [b].aInd].n)
      {
        ArrayCopy(areas [bees [b].aInd].cB, bees [b].c, class="num">0, class="num">0, WHOLE_ARRAY);
        areas [bees [b].aInd].n = bees [b].n;
      }
      if (bees [b].n > nB)
      {
        ArrayCopy(cB, bees [b].c, class="num">0, class="num">0, WHOLE_ARRAY);
        nB = bees [b].n;
      }
    }
    SortingAreas();
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//if the areas is not scouting - send all the bees to scouting----------------
if (!scouting)
{
  for (class="type">int b = class="num">0; b < beesNumber; b++)
  {
    for (class="type">int c = class="num">0; c < coordinates; c++)
    {
      bees [b].c [c] = RNDfromCI(rangeMin [c], rangeMax [c]);
      bees [b].c [c] = SeInDiSp(bees [b].c [c], rangeMin [c], rangeMax [c], rangeStep [c]);
    }
  }
}
for (class="type">int s = class="num">0; s < areasNumber; s++)
{
  class=class="str">"cmt">//move the center of the area to the best point in with more nectar-------
  ArrayCopy(areas [s].cC, areas [s].cB, class="num">0, class="num">0, WHOLE_ARRAY);
  class=class="str">"cmt">//ordinary area-----------------------------------------------------------

「蜂群派单与飞行扰动的具体实现」

当相邻两个超球面区域中心距离小于预设阈值 distHyperspCenters 时,算法认为它们发生了交叠,此时会把当前区域中心沿连线方向外推,推挤幅度正比于 (distHyperspCenters - centersDistance) / centersDistance。外推后还要用 SeInDiSp 把坐标夹回各自维度的 min/max/step 网格内,避免越界。 紧接着是「舞蹈」分派:beeDance 数组按区域记录蜜蜂的起止编号区间。前 areasNumber-1 个区域每只分配 (bees[a].n - bees[areasNumber-1].n) 只工蜂;最后一个区域只拿到前区数量的 10%,即 (bees[a-1].n - bees[a].n) * 0.1,明显是侦察倾斜。 工蜂(b < workerBeesNumber)按区间均匀随机落点选区域,调用 BeeFlight 做局部搜索;多余蜜蜂作侦察蜂,随机选区域后走 ScoutBeeFlight。BeeFlight 里对每维先取 [0,1] 三次方扭曲 r,再 Scale 到区域半径内,并 50% 概率取反——这种立方压缩让大部分扰动贴近中心,偶尔才跳到边缘。 在 MT5 里把这段接进自己的 ABC 优化器,把最后区域那行 * 0.1 改成 * 0.3,侦察覆盖会显著扩大,黄金 1 分钟图参数寻优的逃离局部极小值概率可能提高,但外汇与贵金属杠杆交易高风险,回测不等于实盘。

MQL5 / C++
  if (s != class="num">0)
  {
    class=class="str">"cmt">//check if there is no intersection of a region with a region of higher rank
    class=class="str">"cmt">//measure the distance from the centers of the regions
    class="type">class="kw">double centersDistance = class="num">0.0;
    for (class="type">int c = class="num">0; c < coordinates; c++) centersDistance += pow(areas [s].cC [c] - areas [s - class="num">1].cC [c], class="num">2.0);
    centersDistance = MathSqrt(centersDistance);
    class=class="str">"cmt">//the areas intersect, then need to move the current area
    if (centersDistance < distHyperspCenters)
    {
      class="type">class="kw">double incrementFactor = (distHyperspCenters - centersDistance) / centersDistance;
      class="type">class="kw">double coordDistance   = class="num">0.0;
      for (class="type">int c = class="num">0; c < coordinates; c++)
      {
        coordDistance = areas [s].cC [c] - areas [s - class="num">1].cC [c];
        areas [s].cC [c] = areas [s].cC [c] + (coordDistance * incrementFactor);
        areas [s].cC [c] = SeInDiSp(areas [s].cC [c], rangeMin [c], rangeMax [c], rangeStep [c]);
      }
    }
  }
}
class=class="str">"cmt">//send bees to the marked areas-----------------------------------------------
class="type">class="kw">double r = class="num">0.0;
beeDance [class="num">0].start = bees [class="num">0].n;
beeDance [class="num">0].end   = beeDance [class="num">0].start + (bees [class="num">0].n - bees [areasNumber - class="num">1].n);
for (class="type">int a = class="num">1; a < areasNumber; a++)
{
  if (a != areasNumber - class="num">1)
  {
    beeDance [a].start = beeDance [a - class="num">1].end;
    beeDance [a].end   = beeDance [a    ].start + (bees [a].n - bees [areasNumber - class="num">1].n);
  }
  else
  {
    beeDance [a].start = beeDance [a - class="num">1].end;
    beeDance [a].end   = beeDance [a    ].start + (bees [a - class="num">1].n - bees [a].n) * class="num">0.1;
  }
}
for (class="type">int b = class="num">0; b < beesNumber; b++)
{
  if (b < workerBeesNumber)
  {
    r = RNDfromCI(beeDance [class="num">0].start, beeDance [areasNumber - class="num">1].end);
    
    for (class="type">int a = class="num">0; a < areasNumber; a++)
    {
      if (beeDance [a].start <= r && r < beeDance [a].end)
      {
        bees [b].aInd = a;
        break;
      }
    }
    BeeFlight(areas [bees [b].aInd].cC, bees [b]);
  }
  else
  {
    class=class="str">"cmt">//choose the area to send the bees there
    r = RNDfromCI(class="num">0.0, areasNumber - class="num">1);
    bees [b].aInd = (class="type">int)r; class=class="str">"cmt">//area index
    ScoutBeeFlight(areas [bees [b].aInd].cC, bees [b]);
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABC::BeeFlight(class="type">class="kw">double &cC [] , S_Bee &bee)
{
  class="type">class="kw">double r = class="num">0.0;
  for (class="type">int c = class="num">0; c < coordinates; c++)
  {
    r  = RNDfromCI(class="num">0.0, class="num">1.0);
    r  = r * r * r;
    r = Scale(r, class="num">0.0, class="num">1.0, class="num">0.0, areasRadius [c], false);
    r *= RNDfromCI(class="num">0.0, class="num">1.0) > class="num">0.5 ? class="num">1.0 : -class="num">1.0;

常见问题

用基类定义初始化、评估、更新三个纯虚函数,派生类只实现具体采蜜逻辑,主循环靠多态调用,避免每类写一套调度。
按搜索空间维度用均匀随机+拉丁超立方撒点,初始半径设成变量范围的 30%~50%,能让首代分布更散。
小布可代你生成 ABC 结构体模板与采蜜阶段状态机代码,并对照你的参数给初始化半径和扰动系数的调参建议。
前 1/3 代用大半径全局探,中期线性收缩,后期加随机重啟,可显著降低陷入局部的概率。
中小规模用轮盘赌按蜜源质量加权更稳,维度超 50 用锦标赛省计算且不易被极差解带偏。