🏠 总目录📚 本教程 16 · 合成数据 ← →
📑 本页目录(点开跳转)

16 · 合成数据

⏱ 58 分钟 | ⭐ 同一批合成数据,进训练集是止痛药,进评测集是毒药


🎯 一句话

合成数据能解决「没有数据」,解决不了「不知道真实分布长什么样」。 它是一台把你已有的假设放大成几十万行样本的机器 —— 假设对,它帮你省钱;假设错,它把错误铸成了看起来非常真实的数据。


🧬 一、先分清:四种完全不同的东西都叫「合成数据」

这四种的风险等级差一个数量级,混着讨论必然吵不出结果。

叫法 怎么造 保留了什么 典型用途
数据增强 对真实样本做变换(旋转、裁剪、同义替换、加噪) 原样本的标签和主体 扩样本量、提鲁棒性
规则 / 仿真生成 写一个生成器(业务规则、状态机、物理引擎) 你写进去的那些规则 压测造数、冷启动、边界样例
统计模型生成 拟合分布再采样(GMM / copula / CTGAN / 扩散) 拟合到的边缘分布 + 部分相关性 隐私替代、稀有类补充
大模型生成 让 LLM 直接写样本、或写标注 ⚠️ LLM 的世界观 冷启动、指令数据、标注草稿

它们的差别不在技术,在「信息从哪来」:

对照

数据增强 信息来自 —— 你知道哪些变换不改变标签(不变性先验)

仿真生成 信息来自 —— 你亲手写下的规则

统计生成 信息来自 —— 那份真实数据本身

⭐ 它【不产生新信息】,只是把已有信息重新排列成更多行

大模型生成 信息来自 —— LLM 的预训练语料

⭐ 唯一真的引入了外部信息的一种

⚠️ 也是唯一【你说不清引入了什么】的一种

⭐ 一条判据能省掉后面所有争论: 拿到一批合成数据,先问 —— 「这里面的信息是从哪来的?」 答得上来,你就知道它能用在哪、不能用在哪; 答不上来,就别让它进入任何影响决策的地方。

⚠️ 「统计生成不产生新信息」这句话要认真对待: 你用 1 万条真实数据拟合一个生成器,再采出 100 万条 —— 信息量还是那 1 万条的信息量,只是被摊薄成了 100 万行。 模型看到的样本数涨了 100 倍,有效信息一点没涨, 但它的置信度会涨(因为"见得多了")—— 这是过拟合的一种伪装形式。


🚦 二、什么时候有用:五个场景,各自的成立条件

不要问「合成数据行不行」,要问「这个场景的成立条件我满足了没有」。

场景 为什么合成有用 ⭐ 成立条件 ⚠️ 失败信号
隐私替代 真实个人数据出不了域,合成表可以给外部/测试环境 下游任务只依赖你显式保留的那几个统计量 合成表上训的模型,换到真实数据上评估分数明显掉
长尾 / 稀有类补充 稀有类只有几百条,模型学不动 已有真实样本足够刻画这一类的形态,你补的是"密度"不是"形态" 召回上去了但精度掉得更多
冷启动 一条真实数据都没有,先得让系统跑起来 ⭐ 合成数据有明确的退役日期 上线三个月了训练集里还有 60% 合成
压测 / 造数 需要 1 亿行来测管道容量 ⭐ 它不进模型,只进管道 —(这是最安全的一种)
边界样例 / 反例 真实数据里罕见但一出就是事故 ⭐ 数量少(几十到几百条)、每条人写人核 —(这是 ROI 最高的一种)

四条具体的操作建议:

操作步骤

  1. 稀有类补充要【封顶】
  2. 合成样本量 ≤ 该类真实样本量 × 3~5
  3. ⭐ 超过这个比例,模型学到的是"生成器的口癖",不是这一类的特征
  4. 冷启动的合成数据必须带【退役日期】字段
  5. 每条样本记 source='synthetic' + created_at
  6. ⭐ 第一批真实数据到齐之后,按计划下线,不是"忘了就一直留着"
  7. 压测造数要造【形态】不是造【数值】
  8. 字段长度分布、空值率、极端时间戳、重复主键、编码异常
  9. ⭐ 一份"全是合法值"的压测数据,测不出你的管道会不会崩
  10. 边界样例要写进【评测集的独立分区】,不是混进主评测集
  11. 它们是"必须不出错"的清单,不参与整体指标的平均

⭐ 第 ④ 条是个反例,值得单独记住: 边界样例是唯一可以进评测集的合成数据 —— 因为它们不是"抽样出来代表真实分布"的,它们是一份人工维护的必答题清单。 它们要单独报分("32 条边界样例过了 30 条"),绝不能被平均进总体 F1。


☠️ 三、什么时候有毒:四条红线

💀 红线 1:评测集里混进合成数据

这是本章最贵的一条,因为它不会让你出错,它会让你「看起来很对」。

对照

机制拆开看:

合成数据 D_syn 由生成器 G 产生

你的模型 M 在 D_syn 上训练过(或者 M 和 G 同源)

于是 M 在 D_syn 上的表现,衡量的是

⭐【M 有多懂 G 的口癖】

而不是

【M 有多懂真实世界】

💀 这两件事在指标上长得一模一样,而且前者【永远更容易】

混入方式 有多隐蔽
直接把合成数据划一部分当测试集 显而易见,但真的有人这么干
先合成、再随机划分 train/test ⭐ 💀 最常见,因为划分脚本是通用的,没人记得这批数据是合成的
用同一套 prompt 分别造训练集和评测集 💀💀 看起来是两批独立数据,其实分布完全一样
真实评测集里补了几百条合成的稀有类 ⚠️ 稀有类指标虚高,而稀有类往往正是你关心的

💀 铁律,没有例外: 评测集只能来自真实世界,并且每一条都能追溯到具体来源。 合成数据可以进训练集、可以进边界样例清单,不能进用来做上线决策的那份评测集。 这条和第 13 章「评测集也是数据」是同一句话的两种说法。

💀 红线 2:模型坍塌(第四节整节实跑)

模型坍塌指的是:把模型自己的输出(或另一个模型的输出)当训练数据喂回去,一代代传下去,分布会系统性地越来越窄。 它不是"生成质量变差了"这种模糊说法,而是有很干净的数学形式 —— 每代用上一代的样本估计分布再采样, 方差每代确定地乘一个 (n−1)/n,同时中心在做无方向的随机游走。 💀 于是你得到一个越来越自信、但越来越偏的分布 —— 而它自己看不出来。

它是红线而不是理论隐忧,因为触发它的动作在真实工程里都很常见,而且都不长得像"我在用合成数据":

① 把线上高置信度的预测回灌重训 —— 第五节那个客服事故第 3 周做的正是这件事, 三轮之内 9 个弱意图的样本占比从 4.1% 掉到 0.6%。

② ⚠️ 常规监控看不见它 —— 纯自消费十代,样本标准差只掉 2.5%, 而有效模态数掉 9.9%、尾部三个稀有模态的占比掉 11.5%: 坍塌先发生在长尾,等方差报警时稀有类已经死了很久。

③ 💀 "只保留模型最有把握的那一半样本"这个人人都觉得是好习惯的动作,是加速器不是保护措施 —— 实验里只过滤一次,尾部三个模态的占比就从 9.96% 掉到 0.02%,一代之内长尾清零。

⚠️ 而且"每代样本量"不是你的数据集行数,是模型在那个子区域上实际见过的独立样本数 —— 长尾类别的有效样本量常常只有几十,而每代 30 个样本时方差减半只要约 20 代。

⭐ 但这条红线的位置要划准,否则会矫枉过正: 真正的触发条件不是"用了合成数据",是"用合成数据把真实数据换掉了"。 第四节的对照实验里,只要真实数据永远留在训练池里(累积,而不是替换), 十代之后熵只从 1.770 掉到 1.753,掉 1.0% —— 几乎没有坍塌。

⚠️ 红线 3:合成数据继承并放大原始偏差

生成器学的是训练它的那份数据。原始数据里 A 类占 80%,采出来的通常大于 80% —— 因为「拟合 + 采样」在数学上是一个锐化操作:低密度区被当成噪声,高密度区被强化。

合成数据把头部放大,把长尾抹掉

生成器输出与真实类别分布的对比
类别真实数据合成数据变化
A80%86%头部被放大
B15%13%略少
C4%1%长尾被压缩
D1%0%直接消失

对每个关键分组算 |合成占比 − 真实占比| / 真实占比;任何一组超过 10%,这批合成数据不能直接用。

⚠️ 红线 4:用大模型造标注,但没人核验

对照

LLM 标注的一致性【非常高】—— 同样的输入,它给同样的答案

⚠️ 但「一致性高」不等于「正确」

💀 更糟的是:它把一个最有价值的信号【抹掉】了 ——

人和人标不一致的地方,恰恰是【任务定义本身模糊】的地方

(这是第 10 章「标注一致性」的核心)

LLM 会在那些地方给你一个看起来非常确定的答案,

于是你永远不会发现你的标注规范有洞

该怎么做 具体
分层抽样人核 ⭐ 每个类别抽 20–30 条,总量 200–300 条,由真人独立标一遍
报「人机一致率」 ⭐ 不是报 LLM 的自洽率。人机一致率 < 0.75 的类别,整类退回重写规范
低置信度单独处理 LLM 输出的 logprob 低的那一档,全部转人工,不要靠阈值一刀切
保留 labeled_by 字段 每条标注记录是人标的还是机器标的,永远不要混成一列

🛑 读到这里可以停 —— 前半章讲完了(约 15 分钟)。 后半章还有:实跑:多代自训练里,多样性是怎么一代代收窄的 · 一次真实的事故复盘 · 一张判断表:这批合成数据能不能用 回来的时候不用重读,直接从下一节接着看就行。


🧪 四、实跑:多代自训练里,多样性是怎么一代代收窄的

「模型坍塌」听起来像个理论隐忧,实际上它有非常干净的数学形式。 下面三个实验全部实跑,代码可以直接抄。

实验一:最干净的形式 —— 一维高斯

每一代:用上一代的数据估计均值和方差 → 采 n 个新样本 → 丢掉上一代。

import numpy as np

def self_consume_1d(n, gens=10, trials=4000, seed=0):
    """纯自消费:每代拟合 MLE 再重采样,上一代数据全部丢弃"""
    rng = np.random.default_rng(seed)
    var = np.zeros((trials, gens + 1))
    mean = np.zeros((trials, gens + 1))
    for t in range(trials):
        x = rng.normal(0, 1, n)                    # 第 0 代:真实数据
        var[t, 0], mean[t, 0] = x.var(), x.mean()
        for g in range(1, gens + 1):
            mu, sd = x.mean(), x.std()             # ⭐ MLE,方差是有偏的(除以 n)
            x = rng.normal(mu, sd, n)              # ⭐ 用模型采样,替换掉真实数据
            var[t, g], mean[t, g] = x.var(), x.mean()
    # ⭐ 返回:方差的期望(衡量多样性)、均值的标准差(衡量漂移)
    return var.mean(axis=0), mean.std(axis=0)

# 实跑:n=100, 10 代 → 方差降到 0.9012,理论值 ((n-1)/n)**10 = 0.9044

每代样本量 n 决定了收窄速度:

每代样本量 第 10 代方差 / 初始方差 理论值 ((n−1)/n)^10 方差减半需要多少代
n = 1000 0.9904 0.9900 ≈ 693 代
n = 100 0.9012 0.9044 ≈ 69 代
n = 30 0.7073 0.7125 ⭐ ≈ 20 代

同时,均值在做无方向的随机游走(n = 100):

代 0 2 5 10
各次实验的均值标准差 0.101 0.175 0.241 0.325

⭐ 这两行数字合起来就是模型坍塌的全部: 分布的宽度单调收缩,中心无方向漂移。 收缩是系统性的(每代确定地乘一个 (n−1)/n), 漂移是随机的(每代累加一点噪声,标准差按 √代数 增长)。 💀 也就是说:你会得到一个越来越自信、但越来越偏的分布 —— 而且它自己看不出来。

⚠️ 别被 n=1000 那一行安慰到:真实系统里的"每代样本量"不是你的数据集行数, 而是模型在那个子区域上实际见过的独立样本数。 长尾类别的有效 n 常常只有几十 —— 对它们来说,第 20 代方差就没了一半。

实验二:长尾先死

真实分布:8 个模态的二维混合高斯,权重 [0.30, 0.25, 0.15, 0.12, 0.08, 0.05, 0.03, 0.02]。 每代 500 个样本,用 8 成分 GMM 拟合再采样,跑 10 代,25 次重复取平均。

代 存活模态数 分布熵 H 有效模态数 e^H 样本标准差 尾部 3 个模态占比
0(真实) 7.96 1.770 5.87 2.625 9.96%
2 7.76 1.743 5.73 2.617 9.28%
5 7.28 1.699 5.49 2.595 8.97%
10 7.12 1.656 5.29 2.560 8.81%
十代累计变化 −0.84 个 −6.4% −9.9% ⭐ −2.5% −11.5%

⭐ 注意四个指标掉的速度不一样: 样本标准差只掉了 2.5% —— 如果你只监控"方差",你几乎看不到任何异常。 但有效模态数掉了 9.9%,尾部模态占比掉了 11.5%。 坍塌先发生在长尾,最后才反映到总体方差上 —— 等你的方差监控报警时,稀有类已经死了很久了。

💀 实验三:加一步「质量过滤」,坍塌快 20 倍

这是真实工程里几乎人人都会做的一步:合成完之后, 「我们只保留模型最有把握的那一半样本,这样质量更高」。 在实验里就是 —— 采 1000 条,按生成器给的对数似然排序,保留最高的 500 条。

代 存活模态数 分布熵 H 有效模态数 样本标准差 尾部 3 模态占比
0(真实) 7.96 1.770 5.87 2.625 9.96%
1 ⭐ 4.44 1.193 3.31 1.875 0.02% 💀
3 3.36 0.883 2.44 1.490 0.00%
5 2.60 0.703 2.06 1.223 0.00%
10 1.24 0.140 1.19 0.268 0.00%

💀💀 这是全章最重要的一张表,请看第 1 行: 只过滤了一次,尾部三个稀有模态的占比就从 9.96% 掉到 0.02% —— 一代之内,长尾清零。 十代之后标准差从 2.625 掉到 0.268(方差只剩 1.0%),有效模态数从 5.87 掉到 1.19: 八个模态的分布,最后塌成了一个点。

⭐ 为什么「质量过滤」是加速器而不是保护措施: 生成器给的似然分数,衡量的是「这条样本有多像我已经学会的东西」。 按它排序保留高分样本,等于每一代都主动把分布的边缘剪掉一圈。 你以为你在提高质量,实际上你在做定向的多样性删除。 💀 纯自消费十代掉 9.9% 的有效模态数,加上质量过滤一代就掉 44%。

🛡️ 四种做法对照:什么能刹住车

同一个实验,第 10 代的结果:

做法 存活模态数 熵 H 有效模态数 标准差 尾部 3 模态
纯自消费(替换) 7.12 1.656 5.29 2.560 8.81%
自消费 + 质量过滤 💀 1.24 0.140 1.19 0.268 0.00%
每代掺 10% 原始真实数据 7.48 1.686 5.41 2.544 7.85%
每代掺 30% 原始真实数据 ⭐ 7.64 1.730 5.65 2.607 9.24%
累积(真实数据永远留在池子里)⭐ 7.72 1.753 5.78 2.630 9.71%

⭐ 看最后一行:累积模式下,十代几乎没有坍塌(熵 1.770 → 1.753,掉 1.0%)。 「替换」和「追加」是两件性质完全不同的事: 只要真实数据永远不被丢出训练池,自消费就不会失控。 💀 模型坍塌的真正触发条件不是"用了合成数据",是"用合成数据把真实数据换掉了"。

一段可以直接放进日常监控的多样性度量:

import numpy as np
from sklearn.cluster import KMeans

def diversity_report(emb, k=64, seed=0):
    """emb: (N, D) 样本嵌入。把嵌入聚成 k 簇,用簇分布的熵度量多样性。
    每次数据刷新都跑一遍,比较代际变化。"""
    km = KMeans(n_clusters=k, n_init=4, random_state=seed).fit(emb)
    cnt = np.bincount(km.labels_, minlength=k).astype(float)
    p = cnt / cnt.sum()
    nz = p[p > 0]
    H = float(-(nz * np.log(nz)).sum())
    return {
        "有效簇数": round(float(np.exp(H)), 2),          # ⭐ 比熵本身好读:等效于多少个均匀簇
        "空簇数": int((cnt == 0).sum()),                 # ⭐ 长尾死亡的最早信号
        "尾部10%簇的样本占比": round(float(np.sort(p)[: k // 10].sum()), 4),  # ⭐ 最灵敏
        "平均维度标准差": round(float(emb.std(axis=0).mean()), 4),            # 最迟钝,别只看这个
    }

# ⭐ 判读:有效簇数相对上一版掉超过 5%,或尾部占比掉超过 20%,就停止再喂合成数据

💀 五、一次真实的事故复盘

对照

系统:某 SaaS 的客服工单意图分类,42 个意图,线上自动分流到不同处理队列

冷启动时的家底:3800 条人工标注工单

决定:用大模型按每个意图的定义各造 2000 条,得到 8.4 万条合成训练数据

同一套 prompt 换个随机种子,另造 4200 条当"评测集"

发生了什么

阶段 事件
建模 训练集 = 8.4 万合成 + 3800 真实;评测集 = 4200 条合成 ⭐ 红线 1
离线 macro-F1 0.94,团队认为可以直接全量上线
上线 42 个意图自动分流,分不出来的转人工兜底(预期兜底率 8%)
第 2 周 兜底率 17%,被解释成"新系统磨合期"
第 3 周 团队把线上模型置信度 > 0.9 的工单加回训练集重训 ⭐ 自消费闭环成型
第 6 周 兜底率 27%;抽查发现 9 个意图的真实召回率 < 0.35

为什么没被发现

流程图

① 评测集和训练集用【同一套 prompt】生成 💀
模型只需要学会"LLM 写工单的口癖",就能拿到 0.94
—— 它测的是"M 有多懂 G",不是"M 有多懂真实工单"
② 真实工单和合成工单根本不是一个东西:
真实 合成
平均长度 23 字 58 字
含错别字比例 37% ~0%
一句话多意图 19% 0% ⭐ LLM 永远只写单意图
长度分布 KS 距离 0.61 💀 这一个数就能提前六周报警
③ 42 个意图里,LLM 对 9 个【边界模糊】的意图理解错了
—— 而这 9 个恰恰是当初人工标注一致性最低的那几个(Kappa 0.41~0.58)
💀 没有任何人核验过合成标注,LLM 的"确定"把这个信号盖住了
④ 第 3 周的"置信度 > 0.9 回灌"把它变成了自消费闭环:
模型只对它已经擅长的意图有高置信度→只有这些进训练集
⭐ 这就是【质量过滤 + 替换】,实验三里一代掉 44% 的那个组合
那 9 个弱意图的样本占比:4.1%→0.6%(三轮之内)

代价

信息关系

人工兜底率 8%(预期)→27%(第 6 周)
人工客服工时 +19%
6 周额外人力成本 ≈ 41 万元
SLA 投诉 4 个企业客户,其中 1 个进入合同重议
补救 重新采集 + 人工标注 1.2 万条真实工单
2 名标注员 × 3 周
⭐ 发现延迟 = 6 周
而【长度分布 KS = 0.61】这个数,在第 0 天就能算出来

该补什么

缺失 补上之后
评测集是合成的 💀 ⭐ 先攒够 1500 条真实工单做评测集再上线。补测后的真实 macro-F1 是 0.61,不是 0.94
没有比对真实/合成的分布 上线前必跑:长度、错别字率、多意图比例、token 分布 KS。任一 KS > 0.2 就不许上线
合成标注没人核验 每个意图抽 25 条人核,报人机一致率;< 0.75 的意图整类退回重写定义(第 10 章)
高置信度回灌 ⭐ 💀 禁止按置信度回灌。要回灌就随机抽样回灌,并且真实数据永远不出池(累积,不是替换)
没有多样性监控 每轮重训后跑 diversity_report:有效簇数掉 > 5% 或尾部占比掉 > 20% 直接卡住发布
合成数据没有退役计划 每条样本带 source 和 created_at;真实数据到 1 万条时合成数据降权到 0.3,到 3 万条时清零

⭐ 这个案例最值钱的一句话: 离线 0.94 和真实 0.61 之间的 0.33,全部是「模型有多懂 LLM 的口癖」。 它不是过拟合,不是泄漏,也不是漂移 —— 是他们出的题和他们做的题,来自同一支笔。


🧭 六、一张判断表:这批合成数据能不能用

先问这个 如果是 该做
它会进评测集吗? 会 💀 停。除非它是单独报分的边界样例清单
它的信息从哪来,说得清吗? 说不清 ⚠️ 只能用在压测、不能进模型
它是模型自己的输出回灌吗? 是 ⚠️ 必须累积不替换、随机抽样不按置信度、上多样性监控
我做了质量过滤 / 只留高分样本吗? 做了 💀 这是坍塌加速器,实验里一代掉 44% 有效模态数
各关键分组的合成占比 vs 真实占比差多少? > 10% ⚠️ 生成器在锐化,先修生成器
合成标注有人核过吗? 没有 ⚠️ 抽 200–300 条人核,报人机一致率
合成数据有退役日期吗? 没有 ⭐ 现在就定:真实数据到多少条时降权、到多少条时清零
我监控多样性吗? 不监控 ⭐ 上有效簇数 + 尾部占比。只看方差是看不见坍塌的(十代才掉 2.5%)

🔗 这一章连到哪里

去哪 为什么
数据泄漏的七种来源 ⭐ 「评测集里混进合成数据」是泄漏家族的一员,但机制不同:那边是信息穿越,这边是出题人和答题人同源
全景导论主线 4 · 它怎样从续写机变助手 合成数据在 SFT / 对齐阶段的正规用法。那边讲怎么用,这一章讲什么时候别用
智能体工程教程 14 评测 Evals ⭐ 用 LLM 造评测和 LLM-as-judge 的完整做法,以及它们各自的核验要求
Kaggle 08 数据增强与输入处理 数据增强这一支的实操细节 —— 它是四种里风险最低的一种
模型上线之后 02 离线好不等于线上好 客服那个案例(0.94 → 0.61)的典型形态,那边给了完整的排查清单

✅ 检查点

  1. 四种「合成数据」分别是什么?它们的关键差别是哪一条?统计模型生成为什么"不产生新信息"?
  2. 用 1 万条真实数据采出 100 万条合成数据,模型的有效信息量变了吗?会带来什么伪装形式的问题?
  3. 五个「有用」场景里,最安全的是哪两个?为什么?稀有类补充为什么要封顶、封到多少?
  4. 边界样例为什么是唯一可以进评测集的合成数据?它该怎么报分?
  5. 「评测集里混进合成数据」的机制是什么?最常见的混入方式是哪一种、为什么隐蔽?
  6. 一维高斯自消费实验里,方差每代乘以什么?n=30 和 n=1000 时方差减半分别要多少代?均值是怎么变化的?
  7. 长尾实验里,十代之后哪个指标掉得最少、哪个掉得最多?这说明监控该看什么?
  8. 「质量过滤」为什么是坍塌加速器?一代之内尾部占比从多少掉到多少?十代后标准差和有效模态数是多少?
  9. 四种做法对照里,什么能刹住坍塌?模型坍塌的真正触发条件是什么?
  10. 客服工单那个事故里,离线 0.94 和真实 0.61 之间的差距是什么造成的?哪一个数在第 0 天就能报警、它是多少?第 3 周他们做错了什么?
👀 答案
  1. 数据增强 / 规则仿真 / 统计模型生成 / 大模型生成。关键差别是信息从哪来:增强来自不变性先验,仿真来自你写的规则,统计生成来自那份真实数据本身,大模型生成来自预训练语料。统计生成不产生新信息,因为它只是把已有信息重新排列成更多行 —— 拟合和采样都不引入外部知识。
  2. 有效信息量还是那 1 万条,只是被摊薄成 100 万行。问题在于模型的置信度会涨("见得多了"),这是过拟合的一种伪装形式。
  3. 最安全的是压测造数(它不进模型,只进管道)和边界样例(数量少、每条人写人核,ROI 最高)。稀有类补充要封顶是因为超过比例后模型学的是生成器的口癖而不是这一类的特征,封到该类真实样本量的 3–5 倍。
  4. 因为它们不是"抽样出来代表真实分布"的,而是一份人工维护的必答题清单。所以要单独报分(如"32 条过了 30 条"),绝不能被平均进总体 F1。
  5. 机制:合成数据由生成器 G 产生,模型 M 在它上面训过或与 G 同源,于是指标衡量的是「M 有多懂 G 的口癖」而不是「M 有多懂真实世界」,而前者永远更容易。最常见的是 先合成、再随机划分 train/test —— 因为划分脚本是通用的,没人记得这批数据是合成的。最隐蔽的是用同一套 prompt 分别造训练集和评测集(看起来是两批独立数据,其实分布完全一样)。
  6. 每代乘 (n−1)/n(MLE 方差有偏)。实跑十代:n=1000 → 0.9904(理论 0.9900),n=100 → 0.9012(理论 0.9044),n=30 → 0.7073(理论 0.7125)。方差减半:n=30 约 20 代,n=100 约 69 代,n=1000 约 693 代。均值在做无方向随机游走,标准差按 √代数 增长(n=100:0.101 → 0.325)。合起来就是分布越来越窄、中心越来越偏,而它自己看不出来。
  7. 样本标准差掉得最少(−2.5%),尾部 3 模态占比掉得最多(−11.5%),有效模态数 −9.9%,熵 −6.4%。说明监控不能只看方差 —— 坍塌先发生在长尾,最后才反映到总体方差,等方差报警时稀有类已经死很久了。该看有效簇数、空簇数、尾部簇占比。
  8. 因为生成器的似然分数衡量的是「这条样本有多像我已经学会的东西」,按它排序保留高分等于每一代主动把分布边缘剪掉一圈 —— 你以为在提高质量,实际在做定向的多样性删除。一代之内尾部 3 模态占比从 9.96% → 0.02%(长尾清零)。十代后标准差 2.625 → 0.268(方差只剩 1.0%),有效模态数 5.87 → 1.19 —— 八个模态塌成一个点。
  9. 累积模式(真实数据永远留在池子里)几乎完全刹住:十代熵 1.770 → 1.753,只掉 1.0%;掺 30% 真实数据也基本刹住(熵 1.730)。真正的触发条件不是"用了合成数据",是"用合成数据把真实数据换掉了" —— 替换和追加是性质完全不同的两件事。
  10. 那 0.33 全部是「模型有多懂 LLM 的口癖」 —— 训练集和评测集来自同一套 prompt,出题和答题来自同一支笔。第 0 天就能报警的是真实 vs 合成的长度分布 KS 距离 = 0.61(真实工单平均 23 字、37% 有错别字、19% 多意图;合成 58 字、几乎无错别字、100% 单意图)。第 3 周他们把置信度 > 0.9 的线上工单回灌重训 —— 这正是「质量过滤 + 替换」的组合,导致 9 个弱意图的样本占比三轮内从 4.1% 掉到 0.6%,兜底率最终 8% → 27%,6 周多花约 41 万。

🛑 可以停在这里

⚡ 走神救援

⭐ 合成数据能解决「没有数据」,解决不了「不知道真实分布长什么样」——它是一台把你已有的假设放大成几十万行样本的机器,假设错就把错误铸成了看起来非常真实的数据。

⭐ 万能判据:问「这批数据里的信息是从哪来的」,答不上来就别让它影响任何决策。 ⚠️ 用一万条真实数据采一百万条,有效信息还是那一万条,但模型置信度会涨——这是过拟合的伪装形式。

⭐ 边界样例是唯一可以进评测集的合成数据,因为它是必答题清单不是抽样——而且必须单独报分,绝不能平均进总体 F1。

☠️ 四条红线里最狠的两条:💀 评测集混进合成数据——你测的是「模型有多懂生成器的口癖」,而这件事永远更容易;最常见的混入方式是先合成再随机划分 train/test(划分脚本是通用的,没人记得这批是合成的)。💀 模型坍塌:分布宽度单调收缩、中心随机漂移,得到一个越来越自信又越来越偏的分布,而它自己看不出来。

⭐ 坍塌先发生在长尾,最后才反映到总体方差——只监控方差等于全盲。💀 更反直觉的是「质量过滤」是加速器:按似然筛选等于每代主动把分布边缘剪掉一圈,你以为在提质量,实际在做定向的多样性删除。

⭐ 真正的触发条件不是「用了合成数据」,是「用合成数据把真实数据换掉了」——替换和追加是性质完全不同的两件事,真实数据永远留在池子里基本无损。

💀 那个客服工单事故最值钱的一句:离线 0.94 和真实 0.61 之间那 0.33 的差距,全部是「模型有多懂 LLM 的口癖」——不是过拟合、不是泄漏、不是漂移,是他们出的题和做的题来自同一支笔。

下一节 👉 17-数据版本与血缘.md

打卡记录保存在你的浏览器里,首页能看到总进度