训练常见问题
我应该关注哪些指标?
在执行语言模型的经典监督微调时,损失(尤其是验证损失)可以作为训练进度的良好指标。但是,在强化学习 (RL) 中,损失对于模型的性能来说信息量较少,并且其值可能会波动,而实际性能却有所提高。
为了解决这个问题,我们建议首先关注两个关键指标
平均奖励:主要目标是在 RL 训练期间最大化模型获得的奖励。目标 KL 散度:KL 散度(Kullback-Leibler 散度)衡量两个概率分布之间的差异。在 RL 训练的背景下,我们使用它来量化当前模型和参考模型之间的差异。理想情况下,我们希望将 KL 散度保持在 0 到 10 之间,以确保模型生成的文本与参考模型生成的文本保持一致。
但是,还有更多指标可用于调试,请查看日志部分。
为什么我们使用参考模型,以及 KL 散度的用途是什么?
在训练 RL 模型时,仅针对奖励进行优化可能会导致意想不到的行为,模型会以不符合良好语言生成的方式利用环境。在 RLHF 的情况下,我们使用一个奖励模型,该模型经过训练可以预测人类是否会对生成的文本给予较高排名。
然而,针对奖励模型进行优化的 RL 模型可能会学习到产生高奖励但并不代表良好语言的模式。这可能导致极端情况,例如模型生成包含过多感叹号或表情符号的文本以最大化奖励。在某些最坏情况下,模型可能会生成与自然语言完全无关的模式,但仍然获得高奖励,类似于对抗攻击。
图:来自 https://huggingface.co/papers/1909.08593 的无 KL 惩罚样本。
为了解决这个问题,我们在奖励函数中添加了一个基于当前模型和参考模型之间 KL 散度的惩罚项。通过这样做,我们鼓励模型保持接近参考模型生成的输出。
负 KL 散度存在什么问题?
如果通过纯从模型分布中采样来生成文本,通常情况下工作良好。但是,当使用 generate
方法时,存在一些注意事项,因为它并不总是纯采样,具体取决于设置,这可能导致 KL 散度变为负数。本质上,当活动模型达到 log_p_token_active < log_p_token_ref
时,我们会得到负的 KL 散度。这可能发生在几种情况下
- top-k 采样:模型可以平滑概率分布,导致 top-k 标记的概率小于参考模型中的概率,但它们仍然会被选中
- min_length:在达到
min_length
之前,此设置会忽略 EOS 标记。因此,模型可以为 EOS 标记分配非常低的 log 概率,并在达到 min_length 之前为所有其他标记分配非常高的概率
这些只是一些例子。为什么负 KL 会成为问题?总奖励 R
计算为 R = r - beta * KL
,因此如果模型可以学会如何将 KL 散度降低到负值,它实际上会获得正奖励。在许多情况下,利用生成中的这种错误比实际学习奖励函数要容易得多。此外,KL 可以变得任意小,因此实际奖励与之相比可能非常小。
那么,你应该如何为 PPO 训练生成文本呢?让我们来看一下!
如何为训练生成文本?
为了避免上述 KL 问题,我们建议使用以下设置
generation_kwargs = {
"min_length": -1, # don't ignore the EOS token (see above)
"top_k": 0.0, # no top-k sampling
"top_p": 1.0, # no nucleus sampling
"do_sample": True, # yes, we want to sample
"pad_token_id": tokenizer.eos_token_id, # most decoder models don't have a padding token - use EOS token instead
"max_new_tokens": 32, # specify how many tokens you want to generate at most
}
使用这些设置,我们通常不会遇到任何问题。你也可以尝试其他设置,但如果你遇到负 KL 散度问题,请尝试回到这些设置并查看问题是否仍然存在。
如何调试你自己的用例?
由于 RL 管道的复杂性,调试它可能具有挑战性。以下是一些使调试过程更容易的技巧和建议
- 从一个可工作的示例开始:从 trl 存储库中的一个可工作的示例开始,并逐渐修改它以适应你的特定用例。一次更改所有内容可能会使识别潜在问题根源变得困难。例如,你可以从替换示例中的模型开始,一旦你确定了最佳超参数,尝试切换到你的数据集和奖励模型。如果你一次更改所有内容,你将不知道潜在问题来自哪里。
- 从小规模开始,然后扩展:训练大型模型可能非常缓慢,并且可能需要几个小时或几天才能看到任何改进。对于调试来说,这不是一个方便的时间尺度,因此请尝试在开发阶段使用小型模型变体,并在其工作后进行扩展。话虽如此,有时你必须小心,因为小型模型可能也没有能力解决复杂的任务。
- 从简单开始:尝试从一个最小的示例开始,并在此基础上构建复杂性。例如,你的用例可能需要一个由许多不同奖励组成的复杂奖励函数——尝试首先使用一个信号,看看是否可以对其进行优化,然后在此基础上添加更多复杂性。
- 检查生成的文本:检查模型生成的文本始终是一个好主意。也许你的后处理或提示中存在错误。由于设置不当,你可能会过早地截断生成。这些事情在指标上很难看到,但如果你查看生成的结果,就会非常明显。
- 检查奖励模型:如果你的奖励没有随着时间的推移而提高,那么奖励模型可能存在问题。你可以查看极端情况,看看它是否按预期工作:例如,在情感案例中,你可以检查简单的正面和负面示例是否真的获得了不同的奖励。并且你可以查看数据集的分布。最后,奖励可能受模型无法影响的查询支配,因此你可能需要对其进行归一化(例如,查询+响应的奖励减去查询的奖励)。
这些只是一些我们发现有用的技巧——如果你有更多有用的技巧,请随时打开 PR 来添加它们!
< > 在 GitHub 上更新