简介多智能体协同围捕算法在多种环境下的实现源码与项目说明主要面向计算机相关专业正在完成课程设计、期末大作业的学生以及希望加强多智能体系统实战能力的开发者项目由个人完成并得到导师指导最终获得九十八分的高分评价整体设计思路和文档组织都较为规范。压缩包共包含十四个文件其中十三个为可运行的源码脚本另有一个项目说明文档整体体积约四十五KB源码覆盖单出口、多出口、凸环境、近距离交互等典型围捕场景并集成维诺图区域划分、多智能体策略训练、碰撞判定、运动控制及仿真环境测试等模块通过调整出口数量、障碍布局和智能体密度可观察不同参数下围捕策略的差异。项目说明文档对目录结构和运行方式做了梳理便于快速理清代码脉络目前已有173人学习下载。读者可借助该包完成课程报告、期末答辩或算法对比实验也能在此基础上改进策略进一步提升多智能体协同围捕的实用能力。1. 多智能体协同围捕值不值得做先看它解决什么问题把“多智能体协同围捕算法”从论文落到代码最容易被忽略的其实不是算法本身而是“各种环境”这四个字。同一套围捕策略在固定地图上能跑到 90% 成功率换一张带障碍物的地图就可能直接降到 20%这是追逃问题里最常见的翻车现场。这类 Python 源码加项目说明的高分项目核心价值在于把环境模拟、决策算法、训练评估串成一条能复现的链路先跑通一个最小场景再逐步换环境验证鲁棒性。适合正在做课程设计、毕业设计或者想入门多智能体强化学习的人把它当成一个落地样本而不是又一个调不好的黑匣子。2. 围捕不是追赶先把问题建模和三条算法路线定下来2.1 围捕和追赶的数学模型差在哪追赶问题的本质是“最短时间最近距离”追捕者有速度优势沿着目标轨迹做纯追踪就能收敛。围捕不一样尤其当捕手速度不比目标快时单点追踪永远追不上必须靠多智能体在空间上形成包围势场把目标的可行逃逸方向压到零。落到程序里围捕成功的判定通常写成两个条件的与至少一个追捕者进入目标的捕获半径。所有追捕者相对目标的方向角覆盖足够完整常见做法是“最大角间隙”小于阈值。方向角覆盖的计算很容易实现把每个捕手与目标连线与 x 轴的夹角排序再首尾相接算出相邻夹角差值取最大值。若最大间隙仍小于阈值比如 2π/3说明目标无论往哪个方向跑都有捕手能形成拦截围捕态势才算成立。这个判定比单看距离要严格得多也正是“各种环境”下最值得抽象的部分。带障碍物时被墙壁挡住的捕手不应该计入覆盖角目标视野受限时覆盖角还要换成视距可达覆盖。很多移植失败的项目都是死在这一步捕获判定写死了环境一变训练目标自己就矛盾了。2.2 规则、优化、学习三条路线的取舍做围捕有常见三类路线先想清楚再选不然源码都难拆。规则路线是手工给每个捕手指派拦截点再配合势场避障。比如目标前方左右各取一个虚拟点捕手按照“领点 斥力场”移动。上手最快半小时就能出可视化效果但目标一旦反向突破或做假动作队形重构极慢动态对抗上限低。优化路线把围捕写成分布式模型预测控制每步求解代价函数距离代价加包围角代价加避碰代价。适合有精确运动模型的实物机器人但目标加速度不可测、模型有偏差时预测就会失真调参成本并不低。学习路线是当前源码项目的主流也就是多智能体强化学习。策略从大量对局里自己学出来目标机动模式复杂、地图尺寸变化频繁时泛化通常比前两种好。常见实现包括 MADDPG、QMIX、MAPPO训练时间以小时计这是它的主要成本。三条路线不一定要互斥。我的常用组合是“学习出决策 规则做兜底”强化学习模块输出围捕点或期望速度底层控制器负责跟踪既能快速看到成形队形也方便换环境。路线上手速度强机动目标对抗换环境泛化最适用阶段规则 / 几何半小时弱差快速 Demo、基线对比分布式优化半天到一天中中有精确运动模型的实物多智能体强化学习一天加训练强较好复杂动态场景、研究型项目2.3 多智能体 MDP环境模块与算法模块解耦的接口写代码之前需要把围捕问题整理成部分可观测马尔可夫决策过程。状态 s_t 是所有智能体的完整信息观测 o_t 是每个捕手自己能感知的部分动作 a_t 可以是离散方向也可以是连续速度。转移概率交给仿真引擎处理环境模块只负责“状态-动作-奖励-下一状态”的转换算法模块只负责策略更新。这个解耦非常关键。优秀源码包一定把这两层用接口分开换环境不需要改动学习器只替换环境类里的 reset、step、reward 三个方法即可。后面所有代码示例都遵循这个约定这也是我最推荐你仿照的结构。3. 拆开一套围捕源码包环境、训练、评估三层怎么配合拿到一套“Python 源码 项目说明”第一件事不是双击运行 main.py而是看目录结构。这类源码几乎都长成一个模板环境层负责仿真和判定算法层负责网络和经验池运行层负责训练和评估。把三层之间的数据流摸清楚你就能按自己的需求改而不是跟着不存在的“官方文档”盲改。3.1 先读目录和项目说明四样东西缺一不可一个能打高分、能说服评审的项目说明不需要花哨但必须让另一个人照着能跑起来。我习惯按四个板块写环境和依赖Python 版本、PyTorch/Gymnasium 版本、安装命令。运行顺序训练入口、评估入口、可视化入口一行条命令对应一个效果。参数总表每个超参数的默认值、含义、调大调小分别会发生什么。环境替换指引如果我想换地图、加障碍、改目标数量改哪几个文件。这四个板块对应读者最常见的四类问题“这是什么”“怎么跑”“参数怎么调”“想改怎么做”。一份项目说明如果只有代码结构和理论推导多半只能算脚本算不上可复现的工程。3.2 最小围捕环境reset、step 和捕获判定怎么写这里给出一个极简的二维连续空间围捕环境捕手可以自由移动目标会向最远捕手的反方向逃跑围捕成功采用“距离 覆盖角”双重条件。你可以直接把它作为环境层的起点。import numpy as np class EncirclementEnv: 极简二维连续空间围捕环境。 目标策略向离自己最远的捕手反方向逃跑。 围捕成功任一捕手进入捕获半径且捕手对目标的 方向角最大间隙小于 max_gap。 def __init__(self, n_pursuers4, field_size10.0, capture_dist0.3, max_gap1.8, max_steps200): self.n_pursuers n_pursuers self.field_size field_size self.capture_dist capture_dist self.max_gap max_gap # 弧度约 103 度 self.max_steps max_steps self.action_space_dim 2 def reset(self, seedNone): if seed is not None: np.random.seed(seed) self.pursuers np.random.uniform( 1.0, self.field_size - 1.0, (self.n_pursuers, 2)) self.evader np.array([self.field_size / 2, self.field_size / 2]) self.step_count 0 return self._get_obs() def _get_obs(self): obs [] for p in self.pursuers: # 相对坐标是换环境后依然能迁移的关键 rel p - self.evader obs.append(np.r_[rel / self.field_size, 1.0]) return np.array(obs) def step(self, actions): self.pursuers np.clip(actions, -0.3, 0.3) self.pursuers np.clip(self.pursuers, 0, self.field_size) # 目标向最远捕手的反方向逃跑 dists np.linalg.norm(self.pursuers - self.evader, axis1) farthest self.pursuers[np.argmax(dists)] escape_dir self.evader - farthest escape_dir / (np.linalg.norm(escape_dir) 1e-6) self.evader escape_dir * 0.5 self.evader np.clip(self.evader, 0, self.field_size) self.step_count 1 capture, info self._check_capture() reward self._compute_reward(capture) done capture or self.step_count self.max_steps return self._get_obs(), reward, done, info def _check_capture(self): dists np.linalg.norm(self.pursuers - self.evader, axis1) if dists.min() self.capture_dist: return False, {capture: False} angles np.sort(np.arctan2( self.pursuers[:, 1] - self.evader[1], self.pursuers[:, 0] - self.evader[0])) gaps np.diff(np.r_[angles, angles[0] 2 * np.pi]) max_gap gaps.max() return max_gap self.max_gap, {capture: True, max_gap: max_gap} def _compute_reward(self, capture): # 简单版本捕获大奖励未捕获给小惩罚 if capture: return 50.0 return -0.01这段代码里最关键的是_check_capture。它先把所有捕手相对目标的方位角排序再用np.diff算出相邻夹角差最后取最大值。最大间隙小于max_gap意味着目标朝任何一个方向逃都会撞上至少一个捕手的拦截方向从数学上形成包围。max_gap设 1.8 弧度约 103 度是一个比较宽松的起点四个捕手均匀分布在目标四周时最大间隙接近 π/2因此 1.8 既能体现协作要求又不至于难到永远无法满足。reset返回的观测只用相对坐标并除以场地尺寸做归一化这个细节决定了换场地尺寸时策略是否还能用。绝对坐标会让网络隐式记住地图左上角在哪相对坐标则天然不依赖地图位置。3.3 训练循环共享参数的 DQN 先跑通再升级 MADDPG围捕项目第一版建议先用共享参数的 DQN 跑通。所有捕手角色对称共享一个 Q 网络完全可以成立。等到确实需要捕手分工、角色不对称时再升级到 MADDPG 这类 CTDE 框架。# 共享参数 DQN 训练骨架 replay ReplayBuffer(capacity200_000) q_net QNet(obs_dim, action_dim) target_net QNet(obs_dim, action_dim) target_net.load_state_dict(q_net.state_dict()) optimizer torch.optim.Adam(q_net.parameters(), lr5e-4) for episode in range(total_episodes): obs env.reset() episode_reward 0.0 while True: actions [] for o in obs: if np.random.rand() epsilon: action env.sample_action() else: action q_net.argmax(o) # 所有捕手共享同一个 Q 网络 actions.append(action) nxt_obs, reward, done, _ env.step(np.array(actions)) replay.add(obs, actions, reward, nxt_obs, done) if replay.size() batch_size: batch replay.sample(batch_size) td_error td_loss(batch, q_net, target_net, gamma) optimizer.zero_grad() td_error.backward() optimizer.step() obs nxt_obs episode_reward reward if done: break注意这里的动作是逐智能体独立生成的但奖励只有一个全局值。共享参数意味着每个捕手遵循同一套策略不同捕手的选择差异完全来自各自观测不同。对于对称角色这种设计收敛速度远快于独立 Q 网络也是“各种环境”下最稳妥的第一版。如果升级到 MADDPG核心变化集中在一点actor 只看自己的观测critic 拼接所有智能体的观测和动作来做价值评估。这样每个捕手能从全局视角学到“我的动作对整体包围态势的影响”弥补共享参数 DQN 无法表达差异化角色的不足。3.4 评估脚本成功率、平均围捕时间和轨迹回放训练时看的 loss 曲线和 reward 曲线并不能完全反映围捕质量需要有独立的评估脚本用贪婪策略跑固定数量的回合统计三个数字成功率、平均围捕时间、围捕成功时的最大角间隙。def evaluate(model_path, seed0): env EncirclementEnv(seedseed) model QNet(obs_dim, action_dim) model.load_state_dict(torch.load(model_path)) success, times, gaps [], [], [] for _ in range(50): obs env.reset(seedseed) for t in range(env.max_steps): actions [model.argmax(o) for o in obs] # 评估不用随机探索 obs, _, done, info env.step(np.array(actions)) if done: success.append(info[capture]) times.append(t) gaps.append(info.get(max_gap, 3.14)) break print(fsuccess_rate{np.mean(success):.2f} favg_time{np.mean(times):.1f} favg_gap{np.mean(gaps):.2f})评估时固定随机种子是必须的否则环境初始化不同两次评估结果不可比。成功率的统计要基于全部回合而不是只看训练最后几个 episode。可视化轨迹回放通常会把每个捕手的位置序列和目标的位置序列画在同一张图上这一步能直观暴露出“追在一起”和“真正形成包围”的差别。4. 各种环境的通用配方观测、奖励、超参数三处必调项环境从二维栅格换成连续平面加障碍物目标数量从 1 个变成 3 个多数人改完代码直接崩。原因是环境参数改动没有落实到底层的三处观测的坐标系、奖励的塑形方式、超参数的范围。这三处抽象得好换环境不需要重写策略代码。4.1 用相对观测代替绝对坐标换地图不需要改策略绝对坐标是最容易让模型过拟合环境的东西。捕手记住了地图左上角的原点位置地图尺寸一变分布整体平移策略立即失效。改成相对观测模型学的就是几何关系而不是空间位置。def make_obs_with_neighbors(i, pursuers, evader, evader_vel, field_size, max_speed, max_neighbors3): p pursuers[i] rel p - evader dist np.linalg.norm(rel) angle np.arctan2(rel[1], rel[0]) feat [dist / field_size, angle / np.pi, evader_vel[0] / max_speed, evader_vel[1] / max_speed] # 拼接最近几个捕手的相对位置固定长度便于网络输入 others np.delete(pursuers, i, axis0) dists_to_others np.linalg.norm(others - p, axis1) nearest_idx np.argsort(dists_to_others)[:max_neighbors] for j in nearest_idx: feat ((others[j] - p) / field_size).tolist() while len(nearest_idx) max_neighbors: feat [0.0, 0.0] return np.array(feat, dtypenp.float32)这段代码有两个细节值得注意。一是所有特征都做了归一化距离除以场地尺寸角度除以 π速度除以最大速度。连续空间的动作值动辄在 0 到 10 之间不归一化会让 Q 值的方差被个别大数值特征主导训练很难稳定。二是邻居数量固定为max_neighbors不足时补零这样即使环境里捕手数量变化网络的输入维度也不会变这就是“各种环境”下最偷懒但有效的兼容方案。4.2 把稀疏捕获奖励拆成进度奖励否则成功率永远是 0只有捕获成功给大奖励的稀疏设定在围捕问题上几乎必挂。初期随机策略根本不可能形成包围所有回合奖励都是轻微的负值网络学不到任何有意义的梯度信号。我把奖励拆成了三段接近奖励、包围进展奖励、捕获成功奖励。接近奖励看的是“离目标最近距离的减小量”包围进展奖励看的是“最大角间隙的减小量”。用差值而不是绝对值可以去掉常值偏置让正负信号更干净。def compute_reward(pursuers, evader, prev_min_dist, prev_max_gap): dists np.linalg.norm(pursuers - evader, axis1) min_dist dists.min() angles np.sort(np.arctan2( pursuers[:, 1] - evader[1], pursuers[:, 0] - evader[0])) gaps np.diff(np.r_[angles, angles[0] 2 * np.pi]) max_gap gaps.max() reward 0.0 reward (prev_min_dist - min_dist) * 1.0 # 靠近目标 reward (prev_max_gap - max_gap) * 2.0 # 包围圈收紧 if min_dist capture_dist: reward 50.0 # 捕获大奖励 reward - 0.01 # 时间惩罚 return reward包围角差值权重设为 2.0高于距离权重的 1.0是为了让捕手优先分散站位。只给接近奖励捕手会全部挤到目标身后追着跑加上包围角奖励后捕手才有动力走到目标两侧和前方去封路。这个权重比例是我目前试下来最容易出现成型包围队形的组合但捕手数量变化时要注意捕手越多均匀分布后的最大间隙天然越小包围角奖励的权重可以适当下调否则多出来的捕手得到的奖励信号过于雷同分工还是学不出来。4.3 四组超参数初始值从这些开始调别一上来照搬论文多智能体围捕的超参数和单智能体强化学习不完全一样原因在于多个智能体共享同一环境训练步数看起来很多但有效经验密度低。下面这四组数值是我建议的起点。参数推荐初始值调整方向学习率5e-4Adam训练震荡就降到 1e-4loss 完全不动可试 1e-3batch size64 ~ 128数据充足用 256过小更新方差大折扣因子 gamma0.95回合短/ 0.99回合长目标几十步内能捕获可以低一点epsilon 衰减1.0 到 0.05约 20 万步衰减太快会锁死在探索策略里连续动作空间下学习率建议从 5e-4 开始这是最容易平衡稳定性和收敛速度的区间。离散动作空间可以放宽到 1e-3因为动作空间小Q 值更新对网络扰动相对更可控。gamma 的选择看捕获时间如果目标在 50 步内能被围住0.95 就够过大的 gamma 会让远期奖励累积过重学习初期反而不稳。5. 围捕项目最容易踩的五个坑现象、原因、修法下面五个坑是我做围捕项目时真实遇到过的血泪经验每条按“现象 → 原因 → 解决”拆解方便你对号入座。5.1 捕获判定设太严训练永远不收敛现象成功率始终为零reward 曲线一开始下降然后就再也抬不起来。可视化里捕手会靠近目标但永远差一点形成包围。原因捕获判定用了“距离小于阈值且方向角最大间隙小于较小角度”的双重条件。随机策略下方向角覆盖几乎不可能满足奖励全是负的Q 网络学到的是“怎么做都失败”自然不收敛。解决先只保留距离条件做训练等捕手学会接近目标后再逐步把方向角阈值从宽松值收紧。或者在奖励里加入未达到包围角时的进展奖励让网络先学会“往两侧分散”这个子目标。5.2 所有捕手共用一个全局奖励结果谁都学不会配合现象捕手全部扎堆追在目标后面没有一个绕前拦截围捕成功率很低。原因全局共享奖励时单个智能体无法感知自己的动作对团队奖励的边际贡献。早期探索阶段任何一个捕手靠近目标都会让全局奖励上升其他捕手却因此拿到同样的正奖励“搭便车”现象直接把策略带偏。解决把奖励拆成个体项加团队项。个体项用该捕手自己带来的最小距离变化和角度覆盖变化团队项只在真正捕获成功时发放。这样每个捕手能明确知道“我走到哪个位置对包围有贡献”。5.3 一换环境就崩绝对坐标加未归一化是原罪现象固定地图上训练到 85% 成功率把 field_size 从 10 改成 20成功率掉到 10% 以下且训练多轮也回不来。原因观测里包含绝对坐标且没有做归一化。网络已经隐式记住了地图尺寸和坐标分布测试环境一旦偏离训练分布输出直接失真。解决观测统一改成相对坐标并归一化。更进一步的做法是训练时做 domain randomization每个 episode 随机生成场地尺寸、初始位置和速度范围让模型在“各种环境”下都见过相似分布这是提升泛化最直接的手段。5.4 老源码在新 gymnasium 下报错API 错位别硬扛现象跑别人的源码包第一行环境创建就报错或者 reset 返回的元组数量不对step 报“missing 1 required positional argument: action”。原因老代码基于 gym 0.21 或更旧版本新版 gymnasium 里 reset 返回(obs, info)seed 参数处理方式也变了动作空间类型同样有差异。API 错位不是代码逻辑问题硬改源码容易引入新错误。解决先看项目说明里锁定的依赖版本用虚拟环境安装旧版本跑通。跑通之后再考虑是否升迁到新 API而不是一上来就全局替换。一个最小环境测试脚本比任何排查都有效率。5.5 训练步长和部署步长不一致策略上真机就乱套现象仿真里训练收敛的模型放到另一个固定频率控制的环境里动作变得很冲捕手来回震荡。原因训练时决策频率等于仿真步长策略学会了“每一步都能立即改变方向”。真实控制器通常固定 20Hz 或 50Hz决策延迟一出现超调就跟着来了。解决训练时故意降低决策频率比如仿真实例每 5 步才让捕手决策一次同时在动作上叠加高斯噪声模拟执行误差。这样学出来的策略对控制周期不敏感迁移到不同步长的环境里表现更稳。6. 从训练收敛到效果可信验证链路与两个进阶方向判断围捕模型是否真的可用我的验证链路分三层。第一层是定量指标。固定随机种子跑 50 个测试回合统计成功率、平均捕获时间和成功时的最大角间隙。成功率低于 80% 的模型无论 reward 曲线多漂亮都不建议进入交付环节。第二层是可视化检查。画出捕手和目标在最后 20 步的轨迹确认捕手是在形成包围而不是单纯追击两者在轨迹图上的差别非常明显。第三层是环境泛化。改变场地尺寸、目标速度、捕手数量看成功率衰减幅度这直接回应标题里“各种环境下”的要求。进阶方向有两个比较值得投入。第一个是给捕手加通信约束从完全无通信的隐式协作改成有限带宽的消息通信。你会发现策略结构会明显改变捕手会主动利用消息传递目标位置和自身意图而不是仅凭观测猜测队友动作。第二个是从离散动作切到连续动作再对接底层控制器。连续控制版本跑通之后策略就可以接到 ROS 或实际底盘上做实物验证这也是这类源码项目从课程设计走向工程落地的必经路径。我第一版围捕项目只给了稀疏奖励连续熬了两个通宵都看不到成型的包围队形后来把包围角进展拆进奖励第三天上半夜第一次看到四条轨迹从三个方向收拢到目标周围那一刻才真正理解奖励塑形对多智能体协作的意义。如果你正卡在同一个位置希望这些经验能帮你少熬几个夜。希望这篇文章能帮到你。本文还有配套的精品资源点击获取