我最早参与一个NLU评测时,信心满满交上去的模型在某个公开测试集上拿了接近满分的成绩。团队一片欢呼,直到有人手贱查了一下训练数据。结果发现,测试集里20%的样本原封不动地在训练语料里出现过。不是模型强,是它把答案背下来了。

这件事让我后怕了很久。因为后来我发现,几乎所有大规模预训练语料都存在这样的“污染”,这不是个例,而是整个领域早期普遍忽视的漏洞。今天我们聊的就是这件事:训练数据去重和去污染,为什么它比你想的更重要,以及做起来到底有多难。
偷看答案的模型:什么是数据污染?
说得直白一点:如果你的训练集里包含了测试集的问题或答案,模型在测试时就不是在“理解”,而是在“回忆”。这就像高考前你拿到了真题,考场上不管会不会做,分数都失真了。在NLP里,这种现象被称为基准污染(benchmark contamination)或数据泄漏(data leakage)。
最经典的案例来自GPT-3的时代。OpenAI在技术报告里坦承,Common Crawl 语料中确实存在与测试集重叠的文本,他们对所有训练数据做了“去污染”处理,但即便如此,某些数据集(如LAMBADA)的污染程度仍然可能达到百分之几。GPT-3论文里专门有一段描述他们如何检测并移除与测试集有13-gram重叠的文档。为什么是13-gram?因为再短就容易误删正常内容,再长又漏掉很多。
但GPT-3的报告是在2020年,当时很多团队还没有意识到这个问题的严重性。直到2022年,BigScience项目在构建BLOOM模型时,用了一整套更严格的流程,才把数据污染检测推到了一个新高度。他们甚至发现,有些公开数据集本身就是用测试集来源的文本构建的,比如某些数据集爬取了维基百科,而维基百科的页面恰好也是很多测试集的来源。BLOOM的ROOTS语料库论文详细描述了这种“间接污染”,以及他们如何用MinHash+LSH来搜索近重复文档。
去污染和去重,真的是两回事
很多人会把这两个概念混着用,甚至觉得“去重了不就顺便去污染了吗?”这正是我当年踩过的坑。实际上,去重(deduplication)解决的是训练集内部冗余的问题,去污染(decontamination)解决的是训练集与测试集重叠的问题。两者目标不同,方法不同,难度也不同。
| 对比维度 | 去重 | 去污染 |
|---|---|---|
| 目标 | 减少训练集内部重复文本,提升训练效率,防止模型对重复模式的过拟合 | 移除训练集中与测试集相似的样本,避免基准评测失真 |
| 检测对象 | 训练集自身 | 训练集 vs 测试集 |
| 典型方法 | 精确匹配、MinHash、SimHash、后缀数组 | n-gram重叠、MinHash、模糊匹配、人工审查 |
| 最怕的事 | 长文档部分重复(如复制粘贴的模板) | 测试集的一个子串出现在训练文档中,甚至只是语义高度相似 |
| 常见工具 | Onion、Deduplicator、CCNet | 无通用工具,多靠脚本定制 |
我最早以为去重做好了,测试集自然就安全了。但后来发现,去重通常只处理训练集内部,它根本不会去对比测试集。比如你的训练语料里有100份一模一样的维基百科“量子力学”页面,去重会只保留一份,但如果你测试集里有一道题恰好问了“量子力学”,那这份保留的文本依然会污染评测。去重只管“自己跟自己重复”,不管“自己跟别人重复”。
反过来,去污染也不等于去重。去污染的目的是清除所有与测试集相关的痕迹,哪怕这段文本在训练集里只出现一次,只要它和测试集高度相似,就必须删掉。这比去重更精细,也更耗时。
去污染到底怎么做?为什么说它是“脏活累活”
如果你要训练一个真正不会被“作弊”指责的模型,去污染是绕不开的。但这件事的难点在于:你不可能在训练前就知道所有测试集,测试集本身是动态的、不断被提出的。所以工业界通常的做法是:对已知的公开测试集进行全面检测,同时对未知的测试集采用一些启发式方法。
具体流程,我参与过的一次实践大致是这样的:
- 收集所有你要保护的测试集,包括它们的训练集划分(比如某些数据集有公开的train/dev/test split,但你的训练语料可能混入了其中的train部分,这也算污染,因为模型可能通过训练集部分间接学习到测试集分布)。
- 对每个测试样本,提取其n-gram(通常选8-13)并构建一个布隆过滤器或者倒排索引。
- 遍历训练语料的所有文档,检查是否存在高重叠的n-gram片段。如果某篇文档与测试集样本的n-gram重叠度超过阈值(比如13-gram重叠超过80%),就标记为污染文档并移除。
- 之后还要人工抽查,因为有些重叠是巧合(比如通用的法律条文、歌词),但大多数情况下,高重叠意味着直接复制。
但这里有一个很坑的地方:n-gram重叠检测对短文本测试集几乎无效。比如一个常识推理数据集,测试样本就是一句话“如果我把鸡蛋放进冰箱,它会在______。”这句话如果出现在训练语料里(比如育儿博客),那就会污染。但一句话的13-gram根本没有,因为13-gram要求连续13个词完全相同,这句话太短了。所以后来BigScience在BLOOM里用了更高级的MinHash方法,它把文档切分成多个片段,计算签名相似度,能捕捉到不精确的重复。BigScience的去污染论文里提到,他们最终的方案是先用MinHash粗筛,再用n-gram精确匹配,最后人工检查,这才把污染率降到了0.1%以下。
还有一个隐蔽的污染源:测试集本身被作为训练数据的一部分在网上流传。比如很多开发者会把某个基准测试集上传到GitHub,或者写成博客,然后被Common Crawl抓取。你训练模型时,这些页面就混进去了。这已经不是“重叠”,而是“直接曝光”。这也是为什么现在很多模型团队在发布技术报告时,会贴出详细的污染检查结果,甚至公开被删除的文档列表,以自证清白。
去重:不只是为了防作弊,更是为了省钱
相比之下,去重更偏向工程优化,但它对模型性能的影响其实比很多人想象得大。2022年有一篇被广泛引用的论文《Deduplicating Training Data Makes Language Models Better》,作者训练了两个1.5B的模型,一个用去重前的C4语料,一个用去重后的。结果发现,去重后的模型在困惑度上几乎追平,但训练时间节省了5%,而且生成的文本里直接复制训练数据的比例从1.2%降到了0.3%。这篇论文直接开启了“去重必要性”的大讨论。
去重的技术栈相对成熟。最常见的是MinHash + LSH(局部敏感哈希),它能把文档表示成一组签名,然后通过哈希桶快速找到相似对。Google的C4数据集就是用的这个方案,发现Common Crawl里有超过30%的文档是重复的。T5论文里详细描述了C4的清洗流程,包括去重步骤。
但去重也有副作用,尤其是在多语言场景下。比如有些语言(如中文)的互联网文本中,法律条款、古诗、名言会被大量引用,这些引用如果被去重掉,模型会失去对经典文本的认知。BigScience在BLOOM语料库中就遇到了这个问题:他们发现如果对中文语料做和英文同样严格度的去重,会导致一些成语和诗句的分布被严重扭曲。所以最后他们针对不同语言设定了不同的去重阈值,这又是一个需要大量人工判断的脏活。
几个你一定想知道的误解
去重会让模型变笨吗?
很多人担心去掉重复数据会减少训练样本量,影响模型能力。但实际实验表明,去掉重复数据后,模型性能持平甚至略有提升。因为重复数据不仅浪费算力,还会让模型对某些特定表述产生过强的记忆,导致生成多样性下降。除非你的训练数据本来就极度稀缺,否则去重绝对是利大于弊。
训练集里混入测试集,是不是说明模型全是“背答案”?
不完全。污染确实会让基准分数虚高,但一个经过严格去污染的模型,在一些任务上依然能拿到很高的分数。比如GPT-4在很多新提出的测试集上表现依然很强,这说明模型确实学到了泛化能力,而不仅仅是记忆。污染只是让评测变得不可信,并不代表模型一无是处。
那我怎么知道一个模型有没有被污染?
主要看模型团队是否公开了污染检测报告。如果他们没有,你可以在自己的测试集上做一些简单的n-gram重叠检测。但更狠的一招是设计只能在一段时间后才出现的动态测试集,比如基于最新新闻的事件推理问题。如果模型也能答对,那就很可疑了——除非它真的读到了事后才发布的训练数据。这也是目前很多LiveBench评测的方向。
我自己的教训:数据清洗不是一次性的
经历过那次“作弊”事件后,我在带团队做预训练时,会强制要求数据清洗流程里包含三个独立的区块:去重、去污染、去隐私。去重最早做,因为最早做效率最高;去污染必须在测试集确定后,独立跑一遍完整的检测脚本,而且每次更新训练语料都要重新跑。最后是去隐私,移除姓名、电话等敏感信息。这三步缺一不可,而且顺序很重要——如果你先做了去污染,再去重,可能会把一些被污染文档的重复版本留下来,污染依然存在。
现在回头看,数据去重和去污染是整个预训练流水线里最无聊、最耗时、但最关键的一步。它不会让你的模型更聪明,但会让你的模型分数更可信。而一个可信的分数,远比一个虚高的数字有价值。
原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/65.html