从零到一:用PettingZoo构建你的第一个多智能体竞技场

如果你对强化学习(Reinforcement Learning)已经有所涉猎,玩过OpenAI Gym里的“CartPole”或“MountainCar”,那么你可能会好奇:当环境里不止一个智能体时,世界会变成什么样?是多个机器人协同搬运一个箱子,还是几个AI在棋盘上激烈对弈?这正是多智能体强化学习(Multi-Agent Reinforcement Learning, MARL)令人着迷的领域。而PettingZoo,就是为你打开这扇大门的钥匙。

它不是一个简单的“多智能体版Gym”。想象一下,Gym为你提供了一个标准的跑步机,而PettingZoo则直接给了你一个设施齐全的体育馆,里面有篮球场、羽毛球场和游泳池,规则各异,参与者数量可变。对于研究者、算法工程师甚至是充满好奇心的学生来说,PettingZoo提供了一个标准化、易用且功能强大的沙盒,让你能快速搭建、测试并观察多个智能体如何在一个共享环境中学习、竞争或合作。今天,我们就抛开复杂的理论,直接上手,在几分钟内点燃你的第一个多智能体“烟花”。

1. 环境搭建与核心概念速览

在编写第一行代码之前,我们需要一个干净、隔离的工作空间。强烈建议使用虚拟环境,这能避免不同项目间的依赖冲突,是Python开发中的最佳实践。

# 创建并激活一个名为marl_venv的虚拟环境
python -m venv marl_venv

# 在Windows上激活
marl_venv\Scripts\activate
# 在macOS/Linux上激活
source marl_venv/bin/activate

激活后,你的命令行提示符前通常会显示环境名(marl_venv),这表示你已进入该沙箱。接下来,安装PettingZoo。由于其包含了多个不同主题的环境集,你可以选择全部安装或按需安装。对于初学者,我推荐先安装classic环境集,它包含了许多像井字棋、围棋等经典游戏,规则简单,便于理解。

# 安装基础库及经典游戏环境集
pip install pettingzoo[classic]

注意:[classic]是一种“额外依赖”的指定方式。PettingZoo采用模块化设计,butterflymagent等环境集可能需要额外的、更复杂的依赖(如特定的游戏引擎)。从classic入手最为平滑。

安装完成后,让我们先理解两个核心概念,这能让你后续的代码不再盲目:

  • AEC(Agent Environment Cycle)模型:这是PettingZoo的基石。与Gym中“环境一步,所有智能体同时行动”的模式不同,AEC模型是顺序步进的。在任何时刻,只有一个“当前智能体”能接收观察、做出行动。环境会严格管理智能体行动的轮转顺序。这种设计天然支持回合制游戏、智能体动态加入/退出(如游戏中角色“死亡”),并且能让奖励的归属更加清晰——你知道是哪个智能体的哪个动作导致了当前的奖励。
  • 关键对象与方法:你可以把env(环境对象)想象成一个裁判。它有几个最重要的属性:
    • agents: 一个列表,包含了当前环境中所有“存活”的智能体ID。
    • agent_selection: 一个指针,指向当前该谁行动的智能体ID。
    • last(): “裁判”告知当前智能体:“这是你看到的局面(observation),这是你上一步行动后得到的奖励(reward),你的回合是否结束了(done),以及一些额外信息(info)。”
    • step(action): 当前智能体提交它的行动action,“裁判”执行它,并自动将agent_selection指针移向下一个该行动的智能体。
    • reset(): 开始一局新的游戏,重置所有状态。

下面这个表格对比了单智能体(Gym)与多智能体(PettingZoo AEC)的核心交互逻辑差异:

交互维度OpenAI Gym (单智能体)PettingZoo AEC (多智能体)
行动模式智能体每步都行动每步仅一个指定智能体行动
状态管理单一stateobservation每个智能体有独立的observation
奖励反馈每步一个标量奖励每步为当前行动智能体提供奖励
循环控制while not done:for agent in env.agent_iter():
智能体集合固定为1动态列表env.agents,可变化

理解了这些,代码就不再是魔法咒语,而是你对这个多智能体世界下达的清晰指令。

2. 第一个交互式示例:井字棋(Tic-Tac-Toe)

让我们从一个最熟悉的游戏开始——井字棋。这是一个双人回合制游戏,完美契合AEC模型。我们将不涉及任何AI算法,仅仅是通过编写一个循环,让两个智能体(实际上由我们手动输入)完成一次对局。

# 1. 导入必要的库
from pettingzoo.classic import tictactoe_v3
import random

# 2. 创建环境
env = tictactoe_v3.env(render_mode="human")  # 使用“human”模式以便可视化

创建环境时,我们传入了render_mode="human"参数。这告诉环境我们需要图形化界面来渲染棋盘。PettingZoo的许多环境都支持多种渲染模式,例如"rgb_array"(返回像素数组,用于录制视频)或"ansi"(控制台文本输出)。

接下来,我们重置环境,开始一局新游戏,并进入主循环。

# 3. 重置环境,开始新对局
env.reset()

# 4. 主交互循环
for agent in env.agent_iter():
    # 4.1 获取当前智能体的状态
    observation, reward, done, info = env.last()

    # 如果当前智能体的回合已结束(赢了、输了、平了),它必须选择None作为行动
    if done:
        action = None
    else:
        # 4.2 为当前智能体选择行动(这里我们手动输入)
        # observation是一个9维向量,表示棋盘状态(0空,1己方,-1对方)
        print(f"\n当前玩家: {agent}")
        print(f"棋盘状态: {observation.reshape(3, 3)}")  # 重塑为3x3棋盘便于查看
        print(f"可选位置(0-8): {[i for i, val in enumerate(observation) if val == 0]}")

        # 这里简化:随机选择一个空位落子
        valid_moves = [i for i, val in enumerate(observation) if val == 0]
        action = random.choice(valid_moves) if valid_moves else None
        print(f"系统随机选择行动: {action}")

    # 4.3 执行行动,环境状态更新
    env.step(action)

    # 4.4 渲染当前棋盘(如果设置了render_mode)
    env.render()

# 5. 游戏结束
print("\n游戏结束!")

运行这段代码,你会看到一个井字棋窗口弹出,两个名为player_1player_2的智能体轮流随机落子,直到棋盘填满或一方获胜。控制台会输出每一步的行动信息。这个简单的循环for agent in env.agent_iter():是PettingZoo交互的核心范式,它保证了每个智能体都能在正确的时机被激活。

提示:env.last()返回的done针对当前智能体的。在多智能体环境中,一个智能体的done可能为True(例如它被淘汰了),但其他智能体还在继续游戏,整个环境并未结束。环境整体的结束由env.agent_iter()循环自然终止来判断(即所有智能体都done了)。

3. 探索更丰富的环境世界

PettingZoo的魅力在于其丰富的环境集合。classic只是冰山一角。每个环境集都针对不同类型的多智能体问题进行了设计。了解它们能帮助你为你的研究或项目选择最合适的“战场”。

  • Butterfly:专注于合作性的连续控制环境。例如cooperative_pong,两个 paddle 需要协作不让球掉落;knights_archers_zombies,骑士和弓箭手需要共同抵御僵尸。这些环境通常基于PyGame,视觉效果出色,适合研究协同策略。
  • MAgent:面向大规模智能体(数百至数千)的网格世界仿真。智能体在像素网格中移动、攻击、繁殖。它非常适合研究群体智能、涌现行为以及高效的并行算法。安装时需要pip install pettingzoo[magent],对计算资源要求较高。
  • MPE (Multi-Agent Particle Environment):由OpenAI提出的经典环境,包含一系列通信与协作场景。智能体是移动的粒子,可以互相观察、通信、推动。场景如simple_spread(协作搬运地标)、simple_reference(通过通信达成共识)。它是研究多智能体通信和信用分配的热门选择。
  • SISL:包含三个合作环境,如multiwalker(多个机器人协同运输货物)。这些环境通常物理模拟更复杂。

让我们快速体验一下MPE中的一个简单合作环境。首先安装它:

pip install pettingzoo[mpe]

然后运行一个简单的脚本,观察智能体行为:

from pettingzoo.mpe import simple_spread_v3
import numpy as np

# 创建环境,3个智能体,3个地标
env = simple_spread_v3.env(N=3, local_ratio=0.5, max_cycles=100, render_mode="human")
env.reset()

for agent in env.agent_iter(max_iter=1000): # 限制最大迭代步数
    obs, reward, done, info = env.last()
    if done:
        action = None
    else:
        # 使用一个简单的启发式策略:向最近的地标移动
        # obs中包含了智能体自身位置、速度、地标位置等信息
        # 这里为了演示,我们采取随机行动
        action = env.action_space(agent).sample()
    env.step(action)
    env.render()

你会看到三个彩色粒子(智能体)和三个彩色地标。智能体的目标是通过移动,使得每个地标都被一个智能体“覆盖”。虽然我们用了随机策略,但你已经能直观感受到多智能体协作任务的动态性。

4. 构建你的第一个智能策略:规则代理与随机代理对战

仅仅观察随机行动是不够的。让我们升级井字棋示例,实现一个简单的规则型代理(Rule-based Agent) 来对战随机代理。这能让你初步体验如何为智能体设计决策逻辑。

我们将创建一个函数rule_based_policy,它接收当前棋盘观察和智能体ID,返回一个行动。一个极其简单的规则是:优先占据中心,其次角落,最后边位。

def rule_based_policy(observation, agent_id):
    """
    一个简单的井字棋规则策略。
    observation: 9维数组,0为空,1为己方,-1为对方。
    agent_id: 字符串,如'player_1'。
    """
    board = observation
    my_symbol = 1  # 假设己方总是用1表示

    # 1. 如果有一步制胜的机会,就下那一步
    for i in range(9):
        if board[i] == 0: # 空位
            board_copy = board.copy()
            board_copy[i] = my_symbol
            if check_win(board_copy, my_symbol):
                return i

    # 2. 如果对方有一步制胜的机会,就堵住它
    opponent_symbol = -1
    for i in range(9):
        if board[i] == 0:
            board_copy = board.copy()
            board_copy[i] = opponent_symbol
            if check_win(board_copy, opponent_symbol):
                return i

    # 3. 优先选择中心(位置4)
    if board[4] == 0:
        return 4

    # 4. 其次选择角落(0,2,6,8)
    corners = [0, 2, 6, 8]
    random.shuffle(corners) # 随机化顺序避免固定模式
    for corner in corners:
        if board[corner] == 0:
            return corner

    # 5. 最后选择边位(1,3,5,7)
    edges = [1, 3, 5, 7]
    random.shuffle(edges)
    for edge in edges:
        if board[edge] == 0:
            return edge

    # 6. 没有空位(平局),返回None
    return None

def check_win(board, symbol):
    """检查给定符号是否在棋盘上获胜"""
    win_lines = [
        [0,1,2], [3,4,5], [6,7,8], # 横
        [0,3,6], [1,4,7], [2,5,8], # 竖
        [0,4,8], [2,4,6]            # 斜
    ]
    for line in win_lines:
        if all(board[pos] == symbol for pos in line):
            return True
    return False

现在,修改主循环,让player_1使用规则策略,player_2保持随机策略:

env = tictactoe_v3.env(render_mode="human")
env.reset()

for agent in env.agent_iter():
    observation, reward, done, info = env.last()

    if done:
        action = None
    else:
        if agent == 'player_1':  # 规则代理
            action = rule_based_policy(observation, agent)
        else:  # 随机代理
            valid_moves = [i for i, val in enumerate(observation) if val == 0]
            action = random.choice(valid_moves) if valid_moves else None
    env.step(action)
    env.render()

多次运行这个脚本,你会发现player_1(规则代理)的胜率显著高于player_2。这就是策略的力量。虽然这个规则很简单,但它已经包含了多智能体决策中的两个关键思想:利用机会防御威胁

5. 连接强化学习算法:与RLlib的集成示例

PettingZoo的最终目的是服务于多智能体强化学习研究。它能够与主流RL库(如RLlib、Stable-Baselines3)无缝集成。这里以Ray RLlib为例,展示如何将PettingZoo环境包装成RLlib可训练的格式。这超出了“5分钟上手”的范畴,但指明了下一步的方向。

首先,确保安装了RLlib:

pip install "ray[rllib]"

PettingZoo环境需要通过一个简单的包装器来适配RLlib的MultiAgentEnv接口。RLlib提供了一个现成的包装函数。

import ray
from ray import tune
from ray.rllib.env import PettingZooEnv
from pettingzoo.classic import leduc_holdem_v4

# 1. 将PettingZoo环境包装成RLlib环境
def env_creator(config):
    env = leduc_holdem_v4.env()
    return env

# 2. 注册环境
ray.init()
tune.register_env("my_leduc", lambda config: PettingZooEnv(env_creator(config)))

# 3. 配置多智能体PPO算法
config = {
    "env": "my_leduc",
    "multiagent": {
        "policies": {
            "player_0": (None, env.observation_space(env.agents[0]), env.action_space(env.agents[0]), {}),
            "player_1": (None, env.observation_space(env.agents[1]), env.action_space(env.agents[1]), {}),
        },
        "policy_mapping_fn": lambda agent_id: agent_id, # 每个智能体对应一个策略
    },
    "framework": "torch",
    "num_workers": 1,
}

# 4. 开始训练(这里仅作演示,实际训练需要大量计算)
# analysis = tune.run("PPO", config=config, stop={"timesteps_total": 100000})

这段代码为Leduc Hold‘em扑克游戏(一个不完全信息博弈)设置了一个双智能体训练任务。RLlib会为每个智能体(player_0, player_1)分别学习一个策略网络。通过这种方式,你可以研究智能体如何在对抗中进化出复杂的博弈策略。

6. 调试与性能优化实战心得

在实际使用中,你可能会遇到一些坑。这里分享几个我踩过之后总结出来的经验:

  • 智能体迭代顺序env.agent_iter()返回的顺序是环境定义好的。有些环境是固定的(如棋类游戏轮流),有些可能是随机的。你的策略不应该依赖隐式的顺序假设。始终通过env.agent_selection来获取当前该行动的智能体。
  • 处理智能体“死亡”:在last()返回done=True后,必须对该智能体调用step(None)。这是AEC API的强制要求,用于通知环境该智能体已终止。忘记这一步会导致程序卡死。
  • 渲染性能:如果你在服务器(无图形界面)上运行,或者需要高性能训练,请将render_mode设置为None(默认)或"rgb_array""human"模式在远程SSH会话中可能无法工作,且会拖慢速度。
  • 自定义环境:PettingZoo的API设计清晰,使得自定义新环境变得相对直接。你需要实现的核心方法包括reset, step, observe, last等,并确保遵守AEC循环。从修改一个现有简单环境开始,是学习自定义环境的最佳途径。

多智能体的世界比单智能体复杂得多,奖励分配、信用归因、非平稳性等都是挑战。但PettingZoo通过一个设计良好的API,将这些复杂性封装起来,让你能专注于智能体策略本身。从今天这个井字棋的随机对局开始,试着去修改策略,尝试不同的环境,甚至将它与你熟悉的RL算法结合。你会发现,观察多个AI在同一个世界里学习、竞争、合作,其乐趣和洞察远大于单智能体的独舞。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐