多轮对话难,一半难在数据。 同一句话,放在第一轮和放在第五轮,正确的答法完全不同——数据要能体现这个差别,模型才学得到。 这一节先给七条能跑成代码的检查项,再说数据集从哪来、三个场景各自该长什么样。
上一章把通用模型调成了能干活的模型,这一章解决它最常被投诉的毛病:聊两句就忘事。 38 课 AI 训练师手册 讲的是标注与质检的通用做法, 这一节讲的是对话数据特有的那几条规矩——轮次、角色、边界。
一、七条原则,七行代码
教材把准备训练数据的要点列成七条原则。 这一节不逐字复述,而是把它们做成七条能跑的检查,理由是:原则写成文字谁都会同意, 写成代码才知道自己有没有做到。 第一条,对话必须以用户开头。 训练时模型学的是「接着往下说」,第一条就是助手的话,它没有可学的条件,这条数据等于半废。 第二条,角色必须交替。两条用户话连着,模型不知道中间少了什么,学下去会变成自问自答。
第三条,助手的回答不能为空。空回答在损失里贡献零,但它占着位置、算进批大小,等于白训。 第四条和第五条,轮数成对、不超过六轮。 太长的对话训练成本高,而且后几轮的梯度会盖住前几轮——模型记住的是结尾,不是开头。 第六条,末轮必须是助手。 末轮是用户的话,这条样本没有目标输出,训练框架会静默丢掉它:不报错,只是你的数据少了一批。
第七条,单条不能超长。超了要截断,而截断常常把答案切掉一半,模型于是学到「话说一半就停」。 还有一条要跨条目查:不能有完全重复的对话。 重复等于给这条加权,十条一样的比一条的权重高十倍,而这种事故单看每一条都正常。
二、定制数据集:从业务里倒着长出来
顺序是先有业务、再有场景、再有对话、最后才是数据格式。 反过来做——先定格式再编对话——写出来的是一堆「像对话的作文」。 四步:抽真实对话、筛出助手答得好的、补全断掉的轮次、最后对齐模板。 筛的标准是「业务上被采纳」,不是「读起来顺」。 这两条经常打架:一段啰嗦但把事办成了的对话,比一段漂亮但没解决问题的更该留。
补轮次是为了让数据完整。 真实对话到处是断点——用户没回、助手答错后重来、中途换人接手——这些都要整理成完整的两轮, 不能原样喂进去。 对齐模板是最后一步,也是最机械的一步。但这一步错了,前面全废,而且错得很安静。
三、三个场景,三种数据
任务型:报修、报价、下单。轮次短、槽位明确、必须精确。数据里要专门覆盖「参数缺了怎么追问」。 咨询型:政策、规范、方案。轮次长、指代多、答案要带依据。 数据里要覆盖「前面说过的不再重复」和「改主意了怎么跟上」。 闲聊型:几乎不需要多轮能力,一个滑动窗口就够。要把注意力放在前两类上——业务价值都在那儿。
三类混在一起训,模型会学会「答得模棱两可」,因为它分不清什么时候该精确、什么时候可以随便聊。
四、动手 Demo:把七条原则做成七行代码,逐条查这份数据
造十二条对话,故意埋进五类毛病:角色不交替、首轮是助手、回答为空、超长、重复。 然后按七条原则逐条查,看能不能全抓出来。 最后逐条修,再跑一遍——修完必须重跑,因为合并用户话这种修法很容易带出新问题。
def chk(ms):
bad = []
if ms[0][0] != U:
bad.append(u'首轮不是 user')
for i in range(1, len(ms)):
if ms[i][0] == ms[i - 1][0]:
bad.append(u'第 %d 轮角色没交替' % (i + 1))
if len(ms) % 2:
bad.append(u'轮数是奇数')
if _n(ms) > 512:
bad.append(u'超 512 token')
return bad
# 跨条目查重复:把整段拼成字符串当键
seen = {}
for cid, ms in BAD:
key = fmt(ms)
if key in seen:
print(cid, u'与', seen[key], u'重复')
seen[key] = cid
实跑输出:
--------------------------------------------------------------------
把七条原则做成七行代码,逐条查这份数据
--------------------------------------------------------------------
① 先看这份数据的骨架
--------------------------------------------------------------------
| 编号 | 轮数 | token |
|:---:|---:|---:|
| c01 | 2 | 72 |
| c02 | 2 | 67 |
| c03 | 3 | 59 |
| c04 | 2 | 41 |
| c05 | 2 | 11 |
| c06 | 2 | 43 |
| c07 | 4 | 97 |
| c08 | 2 | 63 |
| c09 | 2 | 72 |
| c10 | 2 | 596 |
| c11 | 9 | 211 |
| c12 | 3 | 49 |
十二条对话,最短两轮,最长九轮。真机上通常是几千条,这里十二条够看出毛病。
! 注意 c10 的 token 数——它只有两轮,却比九轮的 c11 还长。轮数和长度是两件事,体检要分开查。
② 七条原则,逐条跑
--------------------------------------------------------------------
| 原则 | 违反 | 编号 |
|---|---:|---|
| 首轮必须是 user | 1 | c04 |
| 角色必须交替 | 1 | c03 |
| 助手回答不能为空 | 1 | c05 |
| 轮数是偶数(成对) | 3 | c03、c11、c12 |
| 轮数不超过 6 | 1 | c11 |
| 末轮必须是 assistant | 3 | c04、c11、c12 |
| 单条不超过 512 token | 1 | c10 |
七条里有六条能一行判完,只有「不能重复」要跨条目比,所以放在下一步。
! 这七条是本课按工程实践归纳的,教材对应小节的表述更细。
要点不在于条数对不对,在于每一条都能落成一行代码——落不成代码的原则,检查时一定被跳过。
③ 重复的那一条要跨条目查
--------------------------------------------------------------------
| 重复条目 | 与谁重复 | 长度 |
|:---:|---|---|
| c09 | c01 | 72 token |
! 重复不会报错,只会悄悄给这条数据加权重。
出现十次,等于给它十倍权重——这是最容易被忽略的一类数据事故。
④ 逐条看问题落在哪
--------------------------------------------------------------------
| 编号 | 问题 |
|---|---|
| c03 | 第 2 轮角色没交替;轮数是奇数 |
| c04 | 首轮不是 user;末轮不是 assistant |
| c05 | 第 2 轮助手回答为空 |
| c10 | 共 596 token 超 512 |
| c11 | 轮数是奇数;轮数 9 超过 6;末轮不是 assistant |
| c12 | 轮数是奇数;末轮不是 assistant |
十二条里 6 条有问题,合计 11 处。
⑤ 修完重跑
--------------------------------------------------------------------
| 编号 | 轮数 | token | 检查 |
|:---:|---:|---:|---|
| c01 | 2 | 72 | 通过 |
| c02 | 2 | 67 | 通过 |
| c03 | 2 | 59 | 通过 |
| c04 | 2 | 41 | 通过 |
| c05 | 2 | 51 | 通过 |
| c06 | 2 | 43 | 通过 |
| c07 | 4 | 97 | 通过 |
| c08 | 2 | 63 | 通过 |
| c10 | 2 | 49 | 通过 |
| c11 | 6 | 140 | 通过 |
| c12 | 4 | 92 | 通过 |
修完剩 0 处问题,重复也去掉了(c09 与 c01 重复,直接删)。
! 几条修法值得记:c03 的两句 user 合并成一句、c04 的两轮换顺序、c10 的超长回答截断。
这三类修法覆盖了真实数据里八成的问题。
⑥ 真机上这份数据怎么用
--------------------------------------------------------------------
[真机代码 · 本沙箱不执行]
| from datasets import load_dataset
| from trl import SFTTrainer
|
| ds = load_dataset('json', data_files='train.json', split='train')
| trainer = SFTTrainer(model=model, train_dataset=ds,
| max_seq_length=512, dataset_text_field='text')
| trainer.train()
要 datasets 与 trl,本沙箱都没有,不执行;它读的就是上面那张 train.json。
①先看骨架。注意 c10:只有两轮,596 token,比九轮的 c11 还长。 轮数和长度是两件事,体检要分开查——把它们混成一个「长度」指标,就会漏掉一种。 ②七条原则逐条跑,每条都能一行判完。 违反最多的是「轮数是奇数」和「末轮必须是助手」,各三条,因为它们往往成对出现:末轮是用户, 轮数一定是奇数。 这两条其实是同一个毛病的两种说法。 检查项之间有重叠不是坏事,重叠的部分能告诉你「这是同一个根因」。 ③重复要跨条目查。c09 与 c01 一模一样,单看每一条都合格,合在一起就是给这条加了倍权。 这类事故最隐蔽——它不报错,只让模型对某几句话格外执着。 ④把所有问题列出来,十二条里六条有问题,合计十一处。 这个比例在真实数据里不算高,真实工单数据的第一版经常有一半不合格。 ⑤修完重跑,剩零处。三条修法覆盖了大部分情况:两句用户话合并成一句、两轮换顺序、超长截断。 注意 c05 修完是 51 token,比原来 11 多——补了一个完整回答。修数据会让 token 变多,这是正常的。 ⑥那段训练代码本沙箱跑不了,但它读的就是上面这份 json。 数据修没修好,跟训练能不能跑,是两件独立的事——所以要先在数据这一层把问题清掉。
30 秒实验
- 把「轮数不超过 6」改成 4,重跑第②步。看哪几条从通过变成违反,再想想:这个阈值该按什么定?
- 把 c05 的空答案换成一个空格,重跑。检查还抓得住吗?
(提示:
if not t.strip()和if not t是两回事) - 把 c07 复制三份塞进数据,重跑第③步。重复检查报出几条? 再想想:真实数据里「几乎一样」的算不算重复?
常见误区
- 以为检查脚本能保证数据质量。它只查形状,不查内容——答案胡说八道它一样放行。 形状检查是下限,内容要靠抽检。
- 把七条当成「必须全部满足」。真实数据里总有几条不满足,关键是知道哪条不满足、影响多大。 一条 20 token 的空回答,比一条 600 token 的超长样本危害小得多。
- 修完直接拿去训。 改一处常常带出另一处,尤其「合并两条用户话」这种修法——合完轮数变了,奇偶性可能又不对了。 修完必须重跑。
自测三问(点开对答案)
1. 为什么末轮必须是助手? —— 因为训练目标是助手那一段:模型学的是「给定前面所有内容,生成助手的话」。 末轮是用户,就没有目标可学,框架会静默丢掉这条样本,不报错。 2. 两条用户话连着会训出什么? —— 模型看不到中间缺了什么,会学成「用户说完用户又说」这种模式,表现是自己接自己的话, 或者把两句用户话当成一个指令的两个部分。 3. 「完全重复」为什么必须跨条目查? —— 因为单看每一条都合法,重复是条目之间的关系。 不查关系,这类事故永远发现不了,而它的后果是悄悄给某些样本加权。原则清楚了,下一节把它们用在一份真数据上:从写 JSON 到转模板,走一遍完整流程。
- ☐ (本篇完,下一篇 4.2 实操构造多轮对话微调训练数据集)
相关笔记
- 38 课 AI 训练师手册:标注规范与一致性检查
- 40 课 图解机器学习:样本权重与重复采样的影响
- 数据集准备:模板会让 token 涨两三倍
- 🦙 Llama 大模型实践 · 索引