
在运筹学圈子里混久了听到“鲸鱼算法”和“线性规划”这两个词被放到一起第一反应大概率是同一个这不是拿高射炮打蚊子吗线性规划有单纯形法、内点法这些精确求解算法成熟可靠收敛速度也够快为什么还要拿个2016年才提出的元启发式算法来凑热闹但实际把这两者结合着做了一次完整求解实验之后我的看法发生了一点变化。鲸鱼算法Whale Optimization Algorithm, WOA解线性规划并不是要取代Cplex、Gurobi或者MATLAB里的linprog而是在教学演示、算法对比、非线性/非凸扩展预研、以及部分需要快速得到一个“足够好”可行解的工程场景里有它独特的价值。尤其是当你需要处理的目标函数稍作变形就变成非线性、甚至非凸的时候传统单纯形法直接歇菜而启发性算法反而能继续跑下去。这篇文章我就把整套思路、代码、调参技巧和踩过的坑完整拆开讲适合正在学优化算法的学生、准备数学建模竞赛的团队以及对元启发式算法在运筹问题里怎么落地感兴趣的朋友。1. 鲸鱼算法核心逻辑拆解一头座头鲸是怎么解方程的1.1 从“泡泡网捕食”到数学模型的三个基本动作鲸鱼算法是Mirjalili在2016年提出的一种群智能优化算法核心灵感来自座头鲸一种非常独特的捕食行为泡泡网捕食。座头鲸会绕着鱼群游动同时向上吐出一圈螺旋状的气泡网把鱼群逼到海面中央最后张大口从下方一口吞进去。这个行为翻译成优化算法就是三个关键动作包围猎物、气泡网攻击、随机搜索。先把这三个动作的数学模型贴出来后面的一切都建立在这套公式之上。第一个动作包围猎物。算法假设当前种群中适应度最好的那个个体就是我们“瞄准”的猎物位置其他鲸鱼个体向它靠拢公式如下D |C · X*(t) - X(t)| X(t1) X*(t) - A · D其中X*是当前最优解的位置X是当前鲸鱼个体的位置A和C是系数向量A 2a·r1 - aC 2·r2r1、r2是[0,1]之间的随机向量a则随着迭代从2线性递减到0。第二个动作气泡网攻击。这个动作包含两种机制一种是收缩包围通过缩小a的值让A的幅值逐渐减小相当于包围圈越收越紧另一种是螺旋更新位置模拟鲸鱼螺旋上升吐气泡的路径X(t1) D · e^(b·l) · cos(2πl) X*(t)其中D |X*(t) - X(t)|b是决定螺旋形状的常数通常取1l是[-1,1]之间的随机数。算法用随机概率p来决定当前个体走收缩包围还是走螺旋路径p 0.5时收缩包围否则螺旋更新。这个随机切换很关键它保证了种群不是一路直愣愣地冲向当前最优而是保持了逼近方式的多样性。第三个动作随机搜索。当|A| ≥ 1时当前个体不再向当前最优解靠拢而是随机选择一个个体作为参考位置进行更新D |C · X_rand - X(t)| X(t1) X_rand - A · D这个设计是为了跳出局部最优。|A| ≥ 1意味着步长拉大个体被“踢”出当前的搜索区域去更远的地方探索新的可能性。本质上是算法在跑偏或者陷入局部陷阱时的一种自救机制。1.2 为什么鲸鱼算法能全局寻优探索与开发的平衡我刚开始接触WOA的时候最困惑的一点是既然每个个体都在向当前最优靠拢那不是很容易一开始就被某个局部最优带偏吗后来把迭代过程打印出来仔细看才明白它真正聪明的地方在于探索和开发之间的动态平衡。线性递减的a值就是那个平衡杠杆前期a接近2A的幅值普遍大于1随机搜索发生的概率高鲸鱼散布范围广探索空间大后期a逼近0A幅值小于1的概率变大个体逐渐收拢到最优解附近开发精度提升。你可以把它想象成逛菜市场。前半小时你肯定不会直奔某个摊位而是把整个市场都转一遍看看哪个摊位价格便宜、菜新鲜这就是探索最后十分钟锁定目标摊位专心挑菜砍价这就是开发。鲸鱼算法的参数设计其实就是在模拟人在真实决策中的这种“先广撒网、后重点捕捞”的策略。和粒子群算法PSO比WOA没有“速度”这个显式变量更新方式更直接不需要额外调惯性权重、全局学习因子、个体学习因子这组参数整体参数更少对新手更友好。但代价是它的问题依赖性强有些问题上收敛速度快有些问题上精度不如PSO。和遗传算法比WOA没有交叉、变异这些复杂的遗传算子实现起来代码量小很多特别适合快速验证想法。2. 思路设计用鲸鱼算法解线性规划到底在解什么2.1 需求拆解LP有精确算法为什么还要用元启发式线性规划的标准形式是min cᵀx约束条件为Ax ≤ b、x ≥ 0这类问题单纯形法和内点法已经解决得非常漂亮成熟商业求解器一分钟内能求解上百万变量的规模。所以并不是说鲸鱼算法解线性规划会比 linprog 更快更准这不可能也不应该这样去用。真正合理的组合动机有三个。第一个动机非线性扩展的预研。很多实际问题里目标函数稍微一变就不线性了比如加入二次项、绝对值、逻辑约束问题就变成了非线性规划或混合整数非线性规划。此时精确算法的适用性下降而元启发式算法可以作为快速获得优质初始解的手段。先用WOA跑一遍非线性模型得到一组比较好的初始点再用局部精确优化器精修这种“全局探索局部精修”的混合策略在工程里非常常见。第二个动机处理目标函数不可导、不连续甚至黑箱的情况。有些仿真驱动的优化问题目标函数是一个模拟软件的输出比如有限元仿真的形变结果这种场景下解析表达式根本不存在传统LP/IPM算法无从下手WOA不需要导数信息只要能把参数传进去、能拿到适应度值就行。第三个动机教学和算法对比研究。很多课程设计和建模竞赛中大家需要把元启发式算法和精确求解器做对照实验验证算法的收敛性、鲁棒性这种场景下“用鲸鱼算法解线性规划”是一个很好的标准测试案例因为它有精确解作为标杆可以量化评价元启发式算法的求解质量。2.2 关键方案选型目标函数、约束处理、边界设置用WOA解LP不能把约束条件直接丢给算法不管因为WOA本质上是一个无约束优化器。你给它的输入是一个位置向量x它输出一个适应度值仅此而已。所以关键工作在于如何把带约束的LP改造成适合WOA处理的形式。这里我采用的方法是外点罚函数法。核心思路是把违反约束的程度作为惩罚项加到目标函数上改造后的适应度函数形如F(x) cᵀx λ · Σ max(0, gᵢ(x))²其中gᵢ(x) ≤ 0是原问题的不等式约束λ是一个很大的惩罚系数。初始的线性规划问题就变成了一串无约束优化问题虽然理论上惩罚因子要趋向无穷大才能严格等价但实际计算中只要λ取得足够大比如1e6以上罚函数会迫使不可行解付出极高代价算法自然倾向于搜索可行域内部。这里为什么用平方而不是一次项原因在于平方项在可行域边界附近导数连续不会在边界处形成尖角搜索过程更平滑。对等式约束hⱼ(x) 0同理加上λ · Σ hⱼ(x)²。边界设置方面原问题LP通常有x ≥ 0的约束加上每条决策变量的上限约束共同构成搜索空间的上下边界。上下边界的取值会直接影响求解效果边界设得太松搜索空间大算法收敛慢结果精度差太紧则可能把真实最优解排除在外。一个稳健的做法是先用linprog把精确解求出来然后把边界设成在精确解附近放宽一点点这样既保证可行域包含最优解又给算法一个相对狭小的搜索空间。2.3 参数选型种群规模、最大迭代次数、a衰减方式鲸鱼算法的核心超参数没有很多但每改一个对结果的影响都不小下面是我在多次实验中总结出来的经验参数范围和注意事项。种群规模N一般取20到50。太小了容易早熟整个种群被一个局部最优带偏太大了计算量成倍增长但精度提升并不成正比。我做标准测试问题时常用30既能看到种群多样性又不需要等太久。最大迭代次数T根据问题的维度来定二维问题500次足够十维以上的问题至少2000次起步。线性递减的a值是最常用的衰减方式从2线性降到0简单稳定但也可以考虑指数衰减或者自适应衰减前期让a降得更慢保留更长的探索期后期加快收敛。螺旋形状常数b取1是标准配置一般不轻易动。还有一个容易被忽略的参数就是螺旋更新和收缩包围的切换概率p默认取0.5即两种逼近方式各占一半。如果发现算法收敛太慢可以增大收缩概率如果发现多样性不足可以增大螺旋概率。当然这些改动要配合收敛曲线来看不要盲调。3. 实操过程在MATLAB里跑通一个完整案例3.1 案例定义为什么选择二维LP问题选测试算例有个原则要有精确解可以对照又不能复杂到让问题本身抢了算法的戏。这里用一个小规模二维线性规划问题max z 3x₁ 2x₂约束条件为x₁ x₂ ≤ 4 x₁ 2x₂ ≤ 5 3x₁ x₂ ≤ 7 x₁ ≥ 0, x₂ ≥ 0这组约束正好在平面上围成一个五边形可行域先不管严格的增加约束简单起见直接求最大化。用linprog求极小化时把目标系数取负即可。这个问题的精确解是x* (2, 1)最优值z* 8这个结果可以通过图解法验证也可以直接调MATLAB的linprog拿到。选二维问题还有个好处所有迭代点都可以画在平面图上能直观看到鲸鱼群体怎么向最优点收缩。3.2 WOA核心代码实现从适应度函数到主循环MATLAB实现WOA非常简洁整套核心循环下来不到八十行。先把适应度函数写了这里的关键点是用罚函数处理约束。我直接给出一份完整可跑的代码框架方便你直接替换目标函数和约束矩阵使用。function f fitness(x, c, A, b) % x: 决策变量列向量 % c: 目标函数系数 % 注意MATLAB linprog默认求极小化这里fitness输出的是“极小化”目标惩罚 f c(:) * x(:); % 不等式约束惩罚A*x b g A * x(:) - b(:); penalty sum(max(0, g).^2) * 1e6; f f penalty; end主循环实现了三个基本动作。初始化种群时用rand在上下界内生成均匀随机点之后挨个计算适应度找出全局最优位置X_star。主循环里对每个个体更新a、A、C、l、p这些参数然后按照p的取值决定走收缩包围还是螺旋更新。特别要注意每个新位置的越界修正这里采用最朴素的边界截断法超出上界就置为上界超出下界就置为下界。这个方法简单有效虽然损失了一点多样性但能保证解始终在合法搜索空间内。function [best_x, best_f, curve] woa_lp(N, T, lb, ub, c, A, b) dim length(lb); pos repmat(lb, N, 1) rand(N, dim) .* repmat(ub - lb, N, 1); fit zeros(N, 1); for i 1:N fit(i) fitness(pos(i,:), c, A, b); end [best_f, idx] min(fit); best_x pos(idx,:); curve zeros(T, 1); for t 1:T a 2 - 2 * t / T; for i 1:N r1 rand(); r2 rand(); A 2 * a * r1 - a; C 2 * r2; p rand(); l -1 2 * rand(); if p 0.5 if abs(A) 1 D abs(C * best_x - pos(i,:)); new_pos best_x - A * D; else rand_idx randi(N); X_rand pos(rand_idx,:); D abs(C * X_rand - pos(i,:)); new_pos X_rand - A * D; end else D abs(best_x - pos(i,:)); new_pos D .* exp(1) .* cos(2 * pi * l) best_x; end new_pos max(new_pos, lb); new_pos min(new_pos, ub); new_fit fitness(new_pos, c, A, b); if new_fit fit(i) pos(i,:) new_pos; fit(i) new_fit; end end [best_f, idx] min(fit); best_x pos(idx,:); curve(t) best_f; end end跑完之后主要观察两个东西第一个是最终解和精确解的差距第二个是收敛曲线的下降形态。如果曲线在最后几十次迭代还在明显下降说明迭代次数不够需要加大T如果在第50次迭代就早早平了说明算法很快找到了当前种群下的最优位置后面一直在原地踏步要考虑是不是陷入了局部最优。3.3 求解结果与精度验证和linprog对照着看在同一组参数下我分别用WOA和linprog解上面那个问题结果对照如下。WOA设置种群规模30、最大迭代次数500、惩罚系数1e6。求解方法x₁x₂目标值相对误差linprog精确解2.00001.00008.0000-WOA500代1.99870.99847.99290.089%WOA2000代1.99990.99987.99930.009%可以看到500次迭代时WOA已经能给出一个非常接近精确解的结果误差不到千分之一2000次迭代之后误差缩小到万分之一量级。这个精度说明WOA对这个小规模线性规划问题是完全够用的。但是对于实际生产环境中的大规模LP问题比如上万变量、上万约束WOA的计算开销和精度就完全比不上商业求解器了。使用场景要分清这一点很重要。3.4 收敛曲线与参数敏感性分析要点收敛曲线可以直观展示算法的搜索过程。我在实验中发现WOA的收敛曲线呈现出明显的“阶梯式”下降也就是前期快速下降中期出现平台期后期再次下降这和多峰函数优化中的“阶段性收敛”一致。特别是前100次迭代适应度下降非常快能迅速进入最优解附近的邻域之后就是精细打磨慢慢逼近。参数敏感性方面我对比了几个关键参数的实验组合参数变化对结果的影响种群数量 10 → 50前期收敛加快但后期精度提升有限计算时间线性增加迭代次数 200 → 2000后期误差下降明显尤其对高维问题更显著惩罚系数 1e3 → 1e6系数太小时解可能跑到不可行域内1e6以上结果稳定可靠a衰减方式 线性 → 指数指数衰减前期探索更充分但收敛变慢需要更多迭代次数这些结果说明WOA的主要计算开销集中在适应度函数评估上而适应度函数本身很简单的话整体跑得非常快。对于二维LP30个个体跑2000次迭代在我这台普通笔记本上不到两秒就能出结果所以多跑几次取最优值做统计完全可行。4. 常见问题与排查技巧实录4.1 早熟收敛一开局就被局部最优带偏了这是元启发式算法最容易踩的坑表现是种群在前几十次迭代就集中到某个点之后不论怎么迭代最优解都不再变化而这个点又不是全局最优。我的排查思路是先把收敛曲线画出来如果曲线前期直线下滑后立刻走平大概率就是早熟。应对早熟有几个实用手段。第一种是增大种群数量让初始覆盖度更高第二种是增加随机搜索|A|≥1的概率比如把a的初始值从2稍微调大一些让前期探索范围更广第三种是引入简单的变异策略每次迭代随机挑几个个体做位置扰动相当于给种群“换血”。我在三维以上的LP测试问题里种群加到50、变异概率取0.1基本能稳定收敛到全局最优附近。4.2 罚系数大小带来的两难困境罚函数法最头疼的就是罚系数λ的取值。λ太小不可行解受到的惩罚不够算法可能一直在一个不可行区域里打转最终输出一个明显违反约束的“最优解”λ太大目标函数值被罚项完全支配数值上可能出现溢出也会让搜索过程变得非常敏感稍微动一下就会得到极大的适应度值种群容易被某个不可行点“锁死”。实际调试中我的做法是先用一个较小的λ比如100跑一次观察最优解是否满足约束如果不满足再逐步增大到1e3、1e5、1e6。一旦发现解稳定在可行域内就不必再继续增大了。测试算例里得到的结论是1e6附近已经足够稳定再往上增大对于这个小规模问题没有明显改善。4.3 维度灾难什么情况下WOA不适合解LP把WOA拿到高维线性规划问题上时效果会迅速变差。我做过一组对比同样是10个变量的LP问题linprog几乎瞬间给出精确解而WOA把迭代次数拉到5000、种群加到50相对误差仍然在1%左右徘徊。这说明什么问题维度越高搜索空间体积指数增大随机搜索的效率急剧下降这在所有元启发式算法里都是普遍规律。所以如果问题是标准的、线性的、规模可解的不要犹豫直接用linprog、Gurobi、Cplex这类精确求解器。WOA的真正用武之地是那些精确算法难以处理的问题比如耦合了非线性约束、目标函数不可导、或者决策变量之间存在复杂的组合逻辑关系。另外在数学建模竞赛中如果拿到一个问题明确要求使用启发式算法求解或者题目设定的场景本身就是非线性、非凸的优化问题那么WOA就是一个值得考虑的选项。但竞赛建模应基于题干给定条件开展求解不宜自行引入题目未定义的物理条件这点务必注意不要为了强行套用WOA而擅自修改题目约束。4.4 可行域极窄时随机初始化可能全军覆没还有一种情况比较特殊LP的可行域非常狭窄比如在二维平面里是一条细长的带状区域这时候随机初始化产生的种群很可能没有任何一个个体落在可行域内。此时罚函数法虽然不会报错但所有个体都在不可行区域里挣扎算法会花大量迭代在消除约束违反上收敛速度极慢。这种情况下我推荐一个技巧先用百分之一的种群个体通过随机线性组合生成使得一部分初始点位于约束边界附近更直接的办法是先用linprog求一次精确解或者用单纯形法的一个可行基然后在这组精确解的邻域内生成初始种群。这相当于用精确算法“预热”一下给元启发式算法一个靠近可行域的起点。简单省事效果也很好。4.5 常见问题速查表最后把这段时间反复遇到的坑和对应的解法整理成一个速查表方便以后直接翻。问题现象可能原因解决方案收敛曲线平了但结果离精确解很远早熟收敛增大种群数量到50加入0.1概率变异最终解违反某些约束条件罚系数太小把λ提升到1e6以上或采用自适应罚函数结果忽好忽坏多次运行方差很大随机种子的影响多次独立运行取最优值/平均值做统计高维问题精度差维度灾难加深迭代到5000混合局部精确搜索做精修初始种群没有可行个体可行域太窄用linprog结果做初始群体“预热”边界截断导致结果贴在边界上最优解就在边界加密边界附近个体密度或改用反射式边界处理做这类实验我的一个深切体会是没有哪个算法是万能的关键是要理解每个算法背后的假设和优缺点。WOA解线性规划更像是一个“探秘”的过程它让我们跳出精确求解器的舒适区重新审视优化问题的本质。如果你只是想要一个精确解请直接调用求解器但如果你想理解元启发式算法的搜索机制、想在非线性扩展场景里寻找解决方案那么鲸鱼算法绝对值得你花一个下午时间跑一跑、调一调参数看看那些“鲸鱼”是怎么一步步逼近最优解的。