论文阅读 - 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个示例的样本后,我们建立了启发式来最小化不相干代码注释更改的示例数量:
- 那些有冗长的方法或大量代码更改,这些更改可能不都与注释相关。
- 与重新格式化、更正错误修复和简单改述的相关代码和注释变动的示例。
- 注释变化包括动词短语的示例,因为相关的代码变化可能与这些短语有关,而不是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太弱
论文阅读 - Associating Natural Language Comment and Source Code Entities