ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于MASAC的多无人机协同路径规划Python实现与避坑指南

基于MASAC的多无人机协同路径规划Python实现与避坑指南 简介多架无人机在同一空域执行任务互相避让、同时到达是路径规划领域极具挑战的协同问题。传统算法因环境动态变化和机间耦合难以高效求解而强化学习通过与环境交互自主学习控制策略为连续决策问题提供了新思路。SAC作为高效的最大熵连续控制算法在多智能体扩展为MASAC后可让每架无人机基于局部观测独立决策同时借助中心化Critic评估全局协同效果从而实现时间与空间上的编队协同。该方案广泛应用于低空物流、编队巡逻、灾难搜救等场景。本文围绕MASAC的建模过程与Python工程落地梳理状态空间设计、奖励函数构建、训练循环实现及常见调试陷阱帮助开发者应对真实工程挑战。1. 先弄清楚MASAC多无人机协同路径规划在做什么无人机做协同路径规划难的不是让一架飞机从A飞到B避开障碍而是多架飞机要在接近的时间窗口里同时到达各自目标全程不撞障碍物、不互相挤成一团。这个问题用经典路径规划算法写起来很痛苦每架飞机的计划都会影响其他飞机障碍一变就要全盘重算。基于MASAC强化学习算法的多无人机协同路径规划本质是用多智能体强化学习把这套“互相避让、互相等待”的协同策略训出来。MASAC是SAC在多智能体方向的扩展每架飞机用自己的局部观测独立决策训练时由一个能看见全局的Critic教它怎么协同。下面会把建模、Python落地和踩坑点讲透适合想把手上的无人机路径规划代码从单机搬到多机的人零基础建议先补一下SAC的入门资料再回来看。2. 把协同路径规划拆成强化学习问题运动学模型、状态空间与奖励函数2.1 面向路径规划用二维点质量模型就够了多无人机协同路径规划里最容易被新手上手就劝退的点是试图把四旋翼的全动力学模型塞进强化学习训练。无人机全动力学模型包含姿态角、角速度、旋翼转速适合做飞控层的控制器验证不适合做路径规划层的策略学习。训练一次强化学习要跑几十万到几百万步每一步都在全动力学仿真器里做积分时间成本直接翻几十倍而路径规划层的上层算法在绝大多数情况下并不关心姿态是怎么转过去的。我一般会把无人机简化成带速度上限和加速度上限的质点模型位置和速度作为状态控制量是加速度。加速度上限模拟执行机构的响应能力速度上限模拟平台本身的飞行包线。低空编队和室内巡航场景把高度锁死二维平面模型完全够用遇到真三维地形穿越把z轴加回来动作维度从2变成3障碍物从圆变成球其余逻辑不变。这个“先用二维跑通上层算法”的做法是无人机路径规划算法落地最常见的第一步。具体的仿真参数按100m×100m任务地图来设控制周期dt0.1s最大速度5.0m/s最大加速度2.0m/s²机间安全距离2.0m到达判定半径5.0m。单回合格局下最大步数设在300到500之间保证无人机有足够时间从地图一端飞到另一端同时不会在目标点附近无限徘徊。模型参数默认值作用dt0.1s控制周期也是训练时环境的步长max_vel5.0m/s速度上限更新后直接截断max_acc2.0m/s²动作经此映射成真实加速度safe_dist2.0m机间最小安全距离小于即碰撞goal_radius5.0m进入该范围视为到达目标点map_size100.0m地图边长位置限幅范围2.2 状态空间Actor只看局部Critic才看全局多智能体强化学习里最容易踩的坑是把“每个智能体观测什么”和“Critic能看见什么”混在一起。先分清两个概念Actor是执行策略的网络它只能拿到第i架无人机自己的局部观测Critic是训练阶段评估价值的网络它可以拿到所有无人机的状态和动作。这个结构叫中心化训练、去中心化执行也是MASAC能学到协同行为的前提。单架无人机的局部观测s_i我习惯按这个顺序拼接自身位置、自身速度、目标点相对位置、邻机相对位置、最近障碍物相对位置与距离、自身编号one-hot编码。位置用相对量而不是绝对量是为了让策略对地图平移具有一定的泛化能力换个起点布局不至于完全失效。邻机相对位置取的是通信范围以内的飞机例如20m通信半径超出范围的飞机不参与观测拼接这样扩展到更多无人机时观测维度不会无限膨胀。Critic的输入则是所有智能体观测的拼接和所有智能体动作的拼接即S [s_1,...,s_n]和A [a_1,...,a_n]。对4机编队来说如果每个智能体观测是50维Critic输入就是200维状态加8维动作。这个维度并不算高普通256宽度的MLP就够用了。需要注意的是Critic能看全局不代表Actor能看全局否则部署时一旦某一架飞机通信丢包或延迟策略立刻失效。2.3 奖励函数从单机可跑通到多机协同分三步加多无人机协同路径规划的奖励函数设计我的建议是分三步叠加不要第一次就把所有项全部塞进去。第一步只放到达奖励和距离势能引导确保单智能体环境下能把一架飞机从起点训到目标点。距离势能是r 2.0 × (上一时刻到目标的距离 - 当前时刻到目标的距离)它给了无人机一个连续梯度不然只靠最终100的到达奖励稀疏奖励下策略完全学不动。第二步加入障碍物惩罚。障碍物惩罚不要用“碰到就给-50”的硬惩罚用软惩罚更稳当无人机进入障碍物影响半径时惩罚按距离线性递减奖励曲面连续可导Critic拟合起来容易得多。第三步才是多机协同项机间碰撞惩罚和到达时间差惩罚。到达时间差惩罚可以让先到的飞机学会等待、后到的飞机学会加速而不是各飞各的、到得早晚无所谓。奖励项建议取值说明到达目标100进入goal_radius即触发距离势能2.0 × (d_prev - d_cur)引导持续靠近目标障碍物软惩罚50 × (1 - d_obs / R_obs)进入影响半径后线性增加机间碰撞惩罚-30小于safe_dist时触发到达时间差惩罚0.5 × std(arrive_time)协同项按全局到达时间计算数值尺度有一个硬经验奖励分量之间的量级差不要超过两个数量级。如果距离势能每步给2分机间碰撞一次扣30分障碍物一次扣50分Critic主要精力全在拟合碰撞惩罚上协同项会被直接淹没。先单机跑再加避障最后加协同项每加一步都重新观察奖励曲线是否还能稳住这个顺序能省掉大量调参时间。2.4 任务与终止条件固定目标分配还是动态分配多无人机协同路径规划的任务定义里目标分配方式决定了问题难度。最稳妥的起步方案是固定目标分配第0架飞机固定去0号目标点第1架去1号目标点以此类推。动态目标分配属于任务分配层的另一个问题需要额外加匹配网络或用匈牙利算法在线解算和路径规划耦合起来训练难度会陡增。我建议第一版先把固定分配跑通多机协同体现在时间协同和空间避让上到达时间尽量一致、机间距离始终大于安全值、避障时不互相干扰。终止条件设成“所有智能体都到达或者超过最大步数”单架飞机到达目标点后动作立即置零保持悬停等队友跟上否则先到者还在乱飞会把整个协同到达的语义破坏掉。3. 从SAC到MASAC中心化Critic与去中心化Actor为什么能协同3.1 SAC的核心逻辑最大熵与随机策略SAC能成为连续动作控制场景最常用的深度强化学习算法关键在它的目标函数不止包含累计奖励还包含策略熵。最大化熵意味着策略不会过早坍缩成一条确定性曲线而是会保留一定程度的不确定性。这对多智能体场景特别重要其他无人机策略在训练中持续变化过于确定性的策略很容易被环境变化甩出可用区间随机策略天然带宽泛化余量。SAC的动作采样用重参数化技巧。Actor输出的是动作均值和对数标准差构造高斯分布后先用标准正态分布采样噪声再通过z μ σ·ε得到动作最后用tanh压到[-1,1]区间。这个做法的价值是能保留“采样”这个随机过程的同时让梯度可以回传到网络参数上否则随机采样没法做反向传播。多智能体版本里每个智能体的动作维度通常只有2或3重参数化计算代价很小所以MASAC不像MADDPG那样受限于确定性策略。SAC里还保留了双Critic结构两个独立的Q网络分别估计价值最终用两个网络的最小值作为目标值。这是降低Q值过估计的常用手段。多智能体场景下Critic输入维度更高、样本覆盖密度更低Q值过高估计被进一步放大因此这个取最小值的操作不仅不能省反而是保证训练稳定的关键。3.2 MASAC的三个关键改动独立Actor、全局Critic、带编号的经验池和SAC相比MASAC的改动集中在三个点。第一每个智能体拥有一个完全独立的Actor网络参数不共享输入自己的局部观测输出自己的动作分布。第二Critic输入改成全局拼接向量所有智能体的观测和所有智能体的动作。第三经验回放池共享但每条样本必须记录agent_id否则更新时无法区分每条经验属于哪个智能体。独立Actor网络意味着“去中心化执行”是硬约束这和多无人机场景的物理现实一致部署时每架飞机只能决定自己的动作没有任何中央节点替它做决策。而Critic能看见全局训练时可以回答“在所有飞机当前状态和动作下某架飞机这样做的价值是多少”这个全局视角是协同行为能够被评估和引导的前提。对比方案Critic观测适用规模主要问题独立SAC并行单机obs 单机act23机智能体策略相互视为噪声不收敛MADDPG全局obs 全局act310机确定性策略在连续动作空间容易不稳定MASAC全局obs 全局act310机Critic输入维度随N线性增长MAPPO全局obs 全局act10机以上采样效率要求高训练更慢实际选型时如果你做的是10架以下无人机的小编队协同MASAC是最稳的起点。超过20架或者通信拓扑受限Actor需要考虑用图神经网络聚合邻居信息纯拼接输入会让Critic和Actor的输入维度都难以处理。3.3 中心化训练为什么是“协同”的来源不是奖励函数逼出来的多智能体训练有一个本质问题对任意一架无人机而言其他无人机的策略都在不断变化这导致环境转移概率P(s|s, a)在训练期间是非平稳的。如果Critic不去看其他智能体的动作它拟合出的Q函数就像在移动的沙丘上打地基今天学到的价值明天就失效训练自然发散。MASAC把其他智能体的状态和动作直接拼进Critic输入相当于把这种非平稳性显式建模到了价值函数里。Critic不再需要猜测队友下一步会做什么它直接看到了队友的动作和状态Q函数有了稳定的回归目标。Actor则通过Critic给出的梯度间接学会“我现在这样飞队友会怎么反应最终对整支编队收益如何”的协同策略。所以协同不是靠奖励函数里塞进“协同项”挤出来的而是中心化Critic带来的结构红利。这里有一个常见的误解有人会问“既然奖励里写了避碰惩罚各飞各的也能学会避碰为什么要上MASAC”独立SAC并行训练确实能学会静态避障但学不会时间协同。四架无人机同时到达目标点这个需求单机奖励无法建模“全局到达时间差”必须有一个能看见所有人的价值函数才能评估这个目标。这就是中心化Critic不可替代的原因。4. 用Python落地MASAC环境、网络与训练循环怎么组织4.1 工程文件怎么切一个能迭代的MASAC工程我习惯切成四个文件uav_env.py放无人机环境包含运动学、奖励计算、碰撞检测masac_networks.py放Actor和Critic网络定义masac_train.py放训练循环和经验回放eval.py放评估脚本和轨迹导出。环境、网络、训练三层分开之后想换成MAPPO或MADDPG只需要重写masac_train.py环境和网络不用动。4.2 环境step的核心实现下面给一个最小可运行的环境骨架step函数里包含了加速度控制、距离势能奖励、到达判定、避障软惩罚和机间碰撞惩罚。# uav_env.py import numpy as np class UAVEnv: def __init__(self, n_agents4, map_size100.0, safe_dist2.0, goal_radius5.0, dt0.1, max_vel5.0, max_acc2.0): self.n_agents n_agents self.map_size map_size self.safe_dist safe_dist self.goal_radius goal_radius self.dt dt self.max_vel max_vel self.max_acc max_acc self.obstacles [np.array([40.0, 40.0]), np.array([60.0, 60.0])] self.reset() def reset(self): # 固定初始位置训练时建议替换为可复现随机起点 self.agents_pos np.array([[10.0, 10.0], [90.0, 10.0], [10.0, 90.0], [90.0, 90.0]], dtypefloat) self.agents_vel np.zeros((self.n_agents, 2)) self.goals np.array([[20.0, 20.0], [80.0, 20.0], [20.0, 80.0], [80.0, 80.0]], dtypefloat) self.prev_dist np.linalg.norm(self.agents_pos - self.goals, axis1) return self._get_obs() def step(self, actions): # 动作从[-1,1]映射为加速度 actions np.asarray(actions) * self.max_acc self.agents_vel np.clip(self.agents_vel actions * self.dt, -self.max_vel, self.max_vel) self.agents_pos self.agents_pos self.agents_vel * self.dt self.agents_pos np.clip(self.agents_pos, 0.0, self.map_size) cur_dist np.linalg.norm(self.agents_pos - self.goals, axis1) rewards 2.0 * (self.prev_dist - cur_dist) # 距离势能引导 for i in range(self.n_agents): if cur_dist[i] self.goal_radius: # 到达目标 rewards[i] 100.0 for obs_p in self.obstacles: # 障碍物软惩罚 d_obs np.linalg.norm(self.agents_pos - obs_p, axis1) rewards - 50.0 * (d_obs 6.0) * (1.0 - d_obs / 6.0) for i in range(self.n_agents): # 机间碰撞惩罚 for j in range(i 1, self.n_agents): d_ij np.linalg.norm(self.agents_pos[i] - self.agents_pos[j]) if d_ij self.safe_dist: rewards[i] - 30.0 rewards[j] - 30.0 self.prev_dist cur_dist dones cur_dist self.goal_radius return self._get_obs(), rewards, dones def _get_obs(self): # 实际项目中把目标相对位置、最近障碍物、邻机相对位置、one-hot编号拼进来 obs [] for i in range(self.n_agents): obs.append(np.concatenate([ self.agents_pos[i], self.agents_vel[i], self.goals[i] - self.agents_pos[i]])) return obs这段代码里有两个参数值得注意障碍物软惩罚的影响半径用了6.0m意味着无人机在距离障碍物6m以内就开始“减速”惩罚随距离递减。这是刻意让惩罚管道连续化避免无人机在障碍物边缘突然遭遇一个-50的阶跃导致Critic学出一个陡峭的价值突变。机间碰撞惩罚用了硬阈值距离小于2.0m直接各扣30分因为机间安全距离本身就是硬约束不能用“快撞了但没撞”来模糊处理。4.3 Actor与Critic网络定义Actor网络输出高斯策略的均值和标准差Critic接收全局拼接向量。# masac_networks.py import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden256): super().__init__() self.fc1 nn.Linear(obs_dim, hidden) self.fc2 nn.Linear(hidden, hidden) self.mu_head nn.Linear(hidden, act_dim) self.log_std_head nn.Linear(hidden, act_dim) def forward(self, obs): h F.relu(self.fc1(obs)) h F.relu(self.fc2(h)) mu self.mu_head(h) log_std torch.clamp(self.log_std_head(h), -5.0, 0.0) std log_std.exp() normal torch.distributions.Normal(mu, std) z normal.rsample() # 重参数化采样 action torch.tanh(z) # 限制到[-1,1] log_prob normal.log_prob(z).sum(-1, keepdimTrue) log_prob - torch.log(1.0 - action.pow(2) 1e-6).sum(-1, keepdimTrue) return action, log_prob class Critic(nn.Module): def __init__(self, all_obs_dim, all_act_dim): super().__init__() self.fc1 nn.Linear(all_obs_dim all_act_dim, 256) self.fc2 nn.Linear(256, 256) self.fc3 nn.Linear(256, 1) def forward(self, obs_all, act_all): x torch.cat([obs_all, act_all], dim-1) h F.relu(self.fc1(x)) h F.relu(self.fc2(h)) return self.fc3(h)log_std做了[-5.0, 0.0]的截断这是防止标准差在训练初期爆炸的常用手段。初始状态下网络输出接近零log_std接近0意味着标准差接近1动作接近均匀随机训练过程中网络逐步降低log_std策略才会慢慢确定下来。tanh把动作压到[-1,1]是为了让所有智能体的动作处于同一数值范围方便Critic拼接输入。环境侧再把这个[-1,1]的动作乘以max_acc映射成真实加速度。4.4 训练循环全局样本如何收集和更新样本收集时每个智能体用自己的Actor独立决策但存入经验池时不仅要存自己的obs和动作还要存所有智能体的obs_all和act_all。因为更新Critic的时候需要全局信息这条记录不能事后补。# masac_train.py 片段 for episode in range(total_episodes): obs env.reset() obs_all np.concatenate(obs, axis-1) # 全局观测拼接 step 0 while not done and step max_steps: actions, log_probs [], [] for i in range(n_agents): s_i torch.FloatTensor(obs[i]).unsqueeze(0) a_i, lp_i actors[i](s_i) # 训练阶段采样动作 actions.append(a_i) log_probs.append(lp_i) act_all torch.cat(actions, dim-1) # 全局动作拼接 next_obs, rewards, dones env.step( torch.cat(actions, dim-1).detach().numpy()) next_obs_all np.concatenate(next_obs, axis-1) for i in range(n_agents): buffer.push( agent_idi, obs_iobs[i], act_iactions[i], rew_irewards[i], done_idones[i], obs_allobs_all, act_allact_all, next_obs_allnext_obs_all) obs next_obs obs_all next_obs_all step 1更新阶段和SAC的更新公式一致只有一处改动Critic的输入不是单机观测和动作而是全局拼接向量。Critic损失是双Q的MSEActor损失是alpha乘以当前策略log_prob减去在线Q值。唯一需要小心处理的细节是计算Actor损失时要重新采样第i个智能体的动作并用新动作替换全局动作向量中的第i段再送入Critic。如果直接沿用样本里的旧动作Actor的梯度会错。经验池方面200000条容量对4机编队足够至少能覆盖几十个完整回合。buffer需要超过1000条才开始更新否则采出来的样本几乎都来自同一个回合开头缺乏多样性。4.5 超参起步模板与启动命令超参起步我建议用这一组适配4机场景超参数推荐值备注学习率3e-4Adam训练不稳时降到1e-4gamma0.99最大步数300500时足够tau0.005target网络软更新系数batch_size2564机编队默认值buffer容量200000至少覆盖几十个回合target_entropy-act_dim自动温度调节的目标熵梯度裁剪10.0防止更新步长过大先跑2机、10万步确认奖励曲线能稳定上升再加到4机、200万步。直接上4机在高维状态下报错或发散很难分清是代码问题还是超参问题。python masac_train.py --n_agents 2 --max_steps 200 --total_steps 100000 python masac_train.py --n_agents 4 --max_steps 400 --total_steps 20000005. 多无人机强化学习训练的常见坑现象、原因与排查步骤5.1 奖励曲线在涨无人机却在目标附近画圈现象累计奖励持续上升但把轨迹画出来发现无人机在目标点附近来回绕飞就是不进入goal_radius。原因通常是距离势能引导系数和避障惩罚的相互作用越靠近目标避障压力越大继续靠近带来的势能收益低于避障惩罚策略就停在了一个“绕着走更划算”的平衡点。解决先把势能系数从2.0降到0.5同时检查目标点周围是否有障碍物距离过近。如果目标点就放在障碍物影响半径以内无人机永远无法同时满足“靠近目标”和“远离障碍”两个条件。排查时把目标点挪到离障碍物至少10m的位置问题会立刻消失。5.2 同一套配置两次训练结果天差地别现象代码没改超参没改第一次收敛第二次发散。原因大概率是随机种子没固定。Python的random、numpy的全局随机、torch的初始化、环境的初始位置任何一个没固定训练过程都不可复现。解决在train.py入口统一固定种子并且环境reset内部不要用np.random默认全局接口改成显式传入seed的随机数生成器。另一个细节是如果环境里初始位置是随机的而你没有固定种子单次训练的结果本身就带运气成分。排查时先固定全部种子再判断算法好坏。5.3 多机学成了同质化策略全往一个目标点挤现象四架无人机有两个去了同一个目标点另两个目标点空着。原因是Actor网络结构完全相同如果局部观测里没有身份标识且初始位置和目标分配存在对称性多机很容易学到同一个策略。共享经验池会加剧这个问题所有agent样本高度相似梯度方向几乎相同。解决在局部观测里拼上one-hot编号让每个Actor知道自己是几号机同时检查奖励里有没有分工机制比如到达时间差惩罚。只加避碰惩罚解决不了分工问题避碰只会让它们互相让开不会让它们各自去不同目标。如果加了one-hot还学不会分工把环境恢复成固定初始位置、固定目标分配先验证能收敛再放开随机布局。5.4 自动温度调节失控策略变成白噪声现象开了alpha自动调节后熵权越来越大动作几乎完全随机或者alpha直接崩到0策略坍缩成一条确定性曲线再难恢复。原因是target_entropy设得过大超出策略能达到的熵上限alpha不停增大去追逐一个无法实现的目标alpha学习率设得太大调节速度跟不上价值函数变化。解决把target_entropy设为负的动作维度2维动作就是-2.03维动作就是-3.0这个经验值和大多数连续控制场景匹配。alpha_lr不要超过3e-4。排查时可以先关闭自动调节固定alpha0.2跑通收敛再开自动调节这样能把问题范围缩小到温度超参上。5.5 训练时表现正常评估时到达率暴跌现象训练时用采样的随机动作每回合奖励不错评估时用均值动作无人机反而到不了目标点碰撞率还升高了。原因有两个一是评估时用了带熵的随机采样策略在均值附近抖动在狭小通道或障碍物边缘抖动会导致碰撞二是obs归一化统计量如果只在训练时更新评估时统计量不匹配输入分布发生偏移。解决评估时Actor强制取均值不要采样连续跑10个episode统计到达率、碰撞次数、平均到达时间。如果obs做了归一化训练结束时要冻住mean/std评估和部署必须使用同一份统计量。排查顺序先改评估逻辑再查归一化。6. 验证策略效果奖励曲线之外还要看轨迹回放和多场景泛化6.1 先跑单机基线再谈多机协同拿到这套MASAC代码第一件事不是直接训4机而是把环境切成单智能体版本用普通SAC训100000步。单机版本如果到达率到不了100%问题在环境或奖励函数不在MASAC。这个基线能隔离出大量低级问题比如目标点被障碍物挡住、动作映射方向搞反、dt过大导致积分发散。我在这个环节至少省过五次“以为是算法问题结果发现是环境bug”的情况。单机基线跑通后再看两件事平均到达步数是否在合理范围以及奖励曲线末段是否还有明显的上升空间。如果单机都消耗了大量步数才到达多机协同的时间窗口会更紧张需要先降低任务难度。6.2 导出轨迹把每一局的飞行路径画出来训练完成后用下面的评估脚本把轨迹导出成npy文件画图检查是否有交叉和绕路。# eval.py 片段 for episode in range(10): obs env.reset() traj {i: [obs[i].copy()] for i in range(n_agents)} while not done: actions [] for i in range(n_agents): with torch.no_grad(): mu actors[i](torch.FloatTensor(obs[i]).unsqueeze(0))[0] actions.append(mu) obs, _, done env.step(torch.cat(actions, dim-1).numpy()) for i in range(n_agents): traj[i].append(obs[i].copy()) np.save(ftraj_ep{episode}.npy, np.array([traj[i] for i in range(n_agents)]))画图时重点看三个信号是否有两条轨迹在时间上同时经过同一个点是否有飞机在目标点附近徘徊超过50步是否有飞机为了避让队友绕了一个不必要的远路。这些信息在奖励曲线上完全看不出来但轨迹图一眼就能定位问题。多无人机协同路径规划的输出不是一个数值指标而是一组策略轨迹回放是最直观的验收手段。最后再做一个多布局泛化测试换掉障碍物位置不换网络权重随机跑10个episode记录到达率。强化学习策略不是搜索算法泛化能力有限这个测试不是为了证明它能应对任意未知环境而是排查策略有没有过拟合到训练集布局上。如果换了布局后到达率从90%掉到30%说明策略记住了障碍物位置而不是学会了避障逻辑就需要在训练时随机化障碍物布局重训。我现在拿到多机路径规划任务不管用什么算法第一件事永远是先跑单机版本。单机都跑不稳的曲线再漂亮都是自欺欺人。这个习惯帮我躲过了大量无效调参希望帮到你。本文还有配套的精品资源点击获取
返回列表