期末倒计时三周,命题组王老师在密封室里,把刚印好的数学卷和全校练习册做最后比对。不看不知道,最后一道大题竟和月考模拟卷撞了八成。学生要是提前做过这道题,考试分数还能反映真实水平吗?大模型评测也一样:训练语料就是学生手里的练习册,评测集就是期末考试卷。如果测试题出现在训练数据里,模型等于“开卷考试”,分数虚高却不代表能力。 评测数据去重,就是给大模型评测做考前安检:先建练习册库、清洗文本、用类似“试卷指纹”的 MinHash 算法找出重复题,再人工复核,最后把泄漏样本从考卷中抽走。这篇文章会讲清污染为什么危险、去重流水线怎么做、MinHash 和 LSH 如何让海量比对变快,以及阈值怎么定才不过度。掌握了这些,你就能给团队交出一份可信的评测成绩单。
对 AI 从业者来说,掌握数据去重意味着你能在模型评测、榜单发布和产品选型时,用“干净的数据”换取“可信的结论”,这是评测工程师的核心竞争力。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
污染问题本质
期末命题组的王老师最怕一件事:刚印好的考卷,和某个班月考练习册撞题。学生如果提前做过原题,考试分数只能说明记忆力好,说明不了数学能力。大模型评测里的数据污染,本质上就是这种“撞题”——评测集里的样本,出现在了模型的训练语料里。
预训练语料从互联网上抓取,可能包含公开 benchmark、竞赛题库、问答社区的帖子。于是模型在做评测题时,不是在推理,而是在回忆见过的答案。评测题再难,也测不出真实能力。泄漏路径不止预训练语料一条:指令微调数据、人类反馈数据、甚至少样本提示词里的示例,都可能把测试样本带进去,污染比例越高,榜单越像“开卷考试成绩单”。
为了理解污染带来的偏差,可以把它看成真实分数上的一个“虚高增量”:
对认真做评测的团队而言,污染是致命的不公平;对模型选型来说,则可能让团队选错供应商。查重,就是命题组在开考前把撞题抽出来,还分数一个清白。
- 观测分数\(S_{\text{obs}}\) 是模型在评测集上跑出来的成绩,也是榜单报告的数字。
- 真实能力\(S_{\text{true}}\) 是模型真正泛化能力对应的分数,去污的目标就是让观测分数逼近它。
- 泄漏放大系数\(\lambda\) 表示每单位重复率带来的虚高幅度,数据越重复,放大越明显。
- 整体关系重复样本越多,污染剂量越大,观测分数与真实能力偏离越远。
去重标准流程
王老师查漏题从不凭感觉,而是有一套固定动作:先收齐所有练习册,再誊清试卷,最后逐题比对。大模型评测的去重流程,也是同样四条线。
第一条是构建语料库。把预训练、指令微调、人类反馈等数据汇总成一个可比对的“练习册仓库”。第二条是文本归一化,去掉大小写、标点、空格、全半角差异,让同一道题的不同写法变成同一种标准形态。第三条是相似度比对,用哈希、MinHash 或向量检索计算评测题与语料的重复程度。第四条是人工复核,处理规则无法判定的边界样本,形成可审计的去重报告。
归一化直接决定后续算法效果。比如“CIFAR-10 是什么?”和“cifar10是什么”直接比较会误判,归一化后就能变成同一种文本指纹。流程中间每个环节都应记录重复样本清单,方便回溯。去重是否有效,可以先看污染占比:
就像命题组把撞题清单拿给年级主任签字一样,评测团队也应当把污染占比写进数据集说明,让每一个看榜的人都知道这份成绩单是否干净。
- 污染占比\(p_{\text{dup}}\) 表示评测集中与训练语料重复的样本比例。
- 重复样本数\(D_{\text{dup}}\) 是去重流程识别出的重复样本数量。
- 评测集总量\(N_{\text{eval}}\) 是评测集样本总数,用于标准化。
- 转换成百分比乘以 \(100\%\) 方便与业务方同步风险水位。
精确匹配去重
如果把练习册和试卷逐字对照,同一道题一字不差,老师一眼就能认出来。计算机也有更快的办法:精确匹配去重。
精确去重流程很简单:用 SHA-256 等哈希函数给归一化后的文本生成一串短指纹,指纹相同就判定重复。它实现快、无参数、误报率极低,特别适合判断题、固定选项题、代码题这类不允许随意改写的场景。公式可以写成:
但学生不会傻到一字不差地抄,他们会改标点、换顺序、加语气词。哈希对任何一个字符都极其敏感,加一个空格指纹就完全不同。所以精确匹配只是查重的第一步,真正能抓“换皮题”的高招,是模糊指纹——MinHash。
- 归一化文本\(\text{norm}(q)\) 是评测题经过去标点、统一大小写后的标准形态。
- 哈希指纹\(H(\cdot)\) 把任意长度的文本映射成一段固定长度的短指纹,比如 SHA-256。
- 语料片段\(\text{norm}(c)\) 是训练语料中某个候选片段的标准形态。
- 指纹相等则重复左右指纹完全一致,说明两段文本逐字符相同,因此判定重复。
MinHash原理
要抓改写型重复,命题组换了个思路:不给全文拍照,而是把每道题拆成一堆“字词小卡片”,再从卡片里挑出固定数量的记号,做成一张“指纹卡”。这样两篇看起来不一样的文字,只要内核相近,指纹也会相近。
先看相似度怎么定义。把一段文本切成长度为 \(k\) 的连续字符片段,这些片段叫做 shingle,组成一个集合。两段文本的相似度可以用 Jaccard 来衡量:
直接算集合交并在亿级语料上不可行。MinHash 的精妙之处是:用 \(k\) 个随机哈希函数,对每个集合元素取哈希,并保留每个函数下最小的那个值,形成一条长度为 \(k\) 的签名。两段文本签名中相同位置的相等比例,就是 Jaccard 的无偏估计:
MinHash 不追求逐字精确,而是用概率保证在大量文本中快速筛出可疑对象。它如同给每道题发了一张稳定的“指纹卡”,改写再多,只要内核重复,指纹就会露出马脚。
- 哈希函数族\(h_1,\dots,h_k\) 是 \(k\) 个独立的随机哈希函数。
- 集合最小值\(h_i(A)\) 表示对集合 \(A\) 中所有元素取哈希后,第 \(i\) 个函数下最小的哈希值,也就是 \(A\) 的第 \(i\) 位签名。
- 指示函数\(\mathbf{1}[\cdot]\) 表示括号内条件成立时取 1,否则取 0。
- 取平均所有位上“相等”的比例,就是对真实 Jaccard 的近似估计。
LSH分桶加速
海量练习册不能两两互相比指纹卡,否则一个数学组加班一个月也查不完。王老师的办法是:把指纹卡分成几十叠,每叠只比“同一叠”里的卡。这就是 Local Sensitive Hashing,局部敏感哈希,LSH 分桶加速。
具体做法是:把长度为 \(k\) 的 MinHash 签名分成 \(b\) 个 band,每个 band 有 \(r\) 行。两段文本只要在任意一个 band 内完全相同,就被放进同一个候选桶,只需要对候选桶内的样本做进一步确认。这样就不需要所有样本两两比对。两个相似度为 \(s\) 的文本成为候选对的概率为:
LSH 把计算复杂度从 \(O(N^2)\) 降到接近 \(O(N \cdot b)\)。命题组老师不用拿一万张卡两两比对,只翻同一叠里有标记的卡,几小时就能筛完一整间屋子的练习册。
- 单行一致概率\(s\) 是单个哈希行相等的概率,期望等于 Jaccard 相似度。
- 一个 band 内全部一致一个 band 有 \(r\) 行,全部一致的概率是 \(s^r\)。
- 一个 band 不命中的概率\(1 - s^r\) 表示这个 band 内至少有一行不一致。
- 至少一个 band 命中所有 band 都不命中的概率是 \((1 - s^r)^b\),用 1 减去它,就是至少一个 band 完整命中、成为候选对的概率。
去重阈值把控
阈值就像监考尺度。太严,后排学生翻个橡皮擦都要上报;太松,真抄答案的也漏过去。MinHash 去重同样需要一个“像多少才算重复”的边界。
阈值设得低,算法能抓到更多改写型重复,但也会把表述相近的正常测试样本误删,导致评测集失去多样性和难度;阈值设得高,误删少了,却会漏掉大量换词、调序的泄漏样本。实际工作中,通常先抽样人工标注一批“真重复”和“真不重复”,再通过 F1 分数寻找最佳阈值:
实践中,阈值常从 0.7 到 0.85 起步,再根据文本长度调整:长文本可略微调低,短文本应调高。最优阈值不是数学常数,而是业务权衡的结果——你要防的是漏题,还是保护试题多样性。
- 查准率\(P\) 表示算法标成重复的样本里,真正重复的比例。
- 查全率\(R\) 表示所有真正重复的样本中,被算法找出来的比例。
- 调和平均\(F1\) 在 \(P\) 和 \(R\) 之间取平衡,不会让某一项特别低。
- 阈值调参在抽样集上尝试不同阈值,选择 F1 最高的那个点。
工具实战验证
查重不能停留在理论上,最终要落到工具和流程里。开源生态里,datasketch 提供了成熟的 MinHash 与 MinHashLSH 实现;text-dedup 这类项目提供了面向大规模语料的清洗管线。也可以自己实现 n-gram 加 MinHash 作为基线,再在候选集上做精确确认。
评测集发布前,要对全量测试样本与训练语料做一遍完整去重;发布后,模型更新或训练语料新增时,也要重新扫描。效果指标不能只看“删了多少”,还要看“漏了多少”。可以用人工抽检估计残留泄漏率:
考试不是一次性的,练习册每学期都换,查漏题也要形成制度。评测集只有持续净化,分数才能一直可信。
- 残留重复数\(D_{\text{remaining}}\) 是人工抽检或二次扫描后发现的剩余重复样本数。
- 评测集总量\(N_{\text{eval}}\) 仍然作为标准化分母。
- 泄漏残留率\(R_{\text{leak}}\) 反映去重后评测集的“剩余污染水位”。
- 监控闭环当训练语料更新后再次计算该指标,就能持续判断评测集是否需要清洗。
AI 应用场景
Benchmark 发布前的自动净化和人工复核
阶段:数据准备 / 评测集构建
解决的问题:传统做法依赖人工目测或简单字符串查重,遇到题目被改写就完全失效。用 MinHash + LSH 对评测题和全量训练语料自动分桶,生成候选重复对,再由人工复核,既能覆盖“换皮题”,又不会把语义相近的正常题误删。
具体案例:某团队构建中文知识问答基准,从网上收集 2 万道题;用 datasketch 对 80GB 语料做 MinHashLSH 扫描,过滤掉约 12% 疑似重复样本,最终榜单结果和内部盲测高度吻合。
大模型选型中的可信对比
阶段:模型评估 / 结果分析
解决的问题:甲方用公开 benchmark 选择模型,但候选模型可能已经在这些公开数据集上训练过,分数差异不能反映业务场景的真实能力。先用去重流程清洗出一份私有评测集,再在同样条件下对比模型,才能避免“背题高手”拿到虚假高分。
具体案例:某银行采购 LLM 时,将 500 道业务问答与各家模型的训练语料做去重,剔除重复度高的样例后重新评测,最终选型结果与试点业务表现一致。
评测集持续防污染监控
阶段:线上监控 / 数据治理
解决的问题:模型持续迭代,训练语料可能在后续版本中把评测集“吞”进去。定期用哈希匹配和 MinHash 扫描新语料,监控残留泄漏率,才能发现污染风险并及时更新评测集。
具体案例:某 AI 评测平台每月对新训练语料运行去重流水线,泄漏率超过 0.5% 就触发告警,并重新评估已发布榜单的有效性。
Python 演示
"""Demonstrate eval-set dedup with MinHash similarity estimation."""import hashlibimport re class EvalDeduper: """A small MinHash-based deduplicator for benchmark texts.""" def __init__(self, num_hashes=64, shingle_size=3, threshold=0.7): self.num_hashes = num_hashes self.shingle_size = shingle_size self.threshold = threshold @staticmethod def _normalize(text): text = text.lower() text = re.sub(r"[^\w]+", "", text) return text.strip() def _shingles(self, text): text = self._normalize(text) if len(text) <= self.shingle_size: return {text} if text else set() return {text[i:i + self.shingle_size] for i in range(len(text) - self.shingle_size + 1)} @staticmethod def _hash_shingle(shingle, seed): seed_bytes = str(seed).encode("utf-8") shingle_bytes = shingle.encode("utf-8") digest = hashlib.sha256(seed_bytes + shingle_bytes).digest()[:4] return int.from_bytes(digest, "big") def _minhash(self, text): shingles = self._shingles(text) if not shingles: return [0] * self.num_hashes signature = [] for seed in range(self.num_hashes): values = [self._hash_shingle(s, seed) for s in shingles] signature.append(min(values)) return signature def similarity(self, text_a, text_b): sig_a = self._minhash(text_a) sig_b = self._minhash(text_b) equal = sum(1 for a, b in zip(sig_a, sig_b) if a == b) return equal / self.num_hashes def is_duplicate(self, text_a, text_b): return self.similarity(text_a, text_b) >= self.threshold if __name__ == "__main__": deduper = EvalDeduper(num_hashes=64, threshold=0.7) q1 = "Explain the attention mechanism." q2 = "Explain the attention mechanism." q3 = "What is attention mechanism? Describe briefly." print(deduper.similarity(q1, q2)) print(deduper.is_duplicate(q1, q3))
常见误区
误区评测集和训练集里一模一样的内容删掉,分数就干净了。可实际上,模型见过的题目常常不是原题,而是被改写过的版本。
正解大模型训练语料来自互联网,评测题往往以不同措辞、翻译、拼接形式出现。精确匹配只能发现完全相同或仅格式差异的样本,MinHash 等模糊去重才能捕捉改写型泄漏。就像查抄袭不能只查一字不差的段落,还得查换词调序后的“换皮文”。
为何常见:精确匹配实现简单、容易理解,对完全重复有效,容易让人误以为已经足够。
误区MinHash 不是精确计算吗?签名相同比例应该就是两个集合的真实交集比例。
正解MinHash 是估计器,不是精确计算。它通过多次随机哈希取最小值的技巧,让签名相同比例在期望上等于 Jaccard,但单次结果有方差。签名长度 \(k\) 越大,估计越稳定。真正精确求交集在大规模语料上代价太高,所以工业界普遍接受这种概率近似。
为何常见:从直觉上看,“签名相同的比例”很像精确统计,容易忽略随机哈希本身的抽样本质。
误区把相似文本全部删掉,评测集绝对干净,分数就绝对可信。
正解阈值过低会把大量正常的测试样本误判为泄漏,使评测集失去多样性和难度,分数反而不能代表真实能力。正确做法是同时关注查准率和查全率,比如用 F1 寻找平衡点,再辅以人工复核。
为何常见:对“污染”过度警惕时,容易把去重当成可以无限加码的安全措施,忽略了误删成本。
误区评测集发布前做过一次去重,以后模型随便怎么更新,分数都一样可信。
正解预训练语料、指令微调数据都在动态变化,模型厂商也可能针对公开榜单调高训练数据覆盖率。需要定期对新语料做哈希与 MinHash 扫描,监控残留泄漏率,发现污染回升就及时清洗或下架旧评测集。
为何常见:人们习惯把评测集看作静态资产,忽略了数据和模型都在持续演进。
面试题
Q1. 评测集与训练数据重复时,最主要的风险是什么?
- A.模型训练速度显著下降
- B.评测分数出现虚高← 正确答案
- C.模型参数量成倍增长
- D.显存占用大幅升高
答案:B。重复样本等于让学生提前看到考题。模型表现好是因为记住了答案,而不是学会了推理。选项 A/C/D 属于训练成本相关,与泄漏导致的分数失真无关。
Q2. 面对海量文本,哪种方案最适合快速发现近似重复的评测样本?
- A.两两全文暴力比对
- B.人工逐题阅读判断
- C.MinHash 与 LSH 分桶← 正确答案
- D.单条串行逐一扫描
答案:C。MinHash 把文本压缩成签名,LSH 把签名分桶,只比较候选桶内的样本,大幅降低比对复杂度。暴力比对和人工逐题阅读在亿级语料上都不现实。
Q3. 两个集合 A 和 B 的 Jaccard 相似度定义是什么?
- A.交集元素数除以并集元素数← 正确答案
- B.并集元素数除以交集元素数
- C.交集元素数除以 A 集元素数
- D.差集元素数除以并集元素数
答案:A。Jaccard 相似度衡量两个集合的共同占比,公式是 \(|A \cap B| / |A \cup B|\)。其余选项分别对应错误的方向、集合选择和差集逻辑。
Q4. 关于 MinHash 签名,下列说法正确的是?
- A.可以近似估计集合的 Jaccard 相似度← 正确答案
- B.能够精确求出两集合的全部交集
- C.能把高维集合压缩成固定长度签名← 正确答案
- D.可以完全替代评测集的人工复核
答案:AC。MinHash 通过固定数量的哈希最小值压缩集合,并用签名相同比例估计 Jaccard,但它不直接给出精确交集,也不能替代需要业务判断的人工复核。
Q5. 在 LSH 中,两个相似度为 s 的文本成为候选对的判断依据是什么?
- A.至少一个 band 内所有 r 行签名全部一致← 正确答案
- B.所有 band 中每个 band 内所有行都一致
- C.至少一个 band 内所有 r 行签名全部不一致
- D.单个 band 内部分行一致且部分不一致
答案:A。LSH 把签名分成 b 个 band,每个 band 有 r 行。只要有一个 band 内所有行都相等,就认为它们可能相似并进入候选集。对应的候选概率公式是 \(1 - (1 - s^r)^b\)。
Q6. 去重阈值设得过高,最容易导致什么后果?
- A.遗漏改写后的重复样本← 正确答案
- B.误删大量正常评测样本
- C.训练数据规模急剧膨胀
- D.模型推理速度明显下降
答案:A。阈值过高意味着只有非常相似的文本才会被判为重复,换词、调序后的近似重复会被漏掉。误删大量正常样本是阈值过低的风险,C 和 D 与去重无直接关系。
Q7. 下列哪些环节可能造成评测数据泄漏?
- A.预训练语料混入评测集← 正确答案
- B.网络爬虫抓取评测网站← 正确答案
- C.少样本提示词引用评测题← 正确答案
- D.模型量化降低推理精度
答案:ABC。预训练语料、网页爬取、提示词示例都可能把测试样本带到模型面前。模型量化属于推理优化,不会把评测集写进训练数据,因此不属于泄漏路径。
Q8. 精确匹配去重前做文本归一化的主要目的是?
- A.消除格式差异带来的误判← 正确答案
- B.将文本转换为高维向量
- C.增大评测集的数据规模
- D.降低模型训练的算力需求
答案:A。归一化统一大小写、空格、标点和全半角差异,让“实际上一样”的文本变成同一种标准形态。高维向量是向量检索的任务,规模和算力与归一化无直接关系。
Q9. 为什么评测集去重流程中不能完全去掉人工复核?
- A.需要判断语义改写样本的边界← 正确答案
- B.需要审计自动规则造成的误判← 正确答案
- C.因为 MinHash 必须人来计算
- D.为了保证公开榜单的最终质量← 正确答案
答案:ABD。自动算法擅长生成候选,但语义改写、专业术语、中英混写等边界情况需要人来拍板。MinHash 由计算机计算,不是人工计算,所以 C 错误。
Q10. 对同一评测集去重后,模型分数明显下降。最合理的解读是?
- A.原分数包含污染造成的虚高← 正确答案
- B.去重算法一定存在缺陷
- C.评测集已经完全失去效用
- D.训练数据质量变得更差了
答案:A。去重后分数下降,通常说明模型此前确实“见过”类似题目,原分数被高估。去重算法可能有缺陷,但不能直接推出“一定”;评测集依然有用,训练数据质量与这次评测的下降无关。