PyTorch强化学习实战(35)------多智能体强化学习
0. 前言
在本节中,我们将介绍多智能体强化学习 (multi-agent reinforcement learning, MARL),这是强化学习 (Reinforcement Learning, RL)和深度强化学习的一个相对较新的方向,涉及多个智能体在环境中进行交互的情况。在现实生活中,这类问题出现在宽带通信网络、物联网等场景中。
在本节中,我们将简要介绍 MARL,并在简单环境中进行一些实验。在实验中,我们将采用一种简单的方法,让智能体共享我们正在优化的策略,但观察将从每个智能体的视角出发,并包含其他智能体位置的信息。通过这种简化,我们的强化学习方法将保持不变,只需对环境进行预处理并处理多个智能体的存在。
1. 多智能体强化学习简介
多智能体设定是经典强化学习 (Reinforcement Learning, RL) 模型的自然延伸。在传统RL框架中,单个智能体通过观察、奖励和动作与环境交互。但在现实场景中,许多问题往往涉及多个智能体共同参与环境互动。具体示例如下:
- 象棋对弈:程序需击败对手
- 市场模拟:如产品广告或价格调整,我们的动作可能引发其他参与者的反制
- 多人游戏,如
Dota 2或StarCraft II,当智能体需要控制多个单位,智能体需操控多个单位与其他玩家对抗(此时同一玩家控制的单位也可能协作达成目标)
若其他智能体不受我们控制,可将其视为环境的一部分,仍采用单智能体 RL 模型。我们知道,通过自我对弈训练是一种强大技术,即使环境设置简单也能训练出优秀策略。但在某些情况下,这样的方式过于局限,并不完全符合我们的需求。
研究表明,一组简单智能体可能展现出远超预期的复杂协作行为。例如 OpenAI 在论文《Emergent tool use from multi-agent autocurricula》中描述的"捉迷藏"游戏:智能体通过协作发展出日益复杂的攻防策略(如用可用物体建造围栏、利用蹦床捕捉墙后对手)。
根据智能体交互模式,可将其分为两类:
- 竞争型:两个及以上智能体通过对弈最大化自身收益,如象棋、双陆棋或
Atari乒乓等双人游戏 - 协作型:智能体群体需通力合作达成目标
属于单一类别的事例比比皆是,但最引人入胜且贴近现实的场景往往融合了两种行为模式。现实生活远比模型复杂------从允许结盟的棋盘游戏到假定完全协作的现代企业,现实世界远比理论复杂。从理论角度来看,博弈论为两种交互形式奠定了扎实理论基础,minimax 算法是博弈论中的一个著名成果。MARL 作为新兴领域正持续发展,值得保持关注。
2. 实验环境
在开始第一个多智能体强化学习 (multi-agent reinforcement learning, MARL) 示例之前,我们先来了解可用的实验环境。目前 MARL 的可选环境相对有限:Gymnasium原生环境仅支持单智能体。对于 Atari Pong,随存在补丁可以将其切换为双人模式,但属于非标准特例。
DeepMind 与 Blizzard 合作,开源了 StarCraft II环境,为实验提供了极具挑战性的平台,但对初学者而言复杂度较高。相比之下,Geek.AI 开发的 MAgent 环境更为适合------它依赖简洁、运行高效,能模拟多种多智能体场景。虽然未提供 Gym 兼容 API,但我们可以自行封装。
如果对 MARL 感兴趣,还可以查看 Farama Foundation 的PettingZoo 项目,其包含更多环境与统一交互 API,但本节仅专注于 MAgent 环境。
2.1 MAgent 简介
MAgent 的核心是一个二维网格世界仿真系统。智能体可执行以下操作:
- 基于感知范围观察周边环境
- 在限定距离内移动
- 攻击周围其他智能体
环境中可存在具有不同特性和交互参数的智能体群体。以我们将要使用的"捕食者-猎物"模型为例:"老虎"通过捕猎"鹿"获取奖励。在环境配置中,可定义群体的感知范围、移动能力、攻击距离、初始生命值、行动消耗等参数。除智能体外,环境还可设置不可穿越的墙体障碍。
MAgent 的优势在于卓越的可扩展性------其底层采用 C++ 实现,仅暴露 Python 接口。这种架构使得环境能够同时处理数千智能体的观测与动作计算。
2.2 安装 MAgent
与许多开源项目类似,MAgent 原始版本曾一度停止维护。但 Farama Foundation 复刻了原代码库并进行持续维护,保留了大部分核心功能,命名为 MAgent2,代码库地址为:https://github.com/Farama-Foundation/magent2。安装 MAgent2 需执行以下命令:
shell
$ pip install magent2==0.3.3
2.3 配置随机环境
为快速理解 MAgent 的 API 逻辑,我们实现了一个包含"老虎"和"鹿"智能体的简单环境,两组智能体均采用随机策略运行。虽然从强化学习角度看缺乏挑战性,但能帮助我们快速掌握 API 用法以便实现 Gym 环境封装。代码位于 forest_random.py,接下来进行详细解析。
(1) 首先从 data.py 中定义的 ForestEnv 类开始,该类继承自 magent_parallel_env,这是 MAgent 环境的基类:
python
class ForestEnv(magent_parallel_env, EzPickle):
metadata = {
"render_modes": ["human", "rgb_array"],
"name": "forest_v4",
"render_fps": 5,
}
该类模拟了 Gym API,但并非完全兼容,因此后续代码中需要处理这个问题。
(2) 在构造函数中,实例化 GridWorld 类,该类作为底层 MAgent C++ 库 API 的 Python 适配器:
python
def __init__(self, map_size: int = MAP_SIZE, max_cycles: int = MAX_CYCLES,
extra_features: bool = False, render_mode: tt.Optional[str] = None,
seed: tt.Optional[int] = None, count_walls: int = COUNT_WALLS,
count_deer: int = COUNT_DEER, count_tigers: int = COUNT_TIGERS):
EzPickle.__init__(self, map_size, max_cycles, extra_features, render_mode, seed)
env = GridWorld(self.get_config(map_size), map_size=map_size)
handles = env.get_handles()
self.count_walls = count_walls
self.count_deer = count_deer
self.count_tigers = count_tigers
names = ["deer", "tiger"]
super().__init__(env, handles, names, map_size, max_cycles, [-1, 1],
False, extra_features, render_mode)
(3) GridWorld 类的配置由 get_config 函数返回的 Config 实例完成:
python
@classmethod
def get_config(cls, map_size: int):
# Standard forest config, but deer get reward after every step
cfg = forest_config(map_size)
cfg.agent_type_dict["deer"]["step_reward"] = 1
return cfg
该函数使用从 magent.builtin.config.forest 包导入的 forest_config 函数,并对配置进行调整,为鹿群每步行动添加奖励。当我们开始训练鹿模型时这一点很重要,每步 1 分的奖励将激励智能体延长生存时间。
其余配置定义了环境的诸多细节,包括:
- 环境中的智能体组数量:本例中有"鹿"和"虎"两组
- 各组的属性:视野范围(如鹿只能看到
1格距离,虎能看到4格)、攻击能力与射程、初始生命值、伤害恢复速度等可定制参数 - 攻击交互规则:具有高度灵活性,例如可建模捕食者需成对狩猎的场景。当前设置中,任何虎都可无限制攻击任何鹿
(4) ForestEnv 类的最后一个函数 generate_map 负责在地图上随机生成墙壁、鹿和虎的位置:
python
def generate_map(self):
env, map_size = self.env, self.map_size
handles = env.get_handles()
env.add_walls(method="random", n=self.count_walls)
env.add_agents(handles[0], method="random", n=self.count_deer)
env.add_agents(handles[1], method="random", n=self.count_tigers)
(5) 查看 forest_random.py 的源代码。首先,导入 lib.data 包和来自 Gymnasium 的 VideoRecorder 类:
python
from gymnasium.wrappers.monitoring.video_recorder import VideoRecorder
import data
from PIL import Image
import pathlib
import numpy as np
RENDER_DIR = "render"
我们已经学习了如何使用RecordVideo 包装器自动捕获环境观察值,但由于 MAgent 环境的返回值不同(所有方法都一次性返回所有智能体的字典而非单个值),这种方法不可行。为此,我们将使用 VideoRecorder 类来录制视频并写入 RENDER_DIR 目录。
(6) 首先创建 ForestEnv 实例和视频录制器。环境对象包含 agents 属性,该属性保存环境中所有智能体的字符串标识符。在本例中,它将是一个包含类似 deer_12 或 tiger_3 值的列表。默认配置下,64×64 地图上会有 204 只鹿和 40 只老虎,因此 env.agents 列表包含 244 项:
python
if __name__ == "__main__":
env = data.ForestEnv(render_mode="rgb_array")
recorder = VideoRecorder(env, RENDER_DIR + "/forest-random.mp4")
sum_rewards = {agent_id: 0.0 for agent_id in env.agents}
sum_steps = {agent_id: 0 for agent_id in env.agents}
(7) 我们使用 reset() 方法重置环境,但现在它返回一个值(而非 Gymnasium API 中的两个值)。返回值是以智能体 ID 为键、观察张量为值的字典:
python
obs = env.reset()
recorder.capture_frame()
assert isinstance(obs, dict)
print(f"tiger_0: obs {obs['tiger_0'].shape}, act: {env.action_space('tiger_0')}")
print(f"deer_0: obs {obs['deer_0'].shape}, act: {env.action_space('deer_0')}\n")
step = 0
save_render(env.render(), pathlib.Path(RENDER_DIR), step)
上述代码生成以下输出:
shell
tiger_0: obs (9, 9, 5), act: Discrete(9)
deer_0: obs (3, 3, 5), act: Discrete(5)
动作空间包含鹿的五个互斥动作(四个方向+"静止"动作)。老虎具备相同移动能力,但额外拥有四个方向的攻击能力。
观察值方面,每只老虎获得一个 9×9 矩阵,包含五个信息平面。鹿的视野较短,其观察范围仅为 3×3。观察矩阵始终以当前智能体为中心,显示其周围网格状态。五个信息平面分别为:
- 障碍:单元格存在墙壁为
1,反之为0 - 群体
1(己方群体):单元格存在同组智能体为1,反之为0 - 群体
1(己方)生命值:单元格内同组成员的相对生命值 - 群体
2(敌方群体):单元格存在敌对智能体为1,反之为0 - 群体
2(敌方生命值):敌方相对生命值(无目标时为0)
若配置更多群体,观察张量将包含额外平面。此外,MAgent 支持"迷你地图"功能,可显示各群体智能体的全局位置。本节禁用此功能,但可自行实验其对训练的影响。未开启时,智能体仅感知有限范围内环境;开启后则能获得更全局的视野。
群体 1 和 2 是相对于当前智能体定义的:因此鹿的第二个平面显示其他鹿的信息,而老虎的该平面显示其他老虎。这种设计使观察值具有群体独立性,便于需要时为不同群体训练统一策略。
观察值的可选部分还包括"额外特征",含智能体 ID、上一步动作、上一轮奖励及归一化位置等具体信息。这些功能虽在 MAgent 源码中有详细定义,但本节中不予采用。
(8) 我们通过循环持续执行操作,直到环境中无存活智能体。每次迭代中,为所有智能体采样随机动作并在环境中执行:
python
while env.agents:
actions = {agent_id: env.action_space(agent_id).sample() for agent_id in env.agents}
all_obs, all_rewards, all_dones, all_trunc, all_info = env.step(actions)
recorder.capture_frame()
env.step() 函数返回的所有值都是以 agent_id 为键的字典。MAgent 环境的另一个重要特性是智能体集合具有动态性:智能体可能从环境中消失(例如死亡时)。在我们的"森林"环境中,老虎每步会损失 0.1 点生命值(捕食鹿后可恢复),而鹿仅在遭受攻击时损失生命值,每步会自动恢复(可能通过食草实现)。
当智能体死亡(老虎饿死或鹿被猎杀)时,all_dones 字典中对应条目将设为 True,下一次迭代时该智能体会从所有字典和 env.agents 列表中移除。因此单个智能体死亡后,整个回合仍会继续,训练时需考虑这一特性。
上述示例中的循环会持续执行直到没有存活智能体。由于老虎和鹿都采取随机行为(且老虎每步流失生命值),很可能所有老虎都会饿死,存活下来的鹿将无限期生存。但环境配置会在没有老虎时自动清除所有鹿,因此程序通常在 30-40 步后结束。
(9) 在循环结束时,我们统计智能体获得的总奖励及其存活步数:
python
for agent_id, r in all_rewards.items():
sum_rewards[agent_id] += r
sum_steps[agent_id] += 1
step += 1
(10) 循环结束后,我们展示奖励排名前 20 的智能体:
python
final_rewards = list(sum_rewards.items())
final_rewards.sort(key=lambda p: p[1], reverse=True)
for agent_id, r in final_rewards[:20]:
print(f"{agent_id}: got {r:.2f} in {sum_steps[agent_id]} steps")
recorder.close()
输出如下所示:

程序最后会保存本回合的视频记录。下图展示了两个不同状态:游戏开始时和临近结束时。蓝点代表老虎,红点代表鹿,灰点代表墙壁。攻击方向通过黑色小箭头标示。

小结
在本节中,我们初步探讨了多智能体强化学习领域,其在交易模拟、通信网络等场景具有广泛应用前景。我们可以基于 MAgent 或 PySC2 等环境继续探索更多可能性。
系列链接
PyTorch强化学习实战(1)------强化学习(Reinforcement Learning,RL)详解
PyTorch强化学习实战(2)------强化学习环境库Gymnasium
PyTorch强化学习实战(3)------Gymnasium API扩展功能
PyTorch强化学习实战(4)------PyTorch基础
PyTorch强化学习实战(5)------PyTorch Ignite 事件驱动机制与实践
PyTorch强化学习实战(6)------交叉熵方法详解与实现
PyTorch强化学习实战(7)------表格学习与贝尔曼方程
PyTorch强化学习实战(8)------Q学习详解与实现
PyTorch强化学习实战(10)------强化学习高级组件
PyTorch强化学习实战(11)------N步DQN(N-step DQN)
PyTorch强化学习实战(12)------Double DQN(DDQN)
PyTorch强化学习实战(13)------噪声网络(NoisyNet-DQN)
PyTorch强化学习实战(14)------优先经验回放机制
PyTorch强化学习实战(15)------Dueling DQN
PyTorch强化学习实战(16)------Categorical DQN
PyTorch强化学习实战(17)------强化学习训练加速
PyTorch强化学习实战(18)------基于DQN处理股票交易问题
PyTorch强化学习实战(20)------优势演员-评论家(Advantage Actor-Critic, A2C)
PyTorch强化学习实战(21)------异步优势演员-评论家(Asynchronous Advantage Actor-Critic, A3C)
PyTorch强化学习实战(22)------将强化学习应用于TextWorld互动小说游戏
PyTorch强化学习实战(23)------强化学习在网页导航中的应用
PyTorch强化学习实战(24)------连续动作空间中的强化学习
PyTorch强化学习实战(25)------深度确定性策略梯度(DDPG)
PyTorch强化学习实战(26)------提升随机策略梯度稳定性
PyTorch强化学习实战(27)------进化策略在强化学习中的应用
PyTorch强化学习实战(28)------遗传算法在强化学习中的应用
PyTorch强化学习实战(29)------深入解析强化学习中的探索策略
PyTorch强化学习实战(30)------基于人类反馈的强化学习(RLHF)
PyTorch强化学习实战(31)------AlphaGo Zero详解与实现