简介:面向带约束条件的工程优化场景,这套Matlab代码实现了鲸鱼优化算法(WOA)的约束处理版本。资源适合有一定算法基础、希望在非线性、不可微或多峰值问题中加入罚函数、可行性法则等处理手段的开发者,可迁移到电力系统优化、供应链管理、资源分配和机器学习模型参数寻优等任务。压缩包共5个文件,包含4个.m脚本与1张求解带约束优化问题的示意图,整体仅25KB;脚本分别对应主程序、约束目标函数、WOA核心迭代和种群初始化,结构简洁,便于按模块阅读和改写。目前已有108人学习下载。代码在初始化种群、位置更新和惩罚因子调整等关键环节均考虑了约束条件的可行性,运行main.m即可复现带约束问题的完整求解流程,直观观察不可行解的惩罚策略、搜索收敛过程以及算法在可行域内的寻优效果;配合示例图,能快速理解约束边界对搜索路径的影响,方便在此基础上替换或增加自己的问题模型。
1. 带约束条件的鲸鱼优化算法:不止是避开等式约束那么简单
工程优化里,真正的难题从来不是目标函数有多复杂,而是“可行域”有多小。一个设计变量哪怕越界一点,整个方案就得作废;一次迭代哪怕轻微违反约束,结果就毫无工程意义。鲸鱼优化算法(WOA)本身是个无约束优化器,它模拟座头鲸的泡泡网捕食策略,用螺旋收缩和随机游走来完成搜索,可一旦问题里混入等式约束、不等式约束或边界条件,原始 WOA 可能会把大量计算浪费在不可行区域里,收敛精度也大打折扣。这里要解决的,正是“带约束条件的优化算法鲸鱼优化算法”这个组合命题:如何让种群在满足物理、成本、安全这些硬条件的前提下,去逼近全局最优。本文从约束处理机制切入,结合代码、参数与典型场景,讲清一套可落地的方案;对做物流路径规划、结构设计、参数标定和线材优化的人,有直接可参考的价值。
2. 为什么原生鲸鱼优化算法会在约束问题面前失灵
2.1 无约束假设下的搜索逻辑
基础 WOA 的迭代围绕三种位置更新机制展开:包围猎物、泡泡网攻击、随机搜索猎物。这三种机制都对搜索空间只做“数值扰动”,完全不感知约束存在。假设目标函数是 f(x),种群位置是 X,更新公式可写成:
% Matlab 风格的 WOA 位置更新核心片段 if p < 0.5 if |A| < 1 X_new = X_star - A * D; % 包围猎物 else X_rand = pop(randi(N), :); X_new = X_rand - A * D_rand; % 随机搜索 end else X_new = D' * exp(b * l) .* cos(2 * pi * l) + X_star; % 螺旋更新 end这段代码的问题很明显:X_new 算出后直接赋给下一代个体,不检查变量是否越界,也不检查种群是否跑进不可行域。对无约束问题来说没问题——反正全域都可接受;可一旦加了约束,种群会快速聚集到局部极小附近,而这个局部极小可能完全是不可行的。
2.2 约束处理缺失带来的三个典型故障
一是边界穿透。鲸鱼个体在螺旋更新时,如果对数螺旋的步长较大,位置向量容易越过上下界。很多实现只做简单的“截断到边界”,看起来没毛病,但截断操作会让种群多样性骤降——大量个体钉死在边界上,丧失了跳出局部的能力。
二是等式约束无解。比如压力容器设计里有一个等式约束 h = 0.1x1,如果不做精确处理,普通罚函数很难让搜索收敛到这个零测度流形上。种群会在这条流形附近反复震荡,迭代次数浪费在不可行解上。
三是可行域占比极低时的饥饿问题。某些工程约束下可行域只占整个搜索空间的 1% 甚至更少,初始随机种群几乎全是不可行的。这时如果算法的选择机制不优先保留可行解,收敛过程会非常缓慢。
2.3 三种主流约束处理路线
做带约束的鲸鱼优化算法,核心不在于改鲸鱼的游动公式,而在于重新设计“优劣评价”的标准。目前学术界和工业界最常用的有三种:
- 罚函数法:把约束违反量乘上惩罚系数加到目标函数上。易于实现,但惩罚系数大则早熟,小则不可行解残留。
- 可行性法则(Deb 法则):两个解比较时,可行解总是优于不可行解;都可行时,比目标值;都不可行时,比约束违反总量。不引入额外参数,但可行解稀缺时选择压力不足。
- ε-约束处理法:设定一个动态收缩的容忍阈值 ε,初期允许少量违反,后期强制收敛到完全可行。最稳,但 ε 的退火策略要调。
综合来看,用可行性法则作为主框架、辅以边界反弹策略,跑通常规工程约束问题的性价比最高。下面给出细节。
3. 处理边界与不等式约束的实用改造方案
3.1 边界处理:用“反弹”代替“截断”
大多数资料中鲸鱼优化算法的边界处理方式是:变量越界后直接缩回边界值。但在约束问题里,截断会减少种群多样性,尤其在多峰函数上影响非常明显。实测经验是“反弹策略”更好用——当某个维度的位置超出边界时,让它像撞到墙一样反弹回来:
# Python 实现边界反弹 def bound_rebound(pos, lb, ub): for i in range(len(pos)): if pos[i] < lb[i]: pos[i] = lb[i] + (lb[i] - pos[i]) # 反弹回可行区 elif pos[i] > ub[i]: pos[i] = ub[i] - (pos[i] - ub[i]) return pos反弹策略比截断多保留了搜索动量,粒子穿越边界的“惯性”被转化为反射弹跳,可以避免大量个体压在边界上。代价是早期迭代中某些个体可能反弹后依然违反边界,所以要在每次位置更新后连续做两次判断。
3.2 不等式约束的不可行度排序
在每一代种群更新完成后,对每个个体计算约束违反总量:
def constraint_violation(x, constraints): viol = 0.0 for g in constraints: val = g(x) if val > 0: viol += val return viol这里的 constraints 是“≤0”形式的不等式约束函数列表。用这个 violation 值配合可行性法则来比较个体优劣:
def is_better(ind1, ind2): # ind1 优于 ind2 则返回 True if ind1.viol == 0 and ind2.viol > 0: return True if ind1.viol > 0 and ind2.viol == 0: return False if ind1.viol > 0 and ind2.viol > 0: return ind1.viol < ind2.viol return ind1.fitness < ind2.fitness这段代码的核心逻辑是:可行解永远排在不可行解前面,不可行时按照违反量从小到大排序。这样整个种群就会被推向可行域边界,再从边界往最优可行解方向收缩。整个过程中,鲸鱼优化算法的搜索结构无需大幅修改,只需要在每次迭代后把评价函数替换掉即可。
3.3 一个工程标准算例:压力容器设计问题
为了验证这套改造的效果,以经典的压力容器设计问题为例——四个变量,两个不等式约束,目标是最小化总成本。该问题在测试带约束优化算法时被反复引用,适合做改造效果对照。
import numpy as np # 目标函数:材料成本 + 焊接成本 def pressure_vessel_cost(x): Ts, Th, R, L = x return (0.6224 * Ts * R * L + 1.7781 * Th * R**2 + 3.1661 * Ts**2 * L + 19.84 * Ts**2 * R) # 两个不等式约束,规范成 <= 0 def constraint1(x): Ts, Th, R, L = x return -Ts + 0.0193 * R def constraint2(x): Ts, Th, R, L = x return -Th + 0.00954 * R constraints = [constraint1, constraint2] lb = [0.0625, 0.0625, 10, 10] ub = [6.1875, 6.1875, 200, 200]注意变量是离散和连续混合的,这里先把四个变量都按连续处理,跑完再看离散化影响。这个问题的可行域比较小,而且最优解正好骑在第一条约束边界上,对约束处理机制是个很好的压力测试。
4. 用改进鲸鱼优化算法求解带约束的物流配送路径问题
4.1 问题建模与编码方式
物流配送路径优化是智能优化算法最常被拿来做验证的场景之一:多个客户点,一个配送中心,每辆车有载重限制,总路径长度要最短。这是一个典型的离散组合优化问题,而鲸鱼优化算法天生是连续优化器,直接套用需要做一些编码转换。
常见做法是使用连续值排序编码(random key encoding)。每个个体是一组 [0,1] 之间的实数,长度为客户点数;排序后按车辆容量约束切分成多条路径。比如 6 个客户、2 辆车的场景:
# random key 编码示例 individual = [0.34, 0.86, 0.12, 0.53, 0.91, 0.27] # 排序后得到客户访问顺序: 3, 6, 1, 4, 2, 5 # 按载重约束切分为 路径1: [3, 6, 1], 路径2: [4, 2, 5]这种编码方式的好处是,鲸鱼优化算法的位置更新公式可以直接作用于连续编码向量,不需要改动算法结构,只需要在解码阶段完成组合映射。
4.2 约束条件如何嵌入鲸鱼优化算法
物流配送问题的约束主要包括三类:每辆车载重不超过上限、每个客户只能被访问一次、路径必须从配送中心出发并返回。路径切分时采用“扫描法”做贪心装载,确保载重约束在解码阶段就被满足;每个客户恰好访问一次,由排序编码天然保证。
“每个客户只访问一次”这个约束对应成本函数里若出现重复访问或漏访问,直接给一个巨大惩罚。这种处理比在鲸鱼优化算法的位置更新里加修正项更有效——算法负责搜索,解码负责合法性,分工清晰。
4.3 改进鲸鱼优化算法与粒子群优化算法在同测试集上的对比
用同一个配送点数据集跑 30 次独立实验,改进后的鲸鱼优化算法(混合可行性法则 + 随机 key 解码)与标准粒子群优化算法做对比,统计每次的最短路径长度:
# 伪代码:记录每次独立运行的收敛指标 for run in range(30): woa.run(max_iter=500, pop_size=40) pso.run(max_iter=500, pop_size=40) log_woa[run] = woa.best_route_length log_pso[run] = pso.best_route_length print("WOA均值:", np.mean(log_woa)) print("PSO均值:", np.mean(log_pso))从结果看,改进鲸鱼优化算法在中等规模配送问题(20 到 50 个客户点)上的收敛速度略慢于粒子群优化算法,但最终解的稳定性更好——多次运行的最优路径长度标准差更小。这与鲸鱼优化算法的全局搜索能力较强、局部精细搜索能力偏弱的特点一致。实际使用时建议在后期引入局部搜索算子补足精细度。
4.4 参数表与推荐设置
目标到参数层面,给出一个可直接复用的设置表,这套参数经过多组测试,在 20 到 80 个客户点的规模上表现稳定:
| 参数名 | 推荐值 | 说明 |
|---|---|---|
| 种群数量 | 40-60 | 客户点超过 60 个时建议取 60 |
| 最大迭代 | 500 | 更大规模时增加到 1000 |
| 螺旋参数 b | 1 | 固定 |
| 收敛系数 a | 线性 2 到 0 | 前 50% 迭代保持较大值 |
| 交叉概率(如用混合算子) | 0.7 | 仅在灾变重启时使用 |
| 可行性法则选择压 | 无额外参数 | 替换原目标函数排序逻辑 |
注意,表里没有罚函数系数,因为可行性法则不需要权重。若改用 ε-约束法,ε 初值取最大违反量的 30%,然后线性衰减至 0。
5. 收敛精度不佳或停滞时的三个优化技巧
5.1 等式约束下的定向修复算子
当问题里存在等式约束 g(x) = 0 时,可行性法则无能为力,因为随机扰动几乎不可能精确落在等式约束的超平面上。此时可以在每次位置更新后增加一个“定向修复”步骤:把违反等式约束的分量按最小范数修正回流形上。这个技巧对调参工程特别有用,能大幅提高搜索效率。
# 等式约束 h(x) = x1^2 + x2^2 - 1 = 0 的修复示例 def repair(x): val = x[0]**2 + x[1]**2 - 1 if abs(val) > 1e-6: norm = np.sqrt(x[0]**2 + x[1]**2) x[0] *= 1.0 / norm x[1] *= 1.0 / norm return x这种修复算子可以每 5 代调用一次,或者检则约束违反量超过阀值时触发。注意它只修改个体位置,不动速度或步长信息,因此不破坏鲸鱼优化算法的搜索结构。
5.2 动态可行性法则与 ε 阈值联动
单纯的可行性法则在可行域极小时会陷入“全部不可行、穷举找可行”的低效状态。可以把它与 ε-约束处理法联动:前 30% 的迭代允许一定程度的约束违反,之后随着迭代次数增加逐步收紧。
# ε 动态衰减策略 max_eps = np.max(all_violations_at_first_generation) eps = max_eps * (1 - current_generation / max_generation) ** 2 # 判断两个个体时引入 eps def better_with_eps(ind1, ind2, eps): if ind1.viol < eps and ind2.viol > eps: return True # 都可行或都不可行时,比较目标值 return ind1.fitness < ind2.fitness这个策略的作用是,搜索前期允许种群在不可行区域里跨越,快速逼近全局区域;后期 eps 归零,强制收敛到完全可行的解。实际效果比纯罚函数好很多,尤其适合可行域占比小于 5% 的工程问题。
5.3 多目标冲突下的约束优先级排序
物流配送路径或昂贵多模态优化问题里,经常出现多个约束互相冲突的场景。对约束做优先级分层比加权求和更可控:硬约束(如载重上限)优先级最高,违反硬约束的任何个体直接淘汰;软约束(如单条路径长度上限)转为目标函数的惩罚项参与排序。鲸鱼优化算法本身是单目标优化器,加多目标框架后最好配合帕累托存档,否则收敛方向容易漂移。
6. 验证约束处理有效性的一个实操对照
6.1 用收敛曲线直接对比三种边界策略
为了确认上述改进真的有效,最直接的验证方式是跑一条收敛曲线,对比三种策略的收敛轨迹:原始鲸鱼优化算法、鲸鱼优化算法加罚函数、鲸鱼优化算法加可行性法则与反弹边界。用一个双约束多峰函数做测试:
# 基准函数:带两个约束的 Rosenbrock 变体 def bound_rosenbrock(x): return (1 - x[0])**2 + 100 * (x[1] - x[0]**2)**2 def g1(x): return x[0]**2 + x[1]**2 - 2 # 半径不能超过 sqrt(2) def g2(x): return -x[0] + x[1] - 0.5 # 下半区域不可行每 50 代记录一次当前最优目标函数值,各策略跑 20 次取中位数,绘出三条平滑曲线。预期结果是原始鲸鱼优化算法所得最优值最差,罚函数法居中,改良后的可行性法则与反弹边界最好。若曲线表现与环境无关,则说明改进有效。
6.2 检查算法输出是否真正满足约束
光看收敛曲线还不够,验证最终解是否真正落在可行域内才是关键工程标准。运行结束后检查存档最优解:
best_x = woa.best_solution viol1 = g1(best_x) viol2 = g2(best_x) print("约束1违反量:", viol1) print("约束2违反量:", viol2)当违反量的绝对值小于 1e-4 时,基本可以认定解是有效的。对壁垒型约束(必须为零),阀值要压到 1e-8。每次改造参数后用这个检查脚本做快速回归,可以避免引入新参数时悄悄破坏约束满足度。
6.3 把约束满足度也作为报告指标
在物流配送路径优化一类的项目交付中,建议记录三份指标:目标函数收敛曲线、单次运行耗时、约束满足率的逐代变化。约束满足率从初始代到最终代的上升曲线,比目标函数收敛图更能反映约束处理的优化效果——它直接告诉你算法是从什么时候开始真正找到可行区域的。这个指标在多目标优化算法对比测试时尤其有说服力,因为它做不了假。
本文还有配套的精品资源,点击获取