One Adapter for All Programming Languages? Adapter Tuning for Code Search and Summarization
Deze Wang
引用
Wang, Deze, et al. "One adapter for all programming languages? adapter tuning for code search and summarization." 2023 IEEE/ACM 45th International Conference on Software Engineering (ICSE). IEEE, 2023.
论文: One Adapter for All Programming Languages? Adapter Tuning for Code Search and Summarization
摘要
这篇文章简要介绍了由于预训练模型可以自动执行许多代码智能任务,因此常用的做法是在各种编程语言的任务数据集上对模型进行微调。虽然最近研究表明,多语言微调对多个任务和模型有益,但我们发现这会导致最新模型UniXcoder和CodeT5的性能下降。为了解决多语言模型中的灾难性遗忘问题,我们固定所有预训练模型参数,插入高效的适配器进行微调。相比每种编程语言的全模型微调,适配器调优仅更新0.6%的参数,在代码搜索和摘要任务中实现了一致改进,达到最先进的效果。实验还表明,这种方法在跨语言和低资源场景下非常有效。对每种编程语言进行200个样本的多语言微调几乎能达到对整个数据集进行代码摘要微调的效果。我们的三个探测任务实验表明,适配器调优显著优于全模型调优,并有效克服了灾难性遗忘问题。
1 引言
深度学习模型广泛应用于各种编程语言的任务中,如Java、Python、Ruby等,特别是在代码搜索和总结方面。当前的标准做法是加载预训练的语言模型,并在特定编程语言的数据集上对其进行训练和评估。这意味着每种编程语言都需要单独的模型,导致N种编程语言需要N个独立模型,这些模型的训练、部署和维护都十分复杂。
自然语言处理(NLP)领域的研究表明,暴露模型于多种语言可以提高低资源语言的性能。不同于自然语言,多种编程语言具有相似的语法形式,并且从同一预训练模型微调的单语言模型将共享公共词汇表。因此,多语言训练可能更有利于编程语言之间的知识转移。
Ahmed和Devanbu[4]的相关研究表明,多语言微调基于预训练代码模型CodeBERT[5]和GraphCodeBERT[6],在代码搜索和总结任务上带来了几乎一致的改进。然而,我们的实验发现,这一结论不能推广到最新的预训练代码模型UniXcoder[8]和CodeT5[9]。在这些模型上,多语言微调导致大多数编程语言的代码搜索和总结任务的性能下降。训练一个能够同时支持多种编程语言并保持与专用单语言模型相当性能的模型是一个挑战。
许多研究表明,多语言模型可能由于灾难性遗忘预训练知识而遭受负迁移。我们通过固定预训练模型的所有参数,插入适配器并进行微调,发现它可以缓解上述问题。此外,通过探测实验分析,我们证明了适配器调优的有效性。
我们在不同的预训练代码模型中引入适配器模块。与为每种编程语言训练整个模型相比,适配器调优只需调整不超过0.6%的参数。新模型在代码搜索和总结任务上取得了一致的性能提升,并达到最先进的结果。实验还表明,在代码总结任务中,每种语言仅需200个样本的适配器调优几乎能达到对整个数据集调优的效果,表明快速适应新的编程语言是可能的。此外,我们展示了它在跨语言任务中的有效性,并进行了语言探测实验以说明其效果。
本文的主要贡献如下:
我们发现,多语言微调在CodeBERT和GraphCodeBERT上取得显著性能提升,但在UniXcoder和CodeT5上却导致性能下降。我们通过三个探测任务展示了适配器调优可以显著克服多语言调优中的灾难性遗忘问题。基于预训练的UniXcoder和CodeT5,我们的模型在六种编程语言的代码搜索和总结任务中调整不超过0.6%的参数,获得一致的性能提升并达到最先进的结果。我们表明,每种编程语言200个样本(原始数据集的0.02%)的适配器调优在代码总结任务中表现良好。2 实验方案介绍
为了研究源代码理解和生成的多语言任务,我们提出了一系列研究问题并设计了相应的研究方法。
2.1 RQ1:多语言微调在不同模型和下游任务上的表现如何?
Ahmed和Devanbu[4]发现多语言微调可以使CodeBERT和GraphCodeBERT受益,我们研究了这一发现是否可以推广到其他预训练模型。
**RQ1设计:** 我们在代码搜索和代码总结任务上使用多语言数据集对UniXcoder和CodeT5进行微调。多语言数据集包含六种编程语言的数据:Python、Java、JavaScript、Ruby、Go和PHP。我们评估了这些模型在多语言数据集和单语言数据集上的表现,并与CodeBERT、GraphCodeBERT和PLBART进行比较。
2.2 RQ2:适配器调优在跨语言场景中的效果如何?
跨语言任务涉及使用一种编程语言的数据集微调预训练模型,并用另一种编程语言进行测试。我们关注适配器调优在跨语言任务中的有效性,通过插入适配器并调整少量参数进行实验。 RQ2设计: 我们用六个单语言数据集对UniXcoder和CodeT5进行微调,并在所有编程语言上进行测试。对于适配器调优,我们为每种语言调优适配器,并在所有编程语言上测试。我们还比较了适配器调优与全模型调优在跨语言任务上的表现。
2.3 适配器调优在不同预训练模型上的多语言微调效果如何?
多语言任务需要模型在多种编程语言上表现出色。我们研究适配器调优是否能有效解决多语言任务。RQ3设计: 我们将适配器插入到UniXcoder和CodeT5中,并用多语言数据集对适配器进行微调。然后在代码搜索和代码总结任务上评估适配器的性能。我们将这些多语言模型的表现与相应的单语言模型以及全模型微调的多语言模型进行比较。
2.4 多语言微调在低资源情况下的效果如何?
对于多语言模型,我们期望它能够快速学习和推广到新的编程语言。然而,许多编程语言的标记数据有限。因此,我们对现有数据集进行采样,研究在低资源场景下多语言学习的性能。RQ4设计: 我们从每种编程语言的数据集中随机抽取100、200、500和1000个样本。然后将适配器插入到CodeT5中,并根据这些数据的组合进行模型评估。改变随机种子,重复实验多次,并对结果进行平均,以检验多语言学习的有效性。
2.5 为什么适配器调优在上述场景中优于全模型调优?
尽管适配器调优在上述场景中显示出优越的性能,但我们需要进一步探讨其原因。我们使用探测实验来评估这一点。RQ5设计: 我们通过探测实验评估多个模型的隐藏状态嵌入,测量它们捕获代码相关基本特征的能力。我们采用码长预测、圈复杂度和无效类型检测三个探测任务,这些任务分别对应源代码的表层信息、语法信息和语义信息。在对下游任务上的模型进行微调后,我们提取探测任务上的预训练向量嵌入,以检查模型对代码信息的理解。特别是,我们验证适配器调优在上述场景中的有效性是否在探测实验中得到一致体现。
3 实验评估
为了在提交消息生成任务中验证第5节中的提案,我们做了两个实验:
(1) 比较使用所有代码修改作为输入和仅使用添加或删除的行作为输入的提交消息生成结果。
(2) 消融研究多个初始模型权值,寻找PL和NL在语境表征上差距最小的权值。
3.1 实验设置
从CodeXGLUE中选择数据集,该数据集结合了CodeSearchNet[18],并仔细地进行了重复数据删除。表1显示了该数据集的统计信息。该数据集包含六种编程语言的代码片段和自然语言描述对,包括Python、Java、JavaScript、Ruby、Go和PHP。从表中可以看出,不同编程语言的数据大小有显著差异。特别是Ruby和JavaScript的数据集比其他编程语言的数据集要小得多。
代码搜索:我们使用的数据集改编自相同的CodeSearchNet数据集,并添加了郭等人[6]的候选代码。除了用于检索的额外候选代码外,该数据集与我们用于代码汇总的数据集相同。
探测任务的数据集:我们采用Karmakar和robes[14]的数据集进行探测任务。具体来说,对于LEN任务,长度标签被设置为5个class-bin(0-50、50-100等)。CPX的复杂度标签是用metrix++工具得到的,取值范围是0 ~ 9。TYP任务的代码段根据是否包含无效类型分为两类。每个任务的数据集包含10k个样本,并且是类平衡的。
代码总结:根据前面的工作,我们使用平滑的BLEU-4[19]作为评估指标。它是一种基于精度的度量,测量生成文本和真实文本之间的n-gram几何精度。我们还遵循之前的工作,并对编程语言的BLEU进行平均,以报告总体得分。
代码搜索:我们使用平均倒数秩(MRR)作为评估指标。MRR是一组查询结果的倒数秩的平均值。查询的倒数排名是第一个命中结果排名的倒数。
探测任务:我们使用的所有探测任务都是分类任务,我们使用分类精度作为这些任务的度量标准。
我们的代码都是在Pytorch1中实现的。我们从Huggingface2加载预训练模型,同时保持其超参数设置。由于适配器调整比全模型微调调整更少的参数,我们将UniXcoder的学习率设置为5e−5,CodeT5设置为1e−4。我们在下游任务上重现这些模型的结果,并在下面给出它们。
对于适配器设置,我们将适配器插入到编码器和解码器的自关注层和前馈层的后面。适配器的尺寸设置为128。所有实验均在4张NVIDIA Tesla V100显卡上进行,每张显卡的显存为32GB。
3.2 实验结果
A. RQ1:多语言微调如何在不同的模型和下游任务上执行?
a)代码汇总:在本小节中,我们比较了基于不同预训练模型的多语言和单语言微调对代码汇总任务的结果。单语言微调是在每种语言的数据集上对一个模型进行微调的原始方法,而多语言微调只对所有编程语言的相同大小的一个模型进行微调。预训练的模型包括CodeBERT、GraphCodeBERT、PLBART、UniXcoder和CodeT5,其中CodeT5是用于此任务的最先进的模型。
结果见表二。我们用前缀m表示多语言微调模型,因为mCodeBERT是基于CodeBERT微调的多语言模型。CodeBERT和GraphCodeBERT的结果来自Ahmed和Devanbu[4]。可以清楚地看到,基于CodeBERT和GraphCodeBERT的多语言微调结果明显优于单语言微调,并且多语言微调在所有六种编程语言中都显示出其有效性。总的来说,改进也很显著,CodeBERT改进了6.90%,GraphCodeBERT改进了5.64%。
然而,在PLBART、UniXcoder和CodeT5上,结果显示出不同的趋势。PLBART、UniXcoder和CodeT5的总分反而下降了。PLBART的结果来自其开放源代码存储库3,作者在其中对多语言代码摘要和生成任务进行了探索性实验。这两个任务的结果表明,多语言微调会导致大多数编程语言的性能下降。在UniXcoder上,多语言调优会导致一半编程语言的性能下降。在CodeT5上,多语言调优只在Ruby上得到改进。
b)代码搜索:我们还测试了多语言微调对代码搜索的有效性,结果如表III所示。因为CodeT5不报告代码的结果搜索时,我们只比较基于CodeBERT、GraphCodeBERT和UniXcoder的结果。UniXcoder是这项任务的最先进的模型。
从表III中可以看出,多语言微调在CodeBERT和GraphCodeBERT上显示了有效性,在所有编程语言上优于每种语言单独的微调。在UniXcoder上,多语言调优只在Ruby上得到改善,而在其他编程语言上则有所下降。多语言微调极大地提高了Ruby的性能,这应该归功于它的数据大小。它的数据集具有最小的数据大小,从表1可以注意到,其他编程语言的数据集甚至比它的数据集大两到十倍。
在这两个任务上,我们的实验反映出相似的结果。多语言微调在UniXcoder和CodeT5上不再像在CodeBERT和GraphCodeBERT上那样有效。它只在低资源语言中表现出优势,这与以往的研究结果[1-3]一致,即低资源语言在多语言学习中可以通过正向知识迁移而受益。而在其他编程语言中,多语言微调的结果比单语言微调的结果更差。原因可能是由于在同一模型上学习多种编程语言而导致的灾难性遗忘。
发现1:基于CodeBERT和GraphCodeBERT,多语言微调在所有编程语言中都显示出其有效性。在UniXcoder和CodeT5上,多语言微调有利于低资源语言,同时导致其他编程语言的性能下降。
B. RQ2:跨语言场景中的适配器调优效果如何?
当针对多种编程语言对整个模型进行微调时,由于灾难性的遗忘导致性能下降,我们选择适配器调优来修复大多数预训练参数并更新少量参数。我们首先尝试将适配器调优应用于代码摘要和代码搜索的跨语言场景。
a)代码总结:基于CodeT5,我们比较了适配器调优和全模型微调的性能关于跨语言场景中的代码摘要。我们用一种编程语言对数据集上的模型参数进行微调,并用另一种编程语言对数据集上的模型进行评估。
为了直观地了解性能差异,图2显示了适配器调优相对于全模型微调的BLEU-4改进。
图2所示. 跨语言代码摘要任务上适配器调优相对于全模型调优的BLEU-4改进
纵轴为训练集的编程语言,横轴为评估的编程语言。图2显示,在大多数跨语言任务上,适配器调优比全模型调优执行得更好。唯一的例外是,在Go语言中调优的适配器在大多数编程语言中的表现都不如全模型调优。值得注意的是,其他编程语言中的适配器调优在Go语言中也没有明显的好处。对于适配器来说,Go语言中的数据不容易适应。可能需要涉及更多的数据或参数。
b)代码搜索:在代码搜索任务上,我们还测试了基于UniXcoder的适配器调优与全模型调优的相对性能,如图3所示。
图3所示. 跨语言代码搜索任务上适配器调优相对全模型调优的MRR改进
在大多数跨语言任务中,适配器调优在参数更新较少的情况下优于全模型调优。适配器调优不如全模型调优的任务主要分布在图的对角线部分。这些任务是用同一种语言进行训练和评估的,而不是跨语言任务。在这些单语言任务中,与适配器调优相比,全模型微调允许调整更多参数以适应任务,而不必担心灾难性的遗忘。
发现2:在大多数跨语言场景的代码搜索和汇总任务中,适配器调优比全模型调优更有效。
C. RQ3:适配器调优在不同预训练模型上的多语言全模型微调效果如何?
当适配器调优证明其在跨语言场景中的有效性时,我们将进一步探讨其在多语言任务中的性能。
a)代码汇总:代码汇总任务的对比结果如表4所示。
前缀为m的模型是一个多语言模型,它只需要一组参数,其他模型必须分别训练六种编程语言的模型。与多语言全模型调优相比,基于UniXcoder和CodeT5的适配器调优在所有编程语言中都显示出其有效性。与最初的微调相比,madapter还在大多数编程语言中以更少的参数更新优于各种单语言模型。
为了验证madapter对预训练模型的改进是否优于多语言全模型微调,我们采用单侧两两t检验进行统计分析。对于CodeT5上的所有六种语言和UniXcoder上的大多数语言,零假设被拒绝。很明显,madapter在代码总结方面比多语言训练有统计学上的显著改进。
b)代码搜索:在代码搜索上,如表5所示,适配器调优在大多数编程语言中也优于单语言和多语言全模型调优。
对于Ruby和JavaScript等低资源编程语言,这种改进尤为显著。统计分析结果表明,除了Ruby的p值为0.004之外,madapter比多语言训练的改进在所有编程语言中都具有统计学意义(p <0.001)。
发现3:虽然更新的参数较少,但适配器调优在多语言学习中比全模型调优更有效。此外,它还优于针对每种编程语言分别进行微调的结果。
D. RQ4:在低资源情况下多语言微调的效果如何?
多语言模型是根据多种编程语言的数据进行微调的。联合训练和随之而来的正迁移有利于各种编程语言的学习。实际上,许多编程语言缺乏高质量的标记数据。因此,我们评估是否可以用非常有限的数据来学习每种编程语言的性能良好的多语言模型。
我们从六种编程语言的数据集中均等地抽取训练样例,并收集大小为600、1200、3000和6000的多个多语言数据集。我们在这些数据集上对mAdapter-CodeT5进行微调,并将结果与整个数据集上的适配器调优进行比较。结果见表六。
从表中可以看出,随着训练样本数量的增加,适配器调优的性能也在不断提高。每种编程语言使用100个样本的训练结果与其他结果之间存在显著差异。此时,模型无法收敛因为缺乏训练数据。当每种编程语言使用200个样本进行训练时,微调整个数据集的差异小于2 BLEU-4。当每种语言的样本数量增加到1000时,与基线的差异小于1 BLEU-4。显然,多语言训练对于低资源代码总结任务是有效的。
可以注意到,随着数据的增加,模型性能的增长逐渐放缓。在最后一行和倒数第二行的对比中,即使超过90万个样本,也只带来不超过1个BLEU-4分数的提升。这一方面表明,预训练模型具有足够的鲁棒性,可以在数据很少的情况下快速适应下游任务。另一方面,它展示了多语言微调的潜力,可以充分利用多语言数据在低资源场景下快速收敛模型。
发现4:多语言微调在低资源代码汇总任务中非常有效,以至于可以使用每种编程语言很少的样本来接近整个数据集的微调结果。
E. RQ5:为什么在上述场景中适配器调优优于全模型调优?
适配器调优在跨语言、多语言和低资源场景中通过更新很少的参数来证明其有效性。为了在细粒度级别检查模型并检查适配器的行为是否一致,我们应用探测实验来检查模型是否编码了一组代码特征。我们分别采用LEN任务、CPX任务和TYP任务探测代码表面信息、结构信息和语义信息。
具体来说,我们将适配器插入BERT[20]、CodeBERT、GraphCodeBERT和UniXcoder,并在代码搜索上与原始模型一起对它们进行微调。然后,我们从这些模型的隐藏层中提取特征向量,并训练一个简单的线性分类器将这些特征向量与不同的代码特征相关联。由于线性分类器没有隐藏单元,它在探测任务上的性能很大程度上取决于这些模型的特征向量。
我们从这些模型的最终隐藏层中提取特征向量进行探测,结果如表7所示。总的来说,这些模型在无效类型预测方面表现最好。Adapter-UniXcoder在这个任务上达到了92.65%的准确率,表明该模型确实编码了这样的语义。
Adapter-BERT在码长预测上表现最好,准确率为65.20%。这个任务本质上是预测输入序列中token的数量,其他模型的额外预训练任务可能会损害这个任务。这一发现也与Karmakar和robes[14]在探究式实验中得出的结论一致。在圈复杂度任务上,Adapter-GraphCodeBERT是性能最好的模型。因为GraphCodeBERT是专门针对结构信息进行预训练的,所以这个结果并不意外。然而,它相对于其他模型的优势并不明显,也许是因为任务本身更具挑战性。
进一步将经过适配器微调的模型与表7中的原始模型进行比较,会发现它们的性能存在相当显著的差异,特别是在BERT、CodeBERT和GraphCodeBERT上。在所有探测任务中,具有适配器调优的模型明显优于原始模型。
为了更清楚地比较适配器调优与原始模型,我们还提取了所有先前层的隐藏表示作为特征向量,并评估它们在探测任务上的性能。图4显示了三个探测任务的性能随BERT、CodeBERT和graphcodebert上隐藏层索引的变化情况图中的虚线表示带有适配器调优的模型在探测任务中的性能。所有模型的详细结果显示在下面的匿名存储库中。
相同颜色的线条代表相应的原始型号的性能。总的来说,尽管每个探测任务对应于不同的代码特征进行检查,但在这些任务的模型之间的准确性变化有很大的相似性。在前6个隐藏层中,适配器调优和全模型微调的性能差别不大。在随后的隐藏层中,适配器调优与原始调优之间的性能差距日益突出。
应该注意的是,这些任务是针对代码搜索进行微调的。然而,这些探测任务所需的代码特征并不总是对下游任务有帮助。
因此,不同模型在探测任务上的精度在最后一层隐藏层上下降。由于适配器调优在最后一个隐藏层中的性能明显优于全模型微调,因此具有适配器调优的模型比原始模型编码更多的信息。相反,全模型微调遭受灾难性的遗忘和丢弃这些信息。这些代码特征是低级别的信息,与下游任务所需的信息相比,它们是跨语言的通用信息。因此,在此信息的帮助下,适配器调优可以在跨语言和多语言任务上执行得更好。我们推测,这就是适配器调优可以减轻灾难性遗忘问题的原因。
从精度的整体变化可以看出,LEN上的精度随着隐藏层的深度而降低。而CPX的精度则先升高后逐渐降低。除了自然语言预训练模型BERT外,CodeBERT和GraphCodeBERT在TYP的中间隐藏层也达到了最好的性能。由于LEN对应于探测表面信息,CPX和TYP对应于代码的结构和语义信息,因此很明显,这些模型在较低层学习表面信息,在较深层学习结构和语义特征。这一结论与以往的研究一致。
从每个任务的角度来看,Adapter-BERT在LEN上始终保持较高的精度,而所有其他模型都丢弃了一些表面信息。这个调查任务显示了源代码和自然语言预训练模型在处理代码下游任务方面的区别。在CPX任务中,GraphCodeBERT在浅层隐藏层中保持了较高的准确性,这证实了GraphCodeBERT对结构信息进行了充分的编码。然而,CPX的性能在最后隐藏层显著下降。下游任务可能不需要涉及圈复杂度信息。
在TYP任务中,经过适配器调优的不同模型最终具有较高的准确性,并且该语义信息可以有效地参与下游任务。准确度的变化在GraphCodeBERT上始终是最好的,其次是CodeBERT,然后是BERT。这也与预训练模型在下游任务中的表现相吻合。
发现5:在所有探测任务上,适配器调优明显优于全模型调优。此外,我们观察到这些模型的隐藏层从浅到深逐渐编码更高级别的信息,与之前的研究一致。
转述:韩廷旭