基于关键词的代码自动摘要

2020-09-24 08:41张世琨
计算机研究与发展 2020年9期
关键词:解码器代码语义

张世琨 谢 睿,2 叶 蔚 陈 龙,2

1(北京大学软件工程国家工程研究中心 北京 100871) 2(北京大学软件与微电子学院 北京 100871)ruixie@pku.edu.cn)

代码摘要(code summary)是对一段源代码简短的自然语言描述[1],高质量的代码摘要能够有效帮助代码的理解和软件的维护工作.而代码自动摘要(code summarization)技术通过自动化地生成代码摘要来辅助程序理解和软件维护,可以有效地降低软件的开发维护成本[2].因此,自动化地生成高质量代码摘要一直是软件工程研究的重要任务之一[3-4].

据统计,在软件开发生命周期中,将近59%的时间是用于程序理解及其相关活动的[5].然而由于人工编写代码摘要过程乏味、耗时较长,代码摘要的编写工作在实际软件开发过程中往往不受重视,在大部分软件项目中,代码摘要常常出现不匹配、过时甚至丢失的问题.因此,代码自动摘要工具的引入,可以将软件开发人员从乏味的代码摘要编写工作中解脱出来,极大地提高软件开发效率、降低软件开发成本.

代码自动摘要在软件开发生命周期的很多过程中都具有潜在的应用价值,例如:

1) 代码理解.当软件开发人员加入现有项目或者了解已有开源代码库时,代码摘要可以帮助开发人员快速熟悉该代码库的核心部分.

2) 代码审查.审查人员在审查具体代码时,代码摘要可以帮助审查人员快速理解修改代码的主要功能,使代码审查人员更快地找到本次修改的关键部分.

3) 代码定位.在程序维护期间,开发人员在搜索感兴趣的代码区域时,通常会跳跃式地阅读代码,一次只读取几行代码[6],此时,代码摘要可以帮助快速理解局部代码的主要功能,快速定位其需要的代码.

早期的代码自动摘要主要使用信息检索(infor-mation retrieval)的方法来生成摘要.文献[6-7]通过搜索相似的代码来生成摘要;文献[8]则通过从给定代码的符号序列中抽取关键字的方式来生成摘要.虽然这些方法取得了一定的效果,但是它们过于依赖命名的规范性,并且,如果代码库中没有和给定代码相似的代码,这些方法将无法工作.

随着深度学习的兴起,越来越多的学者利用神经网络对代码的符号序列建模.例如,Allamanis等人[9]利用卷积神经网络和注意力机制从代码符号中捕获信息,预测函数的变量名;Iyer等人[10]提出了CodeNN模型,使用长短时记忆网络对代码和摘要进行建模,实现了代码自动摘要和代码搜索;Loyola等人[11]则利用最先进的机器翻译模型——带有注意力机制的序列-序列模型,自动地生成代码提交评论(git commit message).

在自然语言处理中,机器翻译和文本摘要是2个和代码自动摘要相似的任务,它们都能看成是序列到序列(sequence to sequence, Seq2Seq)的生成任务.代码自动摘要的输入是代码,输出是自然语言,输入输出不在一个空间里面,从这个角度讲,代码自动摘要像机器翻译.但机器翻译中输入、输出的序列几乎是一一对应的,而代码摘要通常是源代码的高度概括,代码摘要的长度通常也远小于代码长度,从这一特点来说,代码摘要与文本摘要更加接近.因此,代码自动摘要的难点不仅仅在于如何利用程序语言和自然语言之间的差异性,更好地捕获代码中的语义,还在于如何更好地筛选信息,更好地找到代码关键信息进行归纳总结,因而代码自动摘要是一项非常具有挑战性的任务.

除此之外,和自然语言处理领域的机器翻译和文本摘要任务相比,代码自动摘要任务还具有2个特点:

1) 代码的结构化特性

自然语言结构化特性相对较弱,相比之下,代码不仅具有结构性,而且其结构是明确的、没有二义性.并且,和自然语言的语法解析树相比,代码的抽象语法树层次更深、结构更复杂[12].代码的这种结构化特性对于代码摘要任务而言既是挑战也是机遇.现有的基于深度学习的代码自动摘要技术通常简单地把代码看成符号的序列并使用简单的神经网络模型进行处理,显然,这种方式无法捕获代码复杂的结构信息,而这些复杂的结构信息里包含了丰富的代码语义.因此,在大代码(big code)时代,如何对代码建模才能更加高效地捕获代码的语义信息已经成为软件工程领域的研究热点.

2) 代码符号词汇表的无限性

源代码主要由关键字、标识符、操作符、分隔符、字面常量组成,其中,标识符占代码符号的绝大多数[13-14].而编程人员可以任意为函数、变量、类等标识符进行命名,即使遵循一定的命名规范,理论上代码符号的可能性也是无穷的.代码符号词汇表的无限性给代码建模带来了巨大的挑战[15].相比之下,自然语言中词汇的产生需要经过较长时间的演化,其词汇表大小是有限的.通常情况下,3万左右的词汇表即可满足自然语言处理相关任务的需要.而面对词频较低的未登录词,自然语言处理领域通常使用UNK符号进行替换,替换后的语句与原语句相比并不会存在较大的语义偏差.然而,在代码自动摘要任务中,由于低频词数量过多,简单地使用UNK符号进行替换低频词将造成代码中的关键信息丢失.例如,在本文实验使用的Java代码摘要数据集[16]中,不同的代码符号(区分大小写)总数约为19.2万个,其数量远远超过常用的自然语言词汇数量.代码符号词汇表的无限性给我们带来了另外一个巨大的挑战:如果采用较大的词汇表,模型训练的难度和时间会大大增加,且大量低频词并不能得到充分训练,导致最终模型效果较差;如果采用较小的词汇表,则很多符号都会被替换成UNK,替换后的代码将丢失大量关键信息,显然,在此基础上,模型不可能学习到很好的代码语义表示.

本文在第2节会深入探讨如何应对“代码符号词汇表的无限性”这个挑战.在总结已有解决方案的基础上,我们提出了自己的解决方案——符号部分拆分算法.需要说明的是,如何对代码的结构建模并不是本文关注的重点.但事实上,代码的语义信息同时来自于符号和结构,因而我们提出的部分拆分算法对改进代码的建模依然是有意义的.

本文的另一个关注点,也是最主要的关注点,在于如何让模型从很长的代码符号序列中筛选出真正有用的信息,从而更好地生成摘要.文献[4]提出了类似的想法,他们的灵感来自编程时编辑器折叠代码的功能;而我们的灵感则来自于人类写代码摘要时的习惯——当我们针对某个函数编写代码摘要时,我们会有意识地寻找代码中的关键信息,进而辅助我们写出更高质量的摘要.

图1给出了短文本摘要任务常用的数据集Gigaword[17]中的一个例子.在这个例子中,原文中所有加下划线的词完全覆盖了摘要里的信息,也就是说,我们只需要关注原文中的部分关键信息,就能写出其对应的高质量摘要结果.从这个例子可以看出原文中有大量的词对最终生成的摘要贡献很小.可想而知,如果我们事先能知道这些是关键词,那么写摘要的时候我们就能更专注于这些主要信息;相反,如果我们把注意力平均分散到每个词,则可能会写出较差的摘要.

Fig. 1 An example from Gigaword dataset图1 Gigaword数据集中的一个例子

类似地,图2给出了Java代码摘要数据集[18]中的一个例子.

Fig. 2 An example from Java method documentation dataset图2 Java代码摘要数据集中的一个例子

从技术的角度讲,在使用经典的序列到序列模型生成文本摘要的实验中,我们发现当使用带有注意力机制的解码器解码时,其注意力的权重并不是特别集中,相当一部分注意力权重之和会分散到次要信息上去.这个现象随着输入序列长度的增加而变得更明显.

这个问题在代码自动摘要任务中则会更加严重.据统计,Gigaword中平均文本长度为31.4,平均摘要长度为8.3(1)http://nlpprogress.com/english/summarization.html;而在Java函数文档数据集[18]的训练集中代码的平均长度为99.94,而摘要的平均长度为17.72.可见,在代码自动摘要任务中,代码符号中包含的次要信息的比例更高.图3展示了代码自动摘要实验中生成摘要的BLEU-4分数和代码序列长度的关系[16].显然,随着代码长度的增加,生成的代码摘要的评分在降低,该现象表明代码序列长度的增加确实会限制模型的最终效果.

Fig. 3 BLEU-4 score of generated summary for different code lengths[16]图3 BLEU-4分数和代码序列长度的关系[16]

针对该现象,很多学者提出了应对的方案,如局部注意力机制(local attention)[19]、选择门机制(selec-tive gate)[20]和多层自注意力机制(stacking self-attention)[21].在已有研究的基础上,本文结合代码本身的特性,提出了一种基于关键词的代码自动摘要方法(keyword-based source code summarization, KBCoS).针对代码,本文将函数签名(method sig-nature)和API(application programming interface)视为关键词,目的是使得解码器注意力的分布更加集中.我们将在本文第3节详细介绍这个模型,并在第4节、第5节中通过实验验证、分析此模型的效果.

为什么要把函数签名和API调用作为关键词呢?文献[22]中研究者跟踪了若干程序员在阅读函数并写注释的过程中视线聚焦点的移动轨迹,通过实验发现,程序员对函数签名的关注度高于对函数调用的关注度高于对控制流的关注度.这一研究不仅表明了函数签名的重要性,也表明了API调用的重要性——API调用本身也是函数调用.此外,API调用的序列存在一定的模式[23],其包含的内在规律可以辅助其他任务,如代码搜索[16]和代码自动摘要[18].综上,我们认为函数签名和API调用是函数中的关键信息,因而把它们作为关键词序列.图2中的例子也佐证了我们的判断:加下划线部分基本上都在函数签名或API调用里面.

我们相信,利用基于关键词的注意力机制改进模型解码时的注意力分布,这一方法和改进代码的编码器是正交的——我们的方法也能提升使用了其他复杂编码器的模型的性能.

本文的主要贡献包括:

1) 针对“代码符号的词汇表大小没有上限”这一问题,提出了符号部分拆分的算法.该算法简单有效,能很好地平衡代码符号序列长度和未登录词数目之间的矛盾.

2) 为了让模型在生成摘要时更集中地关注代码中的关键信息,提出了KBCoS模型,将函数签名和API调用视为关键词,并利用关键词序列来优化解码器注意力机制中的权重分布.实验评测结果和注意力权重的热力图都表明了我们模型的有效性.

3) 与现有模型TL-CodeSum相结合,KBCoS在Java代码摘要数据集上取得当前最优的结果.

1 问题定义

代码自动摘要(code summarization)是典型的序列到序列的生成式任务:若C={C(i)}表示源代码,D={D(i)}表示源代码对应的摘要,则代码自动摘要任务是让机器自动从源代码生成摘要C→D.

在相关文献中,Code Summarization一词的使用比较混乱,可能被用来表示3种相似又略有差异的任务:

1) 函数名预测.即给定一个函数,隐去函数名,要求根据函数中其他的信息预测函数的名称.Allamanis等人[12]提出了代码的自然性假说,并指出代码的似然性与这样一个事实有着很强的联系,即开发人员更喜欢编写[24]和阅读[25]符合规范的、符合习惯的和熟悉的代码,良好的代码风格有助于软件系统的理解和维护.而接近自然语言的命名方式,能让人更加容易理解名字背后表达的含义,是一种良好的命名规范.一个良好的命名通常能拆成若干个单词,组成一个单词序列,该单词序列即为函数名预测的最终目标[9,26-27],因此,函数名预测也能看成是一种特殊的代码自动摘要任务,如文献[9]提供的Java函数名预测数据集中,函数名对应的单词序列长度大约在3左右,因而也被称为是Extreme Summarization[9].

2) 文档注释自动化生成.该任务通常选取函数文档中的第一句话作为生成目标,如Python函数的docstring[28]或Java函数的Javadoc[16,18].这也是本文所关注的任务,该任务又被称为Code Docu-mentation[26].和任务1)相比,文档注释长度较长,在本文使用的Java自动摘要数据集中[18],摘要的平均长度是17.72,是函数名长度的6倍以上.

3) 代码功能描述自动化生成[26].该任务主要针对StackOverflow网站上的提问和被采纳的答案中的代码片段,如文献[10]中的Python和SQL这2个数据集.该任务和2)类似,任务2)中的代码主要针对函数,而任务3)中的代码可能只是一个包含几行代码的代码片段.

本文主要针对任务2)进行了实验与研究,但对于其他任务,本文提出的模型经过简单的调整同样也能够胜任,本文将在未来的工作中对其他任务进行实验与验证.

2 符号部分拆分

如何处理未登录词一直是自然语言处理和程序语言理解领域所关注的主要问题.在自然语言处理领域,通常通过在字符层面对文本建模来降低未登录词的影响,如文献[29]提出了用字符层面的卷积神经网络(character-level convolutional network)对文本进行分类.在代码领域,由于代码符号词汇表的无限性特点,未登录词对模型的影响远大于自然语言处理的相关任务,因此,很多研究者提出了自己的解决方案:

1) 文献[16]提出用未登录词对应的抽象语法树结点的类型代替UNK,针对的任务是代码自动摘要.

2) 文献[30]设计了一种复制(copy)机制,即在解码时让模型额外预测是选择当前解码器输出的词,还是复制输入符号序列中的符号,针对的任务是程序修复.

3)文献[31]则首次提出利用图结构处理未登录词,借鉴了文献[32]中提出的方法,在抽象语法树的基础上添加一些关于变量使用、符号组合的边,将树结构转为了图结构,并利用GGNN(gated graph neural network)[33]在图结构中传递信息.针对的任务是变量名预测和变量填空[32].

上述方法利用了代码的结构信息来学习未登录词的语义表示,忽略了未登录词本身所包含的语义信息.例如,对于登录词addListenerToList,可以拆分成多个子符号(sub-token):add,listener,to,list.拆分后的子符号都属于高频词汇,且子符号序列显然可以充分地表示未登录词addListenerToLis的语义信息,这种简单的方法可能比复杂的模型更加简洁有效.因此,很多学者都采用了这种简单有效的处理方式.同时,在将代码符号全部拆成子符号之后,不同文献有着不同的处理方式:

1) 将拆分后的结果简单地看成子符号序列或词袋[20].

2) 利用指针网络(pointer network)[34]实现复制机制,用于变量名预测[9,35]或生成代码文档[35].

3) 利用字符层级的卷积神经网络[29]结合子符号的向量表示得到原符号的向量表示[31],针对的任务是变量名预测和变量填空.

4) 对子符号的向量取均值得到原符号的向量表示[32],针对的任务是变量误用检测和变量填空.

5) 对子符号的向量求和得到原符号的向量表示[26],针对的任务主要是变量名预测.

注意,在上述处理方式中,3)~5)没有改变编码器输入的序列长度,解码器注意力机制需要关注的还是原始序列中各个符号的向量表示.而处理方式1)和2)则将输入序列完全转化为了子符号序列,我们称为全部拆分.

我们对Java代码自动摘要数据集[18]训练集中的代码也进行了子符号的全部拆分,并统计了拆分前后代码符号子符号序列的长度,结果如表1所示.

由于训练集中约90%的代码原始序列长度不超过200(详见表2),本文选取200作为代码符号长度的分界点.从表1我们可以看到,如果将所有符号全部拆分为子符号,代码符号序列的平均长度会增加41%,长度超过200的数据数目会增加5 751,增加了75%,过长的代码长度将导致模型注意力分散,降低最终生成摘要的效果.这一推测在实验中也得到了验证,当代码符号全部拆分后,基准模型的性能反而降低了,因此处理方式1)不可取.而我们的目标是生成用自然语言写的一句话,因而也不需要引入处理方式2)中的指针网络对每个变量名进行复制.而处理方式3)~5)不仅增加了模型的复杂度,而且多个子符号的向量表示融合成一个向量表示容易造成含义的混淆,实验效果也不是特别理想.

Table 1 Statistics of Code TokenSub-token Sequence Length in the Training Set

表1 训练集中代码符号子符号序列长度统计

Table 1 Statistics of Code TokenSub-token Sequence Length in the Training Set

SequenceNumber of TokensLength>200Length≤200MeanLengthOriginal76686204099.94All Splitting1341956289140.78Partial Splitting919460514110.40

为了更有效地处理未登录词,针对代码自动摘要的场景,我们提出了代码符号部分拆分的算法:只将非频繁出现的符号拆分为子符号序列.对于一个高频的可拆分符号,其本身便包含独立的丰富的语义信息,因此,本文希望模型能够学习该符号整体的向量表示.例如,对于高频词toString,其具有明确的语义信息,完全没有拆分成to,string这2个子符号的必要,简单地对所有词进行拆分只会增加模型学习的难度.

如表1所示,部分拆分之后,代码符号序列的平均长度增加了10%,长度超过200的代码增加了20%,在可以接受的范围之内.采用符号部分拆分,既可以减少未登录词,又不会特别增加代码符号序列的长度,不失为一种简单、有效的策略.

在Java编程规范中,一般都要求采用驼峰命名的方法,如在阿里巴巴的Java编程规范中要求:“类名使用UpperCamelCase风格;方法名、参数名、成员变量、局部变量都统一使用LowerCamelCase风格,必须遵从驼峰形式.”[36]在后续的实验中,我们根据驼峰命名法将代码符号的序列进行了部分拆分,并在部分拆分的过程中生成了词表.算法1详细描述了此算法:

算法1.代码符号部分拆分算法.

1) 初始化符号计数器和词表;

2) 对训练集中的每个函数:

① 将代码解析为符号序列;

② 如果一个符号可以按驼峰拆分为若干子符号,则将这些子符号添加到代码符号的序列里面,同时保留原有的符号;

③ 将添加了子符号的代码符号序列添加进符号计数器里面;

3) 根据符号计数器的计数,选择合适的阈值,将词频大于或等于阈值的符号添加进词表;

5) 对数据集里面的每个函数:

① 将代码解析为符号序列;

② 在词表中查找每一个符号;

若遇到一个非登录词,

若该符号可以按驼峰拆分,

将符号拆分为子符号序列;

在词表中查找每一个子符号;

若其在词表里,输出子符号;

否则,输出该符号;

6) 输出预处理完的代码数据集以及词表.

3 基于关键词的代码自动摘要

编码器-解码器模型是一个广泛运用于文本摘要任务的深度学习模型.本文在编码器-解码器模型的基础上,提出了一种基于关键词的代码自动摘要模型KBCoS,利用基于关键词信息的注意力机制使模型更加关注输入中的关键信息,提高代码自动摘要的性能.

KBCoS主要包括代码编码器、关键词编码器、基于关键词的解码器3个部分.KBCoS的整体结构如图4所示.

Fig. 4 The architecture of KBCoS图4 KBCoS的架构

3.1 代码编码器

代码编码器将输入的代码视为代码符号的序列,并编码为语义向量序列.语义向量中编码了代码的语义信息,解码器进而对这些信息进行解码,得到最终的代码摘要.代码编码器主要包括代码嵌入层、代码编码层和分段最大池化层3个部分.

1) 代码嵌入层

2) 代码编码层

得到符号向量序列后,代码编码层使用循环神经网络(recurrent neural network, RNN)将其编码为符号语义向量序列h={h1,h2,…,hn},其中符号语义向量hi编码了第i个代码符号以及之前代码符号{token0,token1,…,tokeni-1,tokeni}的语义信息.

循环神经网络是针对序列形式提出的网络结构.序列数据具有长度不固定的特点,普通神经网络的结构难以处理变长数据.并且,由于序列数据往往具有前后依赖的特性,即一个序列中当前输出依赖于之前的输出,普通神经网络无法对这种依赖关系进行建模.而循环神经网络使用隐状态hi存储第i步之前的信息,保存了序列之间的前后依赖关系.因此,本文选择循环神经网络作为编码层.

对于神经循环网络中的第i步,其隐状态hi的计算公式为

其中,U,W,b为循环神经网络需要学习的参数,Φ为激活函数,通常选取tanh作为其激活函数.

但是,最基本的循环神经网络单元在训练过程中会遇到梯度消失的问题.针对该问题,长短期记忆网络(long short-term memory, LSTM)[37]、门控循环单元(gated recurrent unit, GRU)[38]等循环神经网络单元的变体被提出.经过实验证明,这些循环神经网络单元的变体在很多任务上性能都超过了最基本的循环神经网络单元.

本文选取GRU作为循环神经网络的基本单元,GRU使用更新门(update gate)控制信息的流动,使用重置门(reset gate)来决定组成激活候选状态的信息.对于GRU中的第i步,其隐状态hi的计算公式为

zι=σg(Wzxi+Uzhi-1+bz),
ri=σg(Wrxi+Urhi-1+br),
hi=(1-zi)hi-1+ziσh(Whxi+Uhrihi-1+bh),

其中,W,U,b为GRU需要学习的参数,zi表示更新门,ri表示重置门,σg和σh为激活函数,σg默认为sigmoid激活函数,σh默认为双曲线激活函数.

本文将循环神经网络的隐状态序列h={h1,h2,…,hn}作为代码符号的语义向量序列.

3) 分段最大池化层

经过代码编码层,可以得到代码符号的语义向量序列.分段最大池化层的目标是将该序列转化为一个长度固定的代码语义向量,其中编码了代码的语义信息.

语义向量通常可以通过3种方式得到:

① 将循环神经网络的最后一个隐状态作为语义向量;

② 将循环神经网络的所有隐状态经过最大池化层得到语义向量;

③ 将循环神经网络的所有隐状态经过平均池化层得到语义向量.

方式①只取了循环神经网络的最后一个隐状态,由于循环神经网络的每一步都会不可避免地遗忘掉前一步的语义信息,最后一个隐状态很容易丢失符号向量序列中靠前的元素的大部分语义信息,导致代码语义向量语义信息不够准确.

方式②③虽然考虑了序列中所有元素的语义向量,但在该过程中,所有元素的权重都是一样的,最后的代码语义向量没有关注代码中的关键信息,导致了关键语义信息的丢失.

从引言分析中我们可以知道,对于一个函数,它的函数签名包含了其代码摘要所需要的很多关键信息,而函数体中包含了大量对代码自动摘要贡献很少的符号,且结构复杂,模型学习较为困难.

本文提出了一种分段最大池化方法,该方法针对代码符号语义向量序列进行分段池化.具体而言,将代码符号语义向量序列h分为函数签名符号语义向量序列hsignature和函数体符号语义向量序列hbody两部分,并分别进行最大池化操作,得到函数签名语义向量esignature和函数体语义向量ebody,最后将这2个向量连接起来得到最终的代码语义向量ecode.

3.2 关键词编码器

本文将函数签名对应的符号和API调用对应的符号组合为关键词序列,并将其作为关键词编码器的输入.关键词编码将关键词序列编码为对应的关键词语义向量,该语义向量包含了本文希望解码器重点关注的关键语义信息,并通过注意力机制在解码过程中帮助解码器更关注输入中的关键信息.

1) 关键词嵌入层

假设从代码中提取的关键词序列长度为m:keyword={keyword1,keyword2,…,keywordm}.

2) 关键词编码层

3.3 基于关键词的解码器

传统的编码器-解码器模型使用循环神经网络作为解码器,并将上一时刻的输出作为当前时刻的输入对语义向量进行解码.此时,解码器只能通过一个语义向量获取输入序列的语义信息,而每一时刻解码器需要关注的语义信息是不一样的,因此,为了进一步挖掘输入序列的语义信息,提高模型对关键信息的关注程度,本文使用了带有注意力机制的解码器,并提出了一种基于关键词的解码器,该解码器在注意力机制中引入了关键词的信息,使解码的每一步都专注于更重要的信息,提高解码器的准确率.

1) 解码层

解码层将代码编码器得到的代码语义向量ecode作为初始隐状态s0,并且,在解码的每一步,利用注意力机制将编码器的所有隐状态序列h={h1,h2,…,hn}(即代码符号的语义向量序列)加权得到当前关注的动态代码语义向量.

其第t步概率公式可写作:

其中,yt表示当前时刻的输出;st表示当前时刻解码器的隐状态,其计算公式为

通过查表的方式可以由yt找到对应的自然语言词表中的单词wordt.当时刻T输出的单词为EOS这个特殊符号时,表示输出序列的结束,此时由word1,word2,…,wordT组成代码的摘要.

此外,在预测生成摘要的过程中,还采用了集束搜索(beam search)的机制,得到若干候选摘要,从中选取概率最大的摘要.原因是解码时每一步都选取概率最大的词,并不意味着最终生成的序列的概率最大.

2) 基于关键词的注意力机制

基于关键词的解码器利用关键词信息对注意力机制进行增强,计算公式为:

4 实 验

4.1 数据集

实验中使用的Java自动摘要数据集来自文献[18],其中包含了87 136个代码,API,摘要的三元组,并按照8∶1∶1的比例划分成训练集、校验集和测试集.训练集中69 708条数据的统计信息如表2所示:

Table 2 Statistics of API, Code and Comment Sequence Length

Java自动摘要数据集中的数据包含了Github上收藏数量超过20的Java项目的代码.数据集以函数为单位,选取函数文档中的第1句话作为代码摘要,并抽取代码中调用的JDK API作为辅助信息,帮助代码摘要的生成.其预处理的方法和文献[39]保持一致,并且,如果某个函数缺少函数文档,或者其函数文档的第1句话是无意义的,该函数将从数据集中删除.

Java函数中API调用的信息是代码的关键信息之一,而大部分的代码摘要数据集都忽略了这一信息.并且,这些数据集经过预处理后只包含函数本身的代码,而丢失了上下文特别是包导入的信息,导致我们不能还原函数所对应的API调用信息.因此,本文采用上述数据集作为本文的主要数据集.

4.2 基准模型和实验超参数

为了验证实验的有效性,我们将带有注意力的序列到序列模型(编码器-解码器模型)作为基准模型.实验中使用的超参数如表3所示:

Table 3 Hyper-Parameters Used in Experiments表3 实验中模型的超参数列表

在后续的实验中,为了公平起见,将每个模型都训练50个回合(epoch)[16],根据校验集上的评测结果选择BLEU指标最高的一组参数,然后在测试集上评测使用了这组参数的模型并记录各项指标.训练时初始学习率为0.5,前5个回合保持不变,之后每过3个回合乘以0.95.本文使用随机梯度下降法(SGD)对参数进行优化.

4.3 评测指标

我们使用BLEU-4,METEOR,ROUGE-L作为实验的评价指标.

1) BLEU-4.BLEU-4是一种基于精确度的相似性度量方法[40],用于分析2个文本间n元组(n-gram)的共现程度,经常被用于机器翻译结果的评估.

2) METEOR.METEOR是一种基于准确率和召回率的评价指标,其考虑了基于整个语料库的准确率和召回率,是另一种常见的评价语料相似度的指标,其目的是解决一些BLEU-4中固有的缺陷[41].并且,METEOR还考虑了同义词匹配等,首先基于WordNet的同义词库给定一组校准m,通过最小化对应语句中连续有序单词形成的块的数目ch来得出,然后计算对应最佳候选译文和参考译文之间的准确率和召回率的加权调和平均.

3) ROUGE-L.ROUGE是一种基于召回率的相似度度量方法[42],主要考察了预测结果的充分性和忠实性.ROUGE共包含4种评价指标:ROUGE-N,ROUGE-L,ROUGE-W,ROUGE-S.本文选ROUGE-L作为评价指标.

4.4 验证符号部分拆分机制

为了验证部分拆分机制的可行性,本文首先对部分拆分前后的词表信息和未登录词信息进行了统计(保留原有的大小写,即符号大小写敏感),统计信息如表4和表5所示:

Table 4 Number of Code Tokens for Different Frequencies表4不同词频的符号数量统计

Table 5 Number of Out-of-Vocabulary Tokens表5 未登录词数量统计

由表4中的统计信息可知,代码符号中有大量符号只出现了不到4次,如果将这些符号加入词表,模型将无法充分训练提取这些符号的语义信息,导致出现大量噪声.由于文献[18]使用的代码符号词表大小为5万,因此,本文选取词频大于等于4的符号构建词表.对于符号部分拆分前后的词表,词表大小由57 493变为63 204,增长不到10%,训练难度不会因此而提升太多.与此同时,由表5中的统计信息可知,符号部分拆分后,未登录词总数由222 349下降为27 416,下降幅度接近90%.这极大减小了未登录词对于模型的影响,提高了代码自动摘要的准确性和可靠性.

为了验证符号大小写敏感和符号部分拆分机制的实际效果,根据是否保留符号的大小写、是否使用部分拆分算法,我们实现了基准模型的4个变体,在测试集上的评测结果如表6所示:

Table 6 Experimental Results for Baselines表6 基准模型的实验结果

对比基准模型3,4的结果,在大小写敏感的前提下,经部分拆分后基准模型的所有评测指标都得到了提升,且大小写敏感+符号部分拆分的基准模型4在所有4个基准模型的3个评测指标中都获得了最高的得分,这表明部分拆分确实是一种有效的提升代码自动摘要效果的方法.

对比基准模型1,3以及基准模型2,4的结果,将大小写不敏感改为大小写敏感后,所有指标都得到了提升或者基本保持一致.实验结果证明,大小写敏感可以更有效地保留符号的语义信息.而且从提升的效果来看,在符号部分拆分以后,大小写是否敏感导致的差异尤为明显.特别是,在大小写不敏感且符号部分拆分的情况下,基准模型3个指标在4个基准模型中都是最低的.我们猜想,部分拆分后,将符号全部小写归一化会导致子符号序列边界的消失.

在大小写敏感且符号部分拆分的情况下,基准模型的效果最好,因此,本文将这2种策略作为默认设置,后续实验如无特别说明,基准模型的默认设置为大小写敏感且符号部分拆分.

我们还尝试了别的文献中采用的将子符号的向量求和或取平均得到代码符号向量的应对未登录词的策略,同样采用基础模型,在测试集上的评估结果如表7所示:

Table 7 Experimental Results for Other Strategies to Deal with OOV

4.5 验证KBCoS

KBCoS将函数签名+API调用序列作为关键词,让解码器在解码生成摘要时每一步的注意力分布相对更集中在关键信息上.为了验证KBCoS模型的有效性,我们将KBCoS与基准模型进行比较,在测试集上的评测结果如表8所示:

Table 8 Experimental Results for KBCoS表8 KBCoS的实验结果

实验结果表明,和基准模型相比,KBCoS在3个评测指标上都有较大的提升,因此,基于关键词的代码自动摘要模型是有效的.

此外,我们还尝试了仅仅使用API调用或仅仅使用函数签名作为关键词,实验结果表明分别使用这2种关键词的KBCoS模型在测试集上的评测结果虽然都比使用了符号部分拆分的基准模型要好,但都不如同时使用API调用和函数签名作为关键词的KBCoS模型效果好.

4.6 与SOTA的方法比较

文献[18]提出基于API调用序列的迁移学习模型TL-CodeSum来提升代码自动摘要的性能,并取得了较好的效果,是当前在这个数据集上State-of-the-Art(SOTA)的结果.

为了和文献[18]的方法更好地比较,我们将KBCoS与TL-CodeSum相结合,使得KBCoS也能够利用API调用序列中的信息辅助代码摘要的生成.从技术角度上说,我们为KBCoS添加了一个API调用序列编码器,该编码器输入为API调用序列,输出为API调用序列的语义表示,表示编码了API调用序列的语义信息,可以很好地辅助代码摘要的自动生成.

我们在测试集上评估了添加API调用序列对基准模型和KBCoS的影响,结果如表9所示:

Table 9 Experimental Results for API表9 API信息的实验结果

根据实验结果,我们可以得出3个结论:

1) 添加一个编码器对API调用序列单独编码,能进一步提升模型的性能,无论是基准模型和KBCoS.这点和文献[18]的结论是一致的.

2) BKBCoS+API、基准模型+API在3个评测指标上均超过了基准模型(原始序列)+API,而文献[18]的模型与本文中基准模型(原始序列)+API的模型是一致的,因而KBCoS+API、基准模型+API在这个数据集上均超过了SOTA的结果(由于不同工具计算各个指标的结果会有差异,因此我们没有直接比较文献[18]中的测试结果,而是复现了他们的实验,然后用一个工具计算了各个指标).

3) 我们提出利用函数签名和API调用作为关键词来改善模型解码时注意力的分布以及部分拆分的算法,都能进一步提升别的模型的效果,具有一定的通用性.

4.7 在Python数据集上的验证

为了进一步验证本文提出算法的有效性,我们又在一个Python数据集[28]上重复了部分实验.该数据集是从Github上的项目中构建而成的,包含了108 726个函数,摘要对.我们将数据集按3∶1∶1的比例划分为训练集、校验集和测试集.实验中其他超参数的设置和Java数据集上的实验一致,同时,由于没有得到Python代码的API调用信息,我们使用函数调用序列代替API调用序列作为关键词.实验结果如表10所示,相比原始符号序列的基准模型,采用了部分拆分算法的基准模型在3个评测指标上均有略微的提升;进一步采用函数签名+API调用序列作为关键词,KBCoS比基准模型在3个评测指标上又有了明显的提升.

Table 10 Experimental Results for Python Dataset表10 Python数据集上的实验结果

5 样例分析

Table 11 An Example of Generated Summary表11 代码自动摘要样例

对比生成的摘要,我们可以看到KBCoS预测的结果和人工摘要是一致的.基准模型生成的摘要也不错,add,listener,list这3个关键词都准确地被预测,只不过略有冗余.相比之下,基准模型(原始序列)的预测结果就差强人意,由于addSearchListener,m_SearchListeners在没有经过部分拆分的情况下都是未登录词,模型只学到了add,SearchListener这2个符号中所包含的信息,而“to the buffer”可能是模型根据“经验”猜测得到的.

此外,注意力权重的热力图可以更好地分析基准模型与KBCoS之间的区别,如图5、图6所示,其中横坐标上依次排列了经部分拆分后的代码符号序列,纵坐标上依次排列了模型预测的摘要序列,而颜色越深的方块表示注意力的权重越大.

对比2张热力图,我们可以看到,在基准模型的热力图中,每一步解码时(图中每一行)基准模型关注的代码符号多而分散.相比之下,KBCoS则可以很好地找到关键信息,函数名部分(“add search listener”)和关键代码部分(“m,search,listeners,add”)都是其关注的重点.由于adds作为摘要中的第一个词已经输出,故API调用“HashSet.add”并没有得到太多的关注.特别一提的是,通过关注关键代码符号“search,listeners”,模型推导出了摘要中包含的“list of listeners”.可见,关键词信息可以帮助模型更好地关注代码中的重要信息.

Fig. 5 The heat map of attention weights in baseline图5 基准模型中注意力权重热力图

Fig. 6 The heat map of attention weights in KBCoS图6 KBCoS中注意力权重热力图

6 总 结

本文针对代码自动摘要任务,提出了一种基于关键词的代码自动摘要方法KBCoS.实验结果表明KBCoS能够生成更好的代码摘要,并且明显提升了基准模型的评测指标,其主要原因有:

1) 通过符号部分拆分,一定程度上缓解了在给定合适大小的词表的情况下代码符号中存在大量未登录词的问题,使模型能更好地提取符号中的语义信息.

2) 通过基于关键词的注意力机制,使解码器解码时更关注代码中的关键信息,提高了摘要的质量.

从本文的实验结果可以看出,符号区分大小写、符号部分拆分以及显示地利用关键词信息(函数签名和API调用)能够帮助模型更好地生成代码摘要.

进一步在KBCoS中添加一个API调用序列的编码器,模型能Java代码自动摘要数据集中取得当下最优的结果,表明我们提出的方法也能提升别的模型的性能.

猜你喜欢
解码器代码语义
真实场景水下语义分割方法及数据集
科学解码器(一)
科学解码器(二)
科学解码器(三)
侏罗纪公园
神秘的代码
一周机构净增(减)仓股前20名
一行代码玩完19亿元卫星
“吃+NP”的语义生成机制研究
近期连续上涨7天以上的股