164 道题考倒大模型:HumanEval 为什么是代码能力的照妖镜

数据集速览卡

数据集名称HumanEval
发布机构OpenAI
发布时间2021 年 7 月(arXiv 2107.03374,Codex 论文)
规模164 道 Python 编程题
许可证MIT
用途定位代码生成模型的「功能正确性」评测基准,pass@k 指标

背景:BLEU 分数测不出「代码能不能跑」

2021 年之前,代码生成模型的评测有个尴尬的问题:大家还在用文本生成的指标来评代码。BLEU 这类指标的思路是「对比模型生成的代码和参考答案在字符层面有多像」,但这套用在代码上基本是错的——一段代码即使和参考答案长得完全不一样,只要它能正确运行、通过测试,就是好代码;反过来,和参考答案字面相似度极高、但有一个变量名写错导致跑不起来的代码,Bleu 分数可能很高,实际却毫无用处。

问题的本质是:代码的正确性,取决于「能不能运行并产出正确结果」,而不是「像不像参考答案」。这是一个「功能正确性」(functional correctness)的问题,文本指标完全抓不住。

OpenAI 在 Codex 论文里正面回应了这个问题。他们引入了 HumanEval 这个评测集,并用一个全新的指标 pass@k 来衡量「功能正确性」。这个思路后来成了整个代码生成评测领域的基石,HumanEval 也成了这个领域最经典、被引用最多的基准之一。

数据构成与规模:164 道题,每题都有测试用例

HumanEval 包含 164 道人工手写的 Python 编程题。每一道题的结构都很统一,由两部分组成:

  • 函数签名 + 文档字符串(docstring):题目以一个函数定义开头,函数名、参数、返回值类型都给定,函数体留空,上面是描述这个函数应该做什么的英文文档字符串。模型的任务,就是根据这个签名和文档字符串,把函数体填出来。
  • 单元测试用例:每道题都配套了若干单元测试(test cases),用来验证模型生成的代码是否正确。论文里提到,平均每道题约有 7.7 个测试用例,这些测试用例覆盖了正常输入、边界情况和一些需要推理的 tricky 情况。

这种「函数签名 + docstring + 单元测试」的结构设计得非常巧妙。它把「代码生成」这个开放问题,规约成了一个边界清晰、可自动判定的任务:模型给出函数体,跑一遍测试用例,全过就是正确,有一个不过就是错。评测过程完全自动化、客观、可复现,不像早期一些基准需要人工判断代码质量。

164 道题的难度分布是经过精心设计的:有简单的字符串处理、列表操作,也有需要算法思维的中等题,还有少量涉及动态规划、递归等较难的题。这种分层让基准既能区分「弱模型」之间的差距,也能在模型变强后继续提供区分度——虽然今天最强的模型已经快把 HumanEval 刷到接近满分,暴露了它的「天花板」问题,这个后面细说。

评测方法:pass@k 是怎么算的

pass@k 是 HumanEval 引入的核心指标,理解它很重要。它的定义是:让模型对每道题生成 k 个代码样本,只要这 k 个样本里有至少一个能通过全部测试用例,这道题就算「过」;pass@k 就是「过的题数 / 总题数」。

比如 pass@1,就是让模型每道题只生成 1 个答案,看这个答案能不能通过测试,通过率就是 pass@1。pass@10、pass@100 则是每道题生成 10 个、100 个答案,看「多试几次能不能成功」。

为什么要设计 k 这个参数?因为代码生成有个特性:模型不是每次都生成对的,但「多采样几次」往往能命中正确解。pass@k 捕捉的正是这种「采样越多、成功概率越高」的行为,它对评估「模型 + 后续验证/筛选」的完整流程很有意义。

论文里的经典数字(逐字记录):Codex 在 HumanEval 上 pass@1 是 28.8%;作为对比,GPT-3 是 0%,GPT-J 是 11.4%。而当允许每道题采样 100 个答案时,Codex 的 pass@100 达到了 70.2%。这组数字漂亮地说明了两个结论:一是专门用代码训练的 Codex 远超通用模型 GPT-3(0% 到 28.8%);二是「重复采样」是提升代码生成成功率的有效策略(28.8% 到 70.2%)。

还有一个技术细节值得了解:pass@k 的无偏估计。如果 k 很大、每道题都真跑 k 个样本,算力开销巨大,所以论文用了「从 n 个样本里取 k 个」的无偏估计公式来降低评测成本,这个细节对做评测的人很重要,也是后来很多评测框架沿用的做法。

题目长什么样:一个具体例子

为了直观理解 HumanEval 的评测方式,看一道典型的题目。题目结构大致是这样(以一道经典的字符串处理题为例):

def has_close_elements(numbers, threshold):
    # Check if in given list of numbers,
    # are any two numbers closer to each other than
    # given threshold.
    ...  # 模型需要补全这里

模型看到的,就是这个函数签名、参数类型和文档字符串。它需要生成函数体,实现「检查给定数字列表中,是否存在任意两个数字的差值小于给定阈值」这个功能。配套的测试用例可能是:

assert has_close_elements([1.0, 2.0, 3.0], 0.5) == False
assert has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3) == True

模型生成的代码会被放进这个函数体,然后逐个跑测试用例,全部通过才算这道题正确。这个例子体现了 HumanEval 题目的几个特征:一是文档字符串用英文写,描述功能但不直接给算法,模型需要自己「读懂需求」再「设计实现」;二是测试用例覆盖了正常情况和边界情况(比如空列表、重复元素、浮点精度等);三是有部分题目故意埋了「陷阱」,需要模型真正理解题意而非套模板。

这种「读 docstring → 写实现 → 跑测试」的评测闭环,让分数真实反映了模型的「从需求到可运行代码」的能力,而不是「背答案」的能力(至少在数据污染之前是这样)。

pass@k 的技术细节:无偏估计为什么重要

pass@k 的定义很直观,但工程实现里藏着一个重要的统计细节——无偏估计。

如果严格按定义做 pass@100,意味着每道题都要让模型生成 100 个样本、跑 100 次测试,164 道题就是 16,400 次生成 + 执行,算力和时间开销很大。而且当 k 很大时,如果只采样恰好 k 个样本,估计的方差会很大。

Codex 论文的解法是:对每道题实际生成 n 个样本(n ≥ k),然后从这 n 个里随机抽取 k 个,统计「这 k 个里是否有正确的」,用组合公式做无偏估计。具体来说,如果 n 个样本里有 c 个是正确的,那么 pass@k 的无偏估计是:1 减去「从 n 个里抽 k 个、恰好全抽到错误样本」的概率,即 1 - C(n-c, k) / C(n, k)

这个公式的精妙之处在于:它用一个更大的采样量 n,同时估计出任意 k 值下的 pass@k,而不需要为每个 k 单独重新采样。这大大降低了评测成本,也让不同论文报告的不同 k 值之间可以互相比较。这个「n 采样、组合估计」的做法,后来被几乎所有代码评测框架(包括 Hugging Face 的评测、HumanEval+ 等)沿用,成为代码评测的标准工程实践。

理解这一点,对读论文很有帮助:当你在论文里看到「我们在 HumanEval 上达到 pass@1 = 74.4%」这样的表述时,背后的数字就是通过这种无偏估计方式算出来的,而非真的每题只采一次。

数据污染的深层问题

HumanEval 的「数据污染」问题值得单独展开,因为它触及了现代大模型评测的一个根本困境。

所谓数据污染,是指评测集的题目(或其答案)混进了模型的训练数据里。一旦发生,模型在评测集上的高分就不反映真实能力,而是「背过了答案」。HumanEval 因为太经典、被引用太多,它的 164 道题和标准答案早已遍布 GitHub、博客、教程,几乎所有后来训练的大模型,训练语料里都极可能包含了这些内容。

污染的直接后果是「分数虚高」。一个模型报告 90% 的 pass@1,可能有一半是因为「见过这道题」。这带来两个严重问题:一是不同模型的分数难以公平比较(有的模型训练数据里恰好没混入,天然吃亏);二是研究者会误判「代码能力」的真实进展。

检测污染的方法通常有:对比模型在「已知污染」题目和「全新题目」上的表现差异、用 n-gram 重叠检测、或者用改写过的变体题目(如 HumanEval+ 的加强版)。但本质上,只要评测集是公开的、固定的,污染就几乎无法根除。这也是为什么社区越来越倾向于使用「定期更新、动态生成」的基准(如 LiveCodeBench),来对抗污染问题。

Codex 模型的背景:HumanEval 的「主角」

HumanEval 是 Codex 论文的一部分,理解 Codex,才能理解为什么 HumanEval 会被设计成这个样子。

Codex 是 OpenAI 在 GPT 系列基础上,用大量公开代码(主要来自 GitHub)微调出来的「代码专用」模型。它的定位非常明确:让模型学会「根据自然语言描述写代码」。论文里的 Codex 是 GitHub Copilot 的技术底座——后来 Copilot 成为全球开发者最常用的 AI 编程助手,Codex 正是它的前身。

Codex 的出现,让「代码生成」从研究热点变成了实际产品。而评测这样一个模型的代码能力,就成了一个必须解决的问题——这正是 HumanEval 诞生的直接原因。因为 Codex 的能力核心是「从需求(docstring)生成可运行代码」,HumanEval 的「函数签名 + docstring + 单元测试」设计,几乎就是为 Codex 量身定制的评测方式。

论文里的那组数字(Codex 28.8%、GPT-3 0%、GPT-J 11.4%)之所以震撼,是因为它第一次用「功能正确性」这把尺子,清晰量出了「代码专用模型」和「通用模型」之间的巨大鸿沟。GPT-3 是个强大的通用模型,但在「写能跑的代码」这件事上,它一题都做不对——这生动地说明了「专门训练」的价值。

题目的类型分布与设计逻辑

HumanEval 的 164 道题不是随机凑的,它们覆盖了编程能力的多个维度,设计上有明确的逻辑。

从题型看,题目大致可以分为几类:字符串与文本处理(拼接、查找、替换、格式化)、列表与数组操作(遍历、排序、去重、切片)、数学与数值计算(质数判断、进制转换、数列生成)、算法与数据结构(递归、动态规划、贪心、树和图的遍历)、逻辑与条件判断(复杂的 if/else 分支、边界处理)等。

这个分布的意义在于:它既覆盖了「基本功」(字符串、列表这些日常编程高频操作),也覆盖了「进阶能力」(算法、递归这些需要真正推理的内容)。这让 HumanEval 能同时测出「模型会不会写基础代码」和「模型有没有真正的算法思维」两个层次。

还有一个细节值得注意:HumanEval 的题目里,有不少是「看起来简单、实际有陷阱」的。比如要求处理浮点数的精度、处理空输入、处理负数边界等。这些「陷阱」专门用来区分「真正理解题意」和「只会套模板」的模型——后者往往在边界情况上栽跟头。这也是 HumanEval 虽然只有 164 道题,却能有效区分模型能力的原因。

下载与使用

HumanEval 的数据集文件在 OpenAI 的 GitHub 仓库里开源,最经典的位置是 openai/human-eval 这个仓库(MIT 许可证),里面包含 HumanEval.jsonl.gz 数据文件和配套的评测脚本。

评测脚本 evaluate_functional_correctness.py 设计得很实用:它会读取模型生成的结果,在沙箱里执行每道题的单元测试,输出 pass@k 分数。使用时要注意,代码执行涉及安全风险(模型生成的代码可能包含恶意操作),官方脚本默认用了沙箱机制,自己跑的时候也建议在隔离环境里做。

在 Hugging Face 上也有 openai/openai_humaneval 等镜像,datasets 库可以直接加载题目和测试用例。现在 HumanEval 已经是几乎所有代码模型发布时必测的基准,各大模型排行榜(如 Open LLM Leaderboard 的代码部分)都会报告它的分数。

许可证与商用注意

HumanEval 的数据和评测脚本使用 MIT 许可证,非常宽松,商用、修改、再分发都允许。这也是它能被如此广泛采用的原因之一——没有任何许可障碍,学术界和工业界都可以无障碍地使用它作为评测标准。

横向对比同类基准

  • 对比 MBPP:MBPP(Mostly Basic Python Problems)是 Google 同期发布的另一个代码基准,约 974 道题,侧重基础的 Python 入门题,题量更大、更简单。HumanEval 题少但更难、更强调算法推理。两者常被一起用来评测,一个测基础、一个测推理。
  • 对比后来的 HumanEval+(HumanEvalPlus):HumanEval 的一个已知问题是测试用例不够严格,有些「假阳性」——模型生成的代码能过官方测试,但实际是错的。HumanEval+ 用更强的测试用例(更多、更严)来暴露这个问题。这说明 HumanEval 的原始测试集偏弱,分数有水分。
  • 对比 SWE-bench:HumanEval 测的是「给定函数签名写函数」,是单函数级别;SWE-bench 测的是「给定真实 GitHub issue 修整个仓库的 bug」,是工程级别。两者难度和粒度完全不同,HumanEval 是入门基准,SWE-bench 是进阶基准。

局限与争议

HumanEval 最突出的问题是天花板太低、已经「饱和」。2021 年 Codex 才 28.8%,但到 2024-2025 年,最强模型已经逼近 95% 甚至更高。一个接近满分的基准,就无法再区分「顶尖模型之间」的差距了。这是所有评测基准都会面临的「被刷爆」问题,HumanEval 因为太经典、被优化得最狠,饱和得也最快。

第二是测试用例偏弱,存在假阳性,前面提到的 HumanEval+ 就是针对这个问题的修正。

第三是题量小、语言单一。164 道题、只有 Python,统计上的方差较大,也测不了多语言能力。后来的 MultiPL-E 等工作把 HumanEval 翻译扩展到了多种编程语言,部分缓解了这个问题。

第四是数据污染风险。HumanEval 的题目早已遍布互联网,很多模型的训练数据里可能已经混入了它的题目和答案,导致评测分数虚高。这是所有公开基准都难以避免的问题。

总结与启示

HumanEval 的历史地位,在于它用一个干净的「函数签名 + 单元测试 + pass@k」设计,把「代码生成能力」从一个模糊的、靠人判断的概念,变成了一个可自动、可复现、可对比的硬指标。它定义了这个领域的评测范式,pass@k 至今仍是代码生成的标准度量。

同时它的「饱和」也提醒我们:评测基准是有生命周期的,会被模型「做穿」。这正是评测研究需要不断推陈出新、不断做更难基准(如 SWE-bench、LiveCodeBench)的原因。对做评测的人,HumanEval 是最好的入门课;对追求前沿的人,它已经只是一块垫脚石。

参考来源

164 道题考倒大模型:HumanEval 为什么是代码能力的照妖镜

主要菜单