关于 building-finetuning-datasets
这项技能指导团队设计和整理微调数据集,涵盖区分检索需求与训练目标,以及评估能力退化。它提供参考流水线,用于按分类生成数据、过滤、去重、排除评测集污染和报告覆盖情况。
主要功能
- 01区分事实检索与微调目标
- 02生成数据前探测基础模型能力
- 03构建任务、保留能力和行为评测集
- 04按分类生成并整理数据
- 05检查覆盖率、重复项和评测污染
- 06记录数据来源与训练配置
适用场景
- 01为模型行为或输出格式规划数据集
- 02避免训练适合由检索处理的事实
- 03检测微调过程中的能力退化
- 04生成、过滤并检查数据覆盖情况
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ osmontero/ opencode-skills --skill building-finetuning-datasets安装后在 Claude Code 中输入/building-finetuning-datasets确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/avoiding-degradation.mdchoosing-technique.mddata-quality.mdlora-configuration.mdsynthetic-generation.mdscripts/curate.pygenerate.py详细介绍
构建微调数据集
微调的本质是其数据集。超参数决定训练能否收敛;数据集决定模型会变成什么样。大多数效果不佳的微调,都是配置正确的训练过程处理了编码了错误内容的数据。
两种失败造成的大部分损害,都在生成第一个样本之前就已注定:把本该交给检索的事实教给模型,以及没有办法检测模型在其他方面是否变差。
关卡 1:这是知识问题吗?
如果目标包含“让它了解我们的 X”——产品名称、运行手册事实、当前库存、政策细节——那么这部分属于检索问题,微调不是解决它的正确工具。
模型在预训练中获得事实;微调教会它们运用已有知识。与符合现有知识的样本相比,承载真正新事实的样本需要更长时间才能学会;而当模型最终学会这些事实时,模型产生幻觉的倾向会线性增加(Gekhman 等人,EMNLP 2024,在闭卷问答上测得)。这一增长率是按整个评估过程衡量的,而不只是新教的内容,因此代价会体现在整体事实准确性上;训练时间越长,新事实越牢固,这种代价也越大。
继续之前,请明确拆分需求:
| 目标的组成部分 | 对应方案 |
|---|---|
| 事实、文档、实体,以及任何会变化的内容 | RAG |
| 格式、结构、固定风格、语气 | 微调 |
| 任务流程、工具调用语法、推理风格 | 微调 |
| 领域词汇和针对检索上下文的问题形式 | 微调,推理时仍检索事实 |
请说明各部分分别采用了什么方案。用户提出一项需求时,通常希望两部分都得到解决,而不是把检索部分悄悄塞进训练集。
关卡 2:选择哪种技术
阶段和参数预算是彼此独立的选择。决定之前,请阅读 references/choosing-technique.md——其中介绍了提示词/RAG/解码替代方案、持续预训练与 SFT、偏好优化和 RL、DPO/KTO/ORPO/SimPO 的选择、全量微调与 LoRA 和 QLoRA,以及各阶段所需的顺序。
简而言之:如果你能写出正确的输出,就用 SFT。如果你只能判断两个输出哪个更好,就用偏好优化。如果脚本能够验证正确性,且 SFT 已达到瓶颈,就用 RL。默认参数预算选择 LoRA;如果受 VRAM 限制,则选择 QLoRA。
Gate 3:基础模型已经能做到吗?
在为任何能力或行为生成数据之前,先对基础模型进行探测——针对你关注的案例,测试二十来个样本,只保留经测量确认模型表现不佳的部分。这是 Gate 1 的知识规则的泛化:微调是为了纠正模型做错的事,而训练模型已经能处理的部分并非没有代价。
这两种代价都是真实存在且有记录的:
- 白费功夫。 一个团队在构建行为数据集时探测了基础模型,发现它在某一种完整行为上的通过率已约为 94%,且没有可复现的失败模式——于是他们删掉了 150 条不会带来任何学习的样本。
- 实际退化。 同样规模较小的行为微调(约 750 条样本),导致模型在四项推理基准上的通用能力出现可测量的退化(小学数学成绩下降约 10–16 个百分点)。直到运行基准评测面板时,团队才发现这一点,因为本次训练没有评估集。你添加的每条样本都可能以牺牲其他方面的能力为代价;不能带来任何学习的样本只会付出代价,却没有收益。
因此,探测并非可有可无的谨慎步骤——它决定哪些内容应进入数据集。保留已确认失败的案例,对结果不明确的案例重新采样(1/3 次拒答只是采样噪声,并非失败——单次拒答评估的准确率仅约为 92%,Larsen 等人,2025 年),并删除基础模型已经能处理的内容。将已确认的类别报告为比率(“2/3”“3/3”),而非二元结果:在纯噪声下,2/3 的确认结果仍有约 26% 的概率出现,因此它是需要关注的项目,而非已解决的问题;而且它正是下一次训练后应重新探测的类别。
交付物
微调数据集不是一个装满示例的文件。它由 8 个部分组成,缺少其中任何一项的交接成果都无法评估或复现。请按以下顺序交付——顺序本身就是方法:
eval/— 首先构建留出评测套件。 仅使用真实示例,在生成前预留。包含 3 个子集:task(目标行为的真实留出示例)、retention(基础模型已能通过的通用能力探测项)、behavior(需要保留的拒绝、安全性和语气不变项)。baseline.json— 训练前对基础模型在全部 3 个子集上的表现评分。 没有它就没有分母,“看起来不错”也算不上结果。taxonomy.md— 数据必须覆盖的维度,并为每个单元格设定目标数量。多样性来自对条件的变化,因此必须在生成前将这些维度列明,并在筛选后重新计数。空单元格应成为下一轮生成的目标,而不是可接受的结果。train.jsonl/val.jsonl/test.jsonl,采用 messages 格式,按源文档划分,确保合成样本不会跨越数据集边界;在 3 个层面去重(精确重复、近似重复、语义重复),并针对所有计划报告的评测集进行去污染。- 回放混合数据——将任务数据与通用指令数据按明确比例混合,避免模型遗忘已有能力。
- 经过验证的格式约定——确认哪一个 loss-masking 标志与数据集格式匹配(messages 格式使用
assistant_only_loss,prompt/completion 格式使用completion_only_loss),聊天模板实际输出的是哪个 EOS token,并通过解码一个训练批次确认二者。标志错误会导致模型在用户轮次上训练,EOS 错误则会导致模型永不停止;这两种问题都不会体现在糟糕的 loss 曲线上,因此它们必须是经过核验的交付物,而不能仅凭习惯设定。 card.md— 来源信息。 种子来源、生成器模型及版本、生成提示词、筛选条件及阈值、去重和去污染方法、各 taxonomy 单元格的数量、许可证及条款。config.yaml— 训练配置,启用评测曲线,以便及时发现过拟合。
首先构建评测集,才能避免数据集被优化成生成器碰巧产出的样子。
每份说明性文档都先搭建骨架,每次编辑一个章节。 单次工具调用最多只能输出大约 1,000 个 token,而计划、taxonomy 和 card 都更长,因此一次性写入会在字符串中途截断,导致调用失败。先写出文件标题,并在每个标题下放一行占位内容,然后每次编辑替换一个占位内容。与其等到失败再补救,不如从一开始就这样做——骨架不增加成本,且无论如何每个章节所需的编辑次数都相同。
使用脚本生成,而不是手动编写
编写流水线;由流水线生成数据。 无论所需样例多么少,逐条亲自将训练样例写入 JSONL 文件,都不适合这项工作:
- 无法扩展。 300 个样例,每个约 800 个 token,远远超出任何 Agent 的生成能力;你会得到一个被截断的文件,却以为自己生成了完整数据集。
- 无法复现。 无法重新生成的数据集就无法修复。使用脚本、固定的随机种子、锁定版本的生成模型和带版本号的提示词,就能在发现缺陷时重新运行。
- 生成循环确实是个循环。 生成各分类单元的内容 → 验证 → 筛选 → 去重 → 重新统计覆盖情况 → 为样例不足的单元生成内容。这个循环要运行多次,无法手动完成。
- 筛选本来就需要代码。 MinHash 近似重复检测、嵌入向量最近邻阈值和 n 元语法去污染,都不是靠肉眼就能完成的。
因此,你手动编写的产物应当是输入:分类体系、生成提示词、种子样例和筛选阈值。后续一切都应通过运行程序生成。
此技能中的 scripts/generate.py 和 scripts/curate.py 是一套可用的参考流水线——基于分类体系生成内容并支持断点续跑,然后执行去重、去污染并生成覆盖报告。阅读这些脚本,根据任务调整提示词和分类体系,然后运行;它们是起点,不是框架。优先在其基础上扩展,而不是从头编写流水线。
工作顺序
digraph finetune_data {
rankdir=TB;
"Split knowledge from behavior" [shape=box];
"Behavior part non-empty?" [shape=diamond];
"Route to RAG; stop" [shape=doublecircle];
"Choose stage + parameter budget" [shape=box];
"Write eval suite from real data" [shape=box];
"Score base model -> baseline.json" [shape=box];
"Define taxonomy of axes to cover" [shape=box];
"Generate wide" [shape=box];
"Verify, filter, dedup, decontaminate" [shape=box];
"Coverage gaps remain?" [shape=diamond];
"Blend replay data" [shape=box];
"Train with eval curve" [shape=box];
"Gates pass vs baseline?" [shape=diamond];
"Ship with card.md" [shape=doublecircle];
"Diagnose: data or config?" [shape=box];
"Split knowledge from behavior" -> "Behavior part non-empty?";
"Behavior part non-empty?" -> "Route to RAG; stop" [label="no"];
"Behavior part non-empty?" -> "Choose stage + parameter budget" [label="yes"];
"Choose stage + parameter budget" -> "Write eval suite from real data";
"Write eval suite from real data" -> "Score base model -> baseline.json";
"Score base model -> baseline.json" -> "Define taxonomy of axes to cover";
"Define taxonomy of axes to cover" -> "Generate wide";
"Generate wide" -> "Verify, filter, dedup, decontaminate";
"Verify, filter, dedup, decontaminate" -> "Coverage gaps remain?";
"Coverage gaps remain?" -> "Generate wide" [label="yes, target the empty cells"];
"Coverage gaps remain?" -> "Blend replay data" [label="no"];
"Blend replay data" -> "Train with eval curve";
"Train with eval curve" -> "Gates pass vs baseline?";
"Gates pass vs baseline?" -> "Ship with card.md" [label="yes"];
"Gates pass vs baseline?" -> "Diagnose: data or config?" [label="no"];
"Diagnose: data or config?" -> "Generate wide";
}
大多数流水线都会跳过针对覆盖缺口的返工步骤。生成成本低,筛选具有破坏性,因此应当广泛生成,再筛选精简——然后检查哪些分类单元为空,并专门针对这些单元生成内容,而不是再次运行相同的无条件循环,得到相同的模式。
重质量而非重数量,用数据说话
在预算固定的情况下,经过筛选的数百个样例胜过未经筛选的数万个样例,适用于通用指令和风格——LIMA(1,000 个经过筛选的样例可与 50k 个样例竞争)、AlpaGasus 和 LIMO(817 条推理轨迹)都得出了这一一致结论。这些论文衡量的正是这一点,但不能将结论推广到覆盖基座模型的先验行为(如安全拒绝或强默认行为):在这种情况下,行为既需要达到最低数量,也需要在混合数据中占据最低比例;而少量围绕少数场景改写的变体,是最不适合“少即是多”的情形。在得出重质量轻数量的结论前,先判断自己属于哪种情况。典型范围如下:
| 目标 | 样例数 |
|---|---|
| 格式/结构转换 | 100–1,000 |
| 风格、语气、表达方式 | 500–2,000 |
| 分类/提取 | 每类 50–500 |
| 通用指令遵循 | 1,000–10,000 |
| 推理蒸馏 | 800–10,000 条经验证的轨迹 |
如果一次训练效果不佳,把数据量翻倍通常是错误的第一反应——尤其是新增数据只是已有场景的改写时:在更新预算固定的情况下,重复少量样例会像扩展数据规模一样导致世界知识遗忘;数据集狭窄且重复,则会造成模式坍塌。真正有助于保持知识的数据调整,是加入新的通用回放数据,而不是增加目标行为数据。按以下顺序检查:LR(先检查保持能力相关项),然后是目标模块、数据多样性,最后是混合比例。
退化门槛
在判定微调成功前,先针对 baseline.json 运行这些检查。每项都对应一种有记录的失败模式;通过任务指标却未通过这些检查,是糟糕模型上线最常见的原因。
| 门槛 | 检查 | 未通过时的处理方式 |
|---|---|---|
| 任务 | 在留出的真实示例上,目标指标有所提升 | 微调没有起作用——检查 LR 和目标模块 |
| 保留能力 | 在基础模型已通过的能力上,表现高于不同运行间的噪声(在你的 n 下 ≥1 SE) | 在少数基准上出现个位数下降,是 LoRA-SFT 的常见结果,不算“灾难性”(文献中所说的灾难是某项基准接近 0,例如 SLIM 的 MMLU→0.00)。按以下顺序修复:降低 LR(从保留能力角度入手,参见 lora-configuration.md),并加入回放混合数据——这是有证据支持的调节手段;然后使用验证集划分 + 较小的 lora_dropout 作为低成本过拟合控制;最后减少训练轮数 |
| 事实性 | 幻觉率不高于基础模型 | 你教会了模型未知事实——将它们移至 RAG |
| 安全性 | 保留拒绝行为 | 即使仅使用纯良数据,安全对齐也会退化(Qi 等人,ICLR 2024)——在混合数据中加入安全示例 |
| 格式 | 输出正常结束且可解析 | EOS 或聊天模板问题,不是数据问题 |
| 多样性 | 输出没有收敛到单一表述方式 | 过拟合——减少训练轮数、降低 LR,或将 alpha 缩放为 0.5 |
只测任务指标,会导致模型在一项能力上提升 10 分、其他所有能力却下降 15 分,最终仍被上线。
参考资料和脚本
阅读你需要的参考资料;不要一次加载全部五份。
| 文件 | 内容 |
|---|---|
references/choosing-technique.md | RAG/提示词方案、CPT 与 SFT、DPO、RL 的比较,DPO/KTO/ORPO/SimPO,全量微调与 LoRA、QLoRA 的比较,阶段顺序 |
references/synthetic-generation.md | 质量/多样性/复杂度之间的权衡、Self-Instruct、Evol-Instruct、Magpie、角色设定、基于文档的问答、自我对话、轨迹、蒸馏、验证与评判偏差、模型坍塌、许可 |
references/data-quality.md | 评估集构建、规模、去重级别、去污染、平衡、用于覆盖基础模型先验的数据混合占比、消息格式、损失掩码、EOS、聊天模板、数据划分泄漏、数据集说明卡 |
references/lora-configuration.md | rank、alpha、目标模块、LR、学习率计划、打包、作为回归调节手段的 dropout/验证集、rsLoRA/DoRA/LoRA+/PiSSA、配置示例、症状到调节参数的对照表、合并 |
references/avoiding-degradation.md | 遗忘机制、回放比例和来源、未知知识与未知能力检测、安全性回归、过拟合与坍塌症状、门槛 |
脚本目录中的脚本构成一条可运行的参考流水线——请在此基础上改造,而非重写:
| 脚本 | 功能 |
|---|---|
scripts/generate.py | 针对任意 OpenAI 兼容端点进行分类体系驱动的生成,支持断点续跑,并记录每行的来源信息 |
scripts/curate.py | 丢弃格式错误的数据 → 精确/近似重复数据去重 → 根据评估文件去污染 → 按单元生成覆盖率报告(仅使用标准库) |
常见错误
| 错误 | 问题所在 |
|---|---|
| 通过微调注入事实 | 学习速度慢,而且会随之线性增加幻觉 |
alpha = 0.5r | 方向反了。应使用 alpha = 2r(或 r);保持 alpha/r ≥ 1 |
| 只针对注意力层 | MLP 层承载更高秩的更新——应针对所有线性层 |
| 增强数据后再拆分 | 合成数据中的相似样本会同时进入训练集和测试集,得分也就不可信了 |
| 不测量基础模型的表现 | 没有基准值,性能回退便无从察觉 |
| 混合数据中没有回放数据 | 模型学会了任务,却丢掉了其他所有能力 |
| 为了多样性提高温度 | 多样性来自变化条件——角色设定、分类体系单元——而非采样噪声 |
| 信任未经验证的 LLM 评判器 | 位置、冗长程度和自我偏好/熟悉度偏差,衡量的都不是正确指标。对于代码质量,确定性检查(语法、执行、架构)才是经过验证的层——评判器只是用于排序的参考,而不是通过/不通过的关卡 |
| 只对完全匹配项去重 | 合成数据流水线会过量生成语义上近似重复的样本,而精确哈希无法识别这些样本 |
| 对小型数据集进行打包 | 数百条数据无法提升吞吐量,还会带来多轮数据交叉污染的风险——不要启用 |
| 训练基础模型已经能通过的部分 | 最好也只是浪费样本;每新增一条样本,都可能损害其他方面的能力 |
| 没有验证集 | 没有评估损失曲线,就无法及时发现性能回退,直到模型发布后才会暴露 |
| 忽略教师模型的服务条款 | 主要 API 提供商限制使用其输出进行训练;这会阻碍发布,而非开发 |