论文地址:https://arxiv.org/abs/2002.08155
提出了一种用于编程语言(PL)和自然语言(NL)的双模态预训练模型CodeBERT,CodeBERT学习了通用表示,支持自然语言代码搜索、代码文档生成等下游NL-PL任务。本文利用基于Transformer的神经架构开发了CodeBERT,使用**混合目标函数(hybrid objective function)**对其进行训练,该混合目标函数包含了替换token检测的预训练任务,即检测从生成器中采样的可行替代方案,这使我们能够利用NL-PL对的双模态数据以及单模态数据,前者为模型训练提供输入tokens;后者有助于学习更好的生成器。
在实验方面,通过微调模型参数在两个NL-PL应用上评估CodeBERT,结果表明CodeBERT在自然语言代码搜索和代码文档生成任务上都达到了SOTA性能;此外,为了研究在CodeBERT中学习了什么类型的知识,论文构造了一个用于NL-PL探测(NL-PL probing)的数据集,并在一个zero-shot设置中进行评估,其中预训练模型的参数中固定的,结果表明CodeBERT模型在NL-PL探测方面的性能优于之前的预训练模型。
ELMo(2018)/GPT(2018)/BERT(2018)/XLNet(2019)/RoBERTa(2019)等预训练模型在各种NLP任务上显著地提高了最先进的技术,这些预训练模型从大量自监督目标优化的未标记文本学习有效的上下文表示,例如掩码语言建模从一个人工掩码输入序列中预测原始被掩码的词。预训练模型在NLP领域的成功也推动了多模态预训练模型的激增,例如ViLBERT(用于语言-图像)、VideoBERT(用于语言-视频),他们从双模态数据(如带有双模态自监督目标的语言-图像对)中进行学习。
本论文提出了CodeBERT,是一个用于自然语言(NL)与像Python、java、JavaScript等编程语言(PL)的双模态预训练模型。CodeBERT捕获了自然语言和编程语言的语义联系,生成了通用的(general-purpose)表示,广泛支持NL-PL理解任务(例如自然语言代码搜索)和生成式任务(例如代码文档生成)。CodeBERT使用多层Transformer进行开发,为了充分利用NL-PL对的双模态实例以及大量单模态代码,采用一个**混合目标函数(hybrid objective function)**对CodeBERT进行训练,包含了标准的masked language modeling 与 replaces token detection,淡漠太代码可以帮助学习更好的生成器,生成器能够为后一个训练目标产生更好的可替代tokens。
从包含6中编程语言的GitHub代码仓库中训练CodeBERT,其中双模态数据点是与函数级自然语言文档配对的代码,训练是在类似于多语言BERT(multilingual BERT)的设置下进行的,一个预训练模型是在6中编程语言学习的,没有使用显式标记来表示输入的编程语言。
论文的贡献主要包括:
很多成功的方法在具有自监督学习目标的大规模纯文本上训练神经网络,最具有代表性的架构为Transformer,它包含多层self-attention层,并且可以以端到端方式以梯度下降的方式进行常规学习,因为每个部分都是可微的(differentiable)。“自监督”的意思是用于预训练的监督是自动从原始数据(raw data)中收集的。主要的学习目标是语言模型及其变体,例如,在GPT中学习目标是语言模型,即根据某个词wk前面的文本{w1,w2,…,wk-1}去预测词wk,这里只考虑了上文;对于BERT,使用了掩码语言建模目标,学习在给定的上下文情况下预测被随机掩码词序列的掩码词。掩码语言建模被用于CodeBERT训练的一个学习目标。
预训练模型在NLP中的显著成功推动了多模态预训练模型的发展,学习不同模态数据的隐式对齐(implicit alignment),这些模型通常是从双模态数据中学习的,例如语言-图像对或者语言-视频对,例如,ViLBERT从图像说明数据(image caption data)中进行学习,模型通过给定观察到的输入重建掩码图像区域或掩码词的类别进行学习,同时预测说明是否描述了图像内容;VideoBERT从语言-视频数据中学习,通过预测掩码视频或文本进行训练。
本文工作中,将NL与PL视为两种模态,与以往研究不同的是,模型的训练数据不仅包括NL-PL对的双模态数据,还包括大量的单模态数据(如没有配对文档的代码)。
一个同样的研究(Kanade et al., 2019)使用掩码语言模型和下句预测作为目标,在Python源代码上训练一个BERT模型,其中一个句子是一个Python标准定义的逻辑代码行。在预训练过程上,CodeBERT与其他研究的不同点在于:(1)CodeBERT采用跨模态的方式(cross-modal style)进行训练,同时利用了双模态NL-PL数据和单模态PL/NL数据;(2)CodeBERT采用了6中编程语言进行预训练;(3)CodeBERT使用一个新的基于replaces token detection训练目标进行训练。
这一部分介绍:模型结构、输入输出表示、用于CodeBERT训练的目标与数据、如何对CodeBERT进行微调。
遵循BERT、RoBERTa,使用多层的双向Transformer作为CodeBERT的模型架构,使用与RoBERTa-base完全相同的模型架构开发CodeBERT,模型参数总数为125M。
在预训练阶段,将输入设置为带有特殊分隔符的两个片段的拼接,即:[CLS], w1, w2, …wn, [SEP], c1, c2, …, cm, [EOS]. 一个片段是自然语言文本,另一个是来自特定编程语言的代码;[CLS]是在两个片段开头的特殊token,其最终隐藏层表示被认为是聚合的序列表示,用于分类或排序。遵循在Transformer中处理文本的标准方式,将一个自然语言文本视为一个单词序列,并将其拆分为WordPiece;将一段代码视为一个token序列。
CodeBERT的输出包括:(1)自然语言和代码中每个token的上下文向量表示;(2)作为聚合序列表示的[CLS]的表示。
同时使用双模态数据(自然语言-代码对)和单模态数据(无配对自然语言文本的代码或者无配对代码的自然语言)训练CodeBERT。
使用来自GitHub仓库的数据点,其中每个双模态数据点是一个单独的带有配对文档的函数、每个单模态代码是一个没有配对文档的函数。具体来说,使用了一个最近的大型数据集,包含2.1M的双模态数据点和6.4M的单模态代码,跨越6中编程语言(Python、java、JavaScript、PHP、Ruby、Go),具体如表1所示:
数据来自公开的开源GitHub仓库,通过一组约束和规则进行过滤。例如:(1)每个项目应该至少被一个其他项目使用;(2)每个文档被截断到第一段;(3)短语三个token的文档被删除;(4)短语三行的函数被删除;(5)带有子字符串"test"的函数名被删除。具体样例如图1所示:
训练CodeBERT的过程基于两个目标:①MLM:MLM在文献(Devlin et al., 2018; Liu et al.,2019; Sun et al., 2019).中被证明是有效的,对NL-PL对的双模态数据进行了掩码语言建模;②RTD:该目标进一步利用了大量的单模态数据(例如没有配对自然语言文本的代码)。
给定NL-PL对的一个数据点(x={w,c})作为输入,其中w是NL单词序列,c是PL token序列,首先为NL何PL选择一个随机的位置集合进行掩码(分别为mw和mc),然后将选中的位置替换为[MASK] token,x中15%的tokens被掩码。
MLM目标是预测被掩码的原始token,公式如下,其中pD1是从一个大词汇表中预测一个token的鉴别器
有两种数据生成器:NL生成器pGw和GL生成器pGc,用于为随机masked 位置集生成可用的替代。
鉴别器被训练与判断一个词是否为原词,这是一个二分类问题。值得注意的是,RTD目标应用与输入中的每个位置,它与GAN(生成对抗网络,generative adversarial network)的不同之处在于,如果生成器碰巧产生了正确的token,该token的标签的"real"而不是"fake"。RTD关于θ参数化鉴别器的损失函数如下:(其中, δ(i)为一个指示函数;pD2是鉴别器,预测第i个词是原词的概率)
实现了两种有效的双向上下文n-gram语言模型,一个用于NL、一个用于PL,并分别从相应的单模态数据点学习它们,该方法很容易推广到学习双模态生成器或使用更复杂的生成器,如以联合方式学习的基于Transformer的神经架构。PL训练数据是表1所示的单模态代码,NL训练数据来自双模态数据中的文档,我们可以很容易地将这两个训练数据集扩展到更大的量,最终损失函数如下所示:
m i θ n L M I N ( θ ) + L R T D ( θ ) m \underset θ in \ L_{MIN}(θ) + L_{RTD}(θ) mθin LMIN(θ)+LRTD(θ)
在下游NL-PL任务中使用CodeBERT时有不同的设置,例如,
给定一个自然语言作为输入,代码搜索的目标是从代码集中找出语义最接近的代码。在CodeSearchNet语料库中进行实验,在999个扰乱的(distractor)代码集中,遵循官方评估指标为每对测试数据(c, w)计算MRR(Mean Reciprocal Rank),进一步计算了所有编程语言的宏观平均MRR作为整体评估指标。值得注意的是,这个指标与原始论文中的AVG指标不同,AVG指标六种语言的候选语言中检索得到的。为每种编程语言微调得到特定的语言模型,对于每种模型,使用一个二进制分类损失函数进行训练,其中softmax层连接到[CLS]的表示。训练和测试集都是以正负样本平衡的方式进行创建,负样本由随机替换的NL和PL的样本平衡数量组成。
前四行结果为Husain et al.的结果,是NL和PL的联合嵌入(joint embedding)。NBOW表示neural bag-of-words;CNN/GIRNN/SELFATT分别表示1D卷积神经网络、双向基于GRU的循环神经网络、多头注意力。
对于其他结果,将代码视为token序列对所有预训练模型进行训练;还基于掩码语言建模对RoBERTa进行训练,训练仅使用CodeSearchNet中的代码。
实验表明:
进一步研究在没有修改参数的情况下CodeBERT学习了什么样的知识。
(1)Task Formulation and Data Construction:由于目前没有相关研究工作,因此论文将NL-PL探测问题形式化,并创建数据集。
给定一个NL-PL对(c, w),NL-PL探测的目标是测试模型在干扰点(distractors)之间正确预测/恢复masked token的能力(一个code token ci或者word token wj),有两中主要类型的干扰:①用于掩码语言建模目标的整个目标词汇表;②根据专家对被测能力的理解过滤或策划的更少的候选词汇表。论文遵循第二个类型,将NL-PL探测定义为一个多选项问题的问答任务,其中的问题是完型类问题,特定token被[MASK]替代,并基于专业知识策划扰乱的候选答案(distractor candidate answers)。
特别地,论文分别在NL侧和PL侧进行评估。为了减轻数据收集的工作量,从Code-SearchNet的验证集和测试集中自动收集NL-PL对的数据,这两者在预训练阶段都是看不到的。
结果如表3所示:
(2)Model Comparisons:结果如表3所示,报告了每种编程语言的准确性(即正确预测实例的数量除以所有实例的数量),由于不同编程语言的数据集是及其不平衡的,用相同的方式报告了累积的衡量。这里使用CodeBERT(MLM)是因为其输出层自然地适合用于探测。
实验结果表明:CodeBERT在NL和PL探测上几乎在所有语言的性能都优于基线,只有preceding context only的结果要比bidirectional context的结果要查,这表明代码补全是具有挑战性的。
进一步给出了PL-NL探测的案例研究:分别掩码NL token与PL token,然后报告RoBERTa和CodeBERT的预测概率。
如图3所示,可以看到CodeBERT在NL与PL预测中都得到了正确的预测结果,显著优于RoBERTa.
尽管CodeBERT的预训练目标不包含基于生成的目标,这里仍然测试CodeBERT在生成任务上的性能表现,特别地,测试了code-to-NL生成任务,报告了在六种编程语言下,基于CodeSearchNet语料库的文档生成任务的结果。由于生成的文档较短,更高阶的n-gram可能不会重叠,我们通过使用平滑的BLEU评分来解决这个问题。
可以看到:
研究了生成C#代码片段的自然语言摘要任务,在CodeNN数据集上进行实验,该数据集由StackOverflow自动收集的66015个问题-答案对组成,其规模比CodeSearchNet语料库小一个数量级,具有挑战性。使用平滑的BLEU-4 score对模型进行评估。
如表5所示,CodeBERT比大多数模型获得了更优的性能,这表明CodeBERT可以更好地推广到其他预训练期间未使用的编程语言。然而结果略低于Code2Seq,其主要原因可能是Code2Seq在其抽象语法树(AST)中使用了组合路径,而CodeBERT仅使用原始代码作为输入。论文作者通过按照一定的顺序遍历AST的树结构训练了另一个版本的CodeBERT,但应用该模型并没有改善生成任务,这显示了通过合并AST来改进CodeBERT的潜在方向。