背景:重复语料不只是浪费存储
预训练语料中的重复内容通常来自网页镜像、转载新闻、模板化商品页、站群页面、代码仓库 Fork、合成数据重复生成,以及同一基准题目的翻译或改写版本。
这些重复不会只增加磁盘占用。它们会改变训练样本的实际权重,使少数文本被模型反复看到,带来四类生产问题:
- 训练算力被重复内容消耗,有效数据密度下降。
- 记忆和逐字复现风险上升,尤其是高频长文本和敏感片段。
- 验证集与训练集发生泄漏,离线指标被高估。
- 数据版本不可解释,团队无法回答某条样本为何被保留或删除。
Lee 等人在多个语言模型数据集中发现了大量近重复文档和长重复片段,并观察到去重后模型逐字复现训练文本的频率显著下降。这个结论不能直接外推到所有模型和语料,但足以说明:去重是训练数据质量控制,而不是简单的存储优化。
核心原理:把”重复”拆成四个层级
生产系统不应使用一个算法和一个阈值处理所有重复。更稳妥的做法是把问题拆成四层。
第一层:规范化后的精确文档去重
先对文本执行可审计的轻量规范化,例如统一换行、Unicode 规范形式和确定性的空白处理,再计算内容摘要。
这一层适合处理:
- 完全相同的网页快照;
- 重复导入的文件;
- 同一语料多次拼接;
- 采集任务重跑产生的副本。
⚠️ 规范化规则本身必须版本化。若直接删除标点、数字、大小写或 HTML 结构,可能把原本不同的代码、公式、表格和实体信息错误合并。
第二层:精确长片段去重
两篇文档可能整体不同,却共享很长的重复段落。常见场景包括站点页眉页脚、法律声明、转载正文和论坛引用。
可以使用后缀数组、滚动哈希或分块指纹寻找长重复片段。Lee 等人的工作将精确长片段匹配与近重复文档匹配结合使用,说明两者解决的是不同问题。
生产落地时不应只记录”文档被删”,还应记录:
| 字段 | 说明 |
|---|---|
| 命中片段范围 | 重复内容在文档中的起止位置 |
| 片段长度 | token 数或字符数 |
| 来源文档 | 重复片段来自哪个文档 |
| 保留文档 | 去重后保留哪个文档 |
| 规则版本与批次 | 去重规则的版本号和运行批次 |
第三层:MinHash-LSH 近重复文档聚类
MinHash 用一组哈希签名近似估算两个文档 Shingle 集合的 Jaccard 相似度;LSH 则把可能相似的文档放入同一候选桶,避免全量两两比较。
典型流程是:
- 对文档分词并构造 n-gram Shingle;
- 生成 MinHash 签名;
- 用 LSH 产生候选对;
- 对候选对执行更精确的 Jaccard、编辑相似度或包含度校验;
- 构建重复关系图并计算连通分量;
- 每个重复簇只保留一个确定性代表文档。
⚠️ LSH 只是候选召回器,不应直接承担最终删除决策。否则哈希碰撞和阈值边界会把误判直接变成不可逆数据损失。
第四层:语义去重与基准污染检查
字符串层面的去重无法稳定识别翻译、重述、变量替换和答案顺序调整。SemDeDup 等研究使用预训练模型 Embedding 寻找语义重复,说明语义冗余可以在文本表面差异较大时继续存在。
但语义去重风险也更高。两篇内容接近的文章可能分别来自不同语言、不同立场或不同专业上下文。生产上更适合采用分层使用:
- 对超大 MinHash 簇做二次细分;
- 对合成语料做重复生成检查;
- 对高价值来源执行人工抽样;
- 对基准题、答案、翻译和改写版本建立独立污染扫描。
⚠️ 基准污染不应被混入普通语料去重。它应是一条单独门禁,因为其目标不是压缩语料,而是保护评测可信度。
工程落地:建立可回放的去重流水线
1. 先生成不可变数据快照和稳定 ID
每条原始记录至少保留:
| 字段 | 说明 |
|---|---|
document_id | 稳定唯一标识 |
source_id | 数据来源标识 |
| 原始 URI / 对象存储位置 | 可追溯的存储路径 |
| 抓取时间 | 数据获取时间戳 |
| 原始内容摘要 | 未规范化的内容哈希 |
| 语言、文档类型和许可信息 | 元数据 |
| 数据批次版本 | 批次标识 |
去重层只生成保留、聚类和排除清单,不直接覆盖原始对象。这样才能在阈值变化或误删时重新回放。
2. 把规范化规则当成数据契约
下面是一个示意配置。阈值必须基于真实语料抽样校准,不能照抄为统一默认值。
dedup_pipeline:
version: "2026-07-14-v1"
normalization:
unicode_form: "NFC"
normalize_line_endings: true
collapse_repeated_spaces: true
remove_boilerplate: false
exact_document:
hash: "sha256"
exact_substring:
enabled: true
minimum_match_tokens: "${CALIBRATED_MIN_MATCH_TOKENS}"
near_duplicate:
shingle_size: 5
num_permutations: "${CALIBRATED_NUM_PERMUTATIONS}"
lsh_threshold: "${CALIBRATED_LSH_THRESHOLD}"
secondary_verifier: "jaccard_and_edit_similarity"
semantic_duplicate:
enabled_for:
- synthetic_data
- oversized_duplicate_clusters
- benchmark_decontamination
embedding_model_version: "${PINNED_MODEL_VERSION}"
similarity_threshold: "${CALIBRATED_SEMANTIC_THRESHOLD}"
3. 代表文档必须确定性选择
重复簇中不能简单保留”最先到达”的记录,否则并行任务重跑可能产生不同结果。可以使用稳定排序规则:
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class Document:
document_id: str
source_priority: int
quality_score: float
license_ok: bool
captured_at: datetime
token_count: int
def representative_key(doc: Document) -> tuple:
return (
0 if doc.license_ok else 1,
-doc.source_priority,
-doc.quality_score,
-doc.token_count,
doc.captured_at,
doc.document_id,
)
def choose_representative(cluster: list[Document]) -> Document:
if not cluster:
raise ValueError("duplicate cluster must not be empty")
return min(cluster, key=representative_key)
排序依据应写入数据字典,并随去重结果一起保存。若规则升级,必须生成新的数据版本,而不是原地修改旧结果。
4. 把污染门禁放在数据切分之前和之后
建议执行两次检查:
- 切分之前:使用保留的评测集指纹扫描全量原始语料,移除明显命中的训练候选。
- 切分之后:再次检查训练、验证和测试之间的精确、近重复与语义重叠,防止聚类拆分或后续增量数据把污染重新带回。
对公开基准至少维护三类指纹:
| 指纹类型 | 说明 |
|---|---|
| 规范化文本和 n-gram 指纹 | 字符串层面的精确匹配 |
| 题目与答案拆分后的组合指纹 | 防止拼接绕过检测 |
| 翻译、改写和代码变量替换的语义表示 | 覆盖语义层面的泄漏 |
Yang 等人的研究表明,简单改写或翻译可能绕过基于字符串的污染检测。因此,语义扫描应作为高风险基准的补充手段,而不是完全替代精确匹配。
5. 增量语料不要每次全量重算
持续预训练和周期性爬取场景中,可以维护版本化签名索引:
- 新文档先与历史保留集比较;
- 新批次内部再聚类;
- 只重算受影响的连通分量;
- 删除和恢复动作都写入变更日志;
- 定期执行全量审计,防止索引漂移。
需要避免”今天保留 A、明天保留 B”的代表文档震荡。历史代表文档在质量未明显下降时应优先保持稳定。
阈值如何校准
阈值不应从论文或开源配置直接复制。更可靠的方法是按语言、来源和文档类型建立标注样本。
每个阈值版本至少评估以下指标:
| 评估维度 | 具体指标 |
|---|---|
| 去重质量 | 重复对识别的 Precision 和 Recall |
| 数据影响 | 删除 Token 比例 |
| 来源均衡 | 每个来源和语言的保留比例 |
| 簇分布 | 超大重复簇数量及最大簇规模 |
| 污染控制 | 训练集与验证集交叉命中率 |
| 基准安全 | 基准精确、近重复和语义命中率 |
| 模型效果 | 去重前后的训练 Loss 曲线 |
| 回归验证 | 下游能力、长尾领域和多语言回归 |
阈值变更要先跑 Dry Run,输出”将删除什么”,再由数据负责人批准。对代码、数学、法律文本和少数语言应设置独立策略,因为相同模板和固定表达不一定意味着低价值重复。
适用场景
这套方案适合:
- 通用大模型预训练语料;
- 行业模型的持续预训练;
- 大规模 SFT 或合成数据构建;
- 代码模型的仓库和 Fork 治理;
- 多语种网页语料;
- 需要可信基准评测的模型研发流程。
对于规模较小、来源高度受控的人工数据集,精确哈希和人工审核可能已经足够,没有必要直接引入昂贵的全量语义去重。
常见误区
误区一:删除重复率越高,数据越干净
去重率是结果指标,不是优化目标。过度去重可能删除合理重复、少数语言样本和重要领域表达。
误区二:一套阈值适用于所有来源
新闻转载、论坛对话、源代码、专利和产品页的相似度分布完全不同。全局阈值通常会同时造成误删和漏删。
误区三:语义相似就应该只保留一份
语义相似不代表数据等价。观点、事实时间、许可证、语言和专业语境仍可能不同。
误区四:只在训练完成后检查污染
事后检查只能解释风险,无法收回已经消耗的训练算力,也无法彻底恢复被污染评测的可信度。
误区五:去重结果可以直接覆盖原始数据
没有原始快照、簇证据和规则版本,误删无法恢复,阈值也无法重放。
上线检查清单
- 原始语料快照不可变,文档 ID 稳定。
- 规范化、分词器、Shingle 和哈希规则均已版本化。
- LSH 候选经过二次相似度校验。
- 重复簇代表文档选择是确定性的。
- 删除清单包含命中证据、来源和规则版本。
- 训练、验证、测试之间已执行双向污染扫描。
- 公开基准包含改写和翻译风险检查。
- 阈值按语言、来源和文档类型抽样评估。
- 监控长尾领域和少数语言保留比例。
- 支持 Dry Run、回滚和历史版本重放。
- 去重后执行小规模训练与能力回归。
- 增量签名索引有完整性校验和周期性全量审计。
参考资料
- Deduplicating Training Data Makes Language Models Better — arXiv:2107.06499
- SemDeDup: Data-efficient learning at web-scale through semantic deduplication — arXiv:2303.09540
- Rethinking Benchmark and Contamination for Language Models with Rephrased Samples — arXiv:2311.04850
- Dolma: an Open Corpus of Three Trillion Tokens for Language Model Pretraining Research — arXiv:2402.00159
- D4: Improving LLM Pretraining via Document De-Duplication and Diversification — arXiv:2308.12284