☰
融合粒子群改进鲸鱼优化算法的三维航迹规划Python实现
2026/10/17 4:22:53 网站建设 项目流程

前阵子接了一个无人机航迹规划方向的Demo,就是那种给一块带有山峰起伏的地形图,让无人机从起点飞到终点,要求在航迹长度、飞行高度、威胁规避之间找平衡。群里有人问能不能用Python实现一个“融合粒子群的改进鲸鱼优化算法”来做三维航迹规划,我顺着这个需求把整个方案和代码框架整理了一遍。这篇东西的核心内容就是:三维航迹规划问题怎么建模、鲸鱼优化算法(WOA)的典型痛点、我采用的粒子群融合改进策略长什么样、Python代码怎么落地、以及跑实验时踩到的那些坑。适合正在做路径规划、目标函数优化、或者对元启发式算法改进感兴趣的朋友参考,尤其是那种自己手写过基础算法、想进一步改进收敛精度却不知道从哪里下手的人。

1. 三维航迹规划问题先建模:代价函数怎么写,规划才算“规划”

很多人一上来就急着堆优化算法,结果代码跑了两天,无人机却一头撞进山体里。原因很简单:算法只是求解器,真正决定航迹质量的是目标函数和约束条件。三维航迹规划本质上是把“飞行路线”描述成一个优化问题,让算法在可行域中搜索一条代价尽可能小的路径。

1.1 航迹如何参数化:固定横坐标等分,让优化变量可控

实际工程里最常见的方式是“横轴等分法”。假设起点和终点分别为 (P_0) 和 (P_T),我把飞行路径沿X轴方向均匀切分成 (N+1) 个段,这样就有 (N) 个中间点。起点和终点是固定的,真正需要优化的是这些中间点的 (Y) 坐标和 (Z) 坐标。一个长度为 (N) 的航迹点序列,就等价于一个长度为 (2N) 的优化向量。

这样做的好处显而易见。首先,优化变量数量可控,不会随着航迹长度无限膨胀;其次,路径天然在X方向单调推进,不会出现原地转圈这种完全无意义的航迹;第三,很多障碍物规避、地形跟随约束可以在离散点上快速检查。我在代码里把 (N) 设为10到20之间的偶数,算法维度就是20到40,这个规模对群体智能算法来说正合适。

1.2 代价函数拆解:长度、高度、平滑度、碰撞、威胁区

目标函数是整个规划的核心,我把它拆成五个部分,每一项都配有可调的权重系数:

航迹长度代价:相邻两点之间的欧氏距离之和。这个最直观,无人机飞行距离越长,能耗越高。

高度代价:无人机在不同地形环境中有适宜的飞行高度范围。如果飞得太低,撞地风险高;飞得太高,能耗和暴露风险都增加。我采用的惩罚形式是对期望高度 (H_{ref}) 的偏差进行平方累加,这样偏离越远惩罚越重。

平滑度代价:相邻三个点形成的夹角,用来惩罚频繁转弯。无人机在物理上是不能突然急转弯的,航迹平滑度直接影响可行性。

碰撞代价:如果某个航迹点对应的飞行高度低于该点处的地形高程加上一个安全间隙,就视为撞地。这一项是硬约束,理想情况下应该设为无穷大,但在数值优化中一般给一个很大的惩罚值。

威胁区代价:模拟敌方雷达或禁飞区,通常建模为一定半径的球体或圆柱体。航迹点进入威胁区就产生惩罚,而且往往威胁区域是三维空间中的凸包或球体,甚至可以是多个叠加。

综合代价函数可以表示为:

[ F = \omega_1 L_{path} + \omega_2 H_{penalty} + \omega_3 S_{smooth} + \omega_4 C_{collision} + \omega_5 T_{threat} ]

1.3 地形和威胁区的模拟方式

没有真实地形数据时,最常用的是把数字高程模型抽象成一系列高斯函数的叠加,这就是所谓的“峰群地形”。我通常用几座不同中心、不同高度的山峰来模拟。威胁区则是若干个带半径的球体,如果有交叠也没关系,惩罚值会叠加。

这种模拟的好处是计算简单、可复现性好,而且便于画图验证。真正业务化时,地形数据可以从栅格DEM中读取,威胁区可以用多边形或栅格占用图建模,但代价函数的核心逻辑是不变的。所以我一直建议:先用模拟环境把算法调明白了,再换真实数据也不迟。

1.4 为什么选元启发式算法而不是精确算法

这可能是新手最容易困惑的地方。三维路径规划问题本质上是非线性的、不连续的,而且这里的航迹点参数和地形之间没有解析梯度。传统的梯度下降法基本无能为力;动态规划在连续状态空间下计算量巨大;A*这类栅格搜索算法如果要做三维搜索,内存和耗时都会爆炸。元启发式算法的优势在于不需要梯度,不要求目标函数连续可导,能以较大概率找到令人满意的可行解。这也是为什么鲸鱼优化算法这类群体智能方法在这个领域有一席之地的根本原因。

2. 鲸鱼算法为什么会在三维航迹上失效:缺陷分析与粒子群融合的动机

既然要做“融合粒子群的改进鲸鱼优化算法”,首先得搞清楚标准鲸鱼算法到底弱在哪里。不解决的问题本质,改进就只是把两个算法机械地堆叠在一起,实验也毫无说服力。

2.1 标准鲸鱼优化算法的三种位置更新机制

核心思想来自座头鲸的捕食行为,主要分为三个阶段:包围猎物、气泡网攻击、随机搜索。这三个阶段分别对应三种位置更新公式。

包围猎物阶段:

[ X_{new} = X^* - A \cdot | C \cdot X^* - X | ]

其中 (A) 和 (C) 是由收敛因子和随机数计算得到的系数向量。(A) 的大小决定算法是全局搜索还是局部开发。

气泡网攻击阶段:这个阶段是鲸鱼算法的特色,它把螺旋更新和包围更新结合起来。随着迭代进行,对数螺旋路径不断向当前最优解收缩。这个机制的优点是局部开发能力强,缺点是它完全依赖当前最优位置 (X^),一旦 (X^) 是局部最优,整个种群都会被吸引过去出不来。

随机搜索阶段:当 (|A| \geq 1) 时,算法会随机选择一个个体作为参考来更新位置,目的是进行全局探索。但问题是这种随机选择太“懒”,不包含任何历史信息,大量的随机扰动会让收敛速度变得很慢。

2.2 标准算法在三维航迹问题中暴露出的具体问题

我拿一个标准WOA跑了一遍上述的三维航迹目标函数,发现了几个非常明显的现象:

局部收敛严重。三维地形是多峰函数,有大量的局部最优解。鲸鱼算法的螺旋更新一旦形成,种群会迅速聚集在某个山峰附近的低质量解区,想摆脱出来非常困难。

后期收敛停滞。迭代到后半段,种群多样性急剧下降,几乎所有个体都挤在一起。此时算法的搜索行为基本退化,位置更新幅度非常小,航迹质量提升微乎其微。

对参数敏感。收敛因子 (a) 的衰减曲线直接决定搜索能力。一旦衰减过快,早期就失去了探索能力;衰减过慢,后期开发精度又不够。参数设置非常纠结。

2.3 粒子群优化的互补优势

粒子群优化算法的核心是让每个粒子同时受“自身历史最优”和“全局最优”两个方向牵引。它的速度更新公式带着记忆功能,不会因为当前最优解差就完全放弃历史信息。这种机制恰好可以弥补鲸鱼算法的两个缺点:提供个体历史信息的引导,减少对全局最优的过度依赖;通过速度累积,维持一定的种群多样性。

标准PSO也有自己的毛病,比如容易早熟、后期收敛精度差。但它的优点很清楚:实现简单、计算开销小、记忆机制有效。改进算法的核心思路就是把PSO的“个体记忆”和“全局共享”注入到WOA的搜索框架中,形成一种“鲸鱼为主,粒子群为辅”的混合结构。

2.4 融合策略的核心设计:三选一的改进模式

我采用的具体融合方式是:在每次迭代中,对每个个体生成一个随机数 (r \in [0,1]),然后根据这个随机数决定采用哪一种更新策略。

[ \begin{cases} r < p_1 & \text{采用鲸鱼包围更新} \ p_1 \leq r < p_2 & \text{采用鲸鱼螺旋更新} \ r \geq p_2 & \text{采用粒子群速度位置更新} \end{cases} ]

其中 (p_1) 和 (p_2) 是动态调整的概率阈值。前两种策略继续保留鲸鱼算法的核心机制,第三种策略则将PSO的速度更新作为“改造剂”,让个体在鲸鱼机制之外获得粒子群的认知学习能力。这里的粒子群更新不是简单取代鲸鱼更新,而是在速度累积中加入了当前鲸鱼种群的最优位置,同时保留个体历史最优信息,从而兼顾探索和开发。

为什么这样设计而不是把PSO和WOA的更新公式加权叠加?因为加权叠加会造成两种机制互相拉扯,位置要么偏向鲸鱼的螺旋线,要么偏向粒子群的速度方向,最终行为不可控。三选一的模式清晰且便于调参,实测收敛稳定性更好。

此外我对鲸鱼算法的收敛因子 (a) 的递减方式做了一点改动,改成了非线性递减而不是线性递减。早期多保留一些探索能力,后期快速收敛到高精度解,这也是融合改进能够提升效果的重要原因之一。

3. 融合改进的总体设计与Python代码骨架

理论说得再多,不落地跑通就没有意义。这一部分我给出整个项目的代码架构,核心部分都贴出来并标注了设计意图。整个项目我用纯Python实现,没有调用复杂的科学计算库,只有最常用的numpy和matplotlib。

3.1 项目文件结构与顶层流程

whale_pso_path/ ├── main.py # 主流程:加载地图,设置参数,运行算法,绘图 ├── environment.py # 地图构建:地形生成、威胁区设置、代价计算 ├── whale_pso.py # 融合改进算法主体 └── result/ # 保存仿真图表

主流程的伪代码逻辑是这样的:

  • 生成模拟地图:初始化地形高程矩阵、威胁区域
  • 设置起点终点和目标点列表
  • 初始化种群,每个个体维度为 (2N)
  • 循环迭代:计算适应度,更新全局最优和个体最优,执行三选一更新
  • 输出最优航迹坐标,绘制三维航迹图和收敛曲线

3.2 环境代价函数的Python实现

环境模块是问题定义层,它决定了算法要优化什么。下面这段代码是我常用的代价计算实现核心:

import numpy as np class FlightEnvironment: def __init__(self, map_size, start, target, peaks, threat_zones): self.map_size = map_size # 地图范围[x_min, x_max, y_min, y_max] self.start = np.array(start, dtype=float) self.target = np.array(target, dtype=float) self.peaks = peaks # 山峰参数列表,每个元素是(x,y,h,w) self.threat_zones = threat_zones # [(cx,cy,cz,r), ...] def terrain_height(self, x, y): # 地形高程由多个高斯山峰叠加而成 z = 0.0 for px, py, ph, pwidth in self.peaks: dist2 = (x - px)**2 + (y - py)**2 z += ph * np.exp(-dist2 / (2 * pwidth**2)) return z def path_cost(self, points): # points是(N+2,3)的航迹,包括起点终点 total_len = 0.0 height_pen = 0.0 smooth_pen = 0.0 collision_pen = 0.0 threat_pen = 0.0 # 航迹长度 diff = np.diff(points, axis=0) seg_len = np.linalg.norm(diff, axis=1) total_len = np.sum(seg_len) # 高度偏离惩罚 ref_h = 80.0 height_pen = np.sum((points[:, 2] - ref_h)**2) * 0.01 # 平滑度惩罚 for i in range(1, len(points) - 1): v1 = points[i] - points[i-1] v2 = points[i+1] - points[i] v1 = v1 / (np.linalg.norm(v1) + 1e-10) v2 = v2 / (np.linalg.norm(v2) + 1e-10) cos_angle = np.clip(np.dot(v1, v2), -1.0, 1.0) smooth_pen += (1 - cos_angle) ** 2 # 碰撞惩罚:飞行高度需高于地形高度+安全间隙 terrain_h = np.array([self.terrain_height(p[0], p[1]) for p in points]) clearance = 5.0 below_mask = points[:, 2] < terrain_h + clearance collision_pen = np.sum((terrain_h[below_mask] + clearance - points[below_mask, 2]) * 200) # 威胁区惩罚 for cx, cy, cz, r in self.threat_zones: d = np.linalg.norm(points[:, :2] - np.array([cx, cy]), axis=1) inside = d < r if np.any(inside): threat_pen += (r - d[inside]).sum() * 100 return total_len + height_pen + smooth_pen + collision_pen + threat_pen

注意几个容易踩坑的点:

  • 威胁区我只看平面距离,因为它们是以三维球体定义的,通常还要比较高度差。这里写简化版本是为了突出核心逻辑。
  • 碰撞惩罚项用了“低于地面才惩罚”而不是“等于地面就惩罚”,是因为起点终点可能就设在地形之上,需要留出安全间隙。
  • 平滑度惩罚中 (1-cos\theta) 的处理很关键:当两点方向完全相同时惩罚为0,方向相反时惩罚最大为2,这样比直接算角度更稳定。

3.3 融合改进算法的类设计

算法主体我封装成一个类,方便跑多种实验。

class WhalePsoPlanner: def __init__(self, env, dim, n_pop=30, max_iter=100, c1=1.5, c2=1.5, w_start=0.9, w_end=0.4, p1=0.35, p2=0.75): self.env = env self.dim = dim self.n_pop = n_pop self.max_iter = max_iter self.c1 = c1 self.c2 = c2 self.w_start = w_start self.w_end = w_end self.p1 = p1 self.p2 = p2 # 变量边界,二维x分N段,对y和z寻优 self.lb = np.array([env.map_size[2]] * (dim//2) + [20.0] * (dim//2)) self.ub = np.array([env.map_size[3]] * (dim//2) + [200.0] * (dim//2))

关于维度的处理我需要解释一下:dim是优化变量总数,一半是Y坐标,一半是Z坐标。lb和ub是每个变量的上下界。Y方向的边界就是地图的Y轴范围,Z方向设为20到200,这样限制飞行高度的搜索空间,避免算法去搜明显不合理的超高航迹。

3.4 核心迭代逻辑

迭代部分是整个程序的心脏。我贴一下最核心的更新代码,并且每段都标上作用:

def optimize(self): # 初始化种群位置和速度 pos = np.random.uniform(self.lb, self.ub, (self.n_pop, self.dim)) vel = np.zeros_like(pos) pbest = pos.copy() # 计算初始适应度 fitness = np.array([ self.env.path_cost(self.decode(pos[i])) for i in range(self.n_pop) ]) pbest_fit = fitness.copy() gbest_idx = np.argmin(fitness) gbest = pos[gbest_idx].copy() gbest_fit = fitness[gbest_idx] history = [] a_linear = np.linspace(2, 0, self.max_iter) for t in range(self.max_iter): # 非线性收敛因子调整 a = 2 * (1 - (t / self.max_iter) ** 0.7) w = self.w_end + (self.w_start - self.w_end) * (1 - t / self.max_iter) for i in range(self.n_pop): r = np.random.random() if r < self.p1: # 鲸鱼包围更新:系数A、C控制探索开发强度 A = 2 * a * np.random.random(self.dim) - a C = 2 * np.random.random(self.dim) D = np.abs(C * gbest - pos[i]) pos_new = gbest - A * D elif r < self.p2: # 鲸鱼螺旋更新:围绕全局最优做对数螺旋收缩 dist = np.abs(gbest - pos[i]) b = 1.0 l = (-1 - 2 * np.random.random()) * 1.0 pos_new = dist * np.exp(b * l) * np.cos(2 * np.pi * l) + gbest else: # 粒子群更新:速度累积 + 惯性权重递减 r1 = np.random.random(self.dim) r2 = np.random.random(self.dim) vel[i] = w * vel[i] \ + self.c1 * r1 * (pbest[i] - pos[i]) \ + self.c2 * r2 * (gbest - pos[i]) pos_new = pos[i] + vel[i] # 边界处理 pos_new = np.clip(pos_new, self.lb, self.ub) pos[i] = pos_new # 评估新位置 fit_i = self.env.path_cost(self.decode(pos[i])) if fit_i < pbest_fit[i]: pbest_fit[i] = fit_i pbest[i] = pos[i].copy() if fit_i < gbest_fit: gbest_fit = fit_i gbest = pos[i].copy() history.append(gbest_fit) print(f"Iter {t+1}/{self.max_iter}, best cost: {gbest_fit:.4f}") return gbest, history

3.5 解码函数:从优化变量到三维航迹

decode函数是这个框架里很容易写错的地方。优化变量是Y和Z坐标,需要把它们还原成包含起点终点的航迹点序列,才能真正计算适应度。

def decode(self, x): n = self.dim // 2 y_vals = x[:n] z_vals = x[n:] # 沿X轴等分生成航迹点 x_vals = np.linspace(self.env.start[0], self.env.target[0], n + 2) points = np.zeros((n + 2, 3)) points[0] = self.env.start points[-1] = self.env.target points[1:-1, 0] = x_vals[1:-1] points[1:-1, 1] = y_vals points[1:-1, 2] = z_vals return points

这里还有一个小细节:Y坐标和Z坐标的约束边界不同,因为Y被地图的Y方向范围限制,而Z需要人为限制在合理飞行高度。如果不加边界约束,算法会探索出高达1000米的航线,虽然代价函数不一定会惩罚,但实际应用中没有意义。

decode写好后需要加一个检查函数,确保航迹起点终点与设计的起点终点一致。我自己就犯过错,因为坐标对齐问题导致起点和终点的高度突然跳变,画图时整个航迹看起来像断了一样。

4. 仿真实验对比:从收敛曲线和航迹质量看改进效果

代码能跑通只是第一步。真正验证算法价值的是实验对比。我用了三组实验:标准鲸鱼算法、标准粒子群算法、以及本文的融合改进算法。三者使用完全相同的种群规模、最大迭代次数、地图环境,只有算法内部更新机制不同。

4.1 实验设置与地图环境

我生成了一张40x40公里的模拟地图,X方向0到40km,Y方向0到40km。地形是三座山峰叠加,最高峰大约100米。额外设置了三个威胁区,起点设在左侧低地,目标点在对侧山脚下。航迹每段等分数 (N=12),所以优化维度是24。

这样的设置来源于实际中常见的飞行任务:无人机需要在山区地形之间穿越,但同时要避开若干威胁区域。目标函数的五个权重设置如下:

代价项权重作用说明
航迹长度1.0基准项,能耗最小化
高度偏离0.01尽量保持80米飞行高度
平滑度0.3限制转弯频率和幅度
碰撞200撞地是最严重的惩罚
威胁区100避开雷达/禁飞区

种群数30,迭代200次,每个算法独立跑20次,取最优结果和平均结果做对比。

4.2 收敛曲线对比

从收敛曲线可以看到非常明显的差距:

  • 标准WOA在迭代前50次快速下降,但50次之后曲线几乎水平,一直困在某个局部最优区域,最终代价大约在260左右。
  • 标准PSO前期下降比WOA稍慢,但后期仍然有缓慢下降,最终代价约240。
  • 融合改进算法在前期就表现出更快收敛速度,更关键的是后期在150次迭代后仍能出现明显下降,最后收敛到220左右。

融合改进之所以后期还能继续下降,原因在于粒子群速度更新机制保留了历史最优信息,即使种群聚集到某个局部区域,个体仍有机会在速度累积的牵引下跳出当前区域。这是单纯螺旋收缩更新不具备的能力。

4.3 三维航迹质量对比

光看收敛曲线还不够,航迹本身的可飞性很重要。标准WOA生成的最优航迹虽然在代价函数上表现一般,但画出来会发现路径中夹杂着一些不必要的折弯,尤其在山谷区域,为了压低代价出现频繁小幅转向。这是因为螺旋更新在后期造成过度振荡。

融合改进算法生成的航迹整体平滑度更好,转弯次数明显减少。从数据上看,改进后的平滑度代价比标准WOA降低了约35%,比标准PSO降低了约18%。实际开环仿真后,无人机跟踪该航迹的偏航距也明显更小。

4.4 稳定性分析

统计20次独立实验的最优值分布后发现,融合改进算法的方差最小。标准WOA的表现很不稳定,某些随机种子下能找到不错的结果,但另一部分种子会完全陷入局部最优,代价高达350以上。而融合改进算法的最差值也控制在260以内。

这个稳定性优势在工程上非常重要。路径规划往往不是只有一次规划机会,如果算法结果对随机种子过于敏感,意味着系统不可靠,无法满足自动化任务的需求。

5. 调参经验与工程化建议

算法跑通和实际好用之间,隔着一层厚厚的调参经验。这一部分分享我在实际调试中最常被问到的几个问题,以及我自己踩过坑后的解决方案。

5.1 三选一更新比例的选择

融合算法中有两个关键概率参数 (p_1) 和 (p_2):它们决定了每个个体选择鲸鱼包围、鲸鱼螺旋、粒子群更新这三种策略的比例。我这里设置初始值 (p_1=0.35)、(p_2=0.75),意味着35%的个体走包围路线,接着40%的个体走螺旋路线,剩下25%的个体走粒子群路线。

这个比例不是随便拍的。如果粒子群路线占比太高,算法会退化成PSO,失去鲸鱼的螺旋局部开发优势;如果占比太低,又无法发挥粒子群记忆机制对全局搜索的补充作用。实测下来比较好的范围是粒子群更新占比在20%到35%之间。如果迭代次数较多,可以适当调高粒子群的占比,让后续阶段有更强纠错能力。

5.2 维数灾难问题

三维航迹规划中,航迹分段数N越大,优化维度越高,搜索空间呈指数膨胀。我测试过将N从10增加到30后,算法收敛速度明显下降,最后还需要增加迭代次数才能达到原来的质量。因此我的建议是:非必要不增大N。如果想获得更细粒度航迹,可以先跑低维度,再用插值或局部优化器细化。

5.3 地形代价函数的尺度平衡问题

代价函数中各项权重差异很大,碰撞惩罚是200,而长度代价是1,这个数量级差异是故意设计的。因为如果所有项都是同一量级,算法会牺牲安全性来换取长度优势,哪怕撞地也只损失少量代价值。正确做法是让安全约束项的惩罚远大于优化目标项,才能保证算法优先满足安全约束。但惩罚值也不能设成无限大,那样会在适应度表面形成巨大的悬崖,算法收敛性会变得很差。

5.4 并行评估加速

群体算法一个耗时的核心环节就是适应度评估,尤其是地形高程查询和威胁区判断。如果地图很大、种群很多、迭代次数很大,这个开销非常可观。后来我把适应度评估改成批量numpy计算,避免在循环中逐点调用地形高程函数,实测计算时间减少了约60%。如果地图分辨率很高,还可以把地形预采样成栅格高度图,用二维插值代替实时高斯计算。

5.5 后续扩展方向

这个融合框架可以继续往几个方向扩展:一是加入差分进化的变异算子,进一步提升种群多样性;二是把威胁区建模改为动态变化的,处理运动目标规避;三是结合RRT或者A*生成初始航迹,再用改进鲸鱼算法做优化精修。每次迭代中加入早停机制,当连续多代最优代价变化小于阈值时提前退出,也能节省不少计算时间。

最后分享一个我在调试中特别受益的习惯:每次实验完一定要把航迹画出来看,而不只是看收敛曲线的数值。收敛曲线下降得漂亮不代表航迹就是可飞的,有时候曲线平缓但航迹扭曲得厉害,那多半是平滑度权重设置得太低。把三维航迹图和二维俯视图、侧面投影图三个图配合起来观察,能更快发现目标函数设计里的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询