Chapter 04

数据集准备

垃圾进、垃圾出。搞懂指令格式、chat template 与 packing,把数据整理成模型能高效学习的样子。

4.1 微调数据长什么样

指令微调(SFT)的数据本质是"问-答"对:给模型一个指令/问题,告诉它期望的回答。模型学的是"看到这样的输入,该生成这样的输出"。数据质量比数量更重要——1000 条高质量样本常胜过 10 万条噪声数据。

指令数据(Instruction Data)
由 instruction(指令)、可选 input(补充输入)、output(期望回答)组成的样本,SFT 的标准原料。
chat template(对话模板)
把多轮对话拼成模型训练时见过的格式的模板,含 role 标记与特殊 token。必须与模型匹配,否则效果崩。
Alpaca 格式
经典单轮格式:{instruction, input, output}。简单直观,适合指令跟随类数据。
ShareGPT 格式
多轮对话格式:{"conversations": [{from, value}, ...]},用 human/gpt 交替,适合对话数据。
formatting_func
一个把原始样本转成训练文本字符串的函数,传给 SFTTrainer,让它知道如何拼接你的字段。
packing(打包)
把多条短样本拼进一个 max_seq_length 序列,减少 padding 浪费、提升吞吐。适合大量短样本。
train/eval split(训练/验证划分)
留出一小部分数据(如 5%-10%)做验证,用来监控是否过拟合、判断何时停。
EOS token
序列结束标记。训练文本末尾必须补 eos,否则模型学不会"何时停下",推理会停不住。

4.2 加载数据集

用 HuggingFace datasets 加载,来源可以是 Hub、本地 JSON/CSV/Parquet:

from datasets import load_dataset

# 从 Hub 加载 Alpaca 中文数据
ds = load_dataset("yahma/alpaca-cleaned", split="train")

# 或从本地 JSON(每行一个 {instruction, input, output})
# ds = load_dataset("json", data_files="data.json", split="train")

print(ds[0])
# {'instruction': '解释什么是光合作用', 'input': '', 'output': '光合作用是...'}

4.3 应用 chat template(推荐)

对 Instruct/Chat 模型,最稳的做法是用 Unsloth 的 get_chat_template 套上与模型匹配的模板,再把对话渲染成训练文本:

from unsloth.chat_templates import get_chat_template

tokenizer = get_chat_template(tokenizer, chat_template="qwen-2.5")

def to_chat(example):
    msgs = [
        {"role": "user",      "content": example["instruction"]},
        {"role": "assistant", "content": example["output"]},
    ]
    text = tokenizer.apply_chat_template(
        msgs, tokenize=False, add_generation_prompt=False)
    return {"text": text}

ds = ds.map(to_chat)
print(ds[0]["text"][:200])
模板必须对齐模型

Qwen 用 qwen-2.5,Llama-3 用 llama-3.1,Gemma 用 gemma……用错模板 = 训练时的格式和模型认识的格式不一致,微调基本白费。

4.4 Alpaca 风格 formatting_func

若不想用 chat template,也可以自定义一个纯文本模板(注意末尾补 EOS):

EOS = tokenizer.eos_token

alpaca_tpl = """### 指令:
{}

### 输入:
{}

### 回答:
{}"""

def formatting_func(examples):
    texts = []
    for ins, inp, out in zip(examples["instruction"],
                              examples["input"], examples["output"]):
        texts.append(alpaca_tpl.format(ins, inp, out) + EOS)  # 必须补 EOS
    return {"text": texts}

ds = ds.map(formatting_func, batched=True)

4.5 packing:短样本提速

packing=True 会把多条短文本拼进一个序列,减少 padding 空转,短样本场景吞吐可显著提升。长样本或需要精确样本边界时设 False。这个开关在第 5 章的 SFTTrainer 里设置。

4.6 划分训练集与验证集

split = ds.train_test_split(test_size=0.05, seed=3407)
train_ds, eval_ds = split["train"], split["test"]
print(len(train_ds), len(eval_ds))   # 训练 / 验证 条数
数据清洗清单

去重、去空 output、过滤超长/超短样本、统一格式、检查是否含个人隐私。清洗一遍常比加数据更能提升效果。

4.7 小结与下一步