☰
数据污染检测:大模型评测前先确认测试集没有被见过——用TaoToken统一Key跑通检测流程
2026/10/10 7:51:43 网站建设 项目流程

1. 评测分数虚高的元凶:测试集被模型见过怎么办

大模型评测里最容易被忽略的一件事,就是数据污染检测。你辛辛苦苦跑完一轮评测,发现某个模型在测试集上准确率突然涨了十几个点,第一反应可能是"这模型真强",但更常见的情况是:测试集里的样本,模型在训练阶段已经见过了。这就是所谓的数据污染,也叫测试集泄漏。

数据污染检测要解决的问题很具体:在正式评测之前,确认测试集有没有被模型"提前看过"。它适合谁?适合所有需要拿分数说话的人——做模型选型的算法工程师、写评测报告的团队、给客户交付 benchmark 结果的服务方。因为一旦测试集不可信,后面所有的分数对比、能力结论、选型决策都建立在沙子上。

污染不只是完全重复。完全重复最好查,规范化文本后做哈希就能命中。真正麻烦的是近似重复和答案泄漏:题干换了几个词、选项顺序调了一下、训练语料里混进了带解析过程的问答对,这些都会把分数抬上去,但用简单的字符串匹配根本查不出来。

我试过在一个代码评测集上直接跑分,结果某模型通过率高得离谱,后来做了一轮近似重复检测,发现将近 3% 的样本能在参考语料里找到高度相似的题面。剔掉这批样本重跑,分数回落了 8 个点。这件事说明:评测前不做污染检测,后面的分数对比就缺少可信基础。

这篇要交付的是一套可跟做的流程:用 TaoToken 统一 Key 接入检测用的模型接口,跑精确匹配、近似重复、答案泄漏三层检测,最后对同一测试集做污染比对,确认命中率和日志输出。整套流程你可以在本地脚本里复现,不需要改评测框架本身。

核心检索词先明确:数据污染检测是什么——它是评测前对测试集做泄漏排查的步骤;能做什么——发现完全重复、近似重复、答案泄漏;适合谁——需要确认测试集没被模型见过的评测团队。下面从接入准备开始,一步步把流程跑通。

2. TaoToken 统一 Key 接入:检测脚本的模型调用前置

做污染检测,尤其是近似重复和答案泄漏这两层,需要调用 embedding 模型或对话模型来判断语义相似度。如果每个检测脚本都单独配一套 Key、单独处理不同厂商的 Base URL,维护成本会很高。TaoToken 在这里的作用是提供一个统一的 API 入口,让你用同一个 Key 调用不同模型,检测脚本里只改 Model ID 就能切换。

先说清楚它不是什么:TaoToken 不是编辑器替代品,也不是让你绕过评测框架的东西。它就是一个统一的模型调用入口,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。你把它理解成"一个 Key 打通多个模型"就行。

接入前你需要准备三件套,这三件套在任何检测脚本里都要写全:

  • Base URL:https://taotoken.net/api
  • API Key:在控制台创建,地址 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • Model ID:比如做 embedding 相似度用 embedding 类模型,做答案泄漏判断用对话类模型,具体可用模型在文档里查,文档地址 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

如果你用的是 Claude Code 这类编码工具来写检测脚本,接入配置也走同一套。Claude Code 的接入文档在 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面写了 Base URL 和 Key 怎么填。Coding Plan 适合长期跑检测任务的团队,地址 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

为什么检测脚本要用统一 Key?因为污染检测往往要交叉验证:MinHash 算一遍、embedding 算一遍、再用对话模型对高风险样本做人工复核辅助。如果每换一个模型就要改一套鉴权逻辑,脚本会变得很脆。统一 Key 之后,你只需要在配置里换 Model ID。

这里给一个环境变量的约定,后面所有脚本都读这套变量:

export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_EMBED_MODEL="你的embedding模型ID" export TAOTOKEN_CHAT_MODEL="你的对话模型ID"

把 Key 放环境变量而不是硬编码进脚本,是为了避免提交到仓库时泄漏。这一点在团队协作里尤其重要,检测脚本经常要共享,硬编码的 Key 一旦进了 git 历史就很难彻底清掉。

接入验证很简单,先用一个最小请求确认 Key 和 Base URL 通:

import os from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) resp = client.embeddings.create( model=os.environ["TAOTOKEN_EMBED_MODEL"], input=["测试集污染检测接入验证"], ) print(len(resp.data[0].embedding))

能打印出向量维度,说明接入通了。如果这一步就报错,先别往下写检测逻辑,回到第 5 节排查。接入通了之后,检测脚本的模型调用部分就固定下来了,后面只关心检测算法本身。

3. 可复制配置:三层污染检测脚本与 settings 片段

这一节给可直接复制的配置和脚本。检测分三层:精确哈希、近似重复、答案泄漏。三层不是替代关系,是递进的——精确重复都没清掉,复杂检测就没意义。

先给一个统一的配置文件,用 JSON 写,路径放在项目根目录的contamination_config.json:

{ "dataset": "eval_v3", "corpus_path": "./data/reference_corpus.jsonl", "testset_path": "./data/testset.jsonl", "exact_hash": true, "minhash": { "enabled": true, "num_perm": 128, "jaccard_threshold": 0.85 }, "embedding": { "enabled": true, "model_env": "TAOTOKEN_EMBED_MODEL", "cosine_threshold": 0.92, "batch_size": 64 }, "answer_leakage": { "enabled": true, "model_env": "TAOTOKEN_CHAT_MODEL", "top_k": 200 }, "report": { "output_path": "./reports/contamination_report.json", "keep_removed_samples": true } }

阈值不要照搬。代码题、数学题、问答题的文本相似度分布完全不同,统一阈值会带来误杀或漏检。jaccard_threshold和cosine_threshold建议先在小样本上跑一遍,看分布再定。

第一层精确匹配,规范化文本后做 SHA256:

import hashlib import re def normalize(text: str) -> str: text = text.lower() text = re.sub(r"\s+", " ", text) return text.strip() def fingerprint(text: str) -> str: return hashlib.sha256(normalize(text).encode()).hexdigest() def exact_match_check(testset, corpus): corpus_hashes = {fingerprint(s["text"]) for s in corpus} hits = [] for sample in testset: if fingerprint(sample["text"]) in corpus_hashes: hits.append(sample["id"]) return hits

第二层近似重复,用 MinHash 算 Jaccard 相似度:

from datasketch import MinHash, MinHashLSH def build_minhash(text: str, num_perm: int = 128) -> MinHash: m = MinHash(num_perm=num_perm) for token in normalize(text).split(): m.update(token.encode("utf8")) return m def near_duplicate_check(testset, corpus, threshold=0.85, num_perm=128): lsh = MinHashLSH(threshold=threshold, num_perm=num_perm) for i, sample in enumerate(corpus): lsh.insert(f"corpus_{i}", build_minhash(sample["text"], num_perm)) hits = [] for sample in testset: m = build_minhash(sample["text"], num_perm) result = lsh.query(m) if result: hits.append({"id": sample["id"], "matched": result}) return hits

第三层用 embedding 做语义相似度,走 TaoToken 统一 Key:

import os import numpy as np from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) def embed_batch(texts, model, batch_size=64): vectors = [] for i in range(0, len(texts), batch_size): batch = texts[i:i + batch_size] resp = client.embeddings.create(model=model, input=batch) vectors.extend([d.embedding for d in resp.data]) return np.array(vectors) def cosine_sim(a, b): a = a / np.linalg.norm(a, axis=1, keepdims=True) b = b / np.linalg.norm(b, axis=1, keepdims=True) return a @ b.T

答案泄漏检测用对话模型辅助判断,对高风险样本问一句"这段解析是否直接给出了答案":

def answer_leakage_probe(question, reference, model): prompt = ( "判断下面的参考文本是否直接泄漏了问题的答案。" "只回答 是 或 否。\n\n" f"问题:{question}\n参考文本:{reference}" ) resp = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], temperature=0, ) return resp.choices[0].message.content.strip()

如果你用 Cline 或带 MCP 的工具来组织这套脚本,配置里同样要写全三件套:Base URL 填https://taotoken.net/api,Key 填控制台创建的 Key,Model ID 填你选的模型。三件套缺一个都会在请求阶段报错,别只填 Key 就以为通了。

4. 验证请求:对同一测试集跑污染比对看命中率

配置写完,接下来是验证动作:对同一测试集跑一遍完整检测,确认命中率和日志输出符合预期。这一步的目的是证明流程真的能跑通,而不是停留在"配置看起来对"。

先准备数据。测试集和参考语料都用 JSONL,每行一个样本,至少包含id和text字段:

{"id": "q001", "text": "请解释什么是梯度消失问题"} {"id": "q002", "text": "写一个快速排序的Python实现"}

跑检测的主脚本:

import json from contamination import ( exact_match_check, near_duplicate_check, embed_batch, cosine_sim, ) with open("contamination_config.json") as f: cfg = json.load(f) testset = [json.loads(l) for l in open(cfg["testset_path"])] corpus = [json.loads(l) for l in open(cfg["corpus_path"])] report = { "dataset": cfg["dataset"], "total": len(testset), "exact_match": 0, "near_duplicate": 0, "answer_leakage_suspected": 0, "manual_confirmed": 0, } if cfg["exact_hash"]: exact_hits = exact_match_check(testset, corpus) report["exact_match"] = len(exact_hits) print(f"[exact] hits={len(exact_hits)}") if cfg["minhash"]["enabled"]: near_hits = near_duplicate_check( testset, corpus, threshold=cfg["minhash"]["jaccard_threshold"], num_perm=cfg["minhash"]["num_perm"], ) report["near_duplicate"] = len(near_hits) print(f"[minhash] hits={len(near_hits)}") if cfg["embedding"]["enabled"]: import os model = os.environ[cfg["embedding"]["model_env"]] test_vecs = embed_batch([s["text"] for s in testset], model) corpus_vecs = embed_batch([s["text"] for s in corpus], model) sim = cosine_sim(test_vecs, corpus_vecs) threshold = cfg["embedding"]["cosine_threshold"] emb_hits = int((sim.max(axis=1) >= threshold).sum()) report["answer_leakage_suspected"] = emb_hits print(f"[embedding] hits={emb_hits}") with open(cfg["report"]["output_path"], "w") as f: json.dump(report, f, ensure_ascii=False, indent=2) print(json.dumps(report, ensure_ascii=False, indent=2))

跑完之后你会看到类似这样的日志:

[exact] hits=87 [minhash] hits=214 [embedding] hits=39 { "dataset": "eval_v3", "total": 12000, "exact_match": 87, "near_duplicate": 214, "answer_leakage_suspected": 39, "manual_confirmed": 0 }

命中率怎么解读?exact_match是硬命中,直接剔除。near_duplicate是近似重复,需要看匹配到的语料样本,判断是否真的算污染。answer_leakage_suspected是疑似答案泄漏,数量通常不多,但影响大,建议全部进人工复核队列。

报告里要区分污染等级,不要只给一个比例。manual_confirmed字段留给人工复核后回填。评测报告里应明确说明是否剔除了污染样本,以及剔除前后分数变化,否则读者无法判断模型提升来自能力还是数据问题。

验证成功的标志有三个:日志三层都有输出、报告 JSON 字段完整、被剔除样本清单落盘。keep_removed_samples设为 true 时,脚本要把命中样本的 id 和判定依据写进单独文件,方便后续复用。

5. 常见报错排查:401、local proxy failed 与 reading choices

检测脚本跑不起来,八成是接入层的问题。这一节对照真实报错给排查路径。

401 Unauthorized:Key 没读到或填错。先确认环境变量真的导出了:

echo $TAOTOKEN_API_KEY

如果为空,说明当前 shell 没加载。注意脚本里读的是os.environ["TAOTOKEN_API_KEY"],如果你在 IDE 里跑,IDE 可能没继承终端的环境变量,需要在运行配置里单独设。Key 本身也要确认是在控制台创建的、没有多余空格。

local proxy failed / connection error:这类报错通常是 Base URL 写错或网络层配置问题。检查TAOTOKEN_BASE_URL是不是https://taotoken.net/api,注意结尾不要多加/v1或斜杠。如果你本地有网络层工具在跑,先确认它没有拦截这个域名。这类问题不要靠猜,直接用 curl 打一发:

curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ https://taotoken.net/api/models

返回 200 说明链路通,返回 401 是 Key 问题,返回其他码再看文档。

reading 'choices' / KeyError: 'choices':脚本在解析响应时拿不到choices字段。常见原因是请求打到了 embedding 接口却按对话响应解析,或者 Model ID 填成了不存在的模型。检查你调用的方法:client.embeddings.create返回的是data,client.chat.completions.create返回的才是choices。两者别混。

OAuth / 鉴权失败:如果你用 Claude Code 或类似工具接入,报 OAuth 相关错误,说明工具走的是它自己的登录流程,而不是 API Key。这时候要按 Claude Code 接入文档改配置,把鉴权方式切到 API Key,Base URL 和 Model ID 一起填全。文档在 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

embedding 维度对不上:cosine_sim里两个矩阵维度不一致,通常是测试集和语料用了不同 embedding 模型。确认两边都读同一个TAOTOKEN_EMBED_MODEL。

MinHash 命中数为 0:先确认num_perm和threshold是否合理。threshold设太高(比如 0.95)会几乎查不出东西,设太低(比如 0.5)会大量误报。建议从 0.85 起步,看分布再调。

排查顺序建议固定:先 curl 验证链路,再验证 Key,再验证 Model ID,最后才看脚本逻辑。大部分报错在前三步就能定位,不用一上来就怀疑检测算法。

6. 把检测流程固化下来:从一次性脚本到可复用资产

污染检测跑通一次不难,难的是让它成为团队可复用的资产。实际报告里应保留被剔除样本清单和判定依据,这样后续更换模型或扩展数据集时,团队可以复用污染判断,而不是每次从零开始做主观筛选。

具体做法是把三层检测的命中结果落盘成结构化文件,字段至少包含样本 id、污染类型、匹配到的语料 id、相似度分数、判定依据。下次换模型评测时,先拿这份清单过滤测试集,再跑分。这样分数对比才是在同一套干净数据上做的。

阈值也要版本化。把contamination_config.json一起提交到仓库,每次调整阈值都留记录。不同任务的阈值不同,代码题和问答题分开维护配置文件,别用一套阈值打天下。

如果你要长期跑检测任务,Coding Plan 比按次调用更省心,地址 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。需要临时验证某个模型对某批样本的判断,用模型对话页面直接试,地址 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。Key 管理和文档分别在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 和 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后提醒一个容易踩的坑:别把污染检测当成一次性动作。测试集会更新,参考语料会扩充,模型会换代,每次评测前都该重跑一遍。把脚本和配置固化下来,跑一次的成本很低,但省下的是一次错误结论带来的返工。评测分数只有在测试集可信时才有意义,先确认模型没有见过考题,再讨论它是否真的会做题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询