LEVER:基于运行来学习验证语言到代码的生成

互联不一般哥 2024-03-16 10:54:50

LEVER: Learning to Verify Language-to-Code Generation with Execution

Ansong Ni, Srini Iyer, Dragomir Radev, Veselin Stoyanov, Wen-Tau Yih, Sida Wang, Xi Victoria Lin

引用

Ni A, Iyer S, Radev D, et al. Lever: Learning to verify language-to-code generation with execution[C]//International Conference on Machine Learning. PMLR, 2023: 26106-26128.

论文:https://proceedings.mlr.press/v202/ni23b.html

仓库:https://github.com/niansong1996/lever

摘要

基于代码训练的大型语言模型(LLM)的出现使得通过语言生成代码的领域有着重大进展。该领域的现有技术方法将LLM解码与样本修剪相结合,并使用测试用例或基于执行结果的启发式方法进行重新排序。然而,获得许多现实世界的语言到代码应用程序的测试用例是具有挑战性的,并且启发式方法不能很好地捕捉执行结果的语义特征,例如数据类型和值范围,这通常表明程序的正确性。在这项工作中,我们提出了LEVER,这是一种通过学习验证生成的程序及其执行结果来改进语言到代码生成的简单方法。具体来说,我们训练验证器根据自然语言输入、程序本身及其执行结果来确定从LLM中采样的程序是否正确。通过将验证分数与LLM生成概率相结合,并对具有相同执行结果的程序进行边缘化,对采样的程序进行重新排序。在表QA、数学QA和基本Python编程领域的四个数据集上,LEVER始终优于基本LLM(code-davinci-002为4.6%至10.9%),并在所有这些数据集上实现了最先进的新结果。

1 引言

将自然语言映射到可执行代码的能力是各种人工智能应用的基石,如数据库接口、机器人控制和虚拟助理。大型语言模型(LLM)的最新进展,特别是那些在代码上预先训练的模型,在这种具有上下文少镜头学习的任务中显示出了巨大的前景。然而,他们的表现仍然远非完美。考虑到微调此类模型的计算成本,探索在不改变其参数的情况下改进它们的方法是很有吸引力的。

一个关键的观察结果是,虽然LLM在少镜头设置中难以达到精度,但当绘制出足够的样本时,它通常会产生正确的输出。先前的工作表明,当大规模抽取样本时,多数投票和测试用例过滤可以显著提高其性能。Shen等人(2021)和Cobbe等人(2021)进一步证明了训练验证器并使用验证分数对数学世界问题的候选解决方案进行重新排序的有效性。与仅依赖执行一致性和错误修剪的方法相比,经过训练的验证器可以利用模型解决方案中丰富的语义特征,如数据类型、值范围和变量属性,这些都是程序正确性的有力指标。Cobbe等人(2021)和随后的工作专注于通过语言模型验证自然语言解决方案,一个自然的问题是相同的方法是否可以应用于程序解决方案。

在本文中,我们提出学习验证(LEVER)通过LLM在执行的帮助下生成语言到代码。更具体地说,我们训练一个验证器,该验证器学习根据自然语言描述、程序表面形式及其执行结果的联合表示来区分和拒绝不正确的程序。我们进一步将验证概率与LLM生成概率相结合,并对具有相同执行结果的程序进行边缘化。我们使用这个聚合概率作为重新排序得分,并输出执行到最可能结果的程序。

我们在四个不同的语言到代码基准上进行了广泛的实验,这些基准跨越了文本到SQL语义解析、表QA、数学推理和基本Python编程领域。对三种不同LLM的实验结果表明,LEVER可以始终如一地提高生成程序的执行精度。值得注意的是,LEVER与code-davinci-002相结合,在不使用特定任务的模型架构或提示方法的情况下,将使用执行错误修剪的强基线提高了4.6%至10.9%,并在所有四个基准上实现了最先进的新结果。消融研究表明,执行结果对验证至关重要,LEVER在低资源和弱监督环境中也产生了不小的改进。

图1:以文本到SQL为例说明LEVER。

图1包括三个步骤:1)生成:基于任务输入和少量镜头示例,从LLM中生成示例程序;2)执行:与程序执行器一起获取执行结果;3)验证:使用学习过的验证器,根据NL、程序和执行结果,输出程序正确的概率。

2 技术介绍

我们现在介绍LEVER的详细构思和训练程序。关键组件如图1所示。

2.1 使用LLM从语言生成代码

从语言生成代码的任务的输入通常包括自然语言(NL)描述和可选的一些编程上下文(例如,数据存储、断言等)。我们将这种输入表示为x。给定x,生成模型P(y|x)生成一个程序y,该程序随后通过执行器E(·)执行,以获得结果E(y)。对于LLMs的少镜头学习,生成也通常以m个样本的固定集合为条件,{(xi,yi)}i<m。因此,用LLM生成代码的少数语言可以公式化为:

其中prompt(x,{(xi,yi)}i<m)是整个输入的字符串表示。贪婪搜索通常用于查找具有(近似)最高生成概率的程序。

2.2 候选项目的重排

我们方法的关键观察动力是PLM(y|x)中相当大的样本集通常包括正确的程序。这表明,对候选项目进行重新排序可能会显著提高成绩。判别性重新排序的思想是学习一个评分函数R(x,y),该函数衡量了对输入x而言,y是最佳输出的可能性。给定R(·),重新排序器输出候选集合S中具有最高重新排序得分的程序:

接下来,我们将介绍我们如何采用经过训练的验证器来验证和重新排列从LLM中采样的候选程序,从而使重排的y比贪心的y更好。

LLM程序采样:给定输入x,我们从具有温度采样的PLM(y|x)中获得k个程序,而不是执行贪婪搜索。由于相同的程序可能被采样不止一次,我们执行重复数据消除以形成一组n个唯一的程序候选者S={yi}ni=1,其中n≤k。我们选择进行采样而不是波束搜索,主要有两个原因:1)最近的工作表明,由于退化的程序,用于代码生成的波束搜索通常会导致更差的性能;以及2)对于我们测试的所有LLM(例如Codex),波束搜索不可用或不被有效地实现。验证与执行:我们使用问题描述x、候选程序的简单级联及其执行结果的表示作为重新排序器的输入。受最近工作的启发,我们将我们的判别性重新排序器参数化为验证(即二进制分类)模型Pθ(v|x,y,E(y)),其中v∈{0,1}。在实践中,可以使用任何二进制分类架构来实现重新排序。给定一个输入x和一个候选程序y∈S,我们获得重新排序概率作为生成和通过验证的联合概率:

执行结果聚合:由于具有相同语义的程序可能具有不同的表面形式,我们进一步聚合了S中执行到相同结果的程序的重新排序概率。通过这种方式,我们放松了对曲面形式的依赖,转而关注执行结果。因此,重新排序的最终评分函数为:

由于可能有几个程序以最高概率共享相同的执行结果,因此在这种情况下,我们在输 出程序时随机打破平局。

2.3 学习验证程序

前面的部分描述了如何在推理时使用验证器。接下来我们介绍它的训练过程。

训练数据创建:对于从语言生成代码的数据集,每个示例通常是(x,y*,z*)的三元组,其中z*=E(y*)是黄金执行结果,y*是黄金程序。由于注释程序需要领域专业知识,对于一些可以直接获得最终结果的数据集,只提供z*,而不提供y*用于学习。这被称为弱监督设置。为了收集训练数据,我们通过首先从PLM中采样k个程序(Ey|x),然后去除所有重复的程序,类似于推理时间,为训练集中的每个输入x获得一组n个唯一的程序候选项S={yi}ni=1。然后,对于每一个候选程序,我们通过比较执行结果,即v=1(z=z*),来获得它的二进制验证标签。对于包含黄金程序y*的数据集,我们附加(x,y*,z*,v=1)作为额外的验证训练示例,对于弱监督数据集,我们跳过这一步骤。通过这种方式,我们为每个输入x创建了一组验证训练示例{(x,yi,zi,vi)|yi∈S}。学习目标:给定这组验证训练示例,我们用负对数似然函数来表示输入x的损失,该函数由候选程序的数量归一化,规范化步骤对于防止具有大量独特程序候选者的示例主导学习非常重要。

3 实验装置

3.1 数据集

我们在语义解析、表QA、数学推理和基本python编程领域的四个语言生成代码数据集上进行了实验。这四个数据集的主要设置如表1所示。

表1

3.2 大型语言模型

我们使用三种不同的LLM来评估LEVER:

Codex是OpenAI开发的LLM家族的一员。具体来说,我们通过Python官方接口code-davinci-002绑定使用。

InCoder是一个在具有许可证的大型代码库上训练的多达6B个参数的LLM家族的一员。我们对InCoder-6B进行了实验,并将其用于从左到右的生成。

CodeGen是LLM家族的成员,我们评估CodeGen-16B-multi。尽管CodeGen的训练语料库中没有包含SQL文件,但我们发现它在SQL生成任务中仍然表现得相当好,这可能是因为SQL查询与其他编程语言的源文件混合在一起。

3.3 基线和评估指标

基线。我们将LEVER与以下使用LLM生成程序的基线方法进行了比较:

贪婪算法:按每个解码步骤选择最可能的令牌。

最大似然(ML):从k个采样的候选程序中,选择具有最高生成对数概率的程序,即log PLM(y|x)(或归一化生成对数概率为log PLM(y|x)/|y|)。我们使用开发集凭经验确定是否对每个数据集使用归一化概率。

剪枝+最大似然:修剪掉执行错误的候选程序;然后选择具有最大似然的程序。

剪枝+投票:对无错误程序中的执行结果进行多数投票,并选择投票最多的执行结果及其对应的程序。

我们专注于与剪枝+最大似然基线进行比较,因为这是一种简单的重新排序方法,可以利用执行,并在不同的数据集和LLM中一致地产生有竞争力的结果。

评估指标。根据之前的工作,我们使用执行准确性作为所有数据集的主要评估指标,该指标衡量产生黄金执行结果或通过所有测试用例的示例的百分比。

3.4 实施细则

验证训练集。我们使用表1中描述的采样预算,通过从训练集上的LLM中采样来创建验证训练数据。当学习验证器时,如等式4所示,通过对每个示例的所有程序样本进行平均来计算训练损失。当我们将相同示例的程序样本一起批处理时,有效批处理大小也将乘以样本大小。当样本量变大(在我们的实验中高达100)时,这可能会有问题,因为它们可能无法同时放入GPU内存。因此,我们在每次迭代中对每个示例用于学习的程序进行下采样。随机下采样发生在每个训练时期的开始,因此验证器能够在每个时期看到不同的程序。

执行结果表现。验证器的输入是任务输入、候选程序及其执行结果的串联。对于Spider和WikiTQ,我们使用SQL执行的线性化结果表作为执行结果。对于GSM8k,我们使用名为“answer”的变量在执行程序后的值作为执行结果。对于MBPP,我们使用函数返回的类型和值(强制转换为字符串)。所有执行错误都表示为“错误:[原因]”,例如“错误:超时”。

验证模型选择。我们使用开发集来选择最佳的验证器模型。我们为Spider选择基础T5,为WikiTQ和MBPP选择大型T5,为GSM8k选择大型RoBERTa作为验证器在主要实验中使用的基础语言模型。对于T5模型,我们训练它们在给定验证器输入的情况下,为每个正/负示例输出标记“是/否”,并将生成“是”的概率作为推理过程中的验证概率。对于RoBERTa,我们在[CLS]头的顶部添加了一个线性层,遵循仅使用编码器模型进行序列分类的标准实践。

4 主要结果

我们展示了LEVER与Codex-Davinci的性能,并将其与Spider、WikiTQ、GSM8k和MBPP先前工作中最先进的微调和少镜头性能进行了比较。此外,我们还在Spider和GSM8k上使用InCoder和CodeGen模型评估LEVER。

4.1 LEVER的有效性

LEVER持续改进所有LLM在所有任务上的性能,与Codex-Davinci的贪婪解码基线相比,改进了6.6%(Spider)到17.3%(WikiTQ)。对于较弱的模型,如InCoder和CodeGen,我们观察到Spider和GSM8k的改进分别高达30.0%和15.0%。此外,LEVER与Codex Davinci相结合,在所有四个数据集上也取得了最先进的新结果,改进幅度从1.2%(WikiTQ)到2.0%(MBPP)不等。在具有挑战性的文本到SQL数据集Spider上,通过对T5-3B模型进行微调来实现先前的最先进技术,并增强了关系感知的自我关注,我们使用Codex Davinci+LEVER实现了更好的结果,其中验证器使用T5基本模型进行微调。LEVER还将之前使用InCoder和CodeGen在Spider上的最佳结果分别提高了13.2%和20.6%。

由于LEVER是一种简单的方法,它将少镜头语言模型生成与学习的验证器相结合,因此它可能有利于更先进的提示方法或模型架构,我们将其作为未来的工作。

4.1 LEVER的消融实验

我们使用Codex-Davinci对LEVER进行消融研究,并与上文中提到的基线进行比较,结果如图2所示。对InCoder和CodeGen进行了相同的消融,结果如表2所示。在这些结果中,我们包括了“Oracle”性能,这是通过始终选择正确的程序来获得的,只要它们出现在样本集中。

图2:LEVER和Codex-Davinci基线的比较。LEVER及其消融结果为实心条形。

表2:以InCoder和CodeGen作为LLM的结果,在以T5为基础的开发集上进行评估。

包括执行结果的效果:根据图2,当执行结果从验证器输入中删除时,所有四个基准测试的性能都显著下降,这表明执行结果对验证器训练很重要。不同数据集的效果各不相同。

而它导致WikiTQ和MBPP的绝对性能分别下降6.6%和5.6%,因为Spider(3.0%)和GSM8k(1.2%)的下降幅度较小。我们发现WikiTQ和MBPP的代码示例包含更多的执行错误,这解释了为什么我们的方法在这两个数据集上更有效。表6显示了Spider和GSM8k上InCoder-6B和CodeGen-16B的类似趋势。较小的语言模型具有较差的少镜头性能,并且从验证器中删除执行信息通常会导致更大的性能下降。此外,我们发现LEVER总体上优于剪枝+最大似然基线,这表明指示验证器可以利用超出简单执行错误的线索。

执行结果聚合的效果:聚合具有相同执行结果的程序是一种简单且广泛使用的技术。我们发现执行聚合在具有Python输出的数据集上与LEVER配合良好,但仅对SQL数据集略有好处。一个可能的原因是Python代码结构比SQL等领域特定语言更灵活。在数据库查询域中,不正确的程序更有可能执行到一些琐碎但错误的结果(例如“0”或空表)。在聚合之后,这种不正确的结果可能会积累足够的概率质量来加权正确的结果,从而对性能产生负面影响。

弱监督设置:我们还比较了LEVER在完全监督和弱监督设置下的性能。图2和表2显示,当没有给出黄金程序并且使用弱监督设置时,LEVER的性能得到了很大程度的保留,绝对性能下降高达1.1%。这表明LEVER在弱监督设置下工作良好,与执行结果相比,程序本身的验证信息较少。

转述:叶豪

0 阅读:0

互联不一般哥

简介:感谢大家的关注