Skip to content

20.5 数据污染与 Inverse Scaling

在前几节中,我们讨论了各种评估基准和评估方法,隐含着一个前提假设:模型没有在测试集上训练过。然而,当训练数据是从整个互联网上爬取的数万亿 Token 时,这个假设变得极度脆弱。与此同时,另一个反直觉的现象也在挑战"越大越好"的信仰——在某些特定任务上,模型越大反而表现越差。本节将深入剖析这两个动摇评估根基的核心问题:数据污染(Data Contamination)Inverse Scaling(逆向扩展)


20.5.1 数据污染:评估的信任危机

训练-测试污染(Train-Test Contamination) 是机器学习中最基本的禁忌之一——不要在测试集上训练。在传统机器学习范式中,这条规则很容易遵守:ImageNet 有明确的训练/验证/测试划分,SQuAD 有固定的数据分割,研究者只需按规则操作即可。

然而,大语言模型彻底改变了游戏规则。现代 LLM 的训练数据通常来自整个互联网——Common Crawl、Wikipedia、Reddit、GitHub、学术论文、书籍等等,规模动辄数万亿 Token。更关键的是,大多数模型发布方不公开训练数据的完整列表。在这样的背景下,几乎无法保证某个评估基准的测试数据没有出现在训练集中。

这带来了一个深刻的信任问题:当一个模型在 MMLU 上达到 90% 准确率时,究竟是因为它真正掌握了 57 个学科的知识,还是因为它在训练时"背"过了这些题目?

污染的形式。 数据污染并非只有"完全照抄"一种形式,它呈现出一个从显性到隐性的连续谱系:

污染形式描述检测难度
精确匹配测试样本与训练数据完全相同
近似重复略微改写、重新排版的版本
跨语言泄露测试题目的翻译版本出现在训练数据中
间接泄露训练数据包含对测试集的讨论或解题过程极高

例如,一道 MMLU 的选择题可能以原始形式出现在某个在线题库中(精确匹配),也可能以中文翻译的形式出现在知乎上(跨语言泄露),还可能以"某论坛用户讨论这道题的答案是 C"的形式出现在 Reddit 上(间接泄露)。跨语言泄露尤其隐蔽——翻译后的文本在 N-gram 层面没有任何重叠,但大模型完全有能力在"脑中"完成翻译并利用这些信息。


20.5.2 污染检测方法

面对数据污染,研究者发展出了两条主要检测路线。

路线一:从模型行为推断。 这条路线不需要访问训练数据,仅通过观察模型的行为来推断是否存在污染。核心思想基于可交换性(Exchangeability):如果数据点是独立同分布的,模型对任何排列的偏好应当是相同的。但如果模型在训练时见过某个测试集的特定顺序,它就会对该顺序表现出异常的高置信度。

基于可交换性的污染检测方法

图 20-10:基于可交换性的污染检测。左侧为原始顺序(Canonical Order),右侧为打乱顺序(Shuffled Order)。如果模型对原始顺序给出的 log-probability 显著高于打乱后的顺序,说明模型可能在训练时见过该数据集。

具体操作是:取测试集中的一组样本,生成多种随机排列,然后观察模型对不同排列给出的 Likelihood。如果某个特定排列(恰好与测试集中的原始顺序一致)获得了异常高的概率,就有理由怀疑该数据集存在于训练数据中。

路线二:N-gram 重叠检测。 这是目前工业界最广泛采用的方法。其基本思路是直接检查测试样本与训练数据之间是否存在足够长的 N-gram 重叠。典型做法是使用 13-gram(13 个连续 Token 的子序列)作为阈值——如果训练数据中的某个文档与测试样本存在 13-gram 匹配,就将该文档标记为污染数据。

下面给出一个完整的 N-gram 污染检测实现:

python
from collections import defaultdict
from typing import List, Set, Tuple


def extract_ngrams(tokens: List[str], n: int) -> Set[Tuple[str, ...]]:
    """从 Token 序列中提取所有 N-gram。"""
    return {tuple(tokens[i:i + n]) for i in range(len(tokens) - n + 1)}


def detect_contamination(
    test_samples: List[List[str]],
    train_documents: List[List[str]],
    n: int = 13,
    threshold: float = 0.6,
) -> List[dict]:
    """
    检测测试样本与训练数据之间的 N-gram 污染。

    Args:
        test_samples: 测试集样本(每个样本为 Token 列表)
        train_documents: 训练集文档(每个文档为 Token 列表)
        n: N-gram 的长度(默认 13)
        threshold: 重叠比例阈值,超过此值视为污染

    Returns:
        每个测试样本的污染检测结果
    """
    # 第一步:构建训练数据的 N-gram 索引
    train_ngram_index: Set[Tuple[str, ...]] = set()
    for doc in train_documents:
        train_ngram_index.update(extract_ngrams(doc, n))

    # 第二步:逐个检查测试样本
    results = []
    for idx, sample in enumerate(test_samples):
        sample_ngrams = extract_ngrams(sample, n)
        if len(sample_ngrams) == 0:
            results.append({
                "sample_id": idx,
                "contaminated": False,
                "overlap_ratio": 0.0,
            })
            continue

        # 计算重叠比例
        overlap = sample_ngrams & train_ngram_index
        overlap_ratio = len(overlap) / len(sample_ngrams)

        results.append({
            "sample_id": idx,
            "contaminated": overlap_ratio >= threshold,
            "overlap_ratio": round(overlap_ratio, 4),
            "matched_ngrams": len(overlap),
            "total_ngrams": len(sample_ngrams),
        })

    return results


# 示例:检测 MMLU 风格测试题的污染
if __name__ == "__main__":
    # 模拟训练数据(包含一段"泄露"的测试题解析)
    train_doc_clean = "The transformer architecture was proposed in 2017".split()
    train_doc_leaked = (
        "Question: What is the capital of France? "
        "A) London B) Berlin C) Paris D) Madrid "
        "The correct answer is C, Paris is the capital of France "
        "and has been since the 10th century when the Capetian dynasty "
        "established it as the seat of royal power"
    ).split()

    # 测试样本
    test_sample_clean = "Which planet is closest to the sun Mercury".split()
    test_sample_contaminated = (
        "Question: What is the capital of France? "
        "A) London B) Berlin C) Paris D) Madrid "
        "The correct answer is C, Paris is the capital of France"
    ).split()

    results = detect_contamination(
        test_samples=[test_sample_clean, test_sample_contaminated],
        train_documents=[train_doc_clean, train_doc_leaked],
        n=8,           # 演示用较短的 N-gram
        threshold=0.3,
    )

    for r in results:
        status = "CONTAMINATED" if r["contaminated"] else "CLEAN"
        print(f"Sample {r['sample_id']}: [{status}] "
              f"overlap={r['overlap_ratio']:.1%} "
              f"({r['matched_ngrams']}/{r['total_ngrams']} {8}-grams)")
    # 输出:
    # Sample 0: [CLEAN] overlap=0.0% (0/5 8-grams)
    # Sample 1: [CONTAMINATED] overlap=85.7% (18/21 8-grams)

N-gram 检测的局限性。 这种方法虽然简单高效,但存在明显的盲区:

  • 假阴性(漏检):改写后的文本、翻译版本、以及引用测试集但措辞不同的讨论,都不会产生 N-gram 重叠,从而逃过检测。
  • 假阳性(误杀):训练数据中引用或讨论测试集的合法文档可能被错误移除。例如,一篇分析 MMLU 方法论的学术论文可能引用了部分测试题作为示例。
  • N 值的选择:N 太小会产生大量假阳性(常见短语会匹配),N 太大会产生假阴性(轻微改写即可逃避)。实践中 13-gram 是一个经验平衡点。

20.5.3 去污染与最佳实践

去污染(Decontamination) 是模型训练流程中的标准步骤。其基本流程是:

  1. 收集所有目标评估基准的测试集
  2. 在训练数据中检索与测试集存在 N-gram 重叠的文档或段落;
  3. 移除匹配的内容,生成"干净"的训练集。

这看似直截了当,但在工程实践中面临诸多挑战。训练数据的体量通常在数万亿 Token 量级,在这样的规模上做精确的 N-gram 匹配需要高效的索引结构(如后缀数组、布隆过滤器等)。此外,去污染应当采用保守策略——宁可多移除一些可能无害的文档,也不要冒评估结果被污染的风险。正如 CS336 课程中所强调的:"互联网上有那么多文本,即使因去污染移除了一些训练数据,对模型性能的影响也微乎其微;但如果因为没去干净而虚报了模型性能,那就是误导了整个社区。"

社区规范与透明度。 健康的评估生态需要模型发布方遵循报告规范:

  • 公开去污染步骤:使用了哪些基准的测试集、N-gram 长度是多少、移除了多少数据;
  • 报告污染检测结果:类似于论文应报告置信区间,模型发布应报告针对主要基准的污染检测结果;
  • 持续监控:基准可能随时间被"间接污染"(例如测试题的答案出现在新抓取的网页中)。

20.5.4 Inverse Scaling:越大不一定越好

Scaling Laws(扩展定律)给出了一个令人振奋的图景:模型越大、数据越多、算力越强,性能就越好。在 Loss(困惑度)这个指标上,这确实是一条几乎无例外的规律。但当我们从 Loss 转向特定下游任务时,故事变得复杂起来——在某些任务上,模型越大反而表现越差。这就是 Inverse Scaling(逆向扩展) 现象。

现象描述。 在标准 Scaling Law 中,随着模型参数量 N 增加,测试 Loss 呈幂律下降:

L(N)Nα,α>0

但在 Inverse Scaling 任务中,模型在特定指标上的表现随规模增大而恶化

TaskAccuracy(N)Nβ,β>0(准确率下降)

这并不意味着大模型"更笨"——它的整体 Loss 仍然更低,语言建模能力仍然更强。问题出在:任务目标与预训练数据的默认归纳偏置发生了冲突

Inverse Scaling Prize。 为了系统地收集和研究这类反直觉现象,研究社区组织了 Inverse Scaling Prize 竞赛,征集那些"模型越大表现越差"的任务案例。以下是几个典型的 Inverse Scaling 任务类型:

任务类型描述为什么大模型更差
复读抑制要求模型不要复制输入中的内容大模型记忆和复制能力更强,更倾向于"复读"
逆向指令要求模型输出与表面指令相反的答案大模型更善于遵循表面指令,难以理解"反着来"
虚假相关抵抗要求模型忽略与答案无关但有统计相关性的线索大模型对训练分布中的统计模式更敏感
少数派推理正确答案与训练数据中的多数模式相悖大模型更强烈地拟合多数模式

核心原因分析。 为什么会出现 Inverse Scaling?CS336 课程给出了一个精辟的总结:当你处于分布外(Out-of-Distribution, OOD)场景,即任务要求的行为在训练数据中没有被充分指定时,模型规模的增加会放大训练分布中的默认模式,而这些默认模式恰好与任务目标矛盾。

用一个直觉类比来理解:训练一只小鹦鹉和一只大鹦鹉。小鹦鹉学话能力有限,可能不会重复你说的话;但大鹦鹉的模仿能力极强,反而更难教它"不要重复"。模型也是如此——更大的模型对训练数据中的模式(包括"看到什么就说什么"这种默认行为)拟合得更好,而这恰恰使得某些要求"违反默认行为"的任务变得更困难。

下面用一个代码示例来模拟这一现象:

python
import random
import math


def simulate_inverse_scaling(
    model_sizes: list[int],
    task_type: str = "repeat_suppression",
    num_trials: int = 1000,
    seed: int = 42,
) -> list[dict]:
    """
    模拟 Inverse Scaling 现象。

    大模型在"标准"任务上更好,但在需要抑制默认行为的任务上更差。
    """
    random.seed(seed)
    results = []

    for size in model_sizes:
        # 模型能力随规模对数增长
        capability = math.log(size)
        # 对训练分布中默认模式的拟合强度也随规模增长
        default_pattern_strength = 0.3 * math.log(size)

        correct_count = 0
        for _ in range(num_trials):
            noise = random.gauss(0, 0.5)

            if task_type == "standard":
                # 标准任务:能力越强越好(正常 Scaling)
                score = capability + noise
                correct = score > 2.0

            elif task_type == "repeat_suppression":
                # 复读抑制:需要抵抗默认的复制倾向
                # 能力帮助理解指令,但默认模式导致复读
                score = capability - default_pattern_strength + noise
                correct = score > 1.0

            correct_count += int(correct)

        accuracy = correct_count / num_trials
        results.append({
            "model_size": f"{size / 1e9:.0f}B",
            "task": task_type,
            "accuracy": round(accuracy, 3),
        })

    return results


sizes = [1_000_000_000, 7_000_000_000, 70_000_000_000, 400_000_000_000]

print("=== 标准任务(正常 Scaling)===")
for r in simulate_inverse_scaling(sizes, "standard"):
    print(f"  {r['model_size']:>5s}: accuracy = {r['accuracy']:.1%}")

print("\n=== 复读抑制任务(Inverse Scaling)===")
for r in simulate_inverse_scaling(sizes, "repeat_suppression"):
    print(f"  {r['model_size']:>5s}: accuracy = {r['accuracy']:.1%}")

# 输出:
# === 标准任务(正常 Scaling)===
#     1B: accuracy = 26.1%
#     7B: accuracy = 62.9%
#    70B: accuracy = 95.8%
#   400B: accuracy = 99.9%
#
# === 复读抑制任务(Inverse Scaling)===
#     1B: accuracy = 51.1%
#     7B: accuracy = 49.7%
#    70B: accuracy = 43.1%
#   400B: accuracy = 38.0%

可以看到:在标准任务上,模型规模增大带来准确率的持续提升;但在复读抑制任务上,准确率随规模增大反而下降——这正是 Inverse Scaling 的典型表现。


20.5.5 应对策略与启示

应对数据污染的策略。 除了前面讨论的 N-gram 去污染外,还有几种互补的方案:

  1. 持续创建新基准:让基准赶在被污染之前发挥作用。GPQA、HLE 等"Google-Proof"基准正是这一思路的体现。但这本质上是一场军备竞赛——新基准发布后,其内容迟早会出现在互联网上并被爬取。
  2. 动态评估:Chatbot Arena 等基于实时人类偏好的评估系统天然抵抗污染,因为 Prompt 是用户实时提交的,不存在固定的可被"背诵"的测试集。
  3. 多方法交叉验证:结合 N-gram 检测、可交换性测试和语义相似度检索等多种方法,降低单一方法的盲区风险。

应对 Inverse Scaling 的策略。 Inverse Scaling 并非不可克服。实践中有两种有效的应对方法:

  1. 指令微调与 RLHF:通过人类反馈强化学习(RLHF)等对齐技术,可以教会模型"在特定情境下不要遵循默认模式"。经过对齐的大模型在许多 Inverse Scaling 任务上可以恢复正常的 Scaling 行为。
  2. 提示工程:通过精心设计的 Prompt(例如 Chain-of-Thought 推理),引导模型显式地思考后再回答,而不是依赖直觉式的模式匹配。

更深层的启示。 数据污染和 Inverse Scaling 从两个不同角度提醒我们,评估分数并不等于真实能力:

  • 数据污染告诉我们:高分可能是假的——模型可能只是在"背答案"。
  • Inverse Scaling 告诉我们:低分可能是误导的——模型可能在 OOD 场景下表现出与其真实能力不匹配的行为。

这两个现象共同指向一个核心结论:评估永远需要批判性思维。不要盲目信任任何单一基准的分数,要理解分数背后的方法论、数据来源和可能的偏差。


本节小结。 数据污染是 LLM 评估中最严重但最难彻底解决的问题——它动摇了我们对所有基准分数的信任基础。N-gram 重叠检测是目前最实用的防线,但它无法捕捉改写、翻译和间接泄露等隐蔽形式的污染。Inverse Scaling 则揭示了 Scaling Laws 在下游任务上的非单调性:当任务目标与预训练数据的默认偏置冲突时,模型越大反而越"执拗"。这两个现象共同提醒我们,评估不是"跑个脚本看个分数"那么简单,它需要深入的方法论审视和持续的社区规范建设。