数据截至 (上游 commit a649de79c14a)
04 · 去重
这一章讲什么: 全库工程含量最高的部分。以 MinHash 四阶段为主线(FineWeb 生产配置),讲清「签名 → 桶内归并 → 并查集聚类 → 过滤」这条流水线如何在几十亿文档上做近似去重;然后看三个变体:句子级精确去重(会改文本)、整文精确去重、Bloom filter。
1. 它要解决的小问题
网页语料里同一篇文章会被转载几百次。训练时见过几百次的东西会被模型背下来,所以要去重。难点分层:
- 近似:转载时有小编辑(改日期、加导航),精确哈希抓不到——要 MinHash 这类相似度指纹;
- 规模:几十亿文档,两两比对是 O(n²),内存也装不下全量指纹;
- 粒度:有时整篇重复,有时只有中间几句重复(C4 的处理是「删掉重复的句子」而非弃文)。
2. 思路:把「全局比对」拆成「落盘的排序 + 归并」
先看全局直觉。MinHash 的经典 trick 是:把文档切成词的 n-gram(shingle),每个 shingle 算哈希,取最小的若干个作为签名——两篇文档签名撞得越多,Jaccard 相似度越高的概率越大。再把 112 个签名值切成 14 桶 × 8 个,任一桶内 8 个值全同就报疑似重复。
DataTrove 的工程贡献是:不发明新的分布式比对框架,而是把问题规约成两件古老的事——排序和归并排序:
- 每个 rank 给本地文档算签名,按签名排序后落盘(二进制定长记录);
- 同一桶内,多个已排序文件做 k 路堆归并——相同签名在归并流里 必然相邻,扫一遍就找到所有重复对;
- 重复对做并查集聚类,每个簇只留代表元,其余写进
.remove清单; - 过滤阶段按清单逐文档丢弃。
全程顺序 I/O,内存占用可控,中间产物(签名、重复对、清单)全是可复算的落盘文件。
3. 归一化与签名:让「小编辑」失效
3.1 文本先归一化
比对前先抹掉无关差异。simplify_text(src/datatrove/utils/text.py:212-257)按 TextNormConfig(:185-193)依次做:小写化 → 数字统一替换成 0(NUMBERS_PATTERN 用 \p{Nd} 匹配任意文字的数字,:202-205)→ 标点换成空格 → 空白压缩 → NFD 拆变音符并去掉。所以「2023 年 1 月」和「2024 年 3 月」转载版会算出同一批 shingle。
3.2 签名的数学
MinhashDedupSignature(src/datatrove/pipeline/dedup/minhash.py:124)。默认配置(MinhashConfig,:40-57):