代码补全模型评估:HumanEval 与 Pass@k
为什么需要专门的代码评估基准
自然语言处理中常用的困惑度(Perplexity)或 BLEU 分数无法真实反映代码生成的质量。一个代码片段即使与参考答案文字相似度很高,但存在一个细微的逻辑错误,就完全没有用处。因此,代码补全模型需要任务导向的评估方式:给定问题描述,让模型生成代码,再通过执行测试用例来验证正确性。HumanEval 正是在这一思路下诞生的权威基准。
HumanEval 数据集解析
HumanEval 是一个手写编程问题集合,专门用于衡量从自然语言描述生成 Python 函数的能力。它不是为了考察复杂算法,而是聚焦于模型对自然语言的理解与基础编程技能的结合。
数据集结构与规模
- 问题数量:164 个手写编程问题。
- 题目组成:每个问题包含:
prompt:函数签名和文档字符串,描述需要实现的功能。canonical_solution:人工编写的标准解答。test:一组单元测试(通常 7~10 个测试用例),用于验证函数正确性。entry_point:要测试的函数名称。
- 语言:原始版本为 Python。现已扩展出多语言版本(如 HumanEval‑X)。
任务定义
模型接收仅包含函数签名和文档字符串的 prompt,需要补全函数体。以问题 HumanEval/0 为例:
from typing import List
def has_close_elements(numbers: List[float], threshold: float) -> bool:
"""Check if in given list of numbers, are any two numbers closer to each other than given threshold.
>>> has_close_elements([1.0, 2.0, 3.0], 0.5)
False
>>> has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3)
True
"""
模型需要生成完整的函数体,随后其输出会执行 test 中定义的单元测试来判断是否通过。
为什么选择 HumanEval
- 手写保证质量:题目和测试由人类精心设计,避免了大语言模型训练数据的污染。
- 测试覆盖丰富:每个问题都包含边界条件和典型场景的测试用例。
- 结果高度可解释:通过率直观反映模型解决编程任务的真实能力。
Pass@k 指标详解
Pass@k 是评估代码生成模型的核心指标,最早在 Codex 论文中形式化提出。它衡量“对于每个问题,生成 k 个候选答案时,至少有一个能通过所有单元测试的概率”。
原始定义
对于单个问题,模型生成 k 个代码样本。若其中任一通过测试,该问题即被记为一次成功。Pass@k 的计算公式为:
[ \text{pass@}k = \mathbb{E}_{\text{Problems}}\left[ 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}} \right] ]
其中:
- ( n ):对每个问题实际生成的总样本数(通常 n ≥ k)。
- ( c ):( n ) 个样本中通过测试的样本数。
- ( \binom{n}{k} ):从 n 个中选 k 个的组合数。
这个形式的计算目的是在仅使用 n 个样本的情况下,无偏地估计 pass@k。
为什么不能直接用“生成 k 次看是否通过”
直观想法是“对每个问题恰好生成 k 个样本,观察至少有一次通过的比例”。但这种方法需要为每个 k 重新采样,计算成本高。上述公式允许我们一次性生成 n 个样本(例如 n=200),然后用组合数学估计任意 k ≤ n 下的 pass@k,极大提高了评估效率。
无偏估计公式的直观解释
( 1 - \frac{\binom{n-c}{k}}{\binom{n}{k}} ) 表示:从 n 个样本中不放回抽取 k 个,“k 个全都没通过”的概率的补集。这恰好等于“至少有一个样本通过”的概率。由于我们对每个问题都计算该值,再在所有问题上求平均,就得到了无偏的 pass@k 估计量。
如何实施 HumanEval 评估
1. 安装与配置
社区广泛使用 OpenAI 开源的 HumanEval 评估工具。安装命令:
pip install -e .
确保 Python 环境为 3.7 及以上。
2. 生成样本
需要提供一个脚本,对每个问题生成 num_samples_per_task 个代码补全(通常 n=200)。生成策略一般使用核采样(nucleus sampling)且温度 T > 0 以保持多样性。模型输出需要提取纯代码部分,去除上下文无关的文本。
3. 执行评估并计算 Pass@k
运行评估脚本:
evaluate_functional_correctness samples.jsonl
samples.jsonl 每一行必须包含:
task_id:问题 ID,如HumanEval/0completion:模型生成的完整函数体代码
该命令会执行所有单元测试,并输出 pass@k 结果,例如:
pass@1: 0.472
pass@10: 0.762
pass@100: 0.908
4. 重要实施细节
- 沙盒执行:强烈建议在隔离环境(Docker)中运行测试,防止恶意代码破坏系统。
- 超时机制:对每个测试用例设置有限执行时间,防止死循环。
- 去除死代码:生成的代码可能包含无法抵达的语句,但这通常不影响正确性评估。
- 禁止数据泄露:评估数据应与训练数据严格独立,HumanEval 的手写特性一定程度上缓解了此问题。
解读 Pass@k 结果
- Pass@1 表示模型第一次回答就正确的概率。此指标反映了模型的“精准度”,对真实交互场景极为重要。
- Pass@k (k>1) 表示在多轮采样下问题被解决的概率。k 越大,模型“在大量尝试中至少碰对一次”的能力越强。但过高的 k 值会掩盖模型真正理解问题的程度。
- 实际意义:例如 Codex 12B 的 HumanEval pass@1 为 28.8%,pass@100 可达 72.3%,说明多次采样可以大幅提升问题解决率,但同时也暴露了模型首次生成质量的不足。
局限性及补充指标
- 领域覆盖窄:HumanEval 仅有 164 个 Python 问题,且以简单函数为主。不能反映复杂软件工程、API 调用、多文件项目等能力。
- 多语言场景:已涌现 MBPP、DS-1000、CodeContests 等更丰富的基准。对于多语言模型,还会用到 HumanEval‑X 这类多语言翻译版本。
- Pass@k 依赖采样策略:不同的采样参数(温度、top_p)会显著影响 pass@k,模型对比时需统一生成配置。
- 与真实使用存在差距:实际编码辅助工具中,用户依赖的是单次生成时的交互流畅性,而非多次重试。因此 pass@1 通常更受重视。
总结
HumanEval 搭配 Pass@k 为代码补全模型提供了标准化、可复现且具有客观正确性判断的评估框架。理解其运作原理和统计基础,有助于我们在模型对比、论文阅读以及实际选型中做出合理判断。
进一步学习建议:
- 复现评估流程,亲手测量开源的 StarCoder、Code Llama 等模型。
- 阅读 Codex 论文中关于 Pass@k 无偏估计的数学推导。
- 探索其他评估基准(如 MBPP、APPS),对比各自的设计异同。