AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

评测泄漏与 Benchmark 污染

我的学习 →

正在读取学习记录…

评测泄漏是测试信息进入训练、调参、提示示例或其他本应独立的环节。Benchmark 污染通常指模型或系统在评测前接触过基准样本及其变体,导致成绩混入记忆或针对测试的优化。此时测试更像开卷默写,难以说明遇到新问题的能力。

例如训练样例里有“图书订单收货7天内可退款”,测试题只是把7换成全角7、句号换成叹号。原字符串不同,规范化后相同。另一个测试题完全改了问法,却仍来自同一份政策文档;它可能与训练样例共享信息与模板,划分时也要看来源。

测试答案藏在另一个文件里,并不能阻止问题本身进入few-shot示例或检索库。few-shot是放进提示词的少量示范输入与输出,用来提示模型怎样处理任务;若其中包含测试题,系统在正式评测前就已看过它。还有一种反馈循环:每次看失败题后修改Prompt,反复迭代,测试集逐渐承担了开发集的角色。

去重与隔离分别处理什么

去重把同一材料池里的重复记录合并,防止同一条样例反复计数。隔离控制评测与已暴露材料的交集:来源同族、规范化精确相同或字面近重复的测试先进入隔离区,保留原因等待核查。

程序已准备训练样例、Prompt示例、检索材料与五道测试。内容规范化使用NFKC(统一兼容字符形式)、小写化及去标点空白;字符三元片段用于查近重复。来源family表示从同一政策文档或其复制版本衍生的材料组。

训练、提示与检索材料先规范化去重,再审计测试的来源同族、精确重复和字面近重复;可疑项隔离,未发现重叠项保留
图1:隐藏答案不能替代材料去重与来源隔离;无法查看的外部预训练材料保持未知。

保存完整程序并运行

EXPOSURES 是已知暴露材料,TESTS 是候选评测题。程序返回去重项、保留项与隔离原因,没有根据答案正确与否删题。近重复阈值0.55是固定示例参数,生产阈值应在独立标注开发样本上校准。

在 PowerShell 创建并进入文件夹:

Bash
mkdir l7-contamination
cd l7-contamination

把下面完整代码保存为该文件夹的 contamination_lab.py。数据已经写在程序中,直接保存即可运行。

Python
"""对训练样例、提示示例、检索材料与测试题做去重和隔离审计。"""

import hashlib
import unicodedata

EXPOSURES = [
    {"id": "train-1", "pool": "train", "family": "book-policy",
     "text": "图书订单可以在收货7天内申请退款。"},
    {"id": "train-copy", "pool": "train", "family": "book-policy",
     "text": "图书订单可以在收货7天内申请退款!"},
    {"id": "prompt-1", "pool": "prompt", "family": "hotel-policy",
     "text": "酒店订单入住前可以取消。"},
    {"id": "retrieve-1", "pool": "retrieval", "family": "book-policy",
     "text": "图书政策修订说明:收货时间以签收记录为准。"},
]
TESTS = [
    {"id": "test-exact", "family": "book-policy",
     "text": "图书订单可以在收货7天内申请退款!"},
    {"id": "test-family", "family": "book-policy",
     "text": "图书退款到期日如何计算?"},
    {"id": "test-prompt", "family": "hotel-mirror",
     "text": "酒店订单入住前可以取消。"},
    {"id": "test-near", "family": "book-mirror",
     "text": "图书订单可以在收货7天内提交退款。"},
    {"id": "test-new", "family": "ticket-new",
     "text": "演唱会门票按票面规则退票。"},
]
NEAR_THRESHOLD = .55


def normalize(text):
    # 全角字符、大小写、标点和空白差异不应伪装成新样本。
    folded = unicodedata.normalize("NFKC", text).casefold()
    return "".join(c for c in folded if c.isalnum())


def fingerprint(text):
    return hashlib.sha256(normalize(text).encode("utf-8")).hexdigest()


def shingles(text, width=3):
    value = normalize(text)
    return {value[i:i + width] for i in range(max(0, len(value) - width + 1))}


def similarity(left, right):
    a, b = shingles(left), shingles(right)
    # 短文本退回规范化后的精确比较,避免两个空集合被当成相同长文。
    if not a or not b:
        return float(normalize(left) == normalize(right))
    return len(a & b) / len(a | b)


def deduplicate(rows):
    seen = set()
    kept, removed = [], []
    for row in rows:
        # 各材料池分别去重,仍保存跨池暴露记录供审计。
        key = (row["pool"], fingerprint(row["text"]))
        if key in seen:
            removed.append(row["id"])
        else:
            seen.add(key)
            kept.append(row)
    return kept, removed


def audit(exposures, tests, threshold=NEAR_THRESHOLD):
    if not 0 <= threshold <= 1:
        raise ValueError("invalid similarity threshold")
    kept, duplicates = deduplicate(exposures)
    clean, quarantined = [], []
    for test in tests:
        reasons = set()
        for known in kept:
            if test["family"] == known["family"]:
                reasons.add("source-family")
            if fingerprint(test["text"]) == fingerprint(known["text"]):
                reasons.add("normalized-exact")
            elif similarity(test["text"], known["text"]) >= threshold:
                reasons.add("lexical-near")
        if reasons:
            quarantined.append((test["id"], sorted(reasons)))
        else:
            clean.append(test["id"])
    return duplicates, clean, quarantined


def main():
    duplicates, clean, quarantined = audit(EXPOSURES, TESTS)
    print(f"duplicates_removed={','.join(duplicates)}")
    for identifier, reasons in quarantined:
        print(f"quarantine {identifier}: {','.join(reasons)}")
    print(f"clean_tests={','.join(clean)} count={len(clean)}")
    print(f"near_threshold={NEAR_THRESHOLD:.2f} test_near_similarity="
          f"{similarity(TESTS[3]['text'], EXPOSURES[0]['text']):.3f}")
    print("external_pretraining_exposure=unknown")


if __name__ == "__main__":
    main()

保存后列出文件,确认能看到 contamination_lab.py,再运行:

Bash
ls
python -X utf8 contamination_lab.py

程序输出:

TXT
duplicates_removed=train-copy
quarantine test-exact: normalized-exact,source-family
quarantine test-family: source-family
quarantine test-prompt: normalized-exact
quarantine test-near: lexical-near
clean_tests=test-new count=1
near_threshold=0.55 test_near_similarity=0.556
external_pretraining_exposure=unknown

逐项读懂结果

duplicates_removed=train-copy 表示训练池里全角数字与标点差异的副本被合并。不同材料池仍保留暴露记录,因为它们对审计有不同用途。

test-exact 同时命中规范化精确重复与来源同族。test-family 的文字不同,但来自同一图书政策族,因此按来源隔离。test-prompt 与提示示例相同,即使测试答案没有出现在材料里,问题内容已经暴露。

test-near 将“申请退款”换成“提交退款”,没有精确相同,但命中字面近重复。字符三元片段的Jaccard相似度为 |A∩B| / |A∪B|:A、B分别是两段文本的三字符片段集合,交集共有10个,并集18个,10/18≈0.556。超过0.55阈值,进入隔离区。

test-new 来自独立门票政策族,没有发现与这份暴露清单的重叠,所以保留1道。它的“未发现重叠”有明确检查范围:外部模型的预训练文本没有提供,最后一行因此保存 external_pretraining_exposure=unknown。模型答对公开题本身不能证明看过训练答案。

改一处,再看对应结果

把 NEAR_THRESHOLD = .55 改成 NEAR_THRESHOLD = .95,运行 python -X utf8 contamination_lab.py。test-near相似度仍为0.556,却不再达到阈值,保留项变为 test-near,test-new,数量2。

来源同族和精确匹配的三项仍隔离,因为它们不依赖近重复阈值。这个变化展示提高阈值会漏掉哪些样本,不能据此认定更高阈值更合理。恢复0.55后运行测试。

创建保留集时把资料家族一起划分

先按原始文档、用户、题目模板或衍生族建立组,再把整个组放进训练、开发或最终测试的一边。随后做跨组重复审计,防止复制与改写跨边界。若先随机拆单条问题,同一政策衍生的十道题可能分散到所有集合,单条去重无法恢复独立性。

开发集用来选择Prompt、检索参数和阈值;最终保留集用于约定轮次的确认。对最终测试反复逐题调参,应承认它已被用于开发,再准备新的独立保留集。留出时间上更晚的样本也能辅助评估新政策与新用户,但时间切分同样需要查来源重叠。

GPT-3 论文的污染分析比较了潜在重叠样本与更干净子集,并讨论检测局限。Rephrased Samples 论文进一步研究了改写样本可能绕过字面重叠检查。字符片段能够发现部分近重复,却无法保证发现语义改写;有疑点还需来源追踪与人工核查。

训练前发现泄漏,可以清理并重新划分数据。模型已经训练后,删除本地那行记录不会撤回模型可能学到的内容;应记录污染范围、报告独立干净子集,并在需要时重建训练或选用新的评测。检索材料若正是业务必须提供的合法知识,可以设计开放资料任务,但评测目标和允许材料要预先规定,避免在看到成绩后改规则。

保存并运行测试

7 项测试检查规范化去重、全角和标点、来源同族、Prompt暴露、近重复、高阈值漏检与短文本处理。

把下面代码保存为同一文件夹的 test_contamination_lab.py,确认两个文件都在当前目录,运行:

Bash
ls
python -X utf8 -m unittest -v test_contamination_lab.py
完整测试:test_contamination_lab.py
Python
"""检查规范化、来源隔离、提示泄漏与近重复阈值。"""

import sys
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import contamination_lab as lab


class ContaminationTests(unittest.TestCase):
    def test_normalized_duplicate_is_removed(self):
        kept, removed = lab.deduplicate(lab.EXPOSURES)
        self.assertEqual(removed, ["train-copy"])
        self.assertEqual(len(kept), 3)

    def test_unicode_and_punctuation_normalize(self):
        self.assertEqual(lab.fingerprint("A B,7!"), lab.fingerprint("ab7"))

    def test_source_family_catches_different_question(self):
        _, _, quarantined = lab.audit(lab.EXPOSURES, lab.TESTS)
        reasons = dict(quarantined)
        self.assertIn("source-family", reasons["test-family"])

    def test_hidden_answer_does_not_hide_prompt_overlap(self):
        _, _, quarantined = lab.audit(lab.EXPOSURES, lab.TESTS)
        self.assertIn("normalized-exact", dict(quarantined)["test-prompt"])

    def test_lexical_near_is_quarantined(self):
        _, clean, quarantined = lab.audit(lab.EXPOSURES, lab.TESTS)
        self.assertEqual(clean, ["test-new"])
        self.assertIn("lexical-near", dict(quarantined)["test-near"])

    def test_high_threshold_can_miss_near_duplicate(self):
        _, clean, _ = lab.audit(lab.EXPOSURES, lab.TESTS, threshold=.95)
        self.assertIn("test-near", clean)

    def test_short_text_and_invalid_threshold(self):
        self.assertEqual(lab.similarity("甲", "乙"), 0)
        self.assertEqual(lab.similarity("甲!", "甲"), 1)
        with self.assertRaises(ValueError):
            lab.audit([], [], threshold=2)


if __name__ == "__main__":
    unittest.main()

继续查论文

阅读 GPT-3 论文的第4节与附录C,关注污染检测规则、干净子集与结论局限。再读 Rethinking Benchmark and Contamination with Rephrased Samples,理解精确重复检查为何还需要来源审计。

课后练习展开习题收起习题

若把测试答案删掉、测试问题保留在few-shot里,什么仍然泄漏?若同一政策文档的改写题被拆到训练与测试两边,应该在哪一层重新分组?

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.1.0

选择题 · 开发集和测试集存在同一订单文本的轻微改写,怎样处理

开发集和测试集存在同一订单文本的轻微改写,怎样处理?

对应知识点:L7-contamination

选择一个答案
练习 2 / 4 · v1.1.0

简答题 · 解释机制

评测泄漏怎样发生在调参过程中?

对应知识点:L7-contamination

练习 3 / 4 · v1.1.0

代码题 · 核对给定数据

两个来源标识相同的样本应进入同一划分,写一条检查断言。

对应知识点:L7-contamination

练习 4 / 4 · v1.1.0

场景题 · 分析具体变化

清理精确重复后仍有近重复,需要继续检查什么?

对应知识点:L7-contamination

图片查看器
100%

文章配图 · 按 Esc 关闭