
简介这份资源面向强化学习与多无人机协同方向的研究者、研究生及算法工程师提供一套可直接运行的MADDPG多智能体围捕仿真项目用于解决多机协同决策与目标围捕的训练验证问题。压缩包共53个文件、约3.79MB以py源码为主涵盖智能体、网络、经验回放与仿真环境等模块并配有各智能体的actor、critic及target网络权重文件另有csv训练记录、png示意图、md与docx说明文档便于快速复现与二次开发。项目基于PyTorch构建自定义Gymnasium环境实现多无人机对多目标的追踪、拦截与围捕训练曲线与评估脚本齐全可帮助读者理解MADDPG在多智能体协调中的落地方式掌握状态建模、奖励设计与协同策略调优思路。目前已有152人学习适合作为课程设计、科研实验或算法对比的参考方案。1. 多无人机协同围捕仿真环境一份能直接跑通 MADDPG 的 PyTorch 工程如果你正在做多智能体强化学习方向的研究大概率绕不开两个坎一是找不到一个能直接跑通的多无人机围捕环境二是找到了环境却发现算法实现和论文对不上。这份资源就是冲着这两个痛点来的——它把多无人机协同围捕场景封装成了标准的 Gymnasium 环境算法侧用 PyTorch 实现了 MADDPG训练入口、参数配置、评估脚本都是现成的。换句话说你拿到手不需要从零搭环境、不需要自己推导 MADDPG 的更新公式改几个超参数就能开始跑实验。适合的人群很明确做多智能体方向的研究生、需要快速验证围捕策略的算法工程师以及想拿一个完整项目练手 MADDPG 的强化学习入门者。下面我从环境结构、算法实现、训练调参到踩坑排查把这份资源拆开讲清楚。2. 环境与算法架构Gymnasium 接口怎么对接 MADDPG2.1 围捕任务的建模逻辑多无人机协同围捕的核心问题定义是这样的N 架无人机在一个连续二维空间中运动目标是让所有无人机在有限步数内包围一个或多个移动目标。每架无人机只能观测到局部信息——自己的位置、速度以及一定范围内其他无人机和目标的位置。这个局部观测的设定很关键它决定了问题是不是真正意义上的「多智能体」如果每架无人机都能看到全局状态那直接用一个集中式控制器就行了不需要多智能体算法。环境的状态空间设计通常包含每架无人机的位置坐标、速度分量以及目标的对应信息。动作空间是连续的一般是二维的加速度或速度增量这也是选 MADDPG 而不是 DQN 系列的原因——DQN 处理不了连续动作。奖励函数是这类环境里最需要关注的部分常见做法是把它拆成几项距离惩罚无人机离目标越远扣分越多、围捕成功奖励所有无人机进入目标周围一定半径内给正奖励、碰撞惩罚无人机之间或与目标距离过近给负奖励、步数惩罚每走一步给一个小的负值促使尽快完成任务。这份资源的环境封装遵循 Gymnasium 的标准接口也就是说它实现了reset()和step()两个核心方法返回值格式是(obs, reward, terminated, truncated, info)。如果你之前用的是旧版 Gym 的(obs, reward, done, info)四元组这里需要做适配后面避坑章节会具体讲。2.2 MADDPG 的 Actor-Critic 结构拆解MADDPG 的核心思想是「集中式训练、分布式执行」。每个智能体有一个 Actor 网络输入是自己的局部观测输出是动作同时有一个 Critic 网络输入是所有智能体的观测和动作的拼接输出是 Q 值。训练时 Critic 能看到全局信息帮助 Actor 更好地更新执行时每个 Actor 只用自己的局部观测做决策不需要通信。在这份资源的 PyTorch 实现里Actor 网络一般是三层全连接隐藏层维度 64 或 128输出层用 tanh 激活把动作限制在 [-1, 1] 范围内再乘以一个最大动作幅度系数映射到实际动作空间。Critic 网络同样是全连接结构但输入维度是N * obs_dim N * act_dim因为要拼接所有智能体的信息。代码结构上通常会有一个MADDPGAgent类封装单个智能体的 Actor 和 Critic一个MADDPGController或类似的类管理所有智能体的训练流程。经验回放池是共享的所有智能体的转移数据存在同一个 buffer 里采样时一起取出。目标网络的软更新系数tau一般设 0.01这个值不能太大否则目标网络跟得太快训练容易震荡。# MADDPG Agent 核心结构基于 PyTorch import torch import torch.nn as nn import torch.nn.functional as F class Actor(nn.Module): def __init__(self, obs_dim, act_dim, hidden_dim128, max_action1.0): super().__init__() self.fc1 nn.Linear(obs_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, act_dim) self.max_action max_action def forward(self, obs): x F.relu(self.fc1(obs)) x F.relu(self.fc2(x)) # tanh 将输出限制在 [-1,1]再缩放到实际动作范围 return self.max_action * torch.tanh(self.fc3(x)) class Critic(nn.Module): def __init__(self, total_obs_dim, total_act_dim, hidden_dim128): super().__init__() self.fc1 nn.Linear(total_obs_dim total_act_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, 1) # 输出标量 Q 值 def forward(self, all_obs, all_act): x torch.cat([all_obs, all_act], dim-1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) return self.fc3(x)上面这段代码里obs_dim是单架无人机的观测维度act_dim是动作维度围捕场景一般是 2total_obs_dim和total_act_dim是所有智能体拼接后的维度。max_action控制输出动作的幅度上限如果你的无人机加速度范围是 [-2, 2]就把它设成 2.0。Critic 的输出层不加激活函数因为 Q 值可以是任意实数。2.3 训练循环与参数配置训练主循环的逻辑是每个 episode 开始时调用env.reset()拿到初始观测然后每一步让每个智能体的 Actor 根据局部观测选动作把所有动作拼成联合动作传给env.step()拿到奖励和下一帧观测把这条转移数据存入回放池。当回放池里的数据量超过一个最小阈值后每个智能体从池中采样一批数据用 Critic 计算目标 Q 值更新 Critic 和 Actor 的网络参数最后软更新目标网络。关键参数方面回放池容量一般设 100000 到 1000000太小会导致经验重复利用不够太大则早期无效数据占比过高。Batch size 常见 256 或 512。Actor 和 Critic 的学习率通常分开设Actor 用 1e-4Critic 用 1e-3Critic 学得快一些有助于给 Actor 提供稳定的梯度信号。折扣因子gamma设 0.95 到 0.99 之间围捕任务如果 episode 步数不长0.95 就够了。软更新系数tau设 0.01。# 训练主循环骨架 for episode in range(max_episodes): obs, _ env.reset() episode_reward 0 for step in range(max_steps): actions [] for i, agent in enumerate(agents): # 每个智能体根据自己的局部观测选动作 action agent.select_action(obs[i], noise_scale) actions.append(action) actions np.stack(actions) next_obs, rewards, terminated, truncated, info env.step(actions) # 存入共享回放池 replay_buffer.push(obs, actions, rewards, next_obs, terminated) obs next_obs episode_reward sum(rewards) if len(replay_buffer) min_buffer_size: for agent in agents: agent.update(replay_buffer, batch_size) if all(terminated) or any(truncated): break # 每隔若干 episode 做一次评估并保存模型 if episode % eval_interval 0: evaluate(agents, env) save_checkpoint(agents, episode)这段循环里有个容易忽略的点terminated和truncated要分开处理。terminated表示任务真正结束比如围捕成功或全部无人机出界truncated表示达到最大步数被截断。在计算目标 Q 值时只有terminated为 True 才把下一状态的 Q 值置零truncated的情况仍然要 bootstrap。这是 Gymnasium 和旧版 Gym 的一个重要区别写错了训练效果会明显变差。3. 从零跑通训练环境安装、参数调优与评估3.1 环境依赖安装与验证这份资源依赖的核心库是 PyTorch、Gymnasium、NumPy 和 Matplotlib用于可视化训练曲线。PyTorch 的安装要根据你的 CUDA 版本选对应的命令如果只是跑这个围捕环境CPU 版本其实也够用因为网络规模不大但 GPU 版本训练速度会快不少。# 创建虚拟环境推荐用 conda conda create -n uav_maddpg python3.10 -y conda activate uav_maddpg # 安装 PyTorch以 CUDA 11.8 为例CPU 版本去掉 --index-url 那行即可 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Gymnasium 和其他依赖 pip install gymnasium numpy matplotlib # 验证安装 python -c import torch; print(torch.__version__, torch.cuda.is_available()) python -c import gymnasium; print(gymnasium.__version__)安装完成后先跑一个最小验证脚本确认环境能正常 reset 和 step。这一步别跳过我见过太多人直接开训结果跑到一半发现环境返回的观测维度对不上白白浪费几个小时。# 环境冒烟测试 import gymnasium as gym # 假设环境注册名为 UAVSwarm-v0具体名称以资源内为准 env gym.make(UAVSwarm-v0, num_agents3, num_targets1) obs, info env.reset(seed42) print(f观测维度: {[o.shape for o in obs]}) print(f动作空间: {env.action_space}) for _ in range(10): actions [env.action_space.sample() for _ in range(3)] obs, rewards, terminated, truncated, info env.step(actions) print(f奖励: {rewards}, 终止: {terminated}, 截断: {truncated}) env.close()这段测试脚本的作用是确认三件事观测的维度和数量是否和智能体数量匹配、动作空间的形状是否正确、step()返回值的解包格式是不是五元组。如果这里报错后面训练肯定跑不起来。3.2 超参数调整的实操建议MADDPG 的训练对超参数比较敏感同一套参数换个环境可能就完全不 work。以下是我在实际调参中总结的几个经验值范围参数建议范围说明Actor 学习率1e-5 ~ 1e-4太大容易震荡太小收敛慢Critic 学习率1e-4 ~ 1e-3一般比 Actor 大一个量级折扣因子 gamma0.95 ~ 0.99围捕任务步数短用 0.95软更新 tau0.005 ~ 0.020.01 是常用起点回放池容量1e5 ~ 1e6太小经验不够太大早期数据拖累Batch size256 ~ 512太小梯度噪声大探索噪声 sigma0.1 ~ 0.3随训练衰减探索噪声这块值得多说一句。MADDPG 通常用 Ornstein-Uhlenbeck 噪声或者简单的高斯噪声来做探索。训练初期噪声要大一些让智能体充分探索动作空间后期逐渐衰减让策略趋于稳定。如果你发现训练曲线一直上不去先检查噪声是不是衰减太快了智能体还没来得及探索到有效的围捕策略就陷入了局部最优。3.3 训练过程监控与模型评估训练过程中需要盯住的指标有三个每 episode 的累计奖励、围捕成功率、平均完成步数。累计奖励反映整体趋势围捕成功率是最直接的性能指标平均完成步数反映策略的效率。这三个指标要一起看不能只看奖励——有时候奖励在涨但成功率没变说明智能体学会了刷奖励但没真正完成围捕任务。# 评估脚本跑 N 个 episode 统计成功率 def evaluate(agents, env, num_episodes50): success_count 0 total_steps 0 for _ in range(num_episodes): obs, _ env.reset() for step in range(max_steps): actions [agent.select_action(o, noise_scale0.0) # 评估时不加噪声 for agent, o in zip(agents, obs)] obs, rewards, terminated, truncated, info env.step(actions) if all(terminated): success_count 1 total_steps step 1 break if any(truncated): break success_rate success_count / num_episodes avg_steps total_steps / max(success_count, 1) print(f成功率: {success_rate:.2%}, 平均步数: {avg_steps:.1f}) return success_rate评估时一定要把探索噪声关掉否则策略本身是确定性的加了噪声反而测不准。另外评估用的 episode 数量不能太少50 次起步否则成功率波动很大看不出真实水平。4. 避坑与排查训练不收敛、维度报错、奖励设计翻车4.1 训练完全不收敛奖励曲线一条直线现象跑了上千个 episode累计奖励几乎不变围捕成功率始终为 0。原因最常见的原因是奖励信号太稀疏。如果只有围捕成功才给正奖励其他时候全是零或均匀的负值智能体很难从随机探索中找到成功路径。另一个可能的原因是 Critic 的学习率过高Q 值估计发散Actor 拿到的梯度方向是错的。解决先检查奖励函数加入距离引导项——每架无人机离目标越近每步给一个小的正奖励这样即使没围捕成功智能体也能感知到「靠近目标」是有利的。Critic 学习率降到 1e-4 试试同时观察 Critic loss 是否在合理范围内波动。如果 loss 持续增大说明学习率确实太高了。4.2 Gymnasium 返回值解包报错现象env.step()返回四个值而不是五个或者env.reset()只返回 obs 不返回 info。原因环境代码用的是旧版 Gym 的 API而安装的是 Gymnasium。两者在reset()和step()的返回值格式上不兼容。解决找到环境文件里的reset()和step()方法把返回值改成 Gymnasium 格式。reset()返回(obs, info)step()返回(obs, reward, terminated, truncated, info)。如果不想改环境代码也可以在训练脚本里做兼容处理但推荐直接改环境一劳永逸。4.3 智能体学会了「摆烂」策略现象训练一段时间后所有无人机都停在原地不动或者全部飞向边界围捕成功率反而下降。原因奖励函数里的步数惩罚太重智能体发现「不动」比「移动但没围捕成功」扣分更少。或者碰撞惩罚设置不合理无人机之间稍微靠近就扣大量分导致智能体学会了互相远离而不是协同围捕。解决调整奖励权重的比例。步数惩罚要小一般每步 -0.01 到 -0.05 就够了。碰撞惩罚的触发距离要合理不能设得太大否则无人机还没靠近目标就被罚了。围捕成功的奖励要足够大让智能体有明确的优化目标。4.4 GPU 显存溢出或训练速度异常慢现象训练时显存占用持续增长直到 OOM或者 GPU 利用率很低但训练速度很慢。原因显存持续增长通常是回放池实现有问题比如每次采样后没有正确释放中间变量或者把计算图带入了回放池。训练速度慢可能是数据在 CPU 和 GPU 之间频繁搬运。解决检查回放池存储的是不是 numpy 数组而不是 tensor如果是 tensor 要确保.detach()过。网络更新时把数据一次性搬到 GPU不要在循环里逐个搬。如果显存确实不够把回放池容量和 batch size 都降一半试试。4.5 多智能体之间没有协同行为现象每架无人机单独看都在靠近目标但整体上它们从同一方向围过去没有形成包围圈。原因MADDPG 的 Critic 虽然能看到全局信息但如果奖励函数里没有对「包围」这个空间关系的显式激励智能体没有动力去分散站位。另外如果所有智能体的 Actor 网络初始化太相似它们容易学到相同的策略。解决在奖励函数里加入围捕角度或覆盖面积的奖励项鼓励无人机从不同方向接近目标。网络初始化时给不同的智能体加不同的随机种子增加策略多样性。训练时也可以考虑给每个智能体的观测里加入一个标识位让它们知道自己是谁。5. 进阶技巧用课程学习加速围捕策略收敛训练多无人机围捕策略时最耗时的阶段是早期——智能体完全随机探索偶尔碰到目标也不知道是好事因为奖励信号被平均掉了。我自己的做法是引入课程学习先让环境只放一个静止目标无人机数量也减少到两架等成功率稳定在 80% 以上再逐步增加目标移动速度和无人机数量。这样每一阶段的难度都在智能体能力边界附近学习效率比一上来就满配高很多。具体实现上可以在环境外面包一层CurriculumWrapper根据当前训练 episode 数动态调整num_targets、target_speed和num_agents这几个参数。注意调整时不要突变比如目标速度从 0 到 1.0 之间分五档逐步加每档跑够一定 episode 再切下一档。class CurriculumWrapper: def __init__(self, env, total_episodes): self.env env self.total_episodes total_episodes self.stage_thresholds [0.2, 0.4, 0.6, 0.8] # 各阶段切换点 def reset(self, episode): progress episode / self.total_episodes if progress self.stage_thresholds[0]: self.env.set_target_speed(0.0) # 静止目标 self.env.set_num_agents(2) elif progress self.stage_thresholds[1]: self.env.set_target_speed(0.3) # 慢速移动 self.env.set_num_agents(2) elif progress self.stage_thresholds[2]: self.env.set_target_speed(0.6) self.env.set_num_agents(3) else: self.env.set_target_speed(1.0) # 全速移动 self.env.set_num_agents(3) return self.env.reset()课程学习的关键是切换时机——不能等当前阶段完全收敛再切那样太慢也不能还没学会就切那样后面阶段直接崩。我的经验是当前阶段成功率连续 20 个评估周期稳定在 70% 到 80% 之间就可以切了。切过去之后成功率会掉一截这是正常的等它重新爬上来再切下一档。另一个实用技巧是模型热启动。如果你之前已经用简单场景训练过一个还不错的模型可以直接把 Actor 和 Critic 的权重加载到新阶段的智能体里只微调不从头训。这样能省掉大量早期探索时间。加载时注意观测维度和动作维度要匹配如果无人机数量变了导致 Critic 输入维度变化Critic 需要重建但 Actor 通常可以直接复用。验证课程学习是否有效最直接的方法是画两条曲线对比一条是课程学习的成功率曲线一条是固定难度的成功率曲线。如果课程学习的方法在相同总 episode 数下成功率更高、达到目标成功率所需 episode 更少就说明策略有效。我一般还会额外记录每个阶段的切换点在曲线上标出来方便看每次难度提升后策略恢复的速度。从那以后我每次训练新的多智能体任务都会先花十分钟设计一个课程学习方案而不是直接上满配环境硬跑。这个习惯帮我省下了大量等待训练的时间也避免了因为早期探索太慢而误以为算法有问题。希望帮到你。本文还有配套的精品资源点击获取