AI 模型微调数据集准备:从样本设计到评估集
wxk1991 Lv5

AI 模型微调数据集准备:从样本设计到评估集

微调成败,七成在数据。

很多人以为微调失败是因为:

1
2
3
4
学习率不对
epoch 不对
模型不够大
GPU 不够强

这些当然会影响结果。

但更常见的问题是:

1
训练数据本身就不清楚、不一致、不代表真实业务。

如果你拿一堆混乱样本去训练,模型只会更稳定地学会混乱。

这篇文章讲微调前最重要的一步:怎么准备训练集、验证集和评估集。


一、先定义任务,不要先收集数据

不要一上来就说:

1
我们有几千条客服对话,拿去微调吧。

这太粗。

先把任务定义清楚:

1
2
3
4
5
6
输入是什么?
输出是什么?
输出格式是什么?
哪些情况应该拒绝?
哪些情况应该转人工?
哪些字段必须稳定?

比如一个客服分类任务,可以定义成:

1
2
输入:用户和客服的最近一轮对话
输出:JSON,包含 category、risk_level、summary、suggested_action

这样才知道数据应该长什么样。

微调不是把已有数据倒进去。

微调是为一个明确任务构造示范。


二、样本格式要和线上推理一致

训练样本应该尽量接近线上真实请求。

如果线上请求是:

1
系统规则 + 用户输入 + 输出 JSON

训练样本也应该类似。

不要训练时给模型非常完整的上下文,上线时只给一句用户问题。

也不要训练时输出自然语言,上线时要求模型输出 JSON。

数据格式不一致,微调效果会打折。

一个聊天模型常见样本结构类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"messages": [
{
"role": "system",
"content": "你是客服质检助手,必须输出 JSON。"
},
{
"role": "user",
"content": "用户说:物流三天没更新,我要退款。"
},
{
"role": "assistant",
"content": "{\"category\":\"refund\",\"risk_level\":\"medium\",\"summary\":\"用户因物流延迟要求退款\",\"suggested_action\":\"核查物流状态并给出退款或补偿方案\"}"
}
]
}

不同平台具体格式可能不一样。

但原则一样:

1
2
训练时怎么问,上线时就尽量怎么问。
训练时怎么答,上线时就要求怎么答。

三、好样本比多样本更重要

很多官方和工程实践都会强调一点:

1
小而高质量的数据,通常比大而脏的数据更有价值。

一个常见误区是:

1
样本越多越好。

不一定。

如果样本里有大量错误标注、格式不一致、重复数据和低质量输出,数据越多,模型越难学到清晰规律。

好样本至少满足:

  • 输入真实
  • 输出正确
  • 格式一致
  • 边界清楚
  • 不含过时知识
  • 不含无意义废话
  • 标注规则前后一致

如果你只有 100 条高质量样本,也比 5000 条半自动乱标数据更适合作为第一版。


四、训练集要覆盖真实分布

训练集不能只覆盖简单样本。

比如客服分类,如果 90% 样本都是普通咨询,模型可能学得很好看。

但上线后真正麻烦的是:

  • 投诉
  • 退款
  • 威胁差评
  • 敏感内容
  • 恶意套话
  • 多意图混合
  • 用户表达不完整
  • 方言、错别字、口语

所以数据集要覆盖真实业务分布,也要故意加入边界案例。

一个实用比例可以是:

1
2
3
60% 常规样本
25% 高价值困难样本
15% 异常和边界样本

这个比例不是标准答案。

重点是不要只喂模型最容易的题。


五、标注规则要写成文档

多人标注时,一定要有标注规范。

否则同一条输入,A 标成退款,B 标成投诉,C 标成物流异常。

模型最后学到的就是混乱。

标注文档至少写:

  • 类别定义
  • 字段含义
  • 输出格式
  • 正例和反例
  • 优先级规则
  • 多标签冲突时怎么选
  • 不确定时怎么处理

比如:

1
2
3
如果用户明确提出退款,category 优先标 refund。
如果用户只表达不满但未提出具体诉求,category 标 complaint。
如果同时包含辱骂和退款,risk_level 至少为 high。

这些规则比训练参数更重要。

因为模型学的是你的标注体系。

标注体系不稳定,微调结果就不会稳定。


六、训练集、验证集、测试集要分开

不要把所有样本都拿去训练。

至少拆成:

1
2
3
训练集:用于训练
验证集:用于调参和中间观察
测试集:最终评估,不参与训练

如果数据不多,也至少留出一份 holdout eval。

比如:

1
2
3
80% 训练
10% 验证
10% 测试

关键是:

1
测试集不能泄漏到训练集里。

否则模型只是记住了题目,你会误以为它泛化能力很好。


七、评估集要包含“必须答对”的样本

普通准确率不够。

你还应该有一组关键评估样本。

比如:

1
2
3
4
5
敏感内容必须拒绝
高风险投诉必须标 high
JSON 必须可解析
金额字段不能编造
不知道时必须返回 unknown

这些样本不一定多,但必须非常重要。

可以把评估分成几类:

评估类型 看什么
格式评估 JSON 是否可解析,字段是否完整
业务评估 分类、抽取、建议是否正确
安全评估 是否拒绝不该回答的问题
鲁棒性评估 错别字、长文本、噪声输入是否稳定
回归评估 老版本能答对的问题,新版本是否还答对

微调不是只看训练 loss。

最终要看真实任务表现。


八、不要把模型输出直接当训练真值

用大模型辅助生成训练数据很常见。

但不要把它生成的结果不检查就拿去微调。

原因很简单:

1
模型会把自己的错误教给下一个模型。

如果要用模型辅助标注,建议这样做:

1
2
3
4
5
1. 模型先生成候选标注
2. 人类抽查和修正
3. 对高风险类别全量复核
4. 保留标注来源和版本
5. 把争议样本单独记录

模型辅助可以提高效率。

但最后进入训练集的样本,必须有人对质量负责。


九、清洗数据时要看这些问题

训练前可以做一轮数据检查。

重点看:

  • 是否有重复样本
  • 是否有空输出
  • JSON 是否能解析
  • 字段是否缺失
  • 类别是否超出枚举范围
  • 输出是否混入解释文字
  • 是否包含隐私数据
  • 是否有过长样本
  • 是否有明显错误标注
  • 是否有训练集和测试集重复

比如 JSON 输出可以先跑脚本检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json

required_fields = {"category", "risk_level", "summary", "suggested_action"}

def check_output(text: str) -> list[str]:
errors = []
try:
data = json.loads(text)
except json.JSONDecodeError:
return ["invalid json"]

missing = required_fields - set(data.keys())
if missing:
errors.append(f"missing fields: {missing}")

return errors

能用脚本检查的,就不要靠人工肉眼看。


十、第一版数据集不要太贪

第一版微调不要追求覆盖所有场景。

更好的策略是:

1
2
3
4
5
6
7
选一个清晰任务
准备一批高质量样本
训练一个小版本
跑 eval
看错误
补数据
再训练

微调不是一次性工程。

它更像一个循环:

1
数据 -> 训练 -> 评估 -> 错误分析 -> 补数据 -> 再训练

每一轮都应该回答:

1
2
3
这次新增数据解决了什么问题?
模型在哪些样本上变好了?
有没有让旧能力变差?

十一、一个数据集准备清单

微调前,可以按这个清单检查:

  • 任务定义是否清楚
  • 输出格式是否固定
  • 训练样本是否接近线上请求
  • 标注规则是否成文
  • 是否有高质量正例
  • 是否有困难样本和边界样本
  • 是否清理了重复和错误样本
  • 是否拆分训练集、验证集、测试集
  • 是否有固定 eval
  • 是否有隐私和敏感数据处理策略
  • 是否记录数据版本

如果这些都没准备好,先别急着调参。

数据还没站稳,调参只是换一种方式浪费时间。


十二、我的建议

微调数据集的核心不是“多”。

而是:

1
2
3
4
5
清楚
一致
真实
可评估
能覆盖失败模式

第一版可以小。

但一定要干净。

先用 50 到 200 条高质量样本验证方向。

如果有效,再继续扩充。

如果没效果,不要盲目加数据。先回头看任务定义、prompt、样本格式和评估方式。

微调最怕的不是数据少。

而是不知道数据到底在教模型什么。


参考资料