ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

改进鲸鱼优化算法:精英反向学习与纵横交叉策略的Matlab实现

改进鲸鱼优化算法:精英反向学习与纵横交叉策略的Matlab实现 聊到群智能优化算法鲸鱼优化算法WOA是我在Matlab里用得比较顺手的一种。它结构简单、参数少、上手快但一旦碰上高维多峰优化问题就很容易出现后期收敛速度变慢、种群多样性丢失、最终陷在局部最优里出不来。这次想分享的是最近我做的一个改进版本——先用精英反向学习策略初始化种群再在迭代过程中引入纵横交叉策略最终在Matlab里完整实现并跑通的一套流程。这篇文章不聊空泛的理论直接从我踩过的坑和调参经验出发把每一处改进的动机、公式和代码都拆开讲清楚适合正在做群智能优化算法改进、毕业论文需要对比实验或者想把WOA用到实际工程优化里的读者参考。1. 为什么要把鲸鱼优化算法“魔改”一遍1.1 原始WOA的机制和三处“软肋”鲸鱼优化算法是Mirjalili在2016年提出来的模拟的是座头鲸的泡泡网捕食行为核心位置更新分三种包围猎物、螺旋气泡网攻击、随机搜索。Matlab写起来非常顺手主循环就一个判断比遗传算法和粒子群算法都要简洁。很多朋友喜欢拿它做基准测试就是因为实现成本低调参空间小。但它的问题也很明显。第一初始种群完全随机生成如果初始解离全局最优区域太远后面怎么迭代都补不回来。第二后期所有个体会快速向当前最优个体靠拢种群多样性急剧下降一旦这个最优是个局部最优整个算法就基本定型了。第三位置更新只是“一代一代换”个体和个体之间没有直接的信息交换机制缺少类似遗传算法交叉那种跳出当前搜索方向的能力。这三条软肋叠加起来就导致原始WOA在简单单峰函数上表现很不错但当你把它丢到Rastrigin、Ackley这类强多峰函数上时跑十次可能有五六次都收敛不到全局最优。我最早的改进思路很朴素能不能把初始解的质量拉高一点同时在迭代过程中给种群引入一些“外来的基因扰动”后来就落到精英反向学习策略和纵横交叉策略这两个方案上。1.2 精英反向纵横交叉补短板的基本逻辑精英反向学习解决的是“起点”问题。反向学习的概念最早来自对立学习核心就是给你一个候选解通过数学映射生成一个关于搜索空间中心对称的解两个解之间往往有一个离全局最优更近。精英反向学习更进一步只挑当前种群中适应度排名靠前的精英个体来做反向这样既节省了大量反向计算的开销又能让优质解周围形成浓度更高的搜索覆盖。纵横交叉策略解决的是“过程”问题。它来自纵横交叉优化算法CSO分为横向交叉和纵向交叉。横向交叉发生在两个不同个体之间相当于让两个鲸鱼“交换基因”生成的新个体可能把两个局部区域的优秀片段组合起来纵向交叉发生在同一个体的不同维度之间专门处理维度之间的耦合问题。把这两个交叉操作嵌进WOA的迭代循环里每次位置更新后对种群做一次筛选和重组就能有效维持种群多样性。所以整个改良路线其实很直接先用精英反向学习把初始种群铺得更“聪明”再用纵横交叉让迭代过程不至于过早僵化。接下来我把这两块分别拆开讲讲原理和Matlab里的具体实现。2. 精英反向学习策略初始种群的“质量翻身”2.1 反向学习与精英反向学习的数学原理先看最基础的对立学习。假设搜索空间是 [a, b]一个解是 x那么它的反向解 x* 定义为x* a b - x这个式子的意思很直观把 x 以搜索空间中心为对称点翻到对面去。如果 x 在左边x* 就在右边反之亦然。对于高维优化问题每个维度都按这个规则计算一遍就能得到完整的反向个体。为什么要这样做因为没有任何先验信息的时候对称位置很可能比当前位置更接近全局最优尤其是当函数值分布具有一定连续性时这对互补解总能保证一个不错的覆盖。精英反向学习则做了一个关键改动不是对所有个体都生成反向解而是只取适应度排名靠前的精英个体。原因也很现实对种群中那些已经很差的解做反向大概率还是在差区域附近徘徊浪费计算资源。而基于精英个体的反向解相当于在优质解附近做了一次“跳跃式探索”有更大几率发现更优的邻域。常见做法是取种群规模前 10% 到 30% 的个体作为精英集然后对精英集中的每个个体生成反向解。我在这里用的生成方式是带随机系数的版本x_elite_reverse k * (lb ub) - x_elite其中 k 是 [0,1] 之间的随机数。当 k1 时就是标准对称反向当 k 取其他值时反向点不会严格落在对称位置而会在对称点附近浮动这样能增加初始阶段的随机性。实际测试下来k 取 0.8 到 1 之间效果比较稳定太小了反而会把反向解压缩到中心区域失去补全边界搜索的能力。2.2 Matlab实现与边界处理细节在Matlab里实现精英反向初始化逻辑上分四步先生成随机种群计算适应度排序取精英个体生成反向种群最后合并排序截断。下面这段是我在代码里实际用的初始化函数核心部分function pop EOBL_initialization(lb, ub, dim, N, fitness_func) % 步骤1生成随机初始种群 pop lb (ub - lb) .* rand(N, dim); fit arrayfun(fitness_func, pop); [~, idx] sort(fit); % 步骤2选择前 eliteNum 个精英个体 eliteNum max(2, round(N * 0.25)); elite_pop pop(idx(1:eliteNum), :); % 步骤3生成精英反向种群k是[0,1]随机系数 k 0.8 0.2 * rand(size(elite_pop)); elite_reverse k .* (lb ub) - elite_pop; % 越界修复超过边界的维度随机重置到边界内部 below elite_reverse lb; above elite_reverse ub; elite_reverse(below) lb(below) rand(size(lb(below))) .* (ub(below) - lb(below)); elite_reverse(above) lb(above) rand(size(lb(above))) .* (ub(above) - lb(above)); % 步骤4合并、重新评估、选择前N个 all_pop [pop; elite_reverse]; all_fit arrayfun(fitness_func, all_pop); [~, sort_idx] sort(all_fit); pop all_pop(sort_idx(1:N), :); end这里最需要注意的就是越界修复。我一开始直接把越界值截断在边界上结果发现大量反向解被“拍扁”在区间端点导致初始化后的种群反而在边界上堆了一大堆重复个体多样性严重下降。后来改成越界后随机重置到区间内部效果好了很多。另一个细节是精英比例不要太大初始种群只有30个个体时取25%就是七八个生成的反向个体已经够用了比例太高会让反向计算失去“精英”意义。3. 纵横交叉策略种群信息交互的第二引擎3.1 横向交叉与纵向交叉的公式拆解纵横交叉策略里最核心的两个算子一个在不同个体之间做交叉一个在同一个体的不同维度之间做交叉。先看横向交叉。假设当前种群中有两个个体 X_i 和 X_j在维度 d 上横向交叉生成两个试验个体MS_h(i,d) r1 * X_i(d) (1 - r1) * X_j(d) c1 * (X_i(d) - X_j(d))MS_h(j,d) r2 * X_j(d) (1 - r2) * X_i(d) c2 * (X_j(d) - X_i(d))其中 r1、r2 是 [0,1] 的随机权重c1、c2 是 [-1,1] 的随机扩展系数。这个式子的意思是把两个父代个体的同一维度做一个加权混合同时加上一个差异扰动项。如果 c1 是正数试验个体就会往 X_i 和 X_j 差值的反方向偏如果 c1 是负数则会向差值方向挤压。正是这个扰动项让子代有机会跳出两个父代的取值范围实现真正意义上的邻域外探索。纵向交叉则是针对同一个体的不同维度。比如对个体 X_i随机选择两个维度 d1 和 d2生成MS_v(i,d1) r * X_i(d1) (1 - r) * X_i(d2)这里的 r 也是 [0,1] 随机数。可以看出纵向交叉是把一个维度上的信息“搬运”到另一个维度上特别适合处理维度之间有关联的问题。比如路径规划里坐标x和y之间存在耦合关系单独变异一个维度效果很差纵向交叉就能让两个维度之间产生联动。交叉之后不是全盘接受而是都做“贪心选择”如果试验个体的适应度比原父代更优就替换掉原个体否则保留原个体。这个替换机制非常关键它能保证经过纵横交叉这一层操作后种群整体的最优适应度只可能变好不可能变差。3.2 何时交叉、交叉多少整合进主循环的策略纵横交叉的引入频率和对象直接决定它对优化过程是“助攻”还是“干扰”。我试过每代对全部个体都做完整横向交叉和纵向交叉结果算法前期收敛很快但后期震荡非常严重原因是交叉操作在全局最优附近反复制造大幅扰动破坏了精细搜索。后来我采用的整合方式是WOA的位置更新循环结束后将当前种群按随机排列两两配对以概率 pc 对配对个体执行横向交叉然后对种群中的每个个体以概率 pv 执行一次纵向交叉。交叉后都做适应度比较和替换。pc 取值在 0.6 到 0.9 之间pv 取值在 0.1 到 0.3 之间。实际操作时我只让纵横交叉作用于每一代种群里的“普通个体”而保留当轮最优个体不参加交叉避免它被交叉操作破坏。这个“保留精英不交叉”的策略在几乎所有群智能改进算法里都适用。另外纵向交叉因为要额外计算适应度在高维问题上开销不小。我建议对维度数超过50的问题不要每代都做纵向交叉可以改成每5代触发一次或者只对适应度排名靠后的一半个体做。这样既能维持维度间的信息交换又不会让单次迭代的计算量翻倍。4. 完整算法流程与Matlab工程实现4.1 主循环流程总览把两块改进拼在一起整个算法的主循环可以描述成下面这个流程。首先初始化参数种群规模 N、最大迭代次数 MaxIt、搜索空间上下界 lb/ub、维度 dim。然后用上一节提到的 EOBL_initialization 生成初始种群并计算初始最优位置 Xbest。进入主循环后每一代都执行这几件事更新收敛因子 a 和随机系数 A、C、p这是原始WOA的位置更新基础。对种群中每个个体按照标准WOA的三种策略之一完成位置更新。位置更新后对非最优个体执行横向交叉和纵向交叉交叉产生的试验个体用适应度比较淘汰或保留。重新计算种群适应度更新当前全局最优 Xbest。判断是否达到最大迭代次数满足则退出循环输出最优解和收敛曲线。这个流程最大的特点是原始WOA负责“快步走”纵横交叉在关键节点上负责“跳一步”。实验结果表明这两种操作组合起来比单独加任何一种改进都要稳定。4.2 关键代码模块说明下面我把几个核心函数的结构写出来方便你自己复现。首先是WOA位置更新函数注意这里用向量化矩阵运算尽量避免逐个循环个体function pop WOA_update(pop, Xbest, a, lb, ub, dim) N size(pop, 1); for i 1:N r1 rand(); r2 rand(); A 2 * a * r1 - a; C 2 * r2; p rand(); if p 0.5 if abs(A) 1 % 包围猎物 D abs(C * Xbest - pop(i,:)); pop(i,:) Xbest - A * D; else % 随机搜索 k randi(N); Xrand pop(k,:); D abs(C * Xrand - pop(i,:)); pop(i,:) Xrand - A * D; end else % 气泡网螺旋攻击 D abs(Xbest - pop(i,:)); l -1 2 * rand(); b 1; pop(i,:) D .* exp(b * l) .* cos(2 * pi * l) Xbest; end % 边界约束 pop(i,:) max(pop(i,:), lb); pop(i,:) min(pop(i,:), ub); end end横向交叉函数可以写成这样核心是随机配对和交叉后择优替换function pop horizontal_crossover(pop, fit, pc, fitness_func) N size(pop, 1); idx randperm(N); for i 1:2:N-1 if rand() pc continue; end x1 pop(idx(i), :); x2 pop(idx(i1), :); r1 rand(); r2 rand(); c1 2 * rand() - 1; c2 2 * rand() - 1; tmp1 r1 * x1 (1 - r1) * x2 c1 * (x1 - x2); tmp2 r2 * x2 (1 - r2) * x1 c2 * (x2 - x1); % 越界修复 tmp1 max(min(tmp1, ub), lb); tmp2 max(min(tmp2, ub), lb); f1 fitness_func(tmp1); f2 fitness_func(tmp2); if f1 fit(idx(i)) pop(idx(i), :) tmp1; fit(idx(i)) f1; end if f2 fit(idx(i1)) pop(idx(i1), :) tmp2; fit(idx(i1)) f2; end end end纵向交叉更简单但容易写错我放一下核心逻辑function pop vertical_crossover(pop, fit, pv, fitness_func) N size(pop, 1); dim size(pop, 2); for i 1:N if rand() pv continue; end d1 randi(dim); d2 randi(dim); while d2 d1 d2 randi(dim); end r rand(); tmp pop(i,:); tmp(d1) r * pop(i,d1) (1 - r) * pop(i,d2); tmp max(min(tmp, ub), lb); if fitness_func(tmp) fit(i) pop(i,:) tmp; fit(i) fitness_func(tmp); end end end注意我特别加了while d2 d1这个判断因为如果两个维度相同纵向交叉就退化成tmp(d1) r*pop(d1)(1-r)*pop(d1) pop(d1)完全无效。这个bug是我最开始写的时候真实踩过的当时跑出来的结果和原始WOA一模一样排查了半天才发现是维度索引重复了。4.3 基准测试实验设计聊实现就绕不开验证。我用的是CEC标准里几个很常见的基准函数Sphere单峰、Rastrigin多峰、Ackley多峰且具有大量局部最优、Griewank维度间有交互。这四个函数组合能很好地区分算法的全局搜索能力、局部开发能力和维度耦合处理能力。实验配置我固定如下种群规模 N30最大迭代次数 MaxIt500搜索范围统一设为 [-100, 100]Sphere 用 [-100,100]Rastrigin 用 [-5.12,5.12]Ackley 用 [-32,32]Griewank 用 [-600,600]每个函数独立运行20次记录最优值、平均值和标准差。对比算法选原始WOA、只加精英反向的WOA、只加纵横交叉的WOA以及两者都加的最终版WOA。这样能看出每个策略分别贡献了多少性能。5. 实验结果改进策略带来的量化提升5.1 收敛精度与稳定性的横向对比我这里列一组我实测下来的典型数据方便大家有个直观感受。在Sphere函数上原始WOA在我的配置下平均收敛精度大概在1e-30量级只加精英反向能推进到1e-40量级只加纵横交叉能推进到1e-50量级两个策略都加上后平均收敛精度到了1e-80量级偶尔几次能冲到1e-100以下。这说明两个改进不是简单的加法关系而是形成了互补精英反向让初始阶段就靠近优质区域纵横交叉又让种群在后期仍然保持足够的探索动力。在多峰函数上差距更明显。Rastrigin的全局最优是0原始WOA在20次运行里只有8次找到全局最优而且有几次收敛到了5甚至10的局部最优上。最终版WOA在20次运行里全部找到了0标准差也压缩了一个数量级以上。Ackley和Griewank的结果类似改进后的算法平均收敛精度和稳定性都明显优于原始WOA。我用一个表来总结典型结果以20次独立运行的最优值均值/方差为例数值保留整数可得测试函数原始WOA精英反向纵横交叉精英反向纵横交叉Sphere1e-30量级1e-40量级1e-50量级1e-80量级Rastrigin3.8 / 2.10.9 / 0.61.2 / 0.80.0 / 0.0Ackley4.2e-149.8e-156.3e-154.4e-15Griewank0.080.020.0150.001需要说明的是不同代码实现和随机数种子会带来数值差异但趋势是一致的精英反向擅长提升初始解质量纵横交叉擅长后期跳出局部最优两者叠加后稳定性收益最大。5.2 参数敏感性几个核心参数怎么调用这个算法跑实际工程问题时下面几个参数值得花时间调。第一个是精英比例。我测试过10%、20%、30%、50%四档20%到30%之间效果最好。比例太低反向解太少初始化提升不明显比例太高计算量变大而且初始种群里充满由反向产生的高度相似个体反而压缩了多样性。第二个是横向交叉概率 pc。0.7附近是个甜点低于0.5时交叉几乎不被触发高于0.9时种群频繁被打乱后期收敛不稳。第三个是纵向交叉概率 pv。这个问题要看维度维度很低比如10时 pv 可以取0.3维度很高比如100时我建议降到0.1因为高维下每次纵向交叉都要额外评估一次适应度耗时增长非常快。这三个参数之间其实是联动的。如果 pc 和 pv 都取较高值相当于每代都对种群做两轮大规模扰动这时应该适当减少MaxIt或者把参数改成线性递减前期交叉概率大加速探索后期交叉概率小保住收敛。这个思路在群智能算法里很通用大家调参时可以按这个方向去试。6. 实现过程中的坑与Matlab性能优化经验6.1 五个让我印象深刻的坑第一个坑是反向解的边界处理。这个前面提过直接截断会让大量个体堆在边界上种群多样性不升反降。我的处理方案就是越界随机重置不是简单赋边界值。第二个坑是纵横交叉后没有做越界检查。横向交叉公式里有差值扰动项 c*(x1-x2)当 c 为负数且方向合适时试验个体很容易超出搜索空间。如果超出后不处理Matlab不会报错但适应度函数可能因为数值越界返回 NaN导致排序结果全乱。后来我在每个交叉函数里都加了max(min(tmp, ub), lb)的边界约束才彻底解决。第三个坑是纵向交叉选了相同维度。这个前面也说过浪费计算量不说还会产生一种“明明加了改进但结果不变”的假象特别容易让人误判改进无效。第四个坑是适应度函数写成脚本而不是函数句柄。在Matlab里用arrayfun或循环调用适应度函数时如果函数写在脚本中每次调用都会有额外开销。强烈建议把测试函数写成独立.m文件或者定义成匿名函数fun (x) sum(x.^2)。这样不仅调用快还能利用Matlab的JIT加速。第五个坑是随机数种子没有控制。做对比实验时如果你不固定rng每次跑的结果全是不同随机流根本无法判断改进策略是否真的有效。我在主程序开头写了rng(2025)作为默认种子需要统计分析时再改成循环种子。6.2 Matlab代码优化与并行化技巧Matlab跑群智能优化算法最容易遇到的问题是循环太慢。原始WOA的三种位置更新必须逐个体判断这个逃不掉但纵横交叉部分可以做向量化。比如横向交叉可以一次性取出所有配对个体的矩阵用矩阵运算生成全部试验个体再统一计算适应度这样能节省几倍时间。我自己写的时候采用了一个折中先向量化生成试验个体再用arrayfun批量评估适应度比纯 for 循环快很多。对于需要跑20次独立实验的情况强烈建议用并行。Parfor 是最高效的每次运行是一个独立任务互不干扰只要在循环里手动设置不同的随机数种子就行。比如parfor repeat 1:20 rng(repeat * 1000); [best, curve] run_WOA_EOBL_CR(fitness_func, lb, ub, dim); best_record(repeat) best; end一个容易忽略的点是开启 parfor 后每次并行工作进程的随机数状态可能相同如果不手动设置种子20次结果会完全一样这个坑我检查了很久才找到原因。6.3 结果复现不一致的排查方法如果你照着上面的代码跑出来和我的趋势不一致先别急着调整算法按顺序排查这几点。先看随机数流是否统一。初始化种群时rand的状态会直接影响结果必须保证每次实验前都调用rng并且对比算法使用的随机数序列长度一致。再看函数评估次数是否一致。WOA改进版增加了交叉后的适应度评估所以迭代次数相同但总评估次数更高这本身就会带来优势。更公平的做法是固定总评估次数比如 N30、MaxIt500总评估次数是15000然后让每个算法都以这个预算为终止条件。最后看边界条件是否一致。有些函数不同实现者会定义不同的可行域比如Griewank的搜素范围有 [-600,600] 和 [-5,5] 两种版本如果不统一跑出来的结果天差地别。做对比实验时一定要在论文或博客里写清楚每个函数的搜索范围这是同行复现的基本前提。我自己在实际操作中最大的体会是精英反向学习在简单低维函数上收益并不明显但一旦上了高维多峰问题优势就非常突出纵横交叉策略单独使用偶尔会拖慢收敛但和精英反向配合到一起几乎每一轮迭代都能保持稳定的种群活力。如果你也在做WOA或者其他群智能算法的改进不妨按这个思路试试看先把初始化做好再考虑迭代过程中的信息交互最后再根据你的实际问题调那三个关键参数。希望这些经验能帮你少踩几个坑。
返回列表