ClassEval:一个评估LLM在类级代码生成上的表现的手工基准

互联不一般哥 2024-06-30 19:28:45

ClassEval: A Manually-Crafted Benchmark for Evaluating LLMs on Class-level Code Generation

Xueying Du , Mingwei Liu,Kaixin Wang,Hanlin Wang,Junwei Liu

Yixuan Chen,Jiayi Feng,Chaofeng Sha,Xin Peng,Yiling Lou

引用

Du X, Liu M, Wang K, et al. Classeval: A manually-crafted benchmark for evaluating llms on-level code generation[J]. arXiv preprint arXiv:2308.01861, 2023.

论文:https://arxiv.org/abs/2308.01861

Github; https://github.com/FudanSELab/ClassEval

摘要

我们首次尝试在更具挑战性的代码生成场景中评估LLMs,即类级代码生成。我们首先手工构建了第一个类级代码生成基准ClassEval,包括约500人时的100个类级Python代码生成任务。基于新的基准ClassEval,我们对11个最先进的LLMs进行了首次类级代码生成研究。

1 引言

代码生成技术自动生成给定自然语言描述的代码片段,可用于提高开发生产力,并在文献中得到广泛研究。大型语言模型(LLMs)的最新进展在代码生成领域带来了显著进步。迄今为止,研究人员提出了各种LLMs(如GPT-4、WizardCoder和Instruct-CodeGen),通过在大规模通用或特定代码语料库和指令上训练超过数十亿参数的大型模型。

迄今为止,已经提出了许多代码生成基准,如HumanEval和MBPP。尽管这些评估对于人们理解和比较不同LLMs的性能非常有帮助,但现有评估实际上侧重于相当简单的代码生成场景,即函数级或语句级代码生成。

基准ClassEval。本研究首次尝试在更具挑战性的代码生成场景中评估LLMs,即类级代码生成。特别是,我们评估模型生成多个相互依赖方法的类的能力,以适应给定的自然语言描述。由于目前没有现有基准涵盖类级代码生成任务,我们以严格和耗时的方式手工构建了第一个类级代码生成基准ClassEval,大约耗时500人时构建了100个类级Python代码生成任务。总体而言,ClassEval涵盖了实际软件开发中的各种主题(例如管理系统和游戏开发)。每个任务都配备了高度充分的测试套件(例如98.2%和99.7%的分支级或语句级覆盖率),以便便于对生成的代码进行可靠的正确性检查;此外,每个任务都设计为生成具有各种依赖关系的多个方法的类(例如字段、方法和库依赖)。

实证研究。基于新的基准ClassEval,我们进行了首次研究,评估LLMs在类级代码生成上的表现。具体而言,我们的实验包括11个最先进的LLMs,这些LLMs在模型大小、基础模型、来源或领域上各不相同。对于每个研究的LLM,我们探索其在三种不同生成策略下生成类级代码的表现,即整体生成(一次性生成整个类)、增量生成和组合生成(逐方法生成类)。对于每个生成的代码片段,我们使用广泛使用的度量标准Pass@k来衡量其正确性。此外,我们还调查了生成依赖代码的模型能力,并分析了不正确类的糟糕情况。

主要发现和影响。根据我们的结果,我们得出以下主要发现。首先,我们发现所有现有的LLMs在类级代码生成上的表现要比在独立方法级代码生成基准如HumanEval上差得多;而方法级编码能力不能等价地反映LLMs之间的类级编码能力。其次,我们发现GPT-4和GPT-3.5在类级代码生成上仍然表现出比其他LLMs更卓越的优势,而第二梯队模型包括Instruct-StarCoder、Instruct-CodeGen和WizardCoder,它们的性能非常相似。第三,我们发现一次性生成整个类(即,整体生成策略)只适用于GPT-4和GPT-3.5,而逐步生成(即,增量和组合)是其他模型更好的策略,因为它们对理解长指令和利用中间信息的能力有限。最后,我们发现模型在生成依赖方法的代码方面能力有限,并讨论了生成类中的常见错误类型。

总之,本文的贡献包括:

第一个类级代码生成基准ClassEval,耗时500人时手工构建,并在上公开;首次研究,评估11个代表性LLMs在类级代码生成上的表现,并采用三种不同的生成策略;通过分析模型能力和未来方向,提出了类级代码生成LLMs的发现和影响。

2 技术介绍

在这一部分中,我们介绍我们的新基准ClassEval。我们将介绍基准的格式(第2.1节),构建过程(第2.2节)和基准特征(第2.3节)。

2.1 基准格式

在ClassEval中,每个编码任务包括针对目标类(即待生成的类)的输入描述、用于验证生成代码正确性的测试套件,以及作为目标类的参考实现的规范解决方案。通常,LLMs基于输入描述生成代码片段,并通过提供的测试套件验证正确性。生成的代码必须符合测试套件中指定的一致接口(例如,输入参数和返回值的类型)以进行有效执行,我们为编码任务中的输入描述定义了类骨架格式。类骨架作为目标类的结构化蓝图,包含类级信息(导入语句、类名、类描述和类构造函数)和方法级信息(方法签名、功能描述、参数/返回描述和示例输入/输出)。类骨架中元素的详细定义见下表。

列“Mand.”表示元素在类骨架中是否为必需的。方法级元素都采用自基准HumanEval等现有基准。图1进一步说明了一个类骨架的示例,不同组件用不同颜色突出显示。受合同编程的启发,类骨架作为代码生成的正式和精确规范,概述了期望的行为、前置条件和后置条件。LLMs生成与给定测试套件相符的类级代码,基于类骨架。

图 1:在ClassEval中的类骨架示例

2.2 构建过程

图2说明了构建ClassEval的过程。我们遵循四个步骤来创建ClassEval:(i)使用不同策略选择适合的编码任务;(ii)基于合同编程和测试驱动开发的原则构建类骨架;(iii)为每个类骨架创建测试套件;以及(iv)为每个编码任务编写规范解决方案。构建的类骨架、测试套件和规范解决方案构成了我们的类级代码生成基准ClassEval。

图 2:ClassEval构建过程概述

为了避免LLMs在训练过程中看到编码任务,我们的基准完全手动构建,以减轻可能从现有代码来源泄露数据的风险。我们的手动构建涉及一个耗时密集的过程,大约耗费了500人时来构建100个类级编码任务。由于需要大量手动工作,我们目前将基准规模限制在这个大小。此外,与大多数现有基准的趋势一致,考虑到Python的普及性,我们的基准主要侧重于Python。

2.2.1 任务选择

在这一步中,我们为基准设计类级编码任务。

包含来源。 我们设计编码任务以涵盖多样化和真实世界的开发主题,基于以下三个来源。 (i) 重新审视现有基准。我们参考诸如HumanEval和MBPP等成熟基准,以包括普遍和常见的主题。 (ii) 探索PyPI主题。我们手动探索Python软件包索引(PyPI),其中包含大量Python软件包的存储库,提供了各种潜在任务主题。 (iii) 头脑风暴。所有作者(具有2-8年Python开发经验)积极参与头脑风暴,以生成超出以上收集的潜在编码任务的可能性。

排除标准。 我们的基准专注于可以在一个单独类中实现的编码任务。因此,我们排除了对执行环境有复杂依赖的任务,包括与(i)网络编程、(ii)图形用户界面(GUI)设计、(iii)数据可视化、(iv)系统编程和(v)并发编程相关的任务。这些任务通常需要与其他类进行交互,或者不能通过单元测试中的断言语句轻松验证。

通过这种方式,我们获得了一个包含100个不同类级编码任务的列表,涵盖了广泛的主题,如游戏开发、文件处理和管理系统。下表展示了我们任务的主题分布。

2.2.2 类骨架构建

在这一步中,我们为每个编码任务手动构建类骨架,参与者包括5名平均拥有3年Python开发经验的成员。每个骨架最初分配给两名参与者,一个负责编写类骨架,另一个负责进行双重检查。在出现分歧时,第三名参与者促进讨论以达成对类骨架的共识。整个过程遵循以下设计原则。

原则1(依赖): 每个类骨架应包含具有不同依赖关系的方法,即这些方法依赖于类内的其他代码上下文。先前的研究[17]表明,项目中大多数方法(超过70%)依赖于项目中的其他代码上下文。与之前关注独立函数级代码生成的基准(如HumanEval和MBPP)不同,我们的类级基准旨在捕捉现实世界的情况,其中方法通常与其他代码上下文有依赖关系。为了区别于函数级基准,我们故意避免生成具有独立方法的类的任务,这实质上将是一组独立的方法级编码任务。相反,我们的基准中的类骨架包括具有不同依赖关系的方法,包括(i)库依赖,其中方法依赖于外部库;(ii)字段依赖,其中方法依赖于类实例变量(字段);(iii)方法依赖,其中方法依赖于同一类内的其他方法;以及(iv)独立方法,其中方法独立运行,不依赖于字段、方法或外部库。

原则2(类构造函数): 每个类骨架中(如果有的话)的类构造函数应定义类字段及其默认值。构造函数还包括类字段的自然语言描述,以提供对其含义的清晰理解。重要的是,构造函数不调用类内其他方法,以保持类初始化过程的独立性和自包含性。

原则3(方法功能): 我们避免包含像关闭数据库连接这样的复杂功能,因为这些功能不容易测试和验证。此外,通过将常见和重复的功能拆分为单独的方法,我们增强了代码的可重用性和可维护性。这一原则促进了方法之间潜在的相互依赖关系,模拟了更相互关联和实际的编码情景。

原则4(方法参数): 方法参数限制为基本数据类型,避免对象级参数或像kwargs这样模糊定义的参数。这一原则不仅增强了方法调用时的清晰度,还有助于测试,使得更容易创建单元测试并验证单独方法的功能。

原则5(方法返回值): 方法应尽可能包括返回值以供测试。为了指示成功或失败,它们使用布尔返回类型进行标准化,而不是自定义字符串。此外,方法设计可能包括对输入参数的评估条件,并包括异常处理机制。提供了异常类型、消息内容和触发情况的详细规范,以确保对异常处理进行全面测试和验证。

每个构建的类骨架将包含必需元素(即类描述、类名、方法签名和功能描述)和可选元素(即导入语句、类构造函数、参数/返回描述和示例输入/输出)。

2.2.3 测试构建

在这一步中,我们根据每个编码任务的类骨架手动构建测试套件。负责创建类骨架的参与者现在负责编写相应的测试套件。类似地,一个参与者专注于编写单元测试用例,而另一个确保测试用例的质量和正确性。

每个类骨架中的方法被设计为具有多个依赖关系,如第2.2.2节中的原则1所述。因此,参与者需要在两个级别构建测试用例:方法级别测试和类级别测试,以便在单独或一起调用实现的方法时充分测试其正确性。方法级别测试主要通过独立调用每个被测试方法来检查其正确性,而不调用类中的任何其他方法。另一方面,类级别测试主要通过依次调用多个被测试方法来检查它们的正确性。方法级别测试确保独立检查每个被测试方法的正确性,而不受其他方法的不正确实现的影响,而类级别测试通过考虑其交互来评估类的整体正确性。此外,我们还包括了现有基准HumanEval和MBPP中的测试用例示例,以突出差异。现有基准中的函数级测试与我们基准中的方法级测试相当,但主要区别在于现有基准中的函数级测试仅检查被测试函数的返回值,而我们的方法级测试进一步检查类的字段。如图4所示,在测试purchase_item方法时,ClassEval中的方法级测试不仅验证返回值,还评估对库存字段执行的操作。此外,现有基准缺乏类级测试,因为它们主要关注单个函数生成。

接下来,我们分别介绍构建方法级测试和类级测试的主要原则。对于方法级测试,参与者被要求创建至少五个测试用例,以涵盖每个被测试方法的不同场景。对于类级测试,参与者需要构建包含不同被测试方法组合的测试用例,确保每个方法在类级测试中至少被调用一次。为了简化测试构建,参与者需要使用现有的unittest框架,该框架提供多样的断言API和一组测试固定装置(例如setUp和tearDown方法),用于在测试执行之前和之后进行准备和清理任务。此外,所有构建的测试用例的运行时间限制为五秒,以防止生成的代码中出现潜在的无限循环。

2.2.4 规范解决方案构建

在这一步中,我们根据构建的类骨架和测试用例手动编写每个编码任务的规范解决方案。参与此步骤的四名参与者(每人拥有2-4年的Python开发经验),他们没有参与构建类骨架和测试用例。每个编码任务分配给两名参与者,一人负责编写规范解决方案,另一人负责进行双重检查。参与者需要使用测试用例执行解决方案,以识别和修复任何错误。

2.3 基准特征

通过这种方式,我们手动构建了一个包含100个类级编码任务的新基准ClassEval。具体特点如下。

规模。 ClassEval包含100个类和412个方法。为了便于与其他代码生成基准进行直接比较,我们在表1中包含了ClassEval的统计数据。结果显示,与两个最广泛使用的手工编写基准HumanEval和MBPP相比,ClassEval的代码行数(45.7)存在较大差异,分别是其4.0倍和6.7倍。此外,我们对ClassEval中整个文档字符串信息(类骨架)的平均标记数量进行了额外统计(259.3),分别比HumanEval(67.7)和MBPP(14.5)高出3.8倍和17.9倍。这些结果表明,ClassEval中的类级代码生成任务呈现出更高的复杂性,涉及更长的代码生成,以及更详细和复杂的文档字符串信息。

测试充分性。 上表提供了我们基准中测试用例的覆盖统计数据,与HumanEval和MBPP进行了比较。我们使用Python工具包coverage[38]对规范解决方案代码的语句级和分支级覆盖进行了收集。此外,我们提供了方法级测试的平均数量(#Tests/M)和类级测试的平均数量(#Tests/C)。如表所示,与HumanEval和MBPP相比,ClassEval中的测试用例在语句级和分支级覆盖率上均达到了显著高于98%的水平。这表明我们基准中对生成的解决方案进行了更广泛的代码检查,而且事实支持了ClassEval平均包含更多方法级和类级测试的情况。

依赖关系。 ClassEval专注于类级代码生成任务,与先前的基准有所区别。表5显示了ClassEval和先前基准中方法内依赖级别的分布,如第3.1节所述。值得注意的是,库依赖、字段依赖和方法依赖并不是互斥的,有些方法可能同时具有字段和方法依赖的组合。我们将具有字段或方法依赖的方法分类为类级依赖方法,在ClassEval中共有314个(76.2%)。这一包含使得ClassEval成为一个全面的基准,适用于评估必须考虑复杂类级交互和上下文依赖关系的LLMs。

总的来说,与先前手工制作的代码生成基准相比,ClassEval包含复杂的类级编码任务,涉及更大规模的代码片段、多样的依赖关系、充分的测试用例,以及来自实际软件开发的更广泛主题。

3 实验评估

3.1 实验设置

研究问题:

RQ1:LLMs在类级别代码生成中表现如何?

RQ2:不同的生成策略在LLMs在类级别代码生成中的表现如何?

RQ3:LLMs在类级别代码生成过程中如何生成依赖于其他上下文的代码?

RQ4:在类级别代码生成过程中常见的错误是什么?

两种抽样策略。1、核心抽样,每个任务随机生成五个解决方案代码样本,temperature为0.2,默认top_p;2、贪婪抽样,每个任务仅生成一个单一的解决方案代码样本,使用贪婪解码。

三种生成策略。1、整体生成策略:模型被要求一次性生成整个类,输入为类的框架。2、增量生成策略:模型被要求逐个方法地生成类。每次迭代基于之前迭代中生成的方法体。这个迭代过程会重复,直到类中的所有方法都被生成。3、组合生成策略:模型被要求逐个方法地生成类。每次迭代是独立的,不考虑其他已生成的方法。所有生成的方法最终组合在一起形成类。

设备。我们的实验在由八个A800-80G GPU组成的计算基础设施上运行。

评估指标。使用泛使用的Pass@k [18]指标。

RQ1 整体正确性

图3展示了在ClassEval和HumanEval上研究的LLMs使用贪婪抽样的类级别和方法级别Pass@1。

图 3:在ClassEval和HumanEval上的Pass@1(贪婪)

结果 1:根据图1,与现有的方法级别基准HumanEval相比,我们观察到在我们的类级别基准ClassEval上,所有研究模型的正确性显著下降。特别是,在HumanEval中,表现最佳的模型GPT-4和GPT-3.5在方法级任务上的正确性分别达到85.4%/68.9%,但在ClassEval的类级任务中仅为37.0%/27.0%。其他模型也呈现类似趋势,例如,WizardCoder在HumanEval上正确生成了59.8%的方法,但在我们的基准中仅有11.0%的正确类。总之,我们的结果表明,现有的LLMs在解决复杂的编码任务,如类级别代码生成方面,仍然表现有限。此外,我们观察到,在独立的方法级别代码生成任务中,模型的表现并不一定反映它们在类级别代码生成中的能力。模型之间的比较。如图5和表7所示,GPT系列(GPT-4和GPT-3.5)在解决类级别编码任务方面大大优于所有其他模型,无论是使用贪婪抽样还是核心抽样。

RQ2 生产策略

图4比较了三种不同生成策略(即整体、增量和组合生成)的类级别Pass@5和方法级别Pass@5。

图 4:三种生成策略的Pass@5

结果 2:总体而言,我们发现最佳的生成策略因不同的LLMs而异。整体策略与其他策略相比。从一方面来看,整体生成仅对GPT-4和GPT-3.5这两个模型而言是最佳的生成策略,其类级别Pass@5明显高于其他两种策略(即对于GPT-4,改进范围为6%至9%,对于GPT-3.5,改进范围为4%至14%)。此外,即使对于方法级别的正确性,整体生成仍然优于以增量或组合方式生成方法(即方法级Pass@5提高了1.4%至9.0%)。另一方面,对于其他模型,情况则有所不同,实际上,当逐个方法生成类方法时,即采用增量或组合策略时,这些模型表现得更好。例如,就类级别的正确性而言,与整体生成策略相比,CodeGeeX和SantaCoder采用增量策略生成的正确类别更多,分别增加了9%和7%。主要原因在于这些模型能够在单独迭代生成每个方法时生成更多正确的方法(即方法级Pass@5提高了27.9%和19.2%),而不是一次性生成所有方法。因此,如果能够使用增量或组合策略生成更多正确的方法,这些模型就有更高的机会生成更多正确的类别。

总结:一次性生成整个类(即整体策略)仅对GPT-4和GPT-3.5是最佳的生成策略。对于其他模型,逐个方法生成(即增量和组合)效果更好。这种差异可能源自它们理解长指令和利用中间信息能力有限。

RQ3 依赖生成

图5展示了每个模型在核心抽样下的平均字段依赖DEP(F)和方法依赖DEP(M)。

图 5:在核心采样中的DEP(F)和DEP(M)

结果 3:根据图5,我们发现所有模型在生成依赖于字段的代码成功率要高得多,而不是生成依赖于其他方法的代码(即在所有模型中DEP(F)高于DEP(M))。换句话说,模型生成访问字段的代码可能比调用方法的代码要容易得多。此外,在所有模型中,GPT模型仍然表现出在生成依赖代码方面的一致优势,例如,GPT-4在DEP(F)/DEP(M)上至少比其他LLM高出12.6%/6.3%。

图 6:在增加方法依赖性中正确生成方法的分布

考虑到我们上面的观察到生成方法依赖更具挑战性,我们进一步调查每个模型在正确生成调用不同数量其他方法的代码方面的表现。图6是一个堆积条形图,显示了正确生成的方法与具有给定数量(即0、1、2)方法依赖的所有方法的比率(基于规范解决方案)。根据图表,我们发现所有模型在生成不调用类中任何其他方法的方法时表现最佳(图中的蓝色柱)。此外,我们发现当大多数模型生成调用一个其他方法(绿色柱)或调用两个其他方法(黄色柱)的代码时没有明显差异。特别是,对于所有模型,正确生成调用一个或两个方法的代码的平均比率分别为27.7%和27.6%。

RQ4 糟糕案例分析

我们进一步分析了生成错误的类。为此,我们自动解析解释和执行期间生成的错误日志,并在图7中展示所有模型的错误分布。

结果 4:我们发现大多数错误代码遇到了AttributeError和TypeError,表明模型在理解和满足代码上下文中的句法或语义约束方面能力有限。此外,一些情况由于对字典变量的错误操作而遇到KeyError。

转述:宗兆威

0 阅读:0

互联不一般哥

简介:感谢大家的关注