1. 项目概述:从零理解多智能体强化学习仿真平台
如果你对强化学习(Reinforcement Learning, RL)感兴趣,并且已经玩腻了OpenAI Gym里的Atari游戏或MuJoCo里的机械臂,想看看几十个、上百个智能体在同一个环境里互动、竞争、合作会产生怎样复杂而有趣的行为,那么你很可能已经听说过或者正在寻找一个合适的工具。今天要聊的,就是一个在特定圈子里颇有名气的开源项目:MAgent。
MAgent,顾名思义,是“Multi-Agent”的缩写,它是一个专门为多智能体强化学习研究设计的高性能仿真平台。我第一次接触它,是在尝试复现一篇关于群体智能(Swarm Intelligence)的顶会论文时,作者在附录里轻描淡写地提了一句“实验基于MAgent平台构建”。当时市面上主流的选择要么是过于笨重(如基于游戏引擎的Unity ML-Agents),要么是功能单一(如一些简单的网格世界模拟器),而MAgent的出现,恰好填补了一个空白:它足够轻量、性能极高(支持成千上万个智能体同时模拟),并且提供了从环境构建、智能体定义到训练、可视化的完整工具链。
简单来说,MAgent能帮你快速搭建一个“微观世界”。在这个世界里,你可以定义不同的“物种”(智能体类型),赋予它们观察、行动和奖励规则,然后观察它们如何演化。无论是研究捕食者-猎物模型中的生态平衡,还是模拟交通流中车辆的协同与博弈,亦或是探索多机器人编队控制的算法,MAgent都能提供一个可控、可复现、且计算高效的沙盒。对于研究者、算法工程师乃至对复杂系统感兴趣的学生来说,它是一个极具价值的“生产力工具”。
2. 核心架构与设计哲学拆解
2.1 为什么是“高性能”仿真?
多智能体仿真的核心挑战在于计算复杂度。假设一个环境中有N个智能体,每个智能体每一步都需要根据其局部观察(可能包含周围其他智能体的信息)做出决策。朴素实现的复杂度很容易达到O(N²)甚至更高,因为每个智能体都需要感知其他所有智能体。当N增长到几百上千时,仿真速度会急剧下降,使得大规模实验变得不切实际。
MAgent的高性能秘诀在于其底层采用C++编写核心仿真逻辑,并通过Python接口暴露给用户。这种设计带来了两个直接好处:
- 计算效率:C++负责处理密集的网格世界更新、碰撞检测、视野计算等,速度远超纯Python实现。
- 易用性:用户可以用熟悉的Python来定义智能体策略、设计奖励函数、配置训练流程,无需关心底层细节。
更关键的是,MAgent在环境状态管理和智能体交互计算上做了大量优化。例如,它采用了空间哈希(Spatial Hashing)或网格分区(Grid Partitioning)技术来管理智能体的空间位置。当一个智能体需要获取其视野范围内的其他智能体时,系统无需遍历全场所有智能体,而是快速查询其所在及相邻的网格单元,将计算复杂度从O(N)降低到接近O(1)。这对于大规模群体模拟至关重要。
2.2 环境、智能体与大脑:核心三要素
MAgent的抽象模型非常清晰,主要由三个核心部分组成:
环境(Environment):这是一个二维的网格世界(Grid World)。每个格子可以放置墙、智能体或者为空。环境负责维护世界的物理(或规则)状态:执行智能体的动作(如移动、攻击),处理智能体间的交互(如碰撞、攻击命中),并计算每一步所有智能体获得的奖励和新的观察。环境是仿真的“舞台”和“裁判”。
智能体(Agent):智能体是环境中的演员。每个智能体属于一个特定的“种群”(Group),拥有生命值、攻击力、视野范围等属性。智能体在每一步从环境接收到一个局部观察(例如,以自身为中心的一个矩形区域内的格子信息),然后必须输出一个动作(如朝某个方向移动、攻击某个方向、或者什么都不做)。智能体本身没有决策能力,它只是一个执行单元。
大脑(Brain):这才是智能体的“决策中心”。在MAgent的语境下,“大脑”通常指代强化学习策略网络。一个“大脑”对象可以控制多个同种群的智能体。环境将一群智能体的观察(一个观察数组)喂给对应的大脑,大脑输出这群智能体的动作(一个动作数组)。这种“集中式训练,分布式执行”的范式,是处理多智能体问题的常见手段。你可以为不同种群的智能体配置不同的大脑(异构智能体),也可以让同一个大脑控制所有智能体(同构智能体)。
这种分离设计(环境-智能体-大脑)使得MAgent非常灵活。你可以轻松替换“大脑”部分,接入TensorFlow、PyTorch等任何你喜欢的深度学习框架来训练策略,而无需修改环境本身。
3. 从零搭建你的第一个MAgent实验
3.1 环境安装与基础配置
MAgent的安装相对 straightforward。由于核心是C++库,你需要确保系统有合适的编译环境。
# 1. 克隆仓库 git clone https://github.com/geek-ai/MAgent.git cd MAgent # 2. 编译C++后端 # 在Linux/macOS下 bash build.sh # 在Windows下,建议使用WSL或MSYS2环境,同样执行bash build.sh # 3. 安装Python接口 pip install -e .安装完成后,一个简单的验证方式是运行其内置的示例脚本,比如一个简单的捕食者-猎物模型:
python examples/test_battle.py如果能看到一个图形界面窗口,里面有许多红色和蓝色的点(分别代表两种智能体)在移动、交互,那么安装就成功了。这个可视化界面由pygame驱动,如果报错,可能需要单独安装pip install pygame。
注意:编译过程可能会因为缺少依赖(如CMake版本过低、缺少OpenMP支持)而失败。一个常见的坑是macOS系统自带的Clang编译器对OpenMP支持不完善。如果遇到
-fopenmp相关的错误,可以尝试通过Homebrew安装libomp,并在build.sh中指定其路径,或者直接注释掉CMakeLists.txt中的OpenMP选项(这会影响多线程性能,仅作测试)。
3.2 理解并自定义一个网格世界
MAgent提供了几个预设环境,但理解如何从零构建一个环境是掌握它的关键。我们以创建一个简单的“资源收集”环境为例:两种智能体,收集者(Collector)和强盗(Robber)。收集者收集散布在地图上的资源点,强盗可以攻击收集者并抢夺其资源。
首先,我们需要定义这个网格世界的规则,这通过继承magent.Environment类并重写相关方法来实现,但更常用的方式是使用MAgent提供的GridWorld接口进行配置。
import magent def my_resource_world(map_size=50): # 创建一个GridWorld实例 env = magent.GridWorld(map_size, map_size) # 1. 定义智能体种群(Group) # 每个Group有唯一的ID和一系列属性 group_collector = env.register_group( name="collectors", # 设置智能体属性 attr={ "size": 1, # 智能体占据的格子大小(半径) "view_range": magent.agent.RangeCircle(5), # 圆形视野,半径5 "speed": 1.0, # 移动速度(格子/步) "hp": 10, # 生命值 "attack_range": 0, # 攻击范围,收集者不能攻击 "damage": 0, # 攻击力 "step_recover": 0.1, # 每步生命恢复 } ) group_robber = env.register_group( name="robbers", attr={ "size": 1, "view_range": magent.agent.RangeCircle(6), # 强盗视野稍远 "speed": 1.2, # 强盗移动更快 "hp": 8, # 生命值较低 "attack_range": magent.agent.RangeCircle(1.5), # 近战攻击范围 "damage": 2, # 攻击力 "step_recover": 0, } ) # 2. 定义智能体的动作空间 # 添加移动动作:上下左右四个方向 env.add_agents(group_collector, method="custom", pos=(10, 10)) env.add_agents(group_robber, method="custom", pos=(40, 40)) # 3. 定义奖励规则(Reward Function) # 这是强化学习的灵魂,需要在后续与大脑训练结合定义 # 例如:收集者收集资源+1,被攻击-1;强盗抢夺资源+2,攻击消耗-0.1 # MAgent允许通过回调函数或内置机制定义奖励,通常在大脑训练循环中计算。 # 4. 添加静态障碍物和资源点 # 生成一些墙 for _ in range(20): x, y = np.random.randint(0, map_size, size=2) env.add_walls(pos=(x, y), shape=(2, 2)) # 添加2x2的墙块 # 生成资源点(可以用一种特殊的、被动的智能体表示,或者用地图图层) # 这里简化处理,后续在每一步重置环境时随机生成。 return env, [group_collector, group_robber]上面的代码勾勒出了一个自定义环境的基本骨架。关键点在于register_group,它定义了智能体的“物种特性”。view_range、attack_range等参数接受RangeCircle或RangeSquare对象,这决定了智能体感知和作用的形状。
实操心得:
view_range的设置需要谨慎。视野太大,观察向量维度会很高,增加神经网络输入层的负担,拖慢训练;视野太小,智能体可能变成“瞎子”,无法进行有效的策略学习。通常需要根据地图大小和智能体密度进行试验。一个经验法则是,让智能体的视野至少能覆盖其可能在一个Episode内移动范围的一小部分。
3.3 构建智能体的“大脑”:策略网络与训练循环
环境搭建好了,智能体也放上去了,接下来就需要给它们装上“大脑”——即决策模型。MAgent环境本身不包含训练算法,它只负责模拟。我们需要自己实现或调用现有的RL库来训练策略。
这里以使用PyTorch和简单的Policy Gradient方法为例,展示如何为“收集者”种群训练一个大脑。我们假设动作空间是离散的:{上,下,左,右,停留}。
import torch import torch.nn as nn import torch.optim as optim import numpy as np class CollectorBrain(nn.Module): """收集者智能体的策略网络""" def __init__(self, obs_dim, act_dim, hidden_size=128): super().__init__() # 观察空间:视野范围内每个格子的特征(如类型、血量等)展平后的向量 self.net = nn.Sequential( nn.Linear(obs_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, act_dim) ) def forward(self, obs): # obs: [batch_size, obs_dim] logits = self.net(obs) # 输出每个动作的未归一化分数 return logits def act(self, obs): logits = self.forward(obs) probs = torch.softmax(logits, dim=-1) # 转换为概率分布 dist = torch.distributions.Categorical(probs) action = dist.sample() log_prob = dist.log_prob(action) return action.item(), log_prob # 训练循环伪代码 def train_episode(env, brain, optimizer, group_id): obs = env.reset() done = False log_probs = [] rewards = [] while not done: # 1. 获取当前种群所有智能体的观察 agent_obs = env.get_observations(group_id) # 形状: [num_agents, obs_dim] agent_obs_tensor = torch.FloatTensor(agent_obs) # 2. 大脑决策 actions = [] batch_log_probs = [] for single_obs in agent_obs_tensor: act, log_prob = brain.act(single_obs.unsqueeze(0)) actions.append(act) batch_log_probs.append(log_prob) log_probs.extend(batch_log_probs) # 3. 环境执行动作 env.set_actions(group_id, actions) env.step() # 4. 获取奖励(这里需要根据你的奖励规则计算) # 例如,从环境读取全局状态,计算每个收集者的奖励(如靠近资源+,被攻击-) step_rewards = calculate_collector_rewards(env, group_id) rewards.append(step_rewards) done = env.is_done() # 5. 策略梯度更新 (REINFORCE算法) # 将整个episode的奖励进行折扣和归一化 returns = compute_returns(rewards) policy_loss = [] for log_prob, R in zip(log_probs, returns): policy_loss.append(-log_prob * R) # 梯度上升 optimizer.zero_grad() total_loss = torch.stack(policy_loss).sum() total_loss.backward() optimizer.step() return total_loss.item()这是一个高度简化的训练框架。在实际项目中,你会用到更成熟的算法(如PPO、DQN),处理多智能体信用分配问题(Credit Assignment),并可能采用集中式批评家(Centralized Critic)等架构。MAgent的env.get_observations(group_id)和env.set_actions(group_id, actions)是连接环境和大脑的关键API。
注意事项:多智能体强化学习(MARL)的训练稳定性远差于单智能体。同一个种群内的智能体共享策略参数,但它们的经验是并行的。直接使用上述简单PG算法,由于智能体数量多、经验方差大,很容易导致训练崩溃。务必引入基线(Baseline),如一个价值函数网络(Critic),来降低方差。此外,经验回放(Experience Replay)在多智能体场景下需要小心处理,因为旧的经验可能来自已经过时的策略(其他智能体也在学习),这被称为“非平稳性”(Non-stationarity)问题。
4. 深入核心:观察、动作与奖励函数的设计
4.1 观察空间(Observation Space)的编码艺术
智能体看到的“世界”是什么样子的?在MAgent中,观察通常是智能体视野范围内网格的“特征图”。环境会将视野内的每个格子编码为一个特征向量,然后将所有这些向量拼接起来,形成智能体的观察输入。
例如,一个格子可能包含以下信息:
- 格子类型:空、墙、资源点、智能体A、智能体B...
- 如果格子上有智能体:该智能体的血量、所属种群、攻击力等。
- 智能体自身的状态:自身血量、携带资源量等。
MAgent提供了ObservationBuilder来自定义观察的编码方式。默认的编码可能包含过多或过少的信息。一个良好的观察设计应遵循以下原则:
- 相关性:只包含对决策有用的信息。例如,在“资源收集”游戏中,强盗不需要知道资源点的精确剩余量,只需要知道其位置。
- 不变性:观察应该对无关变换具有不变性。例如,旋转视野不应该改变策略(除非方向本身是动作的一部分)。有时需要对观察进行规范化,如以智能体自身为坐标系原点。
- 紧凑性:在保证信息量的前提下,尽量降低观察维度,以加速训练。
你可以通过继承magent.agent.ObservationBuilder类来定制观察。例如,只返回视野内其他智能体的相对位置和类型:
class SimpleObsBuilder(magent.agent.ObservationBuilder): def build(self, env, handle): # handle: 智能体种群的ID agents = env.get_agents(handle) obs_list = [] for agent in agents: pos = agent.get_pos() # 获取视野范围内的所有实体 view_field = env.get_view(agent, range=5) # 简化编码:只记录相对位置和类型 simple_view = [] for entity in view_field: dx, dy = entity.pos - pos simple_view.append([dx, dy, entity.type]) obs_list.append(np.array(simple_view).flatten()) # 展平 return obs_list4.2 动作空间(Action Space)与执行模型
MAgent支持离散和连续动作空间,但离散动作更常用。动作通常包括:
- 移动:向四个或八个方向移动,或停留。
- 攻击:向某个方向攻击,或攻击视野内某个特定ID的智能体。
- 转向:改变自身朝向(如果观察与朝向相关)。
- 特殊动作:如收集资源、建造建筑等,这些需要通过自定义奖励和状态更新来实现。
在环境配置中,通过env.add_actions为种群添加动作。智能体每一步必须从可选动作中选择一个。环境会执行这些动作,并处理动作间的冲突(如两个智能体移动到同一格子)。
一个关键细节是动作掩码(Action Masking)。并非所有动作在任何时候都有效。例如,当智能体紧贴墙壁时,向墙的方向移动是无效的;当没有攻击目标在攻击范围内时,攻击动作是无效的。一个鲁棒的大脑应该在输出动作概率分布前,将无效动作的概率置零(或设为极小的值)。MAgent环境可以提供每一步的有效动作掩码,大脑应该利用这个信息。
4.3 奖励函数(Reward Function)设计:引导智能体行为
奖励函数是多智能体强化学习中最具挑战性也最有趣的部分。它定义了“好”与“坏”的行为。设计不当的奖励函数会导致智能体学会“钻空子”或出现非预期的行为。
在我们的“资源收集”例子中,初步的奖励设计可能是:
- 收集者:
+1:成功收集一个资源点。-0.5:被强盗攻击命中一次。+0.01:每存活一步(生存奖励,鼓励存活)。-0.1:攻击动作(即使无目标,消耗体力)。
- 强盗:
+2:成功攻击一个携带资源的收集者(假设资源被抢夺)。-0.5:攻击动作(消耗体力)。-0.02:每步离最近的收集者距离超过阈值(鼓励寻找目标)。
这只是一个起点。实际设计中,你可能会遇到以下问题:
- 稀疏奖励:收集资源是稀疏事件,智能体可能很久都得不到正奖励,导致学习缓慢。解决方案是设计塑形奖励(Shaped Reward),例如,给收集者一个与最近资源点距离成反比的小奖励,引导它走向资源。
- 奖励黑客(Reward Hacking):智能体找到漏洞获得高奖励,但行为不符合预期。例如,收集者可能学会反复在资源点旁边“蹭”而不真正收集,如果奖励函数设计有瑕疵。这需要仔细审查奖励逻辑,并可能引入内在好奇心(Intrinsic Curiosity)等机制。
- 多目标权衡:生存 vs 收集资源 vs 避免战斗。你需要调整不同奖励项的系数(权重),这个调参过程往往需要大量实验。
实操心得:奖励函数的设计是一个迭代过程。不要指望第一次就设计完美。强烈建议在训练初期开启可视化,频繁观察智能体的行为。如果发现智能体行为“愚蠢”或停滞不前,首先检查奖励值是否正常(打印出来看),然后思考奖励函数是否传达了正确的学习信号。有时,一个简单的奖励函数配合课程学习(Curriculum Learning)——先从简单任务开始,逐步增加难度——比一个复杂但难以学习的奖励函数更有效。
5. 高级特性与性能调优实战
5.1 利用内置环境加速研究
MAgent项目本身提供了几个精心设计的内置环境,这些环境本身就是很好的研究基准和起点:
- Battle:经典的团队对抗游戏。两队智能体在地图上战斗,直至一方全灭。适合研究团队协作、攻防策略。
- Gathering:类似我们自建的“资源收集”,但规则更完善。智能体需要收集苹果(资源),同时可以攻击对手。
- Tiger-Deer:生态模拟。老虎(捕食者)需要捕食鹿(猎物),鹿需要吃草并躲避老虎。适合研究种群动力学和 emergent behavior(涌现行为)。
直接使用这些环境可以跳过繁琐的环境构建阶段,专注于算法研究。你可以通过magent.builtin来调用它们,并且它们通常已经配置好了合理的观察、动作和奖励函数。
import magent.builtin.tf_model as models from magent.builtin.ma_policy import RandomPolicy env = magent.builtin.battle_v4.env() # 使用内置的TensorFlow模型 model = models.MLP(env)5.2 大规模并行仿真与渲染优化
当智能体数量达到数千时,即使有C++后端,仿真速度也可能成为瓶颈。MAgent支持多线程仿真,可以将一个大环境分割成多个线程同时更新。
# 在创建环境时指定线程数 env = magent.GridWorld(width, height, thread_num=4)此外,渲染(可视化)是另一个性能消耗点。pygame渲染上千个移动的智能体可能会卡顿。对于超大规模实验,有两种策略:
- 间歇性渲染:每训练100步渲染一次,或者只在评估时渲染。
- 使用轻量级渲染或日志:可以关闭
pygame,改用matplotlib生成静态快照,或者直接将关键数据(如位置、血量)写入日志文件,事后进行分析和可视化。
# 创建环境时禁用渲染 env.set_render_dir(None) # 不保存渲染帧 # 或者,只在需要时手动渲染一帧 if step % 100 == 0: frame = env.render(mode='rgb_array') # 返回一个numpy数组,而不是显示窗口 # 保存frame到文件或进行其他处理5.3 与主流RL框架集成:以Ray RLlib为例
手动编写训练循环对于研究新算法是必要的,但对于应用已知算法,使用成熟的分布式RL框架可以极大提升效率。Ray RLlib是一个优秀的选择,它原生支持多智能体训练,并且与MAgent有较好的集成(虽然官方不直接支持,但可以封装)。
核心思路是将MAgent环境包装成RLlib兼容的MultiAgentEnv接口。你需要实现reset()、step(action_dict)等方法,其中action_dict是一个字典,键为智能体ID(或种群ID),值为动作列表。
from ray import tune from ray.rllib.env.multi_agent_env import MultiAgentEnv import magent class MAgentEnvWrapper(MultiAgentEnv): def __init__(self, env_config): self.env = magent.builtin.battle_v4.env() self._agent_ids = set(["predator", "prey"]) # 假设有两个种群 def reset(self): obs = self.env.reset() # 将MAgent的obs格式转换为RLlib需要的dict格式 return self._format_obs(obs) def step(self, action_dict): # action_dict: {'predator': [act1, act2,...], 'prey': [...]} # 将动作设置到MAgent环境 for group, actions in action_dict.items(): self.env.set_actions(group_id_map[group], actions) self.env.step() obs = self.env.get_observations(...) rewards = self.env.get_rewards(...) dones = self.env.is_done() infos = {} # 格式化返回 return self._format_obs(obs), rewards, dones, infos # 然后在RLlib配置中使用这个环境 tune.run( "PPO", config={ "env": MAgentEnvWrapper, "multiagent": { "policies": { "predator_policy": (None, obs_space, act_space, {}), "prey_policy": (None, obs_space, act_space, {}), }, "policy_mapping_fn": lambda agent_id: "predator_policy" if "predator" in agent_id else "prey_policy", }, "num_workers": 4, } )通过这种集成,你可以利用RLlib提供的各种先进算法(PPO, IMPALA, A3C等)、超参数优化、以及分布式训练能力,大幅提升MAgent实验的开发和训练效率。
6. 典型问题排查与调试技巧实录
在多智能体仿真中,问题可能出现在环境逻辑、智能体交互、奖励计算或训练算法等多个层面。以下是一些常见问题及排查思路。
6.1 智能体“发呆”或行为异常
现象:智能体长时间不移动,或者重复进行无意义的动作(如原地转圈)。排查步骤:
- 检查观察输入:打印出几个智能体的观察向量。观察是否包含有效信息?视野内是否有其他智能体或关键物体?观察值是否全为零或异常值(如NaN)?
- 检查奖励信号:打印每一步的奖励。奖励是否始终为零或非常小?智能体是否从未获得过正奖励?如果是,学习无法启动。需要检查奖励函数的计算逻辑,确保智能体执行正确行为时能获得奖励。
- 检查动作输出:打印大脑网络输出的动作概率分布。分布是否均匀(说明网络未学习)?是否总是偏向某个固定动作?如果是,可能是网络初始化或梯度问题。
- 检查环境规则:确认动作是否被正确执行。例如,移动动作是否因为撞墙而失败?攻击动作是否因为目标不在范围内而无效?环境
step()函数后,智能体的位置、状态是否按预期更新?
6.2 训练不稳定,奖励曲线剧烈震荡
现象:团队整体奖励在训练过程中没有上升趋势,反而大幅上下波动。可能原因与解决:
- 探索与利用的平衡:多智能体环境中,探索不足会导致策略陷入局部最优;探索过度则导致策略无法收敛。尝试调整策略的熵系数(Entropy Coefficient)或探索率(Epsilon)。
- 非平稳性问题:这是MARL的核心难题。当一个智能体改进其策略时,对其他智能体而言,环境就发生了变化。这会导致用旧经验计算出的价值函数或优势函数不准确。
- 解决方案:使用经验回放池(Replay Buffer)时,尽量使用最近的经验,或者采用像MADDPG、QMIX这类专门处理非平稳性的算法,它们通常采用集中式训练(Critic能看到全局信息)来稳定学习。
- 学习率过高:尝试逐步降低学习率。
- 奖励尺度问题:不同奖励项的数值量级差异过大,可能导致梯度爆炸或某些目标被忽略。对奖励进行归一化(Reward Scaling)是常见做法。可以维护一个运行均值和方差,对每一步的奖励进行标准化。
6.3 内存泄漏与性能下降
现象:随着训练步数增加,程序占用的内存持续增长,仿真速度变慢。排查:
- Python对象引用:确保在训练循环中没有无意中创建并保留了不再需要的大对象(如历史观察、动作的列表)。使用
tracemalloc等工具定位内存增长点。 - MAgent环境重置:每次调用
env.reset()时,是否完全清理了上一局的状态?检查自定义环境代码,确保没有残留的全局变量或缓存。 - 渲染资源:如果开启了渲染,确保
pygame的Surface对象被及时释放。或者考虑如前所述,采用间歇性渲染。 - C++后端:极少数情况下,可能是底层C++代码的内存管理问题。尝试更新到MAgent的最新版本,或回退到稳定版本。
6.4 可视化与日志分析技巧
“一图胜千言”,在多智能体研究中尤其如此。
- 实时可视化:除了MAgent自带的
pygame渲染,可以定期将关键数据(如所有智能体的位置、血量、资源持有量)保存下来。用matplotlib制作动态图或热力图,可以更清晰地展示群体行为的演化。 - 关键指标日志:记录每场游戏(Episode)的统计信息,如:存活时间、收集资源总数、造成伤害总量、双方存活数量对比等。将这些指标随时间(训练步数)的变化绘制出来,是衡量算法进展的核心依据。
- 智能体轨迹分析:对于表现异常(特别好或特别差)的智能体,记录其整个Episode的轨迹(位置序列、动作序列、观察序列)。这有助于理解其策略,发现奖励函数的漏洞或环境设计的缺陷。
我个人在调试一个合作收集任务时,曾遇到智能体总是卡在角落的问题。通过可视化轨迹发现,是因为奖励函数只奖励“收集”,而没有惩罚“长时间停留”。智能体发现移动到角落可以避免被攻击,虽然收集不到资源,但也没有惩罚,导致策略收敛到一个保守但无用的状态。后来加入了“移动鼓励”和“停滞惩罚”后,问题得以解决。这个经历让我深刻体会到,在多智能体系统中,奖励函数需要非常精细地平衡短期收益和长期目标,任何疏漏都可能被智能体利用,产生意想不到的(通常是不希望的)行为。