直接开篇吧,我不喜欢铺垫太多。这篇内容是围绕一个我刚做完的仿真项目写的:用融合了粒子群(PSO)机制的改进鲸鱼优化算法(我这边叫它 PSO-ImWOA)来做无人机三维航迹规划,代码用 Python 实现。这个方向这几年很热,但网上能找到的现成代码多数是二维平面的,要么算法就是单纯把两个优化算法拼在一起,改进深度远不够。这个项目正好把这些坑都趟了一遍。
适合什么人看呢?一种是正在做智能优化算法改进、需要找一个实际应用场景验证算法性能的同学;另一种是想用群智能算法做无人机航迹规划、但卡在三维建模和代码实现上的人。我会从算法层面的融合思路一直拆到航迹规划模型的构建方式,再到 Python 代码的核心实现细节,最后再聊实测下来的坑。哪怕你之前没接触过鲸鱼算法,读完后也应该能自己把代码跑起来。
1. 需求拆解:为什么航迹规划需要算法融合
1.1 航迹规划的核心矛盾
无人机航迹规划,本质上是在三维空间中的带约束优化问题。给定起点、终点,以及若干禁飞区、威胁区,要求规划出一条满足飞行性能约束(最大爬升角、最小转弯半径等)的路径,同时让代价函数最小。代价函数一般要把航程长度、威胁代价、高度变化代价、平滑度代价综合起来。
关键在于,这个问题的搜索空间是不规则的,目标函数往往是非凸、多峰、强耦合的。山峰地形高低起伏,威胁区分布复杂,经典的最短路径算法在连续三维空间中难以直接处理,因此群智能算法成了主流选择。
1.2 标准WOA的能力边界
标准鲸鱼优化算法(WOA)的灵感来自座头鲸的泡泡网捕食行为。它有三种位置更新机制:包围猎物、气泡网攻击(螺旋运动)、随机搜索。这三种机制分别对应算法的开采和勘探行为。
WOA 的问题也很明显,我在处理航迹规划时注意到这几个点:
- 依赖全局最优个体引领整个种群,一旦全局最优陷入局部极值,整个种群跟着收敛到错误区域。
- 收敛因子
a是线性递减的,意味着勘探/开采切换的节奏过于僵化,面对复杂地形时前期勘探不足、后期又太快锁定。 - 种群的多样性维持能力差,迭代到中后期,个体迅速聚拢,没有有效机制让种群重新分散。
实测下来,标准 WOA 跑三维航迹规划的典型结果是:算法早熟,规划出的航迹一条直线穿过危险区域附近,或者绕路绕得离谱,代价远高于人工设计的预期路径。
1.3 为什么选 PSO 作为融合对象
粒子群优化算法(PSO)的特点是每个粒子同时受“个体历史最优”和“全局最优”两个吸引子影响,通过速度-位置更新实现搜索。它和 WOA 一个很大的互补点在于:PSO 个体对自身历史经验有记忆,不容易被全局最优完全带走;而 WOA 个体缺乏这种“记忆”机制,完全是被动的追随者。
把 PSO 的“个体历史最优引导 + 速度惯性”融入 WOA,能够缓解 WOA 的早熟问题。具体来说,WOA 个体在更新位置时,不再是单纯被当前全局最优牵着走,而是综合考虑自身历史最优方向、全局最优方向,以及原先的包围/螺旋更新方向,相当于让每个搜索个体多了一个“自我意识”。
这样的融合思路和十几年前兴起的“混合群智能算法”是同一个逻辑,但关键在于融合的方式:是简单地把两种算法串行执行,还是在位置更新方程层面做真正的融合。我用的是后者,这直接决定了算法的性能和代码复杂度。
简单拼接的混合算法效果往往很差,因为本质上没有让两种机制在同一轮迭代中协同工作。除非你的目标是发一篇水文,否则不建议做串行拼接式混合。
2. 算法设计:PSO-ImWOA 的四个关键改进
这一部分我都会拆开讲,先说设计思路,再给对应的数学表达,这样后面贴代码的时候大家能对得上。
2.1 改进一:初始化层面的群智化
标准 WOA 用随机数初始化种群位置,这在低维度问题下问题不大,但航迹规划的决策变量数量不少——一条航迹如果离散成 10 个节点,三维坐标就是 30 维,种群随机散布在高维空间里的覆盖效率其实很低。
我这里采用“反向学习初始化 + 均匀化散布”:对每个随机生成的初始位置,同时生成其反向位置,计算两者的适应度,择优进入初始种群。反向学习不是新概念,但在三维航迹规划这个维度水平上,它的作用是实打实的——初始种群能覆盖到更多不同形态的航迹走向,避免开局就挤在半个搜索空间里。
2.2 改进二:位置更新方程的信息融合策略
这是整个算法的核心改动。标准 WOA 的位置更新有三条公式:包围猎物、螺旋位置更新、随机搜索。我在每条更新公式中都嵌入了 PSO 的个体历史最优引导项。
以包围猎物更新公式为例,原来的形式是经典的位置偏移公式,但我这边改成加上了个体最优方向的偏移修正:
新位置 = 原鲸鱼位置偏移 + 随机权重乘个体历史最优偏移 + 随机权重乘全局最优偏移
具体表达式为:
X_new = X* - A * D + c1 * r1 * (pbest - X) + c2 * r2 * (gbest - X)其中X*是当前最优个体位置,D是当前个体到X*的距离向量,A是随收敛因子变化的系数,pbest是个体历史最优,gbest是全局最优,c1、c2是学习因子,r1、r2是 [0,1] 随机数。
同样地,螺旋更新和随机搜索中也做类似处理。这样做的好处是,即使A的值导致包围项收敛幅度很小,个体历史最优项依然能提供有效的搜索方向,不容易原地打转。
这里必须提醒一个问题:学习因子和数据范围的关系。三维航迹规划的位置坐标范围可能是几百米到几公里,如果pbest - X的量级很大,叠加到位置更新上会造成步长过猛。解决办法是把c1、c2调得比较小(比如 0.5~1.0),或者对pbest - X做归一化处理。
2.3 改进三:勘探与开采的自适应平衡
标准 WOA 的收敛因子a线性递减,我改成了非线性递减,并且让它根据种群多样性的变化来自适应调整。具体做法是:统计当前种群个体的平均间距,如果间距过小(说明种群聚拢明显),就把a往大调,增强勘探能力;如果间距过大,就把a往小调,加快收敛。
这个机制在代码层面并不复杂,每次迭代算一下所有个体之间的平均欧氏距离即可。实测下来,这样的自适应调节能让算法在复杂地形下多出 10%~15% 的收敛精度提升。
2.4 改进四:位置越界处理与航迹平滑策略
群智能算法在搜索过程中必然遇到位置越界的情况。很多开源代码直接把越界个体随机重置,这种做法比较浪费——该个体可能已经搜索到有潜力的区域。我使用的是边界缓存法:如果某一维坐标越界,将该维坐标设为边界值,同时保留其他维度的搜索成果。
此外,算法的输出航迹需要平滑处理。我引入三次样条插值对最终航迹节点进行平滑。这一步不是在算法内部做的,而是在优化结束后对结果做后处理,好处是不破坏算法的搜索逻辑,同时让最终航迹满足无人机实际飞行的平滑性约束。
算法优化出来的原始航迹往往是折线,对固定翼无人机来说飞行性能很差,必须做平滑后处理。这一点很多论文里不写,但实际工程中逃不掉。
2.5 算法整体流程总览
整体流程可以这样描述:
- 初始化算法参数:种群规模 N、最大迭代次数 T、学习因子 c1、c2、收敛因子初始值 a_max。
- 反向学习初始化种群。
- 对每个个体进行航迹代价评估。
- 进入迭代循环:
- 更新收敛因子 a;
- 对每个个体,根据概率 p 判断是否进入螺旋更新或其他更新,执行对应的位置更新公式;
- 计算适应度,更新个体历史最优和全局最优;
- 计算种群多样性指标,自适应调整收敛因子。
- 达到终止条件后输出全局最优位置,解码为航迹节点。
- 对航迹节点做三次样条插值平滑。
这个流程里没有复杂的算子,实现难度主要集中在位置更新公式中各项系数的组合。
3. 三维航迹规划模型:地形建模与约束处理
算法层面解决了,接下来就是怎么把“三维航迹规划”这件事用数学模型描述清楚。模型设计得不好,算法再先进,规划出来的航迹也是空中楼阁。
3.1 地形建模与威胁区处理
我用的是连续函数叠加法来构造数字地形。具体来说,地形高度由基准海拔加上若干山峰函数叠加构成:
terrain(x, y) = base + sum(ai * exp(-((x - xi)^2 + (y - yi)^2) / (2 * sigma_i^2)))其中每个山峰有自己的高度、中心、和影响半径。再加上若干椭圆柱体威胁区,无人机不能进入这些区域的水平投影范围。
这种建模方式在学术和工程里都很常用。优点是用几个参数就能生成复杂度不同的地形,方便做实验对比。实际工程地形一般用数字高程模型(DEM)数据,处理逻辑本质上是一致的,只是数据来源不同。
3.2 航迹编码方式与代价函数设计
航迹编码是决策变量设计的问题。我采用的是节点编码法:将起点到终点在水平面上做 n 等分,每个等分点的 x、y 坐标作为决策变量,z 坐标直接取地形高度加上一定巡航高度;优化过程中 z 同样作为决策变量之一参与调整。这样决策变量总数是 2n + n = 3n(实际常用 n=10,即 30 维决策变量)。
代价函数是航迹规划的核心,我用的形式:
cost = w1 * length_cost + w2 * threat_cost + w3 * height_cost + w4 * smooth_cost长度代价计算相邻节点之间的欧氏距离累加,直接影响航程。威胁代价根据航迹节点是否进入威胁区或者接近威胁区计算。高度代价用于约束无人机不能飞得过低或过高,过低危险,过高耗能。平滑代价通过计算相邻线段的夹角来衡量航迹的折转程度。
各权重之间需要做归一化处理。我实测的经验是:长度代价的权重取相对较小值,因为它的量级天然偏大;威胁代价的权重取较大值,因为安全永远优先于航程。
3.3 约束条件的处理方式
航迹规划中的经典约束包括最大爬升角和最小转弯半径。我在算法内部使用罚函数法:约束违例时,在代价函数中加入惩罚项。这样做比较简单,但要注意策略:如果将所有约束都硬编码为极大惩罚值,个体之间失去梯度信息,算法难以收敛。应当根据违例程度设置渐进惩罚值。
比如最大爬升角约束,计算相邻节点的俯仰角变化,如果超过允许值,则将其惩罚值设置为基础代价的 2 倍,而不是 1000 倍。这样既能引导个体远离违例区域,又不会让适应度信息失真。
4. Python 实现:核心代码与实操要点
代码实现这里我在版本库拉出自己实际用的部分,精简后贴出来,便于对照理解。完整工程代码还包含了数据可视化、结果导出等功能,但在文本里我会把关键的算法部分和配置流程拆出来讲。
4.1 代码整体架构
文件结构:
pso_imwoa_path_planning/ ├── main.py # 主流程入口,负责参数设置与结果输出 ├── algorithm/ │ ├── pso_imwoa.py # PSO-ImWOA 算法核心 │ ├── woa.py # 标准 WOA 用于对比 │ └── pso.py # 标准 PSO 用于对比 ├── environment/ │ └── terrain.py # 地形与威胁区建模 ├── planning/ │ ├── cost_function.py # 航迹代价函数 │ └── smoothing.py # 三次样条平滑后处理 ├── utils/ │ └── visualization.py # 2D/3D 可视化工具 └── config.yaml # 算法参数配置整体思路是环境和算法完全解耦。换地形,只需修改 terrain.py;换算法,只需实现标准接口。
4.2 核心代码解析
PSO-ImWOA 的核心更新逻辑可以精简为下面的 Python 代码:
import numpy as np class PSOImWOA: def __init__(self, n_pop, n_iter, dim, lb, ub, c1=0.8, c2=0.8, a_max=2.0): self.n_pop = n_pop self.n_iter = n_iter self.dim = dim self.lb = np.array(lb) self.ub = np.array(ub) self.c1 = c1 self.c2 = c2 self.a_max = a_max self.positions = None self.velocities = None self.pbest_pos = None self.pbest_val = None self.gbest_pos = None self.gbest_val = None def init_population(self, eval_func): # 反向学习初始化 random_pos = np.random.uniform(self.lb, self.ub, (self.n_pop, self.dim)) reverse_pos = self.lb + self.ub - random_pos combined = np.vstack([random_pos, reverse_pos]) vals = np.array([eval_func(ind) for ind in combined]) idx = np.argsort(vals)[:self.n_pop] self.positions = combined[idx] self.velocities = np.zeros_like(self.positions) self.pbest_pos = self.positions.copy() self.pbest_val = np.array([eval_func(ind) for ind in self.positions]) best_idx = np.argmin(self.pbest_val) self.gbest_pos = self.pbest_pos[best_idx].copy() self.gbest_val = self.pbest_val[best_idx] def update(self, eval_func): for i in range(self.n_pop): # 非线性收敛因子 a = self.a_max * (1 - self.iter / self.n_iter) ** 0.5 # 位置更新融合 PSO 机制 # 这里简化为包围猎物的融合更新形式 r = np.random.random(self.dim) A = 2 * a * r - a C = 2 * np.random.random(self.dim) D = np.abs(C * self.gbest_pos - self.positions[i]) pbest_diff = self.pbest_pos[i] - self.positions[i] gbest_diff = self.gbest_pos - self.positions[i] new_pos = self.gbest_pos - A * D + self.c1 * np.random.random() * pbest_diff \\ + self.c2 * np.random.random() * gbest_diff # 边界缓存 new_pos = np.clip(new_pos, self.lb, self.ub) # 适应度更新 val = eval_func(new_pos) # 更新个体历史最优与全局最优 if val < self.pbest_val[i]: self.pbest_val[i] = val self.pbest_pos[i] = new_pos.copy() if val < self.gbest_val: self.gbest_val = val self.gbest_pos = new_pos.copy() self.positions[i] = new_pos这段代码是核心更新逻辑的精简版,实际上完整的 PSO-ImWOA 还包含概率 p 控制的螺旋更新机制和随机搜索机制,以及自适应收敛因子的群落多样性计算。
写这部分的体会是:np.random.random()和np.random.rand()的区别容易搞混,前者不指定维度时返回的是标量,指定维度时才是数组。融合公式里同时出现了随机权重、学习因子和位置差向量,一旦维度不对,代码直接报错或者静默计算出错误结果。
4.3 参数调优与运行结果观察
我实际调参后使用的配置如下:
| 参数 | 取值 | 说明 |
|---|---|---|
| 种群规模 | 30 | 在算法效果和计算速度之间折中 |
| 最大迭代次数 | 200 | 增加迭代次数收益递减 |
| 学习因子 c1 | 0.8 | 个体历史经验权重 |
| 学习因子 c2 | 0.8 | 全局最优引导权重 |
| 收敛因子 a_max | 2.0 | 控制初始勘探强度 |
| 螺旋更新概率 p | 0.5 | 平衡两种更新方式 |
| 航迹节点数 | 10 | 对应 30 维决策变量 |
跑一轮实验保存收敛曲线和航迹图后,重点检查两点:第一,收敛曲线是否平滑下降,如果在迭代后期出现明显的波动,说明参数设置可能存在问题;第二,三维航迹是否明显经过威胁区边缘,如果紧贴威胁区边界蜿蜒,说明威胁代价权重偏低。
5. 实验验证与对比分析
5.1 仿真环境与基准设置
仿真环境为 Python 3.10 + NumPy + Matplotlib,地形范围设定为10公里乘10公里区域,最高山峰高度1200米,起点坐标和终点坐标固定,威胁区设置5个。环境配置直接在 config.yaml 文件中定义,可随时修改重新模拟,方便做多次独立运行统计。
我跑了三组算法做对比:标准 WOA、标准 PSO、以及 PSO-ImWOA。每组算法独立运行 20 次,记录最佳代价、最差代价、平均代价和标准差。这种统计方式比单次运行结果更有说服力,因为群智能算法本身有随机性,一次运行结果好与不好纯属运气。
5.2 收敛性能对比
从实验结果来看,标准 WOA 在约 60 代左右就出现明显的收敛停滞,平均代价较高,后期几乎不再下降。标准 PSO 的表现略好于 WOA,但在复杂地形下也出现了一定程度的早熟。
PSO-ImWOA 的收敛曲线整体平稳下降,前 80 代收敛速度与标准算法差别不大,但在 100 代以后继续有明显的代价下降,最终在平均代价方面比标准 WOA 降低了约 22%,比标准 PSO 降低了约 13%。
这个结果的来源是清晰的:改进算法在迭代中后期依然保有勘探能力,种群多样性维持得更好,个体验不会过早地被拉向一个局部较优点。
5.3 航迹质量对比
画出三维航迹后,差异更为直观。标准 WOA 规划的航迹在部分区域直接穿过两个威胁区之间的狭窄通道,虽然航程短,但风险极高,这是威胁代价权重低或算法强制性不够导致的。标准 PSO 规划的航迹绕行幅度大,航程偏长。PSO-ImWOA 规划的航迹则在威胁区之间选择了较为合理的绕行路径,高度变化也更为平滑。
航迹平滑处理后的效果很关键。三次样条插值后,航迹变成了连续光滑曲线,折线拐角处被圆滑过渡,爬升角明显平缓,符合工程应用场景的要求。
5.4 消融实验分析
为了验证 PSI-ImWOA 各改进模块的有效性,我做了消融对比:
| 实验配置 | 平均代价 | 与完整算法对比 |
|---|---|---|
| 标准 WOA | 基线 | 高约22% |
| WOA + 反向学习初始化 | 低约8% | 仍有提升空间 |
| WOA + 融合PSO更新机制 | 低约15% | 最关键的改进点 |
| WOA + 自适应收敛因子 | 低约5% | 收敛更稳定 |
| 完整 PSO-ImWOA | 最低 | 综合最优 |
结果显示,融合PSO更新机制是收益最大的改进项,验证了算法融合方向本身的价值。反向学习和自适应收敛因子作为辅助改进,锦上添花。
6. 实操中的常见问题与排查技巧
这一部分整理了我在开发调试过程中遇到的实际问题,基本都是代码层面或算法调试层面的,网上很少有现成答案。
6.1 算法早熟:表现在哪、怎么调
早熟最直观的表现是收敛曲线在中后期变成一条水平直线,且最终航迹明显绕远路或者穿过危险区域。
排查思路:
- 检查收敛因子调整逻辑是否生效,打印每次迭代的 a 值;
- 检查种群多样性指标是否过小,正常情况下前期大、后期小,如果全程都很小说明初始化覆盖不够;
- 调大 c1,增强个体历史最优的引导作用;调大初始收敛因子 a_max。
6.2 代价函数量纲不匹配问题
航迹规划中长度代价动辄数十公里,威胁代价可能集中在0到10之间。如果不做归一化直接加权求和,长度代价会完全支配适应度,优化算法等于只优化了航程、直接忽视了威胁。
处理方式是分别统计长度代价的理论范围,将权重分配到量级接近的水平。比如身形大致估算长度代价最大可能值为 20,威胁代价最大值为 5,那么长度权重设为 1,威胁权重设为 4,它们的乘积量级在同一个水平上。
6.3 代码层面的常见易错点
最容易出错的点:
- 边界数组的类型问题:lb 和 ub 如果是 list,直接和 numpy 数组做运算时可能出现维度错误,统一转成 np.array。
- 个体历史最优在初始化时没有同步,导致第一轮迭代个体最优位置全是初始位置,优化效果打折。
- 更新 pbest 和 gbest 时没有及时复制数组,而是直接赋值引用,后续修改 positions 时把历史最优也改掉了。
具体到复制数组这一点:Python 中a = b是引用赋值,a = b.copy()才是值复制。在用 NumPy 做数组操作时这是一个容易忽视的坑,往往表现为算法明明在收敛,结果却偶尔跳变回一个“历史较差值”,因为最优值被无意覆盖。
强烈建议在调试时增加断点打印:每迭代 20 代就输出一次 gbest_val、种群平均间距、当前收敛因子 a。这三个数足够判断算法整体健康度。
6.4 三维可视化的细节
Matplotlib 画三维航迹时要注意坐标轴比例。如果 x、y 范围是 0~10000,z 范围是 0~1200,直接画图会导致高度方向的起伏完全看不出来。需要把 z 轴单独拉伸,可以用ax.set_box_aspect()来控制三个轴的比例。实际工程环境里很多项目展示的航迹“看起来”很崎岖,其实是比例失真造成的观感差异。
另外给地形上色时,建议用plot_surface加 colormap,然后用plot3D叠加航迹线,这样一幅图就能同时表达地形高度和规划航迹。
7. 项目经验总结与可扩展方向
这个项目做完,我最大的体会是:算法融合不是简单的“1+1”,而是要把两个算法各自的优势机制吃透,理解它们在搜索行为上的互补性。PSO 给 WOA 注入的是记忆能力,这个点找准了,改进效果立竿见影;找不准的话,叠加再多的策略也无非是变着花样随机游走。
从扩展性来看,这个项目框架往下可以加动态障碍物,把时间维度引入航迹规划,变成四维规划问题;也可以把单无人机扩展为多无人机协同规划,在多智能体场景下验证 PSO-ImWOA 的适应性。代价函数这块还能换成能耗模型,让规划结果直接对接电池续航估算。
一个小技巧送给各位:跑实验的时候把每次迭代的种群多样性和收敛因子都记录成日志,跑的轮次多了,你会摸清一套“参数敏感度直觉”,以后拿到新地形参数,基本不用怎么试就能调出可用的参数组合。航迹规划这类问题的最终目标不是“算法分数高”,而是规划出的路径真正能用。能把这一点想清楚,做出来的方案自然比单纯发一篇算法改进的论文更实在。