头脑风暴优化算法(第二部分): 多模态·进阶篇
(2/3)·从第一部分的理论铺垫走到真实代码与多模态测试,看 BSO 如何绕过搜索空间障碍
接上篇,我们继续深挖头脑风暴优化算法。第一部分里聚类与变异的逻辑还停在纸面,很多人卡在不知道智能体结构该怎么写、种群类如何继承。本篇把代码和测试结果摊开,省去你反复试错的时间。
◍ 正文
<span class="keyword">const</span> <span class="keyword">int</span> epochsP = <span class="number">0</span>); <span class="comment">//number of epochs</span> <span class="keyword">void</span> Moving (); <span class="keyword">void</span> Revision (); <span class="keyword">void</span> Injection (<span class="keyword">const</span> <span class="keyword">int</span> popPos, <span class="keyword">const</span> <span class="keyword">int</span> coordPos, <span class="keyword">const</span> <span class="keyword">double</span> <span class="keyword">value</span>); <span class="comment">//----------------------------------------------------------------------------</span> <span class="keyword">int</span> parentPopSize; <span class="comment">//parent population size;</span> <span class="keyword">int</span> clustersNumb; <span class="comment">//number of clusters</span> <span class="keyword">double</span> p_Replace; <span class="comment">//replace probability</span> <span class="keyword">double</span> p_One; <span class="comment">//probability of choosing one</span> <span class="keyword">double</span> p_One_center; &
聚类中心偏移与新想法的生成逻辑
这段逻辑跑在种群迭代里,核心是用概率门控决定要不要动聚类结构。当随机数小于 p_Replace 时,算法挑一个非空聚类,把它质心沿各维度用高斯扰动重采样,扰动半径取到该维区间跨度的 0.8 倍。 代码里先建 clustList 把 count>0 的聚类收拢,clListSize 就是非空聚类数;RNDminusOne 返回 [0, clListSize) 的索引。质心重算时 min/max 做了边界夹紧,避免越出 rangeMin/rangeMax,再用 GaussDistribution(...,3) 做 3 倍标准差内的截断正态采样。 另一路 p_One 分支则是从单个聚类抽想法,cIndx_1 同样来自非空表。外汇与贵金属市场高波动,这类基于聚类的搜索若直接接实盘信号,参数失配可能导致频繁重聚类,回测与实盘偏差倾向放大。 把下面这段贴进 MT5 的 EA 源码对照,能看清质心偏移的边界处理:dist 写死 0.8 倍跨度,若你的特征区间差异大,可改成按维自适应。
for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = u.RNDfromCI(rangeMin [c], rangeMax [c]); a [i].c [c] = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); agent [i].c [c] = a [i].c [c]; } } class="kw">return; } class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">//---------------------------------------------------------------------------- class="type">int cIndx_1 = class="num">0; class=class="str">"cmt">//index in the list of non-empty clusters class="type">int iIndx_1 = class="num">0; class=class="str">"cmt">//index in the list of ideas in the cluster class="type">int cIndx_2 = class="num">0; class=class="str">"cmt">//index in the list of non-empty clusters class="type">int iIndx_2 = class="num">0; class=class="str">"cmt">//index in the list of ideas in the cluster class="type">class="kw">double min = class="num">0.0; class="type">class="kw">double max = class="num">0.0; class="type">class="kw">double dist = class="num">0.0; class="type">class="kw">double val = class="num">0.0; class="type">class="kw">double X1 = class="num">0.0; class="type">class="kw">double X2 = class="num">0.0; class="type">int clListSize = class="num">0; class="type">int clustList []; ArrayResize(clustList, class="num">0, clustersNumb); class=class="str">"cmt">//---------------------------------------------------------------------------- class=class="str">"cmt">//let&class="macro">#x27;s make a list of non-empty clusters for (class="type">int cl = class="num">0; cl < clustersNumb; cl++) { if (clusters [cl].count > class="num">0) { clListSize++; ArrayResize(clustList, clListSize); clustList [clListSize - class="num">1] = cl; } } for (class="type">int i = class="num">0; i < popSize; i++) { class=class="str">"cmt">//========================================================================== class=class="str">"cmt">//generating a new idea that replaces the selected cluster center(cluster center offset) if (u.RNDprobab() < p_Replace) { cIndx_1 = u.RNDminusOne(clListSize); for (class="type">int c = class="num">0; c < coords; c++) { val = clusters [clustList [cIndx_1]].centroid [c]; dist = (rangeMax [c] - rangeMin [c]) * class="num">0.8; min = val - dist; if (min < rangeMin [c]) min = rangeMin [c]; max = val + dist; if (max > rangeMax [c]) max = rangeMax [c]; val = u.GaussDistribution(val, min, max, class="num">3); val = u.SeInDiSp(val, rangeMin [c], rangeMax [c], rangeStep [c]); clusters [clustList [cIndx_1]].centroid [c] = val; } } class=class="str">"cmt">//========================================================================== class=class="str">"cmt">//an idea from one cluster is selected if (u.RNDprobab() < p_One) { cIndx_1 = u.RNDminusOne(clListSize); class=class="str">"cmt">//------------------------------------------------------------------------
「聚类交叉育种的两种抽取路径」
这段逻辑处理遗传算法里「从聚类空间取父代」的两种模式:单聚类内取样,以及双聚类之间交叉取样。外汇与贵金属市场高波动,这类种群初始化方式只影响搜索多样性,不预示任何收益。 单聚类分支里,先以 p_One_center 概率直接搬用该聚类质心坐标;否则从聚类已登记 idea 列表中随机抽一条作为父代向量。双聚类分支则先决出两个不同聚类索引 cIndx_1 与 cIndx_2,当聚类列表长度为 1 时两者同指 0,长度为 2 时固定取 0 和 1,更长则随机不重复抽取。 双聚类取样的核心在 p_Two_center:命中则对两质心做区间随机(RNDfromCI),未命中则各自从两聚类随机抽 idea 再做区间随机。你可以把 p_One_center、p_Two_center 两个阈值从 0.5 起调,观察 MT5 策略测试里种群分散度的变化。
if (u.RNDprobab() < p_One_center) class=class="str">"cmt">//select cluster center { for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = clusters [clustList [cIndx_1]].centroid [c]; } } class=class="str">"cmt">//------------------------------------------------------------------------ else class=class="str">"cmt">//random idea from the cluster { iIndx_1 = u.RNDminusOne(clusters [clustList [cIndx_1]].count); for (class="type">int c = class="num">0; c < coords; c++) { a [i].c [c] = parents [clusters [clustList [cIndx_1]].ideasList [iIndx_1]].c [c]; } } } class=class="str">"cmt">//========================================================================== class=class="str">"cmt">//select ideas from two clusters else { if (clListSize == class="num">1) { cIndx_1 = class="num">0; cIndx_2 = class="num">0; } else { if (clListSize == class="num">2) { cIndx_1 = class="num">0; cIndx_2 = class="num">1; } else { cIndx_1 = u.RNDminusOne(clListSize); do { cIndx_2 = u.RNDminusOne(clListSize); } while (cIndx_1 == cIndx_2); } } class=class="str">"cmt">//------------------------------------------------------------------------ if (u.RNDprobab() < p_Two_center) class=class="str">"cmt">//two cluster centers selected { for (class="type">int c = class="num">0; c < coords; c++) { X1 = clusters [clustList [cIndx_1]].centroid [c]; X2 = clusters [clustList [cIndx_2]].centroid [c]; a [i].c [c] = u.RNDfromCI(X1, X2); } } class=class="str">"cmt">//------------------------------------------------------------------------ else class=class="str">"cmt">//two ideas from two selected clusters { iIndx_1 = u.RNDminusOne(clusters [clustList [cIndx_1]].count); iIndx_2 = u.RNDminusOne(clusters [clustList [cIndx_2]].count); for (class="type">int c = class="num">0; c < coords; c++) { X1 = parents [clusters [clustList [cIndx_1]].ideasList [iIndx_1]].c [c]; X2 = parents [clusters [clustList [cIndx_2]].ideasList [iIndx_2]].c [c]; a [i].c [c] = u.RNDfromCI(X1, X2); } } }
◍ 变异与种群修订的实现细节
变异阶段用 epochsNow 映射到 1~200 的进度 x,再以逻辑斯蒂函数 ξ = 1/(1+exp(-(100-x)/k_Mutation)) 控制扰动幅度,dist 取参数区间跨度的 distribCoeff 倍乘 ξ。若 x 接近 100,ξ 趋近 0.5,扰动可能达到区间跨度的一半,低进度时扰动显著收窄。 逐行看变异循环:先按当前进度算 x 与 ξ,dist 受 rangeMax/rangeMin 约束;min/max 夹紧在参数边界内,最后用 GaussDistribution 以 8 为形状参数重采样该维参数。保存代理时调用 SeInDiSp 把连续值吸附到离散步长,再写回 agent 与 a 数组。 Revision 函数负责把子代适应度回灌父代池:先拷贝 agent.f 到父代,再把新想法并入 parents,总量 parentPopSize+popSize。排序后若首个体优于历史最优 fB,则更新 fB 与最优坐标 cB。 聚类仅在首次 revision 初始化 KMeans,之后每轮重跑。遍历 clustersNumb 个簇,取簇内父代中适应度最高者作为质心——若某簇 count 为 0 则保持 -DBL_MAX 不参与。外汇与贵金属参数优化属高风险,回测优解在实盘可能失效。
class=class="str">"cmt">//Mutation for (class="type">int c = class="num">0; c < coords; c++) { class="type">int x = (class="type">int)u.Scale(epochsNow, class="num">1, epochs, class="num">1, class="num">200); class="type">class="kw">double ξ = (class="num">1.0 / (class="num">1.0 + exp(-((class="num">100 - x) / k_Mutation))));class=class="str">"cmt">// * u.RNDprobab(); class="type">class="kw">double dist = (rangeMax [c] - rangeMin [c]) * distribCoeff * ξ; class="type">class="kw">double min = a [i].c [c] - dist; if (min < rangeMin [c]) min = rangeMin [c]; class="type">class="kw">double max = a [i].c [c] + dist; if (max > rangeMax [c]) max = rangeMax [c]; val = a [i].c [c]; a [i].c [c] = u.GaussDistribution(val, min, max, class="num">8); } class=class="str">"cmt">//Save the agent----------------------------------------------------------- for (class="type">int c = class="num">0; c < coords; c++) { val = u.SeInDiSp(a [i].c [c], rangeMin [c], rangeMax [c], rangeStep [c]); a [i].c [c] = val; agent [i].c [c] = val; } } class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class=class="str">"cmt">//—————————————————————————————————————————————————————————————————————————————— class="type">void C_AO_BSO::Revision() { class=class="str">"cmt">//get fitness-------------------------------------------------- for (class="type">int i = class="num">0; i < popSize; i++) { agent [i].f = a [i].f; } class=class="str">"cmt">//pass new ideas to the population-------------------------------------------- for (class="type">int i = parentPopSize; i < parentPopSize + popSize; i++) { parents [i] = agent [i - parentPopSize]; } class=class="str">"cmt">//sort out the parent population---------------------------------------- u.Sorting(parents, parentsTemp, parentPopSize + popSize); if (parents [class="num">0].f > fB) { fB = parents [class="num">0].f; ArrayCopy(cB, parents [class="num">0].c, class="num">0, class="num">0, WHOLE_ARRAY); } class=class="str">"cmt">//perform clustering----------------------------------------------------- if (!revision) { km.KMeansInit(parents, parentPopSize, clusters); revision = true; } km.KMeansInit(parents, parentPopSize, clusters); km.KMeans(parents, parentPopSize, clusters); class=class="str">"cmt">//Assign the best cluster solution as the cluster center-------------------------- for (class="type">int cl = class="num">0; cl < clustersNumb; cl++) { clusters [cl].f = -DBL_MAX; if (clusters [cl].count > class="num">0) { for (class="type">int p = class="num">0; p < parentPopSize; p++) { if (parents [p].label == cl) { if (parents [p].f > clusters [cl].f) { clusters [cl].f = parents [p].f; ArrayCopy(clusters [cl].centroid, parents [p].c, class="num">0, class="num">0, WHOLE_ARRAY); } } } } } }