作者:好奇的小逸
https://zhuanlan.zhihu.com/p/2035128896316756341
之前再做Mid-train和SFT时,积攒了一些经验,一直没有去写这方面的内容,这次就好好的总结一下相关的内容, 首先我们先有个认知就是:真正影响训练效果的,往往不是总量,而是样本的信息密度、覆盖面和分布是否合理。
如果Query没筛好,常见的问题包括:
- 大量重复样本让模型反复学习同一种表达
- 某些高频任务占比过高,导致模型能力偏科
- 指令形式过于单一,泛化到真实用户场景时效果差
- 数据看起来很多,但真正带来增量的信息很少
因此,SFT数据筛选最好不要只靠人工“看着挑”,而应该按一套相对固定的流程来做。一个实用的顺序通常是:
先做去重,再做质量过滤,然后检查指令多样性,最后做类别配比和补齐。
1. 数据去重:先解决“重复学习”问题
去重是最基础的一步。因为如果重复样本太多,模型会把训练预算浪费在已经学过很多次的模式上。
在实际处理中,去重通常要分成三层。
1.1 精确去重
这一步处理的是完全重复或几乎完全重复的数据,常用方法包括:
文本标准化后做哈希去重
- 统一全角/半角、大小写、空格、换行、标点形式
- 去掉无意义前后缀,比如固定模板句、重复免责声明
- 对标准化后的
query或query + response计算 MD5 / SHA1 / SimHash - 哈希相同则视为重复
这一步适合清理:
- 完全重复爬取的数据
- 不同来源但内容一样的数据
- 仅换行、标点、空格不同的样本
1.2 近重复去重
很多数据不是完全一样,而是只改了少量词。 例如:
- “帮我总结这篇文章”
- “请帮我总结一下这篇文章内容”
- “总结下面这篇文章”
这类数据如果大量存在,也会让训练信号过于集中。
常用方法:
n-gram Jaccard 相似度
- 将 query 切成 2-gram 或 3-gram
- 计算两个样本的 Jaccard 相似度
- 相似度高于阈值(例如 0.85 / 0.9)时判为近重复
编辑距离 / 归一化编辑距离
- 适合短文本 query
- 对几十个字以内的指令效果较直观
SimHash + Hamming 距离
- 适合大规模数据做快速近似去重
- 可以先召回近邻,再做更精细判断
MinHash + LSH
- 适合百万级以上文本集合
- 能高效找出高相似候选对
实际工程里,一般不会直接全量两两比较,而是:
1.先标准化文本
2.用 SimHash / MinHash / LSH 做候选召回
3.再用 Jaccard 或编辑距离做精筛
这样速度和效果会比较平衡。
1.3 语义去重
语义去重处理的是“写法不同,但任务本质相同”的样本。
例如:
- “把这段话换一种更正式的说法”
- “请将下面内容改写得更书面一些”
字面上差异不小,但训练意义接近。如果这种数据占比过高,会挤压其他任务类型的训练空间。
常用方法:
Embedding 相似度去重
- 使用文本向量模型将 query 编码成 embedding
- 计算余弦相似度
- 相似度高于阈值时判为语义近邻
聚类后簇内保留代表样本
- 可用 KMeans、层次聚类、HDBSCAN
- 每个簇只保留中心样本、最高质量样本或最具代表性的若干条
ANN 检索 + 阈值过滤
- 用 Faiss / HNSW 建索引
- 对每条样本搜索 top-k 相似邻居
- 相似度过高时删除冗余样本
这里需要注意的是:
SFT 数据不应把所有语义相近样本都删光。
因为模型仍然需要学习“同一任务的不同表达方式”。所以更合理的做法不是“完全去掉语义相近样本”,而是:
- 对高度冗余的簇做降采样
- 每个语义簇保留 2~5 种不同表达风格
- 保留不同语气、长短、约束条件下的变体
1.4 去重时保留哪条样本
当发现重复或相似样本后,不是随便删,而是要有保留规则。
常见保留策略:
- 保留 response 更完整、更准确的一条
- 保留约束更清晰的一条
- 保留格式更规范的一条
- 保留来自高质量来源的一条
- 如果 query 类似但 answer 风格差异明显,可保留少量风格变体
2. 质量过滤:先把明显低质量样本剔掉
去重之后,紧接着就是质量过滤。
常见过滤规则包括:
2.1 格式质量
过滤掉以下样本:
- query 或 response 为空
- 文本截断、不完整
- 大量乱码、HTML 残片、转义符污染
- 多轮对话字段错位
- system / user / assistant 角色混乱
2.2 内容质量
过滤掉以下样本:
- answer 明显答非所问
- 低信息量回复,如“好的”“收到”“不知道”
- 大段重复句子
- 强模板化、无实际任务信息的样本
- 含明显事实错误或逻辑错误的样本
2.3 安全与合规
过滤或单独标记:
- 泄露隐私信息的数据
- 含违法违规、极端有害内容的数据
- 敏感任务中未经审查的答案
- 不适合当前模型目标的高风险样本
如果数据规模较大,质量过滤可以用“规则 + 小模型打分 + 人工抽检”结合的方式:
- 规则先过滤明显脏数据
- 分类器或 reward model 做质量打分
- 人工抽检边界样本,防止误删
3. 指令多样性:不是多,而是结构化地多样
很多文档在说“要保证指令多样性”时,只停留在“问答、总结、分类都要有”这一层,这还不够。真正有效的多样性,至少要覆盖下面几个维度。
3.1 任务类型多样性
最基本的一层是任务意图覆盖。
常见类别包括:
- 问答
- 信息抽取
- 总结
- 改写
- 分类
- 翻译
- 推理
- 代码生成 / 代码解释
- 创作
- 结构化输出
- 工具调用 / Agent 式指令
具体方法:
先定义任务 taxonomy
- 不要先看数据量,而是先定义你希望模型具备哪些能力
- 给每条 query 打上一级/二级任务标签
按标签统计占比
- 看是否某几个类型过高
对头部类型做下采样,对缺失类型做补齐
如果没有现成标签,可以这样做:
- 用规则关键词做初筛
- 用 embedding 聚类找潜在任务簇
- 再人工命名簇标签
- 或用 LLM 对 query 做批量意图分类
3.2 表达方式多样性
即使任务类型一样,也要保留不同表达方式。
例如“总结”类指令,可以分成:
- 直接命令式:总结下面内容
- 请求式:请帮我概括这段话
- 结果导向式:提炼成 3 个要点
- 角色约束式:以产品经理视角总结
- 风格约束式:用正式、简洁、口语化方式总结
具体方法:
句式模板聚类
- 将 query 抽象成句式模板,例如“请帮我 + 动作 + 对象”
- 统计模板分布,避免单一模板过多
基于 embedding 的表达聚类
- 在同一任务标签内再做聚类
- 每个簇保留若干不同表达
按语气标签采样
- 命令式、礼貌式、口语式、专业式、模糊式、强约束式
3.3 输入长度与上下文形态多样性
真实用户不会总给一句简短指令。
还要覆盖:
- 短 query vs 长 query
- 有上下文 vs 无上下文
- 单轮 vs 多轮
- 非结构化文本 vs 表格 / 列表 / JSON
- 干净输入 vs 含噪输入(错别字、口语、省略)
这里可以使用:
按 token 长度分桶采样,例如:
- 0~32 tokens
- 33~128 tokens
- 129~512 tokens
- 512+ tokens
按上下文类型打标签:
- 单轮
- 多轮
- 文档问答
- 表格理解
- 代码上下文
对每个桶设置最小保留量
3.4 输出约束多样性
模型训练不只学“做什么”,还学“按什么要求做”。
所以要覆盖不同输出约束,例如:
- 字数限制
- 格式限制(JSON、Markdown、表格、列表)
- 风格限制(正式、简洁、幽默、学术)
- 角色限制(像老师、面试官、客服一样回答)
- 步骤限制(先分析再回答、先列提纲再展开)
针对这些问题,这里可以使用:
- 解析 query 中的约束字段
- 将约束条件标准化为标签
- 统计不同约束组合的频次
- 对高频无约束样本降采样,保留更多有明确输出约束的样本
3.5 多样性不是平均分布
多样性不是把所有类别都做成完全一样多,而是要匹配模型目标。
例如:
- 通用助手模型:任务面要广
- 企业客服模型:问答、检索增强、多轮澄清占比可以更高
- 代码模型:代码补全、debug、解释、重构的覆盖更关键
所以多样性控制的核心不是“均匀”,而是:
在符合目标任务分布的前提下,避免某一种模式过度垄断。
4. 类别覆盖与配比:解决“头部过多,长尾缺失”问题
实际数据分布常常是高度偏斜的,头部任务占了大多数。仅靠自然采样,最后得到的数据集往往不均衡。
4.1 先建立类别体系
常见做法:
一级标签:问答 / 总结 / 改写 / 分类 / 推理 / 代码 / 创作
二级标签:事实问答 / 开放问答 / 情感分类 / 摘要生成 / 风格改写等
领域标签:教育 / 法律 / 医疗 / 金融 / 通用办公 / 编程
交互标签:单轮 / 多轮 / 工具调用 / 长上下文
4.2 统计每个类别的样本量与质量
不要只看数量,还要同时看:
- 样本数量
- 平均长度
- 平均质量分
- 重复率
- 失败率 / 脏样本率
有时候某个类别样本很多,但大量是低质量模板,实际有效占比并不高。
4.3 常用配比方法
方法一:固定配额采样
为每个类别设定目标样本数或比例。
适合:
- 已知目标能力结构
- 数据集规模可控
- 希望结果稳定可解释
方法二:温度采样 / 平滑采样
对头部类别降权,对长尾类别升权,但不做到完全平均。
常见形式:
- 按类别频次的
p_i^α采样,其中0 < α < 1 α越小,分布越平α = 1接近原始分布
这种方法通常比强行平均更自然。
方法三:分层采样
先按大类分层,再在类内按质量、长度、表达方式进一步采样。
例如:
1.先保证总结、问答、改写、推理都有覆盖
2.再在“总结”类里保留短文本总结、长文总结、要点提炼、风格总结
3.最后按质量分排序保留前 N% 或分段采样
这是实践里比较推荐的方法,因为兼顾了覆盖和质量。
5. 如何用 NovelSelect 一类方法做“信息增量”筛选
如果完全靠人工筛选,很难同时兼顾去重、质量、多样性和配比,因此通常需要一种“样本价值评估”机制。
像 NovelSelect 这类方法,本质上是在回答一个问题:
这条样本相对于当前已选数据,是否还能提供新的训练信息?
5.1 NovelSelect 类方法通常关注什么
一般会综合考虑:
- 与已选样本的相似度是否过高
- 是否来自覆盖不足的任务簇
- 是否提供新的表达方式或新的约束组合
- 是否属于高质量、高信息密度样本
5.2 一种可执行的筛选思路
可以把每条样本的选择分数写成类似下面的形式:
score = 质量分 × 新颖性分 × 覆盖补偿分
其中:
- 质量分:回答质量、格式完整性、人工偏好分
- 新颖性分:与已选集合的最相似样本距离越大,分数越高
- 覆盖补偿分:来自稀缺类别、稀缺长度桶、稀缺约束类型的样本加权更高
然后用贪心或分批迭代的方式选样本:
1.先选一批高质量种子样本
2.每轮计算候选样本与已选集合的相似度
3.优先加入高质量且与当前集合差异大的样本
4.对已经过饱和的类别降低权重
5.直到达到目标规模
5.3 它比简单随机采样好在哪里
随机采样的问题在于:
- 容易重复抽到头部模式
- 无法主动补足长尾能力
- 很难控制信息冗余
而 NovelSelect 类方法更适合做:
- 冗余压缩
- 信息增量最大化
- 类别覆盖补齐
- 训练预算受限时的样本精选
6. 一个实用的数据筛选流水线
第一步:基础清洗
- 去掉空样本、乱码、截断样本
- 统一字段格式
- 统一多轮对话结构
第二步:精确去重 + 近重复去重
- 标准化文本后做哈希去重
- 用 SimHash / MinHash / Jaccard 做近重复过滤
第三步:质量打分
- 规则过滤
- 小模型或 reward model 打分
- 人工抽样校验阈值
第四步:任务与属性标注
给每条样本打标签:
- 任务类型
- 领域
- 长度桶
- 单轮 / 多轮
- 输出约束
- 风格 / 语气
第五步:语义聚类与多样性采样
- 在 embedding 空间聚类
- 每个簇保留代表样本 + 少量表达变体
- 避免一个簇占比过高
第六步:类别配比
- 对头部类别下采样
- 对长尾类别补样或提权
- 根据目标场景做最终分布调整
第七步:人工抽检
重点抽查:
- 去重误杀率
- 低质量漏网率
- 类别标注准确率
- 长尾类别是否真的保留住了
7. 实际执行时的几个经验
7.1 不要只对 query 去重
如果是SFT,对话质量往往由 query-response 对共同决定。
有些query 一样,但 response 一个好一个差,这种情况下不能只保留任意一条,而应优先保留高质量 answer。
7.2 不要把“多样性”理解成“随机性”
真正有用的多样性,是任务结构、表达方式、上下文形态和输出约束的系统性覆盖,而不是随便混入各种样本。
7.3 不要把长尾类别全部强行拉平
如果长尾类别质量低、标注噪声高,硬补太多反而会伤害训练。更好的做法是:
- 先保证核心能力的高质量覆盖
- 再逐步补长尾
- 长尾类别不足时宁可少,也不要大量灌低质量数据
7.4 用小样本先验证筛选策略
不要一上来就在全量数据上跑复杂流程。可以先在一小部分样本上测试:
- 阈值是否合理
- 去重是否误删
- 聚类是否把明显不同任务混在一起
- 最终类别分布是否符合预期
先验证,再扩到全量,成本会低很多。