数据飞轮与合成数据
状态:✅ 已补齐(2026-08-30)
一句话定义:把线上反馈变成 可训练/可评测数据 ,形成「用 → 标 → 评 → 改(提示/检索/微调)」飞轮;合成数据加速冷启动。
大纲
已有相关文档(先读这些)
学习要点(读后应能回答)
一、飞轮环节与责任人
数据飞轮 /ˈdeɪtə ˈfaɪwiːl/ (Data Flywheel,数据飞轮)的本质:产品运行产生的数据反哺产品,形成越用越好的闭环。LLM 应用的飞轮有四个环节:
flowchart LR
A[用<br/>线上请求与反馈] --> B[标<br/>筛选 · 标注 · 入库]
B --> C[评<br/>黄金集 · 回归 · 指标]
C --> D[改<br/>提示 / 检索 / 微调]
D --> A
1.1 四个环节的分工
| 环节 | 做什么 | 责任人 | 关键产物 |
|---|---|---|---|
| 用 | 记录请求、响应、用户显式/隐式反馈 | 工程师(埋点)+ 产品(定义反馈语义) | 结构化日志 |
| 标 | 采样、脱敏、标注质量标签 | 标注团队 + 领域专家(抽检仲裁) | 带标签数据集 |
| 评 | 维护黄金集、跑回归、看指标 | 算法工程师 | 黄金集 + 评测报告 |
| 改 | 改提示词 / 检索策略 / 触发微调 | 算法 + 工程 | 新版本 + 变更记录 |
1.2 工程要点
- 反馈语义先定义:点赞/点踩、复制、重试(retry)、会话放弃各代表什么,由产品给出解释,再由工程落埋点——顺序反了,日志就是废数据;
- 闭环周期要短:飞轮转速取决于「发现坏 case → 上线修复」的周期。目标以周为单位,而不是以季度为单位;
- 每个改动可归因:评测集版本、提示词版本、模型版本三者绑定记录,否则改进无法复现。
1.3 贯穿案例:电商客服「小智」
后续所有示例都基于这个虚构但真实感足够的场景:
| 项 | 设定 |
|---|---|
| 业务 | 电商售前咨询 + 售后工单机器人 |
| 流量 | 日均 2 万次请求 |
| 现状 | v1 版本(RAG + 中档模型)已上线,无数据回流 |
| 目标 | 8 周内把黄金集通过率从 82% 提到 85%+ |
第一步是产品定义反馈语义(先于埋点),每种反馈的「证据强度」不同:
| 反馈动作 | 产品定义 | 证据强度 |
|---|---|---|
| 点「转人工」 | 机器人没解决问题 | ★★★(最强坏例信号) |
| 点踩 | 答案错误或没用 | ★★★ |
| 重新提问(retry) | 可能对首答不满意 | ★★(需标注确认) |
| 复制答案 | 答案被采纳走 | ★(好例信号) |
| 点赞 | 明确满意 | ★ |
对应的埋点事件结构:
interface FeedbackEvent {
requestId: string;
sessionId: string;
userId: string; // 脱敏后的 ID,不存原始账号
modelVersion: string; // 如 "v1.3.0"
promptVersion: string; // 如 "cs-sys-p42",改提示词必须换版本号
feedbackType: "like" | "dislike" | "copy" | "transfer" | "retry";
latencyMs: number;
retrievedDocIds: string[]; // RAG 命中的文档,定位"检索没命中"类坏例
createdAt: Date;
}
二、日志 → 黄金集 → 回归
2.1 从日志到数据集的流水线
- 采样:全量日志太贵,按「坏例优先」分层采样——差评、重试、人工接管、兜底话术命中的请求优先入选;
- 脱敏:先做 PII /ˌpiː aɪ ˈaɪ/ ( Personally Identifiable Information ,个人身份信息)检测打码,再进数据集;
- 标注:给每个样本打质量标签(好 / 坏 / 有条件好),坏例补注「坏在哪」(事实错 / 格式崩 / 拒答错误 / 检索没命中);
- 入库:带版本号入库,记录来源、标注人、标注时间。
落地示例:坏例优先的加权采样(Node.js + TS):
interface SampledCase {
requestId: string;
priority: number;
}
const WEIGHTS: Record<FeedbackEvent["feedbackType"], number> = {
transfer: 10,
dislike: 5,
retry: 3,
copy: 1,
like: 1,
};
// 坏例优先分层采样:按反馈权重加权随机抽 500 条,再混入 5% 无反馈请求做基线
function sample(events: FeedbackEvent[], quota = 500): SampledCase[] {
return events
.filter((e) => !hasPII(e)) // 先过 PII 检测,脏数据直接丢弃
.map((e) => ({
requestId: e.requestId,
priority: WEIGHTS[e.feedbackType] * Math.random(), // 加权随机,避免全是同类
}))
.sort((a, b) => b.priority - a.priority)
.slice(0, quota);
}
标注后的分流结果长这样(真实样子,不是抽象分类):
| requestId | 原始反馈 | 标注结论 | 坏因 | 去向 |
|---|---|---|---|---|
| rq-8101 | 转人工 | 坏 | 检索没命中退货政策文档 | 训练集(补正确答案后) |
| rq-8102 | 点踩 | 坏 | 事实错:承诺了不支持的「7 天无理由」 | 训练集(偏好对,见 4.2) |
| rq-8103 | 复制 | 好 | — | 训练集候选 + 黄金集候选 |
| rq-8104 | retry | 有条件好 | 首答啰嗦,追问后才简洁 | 训练集(偏好对:简洁版 > 啰嗦版) |
| rq-8105 | 无反馈 | 坏 | 命中兜底话术「抱歉我不明白」 | 评测集红线组(禁兜底场景) |
2.2 黄金集
黄金集 /ˈɡoʊldən set/ (Golden Set,黄金评测集)是人工确认过标准答案或质量等级的小规模精选集,通常几百到几千条。它是整个飞轮的「度量衡」:
- 覆盖核心业务场景与高价值长尾,而不是随机抽样;
- 有明确判分标准:可自动判(精确匹配 / 断言)的进自动评测,只能人工/模型判的单独分组;
- 禁止混入训练集——黄金集一旦被训练污染,所有指标都会虚高;
- 定期由领域专家审订,但频繁改动会让版本间指标不可比,改动要发版本号。
落地示例:一条黄金集样例的完整形态(YAML /ˈjæməl/ ,一种配置文件格式):
id: gs-cs-0007
version: v2026.08.3
scenario: 售后-退货政策
question: "买了个电饭煲用了10天能退货吗?"
reference: |
可以。电器类目支持 15 天无理由退货,需商品完好……
(依据:《电器类目售后政策》第 3 条)
checks:
- type: contains # 自动判:答案必须出现关键事实
value: "15 天"
- type: not_contains # 自动判:不得出现历史坏答案的错误承诺
value: "7 天"
- type: llm_judge # 模型判:语义层面打分
rubric: 须引用政策来源,且不得承诺政策外条款
source: rq-8103 # 可追溯到来源日志
要点:checks 里自动判(字符串断言)与 llm_judge(模型评)分层——能自动判的绝不交给模型,减少评测噪声。
2.3 回归
回归测试 /rɪˈɡreʃn test/ (Regression Test,回归测试):每次改动(提示词、检索参数、模型版本)上线前,在黄金集上重跑并对比指标。要点:
- 改进目标场景的指标时,盯住其他场景指标是否回退(跷跷板效应);
- 指标波动超过噪声阈值才认定有效——评测模型打分本身有随机性,先用同一集跑两次估出噪声带;
- 回归报告进变更记录,与版本绑定。
落地示例:噪声带测定与回归判定(Node.js + TS):
interface RunResult {
version: string;
passRate: number; // 黄金集通过率
}
// 同一版本连跑 3 次,估出评测噪声带
function noiseBand(runs: RunResult[]): number {
const rates = runs.map((r) => r.passRate);
return Math.max(...rates) - Math.min(...rates);
}
// 新版提升超过噪声带 1.5 倍才认定真实有效
function isRealImprovement(baseline: number, candidate: number, band: number): boolean {
return candidate - baseline > band * 1.5;
}
代入「小智」的真实数字:
- v1.3 同版连跑三次:82.0% / 82.3% / 82.6% → 噪声带 0.6pp,判定门槛 = 0.9pp;
- v1.4(改了退货政策的检索策略)跑出 84.8% → 提升 2.8pp > 0.9pp,认定有效;
- 对照组:另一场景「物流查询」从 88.1% 掉到 85.9% → 跷跷板回退,回归失败,不发全量。
工具上不必自研评测框架: promptfoo 类开源工具原生支持 YAML 用例 + 断言 + 模型评分,把上面的 checks 直接翻译成它的配置即可接入 CI。
2.4 分流判据:进训练集还是评测集
这是文首第一个学习问题,判据一句话:反映「目标行为」的样本进训练集,衡量「行为底线」的样本进评测集。
| 样本类型 | 去向 | 理由 |
|---|---|---|
| 高质量问答(确认正确、有代表性) | 训练集 | 教模型「该怎么做」 |
| 差评样本 + 修正答案 | 训练集(成对) | 构成偏好对比,教「哪种更好」 |
| 黄金集候选(判分标准明确) | 评测集 | 只能考,不能练 |
| 涉及安全红线的拒答案例 | 评测集(红线组) | 底线必须每次考试,不能只靠学过 |
| 极端长尾、标注拿不准 | 弃置或仲裁池 | 噪声样本既教坏训练也污染评测 |
经验法则:评测集考验稳定性,训练集改变行为。拿不准时先只进评测集——错误进训练集的代价(学坏)远大于错误进评测集(考官变难)。
三、合成数据:用途与污染风险
合成数据 /sɪnˈθetɪk ˈdaɪtə/ (Synthetic Data,合成数据):用模型生成的数据(或程序构造的数据)代替/补充人工数据。
3.1 用途
| 场景 | 做法 |
|---|---|
| 冷启动 | 没有线上日志时,用强模型按业务场景批量生成指令-回答对,先训出可用版本 |
| 偏好对构造 | 同一 prompt 采样多个回答,用强模型或规则排序,生成成对偏好数据(如 Self-Instruct /self ˈɪnstrʌkt/ 思路的延伸) |
| 边界与红线扩充 | 程序化生成攻击样本、格式极端样例,补足真实日志中的稀缺分布 |
| 数据增广 | 同一事实改写为多种问法,提升鲁棒性 |
3.2 合成数据的生成原则
- 种子真实:从真实业务日志/文档出发生成,不要让模型凭空想象场景;
- 多样优先:prompt 层面控制话题、长度、角色、格式的多样性,防止生成分布塌缩到几种模板;
- 质量过滤:生成后过一遍质量关(规则校验 + 强模型打分),宁缺毋滥;
- 留出真实集:永远保留一批纯人工/纯线上数据作为最终评测的「真实分布锚点」,合成指标只能参考。
落地示例:用多样性轴组合做冷启动生成(Node.js + TS,OpenAI 协议):
import OpenAI from "openai";
const client = new OpenAI();
interface Axis {
name: string;
values: string[];
}
// 多样性轴:每条样本从各轴随机取值组合,从源头防模板化
const AXES: Axis[] = [
{ name: "场景", values: ["退货", "换货", "开发票", "查物流", "改地址"] },
{ name: "情绪", values: ["平静", "着急", "愤怒", "含糊其辞"] },
{ name: "表达", values: ["口语", "书面", "多错别字", "多轮追问"] },
{ name: "身份", values: ["新用户", "会员", "代下单的家属"] },
];
function pick<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
async function genQuestion(): Promise<string> {
const combo = AXES.map((a) => `${a.name}=${pick(a.values)}`).join(",");
const resp = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "system",
content: "你是电商客服语料构造器。根据给定组合生成一条用户真实提问,只输出问题本身,不要回答。",
},
{ role: "user", content: `生成一条用户问题:${combo}` },
],
});
return resp.choices[0].message.content ?? "";
}
生成的 2000 条里,三道过滤关通常只剩 1000~1500 条可用:
interface SynthSample {
q: string;
a: string;
judgeScore: number; // 强模型 1-5 打分
}
async function pass(s: SynthSample): Promise<boolean> {
// 第 1 关:规则(便宜,先跑)
if (s.a.length < 20 || /作为一个AI|很抱歉/.test(s.a)) return false;
// 第 2 关:查重(向量相似度 > 0.92 视为模板重复)
if (await tooSimilar(s.q, existingCorpus)) return false;
// 第 3 关:强模型打分,>= 4 分才留
return s.judgeScore >= 4;
}
3.3 污染与偏见放大:何时发生
这是文首第二个学习问题。合成数据放大偏见与污染的四个典型时机:
- 自我循环(self-consumption):用模型 A 的输出训练模型 B,再用 B 的输出训练 C——尾部分布逐代丢失,多样性单调下降,最终 模型坍缩 /ˈmɒdl kəˈlæps/ (Model Collapse,模型坍缩);
- 种子带偏:生成种子里某类人群/某类问法占绝对多数,生成会放大该倾斜(模型倾向于把多数模式推得更极端),下游在少数分布上表现恶化;
- 评测集泄漏:合成数据与黄金集同源(同模型、同场景、同模板)时,评测变成「考自己做过的题」,指标虚高,飞轮失去导航能力;
- 风格锁定:合成数据全是同一个生成模型的文风,训练后模型输出千篇一律,真实用户偏好反而下降。
判定口诀:合成数据只用于「扩大数量与覆盖」,不用于「定义对错标准」。对错永远来自人工确认的真实样本(黄金集)。
四、与 SFT / DPO / 偏好数据的衔接
飞轮产出最终要落到训练范式上,三种衔接方式:
4.1 三个数据形态
| 数据形态 | 长什么样 | 训什么 |
|---|---|---|
| 指令对 | (prompt, 好回答) |
SFT /ˌes ef ˈtiː/ ( Supervised Fine-Tuning ,监督微调) |
| 偏好对 | (prompt, 好回答, 差回答) |
DPO /ˌdiː piː ˈoʊ/ ( Direct Preference Optimization ,直接偏好优化) |
| 排序列表 | (prompt, 多个回答 + 排序) |
RLHF /ˌɑːr el eɪtʃ ef/ ( Reinforcement Learning from Human Feedback ,基于人类反馈的强化学习)奖励模型 |
4.2 飞轮数据如何变成训练数据
- 好例 → SFT:线上确认的高质量问答,按训练格式整理(含 system、工具调用轨迹要完整);
- 好差对 → DPO:同一请求的「用户采纳版本 vs 修订前版本」「差评样本 vs 修正样本」天然成对,是 DPO 数据最廉价的来源;
- 标注排序 → RM /ˌɑːr ˈem/ ( Reward Model ,奖励模型):标注团队对同请求多回答排序,训练奖励模型,适合关键场景做 RLHF;
- 合成 → 冷启动 SFT 为主:合成数据质量不可控,优先只用于 SFT(有标准答案约束),避免直接做偏好对(排序本身可能继承生成模型的偏见)。
落地示例:rq-8102(点踩案例)如何变成一条 DPO 数据——rejected 直接取线上 v1 的原始错误回答,chosen 取客服修正后的答案,不造数据,改数据:
{
"prompt": "电饭煲用了10天,可以以\"不想要了\"为由退货吗?",
"chosen": "可以。电器类目支持15天无理由退货,\"不想要了\"属于有效理由,需保持商品完好,依据《电器类目售后政策》第3条。",
"rejected": "可以哦,收货7天内不想要了都可以退的呢~"
}
配套说明字段(进训练框架前展开成列):source: rq-8102、failure_mode: 事实错-政策期限、annotator: 客服-王某。累积 300~500 对这样的数据,足够做一次 LoRA /ˈlɔːrə/ ( Low-Rank Adaptation ,低秩适配)轻量 DPO:
# LLaMA-Factory 训练配置(节选)
stage: dpo
model_name_or_path: Qwen/Qwen2.5-7B-Instruct
dataset: cs_dpo_v1 # 上面那批偏好对
lora_rank: 16
learning_rate: 5.0e-6
num_train_epochs: 2
4.3 迭代节奏
- 冷启动:合成数据 + 少量人工 → SFT v1;
- 上线:埋点收集反馈,跑通「日志 → 标注」;
- 每周:差评样本修复成偏好对,攒够一批做 DPO;
- 每月:黄金集审订扩容,回归全量指标,决定是否触发更大动作(换基座模型、大规模微调)。
五、端到端落地示例:四周迭代
把前面所有零件拼成「小智」的四周排期——人少(1 算法 + 1 工程 + 1 名义兼职标注)也能转起来的最小版本:
| 周 | 动作 | 产出 | 用到的工具 |
|---|---|---|---|
| W1 | 多样性轴生成 2000 条 → 三道过滤剩 1200 条 → SFT 训出 v1(LoRA) | 可上线的基础版 | OpenAI 协议生成 + LLaMA-Factory |
| W2 | 反馈埋点上线;黄金集 v1(300 条,每条含 YAML 判分) | 日志表 + gs-cs-v1 |
ClickHouse + promptfoo |
| W3 | 第一次人工标注:加权采样 500 条 → 标出 80 个坏例 → 修复成 60 对偏好数据 | cs_dpo_v1 数据集 |
Label Studio |
| W4 | DPO 训练 v1.1 → 黄金集回归(82.4% → 84.8%,过噪声带门槛)→ 灰度 10% 流量 | v1.1 灰度上线 | LLaMA-Factory + promptfoo |
工具栈速查(全部有开源实现):
| 环节 | 工具 | 备注 |
|---|---|---|
| 日志存储 | ClickHouse / PostgreSQL | 按天分区,反馈事件与请求绑定 |
| 标注平台 | Label Studio、Argilla | 坏因打标 + 偏好排序都支持 |
| 评测回归 | promptfoo、DeepEval、Ragas(RAG 场景) | 接 CI,每次改提示词自动跑 |
| 训练 | LLaMA-Factory(SFT/DPO/LoRA 一条龙)、TRL | 单卡 A10 也能跑 7B 级 LoRA |
| 调度 | 自写 TS 脚本 + 定时任务(GitHub Actions / cron) | 采样、过滤、对账都是批处理脚本 |
四周之后的常态节奏回到 4.3:每周一批偏好数据,每月一次黄金集审订——飞轮转起来的标志是:修复一个坏例的成本从「改代码」降到「攒一条数据」。
自检清单
- 反馈埋点有明确的语义定义,产品与工程达成一致;
- 黄金集与训练集物理隔离,黄金集有版本号;
- 每次改动跑回归,噪声带已测定;
- 合成数据有真实种子、多样性控制、质量过滤三道关;
- 保留纯真实数据的最终评测锚点;
- 飞轮闭环周期以周为单位。
本文缩写
| 缩写 | 音标 | 全拼 | 中文 |
|---|---|---|---|
| DPO | /ˌdiː piː ˈoʊ/ | Direct Preference Optimization | 直接偏好优化 |
| LoRA | /ˈlɔːrə/ | Low-Rank Adaptation | 低秩适配 |
| PII | /ˌpiː aɪ ˈaɪ/ | Personally Identifiable Information | 个人身份信息 |
| RLHF | /ˌɑːr el eɪtʃ ef/ | Reinforcement Learning from Human Feedback | 基于人类反馈的强化学习 |
| RM | /ˌɑːr ˈem/ | Reward Model | 奖励模型 |
| SFT | /ˌes ef ˈtiː/ | Supervised Fine-Tuning | 监督微调 |
| YAML | /ˈjæməl/ | YAML Ain't Markup Language | 一种配置文件格式 |
参考资料
- Self-Instruct: Aligning Language Models with Self-Generated Instructions(Wang 等,2022,合成指令数据开山之作)
- The Curse of Recursion: Training on Generated Data Makes Models Forget(Shumailov 等,2023,模型坍缩)
- DPO: Your Language Model is Secretly a Reward Model(Rafailov 等,2023)
- 直接偏好优化 · 本仓库
评论
评论加载中…