CIRCLE:跨编程语言的持续修复

互联不一般哥 2024-06-01 21:32:46

CIRCLE: Continual Repair across Programming Languages

Wei Yuan, Quanjun Zhang, Tieke He, Chunrong Fang, Nguyen Quoc Viet Hung, Xiaodong Hao, Hongzhi Yin

引用

Yuan W, Zhang Q, He T, et al. CIRCLE: Continual Repair across Programming Languages[J]. arXiv preprint arXiv:2205.10956, 2022.

论文:https://arxiv.org/pdf/2205.10956

摘要

CIRCLE是一种新颖的自动程序修复(APR)框架,它通过持续学习能力,解决了现有基于深度学习的APR模型在多语言支持和在线适应性方面的局限性。CIRCLE利用提示函数将自然语言处理(NLP)预训练任务与APR任务相衔接,并通过基于难度的复习策略实现终身学习,无需依赖完整历史数据。此外,CIRCLE采用了弹性正则化方法来增强其持续学习能力,有效防止了灾难性遗忘问题。为了处理跨语言编程时可能出现的错误,CIRCLE还引入了一种简单有效的重新修复方法。实验表明,CIRCLE能够在持续学习的环境中高效地修复多种编程语言的代码,并且使用单一修复模型就达到了最先进的性能水平。

1 引言

自动程序修复(APR)对软件开发者至关重要,因为手动检测和修复漏洞不仅劳动密集而且耗时。近年来,随着深度学习(DL)的进展,许多APR方法被提出,使用神经网络技术从可访问的代码库中学习修复模式,基于监督学习优化编码器-解码器模型,以学习基于bug修复对的潜在模式。这些基于DL的APR在性能上取得了显著成果,通常将程序修复任务视为从错误代码到修复代码的翻译。

然而,现有的基于DL的APR技术存在至少两个限制。首先,大多数技术只能修复单一语言的漏洞。如果想要修复多种语言的代码,就必须训练和存储相应数量的模型,并且这些模型是独立训练的,限制了它们从其他语言学习到的知识迁移能力。其次,当前的APR模型是离线开发的,因此它们不能持续改进其修复能力,这降低了DL-based APR在现实世界场景中的价值,因为在现实世界中新任务需求在不断增加。

为了解决这些问题,我们提出了CIRCLE(ContInual Repair aCross Programming LanguagEs的缩写),这是一个能够跨多种编程语言持续学习修复漏洞的基于T5的APR框架。CIRCLE包含五个组成部分:基于提示的数据表示、基于T5的模型、基于难度的例子复现、基于EWC的正则化以及重新修复机制。具体来说,CIRCLE利用一个提示模板将程序修复转换为填空任务,缩小了T5预训练任务和APR任务之间的差距。基于难度的复现和EWC正则化是两种终身学习策略,使CIRCLE能够根据递增的任务需求持续更新其参数。最后,应用了一个简单但有效的重新修复方法,以消除由多种语言修复引起的形式错误。

据我们所知,这是第一次构建一个同时解决基于持续学习方法的多种编程语言的APR模型。我们在4种编程语言的5个基准测试上进行了广泛的实验,以证明我们的CIRCLE的有效性。实验结果表明,我们的CIRCLE(1)能够跨语言持续学习修复漏洞;(2)使用单一模型在所有基准测试上实现了最先进的性能。

2 方法

CIRCLE是一个能够持续学习修复多个编程语言中缺陷的神经APR模型。本节中,我们将介绍CIRCLE的设计和实现。首先,在2.1节中介绍CIRCLE的概览。CIRCLE旨在实现持续学习和多语言修复,这比以往的基于DL的APR更为复杂且实用。CIRCLE由五部分组成:首先,CIRCLE利用一个大型预训练模型作为其模型框架以获得强大的学习能力(第2.3节),同时,它采用提示函数来有效微调预训练模型(第2.2节)。接着,CIRCLE使用一种新颖的基于难度的复习方法(第2.4节)和基于参数重要性的正则化(第2.5节)来解决遗忘问题。最后,采用一种简单但有效的重新修复方法来消除跨语言生成错误所造成的问题(第2.6节)。

图1 整体方法概览

2.1 概述

图1展示了我们方法的概览。正如右上角部分所示,由于其持续的程序修复学习能力,CIRCLE能够处理任务需求的不断增长。不失一般性,我们假设新的编程语言修复任务随时间推移到达,并带有相应的数据集。CIRCLE能够基于任务到达顺序自动逐个学习这些任务,无需重新训练或存储所有先前任务的数据。对于每个任务,CIRCLE包括两个阶段:训练阶段和测试阶段。在训练阶段,首先手动设计的提示函数将修复输入转换为填空形式。我们的训练集由两个子集组成:当前任务语料库和从先前任务中选择的一些示例。然后,这些处理过的数据被输入到基于T5的APR模型中。T5使用子词标记方法来解决词汇表外(OOV)问题。与之前的作品不同,我们保留了原始的标记词汇表,而不是使用字节对编码(BPE)算法构建新的词汇表。因为(1)我们希望APR模型继承自然语言理解能力,从一个好的起点开始学习修复;(2)BPE需要统计子词的频率,然而,跨语言时频率会动态变化。基于T5的APR生成候选补丁,并应用损失函数来评估这一生成。为了减轻灾难性遗忘,CIRCLE需要仔细更新其参数。因此,采用了EWC正则化来计算每个参数对先前任务的“重要性”,避免这些更“重要”的参数发生太大变化。最后,APR模型根据损失和EWC正则化进行更新。当训练收敛后,我们使用训练良好的APR模型通过新颖的数据选择方案从当前任务语料库中选择一小套示例。这些选定的示例被存储起来,以便在即将到来的任务训练中重放。在每个任务的推理阶段,APR模型接收所有见过的语言的bug代码,并通过生成一组候选补丁来修复它们。在多编程语言修复场景中,APR模型很容易由于语言交叉而错误生成一些关键字。例如,Java和JavaScript中的“null”在程序和自然语言方面与Python中的“None”非常相似。在某些情况下,我们的APR模型会在这些关键字上犯错误。然而,这类关键字的数量并不多。我们简单地构建了一个简单的映射表,在模型生成后将它们转换为相应的关键字。

2.2 提示基础的数据表示

CIRCLE的输入由两部分组成:错误代码和上下文代码。传统的方法尝试分别对这两部分进行编码然后将编码向量合并,但如何有效融合这些分离的编码向量并消除编码器之间的语义差距仍然是一个值得探讨的问题。最近的研究提出,在微调预训练模型时,通过在输入中添加提示(prompt)可以缩小预训练任务和下游任务之间的差距。因此,CIRCLE采用了一个手工设计的提示模板,将错误代码和相应的上下文转换成统一的填空格式。具体来说,我们使用“Buggy line:”和“Context:”来标记错误行和上下文代码,然后用“The fixed code is:”引导预训练模型根据前面的输入生成修复后的程序。由于T5在自然语言上的预训练中已经熟悉填空任务,因此它更容易在提示数据上进行微调。此外,CIRCLE使用子词标记法来解决词汇表外(OOV)问题,但并没有为此构建新的标记词汇表,因为我们希望充分利用T5的预训练知识,而且整个数据集的标记频率在跨语言时是不可用的。换句话说,将T5微调到APR任务在某种程度上可以被视为“领域适应”任务,即下游任务与预训练任务之间的差距较小。

图2 传统输入和我们基于提示的输入的比较。绿色文本代表提示,指示每个输入组件的语义含义。提示输入将错误修复任务表述为填空任务,类似于T5的预训练任务。

2.3 T5作为APR模型框架

T5模型是一个基于变换器(transformer)的编码器-解码器模型,它在超过750 GB的数据集上进行了预训练,并在多种自然语言处理(NLP)任务上取得了最先进的性能。T5的编码器由多个变换器块堆叠而成,每个块包含多头自注意力层和逐位置的前馈网络。为了稳定训练过程,还在每个子组件之间应用了层归一化、残差连接和dropout操作。T5的解码器与编码器类似,但在自注意力之后有注意力机制,以便它可以关注编码器的输出,并且注意力是因果性的,以防止解码过程中的信息泄露。

在本研究中,我们使用预训练的“t5-base”版本作为训练起点,没有修改词汇表大小。T5模型的不同尺寸具有不同数量的层和注意力头。在CIRCLE中,我们采用T5作为自动程序修复(APR)模型的基础,利用其强大的学习能力和在多种任务上的卓越性能。T5模型的预训练特性使其能够捕捉到程序代码的潜在模式,为CIRCLE提供了一个坚实的基础,以实现跨多种编程语言的持续学习修复能力。

2.4 基于难度的例子复现

CIRCLE的一个关键创新是它能够使APR模型持续学习修复程序错误。为实现这一点,CIRCLE结合了基于难度的例子复现和基于EWC的正则化,以避免遗忘。例子复现的核心思想是从先前的数据集中保留一小部分样本,并在后续训练中重放这些样本,使模型能够在不严重遗忘的同时,不需要重新训练整个历史数据。

为了选择有效的复现样本,CIRCLE提出了基于难度的选择标准。这个标准背后的直觉是,难度较高的数据可能包含更多信息,对改进当前模型更有用。由于模型在之前的训练中对这些难度较高的数据表现不佳,它可能首先忘记了从这些数据中学到的模式。我们根据以下标准选择代表性和多样性数据:

其中,和是任务t中的第i对数据。是在任务t中已经训练好的模型。是基于模型参数的损失函数(例如交叉熵)。

由于较长的句子往往会累积更高的损失值,我们使用y的长度的倒数作为归一化因子。基本上,公式1反映了模型在修正数据时的置信度,其中较高的值表示更难以学习。我们从任务t的数据集中收集可以最大化公式1的N个样本,作为困难样本集。N远小于整个数据集。因此,对于每个任务i,我们维护一个对应的样本集。

在APR的持续训练过程中,先前选择的困难样本集会被整合到即将到来的任务t+1的数据集中。因此,训练的目标是找到能够最小化组合数据集损失的:

2.5 基于采样的EWC正则化

由于选择的样本集的大小应尽可能小以减少计算和存储成本,记住旧模式的效果不够强。因此,我们进一步应用一个约束来避免模型丧失以前的知识。这个约束基于弹性权重巩固(Elastic Weight Consolidation,EWC),它被广泛采用以克服持续学习中的灾难性遗忘。EWC 根据参数对以前任务的重要性对参数施加限制。参数在以前任务中越重要,EWC 对它们的更新限制就越严格。EWC 使用费舍尔矩阵作为权重重要性的度量,其计算如下:

测量了中第个参数的重要性。被用来规范模型的更新:

其中,是控制EWC正则化贡献的超参数。然而,直接计算公式3是不可扩展的,因为它需要访问所有的历史数据。为了有效地近似EWC,我们只在通过我们的基于难度选择方案收集的数据上计算费舍尔矩阵,即。此外,我们仅从中采样M个项目以进一步减少计算成本。最后,训练的目标函数变为:

2.6 跨语言重新修复方法

CIRCLE在经过持续训练后,能够学习多种编程语言的潜在修复模式并生成正确的补丁。但由于编程语言具有严格的语法规则,与自然语言不同,CIRCLE在处理多语言修复时仍面临几个问题。首先是“关键字不匹配”问题,即CIRCLE可能生成在语义上相同但属于其他编程语言的关键字。例如,在修复Java漏洞时,CIRCLE可能错误地生成了Python中的“None”关键字。

为解决这一问题,CIRCLE采用了一个简单的映射表,将这些不匹配的关键字转换为相应的正确关键字。此外,还存在“格式不匹配”问题,指的是生成了正确的令牌但格式错误,例如将“==”生成为“= =”导致语法错误。CIRCLE通过构建正则表达式来移除不必要的空格,解决了这一问题。最后,由于T5模型的分词器是为自然语言设计的,尽管采用了WordPiece算法,但仍对一些罕见符号存在词汇表外问题。CIRCLE的重新修复机制会将这些未知标记替换为特殊的符号。

通过这些策略,CIRCLE能够跨多种编程语言有效地生成和修正代码补丁,提高了自动程序修复的准确性和可靠性。

表1 关键字映射表示例

3 实验设计及结果

3.1 研究问题

在本文中,我们提出了以下几个研究问题来进行实验和评估:

RQ1: CIRCLE是否能有效学习在不断增长的任务需求场景下进行跨语言的程序缺陷修复?

RQ2: 单一CIRCLE模型与专门训练的最先进自动程序修复(APR)方法相比表现如何?

RQ3: CIRCLE的不同组成部分对整体性能有何贡献?

在实验设计中,我们使用了CoCoNut的训练数据作为CIRCLE方法的训练语料库。为了确保评估的真实性,我们移除了2006年之后提交的Java数据。原始数据集的规模非常庞大,包含了Java、Python、C和JavaScript的缺陷修复对,数量分别为3,241,966、480,777、2,735,506和3,217,093。由于计算能力的限制,我们随机选择了剩余数据的一部分(约40万)进行训练。实验结果表明,即使只使用了数据集的一部分,CIRCLE仍然能够展现出卓越的性能。我们还采用了提示模板将上下文数据和错误数据连接起来,并在子词标记后将长度超过512的输入进行了截断处理。

对于RQ1,为了更好地符合现实世界中持续学习的场景,我们假设CIRCLE按照语言的流行度顺序学习不同的语言修复任务:从JavaScript开始,接着是Python、Java,最后是C/C++。这种设置是合理的,因为在实践中,公司通常会首先为最受欢迎的部分构建工具,然后逐步完善其他部分。最终训练完成的CIRCLE模型被用于与所有最先进的APR模型进行比较,以回答RQ2。

在实验的具体实施方面,我们基于PyTorch框架构建了所有的方法,并采用了HuggingFace提供的T5模型的实现版本。我们选择了“t5-base”作为训练的起点,考虑到先前工作的推荐和我们设备的限制。优化器采用的是AdamW,并设置了3e-4的学习率。对于每个任务,我们最多训练20个周期,如果验证损失在3个周期后没有下降,则会提前停止训练。批量大小设置为64,输入的最大长度设定为512,而EWC的超参数值为110000。在推理阶段,我们使用了250的束搜索,并在每个步骤的标记选择中应用了top-k和top-p采样。然后,我们使用前文提到的映射表对生成的补丁进行了重修复,最终CIRCLE最多生成了1000个候选补丁。为了评估目的,三位作者手动验证了合理的补丁,并且只有当三位作者一致认为与真实数据在语义上等价时,才认为补丁是正确的。所有的训练和评估都是在一台装有八个Tesla V100-SXM2 GPU的CentOS 7.7服务器上进行的。

对于基准测试和基线,我们使用了五个基准测试,涵盖了Java、Python、C和JavaScript这四种流行的编程语言:Defects4J和QuixBugs用于Java,BugIDs用于JavaScript,ManyBugs用于C,以及QuixBugs用于Python。这些基准测试在先前的APR工作中都已被采用。为了验证CIRCLE的持续学习能力,我们训练了一个传统微调方式的T5模型作为基线。微调方式意味着随着任务的进展,模型以上一个任务获得的检查点进行初始化,然后使用当前任务的数据进行微调。这个模型被命名为Finetuned-APR。对于RQ2,我们采用了五个常用的APR基准测试,包含了现实世界中的缺陷。为了进行全面评估,我们将CIRCLE与30种APR技术进行了比较,这些技术涵盖了不同的编程语言和技术类别。具体来说,我们考虑了之前评估中的所有APR工具以及三种最新的基于NMT的工具。

RQ1 CIRCLE在持续增长的任务需求场景中的学习效果

实验设计:为了评估CIRCLE在任务需求不断增长的情况下学习修复程序缺陷的能力,我们将CIRCLE与Finetuned-APR进行了比较。Finetuned-APR是直接以微调的方式训练的模型,不包含持续学习模块。实验中,我们跟踪了模型在每个任务中以及之前学习的任务中能够修复的缺陷数量。例如,在模型学习了Java语言的修复任务后,我们计算了它在JavaScript、Python和Java的基准测试中修复缺陷的能力。

实验结果:实验结果显示,CIRCLE在所有任务中的表现均优于Finetuned-APR。特别是在历史任务上,CIRCLE显著优于Finetuned-APR。例如,在完成Java任务学习后,Finetuned-APR在Python的QuixBugs基准测试中只能修复18个缺陷,而CIRCLE能够修复26个。这一发现支持了我们提出的持续学习模块的有效性。即使在当前任务上,CIRCLE也展现出了比Finetuned-APR更好的性能。例如,在Python任务中,Finetuned-APR正确生成了23个缺陷的补丁,而CIRCLE则正确生成了28个。

此外,为了展示CIRCLE的持续学习能力,我们在图3中展示了CIRCLE和Finetuned-APR在任务进展中的性能趋势。图中的点值(x)/(y)报告了模型在所有已见任务上的表现,其中"x"显示了模型在每个基准测试上的表现,而"y"是"x"的总和。CIRCLE和Finetuned-APR在第一个任务上表现相同,但随着任务的进展,Finetuned-APR逐渐落后于CIRCLE。图3还展示了估计的上限性能,即CIRCLE或Finetuned-APR在每个基准测试上达到的最佳性能,这表明了CIRCLE仍然遭受的遗忘问题的程度。CIRCLE略微落后于估计的上限,与Finetuned-APR相比,进一步表明了我们持续学习策略的有效性。

图3 在“任务要求不断增加”的情境下,传统训练方法与我们的 CIRCLE 的性能趋势比较。点值 (x)/(y) 报告模型在所有已见任务上的表现。"x" 中的数字显示了按以下顺序在每个基准测试上的性能:BugAID、QuixBugs-Py、Defects4J、QuixBugs-Java 和 ManyBugs。"y" 是 "x" 的总和。

RQ2: CIRCLE与专门训练的最先进APR方法性能比较

实验设计:为了评估CIRCLE的性能,我们降最终训练的CIRCLE模型用于四个编程语言的五个bug基准测试,与包括传统和基于深度学习(DL)的最先进APR技术进行比较。

表2 与最先进技术的比较

图4 不同方法修复的错误之间的重叠

实验结果:实验结果表明,单一的CIRCLR模型在不同语言的缺陷修复上达到了最先进的性能水平。在Java基准测试中,CIRCLE在QuickBugs上修复了19个bug,与CoCoNut相当,并与CURE竞争。值得注意的是,这19个bug是通过精准匹配计算的,这通常是正确补丁的最小数量,而CoCoNut和CURE运行所有生成的补丁对抗测试套件。同时,CIRCLE在Defects4Jv1.2上正确修复了64个bug,超过了所有先前的传统APR技术。特别是,CIRCLE比最先进的传统方法(例如TBar)多修复了23.1%(12个bug)。此外,CIRCLE还比大多数基于DL的APR技术(例如CoCoNut 和 CURE)修复了更多的bug。CIRCLE还被发现与最新的基于DL的方法Recoder竞争,后者被报道为首个超越传统APR方法的基于DL的APR方法。

对于其他三种语言的基准测试,CIRCLE在三个基准测试中的两个上表现最佳(即在QuickBugsPY上修复了19个bug,在ManyBugs上修复了9个bug,在BugAID上修复了5个bug),表明了CIRCLE使用单一模型跨不同编程语言的修复能力。值得注意的是,尽管大多数现有的基于DL的方法(例如CURE)考虑了复杂的代码感知特性(例如代码编辑和抽象语法树),而CIRCLE将程序修复过程视为简单的机器翻译任务,并设置了250的束大小,而CURE的束配置为1000。根据之前的作品,更大的训练集和束大小可能会导致更好的修复性能。尽管如此,CIRCLE仍然超越了大多数最先进的APR技术。因此,我们强调了跨多种编程语言的持续学习能力方向对于自动程序修复的重要性。

RQ3 生成的测试用例的质量

实验设计

为了深入理解CIRCLE中各个组成部分的影响,我们关注了四个关键组件:基于提示的数据表示、跨语言重修复机制、持续学习模块。我们通过一系列对照实验,逐一评估了这些组件对CIRCLE整体性能的贡献。

图5 有无提示输入对于同一模型验证损失的影响

图6 BREADTH_FIRST_SEARCH在QuixBugs-Py中的关键词不匹配示例

图7 BugAID中INCORRECT_COMPARISON1_2011的格式不匹配示例

图8 Defects4J中Closure #63的罕见符号示例

图9 每个基准上修复的错误数量,无论是否使用重新修复机制

图10 Defects4J中Chart #9的持续学习示例

实验结果

我们发现,使用基于提示的输入表示可以显著提高模型的收敛速度。具体来说,与没有使用提示的输入相比,提示输入在相同数量的训练周期内帮助模型达到了更低的验证损失。我们引入的重修复机制有效解决了“关键词不匹配”、“格式不匹配”和“罕见符号”问题。实验统计显示,该机制在所有基准测试中提高了修复bug的数量。例如,在图6中,我们展示了CIRCLE如何通过重修复机制纠正了Python中的关键词不匹配问题;在图7中,展示了如何修正了BugAID中的格式不匹配问题;在图8中,展示了如何处理Defects4J中的罕见符号问题。CIRCLE的持续学习模块主要由基于难度的样本复现和基于EWC的正则化组成。通过与没有持续学习模块的Finetune-APR模型进行比较,我们发现持续学习模块有助于CIRCLE在连续任务中保持性能。例如,在图10中,展示了CIRCLE在Java任务中生成了与开发者相同的正确补丁,并且在随后的C/C++任务中没有遗忘之前学习的知识,而Finetune-APR模型则遗忘了生成特定条件。

转述:田方源

0 阅读:0

互联不一般哥

简介:感谢大家的关注