Is Your Code Generated by ChatGPT Really Correct? Rigorous Evaluation of Large Language Models for Code Generation


TL;DR

本文提出了一个名为 EvalPlus 的代码合成评估框架,通过大规模自动生成测试用例(结合 LLM 和变异测试策略)来严格评估大语言模型(LLM)生成代码的真实功能正确性,揭示了现有基准(如 HumanEval)因测试不足而严重高估了模型的性能。

关键定义

相关工作

当前,评估大语言模型代码生成能力的主流方法(SOTA)是使用编程基准,如 HumanEval。这些基准提供一系列编码问题和手动编写的测试用例,通过 \(pass@k\)(即生成 k 个解中至少有一个通过所有测试的概率)等指标来衡量模型性能。

然而,这些现有基准存在关键瓶颈:

  1. 测试不足 (Insufficient testing):每个问题的测试用例平均数量很少(通常少于10个),且测试场景过于简单,难以覆盖代码的各种功能和边缘情况。这导致逻辑上错误的代码也能通过所有测试,从而被错误地判定为正确。
  2. 问题描述不精确 (Imprecise problem description):任务的自然语言描述往往很模糊,没有明确定义输入域或异常处理方式,导致 LLM 可能对程序功能做出与测试意图不符的解读。

因此,本文旨在解决的问题是:如何克服现有基准测试不充分的缺陷,从而能够更严格、更准确地评估 LLM 生成代码的真实功能正确性,避免对模型能力的错误高估。

图1:由 ChatGPT 为 HUMANEVAL #58 生成的错误代码示例

本文方法

EvalPlus 框架的核心是一个自动测试输入生成引擎,旨在通过增强现有代码基准来精确评估 LLM 生成代码的函数正确性。其工作流程如下图所示。

图2:EvalPlus 概览

自动化测试输入生成

该过程结合了 LLM 和传统的变异测试方法,分为两个阶段:

  1. 通过 ChatGPT 初始化种子输入:首先,EvalPlus 构建一个提示(Prompt),其中包含问题的参考正确实现(ground-truth solution)、现有数据集中的示例测试输入以及鼓励生成“有趣”输入的特定指令。利用 ChatGPT 强大的代码理解能力,生成一批高质量的、旨在测试困难角落和边缘情况的种子输入。这些输入在格式上是有效的,并且能满足一些传统生成器难以处理的语义约束(例如,输入必须是回文)。

  2. 类型感知输入变异:由于直接用 LLM 大规模生成测试成本高且速度慢,EvalPlus 以 ChatGPT 生成的种子输入为起点,进行类型感知的输入变异。该方法遵循典型的基于变异的模糊测试(fuzzing)工作流,对种子输入进行随机选择和变异,以高效生成海量新测试。变异操作会考虑数据的类型,如下表所示。例如,对整数进行加/减一操作,对列表进行元素移除、重复或递归变异。这种方法能够保持输入结构有效性的同时,大规模扩展测试用例的数量。

类型 变异规则 类型 变异规则
int | float 返回 \(x±1\) List 移除/重复随机项 \(x[i]\)
插入/替换 \(x[i]\) 为 \(Mutate(x[i])\)
bool 返回一个随机布尔值 Tuple 返回 \(Tuple(Mutate(List(x)))\)
NoneType 返回 \(None\) Set 返回 \(Set(Mutate(List(x)))\)
str 移除/重复一个子字符串 \(s\)
用 \(Mutate(s)\) 替换 \(s\)
Dict 移除键值对 \(k→v\)
更新 \(k→v\) 为 \(k→Mutate(v)\)
插入 \(Mutate(k)→Mutate(v)\)

最终,所有生成并通过有效性检查的测试用例都将用于通过差分测试(differential testing)来评估 LLM 生成的代码,即将其输出与参考实现的输出进行对比。

测试套件规约

为了在保持高测试强度的同时降低评估成本,EvalPlus 提供了一个可选的测试套件规约(Test-Suite Reduction)功能。该问题被形式化为集合覆盖问题(set covering problem),目标是找到一个最小的测试子集,使其能够满足与完整测试集相同的测试要求。本文定义了以下三种测试要求:

  1. 代码覆盖率 (Code coverage):保留能够达到与完整测试集相同分支覆盖率的最小测试子集。
  2. 变异体杀死 (Mutant killings):通过对参考代码进行微小修改(如将 \(<\) 改为 \(≤\))创建大量人造的错误程序(变异体),保留能够“杀死”(即检测出)与完整测试集相同数量变异体的最小测试子集。该指标比代码覆盖率更能评估测试的缺陷检测能力。
  3. LLM 样本杀死 (LLM sample killings):根据经验,保留能够检测出由其他 LLM 生成的已知错误代码样本的最小测试子集。

此外,本文还探索了合并上述所有三种要求的策略。

程序输入契约

为了解决原始问题描述模糊性的问题,本文引入了“按契约编程”的思想,通过代码断言(assertions)的形式为每个函数手动标注输入前置条件。这样做有双重好处:

  1. 在测试生成阶段,它可以自动过滤掉所有不满足契约的无效输入,避免产生误报。
  2. 这些契约可以作为对自然语言描述的补充,为 LLM 提供更清晰、无歧义的功能约束。

实验结论

本文基于 EvalPlus 框架构建了 HumanEval+ 和 HumanEval+-MINI 数据集,并对 26 个主流 LLM 进行了广泛评估。

  平均测试数 中位测试数 最小测试数 最大测试数 任务数
HUMANEVAL 9.6 7.0 1 105 164
HUMANEVAL+ 764.1 982.5 125 21,100 164
HUMANEVAL+-MINI 16.1 13.0 1 110 164

关键实验结果

图4:HUMANEVAL (#124) 中一个逻辑错误的参考实现示例

总结

实验结果有力地证明,当前广泛使用的代码生成基准由于测试不充分,无法准确反映 LLM 的真实编程能力。本文提出的 EvalPlus 框架及其产出的 HumanEval+ 数据集,通过自动化的、大规模的、高质量的测试生成,提供了一种更严格、更可靠的评估方法,是未来评估和研究 LLM 代码生成能力的重要工具。

图3:通过率分布。X轴为164个问题(按HumanEval通过率排序),Y轴为所有LLM生成样本的平均通过率(对数尺度)