爬一遍 Common Crawl 会得到大量"同文多份":同一个论坛帖子被多个镜像站收录、同一篇新闻被转载站改写标题重发、电商模板页只换商品名。GPT-3 论文里有个常被引用的数字:Common Crawl 原始 45TB 经模糊去重加质量过滤后只剩 570GB,缩水到 1.3% 左右。不去重会怎样?模型对高重复内容过拟合,重复 token 挤占有效数据预算,loss 曲线出现诡异的台阶。这篇按三个层次拆去重的做法和账本。
Exact 去重:一行哈希的事
最朴素的层次:把文档(或段落)规范化(转小写、折叠空白、去 HTML 标签)后算 MD5/SHA-1,整个语料哈希表扫一遍,重复的整行丢掉。单机上 Spark/ray 流式跑,几小时能过一遍万亿 token 级别的语料,成本几乎可忽略。
它能抓的是"逐字节相同"的重复——镜像站、备份库、爬虫重抓。Common Crawl 不同 snapshot 之间的重复大头在这一层就能去掉。但网页重复的真相是近似重复(near-duplicate):转载站改了标题和页脚,正文一模一样;SEO 站点对同一篇文章做同义词替换。逐字节哈希对这类完全失明,必须往下一层走。
Exact 层还有个小技巧值得做:按"规范化行"哈希而不是按整篇文档。文档级别的 exact 去重漏掉一种常见情况——A 文档是 B 文档的子串(比如论坛回复引用了原帖全文)。子串层面的检测属于下一层的工具。
Near 去重:MinHash 把集合相似度降到常数内存
核心问题是:两篇文档的相似度怎么高效算。设文档 $A$、$B$ 的词(或字符 n-gram)集合,Jaccard 相似度
$$ J(A, B) = \frac{|A \cap B|}{|A \cup B|} $$
是 near-dedup 的标准度量(它只看"重叠占比",天然对长度差异不敏感)。但语料有几十亿篇,两两算 $J$ 是 $O(N^2)$,根本不可行。MinHash 的贡献是:用一个哈希函数族把每篇文档压成一个固定长度的签名(signature),使得任意两篇文档签名的前 $k$ 个分量相等的概率恰好约等于 $J(A,B)^k$——相似度被保存在了签名的逐位相等率里。
构造方式:从全域哈希函数族里随机取 $n$ 个函数 $h_1, \dots, h_n$,文档 $A$ 的签名为
$$ \mathrm{sig}(A) = \big(\min_{x \in A} h_1(x), \; \min_{x \in A} h_2(x), \; \dots, \; \min_{x \in A} h_n(x)\big). $$
关键恒等式:对随机哈希 $h$,$\Pr\big[\min_{x\in A} h(x) = \min_{x\in B} h(x)\big] = J(A, B)$。直觉是:两个集合里最小的哈希值"同源"当且仅当这个最小值落在交集里。于是 $n$ 个分量的相等比例就是 $J$ 的无偏估计。
签名压出来了,还得解决"哪两篇该比"的问题——总不能拿每篇跟全体比。这一步用 LSH(局部敏感哈希):把 $n$ 个签名分量切成 $b$ 个 band,每个 band 内 $r = n/b$ 个分量整体再哈希一次进桶。两篇文档只要有一个 band 撞桶就进入候选对,再精确验算 $J$。撞桶概率是 $1 - (1 - J^r)^b$,参数 $b, r$ 决定接受门槛:比如 $b{=}20, r{=}5$ 时 $J \ge 0.8$ 的文档对约 99% 能被抓到,而 $J \le 0.3$ 的对几乎全被过滤。这是经典的"用假阳性换扫描量"的权衡。
工程上有几个容易踩的坑。n-gram 的选择:词级 5-gram 对改同义词敏感(替换一个词就打断多个 shingle),字符级 shingle 更鲁棒但签名略长;实践中词级 shingle + 小写规范化是主流。阈值别定太狠:$J \ge 0.9$ 是 GPT-3 用的口径,太低的门槛会误杀"同一事件的不同报道"这类对多样性有贡献的内容——去重的目标是把重复压缩到一遍,不是把相似话题删到只剩一篇。就近保留哪一篇也有讲究:保留域名权重高、页面结构干净的那份,而不是随机留。
Semantic 去重:嵌入空间里的最后一公里
Near-dedup 对"深度改写"(换句式、翻译、AI paraphrase)仍然失明。第三层的思路是:用预训练嵌入模型(如 sentence-transformer 系)把文档映成向量,余弦相似度超过阈值(经验值 0.9+)的视为语义重复,每个连通簇里保留质量最高的一篇。Facebook 的 SemDeDup(Abbas et al., 2023)给出了这个方向最完整的实验:在 CC 上聚类去重后,用省下的算力补新数据,等算力下 loss 反而更低——语义去重抓出来的冗余确实是"有效信息为零"的那种。
但这一层的成本是最难顶的:十亿级文档的全对相似度,k-means 聚类本身要 GPU 天级,阈值边缘的样本还要二次人工复核。更要命的是误伤的形状和前两层次不同:语义聚类会把"对同一事件的多角度报道"(恰恰是有信息量的多样性)和"同义改写"(零信息量)混在一起压掉。论文里用高保留比例(每簇留多个代表)缓解,但这就削弱了去重率。
收益到底有多大
去重对模型质量的影响有两条路径。一是防记忆化:Lee et al. 2021(Deduplicating Training Data Makes Language Models Better)证明,去重能显著降低模型逐字复述训练语料的概率,这对版权合规是直接相关的。二是有效数据量的账:一个 30% 重复的语料,模型在重复样本上的梯度是浪费的——接近收敛时重复 token 的梯度趋零(它们不再提供新的信息),但训练早期和中期并非如此,重复样本实质占走了部分"学习预算"。Chinchilla 的框架里,这近似等效于把 $D$ 打了七折——对一个 20 token/参数的模型,相当于欠训了三成。
不过也要避免去重洁癖。三条经验口径:第一,②层的 MinHash-LSH 是所有严肃预训练的标准动作,不做没有借口;第二,③层语义去重优先用在高质量小语料上(教科书、论文、代码仓库),全量网页跑它的钱不如拿去多爬新数据;第三,去重阈值要和质量过滤联动——很多"高相似度"的文档本来就是模板垃圾,质量过滤层已经把它们删了,dedup 是在质量过滤之后跑还是之前跑,顺序影响两遍的总开销,实践中质量过滤先行的总成本更低。
小结
三层去重是精度递增、成本数量级递增的金字塔:exact 哈希抓逐字节重复,成本数小时、该无条件做;near-dedup 用 MinHash 签名把 Jaccard 相似度压成签名逐位相等率、LSH 撞桶圈候选对,是万亿 token 级语料的标准配置,门槛 $J \approx 0.8$–$0.9$;semantic 去重靠嵌入聚类抓改写和翻译重复,只对高质量子集划算。去重的收益一半是模型质量(有效数据量不打折),一半是合规(降低逐字记忆化的概率),两层收益都真实,但第三层的边际收益已经抵不上它的算力和误伤成本。