人工蜂巢算法(ABHA):测试与结果·进阶篇
📘

人工蜂巢算法(ABHA):测试与结果·进阶篇

第 2/2 篇

蜂群状态切换与轮盘赌选优的实现细节

ABHA 算法里,新手蜂(novice)向其他状态的迁移完全由当前代价与群体平均代价的比较驱动。代码里写得很直接:若 agent.cost > avgCost,就标记为 stateExperienced,否则丢进 stateSearch 并随机初始化搜索方向与步长。这意味着群体里一旦某次采样劣于均值,就被迫转为随机探索,避免全群陷在同一局部。 在有经验蜂的定向动作中,先用轮盘赌从经验蜂里挑一个代表:把所有 p_si 累加得 totalProbability,再乘一个 [0,1) 随机数得到 randomValue,逐个累加直到超过该值就锁定下标 ind。若一轮没选到(ind 仍为 -1),直接退回随机搜索 ActionRandomSearch。这段代码是典型的比例选择,经验越被看好的蜂越可能被借鉴。 [CODE] //---------------------------------------------------------------------------- double totalProbability = 0; for (int i = 0; i < popSize; i++) { if (agents [i].state == S_ABHA_Agent::stateExperienced) { totalProbability += agents [i].p_si; } } //---------------------------------------------------------------------------- double randomValue = u.RNDprobab () * totalProbability; double cumulativeProbability = 0; int ind = -1; for (int i = 0; i < popSize; i++) { if (agents [i].state == S_ABHA_Agent::stateExperienced) { cumulativeProbability += agents [i].p_si; if (cumulativeProbability >= randomValue) { ind = i; break; } } } //---------------------------------------------------------------------------- if (ind == -1) { return ActionRandomSearch (coordInd); } double direction = agents [ind].bestPosition [coordInd] - val; double noise = u.RNDfromCI (-1.0, 1.0); return val + direction * noise; } [/CODE] 逐行拆解:totalProbability 先清 0,遍历种群把经验态蜂的 p_si 累加;randomValue 用均匀随机乘总概率,相当于在 [0,总概率) 落点;第二个循环做累加并判断越界即选中,ind 初值 -1 用于兜底;若没选到就随机搜,否则用选中蜂最优位置与当前值之差作 direction,再乘 [-1,1] 均匀噪声返回扰动后的值。 动作函数里还有两个落点值得在 MT5 里直接验证:ActionMovingDirection 每步让 position 加 stepSize*direction,且 stepSize 乘 stepSizeReductionFactor 收缩;ActionHiveVicinity 用 PowerDistribution 以幂指数 12 在参数范围内做邻域扰动,指数偏大说明蜂群倾向紧贴食物源而非广撒网。外汇与贵金属参数优化用这套,高杠杆下过拟合风险显著,回测盈利不预示实盘概率。

MQL5 / C++
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double totalProbability = class="num">0;
for (class="type">int i = class="num">0; i < popSize; i++)
{
  if (agents [i].state == S_ABHA_Agent::stateExperienced)
  {
    totalProbability += agents [i].p_si;
  }
}
class=class="str">"cmt">//----------------------------------------------------------------------------
class="type">class="kw">double randomValue = u.RNDprobab() * totalProbability;
class="type">class="kw">double cumulativeProbability = class="num">0;
class="type">int    ind = -class="num">1;
for (class="type">int i = class="num">0; i < popSize; i++)
{
  if (agents [i].state == S_ABHA_Agent::stateExperienced)
  {
    cumulativeProbability += agents [i].p_si;
    if (cumulativeProbability >= randomValue)
    {
      ind = i;
      break;
    }
  }
}
class=class="str">"cmt">//----------------------------------------------------------------------------
if (ind == -class="num">1)
{
  class="kw">return ActionRandomSearch(coordInd);
}
class="type">class="kw">double direction = agents [ind].bestPosition [coordInd] - val;
class="type">class="kw">double noise     = u.RNDfromCI(-class="num">1.0, class="num">1.0);
class="kw">return val + direction * noise;
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//class="num">3. Move in a given direction with a step
class="type">class="kw">double C_AO_ABHA::ActionMovingDirection(S_ABHA_Agent &agent, class="type">int coordInd)
{
  agent.position [coordInd] += agent.stepSize * agent.direction [coordInd];
  agent.stepSize *= stepSizeReductionFactor;
  class="kw">return agent.position [coordInd];
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//class="num">4. Move in the vicinity of a food source
class="type">class="kw">double C_AO_ABHA::ActionHiveVicinity(class="type">int coordInd, class="type">class="kw">double val)
{
  class="kw">return u.PowerDistribution(val, rangeMin [coordInd], rangeMax [coordInd], class="num">12);
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABHA::ChangingStateForNovice(S_ABHA_Agent &agent)
{
  class=class="str">"cmt">//Current cost   : Used to enter either the Experienced or Search state.
  class=class="str">"cmt">//Previous cost: Not used.
  class=class="str">"cmt">//Best cost    : Not used.
  class=class="str">"cmt">//Into Experienced. If a novice receives information about a highly profitable food source(for example, through dances of other bees), it may transition to the experienced state.
  class=class="str">"cmt">//Into Search. If a novice does not receive information about food sources, it may begin a random search and enter a search state.
  if (agent.cost > avgCost) agent.state = S_ABHA_Agent::stateExperienced;
  else
  {
    agent.state = S_ABHA_Agent::stateSearch;
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      agent.direction [c] = u.RNDfromCI(-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c]));
    }
    agent.stepSize           = initialStepSize;
    agent.searchCounter      = class="num">0;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABHA::ChangingStateForExperienced(S_ABHA_Agent &agent)
{
  class=class="str">"cmt">//Current cost   : If the current value is high and the information is valid, it can pass this information on to other bees through a dance.

◍ 蜜蜂算法里弃养概率与状态切换的硬逻辑

人工蜂群(ABHA)里每只 agent 维护一个 pab(弃养概率),它直接决定该蜜蜂是继续啃当前食物源还是出去找新的。代码用前后两次 cost 的差来推 pab:比上次好就减 abandonmentRate,比上次差就加,且钳在 0.0~1.0 之间。 当 cost 优于历史最佳时 pab 直接拉满到 1.0,意味着这只蜜蜂认定当前源已无潜力,倾向放弃。若 cost 超过平均成本的 1.2 倍,立即切到 stateSource 且 pab=1;低于平均成本则进 stateSearch,随机重置各维度方向、步长回到 initialStepSize、搜索计数器清 0。 搜索态的 ChangingStateForSearch 另有规则:cost 比上次好会重撒方向并 searchCounter++;一旦超过 bestCost 就按 stepSizeReductionFactor 收缩步长。连续 maxSearchAttempts 次没找到更好源,回退到 stateNovice。外汇与贵金属波动剧烈,这类自适应参数在高杠杆下可能放大回撤,实盘前务必在 MT5 策略测试器跑多品种验证。 [CODE] if (agent.cost < agent.prevCost) { agent.pab -= abandonmentRate; if (agent.pab < 0.0) agent.pab = 0.0; } if (agent.cost > agent.prevCost) { agent.pab += abandonmentRate; if (agent.pab > 1.0) agent.pab = 1.0; } if (agent.cost > agent.bestCost) agent.pab = 1.0; if (agent.cost > avgCost * 1.2) { agent.state = S_ABHA_Agent::stateSource; agent.pab = 1; } else if (agent.cost < avgCost) { agent.state = S_ABHA_Agent::stateSearch; for (int c = 0; c < coords; c++) { agent.direction [c] = u.RNDfromCI (-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c])); } agent.stepSize = initialStepSize; agent.searchCounter = 0; } } void C_AO_ABHA::ChangingStateForSearch (S_ABHA_Agent &agent) { if (agent.cost < agent.prevCost) { for (int c = 0; c < coords; c++) { agent.direction [c] = u.RNDfromCI (-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c])); } agent.stepSize = initialStepSize; agent.searchCounter++; } if (agent.cost > agent.bestCost) { agent.stepSize *= stepSizeReductionFactor; agent.searchCounter = 0; } if (agent.searchCounter >= maxSearchAttempts) { agent.searchCounter = 0; agent.state = S_ABHA_Agent::stateNovice; return; } if (agent.cost > avgCost * 1.2) { agent.state = S_ABHA_Agent::stateSource; agent.pab = 1; } } [/CODE] 逐行拆解:第1行判断当前成本是否低于上次,是则降低弃养概率;第3行把 pab 下限锁在 0。第7行成本上升时提高 pab,第9行上限锁 1。第11行一旦刷新最差(这里 bestCost 实为历史最优参考),pab 置1。第12~16行成本超均值1.2倍切源态,否则低于均值进搜索态并重置方向与步长。搜索态函数里第34行若连超 maxSearchAttempts 次无改善,回 novice;第40行同样超1.2倍均值转 source 态。把 abandonmentRate 从默认调小,蜜蜂会更恋战当前源,你可能看到收敛变慢但误切减少。

MQL5 / C++
if (agent.cost < agent.prevCost)
{
  agent.pab -= abandonmentRate;
  if (agent.pab < class="num">0.0) agent.pab = class="num">0.0;
}
if (agent.cost > agent.prevCost)
{
  agent.pab += abandonmentRate;
  if (agent.pab > class="num">1.0) agent.pab = class="num">1.0;
}
if (agent.cost > agent.bestCost) agent.pab = class="num">1.0;
if (agent.cost > avgCost * class="num">1.2)
{
  agent.state = S_ABHA_Agent::stateSource;
  agent.pab = class="num">1;
}
else
  if (agent.cost < avgCost)
  {
    agent.state = S_ABHA_Agent::stateSearch;
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      agent.direction [c] = u.RNDfromCI(-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c]));
    }
    agent.stepSize       = initialStepSize;
    agent.searchCounter  = class="num">0;
  }
}
class="type">void C_AO_ABHA::ChangingStateForSearch(S_ABHA_Agent &agent)
{
  if (agent.cost < agent.prevCost)
  {
    for (class="type">int c = class="num">0; c < coords; c++)
    {
      agent.direction [c] = u.RNDfromCI(-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c]));
    }
    agent.stepSize = initialStepSize;
    agent.searchCounter++;
  }
  if (agent.cost > agent.bestCost)
  {
    agent.stepSize *= stepSizeReductionFactor;
    agent.searchCounter = class="num">0;
  }
  if (agent.searchCounter >= maxSearchAttempts)
  {
    agent.searchCounter = class="num">0;
    agent.state = S_ABHA_Agent::stateNovice;
    class="kw">return;
  }
  if (agent.cost > avgCost * class="num">1.2)
  {
    agent.state = S_ABHA_Agent::stateSource;
    agent.pab = class="num">1;
  }
}

「蜂群状态切换与概率归一的实现细节」

人工蜂群算法里,每只蜜蜂 agent 维护三个关键代价:当前代价 cost、历史最优 bestCost、以及群体平均 avgCost。当 agent.cost 低于 avgCost 时,说明它当前所在的「蜜源」质量可能不够好,于是按 abandonmentRate 递减停留概率 pab;一旦随机值大于 pab,就切入搜索态 stateSearch,重置方向向量与步长,重新在参数区间扫源。 若 agent.cost 反而高于自身 bestCost,意味着找到了更优解,状态直接置为 stateExperienced,pab 拉满到 1,相当于这只蜂准备把信息「跳舞」传递给其他蜂。外汇与贵金属市场参数寻优中套用该逻辑时,需注意过拟合风险偏高,回测甜区不等于实盘概率。 CalculateProbabilities 先扫一遍种群拿到 maxCost / minCost,再用 (maxCost - cost) / 区间 算出跟随概率 p_si;p_srs 取固定随机搜索概率,p_rul 用 1-pab,p_ab 用 pab。最后三者求和做归一,保证三个动作概率加起来严格为 1。 CalculateAverageCost 只做一件事:把 popSize 只蜜蜂的 cost 累加除以种群规模,写进 avgCost。这个均值会回灌到上面的状态判断,是搜索与停留博弈的分水岭。开 MT5 把 popSize 调到 30~50 跑一遍,能直观看到 avgCost 收敛曲线。

MQL5 / C++
  class=class="str">"cmt">//Current cost  : If the current value is below the threshold, it may decide that the source is not good enough and start looking for a new one.
  class=class="str">"cmt">//Previous value: The bee can use the previous value to compare and determine if the situation has improved. If the current value is worse, it may signal the need to change the strategy.
  class=class="str">"cmt">//Best value    : A bee uses the best value to decide whether to class="kw">continue exploiting the current food source or to look for a new, more profitable one.
  class=class="str">"cmt">//Into Search. If the current food source turns out to be impractical(e.g. the current fitness value is worse than the threshold), a bee may class="kw">switch to a search state to search for new sources.
  class=class="str">"cmt">//Into Experienced. If a food source bee finds a food source that proves beneficial, it may enter the experienced state to pass on the information to other bees.
  if (agent.cost < avgCost)
  {
    agent.pab -= abandonmentRate;
    if (u.RNDprobab() > agent.pab)
    {
      agent.state = S_ABHA_Agent::stateSearch;
      agent.pab = class="num">0;
      for (class="type">int c = class="num">0; c < coords; c++)
      {
        agent.direction [c] = u.RNDfromCI(-(rangeMax [c] - rangeMin [c]), (rangeMax [c] - rangeMin [c]));
      }
      agent.stepSize       = initialStepSize;
      agent.searchCounter = class="num">0;
    }
  }
  if (agent.cost > agent.bestCost)
  {
    agent.state = S_ABHA_Agent::stateExperienced;
    agent.pab = class="num">1;
  }
}
class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABHA::CalculateProbabilities()
{
  class="type">class="kw">double maxCost = -DBL_MAX;
  class="type">class="kw">double minCost =  DBL_MAX;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    if (agents [i].cost > maxCost) maxCost = agents [i].cost;
    if (agents [i].cost < minCost) minCost = agents [i].cost;
  }
  class="type">class="kw">double costRange = maxCost - minCost;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    agents [i].p_si = (maxCost - agents [i].cost) / costRange;
    agents [i].p_srs = randomSearchProbability; class=class="str">"cmt">// random search probability
    agents [i].p_rul = class="num">1.0 - agents [i].pab;     class=class="str">"cmt">// probability of following the dance
    agents [i].p_ab  = agents [i].pab;           class=class="str">"cmt">// probability of staying near the source
    class="type">class="kw">double sum = agents [i].p_srs + agents [i].p_rul + agents [i].p_ab;
    agents [i].p_srs /= sum;
    agents [i].p_rul /= sum;
    agents [i].p_ab  /= sum;
  }
}
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
class="type">void C_AO_ABHA::CalculateAverageCost()
{
  class="type">class="kw">double totalCost = class="num">0;
  for (class="type">int i = class="num">0; i < popSize; i++)
  {
    totalCost += agents [i].cost;
  }
  avgCost = totalCost / popSize;
}
class=class="str">"cmt">//———————

蜂巢算法跑分处在中游

把 ABHA(人工蜂巢算法)接进三套标准测试函数——Hilly、Forest、Megacity,每套各跑 5 / 25 / 500 个峰值、单轮函数调用 10000 次,得到的总分是 4.12678,折算百分比 45.85%。 具体拆开看:Hilly 三项结果 0.84131 / 0.54227 / 0.26304,Forest 为 0.87858 / 0.47779 / 0.17181,Megacity(离散)为 0.50923 / 0.33877 / 0.10397。峰值越多,收敛质量掉得越明显,500 峰时三项都跌破 0.27。 横向拉一张 40 个算法的对比表,ABHA 排第 21 位,紧挨在 ACOm(49.31%)后头、ASBO(40.63%)前头。榜首 ANS 跨邻域搜索拿 68.15%,末位 FSS 鱼群搜索只有 22.84%。 这类基准只说明 ABHA 在复杂多峰地形里倾向居中表现,既不像 PSO 那样掉到 24.77%,也远没摸到第一梯队。真要用于 MT5 上的参数寻优,建议先拿历史品种跑一轮自己的回测,别直接信排名。

◍ 四种优化器的误差横向比对

把 RND、GWO、CSS、EM 四组在测试集上的指标拉平看,第 41 行随机搜索 RND 的 RMSE 约 0.52033、MAE 0.36068,而第 44 行类电磁算法 EM 的 RMSE 降到 0.46250、MAE 0.34594,绝对误差倾向更小。 从综合打分观察,RND 末列得分 22.37,EM 为 19.85,CSS 最低 20.46,说明在这组样本里纯随机反而没比元启发式差太多,灰狼 GWO 22.32 与随机几乎持平。 做 MT5 EA 参数寻优时,别默认「高级算法一定碾压随机」——上述 0.02~0.05 量级的 RMSE 差,可能只是该时段数据噪声内的波动,建议自己换品种复跑这四组对照。

「一点提醒」

ABHA 在相关测试里把颜色等级 ≥0.99 的结果标白,直方图标尺 0–100 且 100 为理论极值,说明它在低维与离散函数上确实能跑出可复现的高分,但作者本人也直言结果低于预期,且外部参数偏多、高维平滑函数收敛慢。 如果你打算在 MT5 里接这套群体优化做参数寻优,建议先拿附录的 ABHA.zip(30.1 KB)在离散品种上小范围验证,别直接丢高维平滑面。外汇与贵金属杠杆高、滑点诡异,任何优化结果都只是概率倾向,实盘前务必做样本外压力测试。 作者公开了 MQL5 实现归档,也强调许多算法为提升搜索能力已被改过,标准描述不担责——这意味着你抄代码时得自己核对逻辑,而不是盲信评级表。

常见问题

当侦查步数超过预设阈值且当前解未更新时触发切换,具体阈值写在状态机循环里,可调参验证。
弃养概率由适应度排名归一后乘以系数得到,系数写在轮盘赌选优段,改系数即可调弃养倾向。
可以,小布能按你给的参数复现蜂群状态机并标记卡死路径,打开对应品种页就能让它替你跑这套诊断。
跑分处在中游,比部分梯度法慢但比纯随机稳,具体误差数字看原文四种比对表。
不归一会导致选中权重溢出或偏向极端解,归一后才能保证每只蜜蜂按真实占比被选中。