ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SciCode-Verified:修复基准测试缺陷后LLM科学编程分数数倍提升

SciCode-Verified:修复基准测试缺陷后LLM科学编程分数数倍提升 先说结论一个用来评估 LLM 科学编程能力的基准测试自己也可能“挂科”。SciCode 原本是不少团队衡量大模型科学编程能力的常用基准但最新的修正研究 SciCode-Verified 发现原版测试用例里存在不少缺陷头部模型在修正后的准确率提升了数倍。换句话说排行榜上的低分并不完全说明模型不会做科学编程很可能只是评测逻辑先出了问题。这篇文章要讨论的不只是“一个论文改了一个 benchmark”。它牵出了一个更根本的问题我们用来给大模型打分的测试代码本身有没有 bug在软件工程里代码要有单元测试、要有回归测试但很少有人用同样的标准去审查 benchmark 的测试用例。SciCode-Verified 做的事情本质上就是给 benchmark 补上了一次系统性的质量审计。读完这篇文章你会理解三件事SciCode 和 SciCode-Verified 之间到底发生了什么基准测试缺陷通常长什么样、为什么会存在以及你在自建 LLM 评测集、做模型选型时怎么样避免被有缺陷的评测数据牵着鼻子走。1. 这篇研究真正在质疑什么SciCode 是 2024 年发布的一个科学编程基准测试目标很明确评估大模型能不能完成大学水平、面向真实科研场景的编程任务。它包含 338 道科学编程问题覆盖生物、化学、物理、金融数学等 16 个研究子领域。每道题不是让模型写一个“Hello World”而是给出科学背景、问题描述和起始代码要求模型生成一段能完成特定科学计算的 Python 代码最后用隐藏测试用例来判定正确性。SciCode-Verified 则是对 SciCode 做的一次系统性修正。研究者的做法本质上和给开源项目提 bug 修复 PR 没有区别逐条检查测试用例是否真的能正确判定模型输出把有问题的测试用例找出来修复把修不了的问题移除然后再用修正后的测试集重新跑一遍主流 LLM。结果与之前相比差距非常大。这篇研究真正在质疑的是 LLM 评测的“底座质量”。我们习惯把 benchmark 分数当作模型能力强弱的客观证据却很少问一句那些用来打分的测试用例本身是正确的吗SciCode-Verified 给出的答案是不一定而且缺陷的影响可能远超预期。从工程角度看这个案例可以类比为“你用一把没校准的尺子去测所有产品的长度最后所有结论都建立在系统误差之上”。如果评测集本身不可靠排名、选型、模型对比、论文结论都会失真。所以这个问题不只是学术圈要关心凡是依赖 LLM 排行榜做技术决策的开发者都应该理解背后的风险。2. 先把概念对齐LLM 评测、基准测试与测试用例在展开缺陷类型之前先把几个基础概念对齐。LLM 评测的核心是“用一组固定任务给模型能力打分”。一个评测方案至少包含两部分任务数据也就是输入给模型的题目以及评测逻辑也就是判断模型输出好坏的规则。对于编程类任务评测逻辑往往表现为“在隔离环境里运行模型生成的代码然后跑一组隐藏测试用例”。基准测试Benchmark就是把上面这套东西固化成标准产品让不同模型可以在同一条件下比较。SciCode 作为科学编程基准它的结构是这样的每个问题包含问题陈述、科学背景、起始代码、参考实现和测试用例。评估时分两步第一步让模型从科学背景中选择有用信息第二步基于问题描述生成 Python 代码。最终分数由隐藏测试用例的通过率决定。这里最容易被忽略的关键点是测试用例本身是一段代码。代码就会有写错逻辑、写错期望值的可能。如果测试用例的期望输出算错了或者断言互相冲突那么无论模型生成了多正确的代码评测结果都是错的。SciCode 原版之所以能产生误导恰恰是因为大家默认“官方发布的测试用例”是可信的而这种默认在软件工程里恰好是最危险的事情。概念说明在 SciCode 中的角色基准测试一组任务 评分规则338 道科学编程任务参考实现开发者编写的“标准答案”用于生成期望输出隐藏测试用例对模型输出做判定的断言集决定模型是否通过评测脚本执行测试并汇总分数的程序运行测试并计算准确率当有人告诉你“某个模型在 SciCode 上只有几个点”的时候先不要急着得出“模型能力不行”的结论。你需要先确认那套测试用例本身可靠吗SciCode-Verified 的价值就是替我们把这道确认工序做了并且做得很彻底。3. SciCode 原版的缺陷类型拆解SciCode-Verified 在修正过程中把发现的问题归结成两大类可解决性缺陷和可判定性缺陷。理解了这两个词也就理解了编程类 benchmark 最常见的病根。3.1 可解决性缺陷测试用例设计层面的正确性问题第一类缺陷可以理解为“测试用例本身设计错误”。它有三个典型表现。第一种是缺少测试用例。一个问题如果只覆盖了少数理想输入没有覆盖边界条件、异常输入、数值稳定性等关键分支那么一个只对特定输入有效的“投机实现”也能通过测试而真正稳健的实现反而可能因为某个分支没有走到无法体现出优势。更严重的情况是某些问题根本没有对应的有效测试用例模型代码写对写错完全无法判断。第二种是冲突测试用例。同一道题内的多个隐藏测试对同一个输入给出了不同的期望值或者断言逻辑互相矛盾。在这种情况下模型无论怎么实现都不可能同时满足所有测试分数必然被拉低。这不是模型的能力问题而是测试集合自相矛盾。第三种是误导性测试用例。测试的期望输出是以一个有问题的参考实现为准生成的参考实现本身算错了测试期望值也跟着错。模型写出了更正确的实现反而被测试判成错误。这正是“能力被低估”的最直接来源也是修复后分数大幅提升的主要原因。3.2 可判定性缺陷测试根本没有真正执行第二类缺陷更隐蔽测试用例设计上看着合理但在实际运行环境中根本无法正常执行。最典型的是编译或导入失败被测代码无法被加载测试脚本自然无法正常工作。还有不可实例化的问题测试依赖的类或函数因为参数错误、初始条件不满足而无法创建对象。另外还有环境依赖缺失的问题测试需要的外部库、数据文件或系统配置不存在导致用例在隔离容器里直接跑不起来。当测试用例无法运行时评测框架统计出来的“未通过”并不等于“模型代码错误”它可能只是表明测试环境没有准备好。但不少评测框架不会区分这两种失败而是统一折算为错误造成模型分数虚低。缺陷层级具体类型排查特征对模型分数的影响可解决性缺少测试覆盖不足无法区分实现好坏漏判或错判可解决性冲突测试同一输入存在多种期望值所有模型无差别失败可解决性误导测试期望值来自错误参考实现正确实现被判错可判定性编译或导入失败被测代码无法加载判错但原因不在模型可判定性无法实例化测试对象创建失败判错但原因在测试可判定性环境依赖缺失外部依赖或数据不存在判错但原因在环境4. SciCode-Verified 的修复方法论SciCode-Verified 的修复思路一句话概括就是把基准测试当作一个被测系统给它做回归测试和 bug 修复。整个流程大致分五步。第一步是人工审计研究者逐问题、逐测试用例阅读确认每个期望值是否有可靠依据。第二步是缺陷标记对每一条测试用例记录它是否存在缺陷、属于哪一类。第三步是修复对可以修复的测试用例修正期望值、补充缺失的断言、消除冲突。第四步是移除对无法确定正确行为或无法修复的问题直接剔除。第五步是重测用修正后的测试集合重新评估一组主流 LLM。这里的修复原则和普通单元测试完全一致一组有效的测试用例必须同时满足两个条件。第一正确实现能够全部通过第二错误实现至少有一个失败。如果一条测试用例对正确实现和错误实现的判定没有区分度它就不应该被放进评测集。这个原则说起来简单但很多基准测试在发布时恰恰没有做这一步验证。SciCode-Verified 最值得借鉴的地方是把“评测集本身的正确性”纳入了评估流程。它没有停留在“修复完分数提高了”这个结论而是把修复过程和修正版本公开让后续研究者可以基于修正后的子集重新比较模型。这种做法相当于给 benchmark 也建了版本管理和回归测试。如果你想亲自感受原版与修正版的差异可以获取官方仓库后对照文档自行复现git clone https://github.com/scicode-bench/SciCode.git cd SciCode进入仓库后具体评估脚本和依赖安装方式以官方 README 为准。这里不过度展开因为本文的重点不是教你复现论文实验而是把评测集质量审计的方法拆出来迁移到自己的项目里。5. 修复后的结果变化低估到底有多严重分数变化是最直观的证据。根据论文公开数据原先在 SciCode 上表现很低的模型在 SciCode-Verified 修正后的测试集上准确率出现数倍增长。例如 GPT-4o 从大约 4% 提升到大约 34%Claude 3.5 Sonnet 从大约 8% 提升到大约 50%。具体数值请以论文原文为准但量级方向是明确的大量此前被判错的回答实际上是正确的。这意味着什么说明原版基准测试把很多正确代码当成了错误代码。模型并不是不理解科学问题而是测试逻辑本身有缺陷。一个分数上的倍数级提升展示的是基准测试质量对模型能力评估的决定性影响。排名层面的影响同样需要关注。分数整体上移之后不同模型的相对位次也可能发生变化。如果你过去根据原版 SciCode 的排行榜来决定“哪个科学编程模型更强”那么你的判断可能建立在有缺陷的数据之上。更麻烦的是这种缺陷不是均匀分布在所有模型上的它与模型生成代码的风格、注释习惯、命名方式都有关因此会造成系统性的偏差而不是简单地在所有分数上加一个常数。当然这里要客观看待SciCode-Verified 修正的是测试用例不是模型。它不等同于“把模型分数刷高”而是让评测更接近真实能力。这件事给所有评估者的提醒是每当你看到一份 benchmark 分数都应该追问一句——这个分数在多大程度上取决于测试用例本身的正确性6. 为什么会出现这么多缺陷修复之后一个很自然的问题是为什么原版基准会埋下这么多缺陷从技术原理上分析大致有四个原因。第一测试用例常常“照抄”参考实现。构造基准测试时开发者通常先写一个 gold solution再根据它生成测试用例。这看起来合理但风险在于参考实现本身的错误会被原样复制进期望值。测试用例不是在验证“正确答案”而是在验证“参考答案”。一旦参考答案有 bug所有依赖它的测试都会跟着错。第二科学编程任务本身是开放式的。科学计算问题往往存在多个合法实现数值精度、算法选择、边界处理方式都会影响最终输出。用一组固定的精确断言来做自动判定很容易误伤实际正确但细节不同的实现。原版测试用例对输出格式和精度的假设过于刚性也是造成误判的重要原因。第三基准测试缺少“回归测试”。软件工程的常识是代码要配套测试。但 benchmark 代码本身往往没有配套测试。发布前很少有人用“正确实现必须通过、错误实现必须失败”的标准去验证测试用例的质量。SciCode-Verified 做的事情本质上就是补上这一课。第四评测规模扩大后人工审核成本过高。338 道问题、每道问题多条测试用例逐条人工校验的代价非常大。如果没有自动化辅助工具缺陷就很容易在发布前漏网。这四个原因在你自建评测集时同样存在。所以 SciCode-Verified 不只是一篇论文里的案例更是一套可以复用的方法论。7. 动手实践如何审计自己的评测集这一节把前面的分析落到代码。无论你是做 LLM 应用开发还是维护一个内部评测集都可以用下面三类检查来验证测试套件是否可靠。7.1 冒烟测试验证测试用例具备区分度核心原则只有一句正确实现必须通过错误实现必须失败。如果错误实现也能全部通过说明测试覆盖不够如果正确实现反而失败说明测试期望值可能有误。下面这个脚本可以对一组测试用例做基本的区分度检查。# benchmark_smoke_test.py import subprocess import sys from pathlib import Path def run_solution(solution_path: Path, test_path: Path, timeout: int 30) - bool: 运行单个解决方案和单个测试文件返回是否通过。 cmd [sys.executable, str(test_path), str(solution_path)] try: result subprocess.run(cmd, capture_outputTrue, textTrue, timeouttimeout) except subprocess.TimeoutExpired: return False return result.returncode 0 def check_distinguishability(solution_groups: dict, test_paths: list[Path]) - dict: solution_groups 示例: { correct: [正确实现路径列表], wrong: [错误实现路径列表], } 规则: 正确实现应通过全部测试, 错误实现应至少失败一个测试。 report {} for group, solutions in solution_groups.items(): for sol in solutions: passed_count 0 for test in test_paths: if run_solution(sol, test): passed_count 1 total
返回列表