用上下文数据丰富源代码以增强代码补全模型:一项实证研究

互联不一般哥 2024-04-04 11:08:46

Enriching Source Code with Contextual Data for Code Completion Models: An Empirical Study

Tim van Dam, Maliheh Izadi, Arie van Deursen

Delft University of Technology Delft, The Netherlands

引用

T. van Dam, M. Izadi and A. van Deursen, "Enriching Source Code with Contextual Data for Code Completion Models: An Empirical Study," 2023 IEEE/ACM 20th International Conference on Mining Software Repositories (MSR), Melbourne, Australia, 2023, pp. 170-182, doi: 10.1109/MSR59073.2023.00035.

论文:

https://arxiv.org/abs/2304.12269v1

仓库:

https://github.com/AISE-TUDelft/ContextualDataCodeCompletion; https://huggingface.co/AISE-TUDelft/CodeGPT-TS-Multi-Untyped; https://huggingface.co/AISE-TUDelft/UniXcoder-TS-Multi-Untyped

摘要

本文探讨了通过使用上下文数据,比如类型注释和注释,提高代码易理解性是否有助于预训练代码语言模型在代码补全任务中的性能。结果显示,所有模型在删除类型注释后表现更好,而在存在多行注释的情况下效果更加突出。

1 引言

基于Transformer的预训练模型最初在自然语言处理(NLP)领域提出,近期扩展至源代码领域。由于源代码的自然属性以及为定制这些模型而进行的修改,它们目前在许多与代码相关的任务中,如自动代码补全,表现卓越。自动补全技术通过在当前开发环境下建议下一个标记,从而补全源代码语句,帮助开发人员更快地编写程序,纠正打字错误,减少输入工作,并促进API探索,使其成为集成开发环境(IDEs)中最突出的功能之一。

自动补全技术利用两个信息通道:自然语言和算法通道。前者解释程序的上下文,而后者指定计算机执行。注释是一种常见的可选信息形式,可以帮助开发人员更好地理解代码,但不影响程序的运行方式。类型注释是另一种辅助信息形式,帮助开发人员更好地生成和/或理解代码。它们可以提高自动补全的准确性,因为变量的类型通常直接决定了这些变量的交互方式。然而,在动态类型或可选类型语言中通常不存在类型。自动补全模型通常关注代码标记,最近还关注程序结构的某些方面,以提供更好的补全。最近,注释也已在这些模型中得到利用。然而,尚未调查在源代码中以注释和注释类型形式嵌入的上下文信息在最近的大规模预训练语言模型性能中的影响程度。

我们通过进行深入的实证调查,填补了对最近源代码语言模型性能的知识空白。我们考虑了三个公开可用的模型,即UniXcoder 、CodeGPT和InCoder 。我们进行了两个粒度级别的自动补全:下一个标记的预测和行补全。此外,我们基于五个常用的NLP模型评估指标报告了结果。为了保留代码片段的底层语义,我们添加/删除了可选的辅助上下文信息,即类型注释和注释,并生成了同一代码的多个变体。此外,我们研究了删除所有注释以及将注释限制为仅包含单行、多行和文档块注释的效果。

结果显示,所有三个模型在无类型注释的代码上的性能优于有类型注释的代码。为了评估超越性能的显著性。此外,结果表明,多行注释的存在对自动补全性能有显著影响。与先前的情况类似,尽管差异显著,但在这种情况下,效应大小也较小。尽管文档块注释是多行注释的一种形式,但相对于基线,它对性能没有实质性的影响。

本文的主要贡献包括:

针对在TypeScript代码中不同数量的类型注释,对三个大型语言模型在标记和行补全方面的类型信息影响进行了深入的实证评估。对三种格式的自然语言文本信息(单行注释、多行注释和文档块注释)对这些代码语言模型在标记和行补全方面性能的全面实证评估。我们的源代码、数据集和选择性微调模型已经公开可用。

2 引例

图1展示了来自Angular仓库的代码片段示例,包括有和没有类型注释以及注释。缺乏适当的类型信息或文档可能会使开发人员更难正确使用此函数。例如,一个人可能会将字符串提供为值,但将数字树提供为节点。在静态类型语言中,这将导致编译时错误;然而,在动态类型语言中,这将正常运行,可能导致错误进入代码库。因此,自动补全模型在缺乏类型注释的情况下正常工作非常重要,以防止用户通过自动补全引入错误到其代码中。理论上,额外的类型信息应该能够提升自动补全模型的性能,因为它为其提供了对源代码的更全面描述。关于注释也可以说类似,通常用于使用文档块描述完整的函数,或者用单行注释注释代码的较小部分。两者之间的区别在于,类型注释是以结构化的方式放置的,而注释不保证遵循特定的结构。

图1:带有和不带有类型注释和注释的示例代码片段

3 研究背景和相关工作

1)Transformer和预训练模型: 基于Transformer的模型最近在NLP领域显示出极大的潜力,BERT ,一个双向Transformer模型,展示了在训练语言模型时考虑左右上下文的价值。Liu等人进一步改进了BERT,提出了RoBERTa 模型。Raffel等人 提出了Text-to-Text Transformer(T5),将各种NLP目标视为文本到文本(即seq2seq)任务。

2)关于类型注释和注释的研究: 关于类型注释和注释的研究表明,先前的工作主要集中在推断动态类型语言中变量和函数类型的能力上。DeepTyper和NL2Type展示了深度学习在推断类型方面的应用,以从无类型代码过渡到渐进类型代码。LambdaNet和TypeBERT则在渐进类型语言中为无类型代码提供类型注释方面取得了进展。

3)关于自动补全模型的实证研究: Ciniselli等人分析了两个文本语言模型,即T5和RoBERTa,在三个粒度级别(单标记、行、块)上补全代码的性能。作者包含了两个数据集,包含来自开源GitHub仓库的Java方法和Android应用程序方法。他们表明T5的性能更好,但当这些模型被要求预测更长的序列时,它们的成功是有限的。

4 研究设计

我们选择了三个最近发布的面向源代码的语言模型,它们是公开可用的,分别是UniXcoder、CodeGPT和InCoder。

UniXcoder是一个预训练模型,利用多种模态来促进多个代码理解和生成任务。除了源代码之外,还在预训练过程中使用了注释和扁平化的抽象语法树(AST)以提高理解。

InCoder是一种基于Transformer的仅解码器模型,能够基于左右上下文进行代码填充,使用因果建模。它在一个大型数据集上进行了训练,主要包含来自GitHub、GitLab和StackOverflow的Python和JavaScript代码。

CodeGPT是基于GPT-2模型的Transformer模型。陆等人利用GPT-2架构训练了几个与代码相关的语言模型。作者训练了四个模型,两个用于Python,两个用于Java:再自动补全方面作者使用了预训练的基于文本的GPT-2作为起始检查点。

我们的研究包括七个主要阶段,即标记、类型推断、数据分割、预处理、超参数调整、微调和后处理。

图2:研究流程概述

图2展示了我们研究的总体流程。请注意,根据使用的自动补全模型,会采取不同的路径。接下来,我们更详细地描述各个步骤。

a) 标记:在测试微调模型时,标记指示应该执行自动补全的位置。我们在其他阶段之前执行此任务,以确保每个数据集具有相同的补全任务:由于转换代码时注释不会移动,因此我们可以使用它们来跟踪代码中的位置,即使在类型推断和预处理期间发生转换。

b) 类型推断:为了确定类型数量对性能的影响,我们转换我们的TypeScript数据集,以创建另外两个数据集:一个使所有隐含类型显式的数据集,一个没有任何类型注释的数据集。

c)数据分割:我们将所有TypeScript文件分为训练、测试和验证集。文件根据80/10/10的比例进行划分。

d) 预处理:我们对数据进行预处理,以准备微调和评估。首先,我们通过将连续的空格和换行符替换为单个字符来规范化间距和换行。然后,以下步骤被应用于为UniXcoder和CodeGPT进行微调的数据。然后,我们用特殊标记hEOLi替换换行符。这些预处理步骤不适用于馈送给InCoder的数据,因为我们不对这个模型进行微调。因此,InCoder期望原始代码,没有预处理阶段添加的特殊标记。 然后,我们对注释应用不同的预处理变体;这个过程产生了15个不同的TypeScript文件集合。

e) 超参数调整:我们在带有原始类型(TS704-OT)和未修改注释的数据集的25%的训练和验证集上进行超参数调整。我们选择使用一个数据集的25%来限制我们实验的计算负担。

f) 微调:接下来,我们在所有数据集上对UniXcoder和CodeGPT进行微调。也就是说,对于每种类型注释变体和每种注释变体。这导致了15个不同的数据集,因此对于UniXcoder和CodeGPT有15个不同的微调模型。我们使用在超参数调整期间找到的超参数。UniXcoder和CodeGPT将hEOLi标记视为序列标记的结束,导致微调模型预测到每行的末尾。

g) 后处理:在微调之后,使用30个微调模型和InCoder生成15个测试集的预测。随后对这些预测进行后处理,包括规范化代码标记的间距(包括换行符),删除所有注释,以及将文本化的字面值的标记版本替换为默认字面值。

5 实验设置

A. 研究问题

• RQ1:这些模型的性能如何受到代码中可用类型注释比例的影响?

• RQ2:丰富源代码上下文与文本信息(即注释)的影响是什么?

B. 数据集

TS704-OT,作为第一个数据集,包含GitHub上前1000个最受欢迎的存储库的子集。通过查询GitHub Search API检索了该数据集。GitHub API返回了总共851个唯一的存储库。为了防止偏见,我们对我们的数据集进行了去重,与用于UniXcoder、CodeGPT和InCoder的(预训练)存储库进行了对比。为了回答第一个RQ,我们基于TS704-OT数据集创建了一个额外的数据集,其中删除了所有类型注释(TS704-NT)。为此,我们使用TypeScript Compiler API遍历每个TypeScript文件的抽象语法树,删除遇到的任何类型注释。此外,我们创建了一个数据集,使用TypeScript编译器使所有隐式类型变得明确(TS704-AT)。

C. 评估指标

EM(精确匹配)将基本事实与预测进行比较,并返回一个布尔值。整个数据集上的EM分数以百分比表示。数值越高越好。

ES(编辑相似性)或Levenshtein相似性按字符比较基本事实和预测。错误的字符(替换)、字符过多(插入)和字符过少(删除)将使Levenshtein距离增加1。ES是一个在范围[0,1]内的数字,通过将Levenshtein距离除以预测或基本事实的长度(取决于哪个更长)来计算。

BLEU-4是BLEU(双语评估基准)的变体,专门处理n-grams,其中n ∈ [1, 4]。BLEU通过计算在预测和基本事实中同时出现的n-gram与基本事实中n-gram的总数之比来比较它们。

ROUGE-L是ROUGE(用于Gisting评估的召回导向型基准)指标的变体,它使用最长公共子序列算法找到在预测和基本事实中同时出现的最大n-gram。ROUGE-L计算精度和召回,并使用它们计算F1分数。

METEOR(用于评估具有明确顺序的翻译的度量)通过映射它们各自的一元组来比较基本事实和预测,并根据映射的精度和召回计算得分。此外,根据映射对真实一元组顺序的遵循程度应用了惩罚。我们使用参数α = 0.9、β = 3.0和γ = 0.5应用METEOR。

D. 实现和配置

我们使用 ts-morph 与 TypeScript 编译器 API 进行交互,以在删除或添加类型注释时进行操作。 在行补全任务创建、预处理和评估阶段,我们使用 js-tokens 对 TypeScript 代码进行标记化,以添加和删除特定类型的注释。

我们使用 UniXcoder 的作者发布的微调源代码进行 UniXcoder 的微调。我们还使用了作者发布的代码进行 CodeGPT 的微调。对这两个脚本进行了轻微的调整,以使它们与我们的数据集文件兼容。

对于 UniXcoder,我们使用初始学习速率为 7.33e-5 和批大小为,这是根据超参数调整的结果(表 I)。我们将最大输入序列长度设置为 936,最大输出序列长度设置为 64,束搜索大小设置为 5,其余参数设置为 UniXcoder 作者发布的微调代码中的默认值。然后我们对 UniXcoder 进行 10 个时期的训练。

表一:调整后的超参数

对于 CodeGPT,我们使用初始学习速率为 1.37e-4 和批大小为 2(表 I)。其余参数设置为默认值。然后我们对 CodeGPT 进行 10 个时期的训练。

我们不对 InCoder 进行微调,因为它没有公开可用的微调代码。

6 结果和讨论

A. RQ1: 类型注释的影响

为了衡量类型注释对自动补全性能的影响,我们使用不同类型明确性比率的数据集的测试集运行三个模型。然后,我们报告每个任务(即行和标记补全)的结果。接下来,为了确定哪种性能差异是显著的,我们进行了威尔科克森符号秩检验。在下表中,我们将 BLEU-4、ROUGE-L 和 METEOR 指标分别缩写为 B4、RL 和 MR。

表二:类型注释的影响(行补全、无注释数据)

1)行补全:表 II 显示了每个模型在无注释的代码上(带有或不带有类型注释)的表现。

移除类型注释对所有三个模型都导致最佳性能。在所有情况下,这种超越是统计上显著但相当小的,尤其是对于 InCoder。这种性能差异可以通过 UniXcoder 和 InCoder 预先训练在包含大量 JavaScript 的语料库上解释几乎与没有类型注释的 TypeScript 相同的事实来解释。然而,值得注意的是,即使 CodeGPT 在其预训练数据中没有任何源代码,它也在没有类型注释的代码上显示出更好的性能。这与添加类型注释可能会提高自动补全性能的理念相矛盾。一个可能的解释是源代码标记对模型比类型注释更有价值,去除类型注释为源代码标记留下了更多的输入空间。

向部分类型化的代码添加类型注释改善了自动补全,但效果不如完全删除类型注释。对于 UniXcoder 和 CodeGPT,添加类型注释导致所有度量标准的性能显著提高。对于 InCoder,观察到了类似的改进,除了 Exact Match 外的所有度量标准。在所有情况下,效果大小要小得多,表明实际效用有限。尽管无法仅从我们的实验中确定这一点,但这些发现可能表明,强类型语言比渐进类型的语言(如 TypeScript)更容易解释。然而,删除类型注释再次导致性能提升更大,这再次表明源代码可能是输入的更重要部分,而不是类型注释。

表三:类型注释的影响(标记补全、无注释数据)

2)标记补全:表III 显示了每个模型在无注释代码上的标记补全任务上的表现。

删除类型注释又一次表现出最佳性能。所有模型的性能提升在统计上都是显著的,效果大小与行级补全相似。

添加类型注释改善了自动补全性能。对于 UniXcoder 和 CodeGPT,这种优越性在所有度量标准上都是显著的;而对于 InCoder,仅对于 Exact Match 是显著的。在所有模型中,效果大小都很小。 总体而言,标记补全的结果与行补全的结果相似。删除类型注释导致的性能提升大于添加类型注释。仅从我们的实验中无法清楚地解释这一点,然而我们推测这可能表明被测试模型更擅长解释源代码而不是类型注释,因此值得去除类型注释以为源代码标记腾出更多空间。

回答RQ1:在几乎所有的度量标准上,所有模型在无类型代码上表现最佳。类型注释并不一定能增强自动补全模型解释和补全代码的能力。此外,对于具有高类型明确性(TS704-AT)的代码,性能显著优于具有正常类型明确性比率的代码(TS704-OT)。这可能表明在渐进类型语言中,类型注释的不规则性可能使语言模型更难解释可选类型的语言。比较效果大小表明,从TypeScript代码中剥离(而不是添加)类型注释是提高自动补全性能的更好选择。我们推测这些观察结果表明源代码对这些模型更有价值,而不是类型注释。因此,去除类型注释以允许模型使用更多源代码可能会提高性能。

B.RQ2:注释的影响

我们运行UniXcoder、CodeGPT和InCoder,针对TS704-NT的测试集,包含不同类型的注释,以确定特定类型的注释是否影响自动补全性能。同样,我们使用威尔科克森符号秩检验来确定显著性,并考虑行和标记补全。

表四:行补全中注释的影响,TS704-NT

1) 行补全:表IV显示了每个模型在包含不同类型注释的代码上的表现。

保留所有注释或多行注释对于所有模型和所有度量标准都导致了最佳性能。这意味着嵌入在多行注释中的信息可以最好地理解源代码。UniXcoder、CodeGPT和InCoder都是在包含英文文本的语料库上(预)训练的,这可能解释了它们在具有多行注释的代码上相对于没有这些注释的代码的性能。尽管效果大小较小,但在所有三个模型上,对这些类型注释的性能提升是统计学显著的。

保留所有注释与仅保留多行注释相比,对UniXcoder和CodeGPT具有类似的效果,但对于InCoder,具体来说,多行注释具有较大的效果大小。

仅保留文档块注释并没有带来实质性或显著的性能提升,尽管文档块是多行注释的一种类型。这进一步强调了多行注释内的自然语言描述引起性能提升,而不是文档块信息,例如参数类型和用途。

仅保留单行注释通常不会像保留多行注释那样引起性能提升。这可能可以通过注释长度的差异来解释:单行注释通常比多行注释小得多。对于UniXcoder,单行注释似乎引起了一个非常小但统计学显著的性能增加。然而,对于InCoder,这些注释具有统计学显著的负面效应,效果较小。单行注释对于CodeGPT的性能没有显著影响。

表五:标记补全中注释的影响,TS704-NT

2) 标记补全:表V显示了所有模型在包含不同类型注释的代码上的标记补全性能。

保留所有注释或多行注释再次导致了最佳性能。这种性能优势在所有三个模型中都是显著的,并且具有最大的效果大小。再次,当保留多行注释和所有注释时,UniXcoder和CodeGPT具有相似的效果大小,而当仅保留多行注释时,InCoder似乎更受益。

仅保留文档块注释再次并未导致显著的性能提升,尽管文档块注释是多行注释的一种类型。

仅保留单行注释显著影响所有模型的性能。对于UniXcoder和InCoder,效果大小略微正面和负面,这在行补全时也是如此。对于CodeGPT,我们观察到略微的、统计学上显著的负面效应,而在行补全时没有观察到这一点。

总体而言,标记补全的结果与行补全的结果一致:不同类型的注释以不同的方式影响自动补全性能,效果大小较小。对于这三个模型,多行注释似乎具有最大的正面效果。

回答RQ2:这三个模型在包含所有注释或仅多行注释的代码上表现最佳。文档块注释的存在(这是多行注释的一种类型)并没有导致显著的性能提升,这表明多行注释的价值来自其中嵌入的自然语言。此外,单行注释并不总是具有相同的效果。尽管包含多行注释的代码表现最佳,但效果大小相对较小。然而,这些类型的注释确实为代码补全模型提供了一些价值。结果表明,这三个代码补全模型可以充分解释包含在多行注释中的自然语言描述。其他类型的注释似乎不会进一步提高自动补全性能,表明这些注释可以在不牺牲性能的情况下省略。

C. 讨论和建议

实验结果表明,自动补全性能的观察差异在统计上是显著的,因此它们不是随机的。然而,效果大小较小,这可能表明在实践中影响有限。

总的来说,我们认为帮助最近的LLM在语法上理解输入可能不是推动具有数百万或数十亿参数的最新模型发展的最佳方式。相反,提供不同类型的上下文信息可能有助于拓宽代码补全模型的范围。

7 总结

源代码标记并非在使用代码语言模型时利用上下文的唯一信息源。可选的类型注释和以注释形式的自然语言文本可以为源代码提供有价值的信息。然而,并非所有这些额外信息都对自动补全模型的语言理解能力提供帮助。在这项工作中,我们研究了这些额外信息源在三个最近的源代码语言模型中的影响。我们的结果显示,并非所有可选的信息通道都对这三个模型有价值。具体而言,类型注释被证明对这些模型产生负面影响,而多行注释的存在则提升了它们的性能。未来的研究可以探讨不同类型的上下文线索如何影响代码语言模型的性能。

转述:朱轶凡

0 阅读:0

互联不一般哥

简介:感谢大家的关注