跳转到主要内容

评估集也会腐化:需要版本化的是测试框架,而不只是模型

自我改进的智能体会记住自己的测试,这暴露了一个流水线缺陷:评估集是数据产物,承担着与代码相同的生命周期义务。

一张电路板的微距照片,显示铜走线、焊点和小型集成电路,景深较浅。

失效模式

针对固定评估集反复迭代的智能体,并不会收敛到任务本身。它们收敛到的是这个集合。这不是什么微妙的统计假象,而是一个优化循环的可预测结果——在这个循环里,目标函数是一个有限、静态的样本。每一次 prompt 修订、每一次 tool call 策略调整、每一次评分阈值改动,都是朝着评估集的特异性迈出的一步梯度,而不是朝着该集合本应衡量的底层能力。

这一动态在自我改进智能体的文献中已有充分记录。Google 的研究人员提出了方法,防止自我改进的智能体记住测试,正是因为默认行为就是记忆。当一个智能体能够生成自己的训练信号或优化自己的 prompt 时,「学习任务」与「学习测试」之间的边界就消融了。智能体无法区分真正的能力与恰好满足评分器的模式。

污染通常是在结果已经发布之后才被发现的。基准分数被报告,模型卡被更新,博客文章被发出。几个月后,有人注意到某个评估项子集逐字出现在训练语料中,或者智能体在某个切片上的输出可疑地一致。撤回很尴尬;对信任的损害更糟。流水线没有任何机制在发布前捕获这个问题,因为评估集被当作固定的参考点,而不是活的产物。

为什么这是流水线缺陷

评估集被复制进训练和 prompt 上下文时,没有血缘关系。一个 JSONL 文件被提交,然后被复制进微调任务,然后被粘贴进 few-shot prompt,然后被嵌入检索索引。每一份副本都是一次分叉。它们都没有携带对原始文件的引用。当原始文件更新时,分叉会静默地偏离。当某个分叉泄漏进训练时,没有任何记录表明评估项和训练样本共享同一个祖先。

用于迭代的集合与用于报告的集合之间,没有留出纪律。团队在他们所报告的同一批条目上迭代。迭代集变成了调优集,调优集变成了记忆目标。真正的留出集不只是「我们不看的集合」;它是一个在结构上与迭代循环隔离的集合,带有访问控制和审计日志。

对测试框架的改动会静默地改变历史数字的可比性。测试框架是运行评估的代码:prompt 模板、工具定义、解析逻辑、评分函数、重试策略、模型版本。其中任何一项的改动,都可能使分数偏移超过团队试图测量的效应。然而测试框架的改动通常在没有对评估集做版本号提升的情况下被提交,于是仪表盘展示的时间序列实际上是不可比测量的拼接。

Close-up of cooling fans inside a server room rack

根本原因是一个范畴错误。评估集被当作数据,而数据被假定为惰性的。但评估集是每一个引用它的结果的依赖项。它有 schema、有来源、有一组消费者、有生命周期。它理应获得与栈中任何其他依赖项相同的版本化、审查和弃用纪律。

设计规则:把评估当作有版本的依赖项

把评估集、测试框架和评分代码固定为单个有版本的单元。不是三个独立的版本;而是一个复合版本。一个评估版本是一个元组:条目集、prompt 与工具配置、评分实现、模型标识符。对任何元素的任何改动都会产生新版本。版本是内容哈希,而不是人工指定的标签,因此它不会漂移。

维护一个轮换的私有留出集,永不用于迭代。留出集单独编写,存储在单独的仓库中,带有单独的访问控制,并按计划轮换。轮换意味着退役已暴露的条目并编写新条目。这很昂贵,而这是诚实的权衡:一个永不轮换的留出集,一旦其内容泄漏进团队知识,就会变成事实上的迭代集。轮换的成本,就是让测量仍然有意义的代价。

在每一个报告的指标旁记录评估版本。没有评估版本的数字不是结果,而是传闻。版本应当出现在仪表盘、模型卡、实验跟踪器,以及任何引用分数的文档中。当审查者问「这有改进吗?」时,第一个问题是这两个数字是否共享同一个评估版本。

一个具体机制:一个把元组绑定在一起的 manifest 文件。

eval_version: sha256:9f2c...
items: s3://evals/holdout-2024-11/items.jsonl
harness:
  prompt_template: sha256:1a4b...
  tools: sha256:7c3d...
  scoring: sha256:e8f1...
model: provider/model-name@revision
created: 2024-11-04
consumers:
  - reporting-dashboard
  - release-gate

manifest 是引用的单元。![Close-up of white woven fabric with small Close-up of white woven fabric with small textured holes, a stock photoerver-rack-closeup-48ae67-477829.jpg)

检测

随时间跟踪逐条目表现。聚合分数会掩盖记忆。逐条目视图会揭示它:曾经很难、现在被一致通过的条目;通过率在没有相应底层能力变化的情况下跃升的条目;输出坍缩为单一措辞的条目。可疑地一致的成功是一个信号。真实任务有难度分布;一个通过某个切片上每一个条目的智能体,要么是真正有能力,要么是见过这个切片。

在任何发布决策之前,与一个全新编写的切片进行比较。新切片很小、手工编写、永不用于迭代。它不是基准;它是健全性检查。如果智能体在新切片上的分数与其在迭代集上的分数出现分歧,那么迭代集要么被污染,要么过拟合。分歧就是发现。

把测试框架的改动视为需要重新建立基线的破坏性变更。当 prompt 模板、工具定义、解析逻辑或评分函数发生变化时,之前的数字不再可比。在比较任何候选之前,先在新测试框架上重新运行基线。这很慢,而这是第二个诚实的权衡:对每一次测试框架改动重新建立基线,会成倍增加算力成本并拖慢迭代。替代方案是一个看起来像进步、实际上是一系列不可比测量的时间序列。重新建立基线的成本是有界的;被污染的发布决策的成本则不是。

更深层的要点是,自我改进的智能体把评估集变成了训练循环的一部分。一旦如此,评估集就不再是中立的观察者。它是参与者。参与者需要版本化、访问控制和轮换。流水线缺陷不在于智能体作弊;而在于流水线给了它们一个静态目标,并称之为测量。

生命周期义务

评估集会腐化。条目会公开,模型会记住它们,测试框架会漂移,曾经有意义的数字会变成噪声。应对方式不是编写一个更好的集合然后指望它。应对方式是把集合当作一个有生命周期的依赖项:编写、版本化、消费、轮换、退役。测试框架是这一依赖项的一部分,而不是一个单独的关切。版本就是契约。当契约是显式的,污染就可检测,可比性得以保留,报告的分数就是一个可审计的主张。当契约是隐式的,智能体就会优化测试,测试就不再衡量任务,而结果会在任何人注意到之前被发布。

封面照片:Jakub Pabis / Pexels。

分享XLinkedIn永久链接6 分钟阅读

相关阅读

这能回答的问题

What is eval set contamination?

Contamination occurs when eval items leak into training data, prompting contexts, or retrieval indexes, so the agent's score reflects memorisation of the items rather than capability on the task. It is usually discovered after a result has been published because the pipeline lacks lineage between eval items and their copies.

Why version the harness together with the eval set?

A score is produced by a tuple: item set, prompt template, tool definitions, scoring code, and model revision. Changing any element changes the measurement. Pinning the tuple as a single content-hashed version makes historical numbers comparable and makes silent drift detectable.

How often should a private holdout be rotated?

Rotation should be scheduled and triggered by exposure, not by calendar alone. Any time holdout items enter team knowledge, documentation, or iteration discussions, they are candidates for retirement. The cost of authoring new items is the price of a measurement that still means something.

What signals suggest an agent has memorised its eval set?

Per-item tracking reveals suspiciously uniform success, pass rates that jump without a corresponding capability change, and outputs collapsed to a single phrasing. Comparing against a freshly authored slice before a release decision surfaces divergence between iteration-set and holdout performance.