目录
在这项工作中,论文系统地回顾了在代码处理方面的最新进展,包括50个+模型,30个+评估任务和500个相关工作。论文将代码处理模型分解为由GPT家族表示的通用语言模型和专门预训练的代码模型,通常具有定制的目标。论文讨论了这些模型之间的关系和差异,并强调了代码建模从统计模型和rnn到预训练的transformer和LLM的历史转变,这与NLP所采取的过程完全相同。还讨论了特定于代码的特性,如AST、CFG和单元测试,以及它们在训练代码语言模型中的应用,并确定了该领域的关键挑战和潜在的未来方向。
在过去的十年里,软件工程界提出了各种评估任务来评估代码模型。CodeXGLUE将大多数此类任务合并整合为一个单一的基准测试,包括代码理解任务,如克隆检测、缺陷检测和序列到序列的生成任务,如代码修复、代码翻译、程序合成和代码摘要。然而,在Chen等人(2021)引入HumanEval和Codex之后,text-to-code的合成成为NLP社区关注的焦点,并已成为评估LLM的标准任务(图2)。因此,论文首先简要介绍了每一个传统任务以及预训练语言模型的应用,并在图3、4中提供了每个任务的相关工作的全面列表。然后,回顾了评估指标,并更详细地研究了程序合成。最后,论文还讨论了存储库级评估的最新趋势。在附录A中,列出了每个下游任务的基准测试。
2.1 代码处理的下游任务
根据软件工程中的自定义,论文根据代码的输入/输出方式对代码的评估任务进行分类,并将这些任务分为五类:text-to-code, code-to-code, code-to-text, code-to-patterns和text-to-text。这种分类法与NLP中的理解-生成二分法交织在一起,因为每个类别可能同时包含理解和生成任务。
2.1.1 text-to-code
Text-to-code 任务以文本作为输入,并输出代码。 - Code retrieval旨在检索给定的自然语言查询的相关代码,或从未注释的语料库中挖掘并行文本代码对。这个任务通常是通过计算查询代码和候选代码的嵌入之间的相似性度量来完成的,而由双向语言模型产生的上下文嵌入——如BERT——已经被证明是非常有用的。 - Code synthesis 旨在生成给定一个自然语言描述的代码(通常是一个函数或一个方法)。这个任务可以看作是使用生成模型而不是检索模型的代码检索的更新版本。统计机器翻译(SMT)和神经机器翻译(NMT)模型通常使用增强的解码器,利用编程语言独特的语法规则,且已被广泛应用于这项任务。然而,基于transformer架构的预训练语言模型通过即使没有特定任务的微调直接生成自回归语言建模风格的源代码,改变了游戏规则。论文将在3.3中更详细地讨论这个任务。 - Text-to-SQL是代码合成的一种特殊情况(也可以说是更容易的),其中模型的任务是从自然语言查询中生成SQL命令。由于SQL的结构化特性(与Python和C等通用语言相比)以及在数据管理中的广泛应用,它一直是一个特别感兴趣的话题。 - Math programming这也是代码合成的一种特殊情况,其中需要一个语言模型,通过生成将由外部解释器执行的代码来解决数学推理问题。该任务从数值计算中抽象出了推理过程,因此对评估LLM具有特别的意义。
2.1.2 Code-to-Code
Code-to-Code的任务以代码作为输入,并输出代码。 - Code search是一个类似于代码检索的任务,与后者的不同之处仅在于,输入是一个现有的代码片段,通常使用与目标不同的编程语言。 - Code completion旨在完成一个带有其前缀的代码片段。这本质上是应用于代码的语言建模,相关技术已经逐步引入: n-gram、RNN和transformer。然而,由于编程语言的结构化性质,许多早期研究发现语法辅助统计模型表现更好,神经模型直到2018年之后才成为主导地位(直观概述见图3)。 - Code translation 旨在将一段代码(通常是一个函数或方法)翻译成另一种编程语言。代码翻译和跨语言代码搜索之间的关系类似于代码合成和text-to-code检索之间的关系,SMT/MNT模型也被广泛应用于这项任务。代码合成在帮助程序员编写代码片段方面很有用,而代码翻译是迁移用过时语言编写的旧项目的一种重要技术。然而,作者还没有看到这样的应用程序,因为即使是最强大的语言模型的上下文窗口也是相当有限的。 - Code repair 也被称为bug修复,旨在修复一个有问题的代码。与代码翻译一样,它也是一项传统的序列到序列的生成任务,关于这个主题目前已有大量的研究。 - Cloze test是在bert式预训练兴起之后,最近提出的代码处理任务。由于编程语言的独特语义,这个测试经常选择几个关键字,如min和max。 - Code infilling是另一项最近提出的任务,在fill-in-the-middle预训练开始流行之后。它是代码完成的泛化,空白不仅给出了左边的上下文,还给出了右边的上下文。但是,它与填空测试的不同之处在于,填空测试的目标只是一个标记,而代码填充的目标可以是整行甚至多行,这需要一个解码器来自动回归生成。 - Obfuscation指重命名标识符(例如变量、方法和类)的过程,例如为通用名称,如var_1、var_2或x、y。它是病毒检测、知识产权保护和代码缩放方面的重要技术。去混淆是指反向过程,即从混淆的程序中恢复有意义的标识符名称。混淆对语言模型的应用很少,因为它可以很容易地静态地实现,但去混淆近年来已经成为人们更感兴趣的话题,并被用作代码语言模型的预训练目标 - Unit test generation旨在为一个给定的程序生成单元测试。在Codex和其他代码LLM出现之前,该领域中几乎所有的工作都采用了非神经方法(见图3)。然而,在LLM的时代,这项任务越来越重要,因为研究表明,目前评估LLM的程序综合能力的单元测试可能不够充分 - Assertion generation是一个与单元测试密切相关的任务。给定一个程序和一组单元测试,这个任务的旨在生成断言(在软件工程中也称为预言),以使用单元测试来评估程序。这个任务通常没有被NLP社区注意到,因为用于评估LLM的程序综合任务通常涉及独立的、竞争风格的方法,这样简单地断言程序输出和预期答案之间相似就足够了。 - Mutant generation旨在为突变测试生成给定程序的变体,与单元测试生成和断言生成密切相关。给定的一组单元测试和断言没有检测到的变体表明需要额外的测试用例或更好的断言。最近,屏蔽源代码中的标记并从屏蔽语言模型的输出中对它们进行采样已经成为这项任务的常用方法。Papadakis等人(2019年)就这一主题进行了调查。 - Fuzzing旨在改变一组给定的单元测试,以生成新的测试用例,这是另一个与测试软件相关的任务。虽然最近有许多关于模糊目标深度学习库的工作,但很少有人使用语言模型来进行这一过程(见图3)。 - Type prediction旨在预测动态编程语言的类型,如Python和JavaScript。它已被用作代码语言模型的预训练目标其中它通常被简化为一个二进制标记任务,以预测代码中的哪些标记是标识符。
2.1.3 Code-to-Text
Code-to-Text任务以代码作为输入,并输出文本。 - Code summarization 也被称为文档字符串生成,其旨在为一个给定的代码段(通常是一个函数或方法)生成一个自然语言描述。这与代码合成相反,SMT/NMT技术也有同样的应用。 - Code review旨在自动化同行代码评审的过程,并可能有多种形式。许多早期的工作将其定义为一个二进制分类任务,即在提交时接受或拒绝更改,而另一些工作则利用信息检索技术从现有的评论池中推荐评论。然而,随着生成模型变得越来越强大,研究人员还将直接生成评论作为一项序列到序列的学习任务进行了研究。 - Identifier prediction是预测代码中的标识符名称的任务。由于这些名称被认为包含重要的语义信息,该任务已被用于代码摘要,以及预训练代码模型。
2.1.4 Code-to-Patterns
Code-to-Patterns任务要对代码进行分类。 - Defect detection预测输入代码是否有问题,这是一个标准的单句分类任务。 - Clone detection预测两个代码片段是否会相互克隆。在软件工程中,有四种类型的代码克隆,其中最具挑战性的识别类型是语义克隆,即具有相同功能的语法不相同的代码。由于该任务可以看作是一个两句分类任务,BERT风格语言模型得到了广泛的应用 - Code classification 由Mou等人(2016)推广,旨在预测一个预定义的标签集内的一个代码片段的功能。一个非常相似的任务是作者识别,它可以预测输入代码的作者。这两个任务都是标准的单句分类任务。 - Code reasoning是最近引入的评估LLM的任务,通常作为一般评估基准的一个子集,如MMLU。该任务要求模型对代码或算法进行推理,并回答以多项选择题形式编写的相关问题,其范围可能从概念理解到数值计算和复杂性分析。
2.1.5 Text-to-Text
Text-to-Text以文本作为输入,并输出文本。 - Document translation是与代码相关的文档的自动翻译。由于机器翻译的模型、数据集和提示策略在NLP中非常丰富,论文没有详细介绍这项任务。 - Log parsing旨在分析由软件产品产生的系统日志,例如将日志解析为结构化模板或从原始日志中查找异常。
2.2 评价指标
在2.1中提到的任务中,理解任务在形式上与自然语言理解任务相似,并同样通过准确性、F1和平均倒数秩(MRR)等指标进行评估,而标识符预测等短生成任务也通过精确匹配的准确性进行评估。Code-to-text的任务使用文本生成的公共指标进行评估,如BLEU 。 另一方面,对涉及代码生成的任务的评估则更为复杂。大多数早期的工作都评估了语法的正确性,即可以成功解析的生成的百分比。Chen等人(2018)反对此类指标,并建议进行参考文献匹配,即与参考文献完全相同的代的百分比。Ren等人(2020)提出了CodeBLUE,这是BLEU的一种变体,通过评估抽象语法树(AST)和数据流的重叠,考虑代码语法和语义。 然而,随着这些年代码生成模型变得越来越强大,这些基于内容重叠的指标已经不够用了,因为功能相当的代码片段在词法形式上可能会有很大不同。因此,研究人员将注意力转向了功能正确性。此类指标的一个流行示例是pass@k,由Kulal等人(2019年)提出并由Chen等人(2021年)完善,它是模型通过k个生成样本中任何一个的程序的所有单元测试的机会的无偏估计值。这个度量可以推广到passn@k,它将模型提交的数量限制为n,但允许通过k个样本输入中给出的单元检验进行过滤。
2.3 程序合成
随着代码模型的发展,研究人员逐渐将注意力转向程序合成的实际任务。CONCODE是该领域的早期数据集之一,它包括超过100K个Java方法,并被纳入CodeXGLUE基准测试的子网。自2021年以来,该社区已经见证了大量针对这项任务的数据集。其中大多数,包括APPS,HumanEval和MBPP,重点关注Python,但最近的工作也将HumanEval扩展到其他编程语言。DS-1000是一个更现实的Python数据集,它专注于数据科学库,如NumPy和SciPy,而一些数学推理基准也已被转换为编程任务,包括MathQA-Python和GSM8K-Python。
2.4 存储库级评估
2.1和图3中讨论的大多数评估任务都仅限于单个文件,甚至是单个函数,因为跨文件代码建模提出的挑战超出了大多数现有语言模型的能力。然而,最近,位置插值技术已经将LLM的上下文窗口扩展到数十万token,使将整个存储库中的代码建模评估上下文化成为可能。一些工作研究了利用存储库级上下文的代码完成,Liu等人(2023)提出引用本来评估这些系统。最近,Bairi等人(2023)研究了存储库级API迁移和时间编辑等更具挑战性的任务,Jimenez等人(2023)引入了相应的基准测试,SWE-bench。
由于语言模型扩展到数千亿参数,它们中的许多已经证明了重要的编码能力,即使它们不是专门为代码设计或训练的。由CodeX首创,研究人员还发现,对代码进行持续的预训练,可以显著提高语言模型在代码方面的性能。
3.1 现成的语言模型
大型语言模型通常按照缩放定律在数万亿标记上进行预训练,如此数量的文本数据通常是一个不同的组合,由不可忽略的部分代码组成。例如,Pile包括从GitHub 800GB原始数据集中抓取的95GB代码,而多语言预训练数据集ROOTS在其1.6TB数据中也包含跨越13种编程语言的163GB的代码。作为两个最大的开源预训练数据集,它们支持了许多具有编码能力的语言模型。例如,Chen等人(2021)报道了GPT-J在HumanEval上的优秀表现,而Scao等人(2022)报道了GPT-NeoX和BLOOM的在HumanEval上类似结果。LLaMA的预训练数据集包括来自GitHub的328GB代码,在HumanEval上获得了23.7的 pass@1表现,其后继者LLaMA 2(获得了更高的分数,为29.9分。 另一方面,闭源模型总体上表现得更好。LaMDA和PaLM,其预训练数据集分别包含12.5%和5%的代码,达到14.0和26.2 pass@1表现,而GPT-4的记录为惊人的67.0(Bubeck等人报告的早期版本(2023)为82),直到最近都一直高于任何预训练或为代码教学的专门模型。 最近,总体趋势是根据修正后的尺度法则,用更大的数据集训练更小的模型。例如,baichuan2是在2.6T token上训练的13B模型,而Qwen是在3Ttoken上训练的14B模型。他们在HumanEval上分别达到17.1和32.3 pass@1。事实证明模型小至1.3B可以获得的编码能力相当于更大的模型,同时保持合理的性能一般文本处理,甚至取得一些应急能力如链推理。他们的模型Phi-1.5在ChatGPT生成的教科书数据的21B标记和从堆栈溢出和细化网络过滤后的网络数据的100B标记上进行训练,并在HumanEval上获得41.4 pass@1的性能。这些模型的确切性能如表1所示。
3.2 带有关于代码的额外预训练的语言模型
随着开创性的基准HumanEval,Chen等人(2021)用Codex启动了LLM时代,这是在100B额外代码token上预训练的GPT-3,是最早的数十亿个代码模型之一。在他们的工作之后,其他研究人员也将他们的LLM专门研究代码处理,并进行了额外的预训练。Chowdhery等人(2022年)在7.8B额外代码token上训练PaLM以获得PaLM-Coder,在HumanEval和MBPP(表1)上取得了新的最先进的表现,这些技术后来才被其继任者PaLM 2-S*打破,PaLM 2的最小版本在未披露的代码量上进一步训练。同样,Lewkowycz等人(2022年)在arXiv论文和数学内容的38.5Btoken上训练PaLM,而Rozière等人(2023年)在超过500B代码token上训练LLaMA 2以获取Code LLaMA,其在HumanEval上的性能超过了除GPT-4之外的所有先前的LLMs(表1)。Liu等人(2023)通过多任务微调(MFT)进一步训练Code LLaMA,引入CodeFuse-CodeLLaMA,达到74.4 pass@1,甚至超过了OpenAI发表的GPT-4。 虽然几乎所有这些模型都是经过CLM预训练的transformer解码器,但正如论文在前文中提到的,在此过程中已经引入了一些架构修改。所有这些模型都使用了预范数,GPT-J引入了并行注意,后来被PaLM、GPTNeoX和Phi-1.5所采用。PaLM将MQA和RoPE引入LLM中,RoPE现在被大多数语言模型使用,包括GPT-NeoX、两代LLaMA、Qwen和baichuan 2 7B版。而BLOOM和13B版的baichuann 2则采用ALiBi进行位置嵌入,而LLaMA 2和Code LLaMA则采用GQA代替MHA或MQA。在后文中,论文展示了专门对代码进行预训练的专门模型也密切跟踪了这些工作进步。
随着GPT和BERT等预训练的transformer在自然语言处理方面取得了显著的成功,这种模型架构、学习范式和训练目标很快被软件工程社区采用,来制造用于代码理解和生成的专门模型。在本节中,论文首先回顾用于预训练代码语言模型的常用数据集,然后通过它们的模型架构深入到复杂的代码lm家族:仅编码器模型、编码-解码器模型、仅解码器模型、UniLM和扩散模型。最后,论文还说明了当前在NLP中应用最近技术的趋势,如指令调优和强化学习进行代码处理。表3提供了这些预训练模型的概述。
4.1 代码训练数据集
虽然用于预训练语言模型的文本数据通常是从网络中获取的,但必须经过细致的、经常是积极的预处理,代码数据自然是来自GitHub存储库的整个公共文档。更好的是,它们提供了现成的质量指标,如star或fork的数量(尽管Allal等人(2023年)表明,star数与下游性能的相关性较差)。因此,引入了许多大规模的代码预训练数据集,包括CodeSearchNet、CodeParrot和the Stack,分别总计20GB、50GB和3TB的代码文档(表2)。
虽然这些数据集是用于训练代码模型的,但需要注意的是,代码最终是自然语言的一种特殊形式,因为大多数编程语言的词汇表都是英语的一个小子集。此外,高质量的代码经常与自然语言的注释或文档交织在一起,这也使模型能够获得一般文本表示的某些知识。事实上,在CodeSearchNet中的6.5m函数中,有2.3M与自然语言文档配对,允许模型对这些双态数据进行显式训练。 与自然语言相比,从GitHub抓取代码的另一个副产品是提交历史,它包括提交前的代码、提交后的代码和描述提交的短消息,这些消息可以松散地作为语言模型的指令。Muennighoff等人(2023)利用这个特性,构建了一个2GB的数据集CommitPackFT,其中包含742K个代码指令数据样本,避免了构建自然语言指令所需的大量人工劳动。 除了双态训练和指令微调之外,最近构建代码数据集的另一个趋势是使用ChatGPT等强大的模型来综合数据。虽然这种方法最初是为生成自然语言的指令数据而提出的,但Gunasekar等人(2023年)更进一步,合成了Python教科书和编码练习的1Btoken来预训练1.3B模型,在HumanEval上实现了与在大得多的数据集上训练的大得多的模型相当的最先进结果。
4.2 编码器
预训练过的transformer编码器,如BERT、RoBERTa和ELECTRA,在自然语言理解任务方面取得了令人印象深刻的结果,这些方法在出现后很快就被引入到代码处理中。Kanade等人(2020)在一个代码语料库上复制了BERT的训练程序,以产生CuBERT,展示了其优于LSTM和未预训练的transformer的优越性能。另一方面,Feng等人(2020),用MLM和ELECTRA’s RTD训练Code BERT。他们还利用了CodeSearchNet中的显式文本代码对,并分别使用它们作为BERT输入中的第一和第二段。当使用CodeBERT初始化普通transformer的编码器部分,用于序列到序列的生成任务,如代码摘要时,他们观察到比未预训练的基线有适度的性能提高。 除了这些标准的训练目标外,还引入了许多专门为代码设计的辅助目标。GraphCodeBERT和SynCoBERT都从源代码提取图(数据流图和抽象语法树)和训练模型预测节点之间的类型关系,而SynCoBERT和Code-MVP也添加类型推理的预训练阶段标记的形式。另一个共同的目标是对比学习: SynCoBERT和Code-mvp在输入的不同视图(如代码、注释、AST和转换代码)之间的对比,而DISCO通过混淆等语义保留转换构建正样本对,通过注入人工bug构建负样本对。
4.3 编码器-解码器
在NLP中,预训练过的transformer编码器-解码器,如T5和BART,在过去几年的语言建模方面也留下了显著的进步。例如,T5将所有文本任务统一为序列到序列格式,并在GLUE和SuperGLUE上取得新的记录。与仅编码器模型相比,编码器-解码器自然更强大,因为它们可以用于条件文本生成,而它们的编码器部分总是可以用于执行需要仅编码器架构的任务,如回归。 受编码器-解码器体系结构的这些优点的启发,许多这样的模型已经被提出用于代码处理。PyMT5和Mastropaolo等人(2021年)在代码语料库上复制了T5的预训练和多任务微调过程,而Ahmad等人(2021年)引入了PLBART,这是一种基于Java、Pyhton和自然语言组合的655GB数据的预训练的BART。Lachaux等人(2021)认为,对于编程语言来说,MLM可能太容易了,因为标识符名称经常在单个上下文窗口中多次出现,并提出了一个去模糊预训练目标,即模型被训练将混淆代码转换回其原始形式。与此方法相关的是,我们注意到,有意义的变量名也被发现对大型语言模型的代码生成过程有积极的影响。 基于这些早期工作,Wang等人(2021)提出了CodeT5,即1)T5的原始跨度损坏;2)标识符标记(标识代码输入中的每个标记被标记为标识符或非标识符);3)屏蔽标识符预测(跨度损坏的特殊形式,所有标识符都被屏蔽);4)text-to-code和Code-to-text的生成。它的继任者CodeT5+从UL2获得灵感,并引入因果语言建模(CLM),以及基于文本代码匹配的其他对比目标。 AlphaCode也训练多个目标,编码器用MLM训练,解码器用CLM进行架构修改,如浅编码器和深度解码器、多查询注意,并且比CodeT5大得多(高达41B参数)。NatGen,另一方面,预训练的“归化”目标类似于去混淆:语义等效但不自然的代码由预定义操作生成如循环转换、死代码注入,变量重命名,模型预训练将这些不自然的代码转换回原始形式。作者注意到,其中一些模型是建立在以前的工作之上的。例如,NatGen是用CodeT5初始化的,而CodeT5+的最大版本是从仅解码器模型CodeGen初始化来的。 除了这些一般的预训练目标外,一些工作还训练了transformer编码器-解码器,重点是代码翻译,这是transformer模型在代码中的自然应用,因为transformer架构最初是由Vaswani等人(2017)用于机器翻译(MT)提出的。然而,与自然语言不同的是,跨两种或两种以上人类语言的并行语料库数量丰富,关于代码的并行数据很少。为了解决这个问题,Roziere等人(2020)提出了编码器,首先用XLM,然后用这个编码器初始化vanilla transformer,继续预训练去噪自动编码和翻译,而其后续工作也利用语言独立的中间表示来增强这一过程,论文将在下文中更详细地讨论。 除了训练数据和目标外,这些模型大多保持了NLP社区提出的原始架构,如表3所示。例如,基于BART的模型使用后归一化和可学习的绝对位置嵌入,而基于T5的模型使用其简化的相对位置嵌入和预向量化。
4.4 解码器
GPT-3和上下文学习的发现,解码器transformer模型成为主导语言建模。在代码处理中也出现了许多类似于CLM预训练的模型,如GPT-C,CodeGPT,PolyCoder,CodeGen,PyCodeGPT,Pangu-Coder,CodeGeeX,CodeFuse,CodeShell。在这些模型中,已经尝试了一些替代的训练目标,如Pangu-Coder中的MLM和掩码CLM,但发现与CLM-only训练相比表现不佳。Zan等人(2022)还提出了在草稿上的持续训练,即模型首先学习生成一个程序的草稿,然后生成实际的代码。值得注意的是,Gunasekar et al.(2023)提出Phi-1,一个1.3B的小模型在一个只有7B个标记的数据集上训练,该数据集由6B个来自StackOverfolw的token和ChatGPT生成的1B个合成数据组成,但实现50.6 pass@1HuamnEval和55.5 pass@1 MBPP,可与大(模型大小和训练数据大小)模型相比,如Code LLaMA或PaLM 2。 尽管Cristofoulou等人(2022)报告了去噪目标在仅解码器模型中表现不佳,但也有其他工作成功地将去噪或多任务预训练与解码器架构结合起来。Incoder,SantaCoder,StarCoder都采用仅解码器的架构,由填充中间(FIM)目标训练,也被Fried等人(2023)称为因果掩码,它本质上是span corruption,。这些填充目标的一个明显优势是,它们为模型注入了在推理时在输入代码中间填充空白的能力,而CLM只允许自回归生成。然而,如表4所示,与CodeGen等CLM-only模型相比,这些目标也导致下游任务具有更高的性能。 从表3中可以明显看出,与其他模型架构相比,代码的仅解码器模型通常更接近地遵循NLP中的实践。所有这些模型都使用了预归一化,而MQA、RoPE和并行注意也被几个模型所采用。值得注意的是,最新的三种模型——StarCoder、Phi-1和CodeFuse——也使用了闪照注意力来提高模型的吞吐量。
4.5 UniLMs
继NLP中的UniLM之后,一些代码处理方面的工作也对这第四族transformer模型进行了代码方面的预训练。CugLM通过交替注意掩模进行CLM和MLM + NSP训练,而UniX编码器使用CLM、MLM、Span Corruption(前缀LM风格),以及对比学习和文本代码相互生成等辅助目标来进行训练。然而,这两种模型的规模都相对较小,这种体系结构是否适合代码处理还有待探索。
4.6 扩散模型
目前,transformer架构主导了文本生成,但一些工作也采用了计算机视觉中的扩散模型进行文本生成。最近,CodeFusion也将扩散模型引入代码建模,并证明了75M扩散模型在3个代码合成数据集上优于StarCoder、CodeT5+和GPT-3。
4.7 代码的教学微调和强化学习
在自然语言处理中,对带有指令前缀的不同任务的训练模型,即指令微调,已被证明可以解锁跨任务泛化的能力。首先,这些指令数据样本是人工编制或众包的,但后来的研究发现LLM生成的指令已经足够。 在使用自然语言的这些工作之后,来自代码社区的研究人员也对他们的模型应用了指令调优。Wang等人(2023)利用InstructGPT生成的20K指令数据微调CodeT5+,获得InstructCodeT5+。 WizardCoder遵循WizardLM的方法将20K编码Alpaca样本演化为一个78K数据集,并使用其微调StarCoder。Pangu-Coder2也使用WizardLM的进化指令从20K代码Alpaca生成68K指令样本,但也通过等级响应引入强化学习,以对齐测试和教师反馈(RRTF)。另一方面,OctoCoder采用不同的路径,使用Git提交历史作为指令数据来调整StarCoder和CodeGeeX2。最近,CodeFuse也采用了多任务微调,并明确地在其指令数据中引入多个下游任务。这些指令精细化的代码模型的性能也可以在表4中找到。 在NLP中,另一项与指令微调密切相关的技术是从人类反馈中进行强化学习(RLHF),它在调整LLM与人类价值方面发挥了重要作用。强化学习的优点在于,它可以将不可区分的奖励信号整合到训练中,如BLEU和人类偏好,但对齐LLM所需的人类反馈往往涉及大量的注释工作。相比之下,将强化学习应用于代码模型具有很天然的优势,因为编译器可以用于为语言模型产生的代码样本自动生成反馈。 CodeRL就是这样一个模型,它为每个生成的程序定义了四个反馈级别(即编译错误、运行时错误、单元测试失败、通过)以及由评论家模型估计的细粒度token级反馈。演员模型是CodeT5的扩展,然后用强化算法进行训练。类似地,CompCoder和PPOCoder分别对CodeGPT和CodeT5进行近端策略优化,而RLTF提出了细粒度的反馈基于错误信息和编译器提供的位置,以及自适应反馈考虑通过测试用例的比例。
编程语言和自然语言之间的一个主要区别是,前者被人为地定义为精确和明确的,在执行之前需要编译(或解释)没有错误。除了CLM、MLM和Span Corruption等词汇操作之外,这允许在设计代码的预训练目标时具有更大的灵活性。在神经网络被引入主流自然语言处理文献之前的过去几年里,也可以观察到类似的趋势,当MT社区的研究人员利用文本的替代观点,如句法特征来提高SMT系统的性能。然而,这些特性并不是普遍适用的,甚至不是一致的,经常导致高度复杂的系统(例如,英语词性标签集的大小可能从几十到数百不等)。 然而,编程语言在这些方面的表现要好得多。每个主流编程语言,如C,Python,Java,有现成的编译器工具包,允许简单和准确的提取语义如抽象语法树(AST),语言独立的中间表示(IR),和辅助信息,如每个token的类型和控制/数据流图(CFG/DFG)。因此,在基于transformer的代码语言建模的环境中,许多工作已经将这些特性纳入到它们的训练过程中。
5.1 抽象语法树和中间表示法
AST是编译过程中最常见的中间结果之一,其中一个程序被解析为一个操作树及其操作数树。在transformer在代码处理社区普及之前,已经有了InferCode等工作,他们使用基于树的CNN等特殊网络架构来处理这些表示,并通过预测子树进行自监督预训练。 TreeBERT是将AST引入基于transformer的预训练-微调框架的首次尝试之一。这是一个transformer编码器解码器预训练树MLM和节点顺序预测,编码器采取一组组成路径作为输入(每个token是一个路径,这是其节点的连接表示)而解码器将代码作为输入。然后,树MLM通过屏蔽路径表示中的某些节点及其解码器输入中相应的码token来执行,而节点顺序预测是通过交换路径中的节点并使用类似于BERT的[CLS]token进行预测来完成的。 然而,TreeBERT所使用的方法是复杂的,而且规模也不好。后来的工作大多选择首先将AST处理为文本序列,并将其当作输入的正常部分。例如,Wang等人(2021),通过深度遍历处理AST,并与代码和注释连接,然后训练SynCoBERT(与TreeBeERT不同,实际上是一个类BERT的编码器模型),有四个目标: 1) MLM;2)标识符标记;3) AST边缘预测(从这些节点表示的点积预测两个AST节点之间是否存在边);4)对比学习i)代码和AST对,以及ii)文本和代码-AST对。类似地,SPT-Code,一种transformer编码器-解码器,以代码、序列化AST和文本的连接作为输入,并预训练1)span corruption;2) codeAST预测(NSP,一个段为代码,一个段为AST);3)方法名称生成,一种特殊的span corruption形式,其中一个方法名称被掩码。然而,与其他作品不同的是,它们并不将文档字符串作为输入中的文本段,而是将代码中出现的所有方法名连接为一种简洁的自然语言描述。同样地,UniXcoder在训练过程中采用扁平的AST而不是源代码作为其输入。 在编译pipeline中,AST之后通常是独立于语言的中间表示,如LLVM IR。这些特性独立于特定的编程语言,使它们成为翻译中心的候选对象,低资源自然语言机器翻译中的英语也一样。Szafraniec等人(2023年)利用这一特性,通过对代码和IR进行翻译语言建模以及从代码生成IR来扩展代码转换器。他们还研究了其他目标,如IR反编译(即从IR生成代码)和IR透视(即直接从另一种语言的IR直接生成代码),两者都显示了有希望的结果。
5.2 控制流和数据流
虽然AST和IR在某些任务中被证明是有用的信息,但它们本质上是静态的,就像源代码一样,可能无法捕获仅在运行时显示的代码的语义属性。然而,这种语义包含在诸如控制流和数据流等动态特性中。与AST类似,在预训练好的transformer出现之前,使用专门的网络来处理这些信息,如ProGraML使用的消息传递神经网络。然而,与AST不同的是,即使在经过预训练的transformer占主导地位之后,也很少有工作关注这个方向。 GraphCodeBERT就是这样一个工作,它创建特殊的token和转换嵌入的变量,并连接文本和源代码后变量序列构造输入模型,定制注意面具代码和变量段从代码段和变量段:当且仅当变量从代码标记中相互识别,而对于变量段内的标记,如果数据流中有从vj到vi的直接边,vi允许关注vj。模型然后预训练MLM结合边缘预测和节点对齐,这两者都是通过二进制分类的点积两个标记的表示(一个从代码段和一个从变量段节点对齐,并从变量段边缘预测)。
5.3 Type
除了AST、IR和数据流之外,类型信息也被用于帮助语言模型处理代码。CugLM,例如,使用类型信息在微调期间帮助预测的标记MLM单向(即MLM单向注意掩码):掩码token的类型首先预测从最终transformer层的表示,然后token本身预测基于隐藏的表示和预测类型。相比之下,CodeT5和SynCoBERT在他们的预训练目标中都包含了标识符标记,这可以被视为粗粒度类型预测。 值得注意的是,Wang等人(2022)将上述许多特性集成到Code-MVP中:源代码、文档字符串、AST、CFG和通过标识符重命名、循环交换和死代码插入转换的源代码。该模型从GraphCodeBERT初始化,然后使用MLM、细粒度类型预测和不同视图之间的对比学习进行训练,如text vs.code、code vs.AST和code vs.CFG。
随着语言模型在软件工程基准上创造了新的记录,软件工程技术也在扩大语言模型的边界,并随后引导它们进入现实世界的开发周期。
6.1 使用编码工具扩展的LLM
NLP社区的研究表明,LLM可以学习使用外部工具,如计算器、MT系统和搜索引擎。因此,解释器已被用于在复杂的推理任务中增强LLM。PAL和PoT都使用Python解释器进行数值计算,而ViperGPT进一步扩展视觉api从视觉输入中提取信息并回答相关问题。 除了减轻了抽象推理任务中的数值计算负担外,解释器还提供了关于代码生成过程本身的反馈,以及单元测试。CodeT和TiCoder使用Codex生成单元测试,并针对生成的代码样本进行运行,以提高模型在代码合成方面的性能。类似地,TransCoder-st通过代码转换的外部单元测试增强了TransCoder和DOBF。在前文中,论文还证明了单元测试的执行结果可以作为代码强化学习的自然监督信号。 值得注意的是,在2023年3月,OpenAI还发布了一个针对ChatGPT的解释器插件,它可以接受来自用户的文件输入,根据用户指令生成代码,并通过实时执行提供反馈。一些工作表明,该特性允许GPT-4进行自我调试。 在LLM研究中,一个与工具使用密切相关的主题是规划作为智能代理,这已被证明在理论上和经验上都可以提高LLM的能力。Ruan等人(2023)发现,LLM可以计划使用外部SQL生成器和Python生成器来解决复杂的任务,而CodePlan则证明了它们可以通过自适应规划来执行存储库级编码。 另一个工作流使用LLM来创建用于代码生成的多代理系统,如自我协作、ChatDev和MetaGPT。在这些框架中,多个LLM会被提示扮演不同的角色,如程序员、评审员和经理。这些角色相互交互,将代码生成分解为不同的阶段(例如,设计、编码、测试和文档化),并协作完成复杂的任务。
???????
6.2 集成到软件开发中的LLM
随着LLM的交互式编码能力的提高,研究人员也开始将它们集成到软件开发的每一个过程中。 自动代码完成是软件开发中语言模型最早的应用程序之一,因为它们只需要预测下一个token的能力。甚至在语言模型扩展到数十亿参数之前,就已经将完成系统和智能代码等集成到流行的IDE中。 然而,最近,代码语言模型的应用已经超越了简单的代码完成。GitHub Copilot可以说是最流行的人工智能代码助手之一,具有多种特性,包括代码生成、漏洞检测和许可证管理,而CodeFuse还将代码生成、代码翻译、代码注释和测试用例生成集成到一个IDE扩展中。然而,随着代码语言模型的增大,它们的客户端部署和实时性能也带来了新的挑战。 随着LLM的不断发展,在其之上构建应用程序本身也将发展成为一项重要的任务。许多针对此类应用程序的开源框架已经发布,包括LangChain、AutoGPT和WorkGPT。这些框架为开发人员提供了语言模型的抽象,并且正在积极地革新软件开发的整个过程,即使这个研究正在完成中。
在这项工作中,论文系统地回顾了使用预训练的transformer语言模型进行代码处理的历史,并强调了它们与在一般领域上预训练的模型之间的关系和比较。代码建模的进展通常遵循NLP的历史进程,从SMT模型到NMT模型,然后到微调经过预训练的transformer,最后到现实生产中LLM甚至自主代理的few-shot应用。与自然语言不同,代码的性质使得它很容易从替代视图中提取辅助信息,并利用解释器和单元测试来进行自动反馈。
考虑到这一点,作者确定了当前代码建模开发中所面临的几个挑战。
- Comprehensive benchmarks to push code LLMs to the next stage.广泛使用的HumanEval基准测试在代码LLM的演化中起着关键作用。然而,它相对较小,它的记分板已经被操纵到接近完美,这并不能准确地反映现实世界的行为。已经提出了许多其他关于代码LLM的基准测试,但它们仍然不够全面,不足以反映生产级别的需求。社区渴望在HumanEval之后有一个新的标准基准测试,以进一步推动代码LLM的进展到下一个阶段。
- Acquisition of high-quality data.随着Gunasekar等人(2023)通过基于教科书数据训练的1.3B模型实现SOTA性能,作者相信,在不久的将来,对于自我监督的预训练和有监督的微调,训练数据的选择和合成数据的利用将更加突出。
- Integration of code features into language models.正如论文所指出的,CFG和DFG尚未在代码语言建模中规模使用。少数使用数据流的工作对模型的注意力掩模进行了改变,这严重限制了它们的跨任务泛化和缩放能力。作者认为,将这些特性无缝集成到文本输入中是值得进一步研究的。
- Application of LLMs in more code downstream tasks.正如论文所指出的,目前对LLM编码能力的评估主要集中在程序合成上,图3清楚地显示了与软件测试相关的任务(即单元测试生成、断言生成、变体生成和模糊化)和去模糊化在LLM中的应用很少。此外,由于LLM的上下文窗口目前相当有限,因此诸如程序合成和代码翻译等生成任务尚未应用到方法级别之外。在论文中,作者列出了一些关于存储库级代码完成和时间编辑的工作,他们相信LLM在更多存储库级任务中的应用将成为未来的热门研究热点。
- Alternative model architectures and training objectives.在表3中,论文展示了许多代码语言模型都是用特定于代码的辅助目标进行预训练的,但这些模型都属于仅编码器或编解码器家族,而仅解码器模型尚未使用替代目标进行增强。此外,正如Singh等人(2023年)所开创的那样,作者相信扩散模型将在未来的代码建模中找到其基础。
- Building code LLM ecosystem for fulllife-cycle of software development.虽然学术界已经见证了大量的代码模型,但大多数都是作为IDE插件部署在编码阶段,而忽略了软件开发生命周期中的其他阶段。在论文中,作者提到了几个鼓舞人心的例子,希望在软件开发的整个生命周期中看到更多的代码LM的应用,从需求分析到DevOps,最终达到像PyTorch和hugging face这样的全面生态系统
- Safety and ethics issues related to code LLMs.随着语言模型的发展,它们也提出了安全问题,包括但不限于数据污染、有害或有偏见的生成、个人信息泄露和幻觉。在软件开发中,这些模型的部署应该格外谨慎,因为它们生成的代码可能包含导致灾难性结果的安全风险。预训练数据也成为伦理的一个敏感话题,Kocetkov等人(2022)通过允许开发人员从堆栈中删除他们的代码,朝着这个问题迈出了有意义的一步。随着综合训练数据的广泛应用,研究人员也应该谨慎对待这种做法,因为用人工智能生成的数据训练人工智能模型的结果还有待大规模调查。
通过本次研究,作者希望提供语言模型在软件工程中应用的全球视角,并将来自两个社区的研究联系起来。作者相信,目前LLM的激增,最终将转化为现实世界的应用,并引领人类进入一个更光明的未来。