我们经常遇到一类很让人困惑的强化学习实验:在训练环境里,智能体的奖励曲线一路上扬,收敛速度和最终分数都很漂亮;可一旦把模型放到新的模拟器、新的对手或者略有变化的业务环境里,效果立刻“打回原形”,奖励崩了,行为也变得不可解释。如果你是在多智能体强化学习(Multi-Agent RL, MARL)场景里做这件事,问题往往会更严重,因为多个智能体之间还会互相“配合”暴露问题。
这里面的核心问题,往往不是算法写错了,也不是超参没调好,而是你从头到尾只使用了一个“冻结的模拟器”。
所谓冻结模拟器,指的就是在训练过程中始终使用同一个环境实例、同一套障碍物布局、同一组奖励参数、同一批对手策略,环境不会因为策略的变化而动态调整。很多团队的第一版训练代码都是这么写的,因为实现简单、工程成本低。但从研究结果和大量实验经验来看,一个冻结的模拟器远远不够——它会让策略慢慢收敛到一条只对当前环境有效的“窄路”上,最终引发我在这篇文章里要重点讨论的 Simulator Collapse,模拟器坍塌。
这篇文章会讲清楚三件事:什么是模拟器坍塌,为什么它会在多智能体强化学习里被放大,以及如何用一套最小可复现的框架去观测、量化和缓解它。文章会给出完整的 Python 示例代码,可以直接在本地跑起来,适合正在做 MARL 训练、仿真环境设计和策略泛化问题的工程师与研究人员。
1. 这篇文章真正要解决的问题
先明确一个事实:在单智能体强化学习里,“训练环境和测试环境不一样”已经是被反复讨论的问题,对应的是 sim-to-real gap、分布偏移(distribution shift)等概念。但在 MARL 里,问题要更深一层。
多智能体环境下,策略的好坏不仅取决于环境本身,还取决于其他智能体的策略。两个智能体(或两支队伍)在同一个模拟器里反复对练,会慢慢形成一种“默契”:A 学会了针对 B 的固定动作模式做反应,B 也学会了针对 A 的固定行动模式做反应。这种默契在冻结模拟器里表现为训练奖励不断上升,仿佛两个智能体都在进步,但实际上它们只是在互相迁就,并没有学会通用、稳健的行为。
一旦评估时换一个模拟器,换一个障碍物布局,或者换一个训练方式不同的对手,这种“默契”就会瞬间失效。更糟糕的是,多智能体系统的性能往往是多个策略共同作用的结果,一个策略失效会连累其他策略,最终表现为整个系统“坍塌”。
这篇文章要解决的核心问题可以概括成一句话:如何判断你的 MARL 训练是否正在走向模拟器坍塌,以及如何在训练流程设计上避免它。
适合阅读这篇文章的读者有三类:
- 正在做多智能体仿真训练,发现模型“训练良好、泛化乏力”的工程师。
- 负责搭建仿真环境、制定训练与评估协议的算法平台开发者。
- 准备入门 MARL 泛化问题,想找一个可运行的最小实验框架的研究者。
读完这篇文章,你会得到一套可以落地的监测指标、一个最小复现模板,以及几条经过实践检验的缓解策略。
2. 核心概念:冻结模拟器与模拟器坍塌
2.1 什么是冻结模拟器
冻结模拟器(Frozen Simulator)指在训练全过程中,环境生成逻辑和交互规则不随策略变化而更新。具体包含三层含义:
| 层面 | 冻结内容 | 示例 |
|---|---|---|
| 环境层 | 地图、障碍物、奖励分布、动力学参数固定 | GridWorld 的障碍物位置固定,不会变化 |
| 对手层 | 对练策略固定或只做小范围更新 | 对手始终使用同一个早期 checkpoint 模型 |
| 学习层 | 环境不会针对策略弱点自动生成新任务 | 训练流程不包含自动课程学习机制 |
冻结模拟器本身并不是错误。在单智能体、任务目标明确、测试分布与训练分布基本一致的时候,冻结模拟器是效率最高的做法。它的问题在于:只要评估分布稍微偏离训练分布,策略的脆弱性就会暴露。
2.2 什么是模拟器坍塌
模拟器坍塌(Simulator Collapse)描述的是一种系统性的失败模式:当训练长期依赖单一冻结模拟器时,策略会过度适配该模拟器支撑的数据分布,导致在环境扰动、对手策略变化或任务参数迁移时,整体性能出现非线性的急剧下降。
这里必须强调“非线性”三个字。正常的性能下降是平滑的,比如环境难度增大,奖励稍微降低;模拟器坍塌是更剧烈的表现:训练环境和测试环境看起来只是小幅不同,但策略几乎完全失效。这类似于神经网络对训练集“背答案”,但发生在强化学习的交互决策层面。
2.3 与相关概念的边界
- 过拟合(Overfitting):监督学习里的过拟合针对的是静态数据分布;模拟器坍塌发生在动态交互过程中,策略通过与环境交互“主动”固化了脆弱行为。
- 模式坍塌(Mode Collapse):生成模型里的模式坍塌指生成样本多样性下降;模拟器坍塌中,策略行为多样性下降是其中一种表现,但不完全等价。
- 分布偏移(Distribution Shift):模拟器坍塌是分布偏移在 MARL 中的严重表现,但偏移本身是原因,坍塌是结果,而且 MARL 中策略之间的耦合会放大偏移的影响。
- 训练-评估不一致(Train-Eval Mismatch):这是模拟器坍塌的直接原因之一,但不是全貌,因为多智能体场景下对手策略的变化比环境参数变化更隐蔽。
3. 模拟器坍塌是怎么发生的:机制拆解
模拟器坍塌不是一蹴而就的,通常经历三个阶段。理解这三个阶段,你才能在训练日志里提前发现苗头。
3.1 第一阶段:策略在单一任务分布上收敛
训练初期,策略是随机探索的,状态覆盖率比较高,行为多样性也足够。但随着训练推进,冻结环境里的“最优路径”会被策略反复寻找到,并被强化。因为这个最优路径是固定的,策略的探索冲动会逐渐降低,状态访问越来越集中到少数几条轨迹上。
如果画出策略的状态覆盖热力图,你会发现,一开始覆盖整个状态空间,中期集中到少数关键区域,后期只剩下几条固定的“管道”。这就是行为层面坍塌的前兆。
3.2 第二阶段:多智能体协同适应的相互锁定
这是 MARL 独有的深层问题。在冻结模拟器里,两个团队的策略会陷入一种 co-adaptation(协同适应)状态:A 的某个弱点会被 B 利用,B 的某个弱点也被 A 利用,双方都在各自弱点的基础上形成了一种“动态平衡”。
这种平衡极具迷惑性,因为双方的训练曲线都可能在上升,评估指标也在变好,看起来很像是在“进化”。但实际上,两者的策略都变得越来越窄。换句话说,它们不是学会了应对各种可能情况,而是学会了应对“当前对手的固定行为模式”。一旦评估时换成第三方对手,这种共同锁定的均衡就会破裂。
3.3 第三阶段:部署时分布偏移全面暴露
当训练结束,你把这个策略部署到新的模拟器、真实业务环境或者与陌生对手对战时,环境中任何偏离训练分布的属性都会造成连锁反应:
- 策略遇到的第一个状态就不是训练分布中的状态,决策质量下降。
- 由于 MARL 策略之间相互依赖,一个决策失误会诱发对手进入训练时从未见过的状态。
- 系统进入劣化螺旋,性能整体坍塌。
在复杂环境里,这种坍塌会在极短的时间内发生,可能几十步之内,训练时积累的优势就全部归零。
4. 如何判断模拟器坍塌:关键指标
要观测模拟器坍塌,不需要太复杂的工具,关键是设计几个能够反映“训练-评估分布差异”的指标。建议至少跟踪以下四类。
4.1 训练-评估奖励差距
这是最直观的指标。定义:
collapse_gap = average_return_on_frozen_simulator - average_return_on_eval_suite如果训练模拟器上的奖励远高于一批未见模拟器上的平均奖励,说明策略已经高度依赖训练环境。差值越大,坍塌风险越高。
4.2 行为熵与策略多样性
统计策略在每个状态下选择的动作分布,计算行为熵:
H(π) = - Σ π(a|s) log π(a|s)如果训练过程中行为熵持续下降,而且下降速度快于正常收敛速度,说明策略的探索空间正在急剧收窄。在多智能体场景里,还可以统计智能体之间的“行为距离”,如果两个智能体最终的行为模式高度相似,也是一种坍塌信号。
4.3 状态覆盖范围
统计训练过程中智能体访问过的状态数量与整个状态空间的比例。冻结模拟器训练后期,状态覆盖范围往往只占全部可达状态的一小部分。这个比例可以直接反映策略的泛化潜力。
4.4 环境参数敏感性
固定策略,依次改变环境的某个参数(障碍物数量、奖励稀疏度、对手随机种子),记录性能变化。如果性能随参数变化剧烈波动,说明策略对环境参数非常敏感,这在工程上是很危险的信号。
5. 最小复现设计:实验方案
为了验证模拟器坍塌,不必一开始就在复杂环境里做实验。下面这套最小实验设计足够暴露问题。
5.1 实验目标
验证一个假设:在冻结模拟器上训练的 MARL 策略,在一组未见过的模拟器上评估时,会出现明显的性能坍塌;而使用环境集合训练的策略,坍塌程度显著更小。
5.2 模拟器集合设计
准备两组模拟器:
- 训练模拟器:只使用一个固定种子生成的环境实例,例如固定障碍物布局的 GridWorld。
- 评估模拟器:使用多个不同种子生成的同一类环境,障碍物布局、可行的最优路径均有差异。
这个设计模拟了最常见的工程场景:训练时环境单一,部署时环境千变万化。
5.3 对照组设计
关键是对照组。至少设置三组:
| 组别 | 训练方式 | 预期结果 |
|---|---|---|
| A 组 | 冻结模拟器训练 | 训练奖励高,评估奖励低,坍塌明显 |
| B 组 | 随机环境集合训练 | 训练奖励略低,评估奖励高,坍塌缓解 |
| C 组 | 自动课程环境训练 | 训练与评估差距最小,但训练成本最高 |
如果你只想快速验证概念,跑通 A 组和 B 组就够了。
5.4 训练协议
为了保证对比公平,所有组采用相同的策略网络结构、优化器、学习率和训练步数。唯一的区别是训练环境采样方式。这样,实验结果的差异只能归因于训练环境的多样性。
6. 完整示例代码:模拟器坍塌监测框架
这里给出一个可以直接运行的 Python 示例。它不依赖任何重型强化学习库,核心逻辑用 NumPy 和标准库实现,方便你快速理解并迁移到自己的项目。
6.1 项目结构
simulator-collapse-demo/ ├── simulator_collapse_demo.py # 主脚本 └── README.md # 运行说明6.2 主脚本
# 文件:simulator_collapse_demo.py import numpy as np import random from collections import defaultdict random.seed(42) np.random.seed(42) class GridSim: """参数化网格世界模拟器。 通过不同的随机种子生成不同障碍物布局, 从而得到"同一个任务族、不同环境实例"的效果。 """ def __init__(self, size=6, seed=0, n_obstacles=4): self.size = size self.rng = np.random.RandomState(seed) self.n_obstacles = n_obstacles self.start = (0, 0) self.goal = (size - 1, size - 1) self.obstacles = self._sample_obstacles() self.state = None def _sample_obstacles(self): obstacles = set() tries = 0 while len(obstacles) < self.n_obstacles and tries < 200: pos = (self.rng.randint(self.size), self.rng.randint(self.size)) if pos not in (self.start, self.goal) and pos not in obstacles: obstacles.add(pos) tries += 1 return obstacles def reset(self): self.state = self.start return self.state def step(self, action): # 动作:0=上,1=下,2=左,3=右 dr, dc = [(-1, 0), (1, 0), (0, -1), (0, 1)][action] nr, nc = self.state[0] + dr, self.state[1] + dc nr = min(max(nr, 0), self.size - 1) nc = min(max(nc, 0), self.size - 1) if (nr, nc) in self.obstacles: reward, done = -0.1, False elif (nr, nc) == self.goal: reward, done = 1.0, True else: reward, done = 0.0, False self.state = (nr, nc) return self.state, reward, done, {} class QAgent: """最基本的表格型 Q-Learning 策略,用于演示训练与评估流程。""" def __init__(self, n_actions=4, gamma=0.95, alpha=0.1, epsilon=0.1): self.q = defaultdict(lambda: np.zeros(n_actions)) self.n_actions = n_actions self.gamma = gamma self.alpha = alpha self.epsilon = epsilon def act(self, state, explore=True): if explore and random.random() < self.epsilon: return random.randrange(self.n_actions) qs = self.q[state] return int(np.argmax(qs)) def update(self, state, action, reward, next_state, done): td_target = reward if not done: td_target += self.gamma * np.max(self.q[next_state]) self.q[state][action] += self.alpha * (td_target - self.q[state][action]) def train_on_env(agent, env, episodes=400, max_steps=50): """在给定模拟器上训练,并记录每轮奖励。""" for ep in range(episodes): state = env.reset() for _ in range(max_steps): action = agent.act(state, explore=True) next_state, reward, done, _ = env.step(action) agent.update(state, action, reward, next_state, done) state = next_state if done: break def evaluate_on_env(agent, env, episodes=30, max_steps=50): """在给定模拟器上评估,返回平均累积奖励与成功率。""" returns = [] successes = 0 for _ in range(episodes): state = env.reset() total = 0.0 for _ in range(max_steps): action = agent.act(state, explore=False) next_state, reward, done, _ = env.step(action) total += reward state = next_state if done: successes += 1 break returns.append(total) return float(np.mean(returns)), successes / episodes def compute_behavior_entropy(agent, env, episodes=30, max_steps=50): """统计策略在模拟器上的行为熵,用于观测策略多样性变化。""" state_count = defaultdict(int) state_action_count = defaultdict(lambda: defaultdict(int)) for _ in range(episodes): state = env.reset() for _ in range(max_steps): qs = agent.q[state] probs = np.exp(qs - np.max(qs)) if np.max(qs) != 0 else np.ones_like(qs) probs = probs / np.sum(probs) for a_idx, p in enumerate(probs): state_count[state] += 1 state_action_count[state][a_idx] += p action = np.argmax(qs) state, _, done, _ = env.step(action) if done: break total_visits = sum(state_count.values()) if total_visits == 0: return 0.0 entropy_sum = 0.0 for s, cnt in state_count.items(): if len(state_action_count[s]) == 0: continue probs = np.array([state_action_count[s][a] for a in range(4)]) probs = probs / (probs.sum() + 1e-12) p_selected = cnt / total_visits entropy_sum += p_selected * (-np.sum(probs * np.log(probs + 1e-12))) return entropy_sum def main(): print("=== 模拟器坍塌最小复现 ===\n") # 训练使用单个冻结模拟器 frozen_env = GridSim(size=6, seed=1, n_obstacles=4) agent = QAgent() print("[1] 使用冻结模拟器训练策略 ...") train_on_env(agent, frozen_env, episodes=300) train_return, train_sr = evaluate_on_env(agent, frozen_env) print(f"训练模拟器评估:平均奖励 {train_return:.3f},成功率 {train_sr:.3f}") # 在多个未见过的模拟器上评估 eval_returns = [] eval_srs = [] print("\n[2] 在多个未见模拟器上评估 ...") for seed in range(10): eval_env = GridSim(size=6, seed=seed, n_obstacles=4) ret, sr = evaluate_on_env(agent, eval_env) eval_returns.append(ret) eval_srs.append(sr) print(f"seed={seed:2d} 平均奖励 {ret:.3f} 成功率 {sr:.3f}") avg_eval_return = float(np.mean(eval_returns)) avg_eval_sr = float(np.mean(eval_srs)) collapse_gap = train_return - avg_eval_return print(f"\n评估集合平均奖励:{avg_eval_return:.3f}") print(f"评估集合平均成功率:{avg_eval_sr:.3f}") print(f"训练-评估奖励缺口(collapse gap):{collapse_gap:.3f}") entropy = compute_behavior_entropy(agent, frozen_env) print(f"冻结模拟器上的策略行为熵:{entropy:.3f}") print("\n=== 复现完成 ===") if __name__ == "__main__": main()6.3 代码关键逻辑说明
这个脚本的核心思路只有一个:用同一个 Q-Learning 算法,在固定环境中训练,然后在多个随机环境中评估。重点不在算法本身,而在训练环境与评估环境的差异暴露。
GridSim用随机种子生成不同的障碍物布局,代表“同一个任务族里的不同模拟器”。QAgent是表格型 Q-Learning,足够演示策略窄化问题。真实项目里可以换成神经网络策略,逻辑不变。compute_behavior_entropy计算行为熵,用于观测策略是否过早收敛到少数固定动作。collapse_gap即训练模拟器与评估模拟器上的奖励差,是判断模拟器坍塌的核心指标。
6.4 多智能体变体:冻结对手策略
如果你的场景是多个智能体对抗,可以在上述框架基础上增加一个“冻结对手”的变体,非常容易暴露协同适应问题。
# 文件:marl_variant_excerpt.py # 假设有两个策略:learner 和 opponent # 训练时 opponent 不更新,learner 与固定的 opponent 对练 def train_learner_vs_frozen_opponent(learner, opponent, env, episodes=200, max_steps=50): """模拟多智能体场景:一个学习智能体 vs 一个冻结的对手策略。 这种方法最容易暴露协同适应问题——learner 只学会针对 opponent 的固定行为模式做反应,而不是学会通用的博弈能力。 """ for ep in range(episodes): state = env.reset() done = False for _ in range(max_steps): # 主智能体根据自身策略选择动作 action_learner = learner.act(state, explore=True) # 对手策略冻结,不随训练更新 action_opponent = opponent.act(state, explore=False) # 两个动作共同决定环境转移(这里做简化合并) next_state, reward, done, _ = env.step( combine_actions(action_learner, action_opponent) ) learner.update(state, action_learner, reward, next_state, done) state = next_state if done: break def combine_actions(action_a, action_b): """实际项目里需要根据环境语义合并两个智能体的动作。 这里的实现只是一个占位符,替换成你的环境转移规则即可。 """ return (action_a + action_b) % 4这段代码的用意是提醒你:在多智能体训练中,对手策略也是“模拟器”的一部分。如果对手长期冻结,主策略就会朝对手的固定模式“定向进化”,评估时换上任何一个新对手,性能都会出现明显下降。
6.5 运行方式
cd simulator-collapse-demo python simulator_collapse_demo.py7. 运行结果与效果验证
我在设计这套示例时,刻意没有把训练过程包装成“效果很好”的样子,而是希望你能在本地看到一套稳定的趋势,便于理解现象,再迁移到自己的实验里。
7.1 预期输出模式
运行脚本后,你会看到类似下面的趋势,注意数值会因为你环境生成方式的不同而略有波动:
- 在冻结模拟器上评估时,平均奖励较高,成功率也较高。
- 在评估集合的多个 seed 上,结果差异明显:有的 seed 奖励尚可,有的 seed 几乎拿不到奖励。
collapse gap是一个较大的正数,说明训练模拟器与评估模拟器之间存在明显的分布差距。- 行为熵通常小于同等训练步数下随机环境集合训练得到的熵值。
7.2 如何判断模拟器坍塌已经发生
满足下面三个条件,基本可以判定训练流程正在走向模拟器坍塌:
- 训练模拟器上的评估奖励明显高于多个未见模拟器的平均奖励。
- 评估集合上的奖励方差很大,部分评估环境成功率接近零。
- 策略行为熵在训练后期持续下降并维持在较低水平,状态覆盖范围狭窄。
如果只满足其中一条,还只是“泛化能力不足”;三条都满足,就是典型的模拟器坍塌。
7.3 第一步失败排查
如果脚本运行结果不符合预期,比如训练模拟器评估本身就很低,先检查这两件事:
- 环境是否太困难,Q-Learning 在 300 个 episode 里根本无法收敛。可以增加
episodes或减少n_obstacles。 epsilon是否过大,导致评估时仍然大量随机探索。评估时explore=False只影响选择策略,但训练不充分时 Q 表本身不准,可以调大训练轮数。
8. 缓解模拟器坍塌的实践方法
从“一个冻结的模拟器不够”这个标题出发,缓解方案的核心思想是:让训练过程中的模拟器不再是单一、冻结的,而是有分布的、有动态性的。
8.1 方法一:环境集合与域随机化
这是最简单、最有效的第一步。不改变算法,只把训练环境从“一个固定环境”改成“一批随机环境”。具体做法:
- 每一次训练 episode 开始前,从环境参数分布中重新采样一个环境实例。
- 参数包括地图尺寸、障碍物数量、奖励权重、物理参数等。
- 这就是 Domain Randomization 的基本思想,也是 sim-to-real 迁移里最常用的手段。
域随机化解决的是“环境静态分布”问题,但它有一个局限:如果环境集合本身覆盖不到关键分布,策略依然可能坍塌。所以它需要配合更动态的机制。
8.2 方法二:自动课程环境设计
如果环境的随机性来自人为设定的分布,而人的设计总是有限的,那么可以引入自动课程机制,让环境生成器基于当前策略的弱点,动态生成更有区分度的任务。
实操中有几个可以参考的思路:
- PAIRED:用两个智能体互相博弈,一个负责生成环境,一个负责在主环境中学习,通过对抗生成更有挑战性的任务。
- PLR(Prioritized Level Replay):维护一个历史环境库,优先采样那些让策略表现较差的环境,让训练重心集中在弱点上。
- 优势加权环境生成:根据策略在环境中的预期收益与历史成功率的差距,动态调整环境采样概率。
这些方法本质上都在打破“冻结”的限制,让模拟器随训练进度演化。
8.3 方法三:种群与多样性约束
尤其是在多智能体对抗场景下,单一策略与单一对手共同进化是坍塌的高发路径。更稳妥的做法是维护一个策略种群,而不是只训练一个策略。
- 每次训练时,从种群中随机挑选对手,而不是只跟固定的一个对手对练。
- 定期评估种群内策略之间的差异,如果发现策略多样性下降,可以回滚到较早的 checkpoint,或者增加探索噪声。
- 这种思路在 AlphaStar 的 League Training 和 OpenAI Five 的训练里都有体现,工程上叫做维护一个“联赛”。
8.4 方法四:评估制度与训练制度分离
训练和评估不应该使用同一套模拟器。建议建立一套独立的评估环境集合,这个集合完全不参与训练,只在固定节奏上做评估。
评估制度还应该包括“跨对手评估”(cross-play evaluation):把你的策略和多个陌生策略对练,而不只是和训练对手对练。这样能够提前发现协同适应问题。
8.5 方法五:指标监控及时止损
把第 4 节里的指标接入训练平台,设置自动化报警。一旦发现以下情况,就要立刻暂停或者回滚:
- collapse gap 超过设定阈值。
- 行为熵下降到设定下限。
- 多智能体之间的行为距离过于接近。
监控的意义在于,不要让训练继续在“虚假的进步”里浪费时间。很多时候,继续训练只会让策略在冻结模拟器上越来越窄,并不会带来真正的泛化提升。
9. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 训练模拟器上奖励很高,换一个模拟器立刻下降 | 策略过拟合到单一模拟器,模拟器坍塌 | 检查训练-评估奖励缺口和行为熵 | 引入环境集合训练或域随机化 |
| 多智能体训练时双方都“在进步”,但评估时双双失败 | 协同适应导致的虚假均衡 | 更换陌生对手评估或做 cross-play | 使用策略种群训练,避免单一对手对练 |
| 行为熵下降过快 | 探索不足,策略过早收敛到少数动作 | 查看状态覆盖热力图和行为熵曲线 | 提高 epsilon,或引入熵正则化 |
| 加入环境随机化后训练变慢 | 环境集合过大,策略学习方差上升 | 观察单环境收敛速度和评估奖励方差 | 使用 PLR 类自动课程,优先采样困难环境 |
| 策略在评估集合上方差过大 | 评估环境难度分布不均匀 | 分桶统计各难度下的成功率 | 按难度分层评估并单独优化薄弱桶 |
| 环境集合训练后,特定测试环境仍然失败 | 该类环境在训练分布中覆盖不足 | 检查训练环境参数分布 | 人工补充该类环境,或引入自动课程 |
10. 最佳实践与工程建议
基于前面的分析,这里整理几条可以直接落到工程里的建议。
10.1 环境采样策略最好具备分层结构
不要简单地“每次随机一个环境”,而是把环境参数空间做分层抽样。例如,障碍物数量是一个维度,地图大小是一个维度,奖励稀疏度是一个维度。每一维度都设置最小覆盖数量和抽样上限,确保训练分布不会集中在某一小簇参数上。
10.2 训练与评估环境严格隔离
训练环境集合和评估环境集合必须完全独立。如果训练环境里用过 seed 1 到 50,评估环境就不要用这些 seed,最好给评估环境单独使用一组保留的 seed 或专门的生成参数。否则评估结果会虚高。
10.3 保存多版本策略,尤其是多样性策略
建议定期保存策略 checkpoint,并且不要只保存“最强”的那个。在做多智能体项目时,保留不同训练阶段、不同对手环境下的策略版本,既是调试的备用工具,也是评估阶段构建对手种群的材料。
10.4 自动课程需要设置安全上限
自动课程环境生成的困难版本可能超出当前策略的学习能力,导致训练不稳定。建议给环境生成器设置难度上限,并监控环境生成参数的分布,防止生成器自己“模式坍塌”,不停生成同一种困难环境。
10.5 监控面板上至少放四个指标
训练日志里只记录 reward 是不够的。至少在监控面板上放置:训练-评估奖励差距、行为熵、状态覆盖率、策略种群内行为距离。这四个指标能让你在坍塌发生前看到迹象,而不是等评估结果出来才发现问题。
10.6 先从冻结模拟器开始,但别停在那里
我不是建议所有项目都立即上复杂的自动课程。事实上,先用冻结模拟器把算法逻辑调通,是合理的第一步。重要的是建立“逐步打破冻结”的意识:先加环境集合,再加动态对手,最后再上完整课程机制。每一步都要用评估指标验证收益,不要为了复杂而复杂。
11. 总结与后续学习方向
模拟器坍塌是一个被很多人忽视、却实实在在影响 MARL 系统落地的问题。它的根源并不复杂:一个冻结的模拟器提供的训练分布是静态的、单一的,而策略的泛化能力恰恰需要多样性来支撑。多智能体环境里,对手策略的冻结会把问题放大,让训练过程出现“看似进步、实际窄化”的虚假均衡。
这篇文章给出了模拟器坍塌的定义、三个阶段的机制拆解、四个可监控的关键指标,以及一套完整的最小复现代码。你可以在本地直接运行,观察训练-评估奖励缺口、行为熵和状态覆盖是如何暴露问题的。同时,文章也整理了几条实践路径:域随机化、自动课程、策略种群、评估制度分离,以及从简单到复杂的工程落地节奏。
如果你的项目正在做多智能体仿真训练,下一步最值得做的事情有两个。第一,把评估环境集合独立出来,跑一次当前策略的 baseline,看看 collapse gap 到底有多大。第二,把单一冻结模拟器改成一个小范围的环境集合,重新训练一轮,对比评估指标的变化。这两步做完,你对模拟器坍塌的理解就会从概念层面落到数据层面。
在更长远的学习方向上,可以继续关注 Unsupervised Environment Design、PAIRED、PLR、Population-Based Training 和 Cross-Play Evaluation 这几条技术路线。它们共同回答的问题是一致的:如何让训练过程中的模拟器不再是一个冻结的快照,而是一个持续演化的、能够覆盖真实部署分布的动态系统。