1、微调训练过程
本节先掌握: 训练围绕“预测 → 损失 → 调整”展开。模型根据输入预测答案,程序对照参考答案计算损失,再调整可训练权重。
沿用第 29 章的图书馆例子,先只看输入和参考答案:
输入:市图书馆周末开设儿童阅读课,读者可通过公众号预约。
请提取关键词,只输出关键词,并使用英文分号分隔。
参考答案:市图书馆;儿童阅读课;公众号预约第 28 章已经区分过:普通推理使用已有权重,微调会更新可训练权重。现在只跟踪这条样本,看训练程序怎样利用参考答案。
先跟着这条样本走一遍。答案里的“市图书馆”后面应该接英文分号,假设模型在这里还不太倾向于使用分号,训练程序会怎样处理?
- 模型读入文章、任务说明和前面的答案片段,计算下一个 token 的预测。
- 程序用参考答案里的分号检查预测,把模型给正确项的概率换算成一个损失值。
- 程序根据损失求出内部参数的调整方向,再按我们设定的更新规则改变可训练参数。
- 执行更新后,后续样本会使用新参数继续计算。实际可以先累积多批信息再更新,第 2 节会解释这种安排。
文章与参考答案没有被改写,改变的是模型内部用来计算预测的数值。 上面只是跟踪一个位置来理解流程,实际训练会汇总多个位置、多个样本的信息;一次更新也不保证每个位置都预测得更好。下面先看程序怎样衡量预测,再看它怎样更新参数。
1.1 预测结果与损失
在“市图书馆”后面,模型会为许多候选 token 计算概率,英文分号只是其中一项。参考答案要求这里使用分号,因此我们先检查模型给它分配了多少概率。下面比较同一个位置的两种假设预测,不是模型实测输出:
| 对同一个正确 token 的预测 | 分号的概率 | 怎样理解 |
|---|---|---|
| 预测 A | 10% | 模型给正确项的概率较低,这个位置的损失较大 |
| 预测 B | 80% | 模型给正确项的概率较高,这个位置的损失较小 |
程序要用一个可计算的数衡量预测,这个数就是 Loss(损失值)。在这里,给正确 token 的概率越高,算出的损失越小。它提供了训练要努力降低的目标,第 1.2 节再解释怎样据此调整参数。
本课程使用**交叉熵(cross entropy)**把模型给正确 token 分配的概率换算成损失。
因此,即使两次预测最后都选中了分号,它们的损失也可能不同。交叉熵还会检查“模型给正确项分配了多大的概率”,不只是记一次对或错。
先用于判断:Loss 是预测损失,不是关键词准确率。 它由参考答案中需要学习的多个位置汇总而来。训练使用参考答案中的前文预测后面的 token,具体位置对应关系可展开查看。
<details> <summary>进阶选读:预测位置怎样对应,为什么模型不能提前看答案</summary>分号的预测是怎样得到的? 第 29 章已将输入和参考答案组织成训练序列。为便于说明,假设答案开头按 [市] [图书馆] [;] 拆分;实际分词以工具处理结果为准:
| 要预测的位置 | 模型可以使用的内容 | 用来检查预测的参考 token |
|---|---|---|
| 答案开头 | 用户文章、任务说明和助手回答的起始标记 | 市 |
市 之后 | 上述上下文 + 参考答案中的 市 | 图书馆 |
市图书馆 之后 | 上述上下文 + 参考答案中的 市图书馆 | ; |
表中最后一行就是刚才检查的位置。虽然训练序列含有完整参考答案,但模型预测分号时,只能使用它之前的内容,不能看到待预测的分号及其后文。训练框架可以同时计算多个位置,每个位置都遵守这个限制。
训练时,前面的答案片段来自参考答案;实际让模型回答时,它要接着自己已经生成的内容往下预测。因此,训练不是先生成一整段回复,再拿两段文字逐字找不同。
一条参考答案里的文字、分号和结束标记都可能有需要预测的位置。程序计算并汇总需要计入的位置,得到训练使用的损失;哪些位置不计入,下一小节会说明。日志中的 Loss 不是答错了几个词,也不是关键词准确率。 第 9 节再结合实际日志说明汇总范围。
</details>本例每个预测位置都有一个确定的参考 token,按普通交叉熵计算,该位置的损失可以写成:
这个位置的 Loss = -ln(正确 token 的概率)
概率 0.1:-ln(0.1) ≈ 2.3026
概率 0.8:-ln(0.8) ≈ 0.2231ln 是自然对数,一种数学运算。它在这里把概率转换成损失:概率越接近 1,算出的损失越接近 0;概率越小,损失越大。
再假设一条短答案只有两个需要计分的位置,损失分别为 2.3026 和 0.2231。若按这两个位置取平均,得到约 1.2629。实际日志会进一步汇总多个样本和记录区间,见第 9 节。
公式与忽略标签的规则可对照 PyTorch 交叉熵说明。这里用概率解释公式;直接调用 PyTorch 的 CrossEntropyLoss 时,接口接收的是模型尚未转换成概率的原始分数,不要把这段手算当成接口调用代码。
1.1.1 训练标签与损失计算位置
输入中既有文章和任务说明,也有参考答案。那么,每个位置都要直接计算损失吗?
回到这个任务:我们希望模型学会“读文章、提取关键词”,而不是练习续写用户提交的文章。因此,本课程保留用户文章作为理解任务的上下文,主要根据助手答案中的预测位置计算损失。
这由课程配置中的 train_on_prompt: false 控制:用户文章提供上下文,其对应的预测位置不计入损失;助手答案对应的预测位置参与损失计算。下面的标签示例展示了这种区别。
如果改为 true,用户文章中相应的 token 预测也会计入损失,程序就同时练习续写这部分内容。尤其是文章较长、关键词答案较短时,损失中会多出大量文章位置。训练目标的组成变了,不能拿修改前后两个 Loss 的大小直接判断关键词提取是否改善。本课程保持 false,将直接监督集中在助手答案上。
训练程序用 **labels(训练标签)**指定这些位置,它是一串与输入位置对应的目标编号。采用本例的 train_on_prompt=False 设置时,Prompt 对应的标签通常记为 -100,表示计算损失时忽略这里;助手答案位置则保留要预测的目标编号。
把“能看到的上下文”和“用来检查预测的目标”分开看,就能理解为什么文章需要保留,却不一定直接计入损失:
图中按目标 token 的位置标记标签;训练内部会将前一位置的预测与下一个目标对齐。分词、角色边界和结束标记均为教学示意,实际仍需核对预处理结果。
-100 只标记忽略损失的位置,用户文章仍完整保留在 input_ids 中。 文章依然参与计算、占用序列长度,帮助模型理解任务。
第 29 章打印的模板文本只展示输入形式,没有展示训练标签。要确认实际哪些位置参与损失计算,还需要检查 LLaMA-Factory 预处理得到的 input_ids 和 labels,不能仅凭模板名称判断。第 4.2 节会结合长度和截断继续检查这一点。
参考答案的质量也会直接影响训练。 如果答案里多写了文章没有提到的关键词,训练程序仍会尝试提高这些词的概率。这就是为什么上一章既要检查分号,也要核对关键词含义,不能只确认 JSON 格式正确。
1.2 梯度与参数更新
现在,模型给正确分号的概率偏低,损失反映出了这个问题。但只拿到一个 Loss,程序还不知道内部那么多参数应该怎样改。
程序会根据损失算出各个可训练参数的调整线索,这些线索叫作梯度。再由优化器按规则执行更新,学习率控制步长。课程已经选好 AdamW 优化器,我们填写训练设置,计算和更新由程序完成。
Loss 描述当前预测的情况,梯度提供调整线索,优化器执行调整。
模型先从输入算出预测,这叫前向计算;得到损失后,沿着计算过程往回求梯度,这叫反向传播。求出梯度还不等于改好了权重,真正的更新由优化器执行。
把图书馆样本放回训练流程:
读图时,沿着“预测 → 损失 → 梯度 → 更新参数”看一遍,再看回到模型的虚线:下一批数据会使用更新后的参数重新计算预测。改变的是计算结果的产生方式,训练文件里的文章和答案仍保持原样。
先只看其中一个参数:在其他参数暂时不变时,它稍微增大一点,损失倾向于增大还是减小,变化有多明显?损失对这个参数的变化率,给出了当前位置的调整线索。把各个可训练参数的这些变化率放在一起,就是 Gradient(梯度)。
梯度由训练框架通过反向传播计算,不需要我们逐个修改参数去试,也不是在训练页面手工填写的数。
有了梯度,接下来由 **Optimizer(优化器)**按更新规则真正改变参数,**学习率(learning rate)**控制更新的步长。先假设某个参数和它的梯度如下,用最简单的规则算一次:
某个可训练参数:0.30
当前梯度:0.20
学习率:0.10
按最简单的梯度下降规则:
新参数 = 原参数 - 学习率 × 梯度
= 0.30 - 0.10 × 0.20
= 0.28这个参数的梯度是正数 0.20,表示在当前数值附近,增大它会使损失倾向于增大。因此,简单梯度下降往反方向调整,把参数从 0.30 减到 0.28;梯度为负时则相反。梯度描述的是当前位置附近的情况,步子迈得太大,仍可能越过合适位置,并不保证每步 Loss 都下降。
上面是简单梯度下降算例。课程训练使用 AdamW 优化器,还会参考之前梯度的统计信息,为各参数调整更新幅度。两种规则都需要先算出梯度,再执行参数更新。
回到图书馆任务,看到 Loss 偏高,下一步是检查数据与训练过程;训练结束后,再看新文章的关键词是否提取完整、是否按分号分隔。
实际训练通常把多条样本一起处理,还可能先累积几批梯度,再执行参数更新。下一节就来认识控制这个过程的设置。