尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多智能体协同围捕算法Python源码解析:Voronoi分区与MADDPG

多智能体协同围捕算法Python源码解析:Voronoi分区与MADDPG 简介面向计算机专业学生与研究者的多智能体协同围捕算法Python实现聚焦不同环境下的协同决策、通信机制与路径规划可直接用于课程设计、综合实践及项目开发训练。源码基于Voronoi区域划分与MADDPG多智能体强化学习框架覆盖单出口、多出口、凸环境以及pygazebo非完整约束小车等典型仿真场景并涉及围捕任务分工、边界判断与冲突消解等关键问题。压缩包共18个文件以13个Python源码为主另含3个zbak备份文件、1个备份zip及1个说明文档整体仅57KB轻量且模块化配合README便于快速定位训练器、环境脚本或算法核心。目前已有67人学习适合初步接触多智能体协同控制的开发者参考。该实现源自通过导师审核并获得优异评价的学术研究提供完整可运行代码与实施方案能够帮助读者理解任务分解、环境配置与算法调优思路也可以基于现有代码扩展新实验是课程设计或毕业设计的可靠起点。1. 多智能体协同围捕算法这套Python源码包能复现什么如果你在找一份能直接复现的多智能体协同围捕算法 Python 源码而不是几个零散的单文件 Demo这份项目包值得先花半小时拆一遍再决定要不要往环境里装依赖。它把两条技术路线都装进了同一个包基于 Voronoi 分区的几何规则围捕以及基于 MADDPG 的学习式围捕。场景覆盖单出口、多出口、凸包环境和 Gazebo 仿真文件里还带了一组围捕判定与共线检测工具正好对应课程设计和综合实践里“协同围捕”最常见的验收点。适合正在做毕设开题、综合实践或者想快速在项目里加入围捕模块的同学如果你已经跑过 MADDPG也能从它的共线处理和场景切换逻辑里挖到一点能直接搬走的细节。2. 拆开源码包Voronoi 分区围捕与 MADDPG 各自负责什么2.1 从文件清单读出设计意图拿到包先别急着装环境按文件名过一遍基本就能猜出作者的设计结构。这份源码的命名很有规律voronoi_*开头的是几何规则路线maddpg/目录是学习路线*_unicycle_test.py、husky_*是仿真验证线judge_collinear.py是姿态判定工具。文件/目录作用voronoi_single_exit.py单出口场景主脚本追捕者把目标逼向指定出口并完成捕获multi_multi_exit.py多追捕者、多出口场景按分区判定每个追捕者负责堵哪个出口multi_multi_close_env.py闭合边界下的多追捕者多目标围捕voronoi_convex_env.py凸包环境中的 Voronoi 围捕验证目标在凸包角点附近的逃逸行为voronoi_API.py对scipy.spatial.Voronoi的封装输入位置列表输出分区、顶点与邻居关系husky_voronoi.pyHusky 轮式机器人模型下的 Voronoi 围捕pygazebo_unicycle_test.pyPyGazebo 与 unicycle 单轮车模型的联调入口unicycle_test.py单轮车运动学模型测试检查角速度/线速度接口是否正常judge_collinear.py追捕者—目标三点共线判断识别围捕姿态退化maddpg/MADDPG 训练与工具含trainer训练主循环和commonreplay buffer、探索噪声README.md.zbak及若干.zbak原文档与源码的备份文件防覆盖/防乱码用的改名文件这种组织方式很常见几何路线负责“看得见”的规则行为学习路线负责“学得会”的对抗策略仿真文件用于把算法输出接到真实机器人运动模型上。voronoi_API.py之所以单独拆出来是因为每个场景脚本都要反复调用同样的分区计算、区域归属判断抽成 API 后单出口、多出口、闭合环境三个脚本的差异就只剩下场景初始化和边界处理了。2.2 Voronoi 分区围捕谁离目标最近谁负责主追Voronoi 围捕的核心思路不复杂把所有追捕者的位置作为种子点生成一张将空间切分成独立区域的维诺图每个区域内的任意一点到该区域种子的距离都小于到其他任何种子的距离。目标落在哪个区域那个区域的追捕者就是离目标最近的“主追捕者”直接朝目标移动其余追捕者则沿区域边界或朝向目标的方向移动压缩包围圈。这一步在scipy.spatial里有成熟实现voronoi_API.py大概率就是把它包了一层。我一般会这样写from scipy.spatial import Voronoi import numpy as np def get_primary_pursuer(pursuers, target): # pursuers: (N, 2)追捕者位置 # target: (2,)目标当前位置 points np.vstack([pursuers, target]) vor Voronoi(points) target_region vor.point_region[len(pursuers)] # 目标点的区域索引 for i in range(len(pursuers)): if vor.point_region[i] target_region: return i return int(np.argmin(np.linalg.norm(pursuers - target, axis1)))这段代码的逻辑是把追捕者和目标放进同一个点集计算 Voronoipoint_region返回每个输入点所属的区域编号目标点所在区域一定对应某个追捕者种子。如果分区正常循环里直接能匹配到主追捕者最后一行用欧氏距离兜底处理目标恰好落在区域边界、浮点误差导致匹配失败的情况。注意target_region取的是len(pursuers)这个索引前提是points中追捕者在前、目标在最后顺序不能乱。参数上要提醒一点scipy的二维 Voronoi 至少需要 4 个点才能稳定生成也就是num_pursuers不小于 3。如果只有两个追捕者加一个目标区域划分会退化成射线主追捕者归属在边界附近来回跳围捕圈直接散掉。所以包里的场景脚本默认追捕者数量都在 3 到 6 之间改参数时不要低于这个下限。2.3 MADDPG中心化训练、去中心化执行MADDPG 走的是另一条完全不同的路。它是多智能体版本的 DDPG每个智能体拥有独立的 Actor 网络决策时只用自己观测到的局部信息但训练时 Critic 网络可以看到所有智能体的状态与动作从而在训练阶段学到其他智能体行为对自身价值的影响。这就是“中心化训练、去中心化执行”也是它和独立 DDPG 最本质的区别。maddpg/trainer里放的就是这套训练主循环common里是经验回放与探索噪声等通用组件。训练更新时critic 的 loss 是标准的 TD 误差actor 的 loss 则是把所有智能体的动作拼起来、用 Critic 对动作求梯度后回传给 Actor# 每个智能体各自采样动作 actions [agent.actor(obs_i) noise_i for agent, obs_i in zip(self.agents, batch_obs)] # 中心化 Critic输入为全部状态和全部动作 q_value critic(tf.concat(batch_all_obs, axis-1), tf.concat(actions, axis-1)) next_q target_critic(tf.concat(batch_all_next_obs, axis-1), tf.concat(next_actions, axis-1)) critic_loss tf.reduce_mean((reward gamma * next_q - q_value) ** 2) # 确定性策略梯度Q 值对动作求导再经 Actor 反向传播 actor_loss -tf.reduce_mean(critic(tf.concat(batch_all_obs, axis-1), tf.concat(actions, axis-1)))关键在tf.concat(actions, axis-1)这一行Critic 看到的是全部智能体的动作所以训练时每个智能体都能感知“队友在做什么”这比单个 DDPG 各自为战稳定得多。训练循环里每个 episode 结束会把整段经验存入 replay buffer之后统一采样一个 batch 做一次更新。MADDPG 的优势是面对动态目标时有更强的泛化性不用手工设计围捕规则代价是训练极其吃参数。原版论文里的常用参数是actor_lr1e-2、critic_lr1e-3、gamma0.95、tau0.01如果你第一次跑这份包建议先把buffer_size从默认的 1e6 降到 1e5否则训练前期内存占用太大。至于 reward 怎么给我会在第 4 章展开。3. 把项目跑起来依赖安装、入口脚本与运行参数3.1 依赖环境Python 版本与 pip 安装先确认 Python 版本。这份包里有pygazebo_unicycle_test.py而pygazebo是老库对 Python 3.10 以上的编译支持很差我建议直接用 Python 3.8 或 3.9省掉一堆源码编译的麻烦。核心依赖是这几样numpy1.21.6 scipy1.7.3 matplotlib3.5.3 gym0.21.3 tensorflow2.10.0 pygazebonumpy和scipy用于 Voronoi 计算与矩阵运算matplotlib负责把围捕过程画成轨迹图gym提供 MADDPG 标准的Env接口tensorflow跑神经网络pygazebo只在你要联调 Gazebo 仿真时才真正需要。安装命令pip install -r requirements.txt如果只想跑 Voronoi 几何路线pygazebo和tensorflow都可以先不装等明确要跑 MADDPG 或仿真时再补能省不少时间。3.2 先跑单出口 Voronoi 围捕最快验证环境通不通的方式是直接跑单出口脚本python voronoi_single_exit.py正常情况下会弹出一个 matplotlib 窗口里面画出追捕者与目标的运动轨迹终端打印每一轮的围捕结果与累计成功率。窗口关闭或达到最大步数后脚本自动退出。单出口场景通常还支持命令行参数用来控制追捕者数量和出口位置python voronoi_single_exit.py --num_pursuers 4 --num_evaders 1 --exit_x 5.0 --exit_y 0.0--num_pursuers建议设在 3 到 6 之间低于 3 时二维 Voronoi 退化围捕行为会变得很奇怪。--exit_x和--exit_y指定出口坐标出口的位置决定了目标的逃逸方向这个参数直接影响后续多出口脚本的设计思路。单出口脚本的预期输出是目标只要到达出口位置就算逃逸成功追捕者必须在目标到达出口前完成包围。3.3 再跑多出口与 MADDPG 训练多出口场景的运行方式类似入口换一下名字就行python multi_multi_exit.py --num_pursuers 5 --num_evaders 1这里--num_evaders同样控制逃逸者数量注意闭合环境脚本里如果设了多个逃逸者围捕判定会变成“所有目标都被捕获”难度一下高了不少。MADDPG 训练入口不在顶层目录而是通过模块方式调用python -m maddpg.trainer --scenario multi_multi_exit--scenario参数指定场景脚本名trainer内部会按名称去maddpg目录下找对应的环境配置。训练过程中每 100 个 episode 打印一次平均回报第一次跑建议先设小一点的总回合数例如--total_episodes 2000只看回报有没有上升趋势。如果要接着上次的模型继续训练用--load-path指定权重目录即可python -m maddpg.trainer --scenario multi_multi_close_env --load-path ./models这里有个容易忽略的细节--load-path读取的是每个智能体子目录下的 actor 和 critic 权重文件MADDPG 按智能体索引分目录保存。如果你改了追捕者数量旧权重的结构对不上加载时要么报 shape 不匹配要么静默跳过训练等于从零开始。4. 核心逻辑逐段解析围捕判定、共线检测与训练参数4.1 围捕成功判定的两种写法围捕判定是整个算法正确率的标尺也是最容易被“差不多就行”带偏的地方。这份包里的场景脚本大概率用的是严格判定所有追捕者同时进入目标周围的捕获半径才算成功。写成代码是这样import numpy as np def is_captured(positions, target, capture_dist0.5): positions: (N, 2)所有追捕者的位置 target: (2,)目标当前位置 capture_dist: 捕获半径通常取机器人半径加安全余量 dists np.linalg.norm(positions - target, axis1) return bool(np.all(dists capture_dist))注意用的是np.all不是np.mean。如果用平均距离来判定会出现一个追捕者贴脸、其他追捕者散在外圈的情况平均距离虽小但包围圈根本没成型。严格判定要求群体整体压缩到捕获半径内这才是“围捕”而不是“单兵突进”。实际工程里还会加一个持续步数条件连续 3 到 5 个时间步都满足捕获条件才判定最终捕获。原因是目标在捕获半径边界附近来回移动时单帧判定会在成功与失败之间横跳统计成功率时噪声很大。4.2 judge_collinear.py围捕姿态退化的提前识别围捕圈最怕的一种情况是追捕者与目标排成一条直线。此时包围圈退化成一条线目标只要沿法线方向加速就能直接穿出而且 Voronoi 分区在这些点的附近会生成宽度接近零的细长区域主追捕者归属在边界处跳来跳去整个队伍开始抖动。judge_collinear.py就是用来识别这种姿态的核心是三点共线判断def judge_collinear(p1, p2, p3, threshold_deg5.0): 判断三点是否近似共线threshold_deg 为角度容忍度 v1 p2 - p1 v2 p3 - p2 cos_a np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) 1e-8) angle np.degrees(np.arccos(np.clip(cos_a, -1.0, 1.0))) collinear angle threshold_deg or angle 180.0 - threshold_deg return collinear计算两个向量的夹角余弦后转成角度小于threshold_deg算同向共线大于180 - threshold_deg算反向共线。1e-8是防止两个点重合时除零。加了np.clip是因为浮点误差可能让余弦值略微超出[-1, 1]不 clip 的话arccos会返回nan这个 bug 在 scipy 版本升级后更容易触发。检测到共线之后常见做法是给最中间那个追捕者一个垂直于连线的分量人为打散直线队形等姿态恢复后再回到分区围捕逻辑。4.3 MADDPG 训练参数与 Reward 设计MADDPG 能不能收敛reward 设计比网络结构更关键。这份包里如果用默认 reward大概率会遇到“训练半天 loss 降了但成功率不涨”的现象我一般会把 reward 拆成两部分即时距离奖励加稀疏捕获奖励。def compute_reward(prev_dist, cur_dist, captured, alpha1.0, beta20.0): prev_dist: 上一时间步追捕者与目标的距离 cur_dist: 当前时间步距离 captured: 本步是否捕获成功 return alpha * (prev_dist - cur_dist) beta * float(captured)前半部分alpha * (prev_dist - cur_dist)是距离差奖励追捕者每步靠近目标一点就获得正的即时回报。这部分必须给否则全部奖励都压在captured这一个稀疏信号上前期探索阶段几乎全是零回报Actor 学不到任何梯度。后半部分是捕获大奖用beta控制权重beta太大会淹没距离信号智能体会发现“随便转几圈碰运气抓到目标收益更高”结果训练出的策略毫无追击能力。我习惯先设alpha1.0, beta10~20如果发现追捕者原地绕圈先把beta降下来。常用训练参数大致这样参数常用值翻车迹象调整方向actor_lr1e-2Actor loss 剧烈波动降到 5e-3critic_lr1e-3Q 值发散、收益负无穷降到 5e-4gamma0.95目标移动快时奖励滞后明显调 0.9~0.99tau0.01目标网络长期跟不上当前网络降到 0.005batch_size1024小场景浪费显存且训练慢改成 512另外要确认critic的输入是否拼接了所有智能体的状态和动作这是 MADDPG 与独立 DDPG 的分水岭。如果代码里只用了当前智能体自己的观测那它退化成 DDPG围捕协作基本学不出来。5. 常见问题与排查跨环境部署里的五个坑5.1 Gazebo 里 unicycle 模型原地打转现象pygazebo_unicycle_test.py启动后机器人在仿真里一直原地打转不朝目标移动线速度明显异常。原因unicycle 单轮车模型接收的控制量是线速度v和角速度w但 Voronoi 脚本输出的是位置增量或期望偏向角。如果直接把这两个量当作v和w传给仿真位置增量会被误当成期望转角机器人就变成原地转圈。解决在仿真接口层把算法输出转成v和w不是去改算法内部。常见做法是加一层纯 P 控制def unicycle_control(current, target, v_max1.0, w_max0.8): dx target[0] - current[0] dy target[1] - current[1] heading np.arctan2(dy, dx) v v_max w_error heading - current[2] w np.arctan2(np.sin(w_error), np.cos(w_error)) w np.clip(w * 2.0, -w_max, w_max) return v, ww_error用arctan2(sin, cos)处理是为了避免角度跨越正负 180 度时出现跳变直接用减法在pi附近会产生很大的角速度命令。w乘以 2.0 是 P 增益要根据仿真步长微调。5.2 Voronoi 分区在目标穿越时让追捕者剧烈抖动现象目标贴着两个 Voronoi 区域的边界移动时主追捕者归属在几个智能体之间反复横跳队伍走位抽搐包围圈收不拢。原因目标恰好落在区域边界附近浮点误差导致区域归属判断不稳定分区退化成细长形时主追捕者切换没有附加条件。解决给主追捕者切换加滞回逻辑新的候选距离必须比当前主追捕者近一定比例才允许切换def switch_primary(current_best, new_best, dist, hysteresis0.05): if dist[new_best] dist[current_best] * (1 - hysteresis): return new_best return current_besthysteresis0.05表示新候选要比当前主追捕者近 5% 才切换这能过滤掉边界上的轻微抖动。但如果目标高速穿越区域边界滞回会让切换偏慢实际使用时把hysteresis调到 0.03 左右更均衡。5.3 MADDPG 训练 loss 下降但围捕成功率不涨现象训练日志里 critic loss 一路下降每 100 个 episode 打印的平均回报却在原地波动成功率长期为零。原因最常见的是 reward 太稀疏追捕者在没有距离奖励的情况下完全靠随机探索撞上捕获条件概率极低训练到后期 Actor 也没有有效梯度可用。解决先把 reward 换成“距离差 捕获大奖”的组合确认每一个时间步都有非零反馈其次检查是否每个 step 都更新而不是 episode 结束后才统一更新MADDPG 一般按 step 更新更稳。最后把buffer_size从 1e6 降到 2e5让旧样本更快被新策略覆盖不然初期探索的垃圾样本会长期占用经验池拖慢收敛。5.4 README.md.zbak 打不开或中文乱码现象解压后README.md.zbak双击打不开用系统记事本打开是一堆乱码。原因.zbak是备份改名文件用来自定义后缀防止 Markdown 文件被重复打开或覆盖。乱码是因为原文件是 UTF-8 编码Windows 记事本默认按 GBK 解码中文显示就成了乱码。解决先把后缀改回.md用 VS Code 打开并把编码切到 UTF-8如果还是乱码说明原文件可能是 GBK 编码用命令转码iconv -f GBK -t UTF-8 README.md README_utf8.mdjudge_collinear.py.zbak同理先改名再打开重点检查第一行import语句是否因为编码问题出现语法错误。这类备份文件是作者防手滑的保留操作并不是资源损坏别急着删。5.5 多出口场景里目标直奔最近出口围捕失败率拉满现象multi_multi_exit.py跑起来后目标开场就朝最近的出口直线逃逸追捕者从初始散点位置出发完全追不上失败率接近 100%。原因追捕者的初始点位是均匀随机生成的没有考虑出口位置。Voronoi 分区只能描述当前时刻的空间归属不能预测出口方向的威胁出口附近没有预置布防就等于放行。解决初始化时把至少一个追捕者放到出口与目标连线的中垂线附近堵住逃逸路线运行时给离出口最近的那个追捕者叠加一个“出口优先占位”的引力项其他追捕者维持分区围捕。这是多出口场景和单出口场景最大的实现差异也是作者把两个脚本分开写的原因。6. 移植到自有场景围捕效果验证的一层关键改动拿到这份源码包很多人会直接跑一遍看动画然后感叹两句就结束了。但真正有价值的是把围捕判定换成你自己场景的物理约束再统计成功率。我建议的最小改造是把“目标到达任意出口即逃逸成功”的逻辑单独抽出来def check_escape(target, exits, escape_dist0.3): for ex in exits: if np.linalg.norm(target - np.array(ex)) escape_dist: return True return Falseescape_dist是逃逸判定的半径取机器人本体尺寸的 1.5 倍左右比较合理。这个函数要挂在env.step的返回里和is_captured共用同一个done信号。改造完就能跑批量验证了def evaluate(env, policy, seedsrange(25), max_steps1000): episodes, captures 0, 0 for seed in seeds: obs env.reset(seedseed) for _ in range(max_steps): actions [policy(o) for o in obs] obs, reward, done, info env.step(actions) if done: captures int(info.get(captured, False)) break return captures / len(seeds)info.get(captured, False)需要环境在step返回时带上这个字段如果原脚本没有就从is_captured的结果里补齐。验证时要固定随机种子至少跑 25 个 episode否则成功率方差太大看不出算法差异。我一般还会同时记录平均捕获时间和平均逃逸时间这两个数比单看成功率更能暴露策略的偏向捕获时间过长说明追捕者在绕路逃逸时间过短说明布防有漏洞。从那以后我每次拿到这类多智能体源码包都会先跑通单出口脚本再把逃逸判定换成自己的物理约束最后才上 Gazebo——这个顺序反过来每一次都是在调车而不是调算法。希望帮到你。本文还有配套的精品资源点击获取
返回列表