【论文解读】用于代码处理的语言模型综述
1.简要介绍
在这项工作中,论文系统地回顾了在代码处理方面的最新进展,包括 50 个+模型,30 个+评估任务和 500 个相关工作。论文将代码处理模型分解为由 GPT 家族表示的通用语言模型和专门预训练的代码模型,通常具有定制的目标。论文讨论了这些模型之间的关系和差异,并强调了代码建模从统计模型和 rnn 到预训练的 transformer 和 LLM 的历史转变,这与 NLP 所采取的过程完全相同。还讨论了特定于代码的特性,如 AST、CFG 和单元测试,以及它们在训练代码语言模型中的应用,并确定了该领域的关键挑战和潜在的未来方向。
2.代码处理的语言模型的评估
在过去的十年里,软件工程界提出了各种评估任务来评估代码模型。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。
3.通用语言模型
由于语言模型扩展到数千亿参数,它们中的许多已经证明了重要的编码能力,即使它们不是专门为代码设计或训练的。由 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。在后文中,论文展示了专门对代码进行预训练的专门模型也密切跟踪了这些工作进步。
4.用于代码处理的特定语言模型
随着 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 提出了细粒度的反馈基于错误信息和编译器提供的位置,以及自适应反馈考虑通过测试用例的比例。
5.语言模型的代码特性
编程语言和自然语言之间的一个主要区别是,前者被人为地定义为精确和明确的,在执行之前需要编译(或解释)没有错误。除了 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.软件开发中的 LLM
随着语言模型在软件工程基准上创造了新的记录,软件工程技术也在扩大语言模型的边界,并随后引导它们进入现实世界的开发周期。
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。这些框架为开发人员提供了语言模型的抽象,并且正在积极地革新软件开发的整个过程,即使这个研究正在完成中。
7.结论与挑战
在这项工作中,论文系统地回顾了使用预训练的 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 的激增,最终将转化为现实世界的应用,并引领人类进入一个更光明的未来。
版权声明: 本文为 InfoQ 作者【合合技术团队】的原创文章。
原文链接:【http://xie.infoq.cn/article/1eb88aa24dc2806584c54c528】。文章转载请联系作者。
评论