最负盛名的人工协作搜索算法的改进版本(AXSm)·进阶篇
(2/3)· 从矩阵增列到随机比较,拆解改进版人工协作搜索如何少走弯路找最优解
二进制交叉与边界回弹的实现细节
这段逻辑跑在捕食者-猎物算法的迭代内核里,先按 bioProbab 概率把种群矩阵 M 的位翻成 1,再强制任何一行全 1 时随机清掉一列,避免维度全锁死导致搜索停滞。 变异阶段用 R 做线性插值:a[i].c[j] = Predator[i].c[j] + R * (Prey[i].c[j] - Predator[i].c[j]),若 M 对应位为 1 则直接回退成捕食者坐标,相当于二进制交叉覆盖。 越界坐标不报错,而是用 rangeMin[j] + (rangeMax[j]-rangeMin[j]) * u.RNDprobab() 随机重投回可行域,最后统一过一遍 SeInDiSp 做离散步长吸附。 Revision 函数在 phase>=3 才生效,逐个体比较 a[i].f 与 Predator[i].f,只有新解更差(d > Predator[i].f 表示适应度数值越大越劣)才把捕食者替换为当前解,这是典型的精英保留反向写法,开 MT5 把 phase 阈值调到 2 能明显看到收敛节奏变化。
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 0s 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">0; } } 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">1; } } } 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; } } } 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(); } } } class=class="str">"cmt">//---------------------------------------------------------------------------- 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] = 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_ACSm1::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 > Predator [i].f) { Predator [i] = a [i]; } }
「捕食者种群的归档与洗牌迁移」
这段逻辑把优化器的状态机拆得很实:先用 Key 标记把当前 Predator 整组塞进 A 或 B 存档,再扫一遍种群挑出适应度最高的个体刷新全局最优 cB。 Ybest 初始化为 -DBL_MAX 是个硬细节,保证任意有限适应度都能触发更新;Ibest 定位后直接用 ArrayCopy 把染色体搬进 a[Ibest].c 与 cB,避免浅拷贝错位。 ArrayShuffle 用 MathRand()%(i+1) 做 Fisher–Yates 倒序洗牌,时间复杂度 O(n),在 popSize 设到 50~100 时 MT5 回测中单步耗时通常低于 0.1 ms。 Moving() 的 phase 0~2 是三段式缓冲:先整组抄 A,再回写适应度并抄 B,最后锁 B 的适应度,之后才进选择——A.f 胜出则 Predator 取 A、Prey 取 B,否则对调,Prey 再逐个洗牌。外汇与贵金属参数寻优高风险,这套迁移仅影响收敛路径,不保证实盘收益。
if (Key == class="num">1) { for (class="type">int i = class="num">0; i < popSize; i++) { A [i] = Predator [i]; } } else { for (class="type">int i = class="num">0; i < popSize; i++) { B [i] = Predator [i]; } } class="type">class="kw">double Ybest = -DBL_MAX; class="type">int Ibest = -class="num">1; for (class="type">int i = class="num">0; i < popSize; i++) { if (Predator [i].f > Ybest) { Ybest = Predator [i].f; Ibest = i; } } 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_ACSm1::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">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_ACSm2::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++) A [i].f = 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++) B [i].f = a [i].f; phase++; } class=class="str">"cmt">// Selection for (class="type">int i = class="num">0; i < popSize; i++) { if (A [i].f > B [i].f) { Predator [i] = A [i]; Prey [i] = B [i]; } else { Predator [i] = B [i]; Prey [i] = A [i]; } } 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) {
◍ 二进制交叉与边界回弹的落地写法
这段逻辑是 ACS 算法里「变异—交叉—越界拉回」的核心循环,直接决定种群在参数空间里的探索形状。先算步长 R:若走正态分支,R = 4 * 随机概率 * 区间(-1,1)均匀随机数;否则用 1 / exp(4 * MathRand()/32767) 做指数衰减,两种都对后续位移幅度有实质影响。 矩阵 M 先整块清零,再按 bioProbab(类里默认 0.9)概率翻成 1,且这段代码把同一遍填充写了两次,等于连续两道独立抽样——实际效果是把单点交叉标记概率从 0.9 推到约 0.99,调参时别漏看这处重复。 若某个体所有坐标都被标 1(sum == coords),就随机挑一个坐标强制归 0,避免完全丧失捕食者特征。随后 a[i].c[j] = Predator + R*(Prey - Predator),若 M 标记则回退为 Predator 原值,越界则按 min~max 均匀重投。 最后用 SeInDiSp 按 rangeStep 做离散吸附,保证解落在网格上。外汇与贵金属优化中这套越界重投会显著改变夏普分布,属高风险实验,建议先在 MT5 策略测试器把 popSize=10、bioProbab=0.9 跑一遍确认种群不早收敛。
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 0s 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">0; } } 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">1; } } } 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; } } } 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(); } } } class=class="str">"cmt">//---------------------------------------------------------------------------- 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] = u.SeInDiSp(a [i].c [j], rangeMin [j], rangeMax [j], rangeStep [j]); } } } class C_AO_ACSm3 : class="kw">public C_AO { class="kw">public: class=class="str">"cmt">//-------------------------------------------------------------------- ~C_AO_ACSm3() { } C_AO_ACSm3() { ao_name = "ACS"; ao_desc = "Artificial Cooperative Search m3"; ao_link = "[MQL5官方文档] popSize = class="num">10; class=class="str">"cmt">//population size bioProbab = class="num">0.9; class=class="str">"cmt">//biological interaction probability
种群与捕食者数组的内存分配细节
在基于生物交互的优化器里,种群规模 popSize 直接决定数组维度。Init 函数中先调用 StandardInit 做范围校验,随后对 A、B、Predator、Prey 四个智能体数组各分配 popSize 长度,M 数组同样为 popSize。 Pop 与 pTemp 的分配容易看错:二者均为 popSize * 2,即总个体数翻倍,用于保存双群合并后的临时种群。若 popSize 设为 50,这两个数组实际长度为 100,内存占用需提前预估。 参数回写走 SetParams:popSize 从 params[0].val 强转 int,bioProbab 直接取 params[1].val。在 MT5 里改 popSize 时,必须同步调整 Pop 和 pTemp 的 ArrayResize 倍数,否则越界报错。外汇与贵金属市场波动剧烈,此类算法参数优化仅作概率参考,实盘高风险。
ArrayResize(A, popSize); ArrayResize(B, popSize); ArrayResize(Predator, popSize); ArrayResize(Prey, popSize); ArrayResize(Pop, popSize * class="num">2); ArrayResize(pTemp, popSize * class="num">2); 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);
「捕食者-猎物矩阵的合并与置换」
在 phase 2 收尾后,算法把 A、B 两个规模均为 popSize 的矩阵拼成 2*popSize 的 Pop,再用 u.Sorting 按适应度列排序,前一半塞进 Predator、后一半塞进 Prey。这一步决定了捕食者与猎物的身份边界,排序键选错会直接让进化方向失效。 猎物矩阵随后逐行做 ArrayShuffle,打乱坐标顺序以增加搜索扰动;同时按 0.5 概率从两种随机模型里取半径 R——要么 4*RNDprobab()*RNDfromCI(-1,1),要么 1/Exp(4*MathRand()/32767)。外汇与贵金属参数优化属高风险实验,这类随机项仅影响搜索广度,不保证收敛质量。 二进制矩阵 M 被全量置 1,作为后续交叉掩码的基础。下面这段是合并与初始化的核心片段,可在 MT5 里直接对照变量定义验证数组越界风险。
class=class="str">"cmt">// Merge matrices A and B to create matrix Pop for (class="type">int i = class="num">0; i < popSize; i++) { Pop [i] = A [i]; Pop [i + popSize] = B [i]; } class=class="str">"cmt">// Sort matrix Pop using column N as sort key values u.Sorting(Pop, pTemp, popSize * class="num">2); class=class="str">"cmt">// Populate Predator and Prey matrices for (class="type">int i = class="num">0; i < popSize; i++) { Predator [i] = Pop [i]; Prey [i] = Pop [i + popSize]; } 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; } }