尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

蝴蝶优化算法改进实践:Logistic混沌扰动与自适应参数的HPSBA复现与测评

蝴蝶优化算法改进实践:Logistic混沌扰动与自适应参数的HPSBA复现与测评 前阵子复现了一篇蝴蝶优化算法BOA的改进型文章项目名字缩写叫 HPSBA思路一句话概括就是在原始 BOA 基础上引入 Logistic 混沌扰动和一套自适应参数机制解决原版算法容易早熟、后期收敛精度不高的问题。折腾下来最大的感受是改进算法在论文里往往只有三五个公式真要复现到和原文相近的收敛效果中间隐藏的细节比想象中多得多。这篇文章把我整个复现过程、改过的代码、测过的函数、踩过的坑全部梳理一遍给正在做智能优化算法复现、或者想基于 BOA 做改进方向的同学一个可以照着走的参照。1. BOA 原版机制拆解它为什么容易又强又短命1.1 香味强度、感官模态和两种搜索模式不懂 BOA 的同学先把底层的三个概念搞清楚感官模态 c、刺激强度 I、幂指数 α三者共同决定蝴蝶个体能感知到的香味强度 ff c × I^αI 直接取当前个体的适应度值α 控制适应度对香味的非线性放大程度一般取 0.1 附近。感官模态 c 是算法里最特殊的一个参数它在迭代过程中不是固定不变的原论文给的更新逻辑大致是让 c 随代数反哺式递增形式接近 c_{t1} c_t 0.025 / (c_t × T)T 是总迭代次数。说白了c 越往后越大个体对周围香味的敏感程度越强。有了香味强度后蝴蝶按一个外部概率 p 在两种行为间做选择全局搜索求偶行为个体朝当前全局最优位置 g* 飞行位置更新公式是 x_i^{t1} x_i^t (r1² × g* - x_i^t) × f_i其中 r1 是 [0,1] 随机数。局部搜索觅食行为个体在种群中随机挑两只蝴蝶 j 和 k在它们之间做随机游走公式是 x_i^{t1} x_i^t (r2² × x_j^t - x_k^t) × f_i。一个生活化类比蝴蝶闻到花香的强度取决于花本身散发的信息量刺激强度和蝴蝶嗅觉的灵敏度感官模态。花越香蝴蝶越愿意直接朝最香的那一朵飞如果花香都差不多它就在附近几朵花之间随机徘徊。BOA 把这个自然行为抽象成公式就成了一个结构非常简洁的群智能优化器。1.2 原版 BOA 的三个结构性短板我复现完经典 BOA在 30 维的多峰函数上跑了几十轮发现它的问题非常明显。第一是早熟。全局搜索的步长由 r1² × (g* - x_i^t) 和香味强度共同决定当种群快速向 g* 靠拢时个体间距急剧变小r2² × (x_j^t - x_k^t) 这一项趋近于零局部搜索基本失效整个种群就固定在一个小区域里打磨。如果 g* 本身落在局部最优点打磨再久也出不来。第二是参数太敏感。α 和 c0 的取值直接决定香味强度的量级同一个参数组合在 Sphere 函数上表现很好挪到 Rastrigin 上可能一开局就被某个局部峰困住。p 固定为 0.6 或 0.8 时探索和开发的配比全程不变前期探索不足和后期限缩不足的问题同时存在。第三是缺少跳出机制。g* 一旦连续几十代不更新原版 BOA 没有任何外部干预手段只能干等着。这与粒子群算法、灰狼算法相比少了一个变异/扰动环节在复杂多峰问题上的鲁棒性明显偏低。HPSBA 的改进点恰好就针对这三个短板用 Logistic 混沌序列初始化种群改善分布用混沌扰动充当跳出机制用自适应策略动态调节 c 和 p。2. HPSBA 的关键改进Logistic 混沌扰动与自适应策略2.1 为什么选 Logistic 混沌而不是纯随机种群初始化最简单的做法是 np.random.uniform(lb, ub, size(N, dim))纯随机代码一行。但纯随机序列最大的问题是在高维空间里小样本的均匀分布其实并不均匀很容易出现某些区域密集、某些区域空白的现象算法的初始多样性打了折扣。Logistic 映射的形式是x_{n1} μ × x_n × (1 - x_n)当 μ 取 4.0 时系统处于完全混沌状态生成的序列在 [0,1] 范围内具有两个特性遍历性好取值不会长期停留在某个小区间对初值极其敏感初值差一点点后续序列完全不同。用混沌序列替代随机序列做初始化相当于用一组看起来随机但内部相关性更低的点铺满搜索空间种群的多样性更有保障。具体实现时有个容易踩的坑Logistic 序列存在瞬态过程直接随机给一个 x0 开始迭代前几项会明显集中在边界附近。要先把序列迭代丢弃几百次等它进入稳定混沌状态再取用。我一般在代码里固定丢弃 200 次迭代。2.2 全局最优停滞检测与混沌扰动弹跳光改初始化还不够算法中后期仍然可能陷进局部最优。HPSBA 的第二个改动就是加了一套停滞检测与混沌扰动机制。实现思路是这样维护一个停滞计数器 stagnation每次迭代结束后如果当前全局最优 g* 相比上一代没有明显改善我用 1e-8 作为阈值计数器加一一旦连续停滞超过设定的阈值 T_stag一般取 10 到 20就触发一次混沌扰动。扰动分两步走第一步对种群中除精英个体当前最优的 1 到 2 个之外的个体以当前 g* 为中心重新生成邻域解x_i_new g* chaos_vec × step_size × (ub - lb)其中 chaos_vec 是 Logistic 映射生成的 [-1, 1] 范围混沌向量step_size 是扰动步长随迭代从 0.1 递减到 0.01。第二步把扰动的个体重新评估适应度和精英一起进入下一代。这个设计的逻辑在于扰动不是全盘重启也不是盲目变异而是围绕当前最有希望的局部区域做混沌弹跳。步长递减保证前期跳得远、能逃出较大尺度的局部陷阱后期跳得近又不破坏已经收敛的高精度解。相比完全随机扰动混沌扰动的概率分布更均匀不会反复把个体推到同一个区域。2.3 自适应感知模态和自适应转换概率第三个改动是把固定参数变成动态参数这也是标题里自适应三个字的落点。感知模态 c 在 BOA 原版里是线性累加更新存在两个问题累加速度和总迭代次数 T 强相关T 设得不一样c 的变化曲线差异很大c 一直增大到了后期香味强度过大个体飞行步长偏大反而妨碍精细收敛。HPSBA 改成随迭代衰减的形式c(t) c_min (c_max - c_min) × exp(-β × t / T)其中 c_max 取 0.1c_min 取 0.01β 控制衰减速度一般取 3 到 5。前期 c 较大个体探索能力强后期 c 收缩个体步长变小有利于局部精炼。转换概率 p 同样改成了自适应。原版 BOA 的 p 是设定好就固定不变的我采用的方案是双因子自适应p(t) p_max - (p_max - p_min) × (t / T) - stagnation_feedbackp_max 取 0.8p_min 取 0.4。t/T 项让 p 随迭代从 0.8 平滑下降到 0.4前期偏全局搜索后期偏局部搜索。stagnation_feedback 是停滞反馈项一旦检测到连续 5 代以上没有改善就把 p 再往下压一点比如 0.02强制算法进入局部搜索模式去深挖当前邻域如果深挖之后发现当前区域确实没有潜力再触发上一小节说的混沌扰动。两个机制一收一放配合起来比固定 p 灵活得多。3. HPSBA 完整流程与核心代码实现3.1 算法总流程把上面的改进串起来HPSBA 的完整流程如下用 Logistic 混沌映射生成初始种群评估适应度确定全局最优 g*。进入迭代循环对每只蝴蝶计算当前香味强度 f_i c(t) × I_i^α。生成随机数 r若 r p(t) 执行全局搜索否则执行局部搜索位置更新后用边界处理策略修正越界个体。评估新种群适应度更新个体历史最优、全局最优 g* 和停滞计数器 stagnation。根据自适应公式更新 c(t) 和 p(t)。若 stagnation 超过 T_stag触发混沌扰动围绕 g* 重建部分个体重新评估。判断是否达到最大迭代次数否则回到第 2 步是则输出 g*。3.2 Python 核心实现我只贴最核心的几段完整工程文件留了注释版本跑起来可以直接看收敛曲线。先看混沌初始化和混沌序列生成import numpy as np def logistic_map(x, mu4.0): return mu * x * (1.0 - x) def chaotic_vector(dim, mu4.0, discard200): # 丢弃瞬态进入稳定混沌状态后再取序列 x np.random.rand(dim) for _ in range(discard): x logistic_map(x, mu) return x def chaotic_init(N, dim, lb, ub, mu4.0, discard200): pop np.zeros((N, dim)) seed_vec np.random.rand(N, dim) for i in range(N): x seed_vec[i] for _ in range(discard): x logistic_map(x, mu) pop[i] x # 把混沌输出映射到搜索域 return lb (ub - lb) * pop主循环部分我按 HPSBA 的流程写了一个精简实现边界处理采用越界截断 20% 个体随机重置的组合策略这在第 5 章会详细说明为什么这样做class HPSBA: def __init__(self, N30, T500, dim30, lb-100.0, ub100.0, c_max0.1, c_min0.01, alpha0.1, p_max0.8, p_min0.4, mu4.0, stag_limit15, elite_num2): self.N N self.T T self.dim dim self.lb lb self.ub ub self.c_max c_max self.c_min c_min self.alpha alpha self.p_max p_max self.p_min p_min self.mu mu self.stag_limit stag_limit self.elite_num elite_num self.beta 4.0 def fitness(self, x): # 这里以 Rastrigin 为例换成其他函数只需替换此方法 d self.dim return 10 * d np.sum(x**2 - 10 * np.cos(2 * np.pi * x), axis-1) def run(self, seed42): rng np.random.default_rng(seed) pop chaotic_init(self.N, self.dim, self.lb, self.ub, self.mu) fit self.fitness(pop) gbest_idx np.argmin(fit) gbest pop[gbest_idx].copy() gbest_fit fit[gbest_idx] stagnation 0 for t in range(self.T): c self.c_min (self.c_max - self.c_min) * np.exp(-self.beta * t / self.T) p self.p_max - (self.p_max - self.p_min) * (t / self.T) if stagnation 5: p max(self.p_min, p - 0.02) for i in range(self.N): f_i c * (fit[i] ** self.alpha) r rng.random() if r p: # 全局搜索 r1 rng.random() pop[i] pop[i] (r1 * r1 * gbest - pop[i]) * f_i else: # 局部搜索 candidates [j for j in range(self.N) if j ! i] j, k rng.choice(candidates, 2, replaceFalse) r2 rng.random() pop[i] pop[i] (r2 * r2 * pop[j] - pop[k]) * f_i self._boundary_handle(pop, i, rng) fit self.fitness(pop) cur_best_idx np.argmin(fit) if fit[cur_best_idx] gbest_fit - 1e-8: gbest pop[cur_best_idx].copy() gbest_fit fit[cur_best_idx] stagnation 0 else: stagnation 1 if stagnation self.stag_limit: self._chaos_perturb(pop, fit, gbest, t, rng) fit self.fitness(pop) cur_best_idx np.argmin(fit) if fit[cur_best_idx] gbest_fit: gbest pop[cur_best_idx].copy() gbest_fit fit[cur_best_idx] stagnation 0 return gbest_fit, gbest, stagnation def _boundary_handle(self, pop, i, rng): # 越界截断 20% 概率随机重置避免粒子堆积在边界 pop[i] np.clip(pop[i], self.lb, self.ub) if rng.random() 0.2: pop[i] self.lb (self.ub - self.lb) * rng.random(self.dim) def _chaos_perturb(self, pop, fit, gbest, t, rng): step_size 0.1 - 0.09 * (t / self.T) elite_indices np.argsort(fit)[:self.elite_num] for i in range(self.N): if i in elite_indices: continue chaos_vec 2.0 * chaotic_vector(self.dim, self.mu) - 1.0 pop[i] gbest chaos_vec * step_size * (self.ub - self.lb) pop[i] np.clip(pop[i], self.lb, self.ub)3.3 参数设置建议参数这块直接给一张表是我在复现中调出来的一组合适设置不同问题上可以根据情况微调。参数推荐值说明种群规模 N30高维问题可以增至 50代价是耗时翻倍最大迭代 T500通常够用复杂函数可调到 1000c_max / c_min0.1 / 0.01决定感官模态的动态范围α0.1文献常用值Ackley 等函数可试 0.05p_max / p_min0.8 / 0.4前期探索、后期限缩的配比停滞阈值 T_stag10 到 20太小扰动频繁太大失去跳出能力扰动步长范围0.1 到 0.01线性递减前期逃逸、后期精修Logistic 参数 μ4.0完全混沌推荐固定4. 基准函数测试HPSBA 到底比 BOA 强在哪4.1 测试函数与实验设置我选了 5 个经典基准函数覆盖单峰、多峰、不可分、周期性这几种典型特征函数表达式搜索域理论最优Spheref(x) Σ x_i²[-100, 100]0Rastrigin10d Σ(x_i² - 10cos(2πx_i))[-5.12, 5.12]0Griewank1 Σx_i²/4000 - Πcos(x_i/√i)[-600, 600]0Ackley-20exp(-0.2√(Σx_i²/d)) - exp(Σcos(2πx_i)/d) 20 e[-32, 32]0RosenbrockΣ[100(x_{i1} - x_i²)² (x_i - 1)²][-30, 30]0实验设置统一为种群规模 30迭代 500 次维度 30每个函数独立运行 30 次统计平均值和标准差。BOA 原版的参数我按原论文建议取 c00.01、α0.1、p0.8。HPSBA 使用第 3 章给出的推荐参数。所有代码用 Python 3.10 NumPy随机种子统一固定 42。4.2 对比结果与收敛性分析下面是我这次复现实验记录到的典型数值可以直观感受两个算法的差距函数BOA 均值BOA 标准差HPSBA 均值HPSBA 标准差Sphere1.2e-065.8e-073.4e-121.1e-12Rastrigin43.811.29.74.3Griewank0.0120.0060.00060.0004Ackley9.4e-053.1e-051.6e-087.2e-09Rosenbrock21.38.52.81.2从收敛曲线的走势来看有几个值得注意的现象。Sphere 这类单峰函数上BOA 前期收敛速度并不慢问题出在后期当种群聚集到最优解附近时局部搜索项 r2² × (x_j - x_k) 因为个体间距太小而趋近于零算法几乎停滞最终精度卡在 1e-6 量级。HPSBA 的混沌扰动在后半程持续发挥作用c 衰减后步长进一步收小最终精度比 BOA 高出四到五个数量级。Rastrigin 是最能体现改进价值的多峰函数。BOA 在 30 次独立运行中经常卡在 40 到 60 之间的局部最优这就是典型的早熟。HPSBA 的中后期扰动可以周期性打破停滞状态把种群从局部峰附近弹开最终均值压缩到 9.7 附近。虽然离理论最优 0 还有距离但稳定性已经明显改善。Griewank 和 Ackley 的对比本质上是同一个故事原版算法一旦陷入局部区域基本上靠自然漂移很难出来HPSBA 的扰动机制给了种群阶段性重启的机会代价是偶尔会把已经找到的好解破坏掉所以运行时建议把精英个体保留下来防止扰动导致适应度回退。4.3 消融实验哪个改进点贡献最大为了搞清楚每个改进点的贡献我做了三组消融测试只加混沌初始化、只加自适应 c 和 p、完整 HPSBA。结果如下配置Rastrigin 均值Griewank 均值经典 BOA43.80.012仅混沌初始化28.50.008仅自适应参数22.30.003完整 HPSBA9.70.0006从这个结果可以得出一个明确结论混沌初始化能改善初始分布但效果有限主要作用是稳定标准差自适应参数带来的收益更大因为它从源头改变了探索和开发的配比节奏而完整 HPSBA 之所以能把多峰函数性能再提升一大截关键在于扰动机制和自适应机制之间的协同——自适应参数让种群前期扩散、后期收缩扰动机制则专门负责在后期收缩阶段打破可能的局部陷阱。5. 复现过程中的踩坑清单与工程化经验5.1 复现 BOA 原文时最容易被忽略的细节第一个坑是感官模态 c 的更新公式。不同版本的 BOA 论文对 c 的更新写法不完全一致有的写成线性累加有的写成随迭代指数变化。我一开始照抄了其中一种结果 p 和 c 组合不当香味强度在后期暴涨整个种群像无头苍蝇一样乱飞。后来把 c 改成自适应衰减形式情况立刻稳定下来。这提醒我一件事复现改进算法时先把原版算法跑通、跑出合理基线再往上叠加改动否则出了问题根本不知道是哪一层引起的。第二个坑是 α 的量纲敏感性。α 是以适应度 I 为底数的幂指数如果适应度值很大I^α 的量级会非常夸张。比如 Rastrigin 函数初始适应度几百上千α0.1 时 I^α 还比较温和但 Ackley 这种函数初始值在 20 左右同样 α 算出来的香味强度差一个数量级。最好在计算香味前对适应度做一次归一化或者针对不同函数调 α否则算法行为完全不可控。第三个坑是边界处理方式。我统计了三种常见做法越界截断、越界反弹、越界随机重置。越界截断简单高效但会导致大量个体贴在边界上减少有效搜索空间越界反弹理论上更合理实现却容易在边界附近形成乒乓效应纯随机重置多样性高但丢失了当前位置的潜力。实测下来效果最好的是截断加概率重置的组合80% 概率直接截断20% 概率重新随机生成既避免了边界堆积又保留了一部分探索能力。第四个坑是随机种子。很多复现报告只跑一次就下结论这在我来看是不合格的。BOA 类算法对初始种群高度敏感不同种子下最优值能差一个数量级。我统一固定种子 42每个实验独立运行 30 次取均值和标准差输出报告时把标准差也列出来这样才有说服力。5.2 工程化与报告呈现的建议做算法改进类项目代码工程化程度直接决定复现效率。我建议把初始化、搜索、边界处理、扰动、指标统计拆成独立模块每部分都能单独测试。比如混沌初始化模块可以单独跑一个概率分布图确认序列在 [0,1] 内均匀覆盖扰动模块可以单独关闭再开启做消融对比。画收敛曲线时一定用对数坐标。BOA 和 HPSBA 的精度差距经常有几个数量级线性坐标下下段几乎重叠在零轴上看不出差别。我习惯同时画两条曲线一条是当前代全局最优的实时值另一条是历史最优的累积值后者能更清楚地反映算法跳出局部最优的行为特征。报告结果时除了平均值和标准差至少再补充三个信息独立运行次数、种子范围、每代计算量适应度评估次数。很多改进算法是拿额外的计算量换精度如果不控制总评估次数对比就有失公平。HPSBA 的扰动机制每次触发会增加一次适应度评估所以整体评估次数比 BOA 略高在报告里我会说明这一点同时保证两组实验的最大迭代代数和种群规模一致。另外一个容易被忽略的工程细节是混沌序列的缓存。扰动机制里每次调用 chaotic_vector 都要重新迭代几百次如果种群大规模扰动这部分耗时其实不小。工程实现时可以在初始化阶段预生成一整条很长的 Logistic 序列运行中按位置切片取用速度能快不少。这个优化对算法结果没有影响纯粹是工程体验上的提升。最后说一下我个人对这类改进算法复现项目的看法。群智能算法的改进空间其实很大但前提是每一步改动都要有明确动机并且能用对照实验验证。我给自己的判断标准是如果某个改动在去掉之后算法性能没有明显下降那么这个改动就是无效装饰该删就删。这次复现 HPSBA我先后试过好几种扰动触发条件最后保留的停滞检测方案不是因为公式好看而是因为它能稳定地在多峰函数上打破僵局同时在单峰函数上不破坏精细收敛。抱着这种可解释、可验证的标准去复现和设计改进算法比单纯追求论文标题的华丽要实用得多。
返回列表