RL策略崩塌的复盘

  • 深度学习

一次 RL 策略崩塌的复盘:为什么已经接近 100% 正确的模型,后来会突然固定走同一个错误出口?

1. 背景

这次实验是一个带有“线索—出口对应关系”的玩具强化学习任务。

环境大致规则是:

  1. 场景里有一个线索;
  2. 线索的形状/类型决定哪个出口是正确的;
  3. agent 需要先观察或记住线索;
  4. 然后根据线索选择对应的出口;
  5. 如果走到正确出口,获得奖励;
  6. 如果走到错误出口,失败或获得低奖励/惩罚。

也就是说,这个任务不是简单的“看到出口就走”,而是需要完成一条信息链:

观察线索 -> 记住线索 -> 在出口处根据线索做选择

更抽象一点:

cue = 当前 episode 的关键信息
正确动作 = f(cue)

比如:

如果 cue 是 A,则正确出口是 TOP
如果 cue 是 B,则正确出口是 BOTTOM

训练早期,模型需要通过探索慢慢学会这条规则。

训练中期,模型成功率逐渐上升,最后一度接近甚至达到 100%。

表面上看,这说明模型已经完全学会任务了。

但奇怪的是,继续训练之后,模型没有保持稳定,而是出现了策略崩塌。


2. 观察到的现象

训练到某个阶段时,模型表现很好:

成功率 ≈ 1.0

这说明它不是靠固定走某一个出口,而是真的在根据线索选择出口。

因为如果正确出口会随线索变化,那么固定走某个出口最多只能达到大约 50% 成功率。

所以成功率为 1 的阶段,模型策略应该近似是:

如果线索对应 TOP,则走 TOP
如果线索对应 BOTTOM,则走 BOTTOM

也就是:

π(action | state, cue)

模型行为依赖 cue。

但是继续训练之后,模型突然变成了:

不管线索是什么,都走同一个出口

例如:

永远走 TOP

于是成功率从接近 1 跌到接近 0.5,甚至在某些评估分布下更低。

这让我一开始困惑:

之前模型明明已经学会了线索和出口的对应关系,为什么后来会突然变成固定走一个出口? 又没有什么信号告诉它“永远走 TOP”。

后来我理解到,问题不是模型“重新学了一个固定出口策略”,而是:

模型原本依赖线索的那条通道退化了;
线索通道退化后,输出层剩下的默认偏置主导了行为。

所以崩塌不是:

模型重新变随机

而是:

模型掉进了一个确定性的捷径策略:永远走同一个出口

3. 一个简化的数学图像

可以用一个很简单的形式理解模型在出口处的决策。

假设模型只需要在两个出口之间选择:

TOP 或 BOTTOM

定义一个 logit 差:

z = score_TOP - score_BOTTOM

如果:

z > 0,则选 TOP
z < 0,则选 BOTTOM

模型对线索的使用可以抽象成:

z = k · h_cue + b

其中:

  • h_cue:模型内部表示出来的线索特征;
  • k:模型使用这个线索特征的强度;
  • b:模型自身对某个出口的默认偏置。

如果线索是二值的,可以进一步简化为:

cue = +1 表示正确出口是 TOP
cue = -1 表示正确出口是 BOTTOM

于是:

z = k · cue + b

在模型表现正常时,可能是:

k = 10
b = 2

那么:

cue = +1: z = 10 * 1 + 2 = 12  -> TOP
cue = -1: z = 10 * (-1) + 2 = -8 -> BOTTOM

此时虽然模型内部有一个偏向 TOP 的 b = 2,但线索项 k · cue 很强,所以模型还是能根据线索正确选择出口。

这就是成功率为 1 的阶段。

但是如果继续训练后,线索通道被削弱了,例如:

k = 1
b = 2

那么:

cue = +1: z = 1 * 1 + 2 = 3 -> TOP
cue = -1: z = 1 * (-1) + 2 = 1 -> TOP

此时不管线索是什么,模型都会选 TOP。

这就是策略崩塌。

关键点是:

固定走 TOP 并不需要一个明确的奖励信号。

它可能只是因为:

线索项变弱以后,默认偏置 b 主导了决策。

4. 为什么成功率接近 100% 之后反而危险?

一开始我以为:

模型成功率都 100% 了,说明它已经完全学会了,继续训练应该只会更稳。

但实际不是这样。

当模型已经几乎总是做对时,强化学习里的有效纠错信号会变得很弱。

策略梯度可以粗略理解成:

策略更新 ∝ advantage · 探索产生的对比 · 对参数的梯度

更具体地,在二分类动作里,如果模型对正确动作的概率已经非常接近 1:

P(correct action) ≈ 0.999

那么它几乎不会采样错误动作。

这会导致几个问题:

4.1 缺少错误对比

如果模型总是做对,它很少得到“这条路错了,那条路对了”的对比信号。

它只知道:

当前行为可以成功

但它不一定持续知道:

成功是因为我正确使用了线索

成功行为里可能包含很多步骤:

移动到某处
观察线索
记忆线索
走向出口
选择出口

最终奖励只告诉模型整个轨迹成功了。

但它不一定能精确地区分:

到底是哪一部分对成功最关键?

尤其当策略已经稳定成功后,模型不会反复测试“如果不看线索会怎样”、“如果记错线索会怎样”。

于是,使用线索这条内部机制缺少持续维护。


4.2 策略太确定后,纠错梯度会变小

假设模型已经非常确信应该走 TOP:

P(TOP) ≈ 0.999

如果它采样到了 TOP,并且这次 TOP 是错的,理论上应该降低 TOP 的概率。

但由于策略已经非常饱和,实际梯度可能很小。

直观理解:

模型越自信,越不容易被一次失败拉回来。

在 softmax 策略里,梯度里会出现类似:

(a - p)

这样的项。

如果模型选择了 TOP,并且:

p = P(TOP) ≈ 0.999

那么:

a - p ≈ 1 - 0.999 = 0.001

这个值很小。

所以即使这次失败了,降低 TOP logit 的梯度也可能很弱。

这和监督学习不太一样。

在监督学习里,如果标签明确告诉模型“正确答案是 BOTTOM”,模型通常可以直接得到很强的纠错信号。

但在强化学习里,失败只告诉它:

这条轨迹不好

不一定直接告诉它:

你应该在那个具体状态选择 BOTTOM

更不一定告诉它:

你应该重新恢复线索记忆通道。

4.3 其它梯度还在继续改变模型

虽然维持线索通道的有效梯度变弱了,但模型参数并不是停止变化。

训练还在继续,模型仍然会受到各种更新影响:

  • batch 随机噪声;
  • critic/value loss;
  • step penalty;
  • optimizer 的动量;
  • Adam 的自适应学习率;
  • 环境中的路径长度差异;
  • 网络共享表征带来的干扰;
  • 探索或采样带来的波动。

这些更新不一定是“错误”的,也不一定完全和任务目标无关。

更准确地说:

这些梯度未必维护“记住线索”这条回路。

比如 step penalty 可能会鼓励 agent 尽量少走路。

如果去看线索、记住线索、再去出口比直接冲出口更费步数,那么模型可能会逐渐偏向一种短路策略:

别管线索,直接去某个出口试试

如果任务里两个出口平均正确率是 50%,而直接冲出口路径短,那么在某些奖励设定下,这个短路策略并不一定会被强烈压制。

于是就可能出现:

维持线索的梯度很弱
其它目标/噪声/偏置还在推模型
线索通道慢慢退化

5. 我的理解:模型忘掉的不是动作,而是“为什么这个动作正确”

我对这个现象的理解是:

训练成功后,模型一直执行正确动作。

因为它已经几乎 100% 符合环境规则,所以结果对某些关键输入细节不再敏感。

模型知道:

看到某种大致特征 -> 走某个出口

但它未必持续保持对线索细节的敏感。

一开始,模型可能真的依赖了线索的关键数值或方向。

后来,因为一直成功,它不需要反复验证这些细节。

在持续训练中,噪声或其它梯度让内部表征发生漂移。

只要漂移还没有影响最终动作,模型仍然会成功。

于是问题不会立刻暴露出来。

这就像:

线索特征的数值慢慢变了;
但只要符号还没变,输出动作仍然正确;
因此训练过程不会强烈纠正这个漂移。

比如可以想象:

logit = k · h_cue + b

只要:

k · h_cue

还足够大,模型就能压过默认偏置 b,继续正确。

但随着漂移继续发生:

h_cue 变小
或者 k 变小
或者 b 变大

最终某一刻:

k · h_cue < b

于是默认偏置接管决策。

此时模型不再根据线索选择出口,而是固定输出一个动作。

崩塌发生后,即使模型开始拿到惩罚,也未必容易恢复。

因为此时:

线索表征已经弱了
策略已经非常确定
模型很少探索另一个动作
失败奖励也不直接告诉它该恢复哪条内部通道

所以它可能陷入一个局部稳定的坏策略。


6. 手机话费类比

我觉得可以用“手机交话费上网”来类比这个问题。

假设规则是:

只有话费金额 > 0,手机才能上网

一开始,每次拿到新手机,我都会:

交话费 -> 上网

反复多次之后,我形成了一个经验:

只要执行了“交话费”这个动作,后面就能上网。

但这里真正关键的不是“有没有点缴费按钮”,而是:

缴费金额是否大于 0

如果长期以来,每次缴费金额都足够,那么“金额检查”这件事就不会被特别强调。

后来由于各种噪声或外部影响,我每次交的钱数开始变化。

一开始金额变化不影响结果:

交 50 元 -> 能上网
交 30 元 -> 能上网
交 10 元 -> 能上网
交 1 元  -> 能上网

因为它们都满足:

金额 > 0

于是我更加觉得:

金额具体是多少不重要。

直到某一次,金额变成了:

0 元

结果上不了网了。

但问题是,我的决策系统里已经只保留了:

是否执行了“交话费”这个动作

而没有保留:

交了多少钱

所以我会困惑:

我明明已经交过话费了,为什么还是不能上网?

对应到模型里就是:

模型还在执行类似的流程
但真正关键的线索数值/线索表征已经不参与决策了

于是失败后,它也不知道该修复哪里。


7. 消防演练类比

另一个更贴切的类比是消防演练。

现实中有很多规矩,一开始看起来很麻烦:

为什么要定期消防演练?
为什么要检查逃生通道?
为什么不能堆东西挡住门?
为什么要保留应急预案?

如果一个地方几十年没发生火灾,人们很容易觉得这些规则没必要。

因为日常数据告诉大家:

不演练也没事
不检查也没事
不维护也没事

但问题是,一旦真正发生火灾,这些规则就是关键。

长期不演练的结果是:

系统看起来正常
但应对异常的能力已经退化

这和 RL 模型很像。

当模型长期成功后,它可能慢慢忘掉:

为什么这个动作是正确的

只保留:

这个动作过去经常成功

所以需要某种“消防演练”机制,让模型持续保留关键能力。

对应到 RL 里,可以是:

  • 保持探索;
  • 使用熵正则;
  • 偶尔随机动作;
  • 强制访问线索;
  • 增加辅助任务;
  • 在 hidden state 上预测 cue;
  • 对策略变化加 KL 限制;
  • 使用 replay 或 curriculum 复习旧场景。

这些机制的共同目的不是让模型一直犯错,而是:

让模型持续知道:成功依赖哪些关键条件。

8. 玩具实验设计:如何复现这个现象

下面设计一个尽量简单的实验,用来复现“成功后继续训练导致策略崩塌”的现象。

8.1 环境结构

可以做一个小型 gridworld。

地图示例:

+-------------------+
|        TOP EXIT   |
|                   |
|                   |
|   CUE             |
|                   |
|                   |
|      AGENT        |
|                   |
|        BOT EXIT   |
+-------------------+

环境元素:

  • agent 初始位置随机;
  • 地图中有一个 cue;
  • 有两个出口:TOP 和 BOTTOM;
  • cue 类型随机:
    • cue = A:TOP 正确;
    • cue = B:BOTTOM 正确;
  • agent 需要先看到 cue,再去正确出口。

也可以让 cue 是不同形状:

circle -> TOP
triangle -> BOTTOM

或者不同颜色:

red -> TOP
blue -> BOTTOM

关键是 cue 和正确出口每个 episode 随机变化。


8.2 观测设计

为了让任务需要记忆,可以限制 agent 视野。

例如:

agent 只能看到周围 3x3 或 5x5 的局部区域

这样 agent 在出口处看不到 cue。

它必须在之前看到 cue 后,把 cue 存到 hidden state 里。

观测可以包括:

局部地图 one-hot
agent 朝向
上一步动作

如果使用 RNN,可以输入:

obs_t -> encoder -> GRU/LSTM -> policy/value head

8.3 动作空间

动作可以设为:

0: 上
1: 下
2: 左
3: 右
4: 停留/交互

如果 agent 到达出口格子,则 episode 结束。


8.4 奖励设计

简单奖励:

到达正确出口: +1
到达错误出口: 0 或 -1
每步惩罚: -0.01
超时: 0 或 -0.5

为了更容易出现崩塌,可以使用:

正确出口: +1
错误出口: 0
每步惩罚: -0.01

因为这会让“直接冲某个出口猜一下”变得有一定吸引力。

如果直接猜出口成功率是 50%,而路径更短,那么它可能在某些阶段和“先看线索再走出口”竞争。


8.5 模型结构

一个简单结构:

局部观测 obs
CNN / MLP encoder
GRU / LSTM
policy head + value head

伪代码:

class Agent(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = CNN_or_MLP()
        self.rnn = nn.GRU(hidden_dim, hidden_dim)
        self.policy = nn.Linear(hidden_dim, num_actions)
        self.value = nn.Linear(hidden_dim, 1)

    def forward(self, obs, hidden):
        x = self.encoder(obs)
        h, hidden = self.rnn(x, hidden)
        logits = self.policy(h)
        value = self.value(h)
        return logits, value, hidden

8.6 训练方法

可以用 PPO / A2C / REINFORCE。

为了观察崩塌,建议做几组对比:

实验 A:无熵正则

entropy_coef = 0

继续训练很久,观察成功率是否先上升到接近 1,然后在后期下降。

实验 B:有熵正则

entropy_coef = 0.01 或 0.005

看是否可以延缓或避免策略坍缩。

实验 C:熵正则退火到 0

entropy_coef 从 0.01 逐渐降到 0

观察退火后是否更容易崩塌。

实验 D:保留少量 ε-greedy

训练时:

以 1 - ε 概率按策略采样
以 ε 概率随机动作

例如:

ε = 0.02 或 0.05

看是否比完全无探索更稳定。

实验 E:加入 cue 辅助损失

在模型 hidden state 上加一个辅助头:

cue_pred = Linear(hidden)

训练它预测当前 episode 的 cue 类型。

辅助损失:

L_aux = CE(cue_pred, cue_label)

总损失:

L = L_RL + λ_aux · L_aux

例如:

λ_aux = 0.1

这个实验用来验证:

只要持续检查 hidden state 里是否还保留 cue,崩塌会不会减少。

9. 需要记录的指标

为了确认是不是“线索通道退化导致默认偏置接管”,不要只看成功率。

建议记录下面这些指标。

9.1 成功率

success_rate

观察是否出现:

先升到 1.0,然后后期下降

9.2 出口选择分布

分别统计:

cue=A 时选择 TOP 的比例
cue=A 时选择 BOTTOM 的比例
cue=B 时选择 TOP 的比例
cue=B 时选择 BOTTOM 的比例

理想策略:

cue=A: TOP ≈ 100%
cue=B: BOTTOM ≈ 100%

崩塌策略:

cue=A: TOP ≈ 100%
cue=B: TOP ≈ 100%

或者:

cue=A: BOTTOM ≈ 100%
cue=B: BOTTOM ≈ 100%

这能直接看出模型是不是变成了固定出口策略。


9.3 policy entropy

记录出口决策点的策略熵:

H(π) = -Σ π(a|s) log π(a|s)

如果崩塌时熵接近 0,说明模型不是随机猜,而是非常确定地固定选择某个动作。


9.4 logit margin

记录出口决策点:

score_TOP - score_BOTTOM

分别按 cue 类型画图。

正常情况下应该是:

cue=A: score_TOP - score_BOTTOM > 0
cue=B: score_TOP - score_BOTTOM < 0

崩塌后可能变成:

cue=A: score_TOP - score_BOTTOM > 0
cue=B: score_TOP - score_BOTTOM > 0

也就是两个 cue 下 logit 同号。


9.5 hidden state 的 cue 可解码性

训练一个 probe,从 hidden state 预测 cue。

比如收集出口决策点的 hidden state:

h_exit

训练一个简单线性分类器:

probe(h_exit) -> cue

如果成功期:

probe accuracy ≈ 100%

崩塌前后下降到:

probe accuracy ≈ 50%

说明模型在决策点的 hidden state 已经不包含 cue 信息了。

这是验证“线索表征退化”的关键指标。


9.6 是否访问 cue

统计每个 episode 中:

agent 是否看到过 cue
agent 到达 cue 附近的频率
agent 从看到 cue 到出口之间间隔多少步

如果崩塌后 agent 不再访问 cue,说明问题可能发生在更早阶段:

不是记忆坏了,而是它根本不再去看线索。

如果它仍然访问 cue,但 hidden state 不含 cue,则说明:

它看到了,但没记住/没用上。

10. 如何区分几种崩塌原因

策略固定走一个出口只是表面现象,背后可能有几种原因。

10.1 不再去看线索

表现:

cue visitation rate 下降

说明 agent 学会了短路:

直接冲出口,不看线索

解决方向:

  • 给看线索一点辅助奖励;
  • 降低 step penalty;
  • curriculum 中强制必须看线索;
  • 增加错误出口惩罚。

10.2 看了线索但没记住

表现:

cue visitation 正常
hidden state probe accuracy 下降

说明问题在记忆通道。

解决方向:

  • 加 cue prediction auxiliary loss;
  • 增大 RNN hidden size;
  • 用 attention/memory;
  • 在训练中加入长延迟场景;
  • 保持探索。

10.3 记住了线索但输出头不用

表现:

probe 能预测 cue
但 action 不随 cue 变化

说明 hidden state 里有信息,但 policy head 没使用。

解决方向:

  • 加出口动作辅助监督;
  • 加 KL 限制;
  • 降低 policy head 学习率;
  • 使用 regularization 防止 output bias 接管。

10.4 策略过度确定,失败也拉不回来

表现:

entropy 接近 0
logit margin 极大

解决方向:

  • 熵正则;
  • ε-greedy;
  • PPO clip / KL penalty;
  • 限制 logit magnitude;
  • label smoothing 类似思想;
  • 降低学习率。

11. 为什么“不一定必须用熵正则”

熵正则的作用是让策略不要太早变成:

P(action) ≈ 1

它的好处是:

保留探索
保留纠错机会
防止策略过早饱和

但它不是唯一办法。

真正需要的是:

持续保留有效的纠错信号。

所以也可以用其它方法。


11.1 ε-greedy

训练时保持少量随机动作:

ε = 0.02 ~ 0.05

即:

95%~98% 按模型策略行动
2%~5% 随机动作

这相当于定期让模型“试错”。

但注意:

如果使用 policy gradient,随机动作来自行为策略,而不是纯当前策略,需要在实现上注意 off-policy 偏差。

简单实验中可以先不严格处理,但如果要严谨,最好记录行为策略概率或使用适合 off-policy 的方法。


11.2 偶尔强制访问线索

如果崩塌原因是 agent 不再看线索,那么只在出口处随机动作不够。

因为问题可能发生在前面:

它根本没有获取 cue。

这时可以设计:

部分 episode 中强制或鼓励 agent 先经过 cue 区域

例如:

  • curriculum 前期必须看到 cue;
  • 看到 cue 给一个小辅助奖励;
  • 没看到 cue 到出口不给奖励;
  • 随机初始化让看 cue 成为自然路径的一部分。

11.3 辅助任务:预测 cue

我认为这是最像“消防演练”的方法。

即使主任务一直成功,也定期检查:

模型内部还记不记得 cue?

做法是在 hidden state 上加辅助头:

hidden_state -> cue_pred

训练目标:

预测当前 episode 的 cue 类型

这样即使主奖励不给出强梯度,辅助损失也会持续维护 cue 表征。


11.4 偶尔故意走错出口可以吗?

直觉上可以,因为这样能让模型保持试错。

但要注意两点。

第一,如果动作是外部强行改的,不能简单当作模型自己采样的动作来做 on-policy 更新,否则会引入偏差。

第二,只在出口处故意走错,不一定能维护完整的信息链:

去看线索 -> 记住线索 -> 根据线索选出口

更好的方式是:

让模型偶尔探索整个过程
或者直接监督它记住 cue

所以“故意走错”不是最推荐的方案。

更推荐:

少量 ε-greedy + cue auxiliary loss

12. 我对这次现象的最终总结

这次策略崩塌可以总结成一句话:

模型成功后,使用线索的回路因为缺少探索和错误对比而缺乏维护;
其它更新力继续改变参数,导致线索项逐渐变弱;
一旦默认偏置压过线索项,策略就固定走一个出口;
由于策略已经高度确定,失败样本也很难产生足够梯度把线索回路修回来。

更直观地说:

模型不是忘了“该怎么走”,而是忘了“为什么这么走”。

成功期:

根据线索选择出口

崩塌期:

线索不用了,只剩默认动作

这说明对于需要记忆、线索、条件决策的任务,仅仅看成功率是不够的。

成功率为 1 不代表内部机制稳定。

还要检查:

模型是否仍然访问线索
hidden state 是否仍然包含线索
policy 是否仍然随线索变化
entropy 是否过低
logit 是否过度饱和

否则模型可能已经在内部慢慢失去关键能力,只是表面上还没暴露。


13. 以后遇到类似问题时的检查清单

如果一个 RL 模型先成功,后崩塌,可以按这个顺序检查:

行为层面

  • 是否还去看 cue?
  • 是否固定走某个出口?
  • 不同 cue 下动作分布是否不同?
  • 是否变成了短路策略?

策略层面

  • 出口决策点 entropy 是否接近 0?
  • logit margin 是否越来越大?
  • 策略是否过早饱和?

表征层面

  • hidden state 能否线性解码 cue?
  • cue 信息是在什么时候丢失的?
  • 是没看到 cue,还是看了没记住?

优化层面

  • entropy coef 是否过早降为 0?
  • step penalty 是否让看 cue 太亏?
  • value loss 是否干扰 encoder?
  • 学习率是否过大?
  • PPO clip / KL 是否过松?
  • 是否训练太久导致漂移?

修复方案

优先尝试:

1. 保留小熵正则
2. 保留小 ε-greedy
3. 加 cue prediction auxiliary loss
4. 记录 hidden state cue probe
5. 降低 step penalty 或增加错误出口惩罚
6. 使用 KL/PPO clip 限制策略漂移
7. 定期评估不同 cue 下的动作分布

14. 最后的一句话

这个实验给我的最大启发是:

长期成功本身也可能让模型忘记成功依赖的条件。

如果一个能力长期不被测试,它就可能在内部退化。

所以对 RL agent 来说,训练不只是让它学会正确行为,还要让它持续记得:

正确行为为什么正确。

这就是为什么需要探索、辅助任务、probe、演练和稳定性约束。