SFT 的 Loss 降了,模型为什么还会答错?

「Python 是什么?」这道题很简单,拿来拆 SFT 的 loss 刚刚好。示范回答是「Python 是一种编程语言。」我们可以看清楚一件事:模型给标准答案的概率变高,和它在推理时自己把整句话答对,分别是怎么测的。

先算第一个 token,再看哪一步最难

假设 tokenizer 把示范切成 [Python, 是, 一种, 编程, 语言, 。],再映射成 token ID。这里的切分只为演示,实际结果由 tokenizer 决定,空格也可能影响边界。模型读到问题和回答起始标记后,会给整个词表打分。假设第一个位置的概率分布如下。

候选 token 概率
Python 0.70
Java 0.10
C++ 0.05
其他 token 合计 0.15

标准答案的第一个 token 是 Python。在普通的硬标签交叉熵里,它在目标分布中的权重是 1,其余候选 token 的权重为 0。因此这个位置的损失就是负对数似然 −ln(0.70) ≈ 0.357。给目标 token 的概率越接近 1,这一项就越接近 0。如果只给 0.01,损失约为 4.605,低概率的目标会在平均值里留下明显影响。PyTorch CrossEntropyLoss 文档

再给出一组虚构的后续概率。每行的概率都以真实前缀为条件。

目标 token 模型给目标的概率 单 token 损失
Python 0.70 0.357
是 0.80 0.223
一种 0.50 0.693
编程 0.40 0.916
语言 0.90 0.105
。 0.80 0.223

只监督这六个位置时,样本的平均 token loss 约为 0.420。表里最难的是「编程」,它的损失比开头的 Python 大得多。看单 token 的数据,比只看曲线上一个均值更容易找出模型在哪些词或字段上犹豫。实际聊天模板若还监督结束标记,就要把那个位置一起纳入分子和分母。

教师强制给了模型怎样的前缀?

假设另一个 checkpoint 在第一个位置更偏向 Java。如果让它自由生成,后面就会看到「Java」作为历史,有可能顺势写出一段关于 Java 的介绍。SFT 计算第二个位置时,喂进去的前缀依然是问题加示范里的「Python」,所以它算的是给定这段前缀后预测「是」的概率。第一个位置的错误会体现在它自己的 loss 上,后面的计分仍使用参考答案的前缀。

这就是 teacher forcing(教师强制)。训练时已有完整示范,因果注意力让各位置读取各自能看到的前缀,可以在一次前向里并行拿到所有位置的 logits。推理则得把自己选出来的 token 接回上下文。两种前缀分布有差异,开头选错可能使后面的回答偏离原任务,也可能在后续上下文中被纠正。想知道模型能否从自己的错误里恢复,得实际让它生成一遍。

掩码和平均值各查一遍

在 response-only 的配置中,「Python 是什么?」仍然要输入模型,让它知道该回答什么。问题对应的目标位置可以用 loss mask 标记为忽略,只对回答区间计算交叉熵。在 PyTorch 风格的实现里,常用 -100 表示忽略标签。排查时可以把参与计分的 token 解码出来。如果只剩句尾标点,或者开头的 Python 被漏掉,即使 loss 下降,也只说明模型更擅长预测当前掩码覆盖的内容。前一篇:Shift 与 Mask 的对齐和代码

比较两个 checkpoint,我会在同一批验证题上分别记录三项:回答区间的 token loss、关键位置(比如实体名或工具名)的目标概率,以及自由生成时的任务通过率。比如模型更会写「是一种」「语言」,但容易把开头实体答错,前两项会告诉你均值改善来自哪里,最后一项能看出这种改善有没有帮助把整道题答对。

相关推荐
I Am a robert girl1 个月前
从科学代码库到智能体训练场:ScienceIDE 的架构拆解与工程启示
架构·强化学习·代码仓库·科学计算·监督微调·智能体训练·可编程环境
HyperAI超神经4 个月前
数据集汇总丨英伟达开源Nemotron系列数据集,超10T tokens+40M 条后训练样本,覆盖数学推理/代码生成/多语言对话
人工智能·大模型·数据集·nvidia·预训练·代码生成·监督微调
阿杰学AI7 个月前
AI核心知识119—大语言模型之 监督微调 (简洁且通俗易懂版)
人工智能·深度学习·ai·语言模型·aigc·sft·监督微调
阿杰学AI1 年前
AI核心知识19——大语言模型之SFT(简洁且通俗易懂版)
人工智能·ai·语言模型·aigc·监督微调