知识库 / 数据治理
GitHub
← 数据治理

LAYER 02 / DATA ENGINEERING

去重:精确去重、MinHash 与语义去重

所属: 数据治理层。本页边界: 怎样减少重复训练、评测泄漏和模板化数据带来的偏置。

#核心公式

J(A,B)=∣A∩B∣∣A∪B∣ J(A,B)=\frac{|A\cap B|}{|A\cup B|}

#符号说明

符号 含义 单位/条件
A,BA,B 两份文档的 shingle、token 或 n-gram 集合 集合
J(A,B)J(A,B) Jaccard 相似度 [0,1][0,1]
∣A∩B∣\lvert A\cap B\rvert 两集合共有元素数量 个
∣A∪B∣\lvert A\cup B\rvert 两集合去重后的总元素数量 个
hπ(⋅)h_\pi(\cdot) 后文中由随机排列/哈希族 π\pi 定义的 MinHash 哈希值
mm MinHash 签名包含的哈希函数数量 正整数
τ\tau 判定近重复的相似度阈值 [0,1][0,1]
e(x)e(x) 语义编码器对文档 xx 产生的向量 Rd\mathbb R^d
s, r, bs,\ r,\ b Jaccard 相似度、每个 LSH band 的行数、band 数 [0,1][0,1]、正整数、正整数
c(x)c(x) 精确去重前对文档执行的规范化函数 文本映射
C(A,B)C(A,B) 对长短不平衡文档更敏感的包含率 [0,1][0,1]
xi,xjx_i,x_j 待比较的两份原始文档 文档

Jaccard 只比较集合重合;顺序、语义和文档长度不平衡需要额外机制处理。

#技术要点

  • 精确哈希处理完全相同文本,典型实现为 SHA-1 前 N 位哈希比较和 Bloom filter 去重。
  • MinHash/LSH 以 shingle 的 Jaccard 相似度处理近重复;置换函数数量在 128 处出现饱和点,继续增加带来边际收益递减。
  • 语义去重处理改写文本,但更容易误删有效平行表达;SemDeDup 在 LAION 子集上可移除 50% 数据而性能损失最小。
  • 去重阈值应按来源、语言和文档类型分层设置。
  • 片段级去重使用后缀数组精确检测长文档内部的重复子串,适合检测多篇文章共享的长段落。
  • 阶段式流水线将精确去重、近似去重和语义去重串联,每一阶段记录去除比例和受影响来源,而非只看总体去重率。
  • 中文文本的 shingle 构造需考虑分词粒度与停用词过滤,K-shingle 的 K 值应根据文本长度和字符集大小动态选择。

#原理与演进

#三种重复,三种代价

方法 比较对象 能发现 主要误差
精确哈希 规范化后的字节或 token 完全相同文档 标点/模板变动即漏检
MinHash + LSH shingle 集合的近似 Jaccard 局部改写、转载与模板变体 短文档或重排时不稳定
片段级去重(后缀数组) 文档内部的重复子串 多篇文章共享的长段落 计算和存储开销较高
向量语义去重 编码后的语义邻近度 同义改写 可能误删表达不同但都应保留的样本
  • MinHash 的关键性质:随机排列下,两集合最小哈希相同的概率为 J(A,B)J(A,B);LSH 再将候选对缩小到可比较的范围。
  • 将签名分成 bb 个 band、每 band 含 rr 个哈希值时,相似度约为 ss 的一对文档进入候选集的概率为:
P(candidate∣s)=1−(1−sr)b P(\text{candidate}\mid s)=1-(1-s^r)^b
  • 增大 rr 使门槛更严格、减少误报但更易漏掉近重复;增大 bb 提高召回,也增加后续精确比较成本。LSH 只产生候选,不能代替原文复核。
  • 需要区分文档级、段落级和训练样本级重复。整篇不同不代表内部没有大段重合。
  • 去重会改变数据分布:一份常见文本删到只剩一份可能过度降低高频概念;阈值应按文档类型和语言校准。

#为什么还要检查评测泄漏

训练文本与测试题的近重复会夸大分数;即便题干不同,公开答案或解析也可能泄漏。数据侧保存可比对的指纹,评测侧再做污染分析。字节级 n-gram 重叠(如 45-gram)对完整句子匹配有效,但对跨页分割或部分泄漏的场景不如语义嵌入方法稳健。实践通常组合多种信号:精确 n-gram 重叠检测逐字复制,语义嵌入(cosine > 0.90)检测改写,字节对编码指纹抵抗格式变化。

#1. 先定义“重复”的单位

单位 例子 去重过粗或过细的风险
整篇文档 同一文章镜像站转载 小改动逃过去重
段落/句子 共同模板、重复题解 可能误删合法引文
固定 token 片段 多篇文章共享一长段 边界切分改变匹配结果
语义等价 改写后的同一答案 容易把不同观点误判为重复
  • 训练语料去重的目标通常是避免重复曝光和记忆放大;评测去污染的目标是避免训练信息泄漏。两者阈值和误报容忍度不同。
  • “同主题”不等于“重复”:两篇独立解释相同原理的文章仍可提供不同表达。

#2. 精确哈希:便宜但只抓完全一致

对规范化文本 c(x)c(x) 求哈希 h(c(x))h(c(x));相同哈希的候选再比较原文或强哈希,避免把哈希碰撞当成事实相同。

xi≡xj⟺c(xi)=c(xj). x_i\equiv x_j\quad\Longleftrightarrow\quad c(x_i)=c(x_j).
  • cc 决定“什么差异被忽略”:大小写、空白、标点的处理会改变重复簇。
  • 过度规范化会把代码、公式或不同数字抹平;应按文档类型定义 cc。
  • 保留每个簇的代表样本和来源列表,而不是直接丢掉所有副本的血缘。

#2.1 CCNet 的 SHA-1 行级去重

CCNet 是 LLaMA 等模型训练数据管道中广泛使用的去重方法。其处理流程为:将所有文本转为小写并移除非标准 Unicode 字符,然后按换行符切分文档为行级单元,对每个单元计算 SHA-1 哈希值,比较不同单元之间的哈希值来识别重复。CCNet 的粒度为行级而非文档级:即使两篇文档不完全相同,只要包含相同的行级单元,这些单元就会被去重。

CCNet 还引入了桶内重复检测机制:将数据集划分为粗粒度类别桶,在每个桶内统计行级单元的出现次数,出现超过 6 次的单元被标记为重复。这一阈值用于平衡去重效果和信息保留。

#2.2 DOLMA 的 Bloom filter 去重

DOLMA(用于 OLMo 训练)在文档级去重中使用 Bloom filter 而非精确哈希表。其参数设置为:假阳性率 0.1%,10 个哈希函数,总大小约 2.27 GB。DOLMA 同时支持段落级去重,对段落计算 n-gram 的 Bloom filter 索引。

使用 Bloom filter 的优点是内存效率高:不需要存储所有文档的完整哈希值,只需要一个位数组。代价是存在假阳性(将非重复文档误判为重复),但 0.1% 的假阳性率在文档级去重中通常可接受。

#2.3 SimHash:文档指纹与海明距离

SimHash 是另一种广泛使用的去重方法,尤其适合海量文档的近似重复检测。它将文档映射为 64 位或 128 位的指纹,两文档的相似度通过指纹的海明距离衡量。SimHash 的核心性质是:内容相近的文档产生相近的指纹。

SimHash 的工程实现通常结合抽屉原理建立倒排索引:将 64 位指纹分为 4 段,每段 16 位,建立 4 个倒排索引。查询时,如果两文档的海明距离小于 3,则至少有一段指纹完全相同,只需匹配同段相同的文档即可大幅缩小搜索范围。这一方法将 64 位指纹从 500 个 Int 压缩为 1 个 Long,存储降低约 250 倍。

#3. MinHash:用签名近似集合相似度

#3.1 基本流程

  1. 把文档切成重叠 shingle(连续的字、词或 token 片段),得到集合 AA。
  2. 对多个独立哈希函数,分别记录集合中的最小值,构成 MinHash 签名。
  3. 两个签名相同位置的比例近似 Jaccard;LSH 用 band 只挑出有希望的候选对。
  4. 对候选再算较准确的相似度并决定保留规则。

已有公式 J(A,B)J(A,B) 对集合重合敏感。若短文完整嵌入长文,Jaccard 可能仍低;可补充包含率:

C(A,B)=∣A∩B∣min⁡(∣A∣,∣B∣). C(A,B)=\frac{|A\cap B|}{\min(|A|,|B|)}.

#3.2 Shingle 大小的选择

K-shingle 的 K 值决定对局部改写的敏感度。K 过小时,文本的 shingle 集合中会出现过多与其他文本一致的 shingle,相似度被高估;K 过大时,相似度被低估。K 的选择应根据文本长度和字符集大小决定。

对于中文文本,shingle 通常基于分词后的词序列而非字符序列。中文的分词粒度影响 shingle 的语义质量:过细的分词(单字)产生过多噪声,过粗的分词(词组)可能遗漏局部改写。中文去重实践中,常用的策略是先进行停用词过滤,然后对过滤后的词集合提取 K-shingle,K 值通常取 3–5。

#3.3 MinHash 置换函数的饱和点

MinHash 的精度随置换函数数量 mm 的增加而提高,但存在明显的饱和点。系统研究表明,置换函数数量从 32 增加到 128 时,F1 分数持续提升;超过 128 后,性能提升变得边际。对于 LSHBloom,盲目增加置换函数数量反而可能导致性能下降——因为 LSH 会在签名矩阵中引入更多 band,需要更多 Bloom filter,从而增加假阳性率。

实践中,128 是常用的默认值。对于质量优先的场景可以增加到 256,但需要同时调整假阳性率和 band 参数以避免引入额外误判。

#3.4 LSH 参数调优

LSH 的 band 数和每 band 行数决定相似度阈值和召回率。网格搜索研究表明,Jaccard 相似度阈值从 0.2 到 1.0 以 0.2 为步长进行评测时,阈值 0.6 通常产生最佳 F1 分数。更严格的阈值导致 F1 分数下降,因为漏检的近重复对增加。

对于 MinHashLSH 和 LSHBloom,调参的关键权衡是:

  • 阈值参数对 F1 分数的影响最大——它定义了算法对文本重叠的敏感度。
  • 置换函数数量影响 Jaccard 估计的精度,但存在 128 的饱和点。
  • band 数和行数的组合决定 S 曲线的形状,可通过联合调优假阳性率和置换函数数量来优化。

MinHashLSH 类方法相比 n-gram 方法的一个关键优势是:参数调优的计算开销低,且有理论分析可界定假阳性和假阴性率。

#3.5 中文去重的特殊考量

中文文本没有空格分隔词边界,分词质量直接影响 shingle 的可靠性。中文去重的实践方案通常包括:

  • 停用词过滤:移除“的”、“了”、“在”等高频虚词,减少无意义 shingle 的匹配。
  • 动态 K 值:根据文本长度动态调整 K,短文本用较小的 K(如 2–3)保证有足够的 shingle,长文本用较大的 K(如 5–7)减少噪声。
  • 语义去重嵌入模型选择:中文语义去重可选用 gte-base-zh(阿里巴巴达摩院,768 维)、text2vec-base-chinese-paraphrase(针对中文改写检测优化)或 bge-base-zh-v1.5 等模型。bge-base-zh-v1.5 在检索、STS 和聚类任务上表现均衡。

#4. 片段级去重:后缀数组

文档级去重无法检测跨文档的局部重复:两篇文档整体相似度不高,但内部可能有一段数千字的完全相同的文本。后缀数组(Suffix Array)是解决这一问题的经典数据结构。

#4.1 基本原理

后缀数组将字符串的所有后缀按字典序排序,存储排序后的起始位置索引。两篇文档若存在重复子串,对应的后缀在排序后会相邻,通过计算相邻后缀的最长公共前缀(LCP)即可定位重复片段。

#4.2 在大规模文本去重中的应用

将整个语料拼接为一个长字符串,构建后缀数组。遍历后缀数组,比较相邻后缀的 LCP。若 LCP 长度超过预设阈值(如 100 个字符),则标记为重复片段。拼接边界处需要额外处理,避免跨文档的虚假匹配。

后缀数组的优势是精确检测:不依赖 shingle 的近似性质,能发现任意长度的重复子串。代价是构建后缀数组的时间和空间复杂度较高,对于万亿 token 级语料需要分布式实现。倍增算法结合基数排序可将构建复杂度控制在 O(nlog⁡n)O(n \log n)。

#4.3 与 MinHash 的分工

MinHash 适合检测文档级近重复(整体相似但局部有差异),后缀数组适合检测片段级精确重复(多篇文档共享长段落)。实践中两者配合使用:MinHash 先粗筛出候选重复簇,后缀数组在簇内做精细的片段级重复检测。

#5. 语义去重:从 SemDeDup 到工程实现

#5.1 SemDeDup 的核心机制

SemDeDup 是语义去重领域最具代表性的方法。其流程为:使用预训练模型的嵌入向量(通常取最后一层激活或句子嵌入)表示每份文档;在嵌入空间中进行 K-means 聚类;在每个簇内计算成对 cosine 相似度;超过阈值的对视为语义重复,每组保留一个代表样本。

在 LAION 图像-文本子集上,SemDeDup 可移除 50% 的数据而性能损失最小,有效将训练时间减半。在 C4 数据集上,SemDeDup 也优于此前的方法。值得注意的是,移除语义重复后,模型在分布外(OOD)任务上的性能反而提升,说明语义重复不仅是效率问题,也是泛化问题。

#5.2 语义去重的参数设置

阈值选择。 语义去重的判定标准是嵌入空间的 cosine 相似度或欧氏距离。阈值的选择需要平衡检测精度和召回率。实践表明:

  • 0.95 以上:仅检测近逐字重复,非常保守。
  • 0.87–0.94:检测强语义重叠,适合训练数据。
  • 0.85 以下:可能开始误判相关但独立的内容。

SemDeDup 在 LAION 上的实验中使用了 50,000 个聚类和 epsilon 值为 0.07 来过滤近重复图像。

阈值的嵌入空间依赖性。 阈值不能跨嵌入模型直接迁移。以 Jina-v2-small 嵌入空间为例,同一事实的轻度改写产生的 cosine 距离在 0.016–0.048,中度改写为 0.052–0.065,完整释义为 0.13–0.16,而不同但同主题的内容距离 ≥ 0.12。这意味着同一阈值在不同嵌入模型下可能产生截然不同的误删率。NeMo Curator 建议从较低值(如 0.001)开始逐步调高,观察数据减少率和下游表现的平衡。

#5.3 语义去重的工程实现

NVIDIA NeMo Curator 提供了 GPU 加速的语义去重工作流。其流程为:生成文档嵌入 → K-means 聚类 → 簇内计算成对 cosine 相似度 → 识别超过阈值的语义重复 → 每簇保留一个代表。

GPU 加速在这一流程中是必需的:嵌入生成和聚类操作在 CPU 上对百万级文档的耗时不可接受。NeMo Curator 使用 cuDF 做 GPU 加速的 dataframe 操作和 PyTorch GPU 模型。

#5.4 语义去重为何更危险

  • 先用编码器把文档映射成向量,再做近邻搜索与阈值比较。
  • 它能发现字面不相似的改写,但编码器可能把“同主题、不同事实”映到附近。
  • 对专业知识,数字、单位、否定词和时间条件往往决定是否同义;纯语义相似度不能替代事实核对。
  • 与其直接删除,边界样本可保留为簇并抽样检查;对训练和评测分别设阈值。
  • 阈值偏松时的过度合并是主要风险,需要用阈值与统计审计控制。每组保留一个代表样本,而非全部删除。

#6. 去重如何影响优化与评测

情形 不去重的后果 去重过度的后果
同一网页转载多次 来源被重复加权 合法版本差异丢失
训练样本含测试题 评测分数虚高 误删相关但独立的知识
多种表达讲同一事实 可能有适度表达增益 语言多样性下降
稀有语言/代码片段 高频模板放大 本已稀少的有效样本被删除

研究显示训练数据去重可减少记忆与训练测试重合,并可能改善模型质量,但这不是“删得越多越好”。原论文

#7. 阶段式去重流水线

大规模去重的工程实践采用阶段式流水线:每一阶段使用不同的方法处理不同粒度和成本的重复,前一阶段的输出作为后一阶段的输入。

#7.1 三级去重架构

天池万亿语料去重赛的优胜方案展示了典型的三阶段架构:

  • Stage AB(近似去重) :对长文本构造 n-gram 并生成 MinHash 签名,通过 LSH Banding 分桶召回候选相似对。核心是用“签名 + 分桶”将全量两两比较降维为桶内局部比较,并调高漏检惩罚权重优先保证召回。
  • Stage C(精确验证) :在 LSH 粗筛后的桶内进行精确比较。先归一化聚合完全相同文本,然后按长度排序,利用“长度差超限必不相似”剪枝、比较预算控制和 DP 提前终止优化编辑距离计算。
  • Stage E(模板检测) :采样统计跨文档高频句段,经多级归一化构建模板库并提取锚点召回变形片段;全量扫描命中区间后做合并扩边与防过删保护。

#7.2 分阶段优化的原则

  • 先处理确定的完全重复:精确哈希的计算成本最低,应先执行以缩小数据量。
  • 再用近似方法缩小候选:MinHash/LSH 将 O(n2)O(n^2) 的成对比较降维为桶内比较。
  • 最后才对高风险簇做昂贵语义判断:语义去重的嵌入生成和聚类成本最高,应只作用于前两阶段筛选后的候选集。
  • 每一步记录去除比例、受影响来源和语言:只看“去重率”无法判断是否删对。

数据血缘与版本见数据版本;基准污染解释见评测。

#原始资料

本页由仓库中的 Markdown 生成。具体技术结论请结合正文引用与实验条件理解。

输入关键词,探索整个知识库

↑ ↓ 选择 ↵ 打开36 篇笔记,一次搜索