☰
多智能体强化学习无人机三维路径规划:MAPPO源码实战与避坑指南
2026/10/12 1:17:12 网站建设 项目流程

简介:本资源面向具备Python与强化学习基础的研究人员、工程师及高年级本科生、研究生,提供一套基于多智能体强化学习(MARL)的无人机三维路径规划完整项目实例。内容围绕三维连续空间环境建模、MAPPO算法协同策略训练、局部观测与集中式价值网络设计、安全约束融入、候选路线评估与自动选择机制展开,实现多无人机在复杂障碍环境中的自主避障与高效路径规划,可应用于城市低空物流、森林巡检、应急救援、电力巡检及室内仓储等场景。资源包为1个docx文档,约141KB,系统梳理了项目背景、模型架构、代码示例与应用领域,涵盖三维环境与障碍物判定、高斯策略网络、集中式价值网络、轨迹缓存与优势计算、MAPPO式训练更新及自动路线选择等模块。已有98人学习,适合作为多智能体连续控制、信用分配与CTDE关键技术研究的可复现实验基础,也可支撑科研原型与工程预研。

1. 从一份能跑起来的 MARL 无人机三维路径规划源码说起

如果你正在找一份能直接跑通的多智能体强化学习无人机三维路径规划项目,这份 Python 源码包值得花时间拆一遍。它不是那种只丢几个函数片段的演示代码,而是把三维连续空间环境、MAPPO 训练主循环、候选路线滚动评估、安全动作过滤,以及一个可交互的 GUI 仿真界面全部串了起来。低空物流、森林巡检、应急救援这些场景里,多架无人机要在有障碍物的三维空间里同时避障、保持间距、省电、按时到达,传统 A* 或人工势场在连续空间里要么搜索爆炸要么陷局部极小,而这份代码用集中训练分散执行的思路把问题拆开了。适合有 Python 和强化学习基础、想拿一个完整工程原型做二次开发或论文实验的人。下面按「它怎么搭起来 → 怎么跑起来 → 哪里会翻车」的顺序拆。

2. 三维环境与 MAPPO 架构:先看清状态、动作、奖励怎么定义

2.1 连续三维状态空间与局部观测设计

这份代码没有把空间离散成网格,而是用连续坐标表示每架无人机的位置和速度。单机状态包含位置 $(x,y,z)$、速度 $(v_x,v_y,v_z)$、目标相对位置、最近障碍物相对特征、最近邻居相对位置和剩余能量。局部观测维度固定,这样策略网络可以用同一套 MLP 处理不同数量的无人机。障碍物用中心点加半径描述,碰撞判定就是位置到障碍中心的欧氏距离比较,简单但够用。

集中式评论家接收的是所有无人机局部观测拼接后的联合状态。训练时它能“看到”全局,执行时每架无人机只用自己的观测,这就是 CTDE 的核心。常见做法是保留距离最近的 K 个障碍物和邻居,不足的用零填充,避免观测维度随场景变化。我一般会把 K 设成 5 到 8,太少容易漏掉关键障碍,太多则网络输入冗余、训练变慢。

2.2 高斯策略网络与集中式价值网络

策略网络输出三维高斯分布的均值和标准差,动作采样后经 tanh 压到 $[-1,1]$,再缩放到实际最大速度。用高斯而不是确定性输出,是为了在训练阶段保留探索能力;执行阶段取均值动作或经过候选评估后的动作,路线就不会抖。网络同时输出动作对数概率,供 PPO 计算新旧策略概率比。

价值网络输入联合观测,输出一个标量价值,只在训练阶段用。它的作用是估计当前多机整体状态能拿多少折扣累计回报,帮策略理解“我这步动作对团队的影响”。推理时价值网络不参与,所以不会增加飞控延迟。这里有个容易忽略的点:价值网络和策略网络不要共享太多底层参数,否则多智能体非平稳性会让两者互相拖累,训练曲线会出现奖励突然塌陷。

2.3 分层奖励与信用分配

奖励是这份代码里最需要细看的部分。它没有只用“到达目标给正奖励”这种稀疏信号,而是拆成几层:个体到目标的距离进展、碰撞惩罚、机间过近惩罚、边界惩罚、能量消耗项、到达奖励。团队奖励鼓励整体完成,个体进展奖励提供密集学习信号,安全惩罚强化避障意识。

信用分配难就难在多架无人机同时动,一次成功说不清是谁的功劳。代码用集中式评论家加广义优势估计来缓解,优势值再做归一化。如果只给团队奖励,个体反馈太稀疏,训练慢;如果只给个体奖励,每架都只顾自己飞,编队和避碰就崩了。分层组合是常见解法,但权重要调——碰撞惩罚通常要比距离奖励大一个量级,否则策略会为了抄近路而擦着障碍飞。

2.4 环境与网络的代码骨架

下面这段是环境状态转移和观测构造的核心逻辑,参数含义写在注释里。

import numpy as np class MultiUAVEnv: def __init__(self, num_uavs=3, map_size=100.0, max_speed=5.0, obs_radius=30.0, safe_dist=4.0, max_steps=300): self.num_uavs = num_uavs # 无人机数量 self.map_size = map_size # 立方体边长 self.max_speed = max_speed # 单步最大位移 self.obs_radius = obs_radius # 局部观测感知半径 self.safe_dist = safe_dist # 机间最小安全间距 self.max_steps = max_steps # 单回合最大步数 self.reset() def reset(self): # 随机初始位置与目标,扩大经验覆盖 self.pos = np.random.uniform(0, self.map_size, (self.num_uavs, 3)) self.goal = np.random.uniform(0, self.map_size, (self.num_uavs, 3)) self.vel = np.zeros((self.num_uavs, 3)) self.energy = np.ones(self.num_uavs) # 归一化剩余能量 self.step_count = 0 return self._get_obs() def _get_obs(self): obs = [] for i in range(self.num_uavs): # 自身位置归一化 + 速度 + 目标相对位置 self_feat = np.concatenate([ self.pos[i] / self.map_size, self.vel[i] / self.max_speed, (self.goal[i] - self.pos[i]) / self.map_size, [self.energy[i]] ]) # 最近邻居相对位置(简化:取最近一架) dists = np.linalg.norm(self.pos - self.pos[i], axis=1) dists[i] = np.inf j = np.argmin(dists) neighbor = (self.pos[j] - self.pos[i]) / self.map_size obs.append(np.concatenate([self_feat, neighbor])) return np.array(obs, dtype=np.float32) def step(self, actions): # actions: (num_uavs, 3) 已缩放到 [-max_speed, max_speed] self.pos += actions self.pos = np.clip(self.pos, 0, self.map_size) # 边界约束 self.energy -= 0.001 * np.linalg.norm(actions, axis=1) self.step_count += 1 rewards = np.zeros(self.num_uavs) done = False for i in range(self.num_uavs): d_goal = np.linalg.norm(self.goal[i] - self.pos[i]) rewards[i] -= 0.01 * d_goal # 距离进展惩罚 if d_goal < 3.0: rewards[i] += 10.0 # 到达奖励 if self.energy[i] <= 0: rewards[i] -= 5.0 # 机间过近惩罚 for i in range(self.num_uavs): for j in range(i + 1, self.num_uavs): if np.linalg.norm(self.pos[i] - self.pos[j]) < self.safe_dist: rewards[i] -= 2.0 rewards[j] -= 2.0 if self.step_count >= self.max_steps: done = True return self._get_obs(), rewards, done, {}

这段代码里几个参数直接决定训练能不能收敛:safe_dist太小策略会贴脸飞,太大在密集障碍里无路可走;max_speed决定单步位移,太大容易一步跨过障碍边界,太小则回合内到不了目标;max_steps要按地图尺寸和目标距离估,一般留 2 到 3 倍余量。奖励里的系数不是随便写的,距离惩罚和到达奖励的比例决定了策略是“稳着飞”还是“冲过去”。

3. MAPPO 训练主循环:从轨迹缓存到策略更新怎么落地

3.1 轨迹缓存与广义优势估计

MAPPO 的训练流程是:采样一批轨迹 → 存进缓存 → 算优势 → 多轮更新策略和价值网络。轨迹缓存要存每步的局部观测、联合观测、动作、动作对数概率、奖励、完成标记和价值估计。优势用 GAE 算,折扣因子和 GAE 参数是两个关键旋钮。

import torch import torch.nn as nn import numpy as np class TrajectoryBuffer: def __init__(self): self.obs, self.joint_obs, self.acts = [], [], [] self.logps, self.rews, self.dones, self.vals = [], [], [], [] def store(self, obs, joint_obs, act, logp, rew, done, val): self.obs.append(obs) self.joint_obs.append(joint_obs) self.acts.append(act) self.logps.append(logp) self.rews.append(rew) self.dones.append(done) self.vals.append(val) def compute_gae(self, last_val, gamma=0.99, lam=0.95): # gamma: 折扣因子,lam: GAE 平滑参数 rews = np.array(self.rews) # (T, N) vals = np.array(self.vals) # (T, N) dones = np.array(self.dones) # (T, N) T, N = rews.shape adv = np.zeros((T, N), dtype=np.float32) last_gae = np.zeros(N, dtype=np.float32) for t in reversed(range(T)): if t == T - 1: next_val = last_val else: next_val = vals[t + 1] delta = rews[t] + gamma * next_val * (1 - dones[t]) - vals[t] last_gae = delta + gamma * lam * (1 - dones[t]) * last_gae adv[t] = last_gae returns = adv + vals # 优势归一化,降低方差 adv = (adv - adv.mean()) / (adv.std() + 1e-8) return adv, returns

gamma设 0.99 是常见起点,任务回合越长越接近 1;lam控制偏差和方差的权衡,0.95 是比较稳的值。优势归一化这一步别省,多智能体环境里奖励尺度差异大,不归一化梯度会炸。缓存按回合清空,不要跨回合混用,否则 done 标记会错。

3.2 裁剪概率比与多轮更新

PPO 的核心是裁剪概率比,限制新策略相对旧策略的更新幅度。MAPPO 把这套搬到多智能体,每架无人机有自己的策略网络(或共享网络加身份编码),但价值网络是集中的。

def mappo_update(policy, critic, optimizer, buffer, last_val, clip_eps=0.2, epochs=10, batch_size=64): adv, returns = buffer.compute_gae(last_val) obs = torch.tensor(np.array(buffer.obs), dtype=torch.float32) joint_obs = torch.tensor(np.array(buffer.joint_obs), dtype=torch.float32) acts = torch.tensor(np.array(buffer.acts), dtype=torch.float32) old_logps = torch.tensor(np.array(buffer.logps), dtype=torch.float32) adv_t = torch.tensor(adv, dtype=torch.float32) ret_t = torch.tensor(returns, dtype=torch.float32) T, N = obs.shape[0], obs.shape[1] obs_flat = obs.reshape(T * N, -1) acts_flat = acts.reshape(T * N, -1) old_logps_flat = old_logps.reshape(T * N) adv_flat = adv_t.reshape(T * N) joint_flat = joint_obs.reshape(T, -1) for _ in range(epochs): idx = np.random.permutation(T * N) for start in range(0, T * N, batch_size): b = idx[start:start + batch_size] mean, std = policy(obs_flat[b]) dist = torch.distributions.Normal(mean, std) new_logp = dist.log_prob(acts_flat[b]).sum(-1) ratio = torch.exp(new_logp - old_logps_flat[b]) # 裁剪概率比,防止策略突变 surr1 = ratio * adv_flat[b] surr2 = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * adv_flat[b] policy_loss = -torch.min(surr1, surr2).mean() # 价值网络用联合观测,均方误差更新 val_pred = critic(joint_flat).squeeze(-1) value_loss = ((val_pred - ret_t.mean(1)) ** 2).mean() loss = policy_loss + 0.5 * value_loss optimizer.zero_grad() loss.backward() # 梯度裁剪,抑制数值震荡 nn.utils.clip_grad_norm_(list(policy.parameters()) + list(critic.parameters()), 0.5) optimizer.step()

clip_eps一般 0.1 到 0.3,太大策略更新猛、训练不稳,太小学习慢。epochs是同一批数据重复利用的轮数,10 左右常见,太多会过拟合当前批次。梯度裁剪阈值 0.5 是经验值,多智能体里梯度范数容易飙,不裁的话一次更新就能把策略带偏。价值损失权重 0.5 也是可调的,价值学得太快会压制策略探索。

3.3 候选路线滚动评估与安全过滤

策略网络直接采样有偶然性,代码里加了一层候选评估:对当前观测生成多个带扰动的候选动作序列,短期滚动几步,按累计代价打分,选最高的执行。这相当于在策略输出外面套了一个局部规划器,降低单次采样的抖动。

def select_safe_action(policy, obs, env, num_candidates=8, horizon=3): # 生成多个候选动作,滚动评估后选最优 best_action, best_score = None, -1e9 with torch.no_grad(): mean, std = policy(torch.tensor(obs, dtype=torch.float32)) for _ in range(num_candidates): # 在策略均值附近加扰动,保持多样性 noise = torch.randn_like(mean) * 0.3 cand = torch.tanh(mean + noise).numpy() * env.max_speed score = 0.0 pos_backup = env.pos.copy() for h in range(horizon): # 简化滚动:只评估距离进展和碰撞风险 new_pos = env.pos + cand d_goal = np.linalg.norm(env.goal - new_pos, axis=1).sum() score -= d_goal for i in range(env.num_uavs): for j in range(i + 1, env.num_uavs): if np.linalg.norm(new_pos[i] - new_pos[j]) < env.safe_dist: score -= 50.0 # 碰撞风险重罚 env.pos = new_pos env.pos = pos_backup if score > best_score: best_score, best_action = score, cand return best_action

候选数 8 和扰动 0.3 是平衡计算量和多样性的起点,滚动步数 3 足够捕捉近期碰撞风险。安全过滤是最后一道闸:动作执行前检查边界和碰撞预测,不通过就换成更保守的候选。这套“学习策略 + 规则安全层”的结构在实际部署里很常见,纯靠策略网络兜底风险太大。

4. 避坑与排查:训练不收敛、无人机盘旋、GUI 卡顿怎么解

4.1 奖励曲线震荡或突然塌陷

现象是训练前期奖励上升,中途突然掉下去再也起不来。原因通常是策略更新幅度过大,或者价值网络和策略网络互相拖累。解决:把clip_eps降到 0.1,梯度裁剪阈值降到 0.3,检查优势归一化有没有生效。如果价值损失远大于策略损失,把价值损失权重从 0.5 降到 0.2,让策略先学。多智能体非平稳性强,学习率别设太大,3e-4 是上限。

4.2 无人机原地盘旋或反复绕圈

现象是回合结束也没到目标,轨迹画出来是一团乱麻。原因一般是距离奖励太弱、能量惩罚太重,策略觉得“不动最省电”。解决:把距离进展惩罚系数调大,或者改成“每步距离减少才给正奖励”的差分形式;能量惩罚系数降到 0.0001 量级,别让它主导。还有一种可能是观测里目标相对位置没归一化,数值范围和其他特征差太多,网络学不动。

4.3 多机贴脸飞、安全距离形同虚设

现象是无人机之间距离经常小于safe_dist,但奖励没明显惩罚。原因是过近惩罚系数太小,或者惩罚只在距离小于阈值时触发、没有梯度过渡。解决:把过近惩罚改成连续函数,距离越近惩罚越大,比如-k * (safe_dist - d);系数调到距离奖励的 5 到 10 倍。另外检查安全过滤层有没有真正生效,候选评估里的碰撞重罚要足够大。

4.4 GUI 仿真卡顿或三维画布刷新慢

现象是点“自动规划”后界面卡住,或者三维路线画布刷新掉帧。原因是主线程里跑了推理或训练,或者每帧重绘了整个场景。解决:把模型推理放到单独线程,GUI 只负责渲染;三维画布用增量更新,别每帧清空重画所有障碍和轨迹。如果用了 matplotlib 的 3D 画布,注意它本身性能有限,路线点多了会卡,可以降采样显示。

4.5 换新地图后完成率暴跌

现象是训练地图上完成率很高,换一组障碍布局就频繁碰撞或超时。原因是策略过拟合了训练地图的障碍分布。解决:训练时随机化障碍物数量、位置、半径,初始位置和目标也随机;评估时专门留几组没参与训练的地图。观测里保留最近 K 个障碍物而不是固定索引,能提升对布局变化的适应。如果还不行,考虑加注意力或图神经网络处理邻居和障碍特征。

5. 进阶:把训练好的策略接进 GUI 做实时仿真验证

5.1 模型加载与推理线程

训练完保存的是策略网络和价值网络的参数。GUI 里只需要加载策略网络,价值网络不参与推理。加载时注意观测维度和训练时一致,否则会报形状错误。推理放在独立线程,主线程只管界面刷新,这样点“单步运行”或“自动规划”时界面不会假死。

import threading import torch class InferenceWorker(threading.Thread): def __init__(self, policy, env, interval=0.05): super().__init__(daemon=True) self.policy = policy self.env = env self.interval = interval # 推理间隔,控制仿真速度 self.running = False self.paused = False def run(self): while self.running: if not self.paused: obs = self.env._get_obs() with torch.no_grad(): mean, _ = self.policy(torch.tensor(obs, dtype=torch.float32)) action = torch.tanh(mean).numpy() * self.env.max_speed self.env.step(action) threading.Event().wait(self.interval)

interval控制仿真节奏,太小界面刷新跟不上,太大看起来一顿一顿。一般 0.05 到 0.1 秒比较顺。线程里不要直接操作 GUI 控件,用队列把状态传回主线程刷新。

5.2 三维路线渲染与状态卡片

GUI 右侧的三维画布要画障碍物、无人机当前位置、历史轨迹和目标点。障碍物用半透明球体或长方体,轨迹用折线,无人机用带朝向的小模型。状态卡片显示每架无人机的剩余能量、到目标距离、当前速度和是否到达。刷新频率跟推理线程对齐,别每帧全量重绘。

界面区域刷新内容刷新频率建议
三维画布无人机位置、轨迹、障碍与推理同步,约 10 到 20 Hz
状态卡片能量、距离、速度每 5 帧更新一次
运行日志事件、碰撞、到达事件触发时追加
参数面板学习率、候选数等用户修改时生效

5.3 验证策略是否真的学到了协同

光看完成率不够,要分场景验证。固定障碍布局跑 100 回合,统计完成率、平均路径长度、碰撞次数、最小安全距离、平均能耗。再换 5 组没训练过的布局重复,对比指标掉多少。如果新地图完成率掉超过 30%,说明泛化不够,回去加随机化或换网络结构。我一般还会把成功回合的轨迹导出来,看是不是所有无人机都走了合理路线,还是有一架在绕远路被队友“带飞”。

从那以后我每次拿到这类多智能体路径规划代码,都先固定随机种子跑一遍基线,再改奖励和网络,不然出了问题根本说不清是代码 bug 还是超参没调好。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询