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

资讯详情

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

PettingZoo多智能体环境入门:AEC与Parallel API实战指南

PettingZoo多智能体环境入门:AEC与Parallel API实战指南 第一次接触 PettingZoo 的时候我刚从单智能体强化学习转过来以为多智能体环境无非就是“把 Gym 的循环外面再套一个 for 循环”。结果第一个 Demo 写完跑起来各种报错差点原地劝退。后来把 AEC 和 Parallel 两条 API 路径彻底理清楚才发现PettingZoo 的设计其实非常讲究——它不是在 Gymnasium 外面随便套壳而是把多智能体环境里的“轮流决策”和“同步决策”抽象成了两套不同的执行模型。这篇内容我就按自己实操踩坑的路径来写先讲清楚为什么选 PettingZoo再讲安装时最容易卡住的几个细节然后分别用两个 Demo 跑通 AEC 和 Parallel 两条线路最后分享一套我自己排错时常用的自检方法。目标只有一个让你不用加班就能把第一个多智能体环境跑起来。1. 先搞明白 PettingZoo 解决了什么多智能体环境库的选型逻辑1.1 单智能体时代的环境抽象为什么不能直接照搬在 Gymnasium 的单智能体环境里整个交互循环非常简洁初始化、循环执行 action、拿到 observation 和 reward、判断是否结束。这个模型假设环境里只有一个决策者所有状态变化都是这个决策者和环境博弈的结果。多智能体环境一上来就把这个假设打破了。环境里同时存在多个决策者每个决策者都有自己的 observation、自己的 reward甚至可能在不同的时间点拥有不同的行动权。更麻烦的是智能体的数量不是固定不变的——有的环境里智能体会中途退出有的环境会动态增加新智能体还有的环境里智能体之间存在信息不对称和行动顺序依赖。这些问题不是“多写一层 for 循环”就能解决的。单智能体循环中obs, reward, done是一组固定结构多智能体环境里这些量全都变成了以 agent 为 key 的字典而且字典的 key 会变化。如果每个研究者都从零开始设计自己的环境接口整个领域就没法做标准化实验了。PettingZoo 正是冲着这个痛点来的。它把“多个智能体同时决策”和“多个智能体轮流决策”分别建模形成了 AECAgent Environment Cycle和 Parallel 两套 API。前者适合棋牌类、回合制、部分可观察的场景后者适合合作控制、机器人编队这类同步决策的场景。两套 API 底层共享一套观测空间和行动空间定义切换成本很低。1.2 PettingZoo 与 Gymnasium 的关系以及它和多智能体生态的差异先理清一个容易混淆的点PettingZoo 不是 Gymnasium 的官方扩展但它和 Gymnasium 同属 Farama Foundation 维护。Gymnasium 定义了单智能体的Env接口和spaces空间体系PettingZoo 直接复用了这套空间体系所以env.observation_space(agent)返回的依然是gymnasium.spaces里的空间对象。这意味着你以前在单智能体项目里写的Box、Discrete、Dict处理逻辑在这里基本都能复用。和同类多智能体库对比PettingZoo 的核心优势在于“生态标准化”。例如 RLlib 内置了自己的多智能体环境字典格式但它在离线训练、策略组合上有自己的强绑定SMAC 专注于星际争霸的微观操作环境虽好但任务领域比较单一MAgent 偏群体大规模智能体更接近群体智能仿真。PettingZoo 则是一套通用的标准化接口同时内置了大量开箱即用的经典环境MPE 粒子世界、经典博弈类石头剪刀布、德州扑克、象棋、围棋、Atari 双人游戏、以及专门设计的合作类 Butterfly 系列。做算法对比实验、入门学习、快速原型验证这套库是目前最不折腾的选择。还有一个实际好处PettingZoo 的安装不依赖任何重型游戏引擎纯 Python 包加少量底层库就能跑通。这对被 C 环境编译折磨过的人来说体验好太多了。2. 安装环节最容易翻车的三个细节版本、虚拟环境和依赖2.1 Python 版本与 PettingZoo 版本的匹配先说结论建议使用 Python 3.10 或 3.11装当前最新正式发布版本。PettingZoo 1.x 是 API 重构后的大版本和早期 0.x 完全不同网上很多教程还停留在 0.x 时代代码抄过来直接报错这个坑我踩过。怎么确认自己装的是新版最简单的办法是安装时直接指定大版本pip install pettingzoo1.24,2如果环境里 Python 版本太旧比如 3.7、3.8很多依赖包的新版本已经不支持了pip 会为了兼容自动降级降着降着就可能出现诡异行为。所以我不建议在旧版本 Python 上折腾直接用 conda 创建一个干净环境最省事conda create -n pz python3.11 -y conda activate pz2.2 两种依赖安装路径基础安装与按需补装PettingZoo 的基础包只包含环境接口和核心依赖经典环境、MPE 粒子环境、部分逻辑简单的环境装完就能跑。命令很简单pip install pettingzooAtari 环境和带图像渲染的环境需要额外依赖最典型的是ale-py和pygame。你可以在安装 PettingZoo 的同时一起装掉pip install pettingzoo ale-py pygame在 Linux 服务器上pygame的底层依赖偶尔会缺比如SDL2相关库没装渲染时不会立即报错但会出现黑屏或无响应的情况。真遇到这种问题用系统包管理器装一下基础图形库sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev如果你是 Windows 环境大部分依赖都有预编译的 wheel 包一般不会遇到编译问题。真正的麻烦反而来自同一个 Python 环境里已经装过旧版gym或gymnasiumPettingZoo 对 gymnasium 的版本有要求冲突时会出现 import 报错。所以我坚决推荐虚拟环境别图省事直接装到 base 环境里。2.3 装完立刻验证的 3 条命令安装完成后不要急着写业务代码先跑一个冒烟测试确认环境能真正加载。我在新机器上必跑这三条python -c import pettingzoo; print(pettingzoo.__version__)python -c from pettingzoo.mpe import simple_spread_v3; env simple_spread_v3.env(); print(env.possible_agents)python -c from pettingzoo.classic import rps_v2; env rps_v2.env(); print(env.observation_space(env.possible_agents[0]))如果第二条能打印出三个智能体的名字说明环境加载没问题第三条能打印出观测空间说明整个空间体系工作正常。这几条验证 5 秒钟就跑完了能帮你把“环境没装好”和“代码逻辑写错”这两类问题快速区分开。3. 第一个 Demo用石头剪刀布跑通 AEC 引擎的完整回合循环3.1 为什么第一个 Demo 选 RPS很多人学 PettingZoo 喜欢直接上 MPE 的simple_adversary觉得粒子环境“更像多智能体”。但我的建议不是这样——第一个 Demo 应该选逻辑最简单、智能体数量最少的经典环境。RPS石头剪刀布只有两个智能体每个智能体的行动空间是Discrete(3)观测也不复杂非常适合用来理解 AEC 引擎的轮转机制。先别急着关注策略第一步只需要让环境跑起来。随机策略虽然没有任何智能但它能帮你验证环境接口、回合循环和资源释放这整条链路是否通畅。3.2env.agent_iter()循环的逐行拆解下面这段代码是 AEC 引擎的标准写法也是 PettingZoo 1.x 版本里最常见的 Demo 入口from pettingzoo.classic import rps_v2 env rps_v2.env(render_modehuman) env.reset(seed42) for agent in env.agent_iter(max_iter100): observation, reward, termination, truncation, info env.last() if termination or truncation: action None else: action env.action_space(agent).sample() env.step(action) env.close()这段代码看起来简单但里面有四个关键点搞不清楚后面必踩坑。第一env.agent_iter()返回的是环境的智能体迭代器。它内部不是简单地遍历env.agents列表而是按照 AEC 的规则每次调度到下一个该行动的智能体。你不需要自己维护“轮到谁了”这个状态环境会告诉你。第二env.last()返回的是“当前被调度的智能体”的观测、奖励和结束标志。注意这里不是一次性返回所有智能体的观测字典而是只返回当前这个 agent 的数据。这是 AEC 和 Parallel 最大的区别之一也是新手最容易犯迷糊的地方。第三termination和truncation是两个独立的结束标志。termination表示这一局游戏结束比如某一方赢了truncation表示因为超出时间步或其它原因截断终止。不管哪一个是True这个智能体当前回合都不能再执行正常动作要传actionNone。这是新版的硬性要求旧写法里的dones已经废弃了。第四env.step(action)是给“当前智能体”执行动作然后环境内部会自动把控制权切换到下一个智能体。你不需要在循环里额外写“切换下一个”的逻辑这也是 AEC 引擎帮你封装好的东西。3.3 把随机动作换成规则策略并观察奖励变化跑通随机策略之后可以立刻做一个小改动把随机策略替换成规则策略比如一个智能体固定出第一个合法动作另一个固定出第二个合法动作。from pettingzoo.classic import rps_v2 env rps_v2.env(render_modehuman) env.reset(seed42) def rule_policy(agent, observation): # 先通过 action_space 确认合法动作范围 # 第一个智能体固定返回 0第二个返回 1 # 注意不同版本 RPS 的动作语义可能不同这里只保证动作合法 return 0 if agent env.possible_agents[0] else 1 for agent in env.agent_iter(max_iter30): observation, reward, termination, truncation, info env.last() if termination or truncation: action None else: action rule_policy(agent, observation) env.step(action) if agent env.possible_agents[-1]: print(fRewards: {env.rewards})这里加了一个打印条件在轮到最后一个智能体行动完之后打印所有智能体的累计奖励。你会发现奖励并不是每回合都存在的因为石头剪刀布的结果是“一锤定音”的某回合可能只有一方得分另一方得负分也可能平局双方都是 0。通过观察奖励变化你能直观感受到“这个环境的奖励是针对每个 agent 分开计算、分开返回的”而不是全局共享一个标量。理解这一点再看复杂环境里的团队奖励和个体奖励思路就顺多了。4. 升级到真正的多智能体场景Pistonball 上的 Parallel API 实战4.1 AEC 与 Parallel 两套 API 的本质区别跑通 RPS 之后你已经掌握了 AEC 的核心循环。但实际做多智能体实验时另一套 API——Parallel——的出镜率其实更高尤其是做合作类任务和同质智能体训练的时候。AEC 和 Parallel 的区别可以用一个生活化类比来理解。AEC 像是轮流下棋每一步只有一个选手行动选手能看到对方刚走完的那一步。Parallel 则像是多个球员在球场上同时跑位每个球员在同一时刻都要做出动作所有动作汇总后一起更新比赛状态。PettingZoo 官方解释里AEC 是更通用的模型因为很多环境天然是轮流决策的而且 AEC 能避免一些并行决策时的循环依赖问题。Parallel 则是更方便的工程抽象适合所有智能体同步行动的场景代码写起来也更接近单智能体 Gymnasium 的风格。两套 API 的对应关系我整理成了一张表对比维度AEC APIParallel API决策方式智能体轮流行动所有智能体同时行动典型场景棋牌、回合制游戏合作控制、群体任务核心循环agent_iter()last()reset()step(actions_dict)观测获取每次只拿当前智能体的数据一次拿到所有智能体的观测字典状态更新每步切换当前智能体所有动作共同作用于环境代码复杂度相对繁琐更接近 Gymnasium实际项目里如果智能体是协作关系且决策节奏一致优先考虑 Parallel如果环境本身是顺序博弈比如棋类游戏、谈判模拟用 AEC 更自然。4.2 Parallel 环境的随机策略 DemoPistonballPistonball 是 PettingZoo Butterfly 系列里的经典合作环境画面下方有一排活塞目标是把球推到右侧目标区域。多个活塞智能体需要协同工作奖励与球的整体移动相关非常适合演示 Parallel API。from pettingzoo.butterfly import pistonball_v6 env pistonball_v6.parallel_env(render_modergb_array, max_cycles200) observations, infos env.reset(seed42) for step in range(200): actions { agent: env.action_space(agent).sample() for agent in env.agents } observations, rewards, terminations, truncations, infos env.step(actions) if all(terminations.values()) or all(truncations.values()): print(fEpisode finished at step {step}) break env.close()对比 AECParallel 的代码结构明显更接近 Gymnasiumreset()直接返回所有智能体的初始观测字典step()接收一个完整的 action 字典返回四个字典和 info。你不需要关心“当前轮到谁”只需要保证字典里的 key 和env.agents里的 key 一致即可。有一点需要提醒Pistonball 的观测不是简单的向量而是包含图像信息的高维数组。第一次跑的时候建议打印env.observation_space(agent)你可能会看到一个Box空间形状是类似(H, W, C)的图像数据。这在实际训练中意味着你需要处理图像观测要么用 CNN要么先做降维预处理。很多初学者一上来就报维度不匹配十有八九是没提前看观测空间。4.3 两套 API 的互相转换与性能提醒PettingZoo 提供了两套 API 的转换工具这个功能在真实项目中很有用。有时候你找到一个环境只有 Parallel 版但自己的算法框架写的是 AEC 循环这时候可以用parallel_to_aec包装一层from pettingzoo.utils.conversions import parallel_to_aec aec_env parallel_to_aec(env)反向转换用aec_to_parallel。转换机制在接口层面帮你适配了两种循环风格但底层每次转换都有包装开销。如果训练规模大、交互频率高我不建议在训练循环里频繁做转换而是先确认原生 API再用对应版本写代码。算法调试阶段用转换工具没问题性能测试和最终训练一定要绕开这层包装。5. 环境跑通后如何确认它在正常工作渲染、信息量与状态自检5.1human模式和rgb_array模式的取舍环境第一次跑通大家的第一反应都是打开渲染窗口看一眼。PettingZoo 的render_mode参数支持两种常见模式human和rgb_array。human模式会弹出独立窗口播放环境画面适合本地联调时直观确认环境逻辑是否正确。但这模式在无桌面环境的服务器上非常不友好要么报 OpenGL 相关错误要么窗口闪一下就消失。我之前在远程服务器上跑实验图省事开了human结果整个训练任务直接挂掉从那以后学乖了。如果你在服务器上工作强烈建议使用rgb_array模式它不会弹窗而是把每一帧渲染结果作为数组返回。你可以手动保存成图片或视频import numpy as np from pettingzoo.butterfly import pistonball_v6 env pistonball_v6.parallel_env(render_modergb_array) observations, infos env.reset(seed42) frames [] for step in range(50): actions {agent: env.action_space(agent).sample() for agent in env.agents} observations, rewards, terminations, truncations, infos env.step(actions) frames.append(env.render()) # 此时 frames 里每项都是 RGB 数组可以交给 imageio 或 opencv 编码成视频这样即使没有图形界面也能事后回看完整过程。对多智能体任务来说“看得见”非常重要很多环境 bug 不渲染根本发现不了比如智能体穿模、奖励异常、目标物消失等等。5.2 观测空间、行动空间和奖励范围的自查方法很多人在跑通 Demo 后急于进入算法训练跳过了环境自检结果训练出来一组无意义的“垃圾策略”。我自己的习惯是任何环境接入训练前先花十分钟做一轮环境自检核心看三个东西。第一观测空间。打印env.observation_space(agent)确认维度是否符合预期。如果是Dict空间还要看每个 key 的边界是否合理。这一步能提前发现“图像像素范围是 0-255 还是 0-1”这类问题。第二行动空间。打印env.action_space(agent)确认是Discrete还是Box。如果是连续控制注意边界值是否对称如果是离散动作确认动作数量是否符合任务语义。第三奖励范围。打印env.reward_range或者用随机策略跑 100 步统计奖励的均值和方差。这样你能在训练前就对奖励尺度有一个粗略感知避免梯度更新一步就爆炸。python - EOF from pettingzoo.mpe import simple_spread_v3 env simple_spread_v3.parallel_env() obs, infos env.reset() print(obs space:, env.observation_space(env.possible_agents[0])) print(act space:, env.action_space(env.possible_agents[0])) print(reward range:, env.reward_range) total_rewards [] for _ in range(100): actions {agent: env.action_space(agent).sample() for agent in env.agents} obs, rewards, terminations, truncations, infos env.step(actions) total_rewards.append(sum(rewards.values())) print(avg random reward:, sum(total_rewards) / len(total_rewards)) EOF这里用随机策略跑 100 步得到的平均奖励虽然没有训练意义但能作为后续模型训练效果的基准线。你的算法不管多复杂至少要跑赢这条基线才算真正学到了东西。5.3 常见错误排查链路从循环崩溃到维度不匹配我在使用 PettingZoo 的过程中遇到过最多的几类错误下面按排查顺序列一下可以当作一份速查清单。第一类报错信息里出现agent ... is not in agents。这种通常是因为你在循环里把env.agents列表自己维护了一份环境内部已经更新了智能体状态你的列表却还是旧的。解决方案是永远使用env.agents作为唯一数据源不要在外部缓存智能体列表。第二类循环无限跑下去不结束。原因往往是agent_iter()没有设置max_iter或者truncation没有正确触发。现在的新版本支持在agent_iter(max_iter...)里直接限制最大迭代次数训练阶段跑太久没结束先检查终止条件是不是设置合理。第三类维度不匹配或类型不匹配。多智能体环境的观测往往包含多个智能体的数据如果直接把多个观测拼成一个 batch形状对不上很正常。排查时先用print(observation.shape)确认再看是不是np.float32或np.float64的类型不一致。PettingZoo 内部大量依赖 NumPy 数组类型不统一会在后续计算中埋雷。第四类渲染黑屏或崩窗。优先怀疑缺依赖尤其pygame和SDL库。可以先跑最基础的human渲染环境试一下如果基础环境能渲染复杂环境大概率只是资源加载问题。第五类从旧教程抄的代码无法运行。这是最折腾人的一类因为报错千奇百怪。你只要记住一个原则PettingZoo 1.x 版本中dones已经被拆成terminations和truncationsreset()的返回结构也变了。遇到任何“这个属性不存在”的报错先去官方文档查你所用版本的 API 签名不要盲目改代码。排错这件事我自己最大的体会是别在真正训练之前追求花哨的算法设计先把环境循环的稳定性跑夯实。多智能体任务本身就比单智能体复杂一个量级如果你连环境接口都没吃透后面训练出的任何结果都没有参考价值。把每个环境接入训练前的自检步骤固定成流程习惯比任何技巧都管用。
返回列表