种群优化算法:细菌觅食优化 遗传算法(BFO-GA)·进阶篇
◍ 细菌觅食优化的初始化与种群播种
下面这段 C_AO_BFO_GA::Init 是把细菌觅食优化(BFO)的容器一次性拉起来的关键。它接收优化参数个数、种群规模、lambda、繁殖概率、生命计数器和变异强度,先把全局最优 fB 置为 -DBL_MAX,再把 range/v/ind/val/b/bT 等动态数组按参数维度和种群规模Resize,避免后面越界。 循环里对每个细菌 b[i] 和临时体 bT[i] 都 Resize 出 parameters 长度的 c、cLast、v,并把个体适应度 f 与 fLast 初始化为 -DBL_MAX。注意 populationSize 若为 50,这里就会连续申请 100 组参数长度数组,MT5 策略测试器里开大种群前要估一下内存。 Swimming 函数体是空的,真正的游动逻辑在后面补;紧接着的 if(!evaluation) 块做种群播种:每个细菌 s 的每个参数 k 用 RNDfromCI 在 [rangeMin,rangeMax] 随机落点,再用 SeInDiSp 对齐到离散步长,v[k] 记为区间跨度,b[s].v[k] 由 NewVector 生成,lifeCNT 清零。evaluation 置 true 后本次只播种不进化。 繁殖分支里 r = RNDfromCI(0,1),当 r < reproduction 时取 st = populationSize/2 做细菌原始体复制。比如 reproduction 设 0.25、种群 200,大约四分之一概率触发、前半 100 个个体进入后续复制循环。外汇与贵金属 EA 用这套做参数寻优时,过度依赖随机播种可能陷入局部优,实盘前务必多轮回测。
class="type">void C_AO_BFO_GA::Init(const class="type">int paramsP, class=class="str">"cmt">//number of opt. parameters const class="type">int populationSizeP, class=class="str">"cmt">//population size const class="type">class="kw">double lambdaP, class=class="str">"cmt">//lambda const class="type">class="kw">double reproductionP, class=class="str">"cmt">//probability of reproduction const class="type">int lifeCounterP, class=class="str">"cmt">//life counter const class="type">class="kw">double powerMutP) class=class="str">"cmt">//mutation power { fB = -DBL_MAX; evaluation = class="kw">false; parameters = paramsP; populationSize = populationSizeP; lambda = lambdaP; reproduction = reproductionP; lifeCounter = lifeCounterP; powerMut = powerMutP; ArrayResize(rangeMax, parameters); ArrayResize(rangeMin, parameters); ArrayResize(rangeStep, parameters); ArrayResize(v, parameters); ArrayResize(ind, populationSize); ArrayResize(val, populationSize); ArrayResize(b, populationSize); ArrayResize(bT, populationSize); for (class="type">int i = class="num">0; i < populationSize; i++) { ArrayResize(b [i].c, parameters); ArrayResize(b [i].cLast, parameters); ArrayResize(b [i].v, parameters); b [i].f = -DBL_MAX; b [i].fLast = -DBL_MAX; ArrayResize(bT [i].c, parameters); ArrayResize(bT [i].cLast, parameters); ArrayResize(bT [i].v, parameters); } ArrayResize(cB, parameters); } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void Swimming() { } if (!evaluation) { fB = -DBL_MAX; for (class="type">int s = class="num">0; s < populationSize; s++) { for (class="type">int k = class="num">0; k < parameters; k++) { b [s].c [k] = RNDfromCI(rangeMin [k], rangeMax [k]); b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); v [k] = rangeMax [k] - rangeMin [k]; b [s].v [k] = NewVector(k); b [s].f = -DBL_MAX; b [s].fLast = -DBL_MAX; b [s].lifeCNT = class="num">0; } } evaluation = true; class="kw">return; } class="type">class="kw">double r = RNDfromCI(class="num">0.0, class="num">1.0); if (r < reproduction) { class="type">int st = populationSize / class="num">2; class=class="str">"cmt">//bacterium original-------------------------------------------------------- for (class="type">int s = class="num">0; s < st; s++) {
菌群寻优里的克隆与变异实现
这段逻辑把细菌群体分成两块处理:前 st 个个体做趋化更新,后面的做克隆。趋化时每个参数在上一轮最优解 cLast 上加随机步长 v,再用 SeInDiSp 按区间和步长离散化,lifeCNT 累加记录存活代数。 克隆段从已存活群体里随机挑一个父代(i 被限制在 0~st-1),把它的 cLast 直接抄给新个体,随后用 PowerDistribution 做幂分布扰动,再离散化,lifeCNT 清零。这样种群后半段靠优秀基因繁殖,前半段靠自身游走进化。 当 lifeCNT 超过 lifeCounter,说明该菌困在原地太久,强制重生:新向量由 NewVector 给,参数再经幂分布和离散化,相当于跳出局部洼地。NewVector 返回 lambda * v[paramInd] * r,r 在 -1.0 到 1.0 均匀抽取,lambda 控制步长缩放。 Evaluation 只干两件事:若本轮适应度 f 优于记录 fLast 就覆盖并备份 cLast;全群体排序后,若头名 fLast 刷新全局最优 fB,就把 cB 更新为头名参数。开 MT5 把这段塞进 EA 回测,调 lambda 从 0.3 到 1.0 能直接看出收敛速度差异,外汇与贵金属品种须警惕过拟合带来的实盘高风险。
for (class="type">int k = class="num">0; k < parameters; k++) { b [s].c [k] = b [s].cLast [k] + b [s].v [k]; b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); } b [s].lifeCNT++; } class=class="str">"cmt">//bacterium clone----------------------------------------------------------- for (class="type">int s = st; s < populationSize; s++) { for (class="type">int k = class="num">0; k < parameters; k++) { class="type">int i = (class="type">int)RNDfromCI(class="num">0, st); if (i >= st) i = st - class="num">1; b [s].c [k] = b [i].cLast [k]; b [s].c [k] = PowerDistribution(b [s].c [k], rangeMin [k], rangeMax [k], powerMut); b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); } b [s].lifeCNT = class="num">0; } } for (class="type">int s = class="num">0; s < populationSize; s++) if (b [s].lifeCNT >= lifeCounter) { for (class="type">int k = class="num">0; k < parameters; k++) { b [s].v [k] = NewVector(k); b [s].c [k] = PowerDistribution(b [s].cLast [k], rangeMin [k], rangeMax [k], powerMut); b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); } b [s].lifeCNT = class="num">0; } if (b [s].f == b [s].fLast) { for (class="type">int k = class="num">0; k < parameters; k++) { b [s].c [k] = b [s].cLast [k] + b [s].v [k]; b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); } b [s].lifeCNT++; } for (class="type">int k = class="num">0; k < parameters; k++) { b [s].v [k] = NewVector(k); b [s].c [k] = b [s].cLast [k] + b [s].v [k]; b [s].c [k] = SeInDiSp(b [s].c [k], rangeMin [k], rangeMax [k], rangeStep [k]); } b [s].lifeCNT++; class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">class="kw">double C_AO_BFO_GA::NewVector(class="type">int paramInd) { class="type">class="kw">double r = RNDfromCI(-class="num">1.0, class="num">1.0); class="kw">return lambda * v [paramInd] * r; } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BFO_GA::Evaluation() { for (class="type">int s = class="num">0; s < populationSize; s++) { if (b [s].f > b [s].fLast) { b [s].fLast = b [s].f; ArrayCopy(b [s].cLast, b [s].c, class="num">0, class="num">0, WHOLE_ARRAY); } } Sorting(); if (b [class="num">0].fLast > fB) { fB = b [class="num">0].fLast; ArrayCopy(cB, b [class="num">0].cLast, class="num">0, class="num">0, WHOLE_ARRAY); } } class=class="str">"cmt">//——————————————————————————————————————————————————————————————————————————————
「BFO-GA 在基准函数上的实测表现」
把遗传算子嵌进原始细菌觅食算法(BFO-GA)后,在 Hilly / Forest / Megacity 三类测试函数上各跑 10000 次函数评估,维度档位取 5、25、500。Hilly 上得分 0.891 / 0.551 / 0.315,Forest 上 0.970 / 0.396 / 0.063,Megacity 上 0.727 / 0.275 / 0.035,综合 4.224 分(占比 46.93%)。 横向对比 30 种算法的总分榜,BFO-GA 排第 9,比裸 BFO 的 2.765 分高出约 53%。说明加 GA 算子不是白费劲,少数变量维度下它甚至压过前列的进化策略与随机扩散搜索。 可视化跟踪显示,菌群能较快圈定全局极值区域,但对局部极值关注偏少;若全局极值判断失误,算法可能卡住。群落现象出现了好兆头,但个体细菌间无通信,各自为战。外汇与贵金属参数寻优属高风险场景,这类收敛慢、易误判的特性需实盘前用 MT5 回测复核。
◍ 杂交算法落地的几个冷事实
把遗传算法的选择、突变、遗传算子接进细菌觅食(BFO)后,BFO-GA 在跳出局部极值上确实比原始 BFO 干净——原版没有退出机制,现在这个窟窿补上了。作者早先算细菌生存数时写错了一处,修正后 BFO 结果只比 ABC 好出一行,说明策略逻辑里藏 bug 时算法往往还在跑,有时“错”反而混成了策略的一部分。 BFO-GA 实测优势就三条:跑得快、代码好写、参数量少的函数收敛不错;短板也直白——搜索空间一大,收敛率就垮。附档 30_The_world_of_AO_BFO_GA.zip(544.54 KB)里是历次算法更新版,含一个算评级的脚本,直方图标尺 0–100,100 是理论顶。 优化算法里大改逻辑未必换来大提升,微小改动却常撬动明显变化,这点在 MT5 里把细菌步长和突变率各动 0.1 就能复现。外汇与贵金属实盘用这类 EA 参数寻优属高风险,回测顺不代表 live 能活。