代码补全相关论文 An Empirical Study on the Usage of Transformer Models for Code Completion

代码补全技术大多在预测下一个token的场景中进行评估,只有少数例外扩展到了预测整个代码语句。因此,对于最先进的代码补全方法在更具挑战性的场景中(例如必须生成整个代码块)性能知之甚少。

作者进行了大规模研究,探索了最先进的基于 Transformer 的模型在支持不同粒度级别代码补全方面的能力,包括单个token、一条或多条完整语句,甚至整个代码块(例如 for 循环的迭代块)。我们在代码补全任务中实验了两种基于 Transformer 模型的几种变体,即 RoBERTa 和 Text-To-Text Transfer Transformer(T5)。结果显示,基于 Transformer 的模型,特别是 T5,是代码补全的可行解决方案,其最佳预测率(perfect prediction)从要求模型猜测整个代码块时的约 29% 到在较简单的场景中(从同一代码语句中掩盖[masked]少量标记)的约 69%。

Introduction

尽管代码补全技术的性能随着时间的推移有了显著提升,但它们对开发者提供的支持类型并没有同等速度地提升。事实上,除了少数几个关注于预测多个代码tokens或甚至预测整个语句的工作外,文献中提出的大多数方法仅在特定场景中进行了实验,即预测开发者可能输入的下一个token。这就留下了一个问题:基于深度学习的token预测能够达到什么程度(甚至超越源代码行的边界)?

作者展开了一项大规模实证研究,探索最先进的深度学习模型支持的代码补全的局限性和能力。除了生成开发者可能编写的下一个token之外,作者还应用深度学习模型来生成整个语句和代码块(例如 if 语句的主体)。众多深度学习模型中,作者重点关注使用 Transformer 架构的模型。 特别是,在作者最近在 MSR 2021 上发表的工作中,作者评估了 RoBERTa 模型在上述代码完成任务中的性能。RoBERTa 是使用特殊的 <MASK> 标记屏蔽输入句子中的随机单词,其中模型负责预测屏蔽单词来进行预训练任务的BERT(来自 Transformers 的双向编码器表示)模型。

在尝试使用 RoBERTa 完成代码完成任务时,面临一个重要的限制,该限制使其不适合研究(即多个屏蔽标记的预测):在 RoBERTa 预训练任务中,n 个<MASK> token 必须用于mask n 个代码token,从而隐式向模型建议必须生成多少代码标记才能自动完成屏蔽语句。这在实际使用场景中是不现实的,即代码补全引擎必须猜测(guess)要生成的token,不需要开发者建议必须生成多少个令牌。为了克服这一限制,我们必须调整 RoBERTa 的预训练目标,使其能够从单个 <MASK> token(mask给定语句中的一个或多个代码token)中猜测出需要生成哪些代码token以及生成多少代码token。这种调整受最近提出的T5架构的影响,说明T5架构适合于代码补全任务

在这项工作中,作者拓展了他们的 MSR 2021 论文,表明 T5 大大超越了 RoBERTa 模型的性能,甚至能够正确预测整个代码块,这是 RoBERTa 无法实现的。正如作者在MSR 2021那篇论文中所示,作者关注三种代码预测场景:(i)token-level预测,即经典代码补全,其中模型用于预测开发人员开始编写的语句中的最后 n 个token; (ii) construct-level预测,其中模型用于预测特定的代码构造(例如 if 语句的条件),这对开发人员在编写代码时特别有用; (iii) block-level预测,屏蔽代码跨越构成代码块的一个或多个完整语句(例如,for 循环的迭代块)。

作者比较了几种模型的性能。 首先,我们使用之前的论文中提出的 RoBERTa 模型作为类 BERT 模型的代表。 其次,本文首次使用T5模型来完成代码补全任务。 最近的研究表明,T5 在代码相关任务中的性能优于许多最先进的技术。 最近的论文展示了训练单个 T5 模型处理四个与代码相关的任务的可能性,即错误修复、代码突变体注入、断言语句生成和代码摘要。 第三,作者用 n-gram 模型作为基于深度学习的模型的baseline进行实验,也展示了 Hellendoorn 和 Devanbu 提出的缓存机制对其性能的影响。

RoBERTa 和 T5 模型都分两个阶段进行训练

  1. 预训练

    允许定义可用于一大类sequence-to-sequence任务的共享知识库(例如,猜测英语句子中的掩码词[masked words]以了解该语言)

  2. 微调

    专门针对特定下游任务的模型(例如,学习从英语到德语的句子翻译)。微调中也能使用多个任务来尽可能利用迁移学习(transfer learning),即模型可以将在一项任务中获取的知识重用于另一项任务。

    例如,在多个翻译任务(例如,从英语到德语、英语到法语、法语到德语)上训练的单个模型可能比分别在特定翻译任务(例如,英语到德语)上训练的三个不同模型更有效。

作者希望通过研究预训练任务和跨不同任务的迁移学习对模型性能所起的作用来研究两种基于 Transformer 的模型的性能。 然而,由于这需要对实验模型的许多不同变体进行训练,因此我们采用以下策略:

  1. 通过针对我们的三种代码完成场景(即token-level级、construct-level级和block-level级)训练三种不同的模型来比较 RoBERTa 和 T5。 这意味着创建三个不同的 RoBERTa 和 T5 模型(总共六个模型)。

  2. 选择表现最好的一个(T5),使用预训练可以提高其性能,尽管影响有限。

  3. 作者展示了微调单个 T5 模型以支持所有三个预测任务可以提高性能,从而确认三个非常相似的任务之间的迁移学习(即在一项任务中获得的知识可以用于执行另一项任务)。

所取得的结果表明,对于典型的代码补全任务(即token-level级别),T5在66%到 69%的情况下正确预测所有masked token(取决于所使用的数据集),而RoBERTa 的正确率达到39%到52%,n-gram模型为42%到44%。

值得注意的是,研究的目的并不是要证明 T5 模型是基于神经(neural-based)的代码补全的最佳选择。 工作重点是实证探索基于学习(learning-based)的代码补全技术的能力,T5、RoBERTa 和 n-gram 模型被选为最先进的模型代表。

综上所述,与 MSR 2021 论文相比,这项工作的贡献如下:

  1. 作者使用另一种最先进的方法(即 T5 模型)进行了全面的实证研究,显示了其在代码完成任务方面非常有前途的性能;

  2. 与先前的论文不同,其中三种不同的 RoBERTa 模型在三种代码完成场景(即token-level级、construct-level级和block-level级)上进行了微调,无需预训练,也无需测试迁移学习的影响,我们预训练并微调性能最佳模型的几个版本(即 T5),以研究这些方面;

  3. 对于表现最佳的模型,作者还探索了利用预测置信度作为预测质量衡量标准的可能性,显示了该指标的可靠性。

Research Questions And Context

研究目标是评估基于 Transformer 的深度学习模型在预测不同粒度级别的masked code tokens方面的有效性。 有以下研究问题 (RQ):

  1. $RQ_1:Transformer 模型在多大程度上是一种学习如何自动补全代码的可行方法?$

    研究T5和RoBERTa可用于预测丢失代码token的程度。 作者从定量(即BLEU得分 、Levenshtein距离)和定性(即完美预测、错误预测的潜在有用性)角度评估生成的预测的质量。 RQ1在以下两个子RQ中进一步详细说明:

    • $RQ_{1.1}:masked\ token的数量在多大程度上影响预测质量?$​

      在数据集上训练和测试实验的方法,其中masked code token涵盖从给定语句中的几个连续token到构成代码块的多个缺失语句。 RQ1.1探讨Transformer 模型在考虑简单和更具挑战性的代码补全场景时的局限性。

    • $RQ_{1.2}:模型的性能在多大程度上受到用于训练和训练的数据集的特殊性的影响?$​

      虽然可以合理地预期更大的训练数据集往往有助于深度学习模型,但作者从不同的角度回答 RQ1.2。

      比较了两个不同数据集上的自动完成性能:第一个是更通用的,由 Java 方法组成; 第二个更具体,由 Android 应用程序的方法组成。虽然编程语言相同,两个数据集的粒度相同(即方法级粒度),但是第二个数据集中的方法大量使用 Android API,并且相同的 API 很可能用于类似的目的,例如,处理 GPS 定位的应用程序功能共享通用的 API 用法。作者希望这能够在 Android 数据集中创建“规律性”,以帮助模型学习。

  2. $RQ_2:预训练和迁移学习对于基于 Transformer 的模型的性能有何作用?$

    RoBERTa 和 T5 都可以进行预训练,然后针对多项任务进行微调。 RQ2 研究 (i) 模型预训练和 (ii) 在多个任务上微调单个模型以利用迁移学习带来的性能提升(如果有)。 仅针对性能最佳的模型(即 T5)进行了此类附加分析。

  3. $RQ_3:Transformer 模型与最先进的 n-gram 模型相比如何?$

    DL 模型的替代方案是基于 n-gram 的统计语言模型。 在这个研究问题中,将深度学习模型与(i)经典的 n-gram 模型进行比较,(ii)在较小的研究中与先进的 n-gram 缓存模型进行比较 。

Context Selection: Datasets

Fine-Tuning Dataset

研究包含两个数据集。

  • 第一个数据集来自于作者的MSR‘21的文章,被用来微调RoBERTa和T5模型以及训练n-gram模型。将此数据集称为微调数据集,它包括 Java 和 Android 数据集以允许回答 RQ1.2。 微调数据集是从 CodeSearch Net 数据集构建的,该数据集包含从开源项目中挖掘的 Java 方法。

    • 从CodeSearchNet Java数据集来构建研究所用数据集,因为 Code SearchNet 已经经过清理步骤,使其适合代码机器学习的应用。此外,CodeSearchNet 已经按照方法级别的粒度进行组织(即一个实例是一种方法),而其他数据集则收集整个存储库。

    • CodeSearchNet 包含从开源、非分支、GitHub 存储库收集的超过 150 万个 Java 方法。数据集构建中使用的最重要的标准是:

      • 排除少于三行的方法

      • 使用 CodeSearchNet 中的重复数据删除算法删除近似重复的方法

        ——这样做是为了不因训练集和测试集之间的重叠实例而夸大模型的性能

      • 删除名称中包含“test”子字符串的方法,以试图删除测试方法;名为“toString”的方法也被删除,因为后者通常由 IDE 自动生成,具有非常相似的结构。因此,它们很少代表具有挑战性的代码补全场景,并且可能导致预测准确性过高。

  • 第二个数据集是专门为回答 RQ2 而构建的,即拥有一个可用于预训练 RoBERTa 和 T5 中性能最佳的模型(即预训练数据集)的不同的数据集。

    • 从 GitHub 克隆了 AndroidTimeMachine 数据集中的 8,431 个开源 Android 应用程序。 然后从每个项目的最新快照中提取方法列表。 这总共产生了 220 万个方法。应用为 Java 数据集定义的相同过滤启发式方法,最终得到 654,224 个方法。
    • 由于研究的目标之一也是比较当模型应用于更通用(Java)和更具体(Android)数据集时,我们从 Java 数据集中随机选择(使用随机 Python 函数)654,224 个方法,以匹配 Android 数据集的大小

作者还尝试了之前研究中使用的代码抽象,以避免开放词汇问题。 然而,由于使用了利用字节对编码(Byte Pair Encoding - BPE)等技术的分词器,基于深度学习的新模型不再受到这种限制。 因此,虽然在之前的论文中作者构建了两个版本的微调数据集(有抽象和没有抽象),但在这项工作中,只关注使用原始源代码的数据集,因为这是使用代码补全技术的真实场景。 需要进行这样的澄清(clarification),因为在构建微调数据集时,排除了在抽象过程中发生解析错误的方法,使得 Java 数据集有 634,799 个方法,Android 数据集有 532,096 个方法。

然后,使用以下masking过程创建下表中汇总的每个数据集的三个版本(Java 和 Android)

  • Token Masking

    对于每个方法中超过一个token的每个代码行 l,我们mask其最后x个tokens,其中 x 是1 … n-1之间的随机数,n是组成l的标记数量。 给定一个有k行的方法m且其超过一个token,生成m的k个版本,每个版本都有一行,其中最后x个标记被masked,其余k-1行没有任何变化(即没有masked token,只是原始源代码)。 作者将屏蔽标记的最大数量设置为 10(即如果 x > 10,则 x=10)。 此场景模拟了经典的代码补全任务,其中开发人员正在编写语句,代码完成工具负责自动完成它。

  • Construct Masking

    作者选择了许多代码结构,自动代码补全对这些代码结构支持是特别有用。 给定方法 m,我们使用 scrML 工具包来识别所有 m 的标记,用于:

    • 定义 if 语句或 while/for 循环的完整条件(例如,在具有 for(int i=0;i<data.size(); i++ 的语句中),我们将这之间的所有token标识为用于定义 for 循环)

    • 定义方法调用中的参数(例如,在 copyFile(source, target) 中,识别token “source”、“,”和“target”)

    • 定义在 catch 语句中捕获的异常(例如,在 catch(IOException io) 中,我们将 IOException io 标识为所涉及的token)。

    对于 m,这会产生一个集合 $S={T_1,T_2,…,T_n}$,其中$T_i$表示前面提到的构造之一的一组相关token(例如,Ti 是用于定义 for 循环条件的集合)

    给定 m,我们生成它的|S|个版本,每个版本都有一个被masked的主题(subject?)结构。 此外在本例中,作者将masked token的最大数量设置为 10。这意味着,如果一个构造需要mask超过 10 个token(数据集中 9.38% 的构造发生这种情况),则它不会在我们的数据集中被mask 。

    由construct masking模拟的代码补全任务类似于开发人员使用该技术来获取关于有一定难度(non-trivial)的代码token的建议,代表程序流中的决策点(例如 if 语句的条件)或错误处理情况(例如要捕获的异常)。

  • Block Masking

    使用 srcML 来识别每个方法m中的代码块。 代码块定义为在两个大括号之间的代码。 例如,除了方法体本身之外,块还可以是在满足 if/else/else if 等条件时在 for/while 循环中执行的代码。然后,给定 k 中识别到的块的数量 m,创建 m 的 k 个版本,每个版本都有一个特定的代码块被mask。 我们将屏蔽(masked)块的最大大小设置为两个完整的语句。 这意味着,如果一个块由两个以上的语句组成(数据集中 49.29% 的块发生这种情况),则它不会被屏蔽(masked)。 这是最具挑战性的代码补全场景,我们在其中测试实验技术。 如果成功完成此任务,代码补全技术可以大大加快代码执行活动。

以上,总共有6个微调数据集:2种语言(Java & Android)* 3种masking level。选择这些masking level来模拟具有不同复杂性的代码补全任务(token-masking是最简单的,block-masking是最复杂的)。

从六个数据集开始,继续构建上表中的训练、评估和测试集。

  1. 第一步,从数据集中过滤掉特定实例

    • 首先,当使用生成深度学习模型时,作为输入提供的句子(在本文例子中为方法)长度的变化可能会影响模型的训练和性能,即使使用填充等技术也是如此。 为此,作者分析了数据集中方法长度的分布,发现其中三分之二最多由 100 个token组成。 因此从数据集中排除了所有具有超过 100 个token的方法。
    • 其次,RoBERTa 无法有效地处理masked token多于non-masked token的情况。 例如,当在block-level方法中屏蔽整个方法体时,这种情况经常发生。 因此,这些实例也被排除在外。
  2. 经过过滤步骤后,将六个数据集分别分为训练集 (80%)、评估集 (10%) 和测试集 (10%)。 虽然数据集中的方法是随机排序的,但分割不是随机的,以避免学习出现偏差。 为了解释这一点,要考虑block-masking数据集的情况。 给定一个有 k 个块的方法m,在数据集中添加 m 的 k 个版本,每个版本都有且只有一个被masked的块。 假设 m 包含两个块 b1 和 b2,从而产生 m 的两个版本:其中 b1 被屏蔽 (mb1 ),b2 未被屏蔽,反之亦然 (mb2 )。 通过随机分割,可能会发生 mb1 分配给训练集而 mb2 分配给测试集的情况。 然而,在 mb1 中 b2 没有被屏蔽。 因此,当模型必须预测 mb2 中隐藏的token时,它会在训练集中找到解决方案,从而导致了高预测性能。 因此,随机选择每个数据集中 80% 的方法,并将其所有屏蔽(masked)版本分配给训练集。 然后,以相同的方式进行评估和测试集。

使用此过程,获得了表中的数据集。需要注意的是,考虑到使用token-level和construct-level masking的数据集的原始大小,将训练集限制为 750k 个实例(评估和测试集中没有变化)。 训练多个 DL 模型的高昂计算成本是必要的(研究需要训练 19 个不同的基于 DL 的模型)。 此外,评估集和测试集的大小略有不同,因为如前所述(而不是其屏蔽版本[masked version])来分割数据集,并且每种方法可能会产生不同数量的生成屏蔽版本(masked versiond)。

Pre-Training Dataset

为了构建预训练数据集,使用 GitHub 搜索平台来检索所有至少有 100 次提交、10 名贡献者和 10 颗star的 Java 存储库。这些过滤标准旨在减少选择玩具(toy)和个人项目来构建该数据集的可能性。按star数量对项目进行排序,克隆前6,000个项目,并从每个项目中提取标记为发布(release)的最新快照中的方法,为了仅依赖可能在语法上正确的方法。没有快照被标记为发布的存储库被排除在外,剩下 3,175 个存储库。最后,为了避免非常大的项目对数据集产生太大影响(即向数据集贡献太多方法),因此将从每个存储库中提取的最大方法数量限制为 1,500 个。这也是根据可用的硬件资源将预训练实例的数量限制在可管理的大小。

除了构建微调数据集时使用的过滤器之外,还删除了所有使用@test注释或驼峰命名中包含“test”一词的测试方法(即不排除updateStatus)。此外,由于预训练数据集的目标是提供被屏蔽(masked)的随机token实例,以使模型“熟悉”特定的上下文(即本案例中的 Java 语言),因此排除了非常短的没有足够的元素来屏蔽(masked)的方法(< 15 个token),并且出于与微调数据集解释相同的原因,长方法(本例中>200个标记)也被排除。然后,删除预训练数据集中的所有精确重复项,仅在数据集中保留每个重复项的第一次出现时的方法。 删除重复项后,数据集包含 1,874,338 种不同的方法。

最后,确保预训练数据集不包含任何属于微调数据集的方法(训练、评估或测试集中均不包含)。 预训练和微调数据集总共有 23,977 个重复项,使预训练数据集中的最终实例数量达到 1,850,361 个。

Context Selection: Techniques

RoBERTa

第一个基于 Transformer 的模型利用现成的 RoBERTa 模型,这是一种 Encoder Transformer 架构。本文集中于解释为什么它是代码补全的合适选择。

基于 BERT 的模型(例如 RoBERTa)使用特殊的预训练,其中输入句子中的随机单词被特殊的 <MASK> token屏蔽(mask)。 此预训练任务非常适合模拟代码补全任务,其中输入是开发人员正在编写的不完整代码片段,而屏蔽标记(masked token)表示自动补全该片段所需的代码。 然而,这种预训练的一个限制是,当尝试预测多个token时,例如整个masked的 if 条件,由于 Transformer 的固定序列长度,它需要已知要生成的token数量。 为了解决这个问题,我们通过使用单个<MASK>标记(token)来掩盖(mask)一段连续的标记(token),从而修改了objective(?)。

如前所述,BERT 模型(例如 RoBERTa)可以针对多项任务进行预训练和微调。 结果将是一个能够支持不同任务的单一模型,并且可能利用它在特定任务中学到的知识来提高其在不同任务中的性能。在研究中,首先在不执行预训练的场景中比较 RoBERTa 和 T5 模型,并通过使用微调数据集构建一个单一模型来完成前面描述的三个代码补全任务(即 token、construct、 和block masking)。 然后,为了获得两者中性能最好的模型(即 T5),作者还尝试了预训练和多任务微调。 训练了 6 个 RoBERTa 模型,每个模型对应上表中的每个数据集。

RoBERTa 模型的实现,使用了 Python Transformers 库中提供的模型。还为每个模型训练一个分词器来克服out-of- vocabulary的问题。 当机器学习模型处理不属于训练集但出现在测试集中的术语时,就会出现out-of-vocabulary问题。 使用HuggingFace 的Python分词器库训练了Byte Pair Encoding (BPE) 模型。BPE使用字节作为vocabulary,允许它对每个文本进行分词而不需要 DL 到 NLP 应用中经常使用的未知标记,从而克服了out-of-vocabulary问题。 在源代码上使用时,BPE已被证明可以去解决out-of-vocabulary问题。

T5

拉斐尔等人提出了在 NLP 领域利用多任务学习实现迁移学习的 T5 模型。 T5 有五种预定义的变体:small、base、large、3 Billion、11 Billion,它们在复杂性、大小以及训练时间上有所不同。$T5_{small}$是较小的变体,有 6000 万个参数,而$T5_{11B}$是最大的变体,有 110 亿个参数。 尽管拉斐尔等人强调最大的模型提供了最好的准确性,但其训练时间有时太长而无法证明其使用的合理性。 考虑到计算资源,选择了$T5_{small}$​模型。 因此,作者认为其结果代表基于 T5 的模型性能的下限。

与其他深度学习(DL)模型相比,T5 具有两个优点:

  1. 它通常比RNNs更高效,因为它允许并行计算输出层(output layer)

  2. 它可以检测标记(token)之间隐藏和远程的依赖关系,而无需假设最近的标记比远处的标记更加相关。 后者在与代码相关的任务中特别相关。 例如,局部变量可能在方法的开头(第一个语句)声明,在 if 语句内的主体中使用,最后在方法最后的语句中返回。 捕获这三个语句之间存在的依赖关系,甚至可能彼此相距甚远(例如,变量声明和返回语句),可以帮助更好地对源代码进行建模,从而提高支持代码相关任务的性能。

n-gram

作为比较的baseline,使用了广泛研究的基于 n-gram 的统计语言模型。 n-gram 模型可以根据其前面的 n-1 个token预测之后的单个token。 尽管 n-gram 模型的目的是在给定n-1个前面的token的情况下预测单个token,但我们为mask多个token的场景设计了一种公平的比较方式。 具体来说,按以下方式使用 n-gram 模型:假设我们正在使用 3-gram模型预测如何补全具有五个标记 T 的语句,其中最后两个被屏蔽(M):<T1,T2,T3,M4,M5>,其中M4和M5分别掩蔽T4和T5。 将T2和T3作为输入提供给模型来预测M4,获得模型预测P4。 然后,使用T3和T4来预测M5,从而得到预测句子<T1,T2,T3,P4,P5>。 基本上,所有预测都被连接起来以预测多个连续的token。

n-gram 模型在深度学习(DL)模型微调的相同训练集上进行训练,但是没有masked token。 我们使用标准 n-gram 模型以及规模较小的研究,采用 Hellendoorn 和 Devanbu提出的 n-gram 缓存模型。

Data Collection And Analysis

Training of Models

RoBERTa

搭载了Nvidia RTX Titan GPU的Linux服务器上,使用Weights & Biases的Python库执行超参数(hyperParameter)调整。Table 2展示了微调的参数,测试的值范围,以及最佳配置的值。

除了那些超参数,作者使用了0.1的注意力丢失(attention dropout)概率和 0.3 的隐藏层丢失(hidden layer dropout)概率。对于分词器,词汇(vocabulary)数量被设置为50k。

使用带有token-masking的Android数据集的评估集和训练集来进行超参数搜索。选择了当应用于评估集时能够获得最多数量的“完美预测”的配置作为最佳配置。 将与开发者编写的代码完全匹配的预测定义为“完美”。 因此,模型正确地猜测了所有masked token。 如果masked token之一不同,则不会认为预测是“完美的”。

虽然原则上每个数据集都需要不同的超参数调整,但这样的过程非常昂贵,并且通过对其他数据集的子集进行的初步调查显示,最佳配置之间存在微小差异

训练是跨服务器(分别使用他们自己的GPUs)进行的。 第一个配备了Nvidia Tesla V100S,第二个配备了Nvidia RTX Titan,第三个配备了3个Nvidia GTX 1080Ti。 训练时间很大程度上取决于数据集的大小和所使用的服务器,但每个模型的训练时间在28到114小时之间。 可以注意到,经过训练后每个模型都可以在一秒钟内执行预测(在笔记本电脑 CPU 上平均为 0.12 秒),从而使它们成为“实时”代码补全的可行解决方案。

对每个模型最多训练 50 个 epoch(一次epoch是指将所有数据训练一遍的次数,epoch所代表的数字是指所有数据被训练的总轮数。)。 然而,采用了以下停止条件。 在每个训练周期结束时,在评估集上运行模型并计算完美预测的数量。 如果观察到在训练过程中,模型的性能在评估集的完美预测方面正在恶化(即模型可能过度拟合训练集),就会停止训练。 特别是,给定一个训练了第 n 个 epoch 的模型,如果评估集上的完美预测数量低于第 n-4 个 epoch 之后实现的完美预测数量,我们将停止训练。 这确保了模型在最多三个epoch内的性能有一些波动。然后,如果它仍然没有改善,我们停止训练并采用迄今为止获得的最佳模型(就评估测试的完美预测而言)。没有一个模型接受过完整的50个 epoch 的训练。

T5

使用Mastropaolo等人使用的相同配置。关于预训练,不调整 T5 模型的超参数,因为预训练步骤与任务无关,这带来的好处是有限的。 相反,使用Table 3中展示的配置,在微调阶段尝试四种不同的学习率计划,并根据评估集上的完美预测确定最佳性能配置。

四个实验配置均在评估其性能前训练了超过了100k步(~7个epoch)。在6个评估数据集中,使用Slanted Triangular Learning Rates(Slanted Triangular Learning Rates的核心思想是通过在训练过程中动态调整学习率来提高模型的训练效率和性能。其具体形式为在训练的前半段逐渐增加学习率,然后在后半段逐渐减少学习率。学习率的变化模式呈现出一个“斜三角形”形状。)的配置获得了最好的表现。同时,构建的所有T5模型都使用了在预训练数据集上训练过且包含32k单词片段的SentencePiece分词器

将上文确定的最佳配置用于训练六种不同的T5模型(即Table 1中的每个数据集一个)并评估它们在相应测试集上的性能。 这些结果可用于直接比较 T5 和 RoBERTa 模型在未经预训练的情况下以及在单任务设置(即无迁移学习)中进行微调时的情况。 由于发现T5的性能优于 RoBERTa,因此使用该模型来回答 RQ2。 因此除了这六个模型之外,我们还构建了另外七个模型:其中六个模型利用预训练加上单任务微调。 换句话说,它们相当于之前构建的前六个模型,但增加了预训练阶段。

为了预训练 T5 模型,随机mask预训练数据集中每个实例(方法)中15%的token。 预训练执行200k步骤(28 个epoch),由于没有观察到任何的改进,使用Google Colab的 2x2 TPU 拓扑(8 核)来训练模型,batch size为 256,序列长度(输入和目标)为256个token。 使用规范配置的平方根倒数作为学习率。 训练100步大约需要26秒。

最后创建了一个利用预训练和多任务微调的 T5 模型(首先对单个模型进行预训练,然后在Table 1中的所有六个数据集上进行微调)。这样做是为了检查迁移学习对模型性能的影响。 总共训练了13个T5模型:6个没有预训练和单任务微调,6个有预训练和单任务微调,1个有预训练和多任务微调。

Analysis of Results

为了回答RQ1,通过在每个训练好的模型上运行Table 1中的测试集计算的度量指标,度量指标总结在Table 4

  1. 第一个指标是Bilingual Evaluation Understudy(BLEU)-n得分,这个指标评估的是自动翻译文本的质量。BLEU得分是通过计算翻译文本和参考文本中出现的单词的加权百分比(即考虑出现次数)。 使用 BLEU 的四种变体,即 BLEU-1、BLEU-2、BLEU-3 和 BLEU-4。 BLEU-n变体通过考虑生成文本中的n元模型来计算BLEU分数。软工方向以前的的大部分工作都采用BLEU-4得分,但是这个变体不能用于计算目标预测小于4的情况(本文中是masked token的数量)。因此,作者计算了从BLEU-1到BLEU-4四个版本的得分。==BLEU-1对于任何预测都可以计算,但BLEU-n(n>1)只适用于长度大于等于n的预测。==BLEU得分的范围是0-100%,其中100%意味着对于maksed token生成的代码和参考代码是一样的。

  2. The Levenshtein distance。为了提供一种开发人员将模型生成的预测转换为参考(正确)代码的所需工作量的衡量标准,在token颗粒度上计算Levenshtein distance:这可以被定义为将预测代码转换为参考代码所需的最小token编辑(插入、删除或替换)次数。 由于这种衡量方式没有标准化,因此在我们的上下文中解释它是困难的。 如果不知道参考代码中的token数量就说必须改变5个token来获得参考代码是没有意义的。 因此,作者通过将预测代码和参考代码中最长序列中的标记数量做除法来标准化该值。

  3. The percentage of perfect predictions。这个指标告诉我们实验模型预测token序列的性能,这些token序列在目标代码中被masked。

通过不同的统计分析,对RoBERTa和T5的结果进行对比。假设显著性水平为95%,使用比例检验(tests on proportions)和数值变量的非参数检验(non-parametric tests)。不使用参数检验是因为根据Anderson-Darling检验(p 值 < 0.001),BLEU得分和Levenshtein距离的结果都偏离正态性。每当分析需要运行多个测试实例时,都会使用Benjamini-Hochberg过程调整p值。

为了(成对)比较 RoBERTa 和 T5 的完美预测,我们使用McNemar检验,这是一种比例检验,适合成对比较两种不同处理的二分结果。 为了计算测试结果,我们创建一个混淆矩阵来计算以下情况的数量:

  • T5和RoBERTa都提供了完美预测

  • 只有T5提供了完美预测

  • 只有RoBERTa提供了完美预测

  • T5和RoBERTa均没有提供完美预测

最后,我们用优势比 (OR) 统计效应来补充 McNemar 检验。

为了回答RQ1.2,不同数据集之间通过比例检验,但是这一次分析是未配对的(即在两个不同的数据集上对比结果),所以在一个矩阵上使用费舍尔精确检验(以及相关优势比),该矩阵包含针对不同方法和不同masking level,使用Java和Android实现的正确和错误预测的数量。

为了按照BLEU-n得分和Levenshtein距离来比较T5和RoBERTa的结果,使用Wilcoxon 符号秩检验和成对Cliff’s delta effect size。相似的,当数据集按照BLEU-n得分和Levenshtein来进行对比时,由于是未配对的,可以使用Wilcoxon 秩和检验和未成对Cliff’s delta effect size

对于T5,也统计地比较了他展示的性能:

  • 有/没有预训练

  • 有/没有迁移学习

同样的,对于T5的比较,McNemar检验也可以被使用来比较完美预测。

最后,得到了表现最好的模型(即经过预训练和多任务微调的T5模型)并检测预测的置信度是否可以用作预测“质量”的可靠代理。如果是这种情况,这意味着在围绕训练模型构建的推荐系统中,开发人员可以决定仅当其置信度高于特定阈值时才接收推荐。 T5 返回每个预测的分数,范围从负无穷到 0。该分数是预测的对数近似 (ln)。 因此,如果它为0,则意味着预测的可能性为1(即最大置信度,因为ln(1) = 0),而当它趋于负无穷大时,置信度趋于0。

为了通过统计分析证实作者的结果,作者报告了通过构建逻辑回归模型获得的OR,该模型将置信度(自变量)与预测实现完美预测的程度(因变量)相关联。 给定逻辑回归模型中的自变量估计值$\beta_{i}$,OR 由$e^{\beta_{i}}$​得出,它表示自变量增加一个单位所对应的几率增加。 还确定了模型报告的置信度与屏蔽标记数量的相关程度。在这个程度上,使用肯德尔相关性,它不会像其他非参数相关性那样受到关系存在(出现在我们的数据集中)的影响。

为了解决 RQ3,对于所有数据集,我们将基于 DL 的模型的性能与 n-gram 模型的性能进行比较。 特别是,作者使用标准 n-gram 语言模型进行了第一次大规模比较,并且在较小的数据集上,还使用以下方法将实验技术与最先进的缓存 n-gram 模型进行了比较。 稍后将说明为什么缓存 n-gram 模型太昂贵而无法在整个数据集上运行。

作者尝试了去设计一个公平的比较方式,尽管n-gram模型是设计来依据给定之前的n个token来预测单一的token。因此在mask超过一个token的场景下,我们按照如下方式使用n-gram模型:

  1. 运行模型来独立预测每个masked token

  2. 将所有预测连接起来来产生最终的字符串(例如之前mask过的token)

  3. n-gram模型在基于DL的模型微调的相同的数据集上训练,但是没有masked tokens

根据在测试集上生成的完美预测来比较这三种方法。 使用 McNemar 检验和 OR 进行统计比较。

Results Discussion

首先对比T5和RoBERTa的性能。 然后,展示n-gram模型与基于DL的模型的比较。 最后介绍模型做出的正确预测的定性示例,并讨论了非完美预测的语义等价性。在解释所取得的结果时,尤其是那些涉及完美(正确)预测的结果时,不可能与之前的代码补全工作中所取得的结果进行直接比较。 这是因为文献中的大多数研究在预测开发人员可能编写的下一个token时都使用代码补全模型进行实验。 正如将要展示的,在这种特定场景中,我们实验的模型可以实现极高的准确性(> 95% 的正确预测)。然而,当预测由多个token甚至多个语句组成的较长序列时,它们的性能会大幅下降。

DL-Based Models Performance Comparison (RQ1)

图 1 描述了基于深度学习的模型在不同masking方法(从左到右依次为token-masking、construct-masking和block- masking)的完美预测方面所取得的结果。 该图显示了完美预测的百分比(y 轴)与masked token数量(x 轴)。 例如,在token maksing场景中,随机屏蔽具有多个token的每个源代码行$l$的最后$x$个token,其中$x$是 $1…n-1$之间的随机数,其中$n$是$l$​的token数量,x 的上限为 10。T5的结果以橙色报告,而 RoBERTa的结果为红色; 实线表示在Java数据集上取得的结果,而虚线表示在Android数据集上取得的结果。

图 1 的左侧图显示了仅mask最后一个token(即一个被masked的token)、最后两个token等情况下完美预测的百分比。在处理block-masking场景,由于屏蔽整个块,因此在某些情况下有数十个masked token。 每个点表示$x-5$和$x$之间的token被mask,例如,对于第一个数据点,最多5个token被mask,第二个数据点在5到10 之间等等。

Table 5报告了四种考虑的变体的平均BLEU得分以及T5和RoBERTa的平均标准化Levenshtein距离。在这种情况下,结果会根据masking level和数据集进行分组。

图 1和Table 5中的结果是基于深度学习的模型在最简单的场景(即没有预训练的单任务)下实现的。 为了回答 RQ1.3,对最佳模型(即 T5)进行了额外的实验。Table 6中提供了实验T5模型不同变体的完美预测的百分比的结果,即有/没有预训练以及是否使用单任务和多任务微调。Table 6还展示了RoBERTa模型在最简单场景中取得的结果以简化结果的讨论。

Impact of Number of Masked Tokens (RQ1.1) and Specificity of the Dataset (RQ1.2)

从图1的分析中有三个发现

  1. masked token的数量越多,模型的表现越低

  2. 在token-masking场景下,对于RoBERTa模型,在更加具体的数据集(如Android数据集,图中的虚线)上取得的效果比在Java数据集上的效果更好(见Table9的数据)。

  3. T5模型的表现远远超过RoBERTa(见Table7和8中的数据),且对比T5的表现,当masked token的数量增加时,RoBERTa的性能下降的更稳定(?steadily)

Table 7展示了使用McNemar检验和OR来对比T5和RoBERTa在进行完美预测的能力的结果。可以看出,(调整后的)p值始终表明统计上存在显著差异,OR表明T5提供完美预测的几率比RoBERTa高2.94到8.87。

关于BLEU分数和Levenshtein距离(Table5中展示了均值)的对比,Table 8中展示了统计结果(Wilcoxon 符号秩检验调整的p值和Cliff的d值)。在这种情况下,差异是统计显著的,不同的效应大小(Java 比 Android 而言BLEU分数水平越高)有利于T5(对于 Levenshtein 距离,negative d有利于 T5,因为它是一个距离)。

  • Token Masking

    图1的左部分展示了正如期待的那样,masked token的数量越少,完美预测越高。并不意外的是,模型在仅对语句中最后一个token被mask的情况下是非常高效的。事实上,最后的token往往是分号、括号、大括号,因此模型预测最后token是非常容易的。当转入更有挑战性的场景像语句中的最后5个token被mask,RoBERTa在Java数据集上的的完美预测率掉到了10%以下,这和T5模型仍然保持40%以上的完美预测率存在巨大的差异。至于数据集方面,两个模型都在Android数据集上获得了更好的表现(费舍尔检验p值<0.001且OR<1),Android数据集是更具体,因此更容易受到源码中的规律的影响。但是对于RoBERTa模型,在Java数据集和Android数据集上完美预测率的差距是更明显(x=5时为~20%,而T5为6%)。

    从Table 5中,BLEU分数和Levenshtein距离证实了从完美预测中观测到的,即Android数据集上的表现比Java数据集上的更好。根据Wilcoxon秩和检验,除了Block level上的RoBERTa模型,所有的区别都是统计显著的,但是cliff的d值是可以忽略不计/很小的。

  • Construct Masking

    在这种场景下(图1中间的图),对于两个数据集,当单一token被mask时,T5和RoBERTa完美预测率分别能达到超过65%和55%。在这种场景下,单一token并不是微不足道的(?trival)因为我们处在一个上下文中,其中单一的token代表着:

    • 补全if语句或while/for循环的条件

    • 方法调用的参数

    • catch方法中捕获的异常

    当预测由单个token表示时,它通常与 if 条件中使用的布尔值(例如 if(true)、if(valid) 等)或方法调用所需的单个参数相关。

    同样的,masked token数量的增加意味着更低的表现,同时在两个数据集上T5的表现要显著的超过RoBERTa尽管差距更小了。最后,正如Table 9中展示的,对于RoBERTa在Android上的效果更好,对于T5能够达到OR$\approx$1。

    至于BLEU分数和Levenshtein距离,对比token-level的masking,获得的值更差。这也证实了construct-level的masking,预测场景是更加困难的。平均来看,当分别使用RoBERTa和T5时,开发者可能需要修改预测出的token中的30%-40%来获得参考代码(这在Java和Android观察到的差异是很小的)。

  • Block Masking

    这代表了最有挑战的代码预测场景:被masked的部分可能包括整个语句或者是涵盖超过2个语句(设置的最大边界)。当处理小型的masked block(即5个token)时,T5和RoBERTa的表现分别是超过50%和35%。这些blocks很大部分和代码块的返回语句有联系(例如当满足if条件时的返回的值),例如{return false;}, {return null;}等。

    对于更长的block,模型的表现显著下降。当考虑拥有6-10个masked token的块,RoBERTa对于大约5%的例子能够生成完美预测,对比之下T5大概能达到大约25%。T5在Android和Java数据集上能够达到完美预测的最大masked token分别是36和39,而RoBERTa的数据在13和15。

    在这一级别上,在Java和Android数据集上的区别并不明显,在T5上甚至更加不显著。正如预期的那样,在这种场景下BLEU分数是最低的,并且当使用RoBERTa和T5时,开发者需要分别独立修改预测出的token中的大约50%和30%。

Answer to RQ1.1

当masked tokens的数量增加时,基于深度学习的模型更难产生完美预测。T5模型的性能看上去是符合预期的,同时能通过合适的预训练和多任务微调进一步提升。

Answer to RQ1.2

当观察最优模型(即T5)时,他在两个数据集上的表现是相似的,并没有主要差别。只有在token-masking场景下,RoBERTa模型会出现强烈的差距。

Impact of Pre-Training and Transfer Learning (RQ2)

如前所述,训练了7个额外的T5模型来评估预训练和迁移学习对性能的影响。首先向6个先前讨论过的T5模型(无预训练&单任务)加入预训练过程(获得一个没有迁移学习,面向单任务场景的预训练模型),然后使用预训练模型在多任务设置下进行微调,探索迁移学习的影响。

Table 6展示了取得的效果。使用McNemar检验获得的统计对比结果在Table 10中给出。正如其中展示的,预训练对于所有情况都拥有积极的(OR>1)和统计显著的效果。Table 6中,预训练对于T5的准确性拥有积极的影响,在使用测试集的条件下,完美预测率从1%激增到4.7%。预训练的好处在最有挑战性的block-level场景下是更显著的。总的来说,将所有测试数据集作为一个整体考虑,完美预测的百分比从54.1%上升至56.2%。

通过在6个训练数据集上训练单一模型,完美预测的百分比进一步提高,总体能达到59.3%。注意到在所有测试集上都能观察到提升,并且对于token-masking场景,能够达到大约5%。性能上的提升也通过了BLEU分数和Levenshtein距离的结果得到了证实,结果可以在: https://github.com/mciniselli/T5_Replication_Package.git上获得。

Answer to RQ2

预训练和多任务微调都对T5的性能拥有积极的影响。总的来说,对于完美预测提升大约为5.2%(即额外36,009个实例被正确预测)。

T5置信度(Confidence Level)

对于每个预测,T5会返回一个分数(Score),从-∞到0。这个分数是预测可能性的log近似拟合。例如如果分数为-2,那么可能性大约为0.14,因为$ln(0.14)\approx -2$​,这也意味着模型对于预测正确的置信度为14%。如果分数为0,则意味着模型对于预测有100%的置信度。

图3展示了完美预测的百分比和模型置信度之间的关系。橙色的线表示置信区间内完美预测的百分比(例如置信度高于0.9的预测中,90%是正确的),红色的线表示该置信区间内的完美预测的百分比总数的(例如,所有完美预测中 78% 的置信度高于 0.9)。

图3展示了模型置信度和和预测准确性之间的强联系。虽然这个结果可能看起来微不足道,但它具有重要的含义:围绕 T5 模型构建可靠的代码补全工具是可能的。 该工具可以配置为仅在预测的置信度高于给定阈值(例如 0.9)时触发建议。 这将导致极高的代码补全精度。

从统计角度来看,将置信水平与完美预测结果相关联的逻辑回归模型表明存在统计显著性(p 值 < 0.001)相关性,估计值为 6.58,这意味着每增加一个单位置信度,完美预测的几率就会提高720,即置信度增加0.1,完美预测的几率提高72。

图 4分析了所有置信区间中完美预测(黄线)、错误预测(橙线)以及所有预测(红线)的平均长度(以token为单位)。 很明显,预测的长度与置信度相关,因为模型对于较短的预测具有更高的置信度。 事实上,最高置信区间的完美预测中的平均标记数(即3个标记)远低于最低置信区间的完美预测中的平均标记数(即6个标记)。 这证实了之前的研究结果,即该模型更有可能正确预测较短的语句。

从统计角度来看,, this is confirmed by a significant (p-value < 0.001), negative, and moderate Kendall’s correlation (t = -0.36).

Comparison With an N-Gram Model