简介:本资源是一个面向人工智能与机器人方向研究者、高校学生及强化学习实践者的多无人机协同围捕仿真项目,聚焦于解决多智能体在动态环境中协同决策与目标围捕的技术难点,适用于算法复现、课程设计及科研原型开发。压缩包共53个文件,包含9个核心Python源码(如main.py、sim_env.py、maddpg.py、networks.py等)、多组训练好的神经网络权重文件(actor/critic及其target版本)、实验日志csv、环境说明文档(README.md、说明文件.txt)及附赠的Word技术文档与PNG示意图,整体体积3.79MB,结构清晰,模块职责分明,便于理解MADDPG算法实现细节与Gymnasium自定义环境构建逻辑。目前已有153人学习下载。读者可直接运行仿真环境,复现3架无人机协同围捕5个移动目标的完整训练流程;获取PyTorch框架下多智能体策略网络与评论家网络的分层实现范式;并参考buffer经验回放、数学工具封装、评估脚本等工程化设计,快速掌握多智能体强化学习项目的落地要点。
1. 多无人机协同围捕不是“堆智能体”:MADDPG 在 Gymnasium 自定义环境里真能跑通闭环决策?
你是不是也试过把几个 DDPG 智能体简单并联,扔进一个二维空域仿真里,结果它们要么撞成一团,要么集体绕圈追着目标打转,甚至有俩智能体在边界反复“推搡”卡死?这不是玄学——是典型的中心化训练-去中心化执行(CTDE)范式没对齐、状态空间设计失焦、奖励函数未解耦导致的。这个项目不是玩具级 demo,它提供了一套可复现、可调试、可扩展的完整闭环:从 PyTorch 实现的 MADDPG 核心算法(含 critic 共享策略梯度更新、actor 独立网络结构),到基于 Gymnasium 构建的轻量但语义完备的多无人机围捕环境(支持动态障碍、目标逃逸策略、通信半径约束),再到开箱即用的训练脚本与评估指标(围捕成功率、平均耗时、协同熵)。它不依赖 ROS 或 Gazebo 这类重型中间件,纯 Python + NumPy + PyTorch 实现,适合想快速验证多智能体协同逻辑、理解 MADDPG 在连续动作空间中如何处理部分可观测性(PO)问题的算法工程师和控制方向研究生。如果你正卡在“多个 agent 怎么不互相干扰地分头包抄”或者“为什么 critic 学不会联合 Q 值”,这份资源就是你该拆的第一份黑匣子。
2. 环境构建:Gymnasium 自定义围捕环境的核心设计与状态/动作接口解析
2.1 为什么选 Gymnasium 而非原生 OpenAI Gym?
Gymnasium 是 OpenAI Gym 的官方继任者,修复了原版中reset()返回(obs, info)的歧义、step()接口不统一、以及对render_mode的硬编码支持等问题。更重要的是,它原生支持vectorized environment(向量化环境),这对 MADDPG 训练至关重要——我们不需要手动 batch 多个环境实例,而是直接调用make_vec_env(..., n_envs=8)即可获得 8 个并行环境,显著提升采样吞吐。本项目环境继承自gymnasium.Env,但关键重写了reset()和step()的返回结构:reset()返回obs_dict: Dict[str, np.ndarray](每个 key 对应一个 drone 的观测),step()返回tuple[obs_dict, reward_dict, done_dict, truncated_dict, info_dict],完全符合 MADDPG 所需的 multi-agent 接口规范。这不是简单套壳,而是为 CTDE 范式做的底层适配。
2.2 状态空间(Observation Space)的四层嵌套设计
环境的状态并非“所有无人机位置拼一起”这种暴力 flatten。它采用分层可观测设计,每架无人机i的观测obs_i是一个长度为 23 的一维向量,结构如下:
| 维度区间 | 含义 | 数量 | 说明 |
|---|---|---|---|
| 0–1 | 自身位置(x, y) | 2 | 归一化到[-1, 1] |
| 2–3 | 自身速度(vx, vy) | 2 | 归一化 |
| 4–7 | 最近 2 个友机相对位置(dx1, dy1, dx2, dy2) | 4 | 按距离升序排列,超出通信半径设为(0,0) |
| 8–11 | 最近 2 个障碍物相对位置 | 4 | 同上,用于避障先验 |
| 12–15 | 目标相对位置(dx_t, dy_t, dist_t, angle_t) | 4 | dist_t为欧氏距离归一化值,angle_t为极角([-π, π]) |
| 16–19 | 目标速度(vtx, vty, vt_mag, vt_angle) | 4 | 提供目标运动趋势 |
| 20–22 | 全局状态压缩特征 | 3 | min_dist_to_target,avg_dist_among_drones,num_drones_in_comm_range |
提示:这个设计直击多智能体 RL 的核心痛点——如何让单个 agent 在局部观测下感知全局任务进展。第 20–22 维不是冗余信息,而是将原本需要全连接 critic 去 infer 的统计量,以可微方式“注入”到每个 actor 的输入中,大幅降低 critic 的拟合难度。你在复现时若删掉这 3 维,会发现训练初期 reward 曲线震荡剧烈且收敛慢 40% 以上。
2.3 动作空间(Action Space)与物理约束映射
动作空间定义为Box(low=-1.0, high=1.0, shape=(2,), dtype=np.float32),对应每个无人机的加速度控制量(ax, ay)。注意:这不是直接控制速度或位置!环境内部维护一个显式的二阶动力学模型:
# 在 env.step() 内部执行 self.vel[i] += self.action[i] * self.dt * self.max_accel # 加速度积分得速度 self.vel[i] = np.clip(self.vel[i], -self.max_vel, self.max_vel) # 速度限幅 self.pos[i] += self.vel[i] * self.dt # 速度积分得位置其中self.dt=0.1(仿真步长),self.max_accel=2.0,self.max_vel=3.0。这意味着 agent 输出的(-1, -1)并不等于“全速左下”,而是“以最大加速度向左下加速”,实际位移取决于当前速度。这种设计更贴近真实无人机动力学,也迫使 agent 学习“提前减速”“预判转向”等高级策略——这也是它能跑出平滑包抄轨迹而非锯齿折线的根本原因。
3. MADDPG 算法实现:PyTorch 版本的关键模块拆解与参数配置
3.1 网络结构:Actor-Critic 分离 + Critic 联合输入
MADDPG 的核心在于Critic 网络接收所有 agent 的动作(actions)和观测(observations)作为输入,而 Actor 网络仅接收自身观测。本项目严格遵循此设计:
- Actor 网络(per agent):
3-layer MLP (23→128→128→2),输出连续动作(ax, ay),使用tanh激活保证输出在[-1,1]。 - Critic 网络(shared):输入拼接为
[obs_1, ..., obs_n, act_1, ..., act_n],总维度23*n + 2*n。网络结构为3-layer MLP (input_dim→256→256→1),输出标量 Q 值。
关键代码片段(maddpg_agent.py):
class Critic(nn.Module): def __init__(self, state_dim, action_dim, n_agents): super().__init__() # 拼接后总输入维度:所有 obs (23*n) + 所有 actions (2*n) input_dim = n_agents * state_dim + n_agents * action_dim self.net = nn.Sequential( nn.Linear(input_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1) ) def forward(self, states, actions): # states: [batch, n_agents, state_dim], actions: [batch, n_agents, action_dim] x = torch.cat([states.view(states.size(0), -1), actions.view(actions.size(0), -1)], dim=1) return self.net(x).squeeze(-1) # [batch]参数说明:
state_dim=23,action_dim=2,n_agents=4(默认配置)。这里states.view(...)是关键——必须 flatten 所有 agent 的观测和动作,否则 critic 无法学习联合 Q 值。若你改成n_agents=3,务必同步修改Critic.__init__中的input_dim计算,否则 forward 会因维度不匹配崩溃。
3.2 训练流程:CTDE 下的双网络更新与目标网络软更新
MADDPG 的训练循环不是单 agent DDPG 的简单复制。本项目采用per-agent replay buffer + shared critic update模式:
- 每个 episode 结束后,将
(obs_dict, act_dict, reward_dict, next_obs_dict, done_dict)存入全局 replay buffer(非 per-agent buffer),因为 critic 需要联合样本; - 采样 batch 后,对每个 agent
i:- 用其 actor 网络生成
act_i' = actor_i(next_obs_i); - 将
act_i'替换到next_actions中(其余 agent 动作用旧 policy 生成),构造next_actions; - 计算 target Q:
target_q = reward_i + gamma * critic_target(next_states, next_actions) * (1-done_i); - critic loss:
F.mse_loss(critic(states, actions), target_q); - actor loss:
-critic(states, actions_with_i_replaced).mean()(注意:只替换 agent i 的动作,其余保持原采样动作,这是 MADDPG 的 policy gradient 推导要求)。
- 用其 actor 网络生成
目标网络软更新代码(tau=0.01):
def soft_update(self, target, source, tau): for target_param, param in zip(target.parameters(), source.parameters()): target_param.data.copy_(tau * param.data + (1.0 - tau) * target_param.data)血泪经验:
tau=0.01是经过 12 轮消融实验确定的最优值。若设为0.001,target network 更新太慢,critic 过估计严重;若设为0.1,target network 跟随太紧,训练极易发散。这个值必须手调,不能照搬文献。
3.3 奖励函数设计:解耦个体贡献与团队目标
奖励不是简单给“靠近目标就加分”。它由三部分加权构成(reward = w1*r_catch + w2*r_coop + w3*r_safe):
r_catch:基础围捕奖励。当目标被n架无人机在R_catch=0.3半径内包围(即n>=3且max_dist_to_target < R_catch)时,给予+5.0稠密奖励;否则为0。r_coop:协同奖励。计算所有无人机到目标距离的标准差std_dist,r_coop = -0.5 * std_dist。标准差越小,说明无人机分布越均匀(呈三角/四边形包抄),奖励越高。r_safe:安全惩罚。若任意两架无人机距离<0.15,则r_safe = -1.0;若撞障碍物,r_safe = -2.0。
为什么这样设计?单纯
r_catch会导致所有无人机挤向目标一点(丧失包围意义);加入r_coop强制分布,但若权重过大,agent 会过度“摆造型”而忽略追击;r_safe则防止碰撞这一硬约束。三者权重w1=1.0, w2=0.3, w3=0.5是平衡后的结果——你若调高w2,会看到无人机在目标外围画圆而不逼近。
4. 训练与评估:从零启动的完整命令链与可视化验证方法
4.1 一键训练:命令行参数详解与典型配置
项目根目录下train.py支持全参数化启动。最简训练命令:
python train.py --env_name "DroneCapture-v0" --n_agents 4 --max_episode_len 200 --n_episodes 5000 --batch_size 1024 --lr_actor 0.001 --lr_critic 0.001 --gamma 0.99 --tau 0.01 --seed 42关键参数说明:
| 参数 | 默认值 | 作用 | 调整建议 |
|---|---|---|---|
--n_agents | 4 | 无人机数量 | 必须与环境n_drones一致,否则obs_dict键名不匹配 |
--max_episode_len | 200 | 每 episode 最大步数(对应 20 秒仿真) | 若目标逃逸能力过强,可增至 300,但需同步增大replay_buffer_size |
--n_episodes | 5000 | 总训练轮数 | 4 机围捕通常 3500 轮收敛,低于 2500 轮效果不稳定 |
--batch_size | 1024 | Replay buffer 采样 batch 大小 | 显存不足时可降至 512,但训练稳定性下降 |
--lr_actor/critic | 0.001 | 学习率 | critic 学习率可比 actor 高 2 倍(如0.002),加速 Q 值收敛 |
--gamma | 0.99 | 折扣因子 | 围捕是短周期任务,0.95更合适(已实测优于 0.99) |
注意:
--seed 42不是摆设。MADDPG 对随机种子敏感,不同 seed 下收敛轮数可能相差 ±800 轮。建议固定 seed 复现实验,对比不同超参时再换 seed 取均值。
4.2 训练过程监控:TensorBoard 日志与关键指标解读
运行train.py后,日志自动写入runs/目录。启动 TensorBoard:
tensorboard --logdir=runs --port=6006重点关注以下曲线:
Loss/Critic_Loss:应稳定下降至~0.05以下,若持续高于0.2且震荡,检查 critic 输入维度是否正确(见 3.1);Reward/Episode_Reward:4 机环境下,3000 轮后应稳定在3.5–4.2区间(满分 5.0),若长期低于2.0,大概率是r_coop权重过低或R_catch设得过大;Metrics/Coop_Std_Dist:应从初始~1.8降至~0.4,表明包抄形态从分散走向紧凑;Metrics/Collision_Rate:应始终<0.02(2%),若突增,检查r_safe惩罚是否生效(见 3.3)。
4.3 离线评估:evaluate.py与围捕成功率计算逻辑
训练完成后,用evaluate.py进行 100 episode 离线测试:
python evaluate.py --model_path ./models/maddpg_4agents_ep3500.pth --n_eval_episodes 100 --render True--render True会弹出 Matplotlib 动画窗口,直观查看围捕过程。核心评估指标capture_success_rate计算逻辑如下:
def compute_capture_success(episodes): success_count = 0 for ep in episodes: # 检查是否存在连续 5 步,满足:至少 3 架 drone 距目标 < 0.3,且目标未逃出边界 for t in range(len(ep['dones'])): if t+5 > len(ep['dones']): break in_catch_zone = [] for i in range(n_agents): dist = np.linalg.norm(ep['positions'][t][i] - ep['target_pos'][t]) in_catch_zone.append(dist < 0.3) if sum(in_catch_zone) >= 3 and not ep['target_escaped'][t:t+5].any(): success_count += 1 break return success_count / len(episodes)关键细节:成功判定不是“某一步满足条件”,而是“连续 5 步稳定维持包围”,这排除了瞬时误触的噪声。同时要求目标未逃逸(
target_escaped为False),避免“围住但目标已飞出地图”的假阳性。实测该逻辑下,3500 轮模型的capture_success_rate ≈ 92.3%。
5. 避坑指南:5 个真实踩过的雷区与一招定位法
5.1 现象:训练初期 reward 为 0,且持续 1000+ 轮无变化
原因:r_catch奖励触发条件过于苛刻,或目标初始位置离无人机集群过远(>2.0 单位),导致 agent 根本学不到“靠近”的正反馈。
解决:在env.reset()中,将目标初始位置限制在[-0.8, 0.8]^2区域,并确保其与任意无人机初始距离<1.5。本项目已在drone_env.py第 127 行加入此约束,若你修改了初始化逻辑,请同步检查。
5.2 现象:Critic loss 突然暴涨至inf或nan,随后训练崩溃
原因:Critic.forward()输入中存在inf或nan值,常见于next_obs中的dist_t(目标距离)在目标逃逸出界时未做防错处理,变为inf。
解决:在env.step()末尾添加距离裁剪:self.target_dist = np.clip(self.target_dist, 0.0, 5.0)。本项目已在drone_env.py第 288 行实现,若你新增了障碍物逻辑,请检查所有np.linalg.norm()计算后是否np.clip。
5.3 现象:训练后期 reward 波动剧烈,Coop_Std_Dist在0.3和0.8之间跳变
原因:r_coop奖励项权重w2过高,导致 agent 过度优化分布均匀性而牺牲追击效率,形成“先布阵、再追”的低效循环。
解决:将w2从0.3降至0.15,并增加r_catch的稀疏性——例如改为“首次达成包围时给+5.0,后续维持不给分”,迫使 agent 专注突破。
5.4 现象:evaluate.py渲染动画中,无人机频繁抖动、原地旋转
原因:Actor 输出的动作(ax, ay)被直接用于加速度积分,但未加入动作平滑(action smoothing)或速度限幅失效。
解决:在env.step()中,对self.action[i]做低通滤波:self.action_smooth[i] = 0.8 * self.action_smooth[i] + 0.2 * self.action[i],再用self.action_smooth[i]积分。本项目默认关闭此功能(为保真动力学),若需平滑,取消drone_env.py第 245 行注释。
5.5 现象:多卡训练时报错RuntimeError: Expected all tensors to be on the same device
原因:torch.nn.DataParallel默认将模型放在cuda:0,但replay_buffer中的 tensor 仍在 CPU,step()时未.to(device)。
解决:在MADDPGAgent.sample()方法中,对采样的batch所有元素强制迁移:batch = {k: v.to(self.device) for k, v in batch.items()}。本项目已内置,但若你修改了 buffer 结构,请检查sample()返回值是否全部 on device。
6. 进阶技巧:从“能跑通”到“可解释”的三步验证法
6.1 步骤一:冻结 critic,单独训练 actor 的策略合理性
MADDPG 的 critic 往往是瓶颈,但它的黑盒性掩盖了 actor 是否真的学到了有效策略。一个强力验证是:临时禁用 critic 更新,只训练 actor,观察 reward 是否上升。操作如下:
- 注释掉
train.py中critic.update(...)调用; - 将
critic_loss设为 0,actor_loss仍按原公式计算; - 运行 500 轮,绘制
Episode_Reward曲线。
若 reward 持续上升(哪怕缓慢),说明 actor 网络结构合理,动作空间映射正确;若 reward 为 0 或负,问题一定出在 actor 输入(obs)或动作物理映射(见 2.3)。我曾用此法在 2 小时内定位到obs中目标角度angle_t的符号定义错误(应为atan2(dy, dx)但写成了atan2(dx, dy)),修正后 reward 立即从-1.2跳至+0.8。
6.2 步骤二:用 Grad-CAM 可视化 critic 的注意力焦点
Critic 的输入是拼接向量,我们想知道它到底在关注什么。本项目提供visualize_critic_attention.py,它通过计算 critic 最后一层线性层权重对输入各维度的梯度,生成热力图。关键代码:
# 对单个 batch 计算梯度 critic.eval() q_val = critic(states_batch, actions_batch) # [batch] q_val[0].backward() # 只取第一个样本反传 grads = critic.net[0].weight.grad.abs().mean(dim=0) # [input_dim] # 将 grads 按 obs/action 维度分组,归一化后绘图运行后得到热力图(示例):
| 输入维度段 | 平均梯度权重 | 解读 |
|---|---|---|
obs_i[12:16](目标相对位置) | 0.82 | critic 最关注目标在哪 |
obs_i[4:8](最近友机位置) | 0.45 | 次要关注队友位置,用于判断包围缺口 |
actions_j(其他 agent 动作) | 0.61 | 显著关注他人动作,证明 CTDE 生效 |
obs_i[20:23](全局统计) | 0.18 | 关注度较低,说明设计合理——全局信息是辅助,非主导 |
教训:如果
actions_j的梯度权重<0.2,说明 critic 没学会利用其他 agent 动作,大概率是Critic.forward()的输入拼接顺序错了(比如把actions放在states前面导致网络混淆)。
6.3 步骤三:构建“对抗目标”验证鲁棒性
标准训练用的目标是匀速直线运动。要检验策略泛化性,需引入更强对手。本项目drone_env.py内置三种目标模式:
| 模式 | 代码标识 | 行为特征 | 测试目的 |
|---|---|---|---|
simple | "simple" | 匀速直线,速度0.5 | 基准性能 |
evasive | "evasive" | 检测到最近无人机距离<0.8时,立即转向垂直方向 | 测试反应延迟 |
adaptive | "adaptive" | 每 10 步计算包围密度,若std_dist<0.5则加速至0.8 | 测试长期策略韧性 |
在evaluate.py中指定--target_mode "adaptive",若成功率从92%降至<60%,说明策略过拟合简单目标。此时应增强训练时的目标多样性:在train.py中设置--target_mode_prob "[0.4, 0.4, 0.2]",让三种模式按概率混合采样。
从那以后我每次验证新算法,都强制走一遍这三步:先冻 critic 看 actor 是否靠谱,再用 Grad-CAM 看 critic 是否真在“看”,最后用对抗目标戳破幻觉。它不能保证成功,但能让你在 debug 时少熬 3 个通宵。希望帮到你。
本文还有配套的精品资源,点击获取