这两年多智能体强化学习被越来越多的人提起,但很多人一上来就被环境配置劝退了。PettingZoo作为目前最主流的多智能体环境库,承载了从博弈论经典场景到粒子物理仿真的一大堆测试环境,不管是做算法研究、写毕业论文还是自己折腾多智能体系统,基本都绕不开它。这篇博文我就从零开始,带你5分钟把PettingZoo跑起来,分析一下背后的API设计逻辑,再把我踩过的坑一并倒给你。
PettingZoo是Farama基金会维护的开源项目,和单智能体领域的Gymnasium是姊妹关系,你可以把它理解成“多智能体版的Gym”。它能解决的核心问题是:当环境里有多个agent同时决策、交替行动时,怎么统一地表示观察、动作、奖励和终止条件。这篇文章适合刚接触多智能体强化学习、被各种环境库搞晕的研究生和开发者,也适合想快速验证一个多智能体算法idea的工程师。我尽量用大白话讲清楚,让你不仅能装上、能跑通,还能知道每一步到底在做什么。
1. 装之前先弄明白:PettingZoo到底解决什么问题
1.1 多智能体环境的难点和PettingZoo的定位
先花一分钟想一个问题:一个环境里有两个agent在博弈,比如下棋、打乒乓球,或者一群agent在协作搬东西,这和单个agent的环境有什么本质区别?
单智能体环境里,你只需要处理一个观察、一个动作、一个奖励,用Gymnasium就能搞定。但多智能体环境不一样,几个关键问题就冒出来了:多个agent之间是轮流行动还是同时行动?每个agent能看到的信息可能不同,奖励可能是共享的也可能是对抗的,环境什么时候算结束?是某一个agent挂了就结束还是所有agent都结束才算完?这些问题如果每个研究组都自己定义一套接口,整个生态就碎片化了。
PettingZoo的定位就是把这些复杂情况抽象成统一接口,让研究者不用关注“怎么和环境交互”这种基建问题,直接专注算法本身。它提供了两类API,一类叫AEC(Agent Environment Cycle),模拟agent轮流行动的博弈过程;另一类叫Parallel,所有agent同时给出动作,环境统一推进。这两套API的适用场景后面我会专门讲。
1.2 什么时候该用PettingZoo,什么时候不该用
很多人一听说多智能体就上来用PettingZoo,其实它也不是万能的。我觉得有必要先帮你划一下边界。
如果你要做的是以下这些事,PettingZoo非常合适:
- 想跑现成的多智能体基准环境,比如MPE粒子世界、Atari多人游戏、经典博弈论问题(石头剪刀布、猜数字)
- 想验证一个多智能体强化学习算法在不同任务上的泛化能力
- 做课程作业或者复现论文,需要一个标准化的环境接口
但如果你的需求是重度自定义的工业级仿真,比如几百个agent带复杂物理引擎的仿真系统,PettingZoo可能不够灵活,你需要考虑更底层的仿真器。另外,如果你的问题本质上可以分解成多个独立的单智能体任务,也不一定非要套多智能体框架,反而会增加不必要的复杂度。
选型这事,我的经验是“用什么环境取决于你要强调什么核心难点”:强调合作,去跑Pistonball;强调竞争,去跑石头剪刀布或Atari对战;强调混合博弈,去跑MPE里的Predator-Prey。
2. 环境准备:用Python虚拟环境把坑提前埋掉
2.1 Python版本和虚拟环境工具选型
PettingZoo本质上是个Python库,所以Python环境是第一道关卡。我推荐使用Python 3.9到3.11之间的版本,太老的版本比如3.7在依赖兼容性上会有麻烦,太新的版本比如3.13有时候某些编译型依赖还没跟上。
虚拟环境工具我一般推荐两个方案:
- conda:适合数据科学和强化学习用户,因为很多RL相关的库用conda装更省心,还能管理Python版本本身
- venv:Python自带的轻量方案,如果你只是跑PettingZoo这种纯Python库,venv完全够用
我个人习惯用conda,不是因为Python库本身需要conda,而是后续大概率还要装PyTorch、CUDA相关的东西,conda在管理这些二进制依赖时确实省事。
注意:不管用哪种虚拟环境,都别直接装在系统Python里。多智能体项目依赖链复杂,今天装PettingZoo,明天装RL框架,后天再来个可视化库,不隔离的话依赖冲突能把人逼疯。
2.2 创建虚拟环境实操
用conda的话,打开终端执行:
conda create -n pettingzoo python=3.10 -y conda activate pettingzoo如果用venv,执行:
python3 -m venv pettingzoo_env source pettingzoo_env/bin/activate # Windows下是 pettingzoo_env\Scripts\activate创建完虚拟环境后,先确认一下Python版本:
python --version这一步看似简单,但它能帮你避开至少一半的环境问题。我自己就无数次因为没注意Python版本,在装某个依赖时莫名其妙报错,最后发现是Python版本不匹配。
3. 正式安装PettingZoo:版本陷阱一次说清
3.1 最基础的pip安装方式
激活虚拟环境后,安装PettingZoo非常简单:
pip install pettingzoo这条命令会安装PettingZoo本体以及它核心依赖的几个库,包括gymnasium、numpy、pygame等。装完之后你可以先验证一下是不是真的装好了:
python -c "import pettingzoo; print(pettingzoo.__version__)"正常会打印出版本号,比如1.24.3之类的。
这里有个特别重要的点,PettingZoo从1.0版本开始API有一次大重构,很多网上搜到的老教程用的是0.x版本的写法,环境名不一样,API也不一样。看到那种from pettingzoo.mpe import simple_spread_v2的教程,多半是老版本。你现在安装的版本肯定是最新的1.x版本,对应的环境名后缀也变成了v3、v4、v6这种。一定要搞清楚自己看到代码的版本,不然直接照抄很容易翻车。
3.2 按需安装特定环境集合包
PettingZoo把环境分成了几大类,基础安装并不会把全部环境的依赖都装上,因为有些环境的依赖比较重,比如Atari环境需要ale-py,这个库在某些平台上安装容易出问题。这其实是设计上的取舍:你只需要给你需要的环境装额外依赖。
常用的环境集合包如下:
| 集合名 | 覆盖环境 | 安装命令 |
|---|---|---|
| classic | 经典博弈论环境:石头剪刀布、德州扑克、猜数字等 | pip install pettingzoo[classic] |
| mpe | 粒子世界环境:simple_tag、simple_spread等 | pip install pettingzoo[mpe] |
| butterfly | 合作类环境:Pistonball(活塞球)、Knights Archers Zombies等 | pip install pettingzoo[butterfly] |
| sisl | 多智能体粒子类环境:Waterworld、Multiwalker等 | pip install pettingzoo[sisl] |
| atari | Atari 2600多人游戏 | pip install pettingzoo[atari] |
比如你想跑Pistonball,就执行:
pip install "pettingzoo[butterfly]"注意:在zsh终端里,方括号是特殊字符,所以安装带有extra的包时要用引号把包名括起来,写成
pip install "pettingzoo[butterfly]",不然会报zsh: no matches found的错误。
3.3 安装PyTorch等其他依赖
如果你打算跑Demo之后马上开始训练算法,那最好把PyTorch也先装上。PyTorch的安装方式会因操作系统和CUDA版本而异,建议去PyTorch官网用配置器生成对应的安装命令。如果只是跑环境Demo,PyTorch可以先不装,等需要训练时再装也行。
这里我想多说一句,PettingZoo只是一个环境库,它本身不包含强化学习算法。你需要在外面自己写训练逻辑或者接入其他RL框架。这点和Gymnasium是一样的。
4. 跑通第一个Demo:两种API打法全拆解
4.1 选什么环境当第一个Demo最合适
PettingZoo里环境很多,但对新手来说,我建议选Pistonball(活塞球)或者Rock-Paper-Scissors(石头剪刀布)。
Pistonball是butterfly系列里的合作型环境,画面里有一排活塞,目标是把球推到右侧目标线,每个活塞是一个agent。它的动作是离散的(活塞向上、向下、保持),观察是局部图像,适合理解“多个agent协作完成共同目标”的场景。运行时不依赖额外系统库,渲染也比较直观,很适合第一个Demo。
Rock-Paper-Scissors是classic系列里的经典博弈环境,有两个agent,动作空间是石头剪刀布三种。它非常简单,适合理解AEC API的执行流程。但画面不丰富,跑起来可能觉得不够酷。
我的建议是:理解API流程用石头剪刀布,视觉效果好、跑起来爽用Pistonball。下面我会用Pistonball作为主Demo,因为跑起来有图像,能直观看到多智能体协作的感觉。
4.2 Pistonball第一个Demo完整代码
直接上代码,这是Parallel API的写法,也是多智能体环境中训练时最常用的API。
from pettingzoo.butterfly import pistonball_v6 # 创建并行环境实例,指定渲染模式为human,这样能看到可视化窗口 env = pistonball_v6.parallel_env(n_pistons=20, render_mode="human") # 重置环境,返回初始观察和infos observations, infos = env.reset(seed=42) # 循环100步,每步所有agent同时选择一个动作 for step in range(100): # 所有存活的agent各采样一个随机动作 actions = {agent: env.action_space(agent).sample() for agent in env.agents} # 并行环境step接收一个动作字典,返回一组结果 observations, rewards, terminations, truncations, infos = env.step(actions) # 如果全部agent都终止了,就退出循环 if all(terminations.values()) or all(truncations.values()): print(f"第 {step} 步提前结束") break env.close()如果你是第一次跑,大概率能直接看到一个小窗口,里面有20个活塞在随机上下移动,球在活塞间弹来弹去,虽然随机策略下球大概率不会被推到右边,但整个环境已经跑起来了。
4.3 AEC API的写法:理解agent轮流行动的机制
再来看看AEC API的写法,用石头剪刀布举例:
from pettingzoo.classic import rock_paper_scissors_v3 env = rock_paper_scissors_v3.env(render_mode="human") observations, infos = env.reset(seed=42) for agent in env.agent_iter(): 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()AEC API的核心是env.agent_iter(),它返回一个迭代器,让环境里的agent按顺序轮流行动。每次迭代你需要调用env.last()获取当前agent的观察、奖励和状态,然后决定动作,再调用env.step(action)。
为什么要设计成这种轮流机制?因为很多博弈场景本质上是顺序决策的,比如下棋就是一人一步。如果强行并行化,就失去博弈的时序信息了。当然,AEC API也能模拟同时决策的场景,只是内部会依次处理每个agent的动作。
4.4 两种API怎么选
我把两张API的对比整理成表格,方便你根据场景选择:
| 对比维度 | AEC API | Parallel API |
|---|---|---|
| 决策方式 | agent轮流决策 | 所有agent同时决策 |
| 适用场景 | 博弈、回合制、顺序决策 | 协作、并行决策、训练友好 |
| 核心方法 | agent_iter, last, step | reset(并行), step(动作字典) |
| 训练集成 | 需要额外处理 | 直接支持RL框架 |
| 代码直观度 | 稍复杂 | 更直观 |
实际项目中,我大部分时间用Parallel API,因为它和深度学习训练循环的契合度更高。但如果你要研究博弈论场景或者需要模拟严格轮流行动的游戏,AEC API才是正确的打开方式。两个都值得掌握。
5. 核心API拆解:看懂这几个方法就能玩转所有环境
5.1 reset和step的返回值细节
不管用哪种API,PettingZoo的接口设计都和Gymnasium保持一致,但返回的数据结构变成了字典,key是agent的名字。
在Parallel API中,env.reset(seed=42)返回两个东西:
- observations:字典,key为agent名,value为该agent的观察
- infos:字典,key为agent名,value为附加信息
env.step(actions)接收一个动作字典,返回四个东西:
- observations:每个agent的新观察
- rewards:每个agent本轮获得的奖励
- terminations:每个agent是否因为进入终止状态而结束
- truncations:每个agent是否因为时间限制等原因被截断终止
- infos:附加信息
这里有一个很关键的细节:terminations和truncations虽然都表示“这个agent不玩了”,但它们代表不同的结束原因。termination表示环境本身达到了终止条件,比如游戏分出胜负;truncation表示超时或步数上限,比如游戏到了最大时间步被强制结束。在多智能体场景中,不同agent的termination状态可能不一样,比如对抗性游戏中一个agent赢了,另一个agent输了,两个agent都终止;但在合作任务中可能某个agent“挂掉”了,另一个还要继续行动。
5.2 观察空间和动作空间的获取
每个agent的动作空间可能不同,PettingZoo里你可以通过env.action_space(agent_name)获取某个agent的动作空间,同样的env.observation_space(agent_name)获取观察空间。
例如:
for agent in env.agents: print(f"Agent: {agent}") print(f" 动作空间: {env.action_space(agent)}") print(f" 观察空间: {env.observation_space(agent)}")Pistonball环境里,所有agent的动作空间是相同的Discrete(3),观察空间是Box类型的局部图像。但在某些异构多智能体环境中,不同agent的动作空间和观察空间可能完全不同,这时候字典的value就会不一样。
5.3 agents列表和可能的agent变化
env.agents是当前环境中所有agent的列表。需要注意的是,这个列表在环境运行过程中可能会变化。比如某个agent因为termination或truncation退出后,后续的step中它就不应该再有动作了。
在Parallel API中,你一般这么处理:
for agent in env.agents: if terminations[agent] or truncations[agent]: continue actions[agent] = policy(observations[agent])在AEC API中,env.agents属性在运行时也会动态调整,因为agent_iter就是根据当前存活的agent来迭代的。理解了这一点,你写训练循环时就不会因为“某个agent提前结束了但还在给动作”这种问题报错了。
6. 从Demo到训练:多智能体RL训练的基本循环
6.1 数据流视角的规范训练循环
跑通Demo只是第一步,真正用PettingZoo做训练时,你需要写一个完整的训练循环。我给出一个并行API下的规范写法,即使还没有接入任何深度学习模型,这个框架也是通用的:
import numpy as np from pettingzoo.butterfly import pistonball_v6 env = pistonball_v6.parallel_env(n_pistons=20, render_mode="rgb_array") observations, infos = env.reset(seed=0) max_cycles = 200 all_rewards = [] for step in range(max_cycles): actions = {} for agent in env.agents: # 这里替换成你的策略网络 actions[agent] = env.action_space(agent).sample() observations, rewards, terminations, truncations, infos = env.step(actions) all_rewards.append(sum(rewards.values())) if all(terminations.values()) or all(truncations.values()): break print(f"平均每步总奖励: {np.mean(all_rewards)}") env.close()注意我把render_mode换成了"rgb_array",这样环境不会弹出可视化窗口,适合在服务器上跑训练或者做实验记录。需要可视化的时候再改成"human",或者用env.render()返回图像数组进行保存。
6.2 和主流RL框架的对接思路
PettingZoo本身不带算法,但很多RL框架提供了对接支持。比如RLlib直接支持PettingZoo环境,你可以把pistonball_v6.parallel_env包装一下传给RLlib的配置。RLlib的MultiAgentEnv接口和PettingZoo的Parallel API非常接近,基本是直接映射的关系。
如果你用的是CleanRL这种偏轻量级的框架,通常需要自己写一个环境包装器,把PettingZoo的字典接口转成框架需要的格式。这个包装器核心就是把observations字典拼成一个batch张量,把actions按agent拆分并处理rewards。
我个人的建议是,如果你只是做实验验证,可以先写一个简单的numpy策略或者一个小的MLP策略,用PettingZoo跑通整个训练循环,再逐步引入更复杂的RL框架。这样出了问题你知道是环境的问题还是框架的问题。
7. 常见问题和排查技巧实录:菜鸟和老手都会踩的坑
7.1 安装阶段的高频问题
问题一:pip安装时网络超时或源太慢
这个在国内网络环境下很常见。解决办法是换用国内镜像源:
pip install pettingzoo -i https://pypi.tuna.tsinghua.edu.cn/simple如果你用的conda,也可以配置conda的镜像源。
问题二:pettingzoo[atari]安装失败
Atari环境依赖ale-py,有时候会和numpy版本冲突。如果你遇到ModuleNotFoundError: No module named 'ale_py',可以先单独安装:
pip install ale-py如果还是报错,看一下numpy版本,把numpy降到1.24.x或者升级到最新版试试。ale-py在不同平台上的二进制包情况不同,遇到问题多在GitHub的issue里搜一搜。
问题三:装了新版本PettingZoo,但环境名是旧的
这基本是老API兼容问题。网上有很多使用simple_spread_v2这样的旧环境名的教程,当前新版本中MPE环境名是simple_spread_v3。解决办法:去PettingZoo官网文档查当前版本对应的环境名列表,直接复制文档里的名字。
7.2 运行Demo时的报错解析
问题一:pygame.error: video system not initialized
这个报错常见于在Linux服务器上运行render_mode="human"的环境,因为服务器没有显示设备。解决办法:
- 改用
render_mode="rgb_array",不弹窗口 - 或者用
xvfb-run python your_demo.py在虚拟显示下运行
如果你确实需要远程看渲染效果,可以用render_mode="rgb_array"把每一帧图像保存成视频。
问题二:AssertionError或KeyError和agent相关
这类报错大多是因为在step时给了已经被terminated的agent一个动作。回到环境里检查一下env.agents列表是否和terminations字典一致,确保给动作的时候跳过已经结束的agent。
问题三:Demo能跑但结果看起来不符合预期
比如Pistonball跑了一会儿球没怎么动,这其实是正常的,因为随机策略本身不具备解决问题的能力。你可以先看看奖励值的变化,Pistonball的奖励设计是球往右移动时给所有agent正奖励,往左移动时给负奖励。如果总奖励往负的方向走,说明随机策略确实是在瞎搞。
7.3 排查工具箱:一套百试百灵的速查流程
我把多次调试中积累的排查经验整理成一个速查表,遇到问题可以按这个顺序排查:
| 排查步骤 | 检查内容 | 常见处理 |
|---|---|---|
| 1. Python版本 | 是否在3.9-3.11之间 | 更换虚拟环境Python版本 |
| 2. 依赖版本 | gymnasium、numpy版本是否冲突 | pip list查看,按需升级或降级 |
| 3. API版本 | 环境名后缀是否为v3/v4/v6 | 对照官网文档修改环境名 |
| 4. 渲染模式 | 是否在无显示环境用了human | 改用rgb_array |
| 5. agent状态 | step时是否考虑了terminated agent | 在动作生成前过滤已结束agent |
| 6. 环境自检 | 是否能用random策略跑通 | 先跑通random再接入算法 |
这套流程我基本每次排查都用,90%以上的问题都能定位到这几类原因。
7.4 几个我强烈建议你养成的习惯
第一次跑新环境前,先打印env.metadata看看有没有什么特殊要求,有些环境有render_fps之类的元信息,能帮你了解环境的节奏。
写训练代码时,把seed固定住,保证实验的可重复性。PettingZoo的env.reset(seed=42)只固定了环境随机种子,如果你在策略里还用了随机数,记得也要固定策略的随机种子。
最后一点,用env.close()释放环境资源。特别是在一个脚本里反复创建销毁多个环境实例时,不关闭可能会导致资源泄漏,长时间跑实验时内存占用会莫名上涨。
我第一次跑多智能体环境的时候,在API版本上卡了两天,后来沉下心来跑去读官方文档,才发现0.x到1.x之间的变化几乎是翻天覆地的。这段经历让我养成了一个习惯:用到任何第三方库,第一件事就是去官网看文档版本号,确认API是否发生了变化。PettingZoo这个库迭代很活跃,环境版本后缀一换就是一批新API,所以我建议你把它加入你的依赖管理清单里,定期关注更新公告。总之一句话,装环境这事靠5分钟就能跑通,但想把多智能体的实验做好,理解API设计背后的逻辑才是真正的功夫所在。