Chapter 02

数据集准备:格式、清洗与质量

数据是微调的核心。糟糕的数据会让最好的训练框架也无能为力。本章建立系统的数据工程思维。

主流数据格式

微调数据本质上是"输入-输出"对,但不同格式适合不同场景:

Alpaca 格式

Stanford Alpaca 提出的经典指令格式,适合单轮指令跟随任务:

{
  "instruction": "将以下中文翻译成英文",
  "input": "人工智能正在改变世界",
  "output": "Artificial intelligence is changing the world"
}

// input 可以为空(纯指令场景):
{
  "instruction": "写一首关于秋天的五言绝句",
  "input": "",
  "output": "秋风吹落叶,红霜染山林。\n..."
}

ShareGPT / 对话格式

多轮对话格式,适合聊天机器人和对话系统微调:

{
  "conversations": [
    {"from": "system", "value": "你是一个专业的法律顾问..."},
    {"from": "human",  "value": "劳动合同试用期最长多久?"},
    {"from": "gpt",   "value": "根据《劳动合同法》第19条..."},
    {"from": "human",  "value": "试用期可以不缴社保吗?"},
    {"from": "gpt",   "value": "不可以。试用期属于劳动关系存续期间..."}
  ]
}

Hugging Face Chat Template

现代模型普遍使用的格式,由 tokenizer 模板渲染:

# Llama 3 / ChatML 格式
messages = [
    {"role": "system",    "content": "你是专业的代码审查员..."},
    {"role": "user",      "content": "请审查这段 Python 代码"},
    {"role": "assistant", "content": "我发现以下几个问题:..."}
]
# 转化为训练文本(由 tokenizer.apply_chat_template 完成)
text = tokenizer.apply_chat_template(messages, tokenize=False)
格式选择原则 用你的目标模型的原生格式。Llama 3 用 ChatML,Qwen 用它自己的格式,Mistral 用 [INST]...[/INST]。格式错误会导致训练效果极差,因为模型学不到正确的"角色边界"。

数据质量的五个维度

准确性(Accuracy)
输出内容是否事实正确?这是最重要的维度。错误数据会让模型学会"自信地犯错",比没数据更糟糕。
多样性(Diversity)
数据是否覆盖不同表达方式、不同难度、边界 case?单一模式的数据会导致模型过拟合,遇到变体就失效。
格式一致性(Consistency)
整个数据集的格式是否统一?混杂格式会让模型困惑,导致输出格式不稳定。
长度分布(Length Distribution)
训练数据的长度分布应覆盖实际使用场景。过多短数据会导致模型输出过短;过多长数据会浪费显存。
覆盖率(Coverage)
是否覆盖了业务中所有关键子任务?遗漏的场景在推理时会退化到基座模型行为。

数据清洗流水线

from datasets import Dataset
import re

def clean_dataset(examples):
    cleaned = []
    for item in examples:
        # 1. 过滤过短的输出(少于 20 字符)
        if len(item['output']) < 20:
            continue

        # 2. 过滤明显乱码(非 UTF-8 合法字符比例)
        if has_garbled_text(item['output']):
            continue

        # 3. 过滤包含敏感词的数据
        if contains_pii(item['output']):
            continue

        # 4. 去除首尾空白和异常字符
        item['output'] = item['output'].strip()
        item['output'] = re.sub(r'\n{3,}', '\n\n', item['output'])

        # 5. 截断超长数据(根据模型 max_length 设置)
        total_len = len(item['instruction']) + len(item['output'])
        if total_len > 4096:
            item['output'] = item['output'][:3000]

        cleaned.append(item)
    return cleaned

数据去重

重复数据会让模型过度拟合某些输出模式:

from datasketch import MinHash, MinHashLSH

def dedup_with_minhash(texts, threshold=0.8):
    """使用 MinHash LSH 进行模糊去重"""
    lsh = MinHashLSH(threshold=threshold, num_perm=128)
    minhashes = {}
    unique_indices = []

    for i, text in enumerate(texts):
        m = MinHash(num_perm=128)
        for word in text.split():
            m.update(word.encode('utf-8'))

        result = lsh.query(m)
        if not result:  # 没有重复
            lsh.insert(str(i), m)
            unique_indices.append(i)

    return unique_indices

使用 LLM 生成合成数据

当真实数据不足时,可以用强大的 LLM(GPT-4、Claude)生成高质量合成数据:

import anthropic

client = anthropic.Anthropic()

def generate_training_sample(topic: str, style: str) -> dict:
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        messages=[{
            "role": "user",
            "content": f"""生成一条高质量的训练样本,格式为 JSON:
主题:{topic}
风格:{style}

要求:
- instruction 字段:一个真实用户会问的问题
- output 字段:专业、准确、格式规范的回答
- 输出纯 JSON,不要额外说明"""
        }]
    )
    return json.loads(response.content[0].text)
合成数据的质量控制 合成数据不能直接使用,需要人工抽样验证准确率(目标 >95%),过滤 AI 常见的"废话模板"("当然!很高兴为您解答..."),并确保风格与真实业务场景匹配。

数据标注的最佳实践

标注一致性(Inter-annotator Agreement)
多个标注者对同一样本的标注结果一致性,通常用 Cohen's Kappa(κ)衡量。κ > 0.8 表示高度一致,是高质量标注数据集的基本要求。一致性低说明任务定义不够清晰,或标注指南有歧义,需要先改进标注指南,再重新标注。在开始大规模标注前,做 50-100 条的一致性测试,发现问题早期修正。
数据飞轮(Data Flywheel)
高质量数据收集的正向循环:部署初始模型 → 收集用户真实反馈(好/差评) → 将高质量的真实交互数据加入训练集 → 训练改进后的模型 → 重复循环。这是为什么大公司的模型越用越好的核心机制。设计产品时,要从第一天就考虑如何收集对改进模型有价值的反馈数据。
困难样本挖掘(Hard Negative Mining)
专门收集模型表现差的样本(错误案例、边界情况)用于训练。方法:定期在测试集上运行模型,找出置信度低或出错的样本;用语义相似度找出容易混淆的样本对。困难样本在训练集中的比例建议 20-30%,它们对提升模型的鲁棒性贡献远超普通样本。
数据增强(Data Augmentation)
通过变换扩充数据集,提高模型的泛化能力。常用 NLP 增强方法:同义词替换(用近义词替换非关键词);回译(中文→英文→中文,产生相似但表达不同的句子);随机删除/调换词序(增加鲁棒性);用 LLM 改写(让 GPT-4 生成同义但风格不同的版本)。注意:增强后的数据质量要验证,不能引入语义错误。

数据集多样性的量化方法

指令多样性评估
衡量训练数据中指令类型的覆盖广度。方法:用 sentence-transformers 将所有 instruction 编码为向量,可视化聚类分布(t-SNE/UMAP)。如果 80% 的数据集中在少数几个聚类,说明数据多样性不足。健康的数据集应有分散均匀的向量分布。工具:用 sentence-transformers/all-MiniLM-L6-v2 编码,成本低且效果好。
词汇覆盖率(Vocabulary Coverage)
训练数据的词汇量相对于模型词表的比例。领域特定词汇(如医疗术语、法律条文)如果在训练数据中出现次数过少(<5次),模型学习效果有限。检查方法:对比训练数据中领域关键词出现频率,对低频关键词补充专项训练样本。
难度分层(Difficulty Stratification)
训练样本应覆盖不同难度层次:基础(70%)、进阶(20%)、困难(10%)。难度的评估方法:用基座模型对所有样本跑推理,根据基座模型的错误率作为难度标签。困难样本(基座模型错误率 >70%)应专门收集和标注,它们是提升模型边界能力的关键。

数据集拆分与检验

from datasets import Dataset
import json

# 加载并拆分数据集
data = json.load(open("training_data.json"))
dataset = Dataset.from_list(data)

# 90% 训练 / 10% 验证
split = dataset.train_test_split(test_size=0.1, seed=42)
train_ds = split['train']
eval_ds  = split['test']

# 基本统计检验
print(f"训练集:{len(train_ds)} 条")
print(f"验证集:{len(eval_ds)} 条")

# 输出长度分布
lengths = [len(x['output']) for x in train_ds]
print(f"平均长度:{sum(lengths)/len(lengths):.0f}")
print(f"最大长度:{max(lengths)}")
本章核心要点
  • 数据格式的重要性:必须与目标基座模型的 Chat Template 完全一致(Llama 3 用 <|start_header_id|>,Qwen 用 <|im_start|>)。格式错误是微调效果差的首要原因,比数据质量问题更致命。
  • 数据质量 > 数量:500 条高质量、一致性强的训练样本,通常优于 5000 条参差不齐的数据。清洗流水线的核心步骤:过滤过短/过长样本 → 去重(MinHash)→ 格式验证 → 人工抽检 10%。
  • 合成数据生成:用 GPT-4o 或 Claude 生成训练数据是扩充规模的有效方法,但需要人工审核 20-30% 防止"幻觉传染"。合成数据与真实数据混合(比例 1:3 到 1:5)效果最佳。
  • 数据分布:训练集的任务类型分布应与生产环境的实际使用分布接近。不要用 100% 的某类数据——多样性防止过拟合,也保持模型的泛化能力。
  • 验证集不可少:必须留出 5-10% 的验证集(与训练集分布相同但不重叠),用于监控训练过程中的过拟合。不能只看训练 loss。