使用预训练模型来增强代码自动化审查

互联不一般哥 2024-06-24 20:46:54

Using Pre-Trained Models to Boost Code Review Automation

Rosalia Tufano SEART @ Software Institute Università della Svizzera italiana Switzerland

Simone Masiero SEART @ Software Institute Università della Svizzera italiana Switzerland

Antonio Mastropaolo SEART @ Software Institute Università della Svizzera italiana Switzerland

Luca Pascarella SEART @ Software Institute Università della Svizzera italiana Switzerland

Denys Poshyvanyk SEMERU @ Computer Science Department William and Mary USA

Gabriele Bavota SEART @ Software Institute Università della Svizzera italiana Switzerland

引用

Tufano R, Masiero S, Mastropaolo A, et al. Using pre-trained models to boost code review automation[C]//Proceedings of the 44th international conference on software engineering. 2022: 2291-2302.

论文:https://dl.acm.org/doi/abs/10.1145/3510003.3510621

仓库: https://github.com/RosaliaTufano/code_review_ automation

摘要

代码审查是开源和工业项目中广泛采用的一种实践。由于此过程的不可忽视的成本,研究人员开始探索自动化特定代码审查任务的可能性。我们最近提出了针对自动化两个任务的深度学习(DL)模型:第一个模型以提交审查的代码为输入,并实施可能会被审查者推荐的更改;第二个模型则以提交的代码和以自然语言发布的审查者评论为输入,自动实施审查者要求的更改。尽管我们取得的结果是令人鼓舞的,但这两个模型都是在相对简单的代码审查场景中测试的,这在很大程度上简化了目标问题。在本文中,我们在此基础上进一步展示,预训练的文本到文本转换变换器(T5)模型可以超越之前的DL模型,用于自动化代码审查任务。此外,我们在一个更大、更真实(且具有挑战性)的代码审查活动数据集上进行了实验。

1 引言

代码审查的优点已经被广泛认可,多项研究提供了经过审查的代码质量更高的证据。此外,代码审查有助于预防错误,并促进开发者之间的知识传递。然而,关于代码审查的研究也突显了这一过程带来的额外成本:实证证据表明,大型软件项目每月可能经历数百次代码审查。这适用于开源项目(例如,Linux 每月约500次审查)和工业项目(例如,微软必应每月约3千次审查)。因此,开发者可能每周需要花费许多小时来审查代码。鉴于代码审查的不可忽视的成本,我们最近提出了自动化特定代码审查任务的方法:目标不是替代开发者,而是帮助他们在两种情况下节省时间。第一种情况是贡献者(即提交代码审查的开发者)希望在提交审查前快速获得关于其编写代码的反馈。反馈由一个深度学习(DL)模型提供,该模型训练用于接受要提交审查的代码Cs作为输入,并输出一个修订版本的Cs(即Cr),实施可能会被审查者推荐的代码更改。第二种情况涉及到审查过程中的审查者:一个DL模型训练用于输入(i)提交审查的代码Cs,及(ii)审查者用自然语言写的评论Rnl,要求对Cs进行特定更改。模型的输出是Cs的修订版本(即Cr),实施了Rnl中推荐的更改。这里的想法是,审查者可以使用模型为贡献者提供一个具体的代码更改示例,他们希望看到这些更改被实施。在我们之前的工作中,我们在一个由GitHub和Gerrit执行的代码审查中提取的约17k个三元组(Cs,Rnl,Cr)数据集上训练并试验了DL模型。特别是,向贡献者推荐代码更改的模型是一个编码器-解码器模型,其中一个编码器将Cs作为输入,一个解码器生成Cr。我们的评估显示,这个模型可以在3%(单一预测)到16%的案例中(10种不同的预测),推荐出审查者可能做出的更改。在第二种情况下使用的模型(即,自动实施审查者推荐的评论),则有两个编码器分别接受Cs和Rnl作为输入,一个解码器生成Cr。这个模型可以在12%(单一预测)到31%的案例中(10种不同的预测),成功实施审查者推荐的更改。尽管初步结果令人鼓舞,我们的方法以及进行的实证研究存在几个限制,我们试图在本文中克服。首先,我们采用了代码抽象来减小词汇量并简化DL模型的学习:模型并未在原始代码上工作,而是在一个抽象的版本中工作,例如,变量标识符被替换为特殊的VAR_ID令牌,其中ID是一个递增的数字(例如,第二个变量由VAR_2表示)。窗体顶端

尽管这样的过程简化了模型的学习,但它对可以由此模型支持的代码审查任务的多样性施加了严格的限制。实际上,抽象过程迫使排除在三元组数据集(Cs,Rnl,Cr)中所有Cr引入了在Cs中不存在的标识符或字面量的情况。这是必要的,因为抽象映射是基于Cs建立的,如果在审查过程中Cr引入了一个新变量VAR_2,则无法将该变量映射回原始源代码,这使得这种方法在实践中无法使用。这意味着我们评估我们方法的三元组(Cs,Rnl,Cr)只涉及在代码审查期间实施的相对简单的更改,不需要引入新的标识符或字面量。

其次,为了简化学习,我们只考虑了那些提交审查的代码(Cs)和修订后的代码(Cr)都不超过100个令牌的三元组。同样,这降低了我们处理的问题的复杂性。

基本上,上述两个选择导致只在相对简单的代码审查实例上训练和试验所提议的模型,这些实例仅代表在代码审查期间实际实施的代码转换的一小部分。

在这篇论文中,我们在更现实和具有挑战性的场景中试验用于代码审查自动化的深度学习模型,以我们之前的工作为基础。我们首先在一个类似的数据集上训练最近提出的文本到文本转换变换器(T5)模型。然而,我们采用了一个分词器(即SentencePiece),它允许我们处理原始源代码,无需进行代码抽象。同时,我们将考虑的代码组件的最大长度从100个“抽象”令牌增加到512个“SentencePiece”令牌(即约390个“抽象”令牌)。没有抽象机制和输入/输出长度的上限增加,使我们能够构建一个中使用的相比,要大得多的数据集(168k实例对比17k),更重要的是,在这样的数据集中包含了在代码审查过程中实施的更广泛的代码转换类型,包括一些非常具有挑战性的实例,例如那些需要引入新标识符和字面量的实例(占我们构建的新数据集的63%)。

我们还试验了与代码审查过程相关的第三个任务的自动化:给定提交审查的代码(Cs),生成一个自然语言评论Rnl,要求作为审查者的贡献者进行代码更改(即模拟审查者对提交的代码进行评论)。

我们还将T5模型与我们之前在原始数据集中介绍的编码器-解码器模型进行了比较。我们的结果显示了T5的优越性能,这代表了自动化代码审查任务方面的一个重要进步。

总结一下,这项工作的贡献包括:

(i) 一个新颖的代码审查自动化方法,克服了现有技术的几个限制;

(ii) 对这种方法进行的全面实证评估,包括与我们之前的技术的比较;

(iii) 自动化第三个任务:给定提交审查的代码,自动生成要求更改的自然语言评论,如同审查者所做;

(iv) 一个用于在更现实的场景中训练和测试DL模型的代码审查数据集;

(v) 一个全面的复现包。

2 技术介绍

我们将描述我们采用的深度学习模型,为其训练所需的数据集构建过程,以及用于超参数搜索、模型训练和预测生成的程序。

2.1 文本到文本转换变换器(T5)

文本到文本转换变换器,简称T5,不仅仅是一个模型。Raffel等人比较了“预训练目标、架构、未标记数据集、转移方法以及其他因素在数十个语言理解任务上的应用”。

这项探索的结果是架构和训练技术的最佳组合,即T5。T5基于变换器架构。提出的实现仅在一些细节上(关于标准化层和嵌入方案)与其原始形式有所不同。Raffel等人提出了几个版本的T5,它们在大小(例如,层数)和因此训练复杂性上有所不同。在这项工作中,我们采用了T5的小型版本,包括:8头注意力,编码器和解码器各6层,每层的维度为512,输出维度为2,048(约60M参数)。

该模型首先经历一次训练(预训练),其目的是为其提供解决一组相关任务的通用知识。假设,例如,我们想训练一个能够(i)将英语翻译成德语,和(ii)总结英语文本的模型。与其开始训练这两个任务的模型,不如可以通过使用去噪目标(或掩码语言建模)以无监督的方式预训练T5:模型输入的句子有15%的令牌(例如,英语句子中的词或Java语句中的代码令牌)被随机掩盖,它被要求预测这些令牌。通过学习如何预测被掩盖的令牌,模型可以获得有关感兴趣语言的通用知识。在我们的例子中,我们可以在英语和德语句子上预训练模型。

一旦预训练完成,T5将在有监督的方式下对下游任务进行微调。每个任务都以“文本到文本”的格式制定(即,模型的输入和输出都表示为文本)。例如,对于翻译任务,一个由英语和德语句子对组成的数据集允许微调模型。同样,总结任务需要输入的英语文本和相应的摘要。在接下来的章节中,我们将解释如何预训练和微调T5以支持代码审查任务。

2.2 训练数据

我们描述了构建用于T5预训练(第2.2.1节)和微调(第2.2.2节)所需数据集的过程。部分微调数据集已用于超参数搜索(第2.3节)和测试T5的性能(第3节)。

2.2.1 预训练数据集

鉴于预训练阶段的目标(即为模型提供关于下游任务语言的通用知识),我们构建了一个数据集,让T5能在Java和技术英语上进行训练。

实际上,除了源代码,技术英语在代码审查过程中也很重要,其中审查者会发布关于代码的自然语言评论。我们从包含源代码和技术英语的两个数据集开始:官方的Stack Overflow数据转储(SOD)和CodeSearchNet(CSN)。Stack Overflow是一个程序员的问答网站。我们使用的数据转储收集了2006年至2020年间的所有问题和相应的答案,总共约有5100万篇帖子(其中帖子是单个问题或答案)。帖子包括英语文本(按照SO指南)和/或代码片段。帖子通常伴有表征其主题的标签(例如,Java,Android),并可以通过上/下投票进行评级,对于答案,可以被标记为问题作者的“接受答案”。

我们从SOD中提取了所有答案(i)带有Java标签;(ii)包含至少一个<pre><code> HTML标签,以确保答案中至少有一个代码片段;以及(iii)至少有5个赞和/或被接受为答案。这些过滤器的目的是为了我们的预训练。实际上,我们希望模型能够获得有关技术英语和Java的知识:专注于包含至少一个代码片段的答案,增加了它们的自然语言文本与代码审查中的实现任务类似的可能性。此外,赞/接受答案过滤器的目的是舍弃低质量的实例,例如包含错误代码解决方案的实例。这也是我们专注于可能包含工作解决方案的高质量答案而不是问题的原因,即使问题被上票(例如,因为它们对许多用户都相关),也可能包含错误的实现。从这一步中,我们从SOD获得了1,018,163个候选实例。

对于每个选定的答案a,我们执行了以下清理步骤:我们移除表情符号、非拉丁字符、控制字符、尾随空格和多个空格。一些特殊符号被替换为具有相同意义的拉丁字符,例如,“≥”被替换为“>=”。此外,我们用特殊标签"<LINK_i>"替换任何嵌入的链接,其中i是一个从0到n-1的整数,n是a中链接的数量。最后,我们移除了所有少于十个令牌或超过512个令牌的实例(40,491)。

这使我们剩下了977,379个有效实例。

CSN 的Java数据集特色是1.5M独特的Java方法,其中一些包含它们的Javadoc。我们过滤掉了那些没有提供Javadoc或Javadoc中不包含任何字母的实例,移除了1,034,755个。与SOD不同,CSN可以包含“文本部分”(即方法评论)不是英语的实例。为了部分解决这个问题,我们排除了没有找到拉丁字符的对。虽然这不排除所有非英语评论,但至少识别并移除了那些用特定语言(例如,俄语,中文)写的评论(15,229)。我们决定接受预训练数据集中的一定程度的噪音(例如,用法语写的评论),因为(i)鉴于这个数据集的大小,这点噪音不应该实质性地影响模型的性能,且(ii)预训练数据集不用作测试集来评估方法的性能。如我们稍后将解释的,为微调数据集执行了更细致的清理,相反,这用于性能评估。在剩下的519,905个实例上,我们执行了与SOD相同的清理步骤(例如,移除表情符号)。

最后,从每对中我们获得一个单一字符串,将Javadoc评论和代码连接起来,保留那些拥有超过十个且少于512个令牌的实例(剩下507,947个实例)。

通过整合从SOD和CSN收集的实例,我们得到了由1,485,326个实例组成的预训练数据集。为了进行预训练,我们在每个实例中随机掩盖15%的令牌。被掩盖的令牌被替换为哨兵令牌<extra_id_i>,其中i是一个递增的数字,范围从0到n−1,n是在给定实例中被掩盖的令牌数量。如果连续的多个令牌被掩盖,它们被一个哨兵令牌替换。这些“被掩盖的实例”代表模型在预训练期间的输入。目标(即模型预期生成的字符串)是通过连接哨兵令牌和它们掩盖的令牌来构建的。添加一个额外的哨兵令牌以指示字符串的结束。我们的预训练数据集是公开可用的[8]。

2.2.2 微调数据集

为了创建微调数据集,我们使用Dabic等人的Web应用程序从GitHub挖掘Java开源项目。使用查询界面,我们选择了所有至少有50个拉取请求(PRs)、十个贡献者、十个星标,并且不是分叉的Java项目。这些过滤器的目标是(i)确保项目中包含足够的“代码审查”材料(即至少50个PRs);(ii)丢弃个人/玩具项目(至少十个贡献者和星标);以及(iii)减少挖掘重复代码的机会。这最终产生了4,901个项目列表。我们还挖掘了六个Gerrit安装,包含关于6,388个项目的代码审查数据。

从GitHub和Gerrit的数据集中,我们提取三元组<ms, cnl, mr>,其中ms是提交审查的方法;cnl是单个审查者的评论,建议对ms进行代码更改;而mr是实施审查者推荐的cnl的ms的修订版本。请注意(i)我们只寻找最终被接受的PR,因为我们想学习如何推荐最终能够得到审查者认为好的代码的更改;以及(ii)GitHub和Gerrit中的单个PR可以在我们的数据集中产生多个三元组。实际上,我们挖掘每个PR中的不同审查轮次。例如,一个方法ms可能提交审查,收到要求更改的评论cnl(第一轮)。然后重新提交解决cnl的ms的修订版本mr,导致第二轮审查(可能导致额外的评论和方法的修订)。我们停止时,代码正式被接受。

总体而言,我们从GitHub和Gerrit挖掘了382,955个有效的三元组,我们在下文中总结这一流程。我们的目标是三元组中评论cnl是由审查者针对方法ms发布的。我们可以识别这些情况,因为GitHub和Gerrit(i)提供有关提交代码和发布评论的开发者的信息;以及(ii)允许检索cnl指的特定代码行(即,审查者在发表评论时突出显示的ms中的代码)。我们排除了所有由代码作者发布的评论(例如,回复审查者的评论),因为它们不代表对代码的审查。因此,我们数据集中的三元组具有cnl是由审查者发布的单一评论。此外,我们排除了与ms中的内联评论(而不是代码行)相关的cnl,因为我们的目标是解决与代码相关的问题。为了将三元组视为有效,cnl必须是审查者在该特定审查轮次中对ms发布的唯一评论。

通过这种方式,我们可以确信作者稍后提交的修订版本(mr)实际上是为了实施cnl。此外,mr必须与ms不同(即,必须已在代码中实施更改以解决cnl)。从技术角度看,对提交审查的补丁中的方法进行解析是使用lizard库完成的。请注意,去除cnl包含多个评论的三元组是在处理流程后期完成的(我们将回到这一点)。实际上,在此之前,我们必须清理可能仅表示噪音的评论。

与预训练数据集一样,我们执行了一些清理步骤。我们用编号的令牌<LINK_i>替换了任何链接,其中i是从0到n-1的整数,n是cnl、ms和mr中链接的总数。如果同一个链接出现在不同部分(例如,在cnl和mr中),它会被替换为相同的令牌。我们还从评论中移除了任何表情符号和非ASCII字符,以及从评论和方法中移除了额外的空格和控制字符,并从方法中移除了内联评论(我们不关心与内部评论相关的问题)。

在清理过程之后,我们获得了一些三元组,其中cnl变成了空字符串,或者ms和mr变得相同(例如,在清理前它们仅因一些空格而有所不同)。我们移除了这些实例(-33,005),以及那些cnl + ms或mr长度超过512令牌的实例(-61,233)。我们考虑cnl和ms的长度之和,因为对于其中一个任务(即,自动实现审查者发布的评论),它们将被连接起来形成模型的输入。

然后,我们从我们的三元组中移除了不相关的评论(-28,581),即不推荐代码更改的评论(例如,“看起来对我来说很好”)。我们手工制作了一套自然语言模式来识别不相关的评论(例如,包含词语如“谢谢”、“不错”等的单词评论)。由于我们注意到在我们更丰富的数据集中,这些模式留下了几个不相关的评论,我们已经扩展了这套模式。这种分析是由一位作者通过手动检查所有cnl由少于六个词组成的三元组完成的。更新的启发式规则可在我们的复制包中找到。

我们还通过由三种语言检测工具组成的流程排除了包含非英语cnl评论的三元组(-4,815)。首次分类是使用Python库langdetect和pycld3执行的。如果这两个工具都将评论分类为非英语,我们依赖Google语言检测API做出最终决定。之所以需要这个过程,是因为我们注意到Google API在检测语言时最为准确,尤其是当评论中也包含代码结构时。在这种情况下,Python库经常生成误报(即,将英语句子分类为非英语)。然而,我们对Google API的请求次数有限。因此,我们使用Python库进行预过滤,当它们都报告评论不是英语时,我们使用Google API进行复查。

经过这一清理过程,我们排除了所有cnl中包含多个评论的三元组(-86,604)。最后,我们从微调数据集中移除了所有重复项(-918)。为了保守起见,我们将具有相同ms的两个三元组视为重复项(因此,即使是具有相同ms但不同cnl/mr的三元组也被移除了)。

生成的数据集包含167,799个三元组,这些三元组用于构建我们旨在自动化的三个任务所需的三个微调数据集。在第一个任务(代码到代码)中,模型以ms为输入,目标是自动生成其修订版本mr,实现代码审查过程中可能需要的代码更改。因此,微调数据集由对ms → mr表示。

在第二个任务(代码&评论到代码)中,模型同时以ms和审查者发布的评论cnl为输入,目标是生成mr,即实施了cnl中推荐的代码更改的ms的修订版本。

ms代码包含两个特殊标签<START>, <END>,标记cnl所指的代码部分。这个第二个任务的微调数据集由对<ms, cnl>→ mr表示。

最后,在第三个任务(代码到评论)中,模型以ms为输入,旨在生成一个自然语言评论(cnl),建议代码更改,就像审查者所做的那样。微调数据集由对ms → cnl表示。所有三个微调数据集已被分割为80%的训练集、10%的评估集和10%的测试集。表1总结了数据集中的实例数量:预训练只用于训练,而微调数据集也用于超参数调整(评估)和评估模型的性能(测试)。在表1中,我们只报告了单个微调数据集的信息(而不是之前描述的三个),因为所有三个微调数据集包含相同数量的实例。实际上,它们都来自同一组三元组。

2.3 训练和超参数搜索

Raffel等人展示了预训练在T5模型性能中的重要作用。预训练的重要性也在代码相关任务(如测试用例生成)的背景下得到了确认。为了进一步研究这个方面,我们决定同时试验预训练模型和非预训练模型,两者都经过了超参数调整过程。

由于我们采用了Raffel等人介绍的T5的小型版本,我们没有试验与其架构相关的变化(例如,改变层数或隐藏单元的数量)。不过,我们试验了不同的学习率配置:(i)固定学习率(C-LR),其中学习率值在训练期间固定;(ii)逆平方根学习率(ISRLR),其中学习率值随训练步骤的逆平方根衰减;(iii)斜角三角学习率(STLR),其中学习率首先线性增加,然后线性衰减,返回到起始值;(iv)多项式衰减学习率(PD-LR),其中学习率在给定步数内按多项式衰减至固定值。

超参数调整仅在微调阶段进行。实际上,尽管我们只关注一个超参数,这个过程仍然相当昂贵,需要训练八种不同的T5模型(即,预训练和非预训练的各四种不同学习率)。

对于预训练,我们使用了Raffel等人提出的相同配置。我们在预训练数据集(表1)上预训练模型200k步(约34个周期)。从预训练的模型开始,我们对四种不同的模型进行了75k步的微调,每个模型使用了一种试验过的学习率。

由于这个程序的目标是为三个代码审查任务找到最佳学习率,我们对这些模型中的每一个都进行了微调,使用三个任务的混合:一个单一模型被训练以支持所有三个任务,使用它们训练集的联合。这是T5的一个特点,即能够训练单一模型以应对多个任务。对于非预训练模型也使用了同样的方法:在这种情况下,四个T5模型(每个学习率一个)被直接微调。

我们在每个任务的评估集上评估了八个模型的性能,以“完美预测”为标准,即生成的输出与目标(预期)字符串完全相同。表2报告了取得的结果。可以看到,没有一个学习率在所有任务中都取得了最佳结果。然而,ST-LR显示了更好的整体性能,因此,我们在实验中采用了这一学习率。在找到预训练和非预训练模型的最佳配置后,我们使用提前停止策略对它们进行了最多300k步的微调。这意味着我们每10k步保存一次模型的检查点,计算其在评估集上的“完美预测”性能,并在模型性能连续三个检查点没有提升时停止训练(以避免过拟合)。

2.4 生成预测

一旦模型被训练好,它们就可以用来生成预测。如先前工作所做的,我们采用波束搜索策略来针对单一输入生成多个预测。例如,在代码到代码任务中,对于作为输入提供的单一ms方法,可以生成多个mr候选。当我们要求模型生成k个预测时,它会根据输入序列生成k个最可能的令牌序列;k被称为束宽,我们试验了k=1, 3, 5, 10。

对于T5生成的每个预测,我们还利用其评分函数来评估模型对提供输入的置信度。

此函数返回的值从负无穷大到0,它是预测的对数似然(ln)。因此,如果它是0,意味着预测的可能性是1(即最大的置信度,因为ln(1) = 0),而当它趋向于负无穷大时,置信度趋于0。在我们的实证研究(第3节)中,我们评估置信度作为预测质量代理的可靠性。

3 实验设计

我们评估的目标是实证评估T5模型在代码审查自动化任务中的表现。背景包括:(i)我们在第2节中介绍的数据集;及(ii)我们之前工作中的数据集。从现在开始,我们将我们之前介绍的方法称为基线。该研究旨在解决五个研究问题(RQs)。

RQ1:T5在多大程度上能自动向开发者推荐审查者可能会提出的代码更改?我们将一个提交审查的Java方法ms作为输入提供给T5,并评估模型在多大程度上能输出一个修订版本的ms(mr),实施在代码审查过程中可能会被要求的代码更改。该RQ评估这样的模型可以在代码提交审查前作为对贡献者的自动检查使用。

RQ2:T5在多大程度上能自动实施审查者推荐的代码更改?鉴于提交审查的Java方法(ms)和一个自然语言评论(cnl),其中审查者要求在ms中实施特定的代码更改,我们评估T5自动修订ms以解决cnl的能力(从而获得一个修订的方法mr)。

第三个RQ关注于我们在本文中引入的新的与代码审查相关的任务:

RQ3:T5在多大程度上能像审查者一样自动用自然语言推荐更改?在这个RQ中,T5的输入是一个提交审查的Java方法(ms),并需要生成一个自然语言评论(cnl),要求进行代码更改,就像审查者会做的那样。

对于RQ1-RQ3,我们试验了T5模型的不同变体。特别是,我们评估T5预测的质量,当(i)模型预训练与否;以及(ii)预测具有不同置信度时。通过这些分析,我们可以回答我们的第四个RQ:

RQ4:模型预训练在T5性能中扮演什么角色?预测的置信度如何影响它们的质量?如第2.3节所述,我们进行了一个剥离研究,在这个研究中,T5在没有任何预训练的情况下进行了微调(即,从神经网络中的随机权重开始)。这允许评估预训练对模型性能的贡献。至于预测的置信度,我们评估它是否可以作为预测质量的可靠代理(即,置信度越高,预测正确的可能性越高)。如果情况确实如此,这一发现将对T5模型的实际使用有所启示:使用模型的开发者可以决定仅接收置信度高于t的推荐,减少接收无意义预测的机会。

最后一个研究问题比较了T5模型与我们以前提出的方法的性能:

RQ5:与最先进技术相比,T5的性能如何?我们使用我们之前工作中的实现和数据集来比较T5模型与基线的性能。

3.1 数据收集与分析

为了回答前四个研究问题,我们在表1中报告的微调数据集的测试集上,用预训练和非预训练的T5模型的最佳配置进行实验。请记住,对于我们支持的三个任务(即与RQ1、RQ2和RQ3对应的任务),16,779个测试集实例是相同的三元组<ms, cnl, mr>。唯一的区别是:在RQ1中,模型被训练(并测试)以ms为输入并产生mr;在RQ2中,它以ms和cnl为输入并产生mr;在RQ3中,它以ms为输入并产生cnl。

通过在测试集上运行模型,我们报告了三个任务中的“完美预测”百分比,即模型输出的结果是预期结果的情况。例如,在RQ3的情况下,这意味着模型能够在给定ms作为输入的情况下,生成一个与审查ms的审查者手写的评论cnl完全相同的评论。

除了计算完美预测,在RQ3中(即要求模型生成自然语言文本的任务),我们还计算预测的BLEU(双语评估替代)分数[32]。BLEU评估自动生成文本的质量。BLEU分数在0到1之间变化,其中1表示在我们的案例中,模型生成的自然语言评论与审查者手写的评论完全相同。我们使用BLEU-4变体,该变体计算生成文本和参考文本之间4-gram的重叠。

在RQ1和RQ2中(即要求模型生成代码的任务),我们改用CodeBLEU[37],这是一个最近提出的相似性度量,灵感来自BLEU分数,但专门用来评估自动生成代码的质量。

与BLEU不同,CodeBLEU不仅计算“基于n-gram的相似性”,还考虑生成代码和参考代码的抽象语法树和数据流的相似性。Ren等人[37]提出的CodeBLEU,显示出它们的度量与开发者对代码相似性的感知相比,与BLEU度量更为相关。

关于RQ4,我们比较了T5模型有无预训练的结果(即,完美预测、BLEU、CodeBLEU)。我们还使用McNemar检验和完美预测的赔率比(ORs)统计比较两个模型(即,有/无预训练)。至于预测的置信度,我们采用表现最好的模型(即,带有预训练的模型),并将其预测分为十个基于置信度c的桶,c从0.0到1.0,步长为0.1(即,第一个区间包括所有置信度c为0 < c ≤ 0.1的预测,最后一个区间为0.9 < c ≤ 1)。然后,我们报告每个区间的完美预测百分比。

最后,在RQ5中,我们将T5与我们之前工作中自动化的两个任务(即与我们的RQ1和RQ2相关的任务)的基线进行比较。

在比较中,我们使用了完美预测的百分比和预测的CodeBLEU作为度量。我们在几种情景中比较了这两种技术。首先,我们使用了[46]中的数据集,其中包含17,194个三元组<ms, cnl, mr>。在对这个数据集进行一些检查后,我们注意到一些实例(97个)的评论(cnl)未用英语编写或包含无效的unicode字符,这阻碍了我们的分词器的工作。因此,我们将这些实例从作者共享的训练集和测试集中排除。然后,训练集被用来(i)训练基线[46];及(ii)对没有任何预训练的T5模型进行微调。这样,我们可以比较两个模型在完全相同数据上训练时的测试集表现。

值得注意的是,基线在抽象代码上进行了训练和测试,而T5直接使用原始源代码工作。

此外,我们还报告了在测试集上运行的预训练T5模型的性能。这个预训练模型使用[46]中的训练数据集进行了微调。显然,这种分析偏向于T5,因为它接受了更多数据的训练(即,预训练数据集)。然而,这提供了关于预训练的作用以及T5模型的整体效果的额外线索。除了报告描述性统计数据,我们还使用McNemar测试和完美预测的赔率比(ORs)来统计比较两个模型。由于涉及多重比较(例如,将预训练和非预训练模型与基线比较),我们使用Holm修正调整p值。

4 结果讨论

我们从回答RQ1-RQ3(第4.1节)开始,展示了我们旨在自动化的三项任务中T5的性能。然后,我们讨论预训练对性能的影响以及置信度水平作为预测质量代理的可靠性(第4.2节)。最后,我们将T5与基线进行比较(第4.3节)。

4.1 RQ1-RQ3:T5的性能

图1为每项任务报告了两个图表。顶部的折线图显示了T5在不同束宽(x轴)下达到的完美预测百分比(y轴);连续线代表预训练的模型版本,而虚线代表非预训练的版本。底部的箱形图报告了两个代码生成任务(即代码到代码和代码&评论到代码)的CodeBLEU,以及生成文本的代码到评论任务的BLEU分数。浅蓝色代表预训练模型。

我们首先评论完美预测(折线图)。模型的性能第一眼看来可能相当低。例如,在代码到代码的情况下,当k=1(即,T5提出单一预测)时,预训练和非预训练模型均实现了约5%的完美预测(分别正确预测了751和863个实例,带预训练和不带预训练)。然而,这样的结果应该在最先进技术报告的背景下考虑,后者在一个更简单的测试数据集上,对于同一任务和相同束宽,实现了2.91%的完美预测。

对于代码&评论到代码的任务,在k = 1时,预训练的T5可以生成14.08%(2,363个实例)的完美预测,而非预训练的为12.06%(2,024个实例)。在我们之前的工作[46]中,我们在一个更简单的数据集上实现了12.16%的完美预测。我们在RQ5中直接比较了这两种方法。

有趣的是,将束宽从1增加到10,对所有任务只带来了边际改进。最大的改进发生在代码&评论到代码任务中,预训练模型的完美预测从14.08%(k = 1)提升到18.88%(k = 10)。考虑到我们方法的目标,我们认为在k = 1时达到的性能最为相关。实际上,向开发者提供多个建议进行检查可能是适得其反的,特别是考虑到在两个代码生成任务中,推荐的都是完整的方法。

转向代码到评论的任务,T5在制定与审查者书写的评论完全相同的自然语言评论方面存在困难。在k = 1时,预训练模型生成了356个正确的评论(2.12%),而非预训练模型为324个(1.93%)。这些数字在k = 10时只有轻微的增加,预训练达到的最高完美预测为2.44%。

图2的上部展示了模型为每个任务生成的完美预测的两个例子。一个虚线在每个任务内部分隔了两个例子。对于代码到代码的任务,每个例子中的第一段代码代表模型的输入,第二段则是其输出。我们用粗体突出显示了模型更改的代码部分,并替换了方法中不相关的部分以节省空间。在第一个代码到代码的例子中,T5移除了一个不必要的instanceof检查,因为FileSystemDataset是Dataset的子类。相反,第二个例子简化了检查集群存在的过程,提供了一个有意义的错误信息。这第二种情况不能由基线[支持,因为它需要引入输入代码中不存在的新代码令牌。这些是完美的预测,实施的更改与开发者在代码审查期间执行的更改完全相同。

对于代码&评论到代码的任务,模型提供的输入包括审查者写的评论,并要求对以橙色突出显示的代码部分进行特定更改。在第一个例子中,审查者建议使用特定对象进行空检查,T5正确实现了更改。

第二个例子很有趣,因为尽管审查者突出显示了返回 null 作为他们评论的相关代码(“else 是多余的”),但模型正确地理解了需要采取的行动是去除不必要的 else 语句。

最后,对于代码到评论的任务,我们报告了作为输入提供给模型的代码(第一行)和它生成的输出评论(第二行)。在第一个例子中,T5建议(如实际审查者所做)添加空检查,并展示了其实现所需的代码。这段代码不仅仅是一个模板,而且适用于提供的输入代码(它涉及到供应商对象)。在第二个例子中,T5建议重命名一个标识符,提供了有效的重命名建议。

查看图1底部的结果,在所有束宽和两个代码生成任务中,CodeBLEU显示的中位数高于0.80。然而,虽然我们为了完整性和与类似工作保持一致而报告这些值,但它们对预测质量的说明很少,主要用于希望与我们的方法进行比较的未来工作(完整分布在我们的复现包中可用)。实际上,出于两个原因,这些值很难正确解读。首先,没有公认的阈值可以声称良好的性能。其次,就像在之前的工作中提出的那样,这些模型以代码片段为输入,并以某种方式“修订”后的相同代码为输出(例如,修复了一个错误,添加了一个单独的语句,或实施了与审查相关的更改),我们在预测代码和目标代码之间计算了CodeBLEU(在我们的案例中是两种方法)。然而,提供给模型的输入已经与目标输出非常相似,这意味着一个以方法为输入且不在其上实施任何更改的模型,很可能获得高CodeBLEU值。因此,我们主要关注完美预测的讨论。关于在代码到评论任务中达到的BLEU分数,中位数在0.10左右(见图1)。鉴于此任务所达到的完美预测的低百分比,这样的结果是预期的。

回到完美预测,图1中折线图报告的结果代表了我们方法性能的下限。实际上,我们只有在预测与参考完全相同的情况下,才认为它是“完美”的。例如,在代码到评论任务中,由T5生成的自然语言评论只有在与参考评论完全相同,包括标点符号时,才被分类为正确。

虽然T5生成的自然语言评论可能与开发者所写的内容不同,但在语义上等同(例如,“变量v应该是私有的”对比“将v的可见性改为私有”)。对于两个代码生成任务也有类似的观察(例如,审查者的评论可以以不同但语义上等同的方式解决)。

为了了解被分类为“错误”的(即非完美预测)中有多少是有价值的预测,三位作者手动分析了每个任务100个“错误”预测的样本(总共300个)。分析在两次会议中进行,每个实例都由三位作者讨论。目标是将每个实例分类为三个类别之一:(i)“语义等同”(即生成的代码/评论与参考内容不同,但在语义上等同);(ii)“替代方案”(即生成的代码/评论在语义上不等同,但有价值);或(iii)“错误”(即生成的代码/评论对于所提供的输入没有意义)。

由于我们还计算了T5生成的每个预测的置信度,而不是随机选择300个实例进行检查,我们决定针对每个任务按置信度选择前100个错误预测进行目标检查。确实,这些案例特别有趣,因为它们代表了模型非常自信但预测错误的情况。表3显示了我们的手动分析结果。对于代码到代码任务,我们观察到,在大多数情况下(89%),模型实际上生成了与开发者实施的更改不一致的错误预测。这些案例有少数例外,主要与模型做出与开发者不同但仍然有效的决定有关(例如,将字符串提取到变量中,并使用不同的名称作为提取变量)。其他两个任务的结果更有趣。

在代码&评论到代码的情况中,我们发现我们检查的100个“错误”预测中有62个实际上是审查者推荐的更改的有效实现。一个例子在图2的底部呈现(黑色背景),我们展示了提供给模型的输入(即第一行的代码和审查者的评论“内联这个变量”)以及模型的输出。T5成功地解决了审查者的评论。然而,预测与目标实现不同,因为后者还包括了代码审查中未明确要求的另一个更改。这种情况代表了我们为此任务分类为“替代方案”的所有56个实例,鉴于代码&评论到代码的目标,我们认为它们代表了好的预测。

最后,对于代码到评论的任务,我们也发现了大量实际上有价值的“错误”预测,其中36个甚至在语义上是等同的(即T5提出了与审查者要求的更改相同的评论,但使用了不同的措辞)。一个例子在图2的最底部报告。虽然模型只接收到代码作为输入,我们还展示了原始审查者的评论(即“请也将这个变为变量”),以便更容易评估T5生成的评论的相关性。

总体而言,我们的分析显示,完美预测确实代表了T5性能的下限,尤其是涉及自然语言评论的两个任务。

4.2 RQ4:预训练和置信度

在图1中,我们观察到预训练模型在代码&评论到代码和代码到评论任务中表现更好,而非预训练模型在代码到代码任务中表现更佳。在k=1时预测的McNemar测试结果证实了这些发现:除了所有任务中显著的差异性被证实(p值<0.01)外,赔率比(ORs)表明使用预训练模型在代码&评论到代码(OR=1.85)和代码到评论(OR=1.59)任务中获得完美预测的几率分别高出85%和59%,而在代码到代码任务中几率低34%(OR=0.66)。有两点观察值得注意。首先,总体而言,预训练模型似乎代表了更有价值的解决方案。其次,代码到代码任务中缺乏改进可以通过我们进行的预训练和微调来解释。实际上,代码到代码任务只关注源代码,输入和输出中没有自然语言。专注于源代码的微调阶段可能已足够模型学习代码语法和可能的变换。额外的预训练,包括技术英语,对代码到代码任务没有带来好处。其他两个任务则包括输入的自然语言(代码&评论到代码)或要求生成输出的自然语言(代码到评论),从预训练中获得了性能提升。

图3描述了使用预训练模型和k=1时,每个置信区间(从0.0-0.1到0.9-1.0,x轴)中完美预测的百分比(y轴)。为了更好地解释报告的结果,灰线表示考虑所有预测时模型的整体表现(例如,在代码到代码任务中为4.48%的完美预测)。在所有三个任务中,我们观察到一个明显的趋势,最高置信桶(0.9-1.0)的预测确保了远高于整体趋势的表现。仅考虑这个桶中的预测时,完美预测的百分比增加到:代码到代码为14.24%(从整体的4.48%提升),代码&评论到代码为28.23%(整体=14.08%),代码到评论为22.23%(整体=2.12%)。考虑到所处理任务的复杂性,性能的飞跃是实质性的,并表明置信度可以作为预测质量的代理指标。此外,尽管完美预测的百分比相当有限,在最佳情况下(代码&评论到代码为28.23%),十个预测中有七个是错误的,这也值得考虑我们手动分析中观察到的“有价值”的预测,这些预测在我们的定量分析中被分类为“错误”。

4.3 RQ5:与基线的比较

图4比较了T5模型与基线获得的性能。

在折线图中,连续线代表预训练的T5,虚线代表非预训练的T5,点线代表基线。有两个重要的点值得记住:首先,图4中的结果是在测试集上计算的。实际上,与图1中的结果相比,由于这个数据集中的实例更简单,完美预测的性能明显更高(见y轴上的数值)。其次,基线在抽象代码上进行了训练和测试(如原始论文中所做的),而T5则处理原始源代码。

当k=1时,T5取得了显著更好的性能。表4中的统计测试结果总是显示出对T5有显著的差异(调整后的p值<0.01),ORs范围从1.69到11.48。在这种情况下,预训练的T5在两个任务中都表现得比非预训练的更好。这可能是由于在此比较中使用的微调数据集的大小有限。实际上,为了与基线进行公平比较,我们训练集(约13.5k实例)上对T5进行了微调(与我们在回答RQ1-RQ4时的微调数据集约134k实例相比)。这可能不足以有效地训练像T5这样的大型模型,使得预训练中使用的实例对于进一步学习语言是至关重要的。尽管如此,即使没有预训练,T5在k=1时也优于基线。例如,在代码&评论到代码任务中,基线实现了9.48%的完美预测,而非预训练T5为15.46%,预训练T5为29.74%。随着k(即束宽)的增加,基线的改进比T5更强(见图4)。我们认为这是由于使用了抽象代码。实际上,在处理抽象代码时,“搜索空间”(即使用给定词汇表可以生成的可能解决方案的数量)要小得多,因为模型不处理标识符和字面量。在较小的搜索空间中尝试十个预测更可能导致正确的预测。CodeBLEU的结果证实了与完美预测观察到的趋势,预训练的T5是最好的模型。

我们还查看了两种方法在测试集上生成的完美预测的并集,以验证这些技术的互补性。在代码到代码(代码&评论到代码)任务中,我们观察到15%(24%)的完美预测是由两种方法共享的(即,两者都成功),65%(70%)的完美预测仅由T5生成,而20%(6%)的完美预测仅由基线生成。

转述:李昕

0 阅读:6

互联不一般哥

简介:感谢大家的关注