论文阅读 - code2vec Learning Distributed Representations of Code

泛读

我们提出了一个神经网络模型,将代码片段表示为连续分布向量(代码嵌入)。其主要思想是将一个代码片段表示为一个固定长度的代码向量,可用于预测代码片段的语义属性。为此,首先将代码分解为其抽象语法树中的路径集合。然后,网络学习每条路径的原子表示,同时学习如何聚合其中的一组路径。通过用从它身上的向量表示来预测方法名称,来证明我们方法的有效性。

论文阅读 - Contrastive Code Representation Learning

Motivation

先前工作从上下文重建tokens来学习源代码上下文表示,对下游的语义理解任务,如代码克隆检测,这些表示应该理想的捕捉程序的功能。但是,我们发现流行的基于重构的RoBERTa模型对代码编辑很敏感,甚至当编辑保留了语义。

ContraCode

我们提出ContraCode:一个对比的预训练任务,学习代码的功能,不只是形式。ContraCode 预训练一个神经网络在许多非等效的干扰项中识别功能相似的程序变体。我们使用一种自动源到源的编译器作为数据扩充的一种形式,伸缩的生成这些变体。对比性预训练在逆序代码克隆检测基准上的表现优于RoBERTa 39%AUROC。令人惊讶的是,改进的对抗性鲁棒性会比自然代码更准确;与竞争基线相比,ContraCode将汇总和TypeScript类型推理精度提高了2到13个百分点。code,paper

像GitHub这样的大型代码库是学习机器辅助编程工具的有力资源。然而,大多数当前的代码表示学习方法都需要标签,而像RoBERTa这样流行的无标签自我监督方法对对抗性输入并不鲁棒。我们不是像BERT这样重建tokens学习代码说什么,而是学习代码做什么。我们提出ContraCode,一种对比性的自我监督算法,它通过基于编译器的数据预测来学习表示不变性。在实验中,ContraCode学习功能的有效表示,并对对抗性代码编辑具有鲁棒性。我们发现,Contra Code显著提高了三个下游JavaScript代码理解任务的性能。

论文阅读 - Associating Natural Language Comment and Source Code Entities

Paper,AAAI-2020

摘要

comment 是与 source code elements 相关联的自然语言描述,理解明确的关联可以提高代码的可理解性,并保持代码和注释之间的一致性。为了初步实现这个目标,我们解决了实体对齐的任务,关联 Javadoc comments 的实体和 Java source code elements 的实体。我们提出一种方法,从开源项目的修正历史自动提取监督数据,并为这个任务提出了一个手动注释的评估数据集。我们提出二分类和序列标注模型,通过构建一个丰富的特征集,包括了代码、注释、它们之间的关系。实验表明,我们的系统优于所提出的监督学习的几个基线。


疑问1:为什么要实体对齐?怎么对齐?

疑问2:数据集提取过程?规模?影响力?

疑问3:模型的细节,特征集怎么构造,基线是什么?

疑问4:这个工作带来的影响,现在的进展如何?

介绍

自然语言元素用于记录源代码的各个方面,summaries 提供了给定代码片段功能的高级概述, commit messages 描述了在软件项目的两个版本之间进行的代码更改,API comments 定义了代码块的特定属性(如先决条件和返回值)。 每一个都为开发者之间的沟通提供了一种重要的模式,对开发过程的高效性至关重要。这些自然语言元素越来越流行,在自然语言处理社区的 code summarization,commit message generation,code generation 的研究中。

特别的是,人们对结合自然语言注释和源代码的跨模态任务越来越感兴趣。要完成这些任务,必须了解注释中的元素如何与相应的代码中的元素关联。之前的研究中,检测代码和注释之间不一致的工作,合并对特定任务的规则来连接注释组件到代码的各个方面。最近在自动注释生成方面的工作,依赖一种注意力机制隐式的逼近注释中生成某种术语应注意的代码部分。

与这些方法相反,我们制定了一个任务,目的是学习注释中的实体和相应源代码中的元素之间的显式关联。我们相信显式关联会帮助系统为下游应用带来改进。比如在代码和注释生成任务,它们可以作为监督注意机制,并使用显示知识来增强神经网络模型,这往往带来性能的显著提高。此外,这提供了一种进行更加细粒度的代码注释不一致检测的方法,而不是识别完整注释是否与代码体不一致的常见方法。这样的系统可以成为自动化代码注释维护的有价值组件,目的在于使注释与它所描述的代码保持一致。通过提供注释中哪些元素被注释中的给定实体引用的信号,系统可以自动检测到注释中的实体是否与代码不一致。

学习这种关联的第一步,我们关注在 Javadoc @return comments,它用于描述返回类型和依赖于给定方法中各种条件的潜在返回值。我们观察到 @return comments 往往比其他形式的注释更加结构化,使其成为更干净的数据源,因此是所提任务的合理开头。此外,我们观察到注释通常描述给定代码体中的实体和动作,它们映射到自然语言中的名词短语和动词短语。至于 @return comments,它们描述的返回值通常是实体和与实体相关的条件(如输入参数、程序状态)。因为我们在本文关注与这样的评论,我们针对 @return comments 中的名词短语实体作为第一步,如图1、2,在注释中给定名词短语,任务是识别与它们关联的 code tokens

然而,学习自动解决注释和代码之间的关联,在数据收集方面具有挑战性。为 code/language tasks 获取注释数据是困难的,因为它需要理解特定编程语言的源代码的专业知识。此外,收集包含源代码和自然语言的高质量并行语料库具有挑战性,因为大型在线代码库中的数据本身就存在噪声。我们提出了一种新的方法,不需要人工注释,利用该平台的提交历史特性,从 GitHub 获得该任务的噪声监督。我们证明这种噪声监督提供了有价值的训练信号。

为今后的研究奠定基础,我们和相对简单的模型设计了一组高度显著的特征。我们提出了两种模型,在噪声数据上训练,在人工标注数据集上评估。第一个是二分类模型,单独的对给定的代码块的每一个元素进行分类,判断它是否与相关注释中的指定名词短语相关联。第二个是序列标记模型,特别是一个条件随机场 CRF 模型,它共同为代码中的元素分配标签,其中标签表示一个元素是否与指定的名词短语相关联。我们设计了一套新颖的特征来捕获上下文表示、余弦相似度以及与编程语言相关的 API 和语法。

在噪声数据上训练,两种模型的表现大大优于基线,二值分类器获得 F1 score 0.677,CRF 获得 F1 score 0.618,分别比基线提高了39.6%和27.4%。我们通过随着噪声训练数据量的增加,模型的性能提升来证明噪声数据的价值性。此外,通过消融研究,我们强调了模型所用的特征的实用性。本文的主要贡献如下:

  • 新任务:关联自然语言的注释和源代码的元素,结合一个人工标注的评估数据集。
  • 一种从软件改变历史和利用监督形式的机器学习系统中获取噪声监督的技术。
  • 一个新的特征集,捕捉代码和注释的特征和它们之间的关系,被用于模型中,可以作为未来工作的 baseline。

任务

给定注释中的一个名词短语(NP),其任务是将它和相应代码中的每个候选 code token 之间的关系分类为关联的和不关联的。候选项是code tokens,不包括Java关键字(如try,public,throw),运算符(如=),符号(如[,{)这些元素与编程语言语法相关,通常不会在注释中进行描述,如图中 token int,opcode,currentBC 和NP中的“the current bytecode”相关,但是int,setBCI,_nextBCT不是。该任务和自然语言文本的 anaphora resolution 回指解析有相似之处,包括明确地提到 antecedents 先行词(coreference 共引用)以及关联关系(bridge anaphora 桥接回指)。在这种设置下,注释中被选择的名词短语是 anaphora 隐喻,属于源代码的 tokens 是 candidate antecedents 候选先行词。然而,我们的任务与两者不同,因为它需要对两种不同的模式进行推理。如图1,“problem”显式关联e,但我们需要知道 InterruptedException 是它的类型,这是Exception的一种,Exception是“problem”的一种编程术语。此外,在我们的设置中,注释中的一个NP可以关联源代码中不属于同一个co-reference “chain” 共同引用链的多个不同的元素。由于这些问题,我们将我们的任务广泛的定义为将自然语言注释中的一个名词短语与相应代码体中的单个 code token 关联起来。

在这项工作中,我们使用Java编程语言和Javadoc注释,即 @return 注释。然而,这项任务和方法可以扩展到其他编程语言。例如,Python Docstring和c#XML文档注释也有类似的目的。

数据

我们使用Java中结构最好的注释类型,即带有 @return 的注释,它是Javadoc 文档的一部分,如图1、2。

@return 注释描述了输出,输出是由组成方法的各种语句计算的。相比之下,其他Javadoc 标签中的内容通常更加窄,非结构注释往往本质上更长和高级,这使得很难映射到代码中的元素,如补充材料。我们未来的工作将把提出的任务扩展到其他类型的评论。在此,我们提到评论时,指的是@return标签后的内容。

我们通过从Github上流行的开源项目的所有commits中提取示例来构造数据集。我们根据stars量来排序项目,使用了前1000个项目,因为它们被认为质量更高。我们提取的每个示例都包括对方法体的代码更改以及相应的@return注释的更改。

噪声监督

我们的噪声监督提取方法的核心思想是利用软件版本控制系统(如Git)的修改历史,基于先前研究表明源代码和评论共同进化。本质上,如果注释中的实体和源代码中的实体同时被edit,则它们相关联的概率会更高,即可近似为同时commit。因此,挖掘这样的共编辑让我们为这个任务获得噪声监督:我们用版本控制系统Git来隔离一起添加和删除的部分代码和注释。

  • 监督设定

    • 添加

      部分添加的代码可能和部分同时添加的注释有关联,基于这样的直觉,我们为代码tokens分配了有噪声的标签。也就是说,我们将在给定提交中添加的任何代码tokens标记为与同一提交中的注释中引入的NP相关联的代码,并将所有其他代码tokens标记为与NP无关的代码。这些正标签是有噪声的,因为一个开发者可能同时做其他的与添加的NP无关的代码更改。另一方面,负标签(未关联)具有最小的噪声,因为从以前版本中保留的代码tokens不太可能与以前版本注释中不存在的NP关联。我们从添加的数据中收集的这一组示例构成了我们的主要数据集。

    • 删除

      理论上,如果我们假设被删除的代码tokens与从注释中删除的NP相关,我们可以从每个提交中提取一个示例。然而,删除的NPs在这方面比添加的NPs要微妙得多。如上面所说,由于添加的NP在以前的版本中不存在,因此以前存在的代码tokens不太可能与之相关联。但由于被删除的NP确实存在于以前的版本中,因此我们不能完全地声称一个在代码中的不同版本之间保持不变的token与删掉的NP没有关联。这将可能会导致更多的负标签噪声,除了固有存在的正标签噪声。如图3,nextBCI被自动标记为与被删除的NP“下一个字节码”无关,即使它可以说是关联的。因此,我们将这些示例从我们的主数据集中分离出来,并形成另一组我们称为删除数据集的示例。

  • 数据处理

    我们在提交中检查代码和注释的两个版本:提交之前和提交之后。使用spaCy,我们从两个版本的注释中提取NPs,并使用javalang库,对代码的两个版本进行tokenize。使用difflib库,我们计算了两个版本的注释中的NPs之间的差异,以及两个版本的tokenized代码序列之间的差异。这些差异的每一行变化都用正负号标记,如图3所示。

    从不同的结果中,我们分别识别了之前和之后的注释和代码版本中唯一的NPs和code tokens,允许我们构建两对(NPs,相关的code tokens)。一个是删除的案例,删除部分的注释和代码只出现在先前的版本中。一个是添加的案例,添加部分的注释和代码只出现在新版本中。

    如果提取的NPs或相关code tokens列表为空,我们丢弃这对。此外,我们丢弃由多个NP组成的对,以获得不模糊的训练数据,以确定哪些code tokens应该与哪个NP相关联。因此,最后的对形式是(NP,相关的code tokens)。注意,对于关联的code tokens中的任何token,如果它不是一个通用的Java类型(例如,int,String),我们会用关联到相同文字字符串来处理code tokens中任何其他token。

    然后,我们回到前后版本的代码(不包括Java关键字、操作符和符号,参照第2节)。我们将代码序列tokenize,并将任何不存在在关联code tokens中的tokens标记为不关联。按照这个过程,每个示例都由一个NP和一个被标记的code tokens序列组成。从以前版本(以前)提取的示例将添加到删除数据集中,从新版本(之后)提取的示例被添加到主(添加)数据集中。

  • 数据过滤

    虽然大型代码基地如Github和StackOverflow提供了大量数据,为源代码和自然语言任务获取海量高质量的并行数据仍是一个挑战,由于显著的噪声和代码重复等原因。先前的工作通过使用人工标记数据训练的分类器来过滤低质量的数据来解决这个问题。但是,手动获取数据是很困难的,我们选择用启发式,如之前的工作,我们施加约束来过滤掉噪声数据,包括重复、细碎的场景,和不相干的代码与注释更改组成的示例数据。

    我们定义细碎场景为涉及到由几个都关联到这个NP的code tokens组成的单行方法的示例,还有那些用简单字符串匹配工具就可以解决关联的示例。

    此外,在手动检查了大约200个示例的样本后,我们建立了启发式来最小化不相干代码注释更改的示例数量:

    1. 那些有冗长的方法或大量代码更改,这些更改可能不都与注释相关。
    2. 与重新格式化、更正错误修复和简单改述的相关代码和注释变动的示例。
    3. 注释变化包括动词短语的示例,因为相关的代码变化可能与这些短语有关,而不是NP。

    此外,由于我们关注的是描述Java方法返回值的@return标记,因此我们消除了不包括返回类型更改或至少一个返回语句的代码更改示例。具体参数和每个启发式丢弃的示例数量见补充材料。

    应用这种启发式方法大大减少了数据集的大小。然而,我们在手动检查200个例子并观察到显著的噪声后,确定这种过滤是必要的,并发现这与上述之前的工作一致,表明了在大的代码库中,如果没有积极的过滤和预处理,就无法从中学习。

    经过过滤后,我们将主数据集划分为训练集、测试集和验证集,如表1所示。基于训练集,NP的中位数是2,四分位数范围(IQR,差异在25%和75%的百分位)1,code token的中位数25,IQR 21,相关的code token是10,IQR 13。我们报告了IQR,因为这些分布不是正态的。

  • 测试集

    测试集中的117个示例是由一位有7年Java使用经验的作者进行注释的。在试验研究中,两个注释者在集中专注于注释的标准之前,共同检查了一个用于注释的方法/注释对的样本集。用于识别相关的code tokens的标准包括:它是否由NP直接引用;它是与NP引用的实体对应的属性、类型或方法;它被设置为等于NP引用的实体;如果令牌被更改,则需要更新NP。有关注释的示例,请参见补充材料。为了评估注释的质量,我们询问了一个不是作者之一且有5年Java经验的研究生,注释286个代码标记(来自测试集中的25个示例),这些标记在嘈杂的监督下被标记为相关的。两组注释之间的Cohen’s kappa得分为0.713,表明一致性令人满意。

表示和特征

我们设计了一组特征,包括表面特征,单词表示,代码标记表示,余弦相似性、代码结构和java api。我们的模型利用通过连接这些特征而得到的1852维特征向量。

  • surface feature 表面特征

    我们合并了两个binary features,subtokens matching 和返回语句中的 presence,这些也被用于下节讨论的baseline。subtoken matching feature 表示一个候选code token完全匹配给定名词短语的组件,在给定token-level或者subtoken-level。subtokenization 是指java中常用的分裂驼峰式大小写,返回语句的presence feature表示候选code token是否出现返回语句还是完全匹配在返回语句出现的任何token。

  • 单词和代码表示

    为了在注释和代码中获得术语表示,我们预先训练了注释的character-level和word-level嵌入表示,代码的character-level、subtoken-level和token-level的嵌入表示。这些128维度的嵌入在大语料库上训练,由GitHub中提取的128,168个@return标签/Java方法对组成。训练前的任务是使用单层、单向的SEQ2SEQ模型为Java方法生成@return注释。我们使用平均嵌入来获得NP和候选代码标记的表示。此外,为了提供一个有意义的上下文,我们对完整@return注释对应的嵌入以及候选令牌出现的同一行中标记对应的嵌入取平均值。

  • 余弦相似性

    最近的工作使用代码和自然语言描述组成的对的联合向量空间,表明一个代码体和它相关描述有相似的向量。由于@return注释的内容经常在代码中提到实体,不是建立一个联合的向量空间,而是通过计算关于java代码训练的嵌入向量表示,我们将NP投影到代码的相同向量空间。然后我们计算NP和候选code token的余弦相似性,在token-level,subtoken-level,character-level。类似的计算NP和候选code tokens出现的代码行的余弦相似性。

  • 代码结构

    抽象语法树捕捉给定代码体在树形式的语法结构,由Java语法定义。使用javalang的AST解析器,我们获得了该method对应的AST。为了表示候选code token相对于该method的整体结构属性,我们提取其父节点和祖父节点的节点类型,用one-hot编码来表示它们。这在method的更广泛的内容的候选code token作用,提供了更深的了解,通过传递详细的信息,如是否在方法调用中、变量声明、循环、参数、try/catch block等等。

  • java api

    我们用one-hot编码来表示与通用Java类型和java.util包(一个实用程序类的集合,如List,我们往往会经常使用)。我们假设这些特征可以揭示这些经常出现的tokens的显示形式。为了捕捉本地文本,我们还包括了与候选code token相邻的code token的java相关特性,例如它是通用的Java类型还是Java关键字之一。

模型

我们开发了代表不同的方式来解决我们提出的任务的两种模型:二进制分类和序列标记。我们还制定了多个基于规则的baseline。

  • 二分类器

    给定一个code token序列和注释中的一个NP,我们独立地将每个标记分类为关联或不关联。我们的分类器是一个前馈神经网络,有4个全连接层和最后一个最终输出层。作为输入,网络接受一个与候选code token对应的特征向量(在前一节中讨论),并且模型输出对该token的二值预测。在实验中,我们还用了逻辑回归模型作为分类器,但它的表现不如神经网络。

  • 序列标记

    给定一个code token序列和注释中的一个NP,我们对代码序列的tokens是否与NP相关联进行一起分类。以这种方式构建问题背后的直觉是,对给定代码标记的分类通常依赖于对附近标记的分类。例如,在图3c中,表示next()函数的返回类型的int的token与指定的NP没有关联,而与opcode相邻的int的token被认为是关联的,因为opcode是关联的,而int是它的类型。

    为了重新建立原始序列的连续顺序,我们将已删除的Java关键字和符号注入回序列中,并引入第三个类作为这些插入标记的黄金标签。具体来说,我们预测了这三个标签:关联的、非关联的和一个伪标签Java。请注意,我们在评估过程中忽略了这些令牌的分类,也就是说,如果在测试时预测了任何code token标记为伪标签,我们会自动将其分配为不关联(平均而言,这种情况的约为1%)。我们构建一个CRF模型,通过在前馈神经网络的前面加上神经CRF层,类似于二元分类器的结构,让网络接受一个由method中的所有tokens的特征向量组成的矩阵。在实验中,我们还用了非神经网络的CRF,采用sklearn-crfsuite,但它的表现不如神经网络。

  • 模型参数

    四个全连接层有512,384,256,126个units,每个被dropout的概率是0.2。如果在5个连续epochs(10epochs之后),F1 score没有改进,我们就终止训练。我们使用最高F1 score的模型。我们用tensorflow实现了这两种模型。

  • baselines

    • Random

      基于均匀分布的code token的随机分类。

    • weighted random

      根据从训练集中观察到的相关类和非相关类的概率分别为42.8%和57.2%,对代码标记进行随机分类。

    • subtoken matching

      将subtoken matching表面特征(在上一节中介绍)设置为true的任何token都被分类为关联,而所有其他token都被分类为不关联。请注意,永远不会有这种情况出现,所有相关的code token在token-level或subtoken-level与NP匹配。在过滤过程中,我们从数据集中删除了这些琐碎的例子,因为它们可以用简单的字符串匹配工具来解决,而不是本工作的重点。

    • presence in return statement

      将返回语句表面特征(在上一节中讨论)设置为true的任何token都被分类为关联,所有其他token都被分类为未关联。

结果

采用micro-level precision,recall,F1 metrics评估模型。在token-level上,测试集中有3592NP-code token pairs。所有报告的分数都是三次运行取平均。下面讨论在主训练集上训练的结果,将删除数据集纳入训练的结果,二值分类器和CRF模型使用的特征的消融研究结果。

  • 主训练集上训练结果

    三个baselines和我们的模型的结果如表2所示。我们的分析主要基于注释测试集上的结果,为了完整性,我们展示了来自未注释集的结果。相对于未注释集的分数,模型通过注释集获得较低的精度分数和较高的召回分数。这是意料之中的,因为在注释过程中,关联有黄金标签的令牌数量减少了。

    我们的两个模型的表现都大大优于baseline。从二进制分类器中获得的样本输出见补充材料。虽然CRF的召回率得分略高于二值分类器,但很明显,二值分类器总体上优于F1得分。这可能是由于CRF需要额外的参数来建模依赖关系,这可能无法准确设置,因为我们的实验设置中示例级数据数量有限。此外,虽然我们期望CRF比二值分类器更对上下文敏感,但我们确实将二值分类器结合了许多上下文特征(周围和邻近标记的嵌入、上下文与NP的相似性以及相邻标记的Java API知识)。我们发现有错误分析,CRF模型倾向于在Java关键字之后的令牌以及在稍后出现的方法中的令牌上犯错误。这表明CRF模型可能难以对更长范围的依赖关系和更长范围的序列进行推理。此外,与二进制分类设置相比,Java关键字出现在序列标记设置,因此CRF模型必须比二进制分类器推理更多的code tokens。

  • 使用删除功能来增强训练

    我们通过从删除数据集中分阶段添加数据来增加训练集。在这些新的补充数据集上训练二值分类器和CRF的结果如表3所示。对于二值分类器,添加500和867个被删除的示例似乎对F1有显著的提升,而对于CRF模型,添加任何数量的被删除的示例都会提高性能。这表明,我们的模型可以从我们认为比我们收集的主要训练集更有噪声的数据中学习。由于我们能够在添加的情况以及对应的删除的情况下找到价值,我们能够大大增加可以收集来训练执行我们提议的任务的模型的数据量的上限。考虑到为这项任务获得大量高质量的数据是多么困难,这尤其令人鼓舞。尽管我们从超过1,000个项目的所有提交的源代码文件中的方法中提取了示例,在过滤噪声后,我们只从添加的案例中获得了总共970个例子。通过包括已删除案例中的867个例子,我们将这个数字增加到1837个。虽然这仍然是一个相对较小的数字,但我们预计随着任务范围扩展到本文中关注的@return之外的其他评论,潜在的规模将大幅增加。

  • 消融研究

    我们对在主数据集上训练的二元分类器进行了消融研究,以分析我们所引入的特征的影响。我们消除了余弦相似性,嵌入,以及和java相关的特性。嵌入特性包括代码嵌入(即对应于候选代码标记的嵌入和方法行中的标记)和注释嵌入(即对应于NP和@retern注释的嵌入)。6根据表4所示的结果,相对于f1度量,所有这些特征都对完整模型的性能做出了积极的贡献。

相关工作

之前的工作研究了一项任务,包括将对话系统中的名词短语接地到编程环境中(Li和Boyer 2015;Li和Boyer2016)。名词短语是从学生和导师之间的互动中提取出来的,而编程环境承载着学生的代码。他们的工作性质类似于共引用解析,因为目标是识别编程环境中被给定名词短语引用的实体。在对话中,学生和导师讨论与代码中特定实体相关的实现细节,这使得共同引用解析成为框架任务的适当方式。相比之下,他们的工作主题——伴随源代码的注释——通常描述高级功能,而不是实现细节。由于代码中的多个组件相互作用以组成功能,因此代码中可能存在由注释中的给定元素直接或间接引用的实体。由于它们的数据和实现无法公开获得,因此我们无法对这些任务和方法进行任何进一步的比较。

Fluri、W¨ursch和Gall(2007)研究了一个变体任务,即基于距离度量和其他简单启发式方法将单个源代码组件(如类、方法、语句)映射到行或块注释。相比之下,我们的方法在更细粒度的级别上处理代码和注释——我们在令牌级别上对代码进行建模,并在注释中考虑NPs。此外,在我们的框架下,多个代码标记可以映射到同一个NP,并且这些映射是从从更改中提取的数据中学习到的。

Liu等人(2018)介绍了一项任务,该任务涉及将单个提交消息中包含的不同更改意图链接到在提交中发生更改的软件项目中的源代码文件。虽然这需要将自然语言消息中的组件与源代码关联起来,就像我们提出的任务一样,但我们感兴趣的关联占据了更高的粒度。也就是说,我们关注评论中的NPs,而他们的工作是关注提交消息中的句子和子句,在我们的例子中,分类单位是每个单独的代码标记,而在他们的工作中是每个文件。此外,在它们的工作中构建的数据集提取已经更改并提交消息的源代码文件,这些文件是为每个提交新编写的。相反,对于我们的任务,我们收集了包含源代码和注释中的更改的例子,这些源代码和注释是共同发展的。

我们从Git版本控制系统中提取示例的过程与Faruqui等人(2018)基于维基百科的编辑历史建立一个维基百科编辑语料库的方法类似。他们从维基百科文章中的句子中连续文本的插入中提取样本,这些例子有望展示自然语言文本通常是如何被编辑的。相比之下,我们不仅仅限制插入或要求编辑是连续的。此外,我们努力收集一些演示两种模式如何一起编辑的例子。

总结

在本文中,我们制定了将Javadoc注释中的实体与Java源代码中的元素关联起来的任务。我们提出了一种新的方法来获得有噪声的监督,并提出了一组丰富的特性,旨在捕获代码、注释和它们之间的关系等方面。基于在一个手动标记的测试集上进行的评估,我们表明,在这样的噪声数据上训练的两个不同的模型可以显著优于多个基线。此外,我们通过展示增加有噪声训练数据的大小如何提高性能,证明了从有噪声数据中学习的潜力。我们还通过消融研究强调了我们的特征集的价值。

Code and datasets

env:py2.7,tnsorflow-gpu=1.15.0
待改进:

  • 数据集太小
  • 模型太简单,baseline太弱

论文阅读 - Deep Reinforcement Learning for Programming Language Correction

新手编程者经常饱受程序语言正规语法的折磨,为了协助他们,作者设计了一个基于强化学习的新型程序语言纠正框架,这个框架允许一个智能体 agent 模仿人类行为进行文本导航和编辑,展示了一个 agent 可以通过直接从原始的输入自我探索来进行训练,自我探索就是说,程序文本它自己,不利用程序语言正规语法的任何经验。我们充分利用专家案例作为 1/10 的训练数据,来加速训练。

程序语言规定程序文本的语法规则检验。一个不遵守规则的程序文本不会被编译执行,这给新手编程者带来了障碍。在目前大量的线上编程课程中,从指导者获得个性化反馈是十分不可实行的。因此,作者的工作旨在利用技术帮助新手编程者,通过自动化修正程序中的通用语法错误。

作者通过强化学习提出这个问题。当面对一个错误,一个程序员根据程序文本找到错误的位置,然后修正编辑来修复错误。作者提出了一个新型程序语言修正框架,在这里,一个 agent 可以模仿这些行为。一个 agent 可以访问和修改一个程序文本,对它来说,检查程序文本语法有效性的编译器是一个黑盒子。编译器通常不会精确地指明错误的位置。所以,不能依赖编译器来找到错误的位置和进行修正。作者利用编译器生成的错误信息的数量设计了一个 reward function。agent 的目标是将程序成功编译所必要的编辑行为表现的最好。

框架的挑战是为程序文本的 agent 学习一个 control policy,不通过任何带有程序语言正规语法知识的 agent 。通过深度学习,agents 可以被训练到专家水平来玩视觉文字游戏,有趣的是,这些技术直接从原始输入,如像素、文本等。在这项工作中,第一次展示了它在程序空间中的可能性。

如图1,修复了税收算法,有两个语法错误:第四行的 scanf 使用错误, 第12行的 “}” 缺失。程序以被标记化的形式展现给 agent,agent 的指针位置被初始化为程序的第一个 token。在程序中 agent 的导向性的动作用箭头表示,系列动作如图所示。agent 准确定位和修复所有错误。首先,agent 导向错误的位置行4,用逗号替代不正确的分号,在错误2中插入丢失的 “}” 。这些编辑操作完成后,程序成功编译,agent 停止。这比蛮力列举编译修正高效很多。

通过长短期记忆网络 LSTM 网络进行编码,agent 被允许执行一系列的导航和编辑行为来修复程序。每一步修复错误的编辑都获得一些小的奖励,最大化达到目标状态的奖励,即程序的无错误状态。agent 的 control policy 就是学习使用 A3C 算法。

训练一个agent的难点有两个:(1)agent 要同时定位错误并在此做出精确的编辑来修复程序。错误的编辑会导致引入更多的错误,使得任务更加困难。为了克服这个问题,我们设定环境来拒绝这样的编辑。这显著的削减了 state space。(2)随着时间增长,任务的 state space 越来越大,state 探索收集的信息越来越多,的强化学习趋向越来越慢。一个方法是利用专家表示来引导 agent。在我们的工作中,这些表示是自动生成的而不是人类干预,我们将此称为 RLAssist。

DeepFix 是目前修复程序错误中表现最好的工具。作者在需要修复的 C 程序上对比该工具和 RLAssist 。作者证明了RLAssist可以通过只使用错误程序自我探索来训练,同时仍然可以达到 DeepFix 的性能。通过专家演示加速训练,为 10% 训练集生成专家演示,90% 的数据集没有演示。RLAssist完全修复了测试集中26.6%的程序,并解决了39.7%的错误消息。与DeepFix相比,这两个版本分别提高了14%和29%。因此,RLAssist在一小部分训练数据上使用专家演示,表现优于DeepFix。

此工作的主要贡献如下:

  1. 设计了一种新颖的程序设计语言校正框架,可用于强化学习。
  2. 我们使用 A3C 来纠正编程语言,并通过专家演示加速培训。
  3. 我们的实验表明,我们的技术只使用了十分之一的训练数据,其性能超过了最先进的工具 DeepFix 。此外,我们的技术也可以在没有任何演示的情况下工作,但仍然可以匹配 DeepFix 的性能。
  4. RLAssist 的实现将是开源的。

a framework for programming language correction tasks

当面对一个错误时,一个程序员会定位程序中错误的位置并编辑操作来修复错误。出现大量错误时,程序员会重复如上步骤,此文提出程序语言修正框架,一个 agent 可以模仿以上的行为。

states

一个 state 用一个 < string , cursor > 表示,string 表示程序文本,cursor $\in$ { 1,…,len(string) },len(string) 表示 string 中 token 的数量。env 跟踪 string 中错误的数量。这些错误可以从 ground truth 确定(当 ground truth可获取时),也可以从编辑器编译 string 时产生的错误消息来估计。对于编译器,我们使用 GNU C 编译器。

编码 state 到 a sequence of tokens。首先,将程序字符串转换成词汇序列,这些词汇是不同的类型,例如关键字、操作符、类型、函数、字面量和变量。此外,我们还保留换行符作为词汇,以允许在程序文本上进行二维导航操作。state 中 cursor 部分由一个特殊的 token 表示,插入到序列中,刚好在游标持有索引的 token 之后。

接下来,在所有程序中构建一个共享词汇表。除了一些常见的库函数(如 printf 和 scanf )外,所有其他函数和变量标识符都映射到一个特殊的 token ID。类似地,所有的字面值都根据其类型映射到特殊的 token ,例如,数字映射到 NUM ,字符串映射到 STR 。所有剩余的 tokens 都包含在词汇表中,无需任何修改。这种映射减少了 agent 看到的词汇表的大小。请注意,此编码仅在将 state 反馈给 agent 时才需要。基于这样的编码,agent 预测到的 actions,在原始程序 string 上被 env 执行。

actions and transitions

agent 的动作可以分为两类,一类是更新 cursor,一类是修改 string。我们将第一类称为导航操作,第二类为编辑操作。导航操作允许 agent 在 string 中导航。这些操作只改变一个 state 的游标,而不是 string 。另一方面,编辑操作用于纠错,只修改 string 而不修改游标。错误的编辑操作会在 string 中引入更多错误,而不是修复它们。我们将环境配置为拒绝所有此类编辑,以删除使修复程序变得更加困难的状态空间。此外,拒绝错误的编辑可以防止对程序的任意更改。

对于我们的任务,我们只允许两个导航操作,右移和下移。它们分别将光标设置为右侧的下一个 token 或下一行的第一个 token 。如果光标已经设置为一行的最后一个 token ,则右移动作没有效果;或者光标是最后一行的任何 token 时,向下移动没有效果。注意,向下移动操作是可能的,因为我们在状态编码中保留了换行符。

基于对程序员新手常见的排版错误的研究,我们设计了三种类型的编辑操作。第一个参数化插入 token 操作,插入参数 token 到光标位置之前。参数可以是一个修复的 token 集合中的任何 token ,称之为可变 tokens。第二个是删除操作,删除光标上的token,只有在来自可变 tokens 时才能删除。我们限制可变 tokens 为以下五个类型的 token:分号、括号、大括号、句号和逗号。第三个是是参数化的将 token1 替代为 token2 操作,将光标位置上的 token1 替换为 token2。在这个类中有四个操作:(1)“;”替换为“,”,(2)“,”替换为“;”,(3)“.”替换为“;”,(4)“;)”替换为“);”。尽管可以用一系列的删除和插入操作替换原子替换操作,但使用它们可以防止组成的删除和/或插入操作被环境拒绝的情况。

episode,termination,and rewards

  1. 一个 episode 的

    • 开始

      string:一个错误的程序文本

      cursor:string 的第一个 token

    • 结束

      到达 goal state :编辑后的程序被编译器成功编译

  2. termination 终止

    • 在一个 episode 中,agent 被允许到达最大数目的 time steps,即 max_episode_len 时终止

    • 在一个 episode 中,agent 只允许通过整个程序一次,即 agent 一旦经过程序的最后一个 token,这个 episode 就终止了

  3. reward 奖励

    在每一步,agent 会受到一个小的步长惩罚,一个较高的编辑惩罚

    • step_penalty 步长惩罚:鼓励 agent 学会在最小步长中来修复程序
    • edit_penalty 编辑惩罚:编辑操作开销较大,需要调用编译器来验证,所以不鼓励 agent 做不必要的编辑
    • maximum_reward 最大奖励:agent 达到 goal state
    • intermediate_reward 中间奖励:纠正至少一个错误的编辑操作

model

首先,使用 LSTM 网络将 state 的 token 嵌入到实向量中, 最终的 state 是输出向量的各个元素均值。考虑到 state 的嵌入,作者采用两个独立的全连接线性层,来生成策略函数 $\pi$(a|s; $\theta$) 和值函数 V(s;w) 。更新网络参数前,计算累积梯度:

$H$ 是熵,$\beta$ 是它的正则化项超参数。

expert demonstrations

RLAssist可以不使用任何演示进行训练,但会耗费更长的训练时间。原因是在A3C算法,每一个 episode 中,一个 agent 从一个随机 state 开始,然后使用它的 policy function 与环境进行交互。由于 policy network 是随机初始化的,因此在训练开始时,这种交互随机进行探索。在只进行随机探索的情况下,agent 会发现很难到达目标状态,也很难获得奖励。因此,训练速度减慢。

作者使用专家演示来加速训练。专家演示是指向事件目标状态的一系列动作。给定一对 (p, p’) ,其中 p 是一个不正确的程序, p’ 是它的正确版本,将自动生成如下的演示。从 p’ 中的第一个 token 开始,每个未修改的行 (w.r.t. p) 都会通过一个向下移动操作跳过。在第一个错误行,光标通过向右移动操作向右移动,直到到达错误位置并生成适当的编辑操作。这个过程一直重复,直到程序中的最后一个错误被解决。我们将 agent 设定为使用以下专家演示。对于可以进行演示的 episodes ,agent 遵循所提供的预定动作序列,而不是由 policy 驱动的 sampling 。对 policy network 参数的更新就像预先确定的动作 sampled 一样。对于其他 episode ,代理将按照标准的 A3C 算法,使用 policy 来 sample 的动作。请注意,演示是在 episode 级别提供的,而不是在更细粒度的转换级别上提供的。因为 agent 需要在整个事件中采取正确的行动来达到目标状态并获得奖励。如果它采取断断续续的指导,那么它仍然无法达到目标状态。

experiments

https://bitbucket.org/iiscseal/rlassist/src/master/



论文阅读 - DeepFix Fixing Common C Language Errors by Deep Learning

program repair ≈ grammar correction in nlp 作者提出了一个端到端的,带 attention 的多层 seq2seq neural network,包括 RNN 编码器,和带 attention 的 RNN 解码器。该网络可以预测程序出错的位置并附上正确的修复。对比其他修复特定编程任务的工作,DeepFix 可以用在任何未预见的任务上。不易解决的错误需要考虑到程序文本中的长期依赖。DeepFix 通过带注意力机制的 seq2seq 模型来捕捉长期依赖。它的优势在于:(1) 利用端对端的基于深度学习网络解决普遍的编程问题;(2) 可以迭代地解决一个程序中的多个错误;(3) 在上千 C 程序评估中得到很好的结果。

Program Representation

程序文本由不同类型的标记组成,如类型、关键字、特殊字符(如分号)、函数、文字和变量。其中,类型、关键字、特殊字符和库函数构成了跨不同程序的共享词汇表。作者在表示程序时保留它们,对其他类型的 token 进行如下建模。首先定义一个固定大小的名称池,然后为每个程序构造一个单独的编码映射 encoding map ,方法是将程序中每个不同的标识符(变量名或函数名)随机映射到池中唯一的名称,并选择一个足够大的池来为数据集中的任何程序创建上述映射。这种转换不会改变程序的语义,且是可逆的。字面量的确切值对学习任务无关紧要,因此,根据字面值的类型将其映射为特殊的 token ,例如,将所有整数字面值映射为 NUM ,将所有字符串字面值映射为 STR 。用 < eos > 表示标记序列的结束。

由于一个程序用 seq 的 tokens 表示时,要产生类似长度的准确修复后的 seq 非常困难,且一个程序通常包含上百个 token。所以作者把代码行号也进行标记,将一个 K 行的程序 P 表示为 $(l_1 , s_1 ),…,( l_k ,s_k)< eos >$,l 和 s 分别是对行号和语句的标记。一个 fix 包括 $l_i , s_i$ ,这比用全部的 token 作为输出简单得多。

Neural Network Architecture

基于 Neural Machine Translation by Jointly Learning to Align and Translate ,NLP 中 encoder-decoder 中第一个使用 attention 机制的工作,将 attention 机制用到了神经网络机器翻译(NMT)。https://arxiv.org/pdf/1409.0473.pdf

这里编码器和解码器 rnn 都由 N 个堆叠的门控循环单元(GRUs)组成。编码器将输入序列中的每个 token 映射到一个称为 annotation 的实向量。对于输入序列$x_1,…,x_{T_x}$, t 时刻的隐藏单元激活计算如下:

$h_t^{(1)} = GRU(h_{t-1}^{(1)},x_t)$

$h_t^{(n)} = GRU(h_{t-1}^{(n)},h_t^{(n-1)}),\forall n \in { 2,…,N }$

解码器网络的隐藏状态被初始化为用编码器网络的最终状态,然后更新:

$d_t^{(n)} = GRU(d_{t-1}^{(n)},d_t^{(n-1)}),\forall n \in { 2,…,N }$

$d_t^{(1)} = GRU(d_{t-1}^{(1)},z_t)$

其中 $z_t$ 是输出 $\hat{y}_{t-1}$在 $t-1$ 和上下文向量 $c_t$ 的连接,定义如下:

$c_t = \sum_{j=1}^{T_x} a_{tj}h_j^{(N)}$

$a_{tj} = \frac{exp(e_{tj})}{\sum_{k=1}^{T_x}exp(e_{tk})}$

$e_{tk} = \Phi(d_{t-1},h_k^{(N)})$

c 就是全部隐状态的一个加权和,用到归一化权重 a 。a 就是一个对齐模型,用来评估当前预测词,与输入词每一个词的相关度。将上一个输出序列隐状态 $d_{t-1}$ 和输入序列隐状态 $h$ 输入网络,然后做 softmax 归一化,计算出权重。

Iterative Repair

DeepFix 使用简单而有效的迭代策略来修复程序中的多个错误。oracle 的工作是通过检查更新后的程序是否比输入的程序好来决定是否接受修复。如果更新后的程序不会比输入程序产生更多的错误消息,则使用编译器并接受修复。我们还使用一些启发式方法来防止对输入程序的任意更改。例如,如果 oracle 不保留原始语句 $s_i$ 中的标识符和关键字,则它拒绝 fix $s_i$。一旦修复程序被接受,DeepFix 将再次向网络显示更新后的程序。

这种迭代策略停止的条件:(1) oracle 确定更新程序没有任何错误;或 (2) 网络视输入程序是正确的,发出一种特殊 token “fix”;或 (3) oracle拒绝修复,或 (4)达到预定的迭代的数量上限。

除了决定是否替换语句,网络还会决定新的一行是否要插入在行前或行后,用 $l_i^-,l_i^+$ 代替 $l_i$,如果要删除行,则将 $l_i$ 带上空字符 $\epsilon$。oracle 使用程序特殊的编码映射,将修复好的 token sequence 重构回原来的标识符。它使用修复中的行号,并用输入程序中相应行中的字面值替换诸如 NUM 和 STR 等特殊标记。如果 oracle 不能重建程序文本,那么它拒绝修复。

作者提出的修复策略有几个优点:(1) 程序完整地呈现在网络上。识别和修复编程错误通常需要能够推断长期依赖关系的全局分析。网络架构能够有选择地参与程序的任何部分,可以推理结构和语法约束,以预测错误的位置和需要的修复。(2) 在输入和输出中都包含行号,降低了粒度,从而降低了预测任务的复杂性。(3) DeepFix可以迭代地修复程序中的多个错误。(4) oracle用于跟踪进度,防止无用的或任意的更改。(5) DeepFix的修复策略比较泛化。例如,如果我们试图修复逻辑错误,我们可以使用测试引擎和测试套件作为oracle。如果修复程序通过了更多的测试,那么它将被接受。

Experiments

https://bitbucket.org/iiscseal/deepfix

数据集:两类程序,一种是编译的程序(正确的程序),另一种是不编译的程序(错误的程序)。一个学生可能会提交几个错误的程序,作者在每个学生的每个编程任务中随机选择一个错误的程序,以避免测试结果的偏差。采用五折交叉验证