监督微调数据准备:从格式校验到高质量评测集

2026-08-27 45 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

监督微调(SFT)的效果上限,往往在训练开始前就已经确定了。模型并不会自动修复含糊的指令、错误的答案、混乱的对话结构或不一致的工具调用格式;它只会尽可能学习数据中反复出现的模式。

因此,数据准备不应被看成把几段文本转换成 JSONL 的机械步骤,而应当包括质量检查、统一格式、明确推理与工具调用结构,以及构造有代表性的训练集和评测集。

先检查内容质量,再讨论数据规模

高质量 SFT 数据至少需要回答几个问题:

  • 用户请求是否清晰、完整,并且与目标任务相关?
  • assistant 的回答是否真正解决了请求,而不是只包含泛泛解释?
  • 答案是否事实正确、格式稳定,并符合产品或业务约束?
  • 同类问题的回答风格是否一致?
  • 数据中是否存在重复样本、相互矛盾的答案或泄露评测集的内容?

数据量大并不等于信号强。如果同一错误答案被复制数千次,它可能比少量高质量样本更强地影响模型。可以先按任务类型、难度、领域和来源统计样本,再对每一类抽样人工检查。自动规则适合发现缺字段、空内容、非法 JSON 和过长样本;事实正确性、回答是否真正满足意图,通常仍需要人工或模型辅助审查。

一个实用的质量记录表可以保存 sample_id、任务类别、来源、人工评分、问题标签和修订原因。这样,后续发现模型行为异常时,可以追溯到具体数据来源,而不是凭感觉重新清洗全部数据。

用 JSONL 表达多轮对话

对话式 SFT 通常将每个训练样本放在 JSONL 的一行中。每条记录包含一个 messages 数组,数组中的消息按顺序使用 rolecontent 表示参与者及其内容:

{"messages":[{"role":"user","content":"把以下句子翻译成英文:数据质量决定微调效果。"},{"role":"assistant","content":"Data quality determines fine-tuning results."}]}

多轮样本可以包含多个 userassistant 消息。系统消息是否允许、字段名称是否必须使用 messages,取决于训练框架或服务接口,因此应在准备阶段确定目标格式,并使用同一套规范生成全部文件。

数据转换时要特别注意:

  • 不要把多个独立任务拼进同一条对话。
  • 保留必要的上下文,删除与任务无关的历史内容。
  • 明确 assistant 的目标回答,避免把草稿、审核意见或内部标记混入训练文本。
  • 统一换行、空白、特殊字符和语言编码。
  • 对超长对话制定截断策略,并记录被截断的样本。

可以用下面的 Python 脚本做一轮基础校验。示例假设输入文件名为 sft.jsonl,每行是一条包含 messages 的 JSON 记录;它不会判断答案是否事实正确,但能尽早发现结构性问题。

import json
from pathlib import Path

path = Path("sft.jsonl")
allowed_roles = {"system", "user", "assistant", "tool"}
errors = []
seen = set()

for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
    if not line.strip():
        continue
    try:
        item = json.loads(line)
    except json.JSONDecodeError as exc:
        errors.append(f"line {line_no}: invalid JSON ({exc.msg})")
        continue

    messages = item.get("messages")
    if not isinstance(messages, list) or not messages:
        errors.append(f"line {line_no}: messages must be a non-empty list")
        continue

    normalized = []
    for index, message in enumerate(messages):
        if not isinstance(message, dict):
            errors.append(f"line {line_no}, message {index}: must be an object")
            continue
        role = message.get("role")
        content = message.get("content")
        if role not in allowed_roles:
            errors.append(f"line {line_no}, message {index}: unsupported role {role!r}")
        if not isinstance(content, str) or not content.strip():
            errors.append(f"line {line_no}, message {index}: empty content")
        normalized.append((role, content))

    sample_id = json.dumps(normalized, ensure_ascii=False, sort_keys=True)
    if sample_id in seen:
        errors.append(f"line {line_no}: duplicate conversation")
    seen.add(sample_id)

if errors:
    print("FAILED")
    print("\\n".join(errors))
    raise SystemExit(1)

print(f"OK: {len(seen)} conversations checked")

实际项目还应加入字段长度、语言、敏感信息、重复度和任务特定约束。例如,分类数据可以检查标签是否属于允许集合;结构化输出数据可以解析 assistant 内容,确认它确实是合法 JSON。

推理数据和工具调用要有明确边界

如果训练目标包含推理过程,数据格式需要清楚区分最终答案与可公开的推理内容。可以采用显式字段,例如 reasoninganswer,也可以把它们放进约定好的消息内容中。关键不在字段名字,而在于训练与推理阶段使用同一种可预测结构,并确认哪些推理内容可以被暴露给最终用户。

一个概念性的结构可以这样写:

{"messages":[{"role":"user","content":"计算 37 * 24。"},{"role":"assistant","reasoning":"37 * 20 = 740,37 * 4 = 148,合计 888。","content":"888"}]}

工具调用数据则应呈现完整链路:用户请求、assistant 发起的工具调用、工具返回结果,以及 assistant 根据结果生成的最终回答。工具名称、参数结构和返回值类型必须稳定;同一个工具不能在不同样本里一会儿使用字符串参数,一会儿使用对象参数。

可以这样表达一个简化的天气查询流程:

{"messages":[{"role":"user","content":"北京今天的天气怎么样?"},{"role":"assistant","tool_calls":[{"name":"get_weather","arguments":{"city":"北京"}}]},{"role":"tool","name":"get_weather","content":"晴,最高温度 28°C"},{"role":"assistant","content":"北京今天晴,最高温度约 28°C。"}]}

这个示例中的字段必须按照目标训练平台的 schema 调整。不要直接假设所有平台都接受同样的 tool_callsargumentsname 字段。准备数据时,应先用少量样本完成端到端解析和训练试跑,再批量生成文件。

训练集与评测集要代表真实任务

一个代表性的拆分不是简单地随机抽取最后 10% 的数据。评测集需要覆盖上线后真正关心的任务分布,包括常见请求、边界案例、不同难度、不同表达方式和关键业务场景。

拆分时应避免近似重复样本跨越训练集和评测集,否则评测结果会被高估。对于同一用户、同一文档、同一问题模板生成的样本,最好按来源或任务组进行分组后再拆分。评测集还应在训练完成前固定,并限制其进入清洗、调参和提示词迭代流程的频率。

比例可以根据数据规模和任务风险调整。小规模项目可以保留一个足够覆盖关键场景的评测集;数据量较大时,可以使用训练集、开发集和最终评测集三部分。这里的重点是代表性、独立性和可重复,而不是追求某个固定百分比。

一份可执行的准备清单

上线训练前,可以逐项确认:

  • 所有记录都能被目标训练工具解析。
  • 每条对话都有清晰的用户目标和有效的 assistant 输出。
  • 角色、字段、工具名称和参数 schema 保持一致。
  • 已处理重复、冲突、空内容、敏感信息和过长样本。
  • 推理内容的可见范围已经明确。
  • 评测集按任务来源或样本组隔离,且没有明显重复。
  • 已保存数据版本、校验结果、修改记录和拆分规则。

SFT 数据准备的产物不只是一个 JSONL 文件,而是一套可检查、可追溯、可复现的数据协议。把质量标准和格式校验前置,通常比训练结束后反复调整超参数更能直接改善结果;同时也应保留原始数据和版本记录,方便定位数据变化对模型行为的影响。


相关推荐