面向神经代码智能(NCI,Neural Code Intelligence)的预训练语言模型综述

面向神经代码智能的预训练语言模型综述

reference:

https://arxiv.org/pdf/2212.10079v1.pdf

Abstract

​ 随着现代软件的复杂性不断升级,软件工程已经成为一项日益艰巨且容易出错的工作。近年来,==神经代码智能(NCI)==领域已经成为一种有前途的解决方案,利用深度学习技术的力量来解决源代码的分析任务,目标是提高编程效率,并最大限度地减少软件行业中的人为错误。预训练语言模型已经成为NCI研究的主导力量,在广泛的任务中始终如一地提供最先进的结果,包括代码摘要、生成和翻译。本文对NCI领域进行了全面的调研,包括对预训练技术、任务、数据集和模型架构的全面回顾。

1. Introduction

​ 软件开发包括编程之外的一系列任务,如测试、文档编写、bug修复,这些任务都很具有挑战性。为了简化软件开发,代码智能工具作为一种自动分析源代码并解决软件工程任务的计算机辅助方法应运而生。此前,代码智能工具大多基于静态分析方法,例如,Microsoft Intellisense是一个代码智能工具,它通过静态分析用户代码和构建定义、引用、类型签名等数据库来提供代码补全建议和提示函数签名;还有一些工具可以自动检测源代码中的漏洞(vulnerabilities )。尽管这些工具在工业界被广泛采用,它们存在局限性,最大的局限在于它们是为特定的编程语言而构建的,而像Python等动态语言很难进行静态分析,这使得传统的代码智能工具对开发人员来说效率较低。

​ 最近研究已经将语言模型和预训练策略应用与代码智能任务,比如程序合成、文档生成、缺陷检测与程序修复,这是受预训练Transformer模型在序列数据建模上的成功启发。受==软件自然性假设(software naturalness bypothesis)==的启发(Hindle et al., 2016; Buratti et al., 2020),这表明编程语言可以像自然语言一样被理解和生成,研究人员将源代码视为序列数据并应用序列神经架构如Transformer模型,以理解和生成程序。在NLP社区中,当有大量未注释数据可用时,预训练范式(pretraining paradigm)使得模型能够学习高质量的上下文token嵌入,并显著提高下游性能;同样的,大量的用于编程语言的代码片段能够在GitHub等开源平台找到,因此研究人员采用了预训练范式来解决各种代码分析任务。与静态分析器相比,基于预训练范式的方法大大节省了开发支持不同任务和适应新的编程语言的代码智能工具的工作量。目前预训练代码语言模型在广泛的任务上取得了SOAT性能,Codex在程序合成方面取得了重要里程碑,并增强了Copilot2等编程智能工具的能力,这些工具甚至被应用与解决线性代数和数学应用题(linear algebra and math word problems )。

​ 尽管将预训练语言模型应用于代码智能任务取得了成功,该领域的研究也在蓬勃发展,但一直缺乏对不断增长的文献进行分类的系统综述。之前的工作既没有充分考虑基于语言的代码模型,也没有对现有的模型设计、下游任务和数据集进行全面的审查。为了建模和理解编程语言的语义,有必要①讨论预训练和下游任务的可用数据集;②如何设计合适的神经架构和有效的训练方案;③由于源代码本身具有丰富的结构信息(Xu et al.,2022;Guo et al., 2021),如何在设计代码智能模型时提取并利用这些结构作为先验知识是一个至关重要的问题,这需要编程语言社区的理论和技术背景知识。Wuet al. (2022b)综述了用于结构化代码理解的深度学习方法,并讨论了基于序列和基于图的建模技术。然而,这项工作更关注程序的结构方面,缺乏对语言模型和预训练策略的深入讨论。

​ 为了连接NLP和PL社区的知识,本文将现有的用于代码智能的预训练语言模型分组为神经代码智能(NCI),并对该领域进行了系统的回顾。具体地:

  • 从预处理技术、模型架构和学习范式方面回顾了编程语言的神经建模技术;
  • 讨论了NCI的各种下游任务,以及用于训练和评估代码语言模型的可用数据集;
  • 探讨了将语言建模方法应用于代码智能的挑战和机遇;
  • 在GitHub仓库中维护了一个NCI研究、新闻和工具的列表,希望该工作可以阐明该领域当前的研究前景,帮助新人了解最近的研究进展,并为未来的研究提供见解。

2. 代码预训练语言模型

将代码语言建模方法拆分为一个由三个阶段组成的管道:预处理、序列建模和训练。流程如图1所示

在这里插入图片描述

  • Preprocessing. 对于代码语言模型,输入将是预训练语料库中的源代码片段或稍后在下游数据集上的源代码片段。必须首先对输入代码进行预处理,包括在代码上运行标记化,并可选地从编程语言中提取先验知识。
  • **Sequential modeling. **然后,将预处理的结果(主要是标记序列)输入到语言模型中,将代码编码为密集表示,预测其属性,或从中生成代码序列。
  • Pretraining and finetuning. 最后,用无监督目标训练模型,并在下游任务上进一步微调。在预训练阶段,模型从大量的代码语料库中学习,没有人工注释,以获得源代码结构和语义的通用和可迁移的知识。然后,针对特定的下游任务对预训练模型进行微调。除了上述的预训练和微调策略,其他学习范式包括零样本(zero-shot)、少样本(few-shot)和多任务学习(multi-task learning)也可以用于训练模型。

2.1 预处理 (Preprocessing)

​ 预处理阶段接受原始代码文件作为输入;对于每个样本,这一阶段的输出包括一系列的token和可选的源代码结构。

​ 在输入代码文件上运行分词器(tokenizer),从源代码中获得的tokens是字符序列,它们组合在一起作为语言建模的基本单元;除了分词(tokenization),可以进一步基于静态分析工具有选择地从代码中提取结构,编程语言本质上具有丰富的由语法规则定义的语法结构,以及由语义分析工具揭示的语义结构(eg. flow graphs)。结构提取的结果通常用图表示,例如,在数据依赖图中,节点表示变量,从节点x到另一个节点y的边表示y的计算涉及到x。

2.1.1 源代码分词(Tokenization of Source Code)

分词(Tokenization)是语言模型不可或缺的预处理步骤,它将输入文本切分为tokens序列,这个token序列将喂给序列模型。以前的工作直接使用NLP社区的分词器,例如BPE、SentencePiece;此外,研究人员探索了不同策略去更好地对源代码进行分词,这些策略要么改进现有的为源代码定制的分词器,要么利用编程语言的命名规定及其编译器中的分词器来保留语法和源代码的语义。

  • Fitting subword tokenizers on source code. 最简单的获取一个有效分词器的方法是在源代码语料库中拟合一个子词分词器。例如,Buratti et al. (2020) 提出了SentencePiece分词器;CodeGPT在他们的编程相关语料库上运行BPE算法。
  • Extending tokenizers with PL-specific tokens. 通过添加PL-specific tokens,可以对现有的分词器进行改进,以高效地编码源代码。Phan et al. (2021) 将一些在源代码中常见的标志(如[, {, $)添加到SentencePiece分词器中以更好地编码编程语言;Chen et al. (2021) 提出了通过将不同长度的连续空白编码为特殊的token来扩展预训练的BPE分词器,所提出的扩展减少了针对同一语料库的token数量,从而提高了代码语言建模的效率。
  • Utilizing tokenizers of compilers. 在自然语言中,可以通过空格或者标点符号对词进行划分,所以他们的边界可以很容易地确定;但是在编程语言中空格有时是可选的,编程语言附带的分词器根据严格定义的规则语法从源代码中扫描token,因此很自然地想到在运行语言模型的子词分词器前,用PL分词器对源代码进行分词。
  • Exploiting naming conventions. 使用命名规范以更好地保留源代码中标志符的语义。命名规范是将单词串联起来形成有效程序标志符的规则,常用的命名约定有snake case(eg. hello_world)依旧camel case(eg. helloWorld)。在分词阶段,可以在运行分词器前先根据命名规定将标志符名进行拆分,以保留标志符的自然语言语义。

2.1.2 从源代码提出结构(Extracting Structures from Source Code)

编程语言有丰富的语法和语义结构,这些可以通过解析工具(parsing tool)和语义分析(semantic analysis)技术进行提取。具体来说,可以在预处理阶段提取抽象语法树(AST, Abstract Syntax Tree),控制流图(CFGs, Control-Flow Graphs),数据流图(DFGs, Dara-Flow Graphs),并利用这届结构用于代码建模。

为了在神经网络中使用代码结构(通常以图或者树的形式),可以将它们压扁(flatten)成序列,这允许我们直接将代码结构输入到Transformer中。一种常见的将代码结构压扁的方法是遍历图/树的节点获得序列,然后使用自注意力机制中的attention masks去恢复结构信息(在2.2.2节中介绍);另一种方法(Guo et al. (2022))是使用一个编码函数将ASTs映射为序列,该序列可以被直接输入到transformer中而不会损失结构信息。

2.2 对代码tokens的神经建模( Neural Modeling for Code Tokens)

从原始输入中获取到token序列以及可选的辅助结构之后,该阶段对这些带有额外结构信息的代码token序列进行建模,并产生密集的代码表示或生成所需的编程语言片段或自然语言序列。

目前常见的代码语言模型包括RNNs、LSTM、Transformers(主要)。为了充分利用编程语言的丰富结构,还设置了针对语言规范的额外组件和架构。

2.2.1 用于NCI模型的Transformer(Transformers in NCI Models)
  • Encoder-only transformers 场景:缺陷检测/代码检索
  • Encoder-decoder transformers 如CodeT5或者PLBART,在各种下游任务中使用
  • Decoder-only transformers 如CodeX或者AlphaCode。场景:程序合成,被设计用于生成高质量的序列
2.2.2 充分利用编程结构(Exploiting Program Structures)
  • Syntax-based self-attention Transformer中的自注意力模块只能对序列中token的线性位置进行建模,序列的线性建模虽然适用于自然语言,但是编程语言本质是结构化的,编译器通常会将其解析为包含语法规则的抽象语法树(ASTs),因此,对源代码进行线性建模忽略了源代码丰富的语法结构。为了充分对代码语法树结构进行建模,可以考虑语法结构对attention模块的位置编码进行修改,例如,①TP-Trans在Transformer encoder中采用了基于树路径的可学习位置编码,具体来说,两个节点之间的树编码是通过在AST中它们时间的路径上运行GRU网络来计算的;②Shiv and Quirk (2019) 提出了一种基于堆栈的绝对位置编码方案,可以表示树中的节点位置,在decoder端,实时(on-the-fly)计算来自局部树的位置编码,以确保输出和位置编码之间的对齐。
  • Semantic-based self-attention 源代码结构不仅包括语法层面,还包括语义层面。静态分析技术可以提取源代码的语义结构,例如控制流图(CFGs)以及数据流图(DFGs)。Guo et al. (2021) 提出了将数据流结构结合进attention结构,首先将数据流图作为序列域源代码一同输入,然后基于数据流图的边以及源代码中标识符与图中节点之间的对应关系来对attention进行掩码。

2.3 训练(Training)

2.3.1 语言模型预训练(Language Model Pretraining)

(1)Language modeling objectives.(语言模型的预训练目标)

  • Masked Language Modeling (MLM). 首先对输入序列的部分tokens进行掩码,然后要求模型进行预测。
  • Next Sentence Prediction (NSP). 在每个样本中,随机选择两个句子s、t,组织为[CLS], s1, · · · , s|s| , [SEP], t1, · · · , t|t| , [SEP] 并喂给模型,[CLS]的最终嵌入被视为序列嵌入,用于预测t是否为s的真实下一句。将NSP应用于编程语言时,(Kanade et al., 2020; Liu et al., 2020), Kanade et al. (2020) 提出了使用源代码的逻辑行而不是物理行以更好地对编程语言的语法进行建模。
  • Masked Span Prediction (MSP). MSP目标被广泛应用与训练带有encoder-decoder架构的语言模型,其中,随机对输入序列的token跨度进行掩码,然后结合一些哨点token对掩码跨度进行预测。基于encoder-decoder架构的代码语言模型通常使用MSP进行训练。
  • Unidirectional Language Modeling (LM). 许多decoder-only语言模型使用的是LM目标,其中,模型基于先前的序列去预测未来的token;具体地,当使用该目标训练Transformer时,使用随机注意掩码,其中序列的每个token只能关注其之前的token;然后,在输出层,只在每个输出token嵌入上使用一个分类头来预测输入序列的下一个token,并使用交叉熵损失来优化模型。在NCI中,基于GPT的代码合成模型采用LM目标。
  • Denoising AutoEncoding (DAE). DAE被用于训练seq2seq语言模型,它通过随机掩码、移除和打乱token来破坏输入序列,并强制模型从已损坏的序列中恢复原始序列。注意,DAE的损失可以被视为MLM损失的扩展,因为它不仅对输入token进行掩码还打乱甚至删除它们。DAE被应用与训练代码翻译模型,比如TransCoder,并被证明可以增强模型解码代码内部表示的能力
  • Back translation. Back translation是为跨语言模型设计的预训练目标,Rozière et al. (2020, 2021) 应用该目标用于训练能够在不同编程语言之间进行翻译的编程语言模型,具体来说,对于语言A的一个输入代码序列,首先要求模型将其翻译为语言B的等效代码序列,然后,强制模型将B中的代码序列翻译回A中的原始代码序列。

(2)Code-specific objectives.

除了源于语言建模的目标,研究人员还探索了特定与编程语言的预训练目标,这些目标利用了编程语言和软件工程领域的先验知识,并被证明可以在许多NCI任务中实现性能提升。

  • Replaced Token Detection (RTD). RTD目标通过生成器将输入序列中的tokens随机替换为多个token,并训练二进制分类器预测某个token是否被替换。它以及被应用与训练BERT-like代码语言模型。
  • Identifier DeOBFuscation (DOBF). 受软件工程中标识符去混淆的概念的启发,DOBF目标首先用占位符token掩码函数和变量名,然后训练模型从占位符恢复原始名称。DOBF被证明略微提高了代码翻译任务中的模型性能。
  • Data-flow-based objectives. GraphCode-VERT合并了输入源代码的数据流信息。数据流是源代码中的变量依赖关系图,其中每个节点代表一个变量,每条边表示一个变量的计算依赖于另一个变量。 Guo et al. (2021)将源代码的数据流输入到模型输入中,并在工作中设计了两个基于数据流的预训练目标,即边缘预测(Edge Prediction)和节点对齐(Node Alignment):边缘预测要求模型预测数据流图中的隐藏边;节点对齐要求模型预测源代码中的标识符token与数据流图中节点的对齐情况。
  • Contrastive Learning (CL). 对比学习通过最大化正样本(positive samples)之间的相似性来训练模型。具体来说,首先用数据增强从每个输入数据样本中生成各种样本,对于每个输入样本,其增强样本被视为正样本,而其他样本则被视为负样本。然后,CL通过强制模型区分正负样本对来对其进行优化。最近,ContraCode采用CL的思想去训练源代码语言模型,ContraCode应用保持语义的代码转换作为数据增强,最大化具有相同功能的增强程序的embedding之间的一致性;UniXCoder提出了一种学习代码语义嵌入的多模态对比学习目标。
2.3.2 额外的学习方案(Additional Learning Schemes)
  • Zero- and few-shot learning. 研究发现,即使没有在下游任务的微调,大规模预训练模型也具备完成多种任务的能力,比如情感分析、文章摘要、问答等,这与代码语言模型的情况类似。Codex在没有微调的情况下再程序合成任务上具有很高的准确性,并且比进行微调的情况效果要更好;同样,Codex在没有微调的情况下在解决线性代数和数学应用题上也有很强的性能(即使没有在任何这些问题上进行过任何优化)。此外,研究发现,预训练代码模型对于其他下游任务(如神经执行、类型推断、变量命名、文档字符串生成等)显示出右前景的性能。
  • Multi-task learning. 多任务学习范式不是针对不同的任务训练不同的模型,而是针对多个下游任务训练一个统一的模型。例如CodeT5,它是一个基于T5的代码语言模型,在微调阶段,它将下游任务形式化为几个seq2seq问题,并构造提示符来提示输入中的任务类型;MulCode为每个下游任务提供了专用的输入和输出层,并由一个统一的表示层来建模源代码,整个模型在多个下游任务上联合优化。

3. Tasks and Datasets

3.1 Downstream Tasks

  • Task types. 下游任务可以分为两类:理解任务(U)与生成任务(G)。理解任务通常以自然语言和编程语言序列作为输入,要求模型预测相应的label,模型仅需要理解自然语言和源代码的含义即可。而生成任务将要求模型从输入中生成编程语言或自然语言文本。模型输入输出通常是自然语言(NL)、编程语言(PL)、以及他们的label。
  • Evaluation protocols.
    • 理解任务通常被表述为分类或检索问题,因此传统的指标可以应用为评估指标;
    • 对于生成任务,通过以下方式衡量性能:①在文本上;②使用ROUGE、BLUE和CodeBLUE衡量结构相似性;③或者测试生成程序的功能准确性

在这里插入图片描述

3.1.1 理解任务
  • Clone detection (克隆检测) 通过度量两个代码片段之间的语义相似性来识别它们之间是否抄袭,其性能可以通过准确性等指标进行评估。BigCloneBench和CodeNet是两个用于评估代码克隆检测算法性能的代表性基准(benchmark)。
  • Defect detection(缺陷检测) 旨在预测给定源代码的脆弱性(vulnerability )、后者检测其中是否包含可能导致运行时错误或攻击的bug或缺陷。这项任务的输入通常是代码片段,输出是指示漏洞的二进制标签,作为一个二分类问题,其性能可以在精度上进行评估。Devign是一个从4个大型开源C项目创建的缺陷检测数据集,包括48687个具有不同漏洞级别的样本;D2A是另一个通过在提交前后分析代码创建的缺陷检测数据集,提供了1295623个样本。
  • Code retrieval (代码检索) 也叫语义代码检索,旨在检索与给定的自然语言查询相关的代码片段。该任务可以自然地形式化为一个匹配问题:模型输出一对表示查询和候选代码片段的NL和PL序列,并给出相关性得分。MRR和S@K可以用于模型评估。CodeSearchNet Challenge是一个从六种流行编程语言的开源仓库构建的代码检索数据集。
3.1.2 生成任务
  • Code summarization (代码摘要) 从给定的源代码片段生成解释性自然语言文档。输出的自然语言描述可以用BLUE和ROUGE评分进行评价。CodeSearchNet语料库可作为代码摘要数据集;FunCom是一个代码摘要基准,包含超过200万个java方法和其对应的自然语言描述。

  • Program synthesis (程序合成) 简称为代码生成,需要模型从文本规范生成程序。这个任务通常是输入一个程序规范,它可以是自然语言描述、伪代码,也可以是示例输入输出程序用例,这些规范可以用自然语言、编程语言或者两者结合的形式来表示。处理使用BLEU/ROUGE这样的文本相似度评分外,生成的程序也可以用CodeBLEU和功能准确性来评估,这将考虑PL的语法和语义。

    有各种可用的数据集:CONCODE是从GitHub抓取的java代码生成数据集,其中每个样本要求模型从编程上下文的自然语言查询中生成源代码片段;HumanEval是一个程序合成基准,由数百个手写编码竞赛问题组成;CodeContests和APPS是从想Codeforces、CodeChef、Codewars等代码平台上提取的代码生成数据集。

  • Code refinement (代码改进) 旨在修复给定代码片段中的错误,并输出无错误的代码片段。这个任务可以视为缺陷检测的扩展,不仅要找到缺陷还要修复它。输出通常用精确的匹配精度(即预测的修复与真实修复完全匹配)来评估。Tufano et al. (2019) 提出了一个从GitHub上托管的数千个java项目中的bug修复提交中挖掘的代码改进数据集。

  • **Code translation (代码翻译)**将一种语言的代码片段翻译为另一种语言,这对于将代码库迁移到一种新的语言非常有用。可以用准确性、BLEU、CodeBLEU进行评估。CodeTrans是从几个开源项目的函数中提出的java-C#代码翻译数据集。

  • Code completion (代码补全) 完全部分代码片段。Lu et al. (2021) 提出了一个基于PY150和GitHub java语料库构建的代码补全基准数据集。基准测试包含两个子任务:token-level和line-level的补全,这需要模型分别预测下一个token或下一行tokens。对token-level补全进行精度评估;line-level预测可以向CodeXGLUE那样使用编辑相似性来评估。

3.2 Pretraining Corpus

常使用的代码预训练预料如Table 2所示。

在这里插入图片描述

4. Challenges and Opportunities

(1)Exploiting prior knowledge of source code.(充分利用源代码的先验知识)

​ 编程语言本质具有丰富的结构,可以用语法规则将源代码解析为抽象语法树,以及使用一系列静态分析器(类型检查器/流分析器……)来提取程序的语义结构。尽管先前工作再利用这些知识方面做了一些尝试,但是目前仍缺乏在为序列数据设计的语言模型中注入结构偏差的原则性方法。此外,程序可以执行,揭示了程序的运行时语义(runtime semantics),这是源代码对于各种任务,特别是程序合成的基本属性。以前大多数工作主要关注机器级别指令,无法用高级编程语言生成通用程序( general-purpose programs),将如此丰富的先验知识,特别是运行时语义,应用于NCI模型是未来很有前途的研究方向。

(2)Linking project- and library-level knowledge.(链接项目级和库级知识)

​ 现有代码语言模型的输入和输出大多是函数、类以及独立的代码片段,相比之下,现实世界的源代码项目有一个必须同时考虑的源代码模块的层次结构,而现有的NCI方法仅能理解和生成单个文件中的源代码,缺乏对相互关联的代码模块进行建模的能力,这对扩展到真实代码项目建模至关重要。

(3)Broadening the horizons of NCI models.(拓宽NCI模型的事业)

​ 研究发现代码语言模型可以应用与理解与生成源代码以外的任务,例如,程序合成模型已被应用于解决线性代数问题、数学应用题、执行自动证明搜索等。具体而言,Polu et al. (2022) 发现代码语言模型可以生成数学证明,GPT-f模型能够在国际数学奥林匹克水平上证明定理;Codex也被用于计算机科学教育,例如解决代码问题、解释代码片段、生成课程教材等;ProgPrompt演示了代码语言模型可以为机器人生成任务计划;此外,代码语言模型很可以也可以应用于其他任务,如加速SAT求解器、解析NL为SQL查询、以及解决更广泛的数学问题,在这些领域应用代码模型是一个新兴的研究领域。

5. Conclusion

这篇论文对基于语言模型的神经代码智能(NCI)模型的任务、数据集合方法进行了系统综述。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值