基于MASAC的多无人机协同路径规划实战:从原理到代码实现
2026/9/13 15:02:50 网站建设 项目流程

简介:强化学习作为人工智能领域的关键技术,在多智能体系统中展现出巨大潜力。多智能体强化学习面临环境非平稳、动作空间爆炸等挑战,而最大熵算法SAC通过熵正则化提升探索稳定性,其多智能体变体MASAC结合集中训练与分散执行框架,有效解决协同决策问题。在无人机编队、自动驾驶、机器人协作等场景中,多无人机协同路径规划是典型应用,要求无人机在未知环境下保持队形并避免碰撞。本文基于PyTorch实现MASAC算法,详细阐述环境建模、状态动作空间设计、奖励函数构建及训练调参经验,为多智能体协同控制提供可复现的工程参考。 多无人机协同路径规划这几年是真火,但真正能把训练代码跑通、把仿真结果调稳的人并不多。我去年接手了一个无人机编队协同的仿真验证需求,目标很直接:让3到5架无人机在未知障碍物环境下,从各自起点出发,保持编队队形避开碰撞,最终全部到达目标点。一开始我走的是传统凸优化加一致性控制的路线,发现障碍物一多、环境一变,就非常被动,航迹重规划次数多到根本没法看。后来切换到MASAC强化学习算法,用Python重写了环境、训练和评估整套代码,效果才真正立住了。这篇文章就把我基于MASAC做多无人机协同路径规划的一套完整思路、代码结构和踩坑记录分享出来。

这套方案适合这三类人看:一是刚开始接触多智能体强化学习,想找一个能落地的协同规划项目做参考的学生;二是已经从单智能体SAC切入,但不太清楚怎么把它扩展到多无人机场景的工程师;三是科研人员想快速搭一套可复现的仿真验证环境,对比不同奖励函数和网络结构下的收敛表现。项目源码基于Python和PyTorch实现,核心算法用MASAC(Multi-Agent Soft Actor-Critic),环境是自研的多无人机二维/三维仿真环境,后面我会把每个模块拆开讲。

1. 多机协同问题的本质与算法选型逻辑

1.1 单机路径规划与多机协同的差异

单无人机路径规划里,核心问题是一个智能体在环境约束下找到从起点到终点的可行路径,经典方法有A*、RRT、人工势场法,以及单智能体强化学习算法。只要环境建模准确、静态障碍物已知,这些方法都能得到不错的效果。

但多无人机协同路径规划不是单机规划的简单求和。举个最简单的例子:两架无人机同时飞向同一个目标点,单机各算各的路线,结果大概率是双方在中途就发生了空间重合。多机协同关注的核心是“耦合”——无人机之间不仅要考虑环境障碍物,还要相互避碰,甚至要保持特定的编队构型。这意味着奖励函数里必须有“队友之间距离”的项,而且动作选择会影响整个编队的下一步状态分布。

这个“耦合”让问题的维度从单机的有限状态空间,暴涨成联合状态空间。举个例子,3架无人机,每架机的状态向量是4维(位置xyz加一个速度),联合状态就是12维;动作空间也一样,如果每架机是4维连续动作,联合动作就是12维。如果使用DQN这类基于离散动作和表格/值函数的方法,动作空间规模没法看;如果使用策略梯度,又很难处理连续动作下的高方差更新。这也是我最终选用MASAC的核心原因。

1.2 为什么选MASAC而不是DQN、DDPG或PPO

我现在复现这个项目时,并没有把市面上所有算法都做一遍,但基于之前的经验,我对比过DQN、DDPG、PPO和SAC/MASAC这几条路线。DQN虽然有各种改进版本,但它天然面向离散动作空间,不适合油门、偏航角这类连续变量;DDPG虽然可以处理连续动作,但训练稳定性差,超参数敏感,一不留神Q值就发散;PPO在单智能体任务里稳定度不错,但在多智能体场景下,每个智能体都在同时更新策略,环境的非平稳性会让每一个单机的PPO优化目标都在移动靶子上做梯度上升。

MASAC的核心优势在于它把SAC的最大熵框架搬到了多智能体场景。最大熵的意思,简单说就是智能体在累积奖励的同时,策略还要尽量随机化,避免过早收敛到某个次优的确定策略。多智能体环境下,最大的问题就是环境非平稳性:别的无人机也在实时改变策略,你的经验池里的数据其实是在一个不断变化的动态环境中采样出来的。MASAC的熵正则化机制让策略保持一定的探索度,在训练初期能更稳地探索整个状态空间,不容易被队友的策略变化带偏。

这句话实际用下来是非常明显的体验。我在同一个环境里换用DDPG和多智能体版本,DDPG大概每跑3次训练就有1次结果崩掉,而MASAC即便超参数差一点也能收敛到可用水平,只是方差会大一点。这个差距在做科研和工程复现时非常致命,稳定压倒一切。

1.3 项目定位与技术边界

这里的实现目标是二维平面内的多无人机协同路径规划,障碍物是静态圆形或者矩形,无人机被建模成固定高度下的质点。如果你需要做三维编队、动态障碍物、甚至复杂气象下的规划,这套代码的算法骨架不需要推翻,但环境模型和奖励函数得改不少。所以项目边界要讲清楚:这是一套验证多智能体PPO/SAC类算法在协同路径规划上可行性的参考实现,不是一套能达到军事标准或工业落地的完整飞行系统。

仿真层面,我的实现里使用自研的Python环境,而不是直接上AirSim或Gazebo。这样做的原因很简单:在算法验证阶段,自研环境更轻量、容易控制变量、调试成本低。如果你要迁移到Gazebo,需要做的是把环境类里的状态获取和动作下发替换成ROS topic;这个在文章最后会专门讲。

2. MASAC算法核心机制:CTDE框架与最大熵的变体

2.1 从单智能体SAC到多智能体MASAC

SAC的核心思想是在标准强化学习目标函数中引入熵项。如果用数学表达式描述,SAC要最大化的是每个时刻的累积奖励加上一个策略熵项。熵稍微解释一下:如果一个策略在一个状态下输出的动作概率分布非常“尖锐”,比如90%概率选择动作A、10%概率选择B,那么熵就低;如果两个动作概率各50%,熵就高。SAC希望策略在收益和随机性之间找到平衡点,这就是最大熵强化学习的基本思路。

MASAC是多智能体版本的SAC。它的做法是在每个智能体的状态信息里引入其他智能体的信息,特别是在Critic网络里。比如无人机i的状态量不仅包括自己的位置速度和目标点信息,还包括队友的位置和速度。训练时,Critic用全局信息做价值评估,Actor只用局部可观测信息做动作决策。这就是“集中训练、分散执行”的经典范式,也是MASAC能够处理多智能体非平稳性的关键原因。

2.2 集中训练与分散执行的关键

直接看“CTDE”可能有点抽象,我用编队飞行的逻辑解释一下。在训练阶段,仿真器是上帝视角,每架无人机的状态都可以直接读出来。此时我让每架无人机的Critic网络输入“所有无人机的联合状态和联合动作”,这样它就能感知到队友策略的变化,给出一条稳定的价值评估。

但到了部署阶段,不会有一台中央计算机能够实时获取所有无人机的完整状态再给每架机下发精确动作(即便有,通信延时也是大问题)。所以每一架无人机实际执行策略的Actor网络,输入只包含自己传感器范围内的信息,包括自己的状态、目标点、可探测到的障碍物以及编队邻居的状态。

这样做的好处是兼顾了训练稳定性和部署可行性。如果训练时Actor也用局部信息,由于看不到队友变化,策略很难学会协同;如果部署时还要用全局信息,那就不具备分布式执行的条件。MASAC的工程价值就在于:它用训练阶段的高信息量,换来了执行阶段的高适配性。

2.3 自动温度系数与熵正则化的稳定作用

SAC里有一个温度系数,控制熵项在总目标函数中的权重。温度系数大,策略更偏向探索;温度系数小,策略更偏向利用已有Q值。手动调这个系数非常痛苦,因为不同训练阶段需要不同的探索水平。SAC作者提出了自动调节版本,让智能体在训练过程中自己控制这个系数,目标就是把熵维持在预设阈值附近。

MASAC在原理上与SAC一致。我的理解是,这个自动温度系数在多智能体环境里特别宝贵。因为多智能体环境比单智能体更不稳定,手动固定温度系数常常会导致训练前中期探索不足。自动温度机制能让策略在队友策略还不稳定的时候,自动保持随机性,等到环境逐渐稳定了,再慢慢降低熵,平滑提高确定性。这个特性对收敛过程非常有帮助,我在调参阶段几乎没动过温度相关的超参数,它自己就能找到比较合理的平衡点。

3. 训练环境搭建:状态空间、动作空间和奖励函数设计

3.1 简化但可扩展的无人机运动模型

我没有一上来就用坐标系的完整六自由度模型。在强化学习算法验证阶段,那样做只会让调参难度指数上升。我采用的是二维平面内的质点模型加一阶惯性约束。每架无人机的状态是 (x, y, vx, vy),其中x和y是坐标,vx和vy是速度。控制输入是目标加速度的x分量和y分量,动作值范围被限制在-1到1之间,再乘上最大加速度系数。

这个模型不是真实飞控里的轨迹控制器,但对多智能体协同路径规划算法的验证完全够用。真实无人机有内环姿态控制,你给它的是一组期望速度或者期望航向,底层飞控会跟踪。所以这里用加速度控制其实是合理的抽象。

如果你后续要迁移到AirSim或Gazebo,也只需要把动作转换成期望速度或者期望姿态角即可,上层决策逻辑不必大改。我在很多厂里的实际项目中,算法层面都是用这种简化模型做预研,再交给飞控工程师去做真机部署。

3.2 状态空间、观测空间和动作空间怎么定义

我设定的目标是5架无人机从不同起点出发,飞到同一个目标点,全程不能互相碰撞也不能撞障碍物。环境区域是一个200米×200米的二维平面,随机生成5个圆形障碍物,圆心和半径在每次reset时随机变化。目标点位于(150, 150)。每架无人机的观测空间包含以下内容:

  • 自身位置和速度(4维)
  • 目标点相对位置(2维)
  • 距离最近的3个障碍物的相对位置(6维)
  • 其他4架无人机相对自身的位置(8维)

总体观测维度是20维。全局状态空间在训练时会把所有无人机的观测都拼接起来,形成一个100维左右的向量。

动作空间是2维连续动作,控制的是加速度在x和y方向的分量。为了让训练初期更容易探索出“向目标点移动”的动作,我还加了一个动作加权系数。

这个设计看起来简单,但有一个非常关键的细节:我故意没有把“其他无人机与自身是否可能碰撞”这种高度抽象的信息作为输入,让网络自己从相对位置中学习碰撞关系。这样做的好处是,环境换了障碍物布局,甚至换了机群数量,网络输入维度不变时可以直接泛化;坏处是训练时间会变长,网络需要自己归纳出“别人离我太近不是好事”这个隐性规则。

3.3 奖励函数:到达、避碰、编队三件套

奖励函数是整个项目中最影响训练效果的模块,我前后迭代了五个版本。最终版本由四部分组成:

  1. 到达奖励:到目标点距离小于阈值时,给予正向奖励,并且本回合结束。为了让智能体学会接近目标,我还加了一个“稀疏到达大奖励”,但只有在最终判定成功时触发。
  2. 碰撞惩罚:每架无人机与障碍物或其他无人机的距离小于安全半径时,给予大的负向惩罚,并终止本回合。这里我分了两档:如果距离小于碰撞半径,会执行终止和惩罚;如果距离小于预警半径但大于碰撞半径,只给一个小的负向惩罚,不终止,目的是让智能体学习保持安全间距。
  3. 编队保持奖励:无人机的目标是到达同一个目标点,但我希望它们在整个飞行过程中保持一定的队形。我定义了一个编队中心点,也叫做“虚拟编队参考点”,每架无人机离参考点的目标相对位置越近,奖励越高。实际代码里,我没有强制队形固定,而是对偏离期望位置的偏差做了惩罚。
  4. 进度奖励:每步给一个小的即时奖励,等于“当前到目标的距离”减“上一步到目标的距离”,再乘一个系数。这可以避免纯粹稀疏奖励带来的收敛过慢问题。

奖励系数我列一下,方便大家参考:

奖励项系数触发条件
到达目标点+100距离阈值 < 3m
碰撞/预警惩罚-50 / -1距离小于碰撞半径 / 预警半径
编队偏差惩罚-0.02 × 偏差²每步持续触发
距离减少奖励+0.5 × Δ距离每步持续触发

这套设计最终让算法能在训练12万步后实现稳定到达率。如果你复现时收敛速度太慢,可以先试着调大距离减少奖励的系数,这是最常用的加速手段。

4. 核心代码实现与工程结构

4.1 项目文件结构

我最终的Python项目按下面这种方式组织,结构清爽,每个模块职责单一,调试起来也方便:

multi_uav_masac/ ├── envs/ │ ├── multi_uav_env.py # 多无人机协同环境 │ └── obstacle_pool.py # 障碍物生成逻辑 ├── models/ │ ├── networks.py # Actor/Critic 网络结构 │ ├── masac_agent.py # MASAC智能体类 │ └── replay_buffer.py # 经验回放池 ├── train.py # 训练主入口 ├── evaluate.py # 评估主入口 ├── config.py # 超参数配置 └── utils/ └── logger.py # 训练日志记录

config.py里放着所有超参数,训练或评估时直接从config读取。我习惯把所有环境参数和算法参数都放一起,一方面方便试验记录,另一方面在多人协作时减少“参数到底写在哪个文件”的争论。

4.2 环境类与核心算法类的编写思路

环境类的核心方法是reset、step和render。reset时随机生成障碍物布局,重置所有无人机的位置到预设起点,并返回初始观测。step接收所有无人机的动作数组,更新位置、速度,计算奖励、终止标志和下一观测。代码量不大,但每一步都可能成为性能瓶颈,所以能用numpy向量化运算的地方尽量向量化,而不是写for循环遍历每架无人机。

MASAC智能体类的设计参考了SAC官方实现。每架无人机有一个Actor网络,用来输出动作的均值和标准差;一个目标Critic、一个当前Critic,构成Double-Q结构。训练时,每个智能体从经验池里采样一个batch,用联合状态和联合动作来更新自己的Critic,再用当前策略采样动作更新Actor。

下面是一个简化版的网络定义代码片段,不包含完整实现,但你能从中看到结构:

import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, action_dim, hidden_dim=256): super().__init__() self.fc1 = nn.Linear(obs_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.mean = nn.Linear(hidden_dim, action_dim) self.log_std = nn.Linear(hidden_dim, action_dim) def forward(self, obs): x = F.relu(self.fc1(obs)) x = F.relu(self.fc2(x)) mean = self.mean(x) log_std = torch.clamp(self.log_std(x), -20, 2) return mean, log_std def sample(self, obs): mean, log_std = self.forward(obs) std = log_std.exp() normal = torch.distributions.Normal(mean, std) z = normal.rsample() action = torch.tanh(z) log_prob = normal.log_prob(z) - torch.log(1 - action.pow(2) + 1e-6) return action, log_prob.sum(dim=-1, keepdim=True)

Critic的输入不再是本机单独的状态,而是拼接了所有无人机状态和动作的联合向量。这里我用了独立Q网络的变体,没有采用共享参数版本。实测下来独立Q网络参数多一点,但训练更稳定。

经验回放池里保存的数据包括:联合观测、联合动作、联合奖励、联合下一观测、联合终止标志。这里必须强调“联合”两个字。在MASAC框架下,训练Critic时需要完整的世界状况信息,所以在经验池里存的是所有无人机的观测和动作拼成的全局向量。Actor执行时再进行切片,取自己的局部观测部分。

4.3 训练主循环与并行优化

训练主循环的逻辑很直接:初始化环境,循环若干episode,每步从每个Actor中采样动作,调用环境step获得转移数据,存入回放池;当回放池容量足够后,每步从池中采样train_batch_size条经验,分别对每个智能体的Critic和Actor更新一次。

代码结构上我做了两个优化:第一,如果状态重置次数很多,可以用多进程并行跑多个环境,增加数据采集效率;第二,目标网络更新采用软更新方式,即每次把目标网络参数往当前网络参数方向移动很小一步。软更新能有效提高训练稳定性,也是SAC默认的做法。

有一个小坑值得单独说:多智能体训练时的“联合奖励”怎么处理。理论上每个智能体的奖励可以不同,但我在协同路径规划里给所有智能体使用了一个共享的团队奖励,即每个智能体的奖励都是全局奖励的同一份数值。这样做的好处是,智能体之间不存在奖励竞争,更容易学会协作;缺点是可能牺牲一些个体效率,比如某架无人机会为了整体编队调整变得比其他无人机更慢。如果你更看重编队保持的严格性,可以使用个体奖励加团队奖励的混合方案。

5. 训练实战:收敛过程中的坑与调参经验

5.1 训练曲线怎么判断收敛

我训练时记录了到达率、平均回合奖励、平均编队偏差、碰撞率这几项指标。其中到达率是最核心的指标。在训练初期,5架无人机几乎全部碰墙或互撞,到达率是0,这很正常,不用慌。大约到4万步后,开始出现个别episode的零碰撞成功案例;到8万步后,到达率会突然上升,然后总体保持一个波动上升趋势;到12万到15万步,基本能稳定在80%以上。

这里有一个容易被忽略的判断标准:看到达率是否“突变式上升”。如果环境设计得当,MASAC的收敛过程往往不是线性上升,而是前期长时间平台期,之后某个阶段突然跳上去。这个跳变点意味着agent终于学会了“先往目标点飞,同时躲开别人”这个组合策略。如果一直不出现跳变,说明奖励信号有问题或熵系数自动调节失效。

5.2 我踩过的三个影响收敛的坑

第一个坑:碰撞惩罚太重导致智能体完全不敢动。初始版本碰撞惩罚是-100,结果无人机学会了待在起点不动,因为这样虽然拿不到到达奖励,但也不会被惩罚。这个问题在强化学习里叫“奖励悬崖”,解决方法是把碰撞惩罚降到一个合理的水平,同时给一个每步必需的最小探索激励。

第二个坑:编队保持项权重太高,导致无人机为了队形宁愿偏离目标点。我一开始把编队偏差惩罚系数设成0.1,结果训练出来的轨迹看起来特别“拧巴”,无人机为了凑队形不停转圈,到目标点的效率很低。最终我把系数降到了0.02,同时引导智能体明白“编队保持是辅助目标,到达目标才是主目标”。

第三个坑:经验回放池里旧策略数据占比过高。多智能体训练中,随着策略更新,旧经验里的“队友行为模式”已经过时,直接使用会导致Q值评估失准。我用了两个手段解决:一是回放池大小不要设得太大,二是在训练后期逐步减少经验再利用次数。这个在很多SAC实现里都能通过调replay_buffer_size和batch_size来平衡。

5.3 超参数参考表

我把一套能稳定收敛的超参数贴在下面,这个配置在5机编队,200×200区域,5个圆形障碍物环境下表现稳定:

参数名数值
无人机数量5
最大速度5 m/s
最大加速度2 m/s²
安全半径3 m
预警半径6 m
障碍物数量5
回放池容量100000
batch_size256
Actor学习率3e-4
Critic学习率3e-4
温度学习率3e-4
折扣因子0.99
软更新系数0.005
每回合最大步数300
目标熵阈值-action_dim

这个表格不是万能配置,但如果你是从零开始复现,建议先按这个跑通,再逐步调参。我自己实验时,把学习率从3e-4降到1e-4后,训练时间大约翻倍;把batch_size从256降到64后,方差明显变大,所以这两个参数尽量维持在上表附近。

6. 从仿真到实机的扩展建议

6.1 如何迁移到Gazebo或AirSim

如果你要把这套训练好的策略搬到Gazebo或AirSim里,核心工作不在算法,而在环境接口。我的建议是写一个仿真适配器,把MASAC环境类的reset和step接口,分别转成Gazebo场景重置和里程计读取。具体说,就是把无人机的位姿从Gazebo的ROS topic中读出来,把MASAC网络生成的目标速度发送给PX4或ArduPilot的飞控栈。

这个迁移过程中最需要关注的是动作映射频率。自研环境里step是20Hz,Gazebo里如果你也是20Hz发布控制指令,Model Predictive Control式的飞控响应基本能跟踪上。但如果在Gazebo里实际步长太大,比如5Hz,那么训练出来的加速度控制指令就失真了。解决办法是在自研环境里把step频率调低,重新训练,再迁移。

6.2 部分可观测与通信约束

仿真训练时我用的是全局观测,但实际部署时,同一时刻每架无人机无法获得所有其他无人机的精确位置。一是通信带宽受限,二是远处无人机的数据对当前决策价值不大。所以我在工程化版本里把观测空间做了截断:只观测通信距离内的队友,通信距离设为30米,超过这个距离的队友不进入观测。

这个改动听起来不大,但会导致模型输入的“队友数量”不再是固定5个,可能是0到4个不等。我建议用“最大邻居数量”加“填充补零”的方式处理,即固定分配最多4个队友的观测槽位,实际邻居不足时用0填充。这种方式在强化学习里非常常见,虽然会牺牲一点信息效率,但能保持网络输入维度不变,不需要改网络结构。

6.3 奖励塑形与安全兜底

实机部署时,神经网络策略的输出不能直接无条件使用。我会在策略输出后面串一个安全过滤器:如果计算出的目标加速度会让无人机在最近一个控制周期内进入禁飞区或碰撞锥,就用安全过滤算法把它修正到安全方向。这个思路在工程上叫“安全屏障”,和强化学习本身是互补关系。

另一种做法是把安全约束嵌入到奖励函数里,比如使用基于约束的强化学习框架。但那个实现复杂度会上升不少,我的建议是先跑通“训练完成 + 部署时加安全过滤器”这条路线,再考虑更复杂的CBF-RL方案。

6.4 关于代码复现的几点提醒

最后再说几句关于复现这套代码的建议。首先,随机种子一定要固定,不然多智能体训练的结果波动会让你怀疑人生。我在训练脚本里固定了Python、numpy、PyTorch和环境的随机种子,这样才能保证每次训练的轨迹可追溯。其次,训练过程中建议定期保存checkpoint,不要等训练结束才保存,因为分布式环境随时可能因为显存不足或终端断连导致前功尽弃。

我自己用的显存是单张RTX 3090,5架无人机的MASAC训练,网络规模不大,显存占用不到4GB,所以大部分消费级显卡都能跑得动。如果你用的是CPU,训练时间会非常感人,强烈建议至少使用一张入门级N卡。

这个项目的完整Python代码我已经打好包,里面包含自研环境、MASAC智能体、训练主程序、评估脚本和几组训练好的模型权重文件。拿到之后先运行train.py,再运行evaluate.py,应该能在20分钟左右看到到达率的上升曲线。训练时如果遇到收敛问题,优先检查奖励函数的权重设置,其次检查回放池大小,最后还是不行再动网络结构,这个排查顺序能帮你省下大量时间。

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

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

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

立即咨询