利用现有代码来增强代码生成

互联不一般哥 2024-03-13 06:34:01

AceCoder: Utilizing Existing Code to Enhance Code Generation

Jia Li1, Yunfei Zhao1, Yongmin Li1, Ge Li1, Zhi Jin1

1Peking University Beijing, China

引用

Li J, Zhao Y, Li Y, et al. Acecoder: Utilizing existing code to enhance code generation[J]. arXiv preprint arXiv:2303.17780, 2023.

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

摘要

本文提出了一种新的提示技术Ace-Coder。Ace-Coder包含两种新的机制(即引导代码生成和示例检索)来解决代码需求理解和代码实现挑战。Ace-Coder首先分析需求并输出一个中间的初步结果(例如,测试用例),用于澄清需求,并告诉大语言模型“该写什么”。随后,Ace-Coder的示例检索在提示中选择类似的程序作为示例,提示提供了大量相关内容(如算法、APIs),并教会大语言模型“如何编写”。

1 引言

大型语言模型(LLMs)在代码生成方面取得了巨大成功。代码生成旨在基于自然语言需求自动生成源代码。最近,LLMs在代码生成方面取得了最先进(SOTA)的成果。LLMs不需要微调,仅需以一个提示作为输入。一个提示包括几个示例(例如,<需求, 代码对>)和一个新的需求。LLMs通过示例学习代码生成,并类似地为新需求生成代码。 LLMs的性能在很大程度上依赖于提示的表面结构。因此,如何设计提示(即,提示技术)是一个热门话题。现有的提示技术(例如,少数示例提示和思路链提示)是为自然语言生成设计的,并且在代码生成中的准确性较低。例如,使用少数示例提示的Codex在一个真实世界基准测试-人类评估上仅实现了37.2%的通过率。因此,探索更高级的代码生成提示技术是必要的。

在本文中,我们提出了一种专门针对代码生成的新颖提示技术,命名为AceCoder。它显著提高了LLMs在代码生成中的性能。我们的动机是代码生成旨在建立从自然语言需求到源代码的映射。在这一映射中存在两个独特的挑战,即需求理解和代码实现。AceCoder提出了两种新颖的机制来缓解这两个挑战。

具体来说,我们使用一个检索器来搜索具有类似需求的程序(例如,前20个)。考虑到LLMs的最大输入长度有限(例如,1024个令牌),提示中的示例数量也是有限的,比如三个示例。因此,我们进一步设计了一个选择器,从检索到的结果中选择一组程序作为示例。选择器将过滤掉冗余的程序并挑选出有信息量的示例。然后,示例被插入到提示中并教导LLMs如何实现代码。总之,给定一个需求,AceCoder通过三个步骤生成程序: 示例检索。它使用检索器和选择器找到类似的程序作为示例,即<需求,代码>对。 提示构建。它使用分析器将检索到的示例转换成<需求,初步结果,代码>三元组。然后,它将三元组示例与输入需求一起拼接以构建提示。 代码生成。它将提示输入到LLMs。通过从示例中学习,LLMs首先输出一个中间初步结果,然后为输入需求生成代码。

总的来说,本文的贡献如下:

我们提出了一个名为AceCoder的新颖提示技术,用于提高LLMs在代码生成中的性能。AceCoder包含两种新颖技术(即引导式代码生成和示例检索),分别用以缓解代码生成中的两大挑战(即需求理解和代码实现)。我们将AceCoder应用于三个LLMs,并在三个公开基准测试上进行了广泛的实验。定性和定量实验表明,AceCoder显著优于最先进的基线(例如,思路链提示,少量样本提示)。

2 技术介绍

AceCoder利用LLMs通过提示生成程序。图1展示了在推理期间AceCoder的概览。给定一个输入需求,AceCoder通过以下三个步骤生成代码:示例检索。它使用一个检索器和选择器从检索库中选择一些相似的<需求,代码>对作为示例;提示构建。它使用一个分析器将示例转换为<需求,初步结果,代码>三元组。初步结果是一个用于澄清需求的软件工件,例如测试用例。示例与输入需求一起被拼接以构建提示;代码生成。将提示输入LLMs。通过学习示例,LLMs首先输出一个中间初步结果,然后生成代码。

图1:AceCoder整体工作流程

2.1 示例检索

如图1所示,第一步有两个目标:(i)检索相似程序和(ii)从检索到的程序中选择少数示例。我们分别设计了一个检索器和选择器来实现这些目标。这两个模块的详细信息如下所述。

检索器:

相似的程序往往具有相似的自然语言需求。因此,我们将输入需求作为查询,以在检索库中搜索相似需求。然后,我们提取相应的程序作为相似程序。具体来说,我们利用一个名为Lucene的开源搜索引擎来构建我们的检索器,并使用训练数据作为检索库。我们采用BM25得分作为检索指标,这在以前的研究中广泛使用。BM25得分是一个词袋检索函数,用于估计两个句子的词汇级相似性。两个句子越相似,BM25得分越高。在本文中,检索器基于BM25得分输出前-m个相似程序。选择BM25+Lucene的原因是它们可以达到良好的检索准确性并具有低复杂性。考虑到检索库通常是大规模的,轻量级检索器更接近实际应用。

选择器:

我们可以从检索器获得前m个相似程序。然而,LLMs的最大输入长度(例如,1024个令牌)和推理预算通常是有限的。这导致提示中的示例数量(即,k)也是有限的(例如,三个示例)。因此,有必要从检索结果中进一步选择k个程序作为示例。一个直接的想法是选择前k个相似程序作为示例。然而,由于程序是独立评分的,我们发现检索结果可能包含冗余程序。图2展示了一个需求及其相似程序。相似程序按BM25得分排序。我们可以看到,前3个程序是冗余的,因为它们都使用了一个API来找到特定模式的序列。在本文中,我们设计了一个选择器,可以过滤掉检索结果中的冗余程序。

图2:检索示例

2.2 提示构建

此步骤的目标是构建一个提示。正如前文所述,我们的引导式代码生成期望LLMs能够首先输出一个中间初步结果,然后生成最终代码。为了实现这一目标,我们设计了一个包含三重示例的特殊提示(即,<需求,初步结果,代码>)。具体来说,我们首先使用一个分析器将引入选定的示例中,得到三重示例。初步结果是用于澄清需求的软件工件。受测试驱动开发的启发,本文默认将测试用例视为初步结果。我们还在实验中探索了其他选择(例如,APIs,方法签名)。然后,我们将这些三重示例与输入需求串联起来构建提示。图3(a)展示了我们提示的一个示例。提示以几个示例开始,并以一个新需求结束。[需求]、[测试用例]和[源代码]是标记三重部分中不同部分的特殊标签。

图3:提示和大语言模型的输出

选择章节中讨论的AlphaRepair中使用的CodeBERT。在某些情况下,一段错误代码可能同时适用于多个修复模板。在这种情况下,我们会在生成第一个正确修补程序后停止修复模板的选择。

2.3 代码生成

在这一步骤中,我们利用LLM基于提示生成代码。跟随前面的研究,我们将LLM视为一个黑盒生成器,并使用它来完成提示。通过学习提示中的示例,LLMs将首先输出一个初步结果(例如,测试用例),然后基于初步结果和输入需求生成代码。图3(b)展示了一个LLM - CodeGeeX的输出。我们可以看到,CodeGeeX首先生成了一些测试用例,然后实现了一个Python函数。测试用例提供了大量有价值的信息(例如,输入输出格式、无效输入)并指导了后续的代码生成。

3 实验评估

3.1 实验设置

研究问题。在本文中,我们研究以下研究问题:

RQ1:AceCoder与现有提示技术相比性能如何?

RQ2:AceCoder与基于检索的模型相比性能如何?

RQ3:人类开发者是否更喜欢由AceCoder生成的代码?

RQ4:AceCoder中不同模块的贡献是什么?

RQ5:三个模块的更好设计是什么?

评估数据集。我们在三个公共代码生成基准上进行实验,包括Python的MBPP、Java的MBJP和JavaScript的MBJSP。MBPP包含974个由众包构建的实际编程问题。每个问题包含一个自然语言需求、一个Python函数和三个测试用例。 MBJP和MBJSP分别包含966个在Java和JavaScript中的众包编程问题。每个问题由一个自然语言需求、一个独立函数和3个测试用例组成。

对比方法。本文旨在提出一种新的代码生成提示技术。因此,我们选择了三种现有的提示技术作为基线,包括Zero-shot prompting、Few-shot prompting和Chain-of-Thought (CoT) prompting。同时,AceCoder检索相似程序以协助LLMs生成代码。一些研究也引入信息检索来增强代码生成。我们将AceCoder与这些基于检索的模型进行比较,包括REDCODER和Jigsaw。

评估指标。跟随之前的代码生成研究,我们采用Pass@k作为我们的评价指标。具体来说,我们为每个需求生成k个程序。如果任何生成的程序通过了所有测试用例,则认为该需求已解决。我们计算解决的需求占总需求的百分比作为Pass@k。在本文中,k设置为1、3和5。

RQ1 提示技术性能比较

实验设计。我们将AceCoder和三种现有的提示技术应用于三个基础模型。然后,我们使用Pass@k来衡量它们在三个基准测试上的性能。

结果。三个基准测试的结果显示在表1中。括号内的值是与最先进的基线 - 少量样本提示相比的相对提升。

表1: AceCoder和提示基线在三个数据集上的结果。

分析。(1) AceCoder在三个基准测试上表现优于基线。与最先进的基线 - 少量样本提示相比,就Pass@1而言,AceCoder在MBPP上的提升达到了56.4%,在MBJP上达到了70.7%,在MBJSP上达到了88.4%。Pass@1是一个非常严格的指标,很难有所改进。这些显著的提升证明了AceCoder在代码生成中的优越性。我们将这些提升归因于我们的新颖技术,即示例检索和引导式代码生成。检索到的示例包含了许多相关的代码元素,教导LLMs“如何写”。引导式代码生成要求LLMs分析需求,告诉LLMs“要写什么”。(2) AceCoder在不同大小和不同编程语言的LLMs中都有效。与少量样本提示相比,就Pass@1而言,AceCoder在CodeGeeX-13B上的提升达到了88.4%,在CodeGen-6B上达到了65.5%,在InCoder-6B上达到了57.5%。特别地,我们发现,配备AceCoder的LLM甚至能超过更大的LLMs。例如,在MBJSP中,配备AceCoder的InCoder-6B超过了使用少量样本提示的CodeGeeX-13B。这证明了AceCoder的潜力。此外,AceCoder是语言无关的,并在多语言代码生成中有效(即Python,Java和JavaScript)。

RQ2 检索模型性能比较

实验设计。在这个研究问题中,我们将AceCoder与两个基于检索的基线进行比较,包括REDCODER和Jigsaw。基线和AceCoder使用相同的检索库。因为REDCODER需要微调,我们按照官方指南使用训练数据训练REDCODER。

结果。三个基准测试的结果显示在表1中。括号内的值是与最先进基线 - Jigsaw相比的相对提升。

分析。AceCoder在三个基准测试中优于基于检索的基线。与最先进基线 - Jigsaw相比,就Pass@1而言,AceCoder在MBPP上的提升达到了13.1%,在MBJP上达到了23.44%,在MBJSP上达到了15.8%。Jigsaw也检索相似程序来制作提示。这些提升显示了我们的选择器和分析器的有效性。选择器过滤掉冗余的相似程序,进一步提高了示例的质量。分析器限制LLMs首先分析需求,然后生成代码。此外,我们注意到REDCODER在三个基准测试中的准确率较低。这是因为训练数据有限,微调容易导致过拟合。这验证了我们的动机,即通过提示引入相似程序是一种更适合LLMs的方法。

RQ3 生成代码的欢迎程度

实验设计。代码生成的最终目标是协助人类开发者编写代码。因此,我们进行了人类评估,以衡量由AceCoder和基线生成的程序。我们遵循了先前研究中人类评估的设置。我们仔细检查了评估设置,认为我们的设置是可靠的。我们从三个方面手动评估程序: 正确性(程序是否满足给定的需求)。0分:程序与需求完全不一致。1分:程序已实现,但缺少一些细节。2分:程序正确实现。 代码异味(程序是否包含坏代码异味)。0分:就性能而言,存在更好的解决方案。或有严重的代码异味。1分:一些细节不到位。存在轻微的代码异味。2分:就性能而言,不存在明显更好的代码。如果可能,相应地释放资源。没有明显的代码异味。可维护性(实现是否标准化且具有良好的可读性)。0分:程序没有遵循一致的规范,或变量命名中有许多无意义的名称,或存在某些重复和冗余代码。1分:程序实现符合某些规范。但一些变量名称可以进一步优化。2分:程序实现相对标准化,变量命名基本语义直白,可读性较好。我们通过一些例子向评估员解释上述方面。在与评估员讨论后,我们将每个方面的分数设置为一个整数,范围从0到2(从差到好)。对于AceCoder和基线,我们选择一个固定的基础模型(即CodeGen-2B),并为每种方法收集200个生成的程序。最终,我们获得了1,000个用于评估的程序。我们邀请了10名拥有3-5年开发经验的开发者通过问卷的形式评估生成的程序。1,000个代码片段被分为5组,每个问卷包含一组。程序被随机打乱并匿名由评估员审阅。

结果。人类评估的结果显示在表2中。括号内的值是与最先进基线 - 少量样本提示相比的相对提升。

表2:人类评估的结果。括号内的值是与最先进基线 - 少量样本提示相比的相对提升。

分析。人类评估的结果显示在表2中。括号内的值是与最先进基线 - 少量样本提示相比的相对提升。

RQ4 不同模块的作用

实验设计。AceCoder包含三个模块,即检索器、选择器和分析器。这个研究问题旨在分析三个模块对性能的贡献。我们选择CodeGeeX作为基础模型,并通过逐步添加这三个模块进行消融研究。

结果。结果显示在表3中。分别代表添加和移除相应模块。没有这三个模块,基础模型使用少量样本提示生成代码。添加检索器后,基础模型选择顶级k个相似程序作为示例并直接生成代码。添加选择器后,基础模型从相似程序中选择k个示例,然后生成代码。进一步引入分析器后,基础模型使用AceCoder生成代码。

表3:AceCoder在不同设计下的性能。“w/”是“with”的缩写。

分析。所有模块对于AceCoder实现最佳性能都是必要的。添加检索器后,基础模型的性能得到了提升。就Pass@1而言,检索器在MBPP中带来了17.6%的提升,在MBJP中带来了40.4%的提升,在MBJSP中带来了67.2%的提升。这验证了我们的动机,即检索到的程序包含大量对代码生成有益的有用信息。添加选择器后,基础模型的性能进一步提高。这表明我们的选择器能够有效过滤掉检索结果中的冗余程序并提高示例的质量。进一步引入分析器后,基础模型取得了更好的结果。就Pass@1而言,基础模型在MBPP中提升了31.1%,在MBJP中提升了70.7%,在MBJSP中提升了88.4%。这证明了引导式代码生成在分析需求方面的有效性。

RQ5 三个模块的设计

实验设计。如前文所述,AceCoder包含三个模块,即检索器、选择器和分析器。在这个研究问题中,我们探索三个模块的不同设计,并验证我们设计的优越性。我们选择CodeGeeX作为基础模型。评估设置如下所示。

(1)检索器将输入需求作为查询,并从检索库中搜索相似程序。(2)选择器旨在给相似程序打分并过滤冗余程序。对于选择器中的评分函数。(3)分析器的作用是将初步结果引入示例中。初步结果是一种特殊的软件工件,有助于需求理解。

结果和分析。 (1) 密集型检索器与我们的检索器相当,但效率较低。在表6中,与AceCoder相比,采用密集型检索器的AceCoder在性能上有轻微下降。这表明代码生成偏好词汇上相似的程序,这些程序包含大量可重用内容。在代码完成工作中也发现了类似的结果。此外,密集型检索器具有更高的复杂性,难以应用于大规模的检索库。(2) BLEU选择器偏好更短的示例,且并非最优。与AceCoder相比,采用BLEU选择器的AceCoder在准确性上有明显下降。我们检查了一些失败的样本,发现BLEU选择器偏好更短的示例。这是因为BLEU是相似需求中n-gram的精确度。相似需求越短,BLEU越高。这导致选择器倾向于选择短程序作为示例,忽略了一些信息丰富但较长的示例。(3) 测试用例比APIs和方法签名更适合作为初步结果。我们仔细检查了一些案例。首先,基准测试中的许多需求不需要APIs或只涉及少数琐碎的APIs(例如,range, split和len)。这导致生成的APIs对代码生成的益处有限。其次,通过生成方法签名,要求LLMs思考输入输出格式,这对代码生成有益。但方法签名缺失了其他必要的细节,如边界情况。AceCoder将测试用例视为初步结果。测试用例在代码文件中很常见。因此,对于用大量代码数据训练的LLMs来说,生成合理的测试用例是可行的。在测试用例的指导下,LLMs可以全面理解需求并确定相关细节(例如,输入输出格式、边界输入、异常值),从而生成更正确的程序。

转述:葛一飞

1 阅读:193

互联不一般哥

简介:感谢大家的关注