面试知识库
中 进阶

合成数据与拒绝采样#

一句话答案#

合成数据是用模型批量生成训练样本,解决冷启动没数据、长尾场景覆盖不到、人工标注太贵的问题;它的价值取决于过滤——规则校验、模型打分、去重、控多样性。拒绝采样(RFT / best-of-N)是其中最常用的一种:对每个问题采 N 个回答,用校验器或奖励模型留下最好的再做 SFT,相当于「只用正样本、离线版」的强化学习。最大的风险是评测污染:合成数据里混进了评测集的题,分数就不再可信。

核心要点

SFT 数据「质量比数量重要」的结论、R1 用拒绝采样造数据的路线分别见 LLM训练流程与对齐 和 推理模型与思维链。本篇讲合成数据怎么造、怎么过滤、拒绝采样的机制以及它和 RL 的关系。

1. 为什么要合成#

动机说明
冷启动新任务、新 schema(比如新的工具调用格式)没有历史数据
覆盖长尾真实日志集中在高频意图,长尾品类、边界输入、对抗输入很少
标注成本推理轨迹、多轮工具调用这种长样本,人工写一条要很久
可控分布能按维度(难度、领域、语言、意图)配比,而不是被线上流量决定
隐私真实用户数据不能直接进训练,可以用它的「形态」合成替代样本

合成数据带来的问题:分布和真实用户不一样(更规整、更长、更书面);教师模型的错误和偏好会被原样学进去;反复用模型自己生成的数据训练自己,分布会逐渐变窄。所以真实数据至少要留一份当评测尺子,不进训练集。

2. 生成方法#

方法做法适合
Self-Instruct少量种子任务做 few-shot,让模型生成新指令和回答,再过滤掉和已有指令太相似的通用指令数据扩充
Evol-Instruct对已有指令做「加约束、加推理步骤、换领域」等改写,逐轮变难提升难度分布
蒸馏强模型(教师)对 prompt 生成回答,弱模型(学生)SFT小模型学大模型的能力或推理轨迹;注意教师 API 的服务条款是否允许
反向生成已有高质量文本(文档、商品、代码),让模型为它生成对应的问题检索训练数据、领域问答
维度矩阵列出维度(领域 × 意图 × 难度 × 表述风格)做笛卡尔组合,每格生成若干条需要覆盖可枚举空间的结构化任务
轨迹合成在沙箱里让 Agent 真实调用工具多轮执行,记录完整轨迹,只留任务成功的多轮工具调用、Agent SFT

轨迹合成要逐轮切样本:一条 5 轮的对话能切出 5 条训练样本(每条的 target 是第 k 轮 assistant 输出,前面是上下文),不同轮次学的是不同决策。工具返回结果要来自真实执行,不要让模型编,否则学到的是「幻想出来的工具反馈」。

对抗样本单独一源:从线上 bad case 归纳出几类失败模式(比如「缺少上文的省略式追问」「口语化、含糊的数值表达」「否定和排除条件」),针对每一类批量生成变体。这部分覆盖的是模型最容易出错的地方,价值往往高于同等数量的普通样本。

3. 质量过滤:生成之后才是重点#

flowchart LR
  G[生成] --> R[规则校验<br/>schema/长度/语言/可执行]
  R --> M[模型打分<br/>judge / RM / 校验器]
  M --> D[去重<br/>精确 + 近似]
  D --> V[多样性与难度配比]
  V --> C[去污染<br/>对评测集查重]
  C --> T[训练集]
  • 规则校验:最便宜、最先做。JSON 能否解析、字段是否齐全、长度范围、语言是否一致、代码能否编译并通过测试、数学答案能否和标准答案对上
  • 模型打分:用强模型当 judge 按 rubric 打分,或用 RM 打分,留高分样本。judge 有自己的偏好(偏长、偏自己的风格),要抽样人工核对一致性,不要用同一个模型既生成又打分又当最终评测
  • 去重:精确去重用规范化后的哈希;近似去重用 MinHash + LSH(文本 n-gram 层面)或 embedding 相似度(语义层面)。模板化生成的数据近似重复率通常很高
  • 多样性:对指令做 embedding 聚类,看各簇大小,压缩大簇、补小簇;Self-Instruct 的做法是新指令与已有指令的 ROUGE-L 太高就丢弃
  • 难度筛选:用目标模型本身对每题采样多次,全对的题太简单、全错的题可能标错了或超出能力,这两端都少放
  • 整批质量门:抽检不合格率超过阈值时整批作废,而不是逐条修。批量生成的问题多是系统性的(prompt 写错、字段名写错),逐条修修不完
from datasketch import MinHash, MinHashLSH

def near_dedup(texts: list[str], threshold: float = 0.8, num_perm: int = 128) -> list[int]:
    """返回保留下来的样本下标;3-gram 字符 shingle 适合中文"""
    lsh = MinHashLSH(threshold=threshold, num_perm=num_perm)
    kept = []
    for i, t in enumerate(texts):
        m = MinHash(num_perm=num_perm)
        for j in range(max(1, len(t) - 2)):
            m.update(t[j:j + 3].encode("utf-8"))
        if lsh.query(m):          # 已有近似重复
            continue
        lsh.insert(str(i), m)
        kept.append(i)
    return kept
python

4. 拒绝采样 / best-of-N / RFT#

机制:对训练集里每个问题 x,用当前模型采 N 个回答,用判定函数挑出可接受的,加入 SFT 数据。

DRFT={(x,y)∣y∼π(⋅∣x),  verify(x,y)=1}\mathcal{D}_{RFT} = \{(x, y) \mid y \sim \pi(\cdot\mid x),\; \mathrm{verify}(x, y) = 1\}

判定函数有两类:

  • 校验器:答案比对、单元测试、schema 校验、真实环境执行结果。信号可靠,是首选
  • 奖励模型 / judge:没有标准答案的任务只能用它,从 N 个里取分最高的(best-of-N)。会把 RM 的偏好一并放大
def rejection_sample(model, problems, verify, n: int = 8, max_keep: int = 2):
    data = []
    for p in problems:
        cands = model.generate(p.prompt, n=n, temperature=0.8)   # 温度要够高,否则 N 条几乎一样
        good = [c for c in cands if verify(p, c)]
        uniq = list(dict.fromkeys(good))[:max_keep]              # 同题去重并限量,防止简单题占满数据集
        data += [(p.prompt, c) for c in uniq]
    return data
python

几个工程要点:

  • 采样温度与 N:温度太低 N 条几乎相同,拒绝采样退化成单次采样;N 越大难题越可能采到正确解,成本线性增长
  • 每题限量:简单题 N 条全对,不限量会让数据集被简单题占满,模型在难题上没有进步
  • 假阳性:只比对最终答案时,推理过程错但答案蒙对的样本也会通过。可以加过程检查(PRM、规则检查中间步骤),或要求多条独立路径得出同一答案
  • 迭代:用 RFT 后的模型再采样、再过滤、再训,这就是 expert iteration;STaR 在此基础上对采不到正确解的题给提示(rationalization)再生成
  • 推理时的 best-of-N 是另一回事:线上同一请求采 N 个选最好的返回,用算力换质量,不改模型权重,见 推理模型与思维链 的 test-time scaling 部分

5. 拒绝采样和 RL 的关系#

RFT / 拒绝采样在线 RL(PPO/GRPO)
用到的样本只有通过的正样本正样本和负样本都产生梯度
奖励形式0/1 过滤(或 top-k)连续标量,优势可正可负
采样时机一轮采完再训,离线每步用当前策略采样,在线
稳定性就是 SFT,很稳要调 clip、KL、学习率
上限受限于当前模型 N 次采样里能采到的最好解能持续从负样本学到「别这么做」

可以把 RFT 看成奖励只取 0/1、只保留正样本、策略每轮才更新一次的策略梯度。它便宜稳定,常用在 RL 之前做冷启动,或者在 RL 之后把 RL 模型的能力通过拒绝采样沉淀成 SFT 数据再训一轮。算法层面的对比见 后训练算法对比SFT-PPO-DPO-GRPO。

6. 数据泄漏与评测污染#

  • 污染从哪来:教师模型见过公开评测集,生成出来的题和评测题高度相似;从网上抓的种子数据本身包含评测题;评测集的真实样本被当成了 few-shot 种子
  • 怎么查:对评测集做 n-gram 重叠检查(GPT-3 论文用过 13-gram)、embedding 近邻相似度检查,命中的训练样本剔除;公开 benchmark 之外,保留一份私有、按时间切分的评测集
  • 自家数据的泄漏:真实线上样本要么只进评测集、要么只进训练集,按 query / 用户 / 会话切分,不能按条随机切;同一条对话的不同轮次要落在同一侧
  • 不要用训练数据的生成模型当评测 judge:同一个模型的偏好在生成和打分两端相互印证,分数会虚高

面试回答(2分钟版)

合成数据主要解决三个问题:新任务冷启动没有数据、真实日志覆盖不到长尾、长样本人工标注太贵。生成方法有几类:Self-Instruct 从种子任务扩展,Evol-Instruct 逐轮把题改难,强模型蒸馏给小模型,已有文档反向生成问题,维度矩阵按领域、意图、难度组合覆盖,Agent 场景在沙箱里真实跑工具、只留成功轨迹并逐轮切样本。生成完关键是过滤:先规则校验,schema、长度、能不能执行;再模型打分;然后精确加 MinHash 近似去重、按聚类控多样性、按目标模型的通过率筛掉太简单和太难的。拒绝采样是其中特别常用的一种:每题采 N 个,用答案比对或单测这种校验器留下正确的,每题限量、去重,再拿去 SFT,这叫 RFT。它可以看成只用正样本、0/1 奖励、离线的策略梯度,很稳,但上限受限于模型采样能采到的最好解,也学不到负样本的信号,所以通常和在线 RL 组合用。最要防的是评测污染,训练集要和评测集做 n-gram 和 embedding 查重,真实数据按会话切分,judge 不要和生成模型是同一个。结合项目时可以讲:数据分几个来源、哪一份真实数据只当评测尺子不进训练、过滤门槛怎么定、用什么评测证明合成数据有用。

追问与易错

追问方向:

  • “合成数据里教师模型错了怎么办?” → 能用规则定的字段(比如金额、日期、格式)用规则算出的标准答案覆盖教师输出;判断类字段用多次、互相扰动(换温度、换选项顺序、换 prompt)的投票,一致的才采纳,否则进人工裁决。
  • “拒绝采样的 N 设多大?” → 看任务难度下单题通过率:通过率高的 N 小就够,难题需要更大的 N 才能采到正确解。实际按预算定,并统计「N 条全错」的题占比,占比高说明题太难或校验器有问题。
  • “拒绝采样会让数据偏向简单题,怎么处理?” → 每题最多保留 k 条、同题去重;按难度分桶配比;对采不到正确解的难题换更强的模型或给提示再生成。
  • “拒绝采样和 DPO 数据怎么同时产出?” → 同一批 N 个采样里,通过校验的当 chosen、不通过的当 rejected,就是 on-policy 的偏好对;只用 chosen 就是 RFT 数据。
  • “为什么反复用自己的输出训练会出问题?” → 每一轮只保留模型分布里高概率的那部分,低概率但正确的多样表达逐轮丢失,分布越来越窄。缓解是每轮都混入真实数据、控制多样性指标。
  • “判断合成数据有没有用,看什么?” → 在一份不来自合成流程的真实评测集上对比加与不加的效果,并看分维指标;只在合成数据同分布的验证集上涨分不能说明问题。
  • “怎么做评测集去污染?” → 训练样本和评测样本做 n-gram 重叠和 embedding 近邻检查,命中就剔除;公开 benchmark 之外维护私有的、时间上晚于训练数据的评测集。

易错点:

  • ❌ “合成数据越多越好” → 未过滤的合成数据会把教师的错误和模板痕迹放大,数量涨了质量可能下降
  • ❌ “best-of-N 就是 RL” → 训练时的拒绝采样只有正样本、离线;推理时的 best-of-N 根本不改权重
  • ❌ “训练集和评测集按条随机切分就行” → 同一会话的多轮、同一 query 的多条样本会跨两侧,评测分虚高