APP下载

组合源码结构和语义的代码注释自动生成方法

2021-12-08周培君吴军华

小型微型计算机系统 2021年12期
关键词:解码器源代码编码器

周培君,吴军华

(南京工业大学 计算机科学与技术学院,南京 211816) E-mail:413402140@qq.com

1 引 言

代码注释又称程序注释,是对源代码的一种简单易读的自然语言描述.在实际应用中,大多数程序员只关注代码而忽略了注释,这使得程序的可读性和可维护性大大降低[1].开发人员平均花费超过50%的时间阅读和理解他人编写的代码[2].好的代码注释可以帮助开发和维护人员更准确、更快地理解程序,从而节省他们额外的阅读时间.

随着软件代码规模的不断扩大,如何帮助开发人员在软件开发过程中理解、编写或维护代码已成为软件工程领域的重要课题.Java作为最流行的主要编程语言被提出了许多方法来生成注释,以减轻对代码进行注释的人工工作.信息检索是自动代码注释生成研究中最早使用的技术.基于信息检索技术的评论算法一般采用基于向量空间模型(VSM)、潜在语义索引(LSI)、潜在狄利克雷分配(LDA)或代码克隆检测等相关技术.近年来,随着深度神经网络在自然语言处理、机器翻译、图像识别和语音处理等方面的发展,软件工程领域中也引入了深度神经网络来解决代码注释问题.绝大多数基于深度神经网络的代码注释工作的灵感来源于自然语言处理(NLP)的最先进的神经机器翻译(NMT),即将源代码到注释的转换问题表示为编程语言与自然语言之间的翻译问题.

本文提出了一种组合源码结构和语义的神经模型Code2Com(Code to Comment),一种基于深度学习的为给定代码生成注释的方法.工作主要集中在模型输入和注释生成.模型组合了两种关于源代码的信息:1)代码的顺序表示;2)抽象语法树表示.与之前的方法不同,Code2Com以不同的方式对待每种输入,增加了方法的灵活性.为了克服曝光偏差,模型训练与推理应在相同的条件下进行预测,Code2Com在训练过程中提供预测词序列作为下一步的输入,以便逐步迫使模型处理自己的错误,让模型在训练过程中进行更多探索,以此减小训练和推理之间的差异.

2 背 景

作为自然语言处理(NLP)系统的核心组成部分,语言模型可以提供词表征和单词序列的极大似然.语言模型描述了单词出现在序列中的概率.对于一个由n个按顺序构成的自然语言序列x=(x1,…,xn),语言模型根据已知序列(x1,…,xk-1)预测下一个词语xk的概率,即:

(1)

对语言模型建模时,为了减少建模时的维数灾难,Brown等人提出一种N元(N-gram)语言模型的近似方法,即预测的第k个词的出现仅依赖于前面的k-1个词:

p(xk|x1,…,xk-1)≈p(xk|xk-n+1,…,xk-1)

(2)

然而,这种N-gram方法有明显的局限性,例如未考虑自然语言的离散型、组合性和稀疏性.为了解决这个问题,神经网络(NN)被引入到语言模型的训练中,例如循环神经网络(RNN,Recurrent Neural Networks)、长短期记忆网络(LSTM,Long Short-Term Memory)、门控循环单元网络(GRU,Gated Recurrent Unit)等.RNN包括3层,将每个输入映射到向量中的输入层,在读取每个输入向量后循计算并更新隐藏状态的循环隐藏层,以及利用隐藏状态计算预测token概率的输出层.RNN在训练过程中,每层的梯度大小会在长序列上呈指数增长或衰减[3].梯度爆炸或消失的问题使RNN模型难以学习序列中的长距离相关性.LSTM通过引入一个能够长时间保存状态的记忆单元,有效解决学习长期依赖关系的问题.在本文中,我们使用Chung等人提出的GRU,因为它在序列建模方面的性能与LSTM相当[4],但参数较少,易于训练.

对源代码和注释之间的关系进行建模可用于自动代码注释.一种直接的方法是将问题转换为机器翻译问题,其中源句子是代码中的token序列,目标句子是相应的注释序列.Iyer等人[5]提出了一种基于LSTM的注释生成模型CodeNN,它使用具有注意机制的LSTM为C#和SQL生成注释.CodeNN通过加入注意力机制,直接将注释中的单词与相关代码token对齐.Allamanis等人[6]使用卷积神经网络(CNN)和注意力机制生成源代码的摘要.该方法使用卷积注意模块对输入的源代码提取特征,并确定序列中应注意的重要token.此外,一些论文也将源代码建模为一系列token[7]和字符[8].这些工作在各种生成代码注释和文档任务中都取得了最先进的性能.还有一些方法考虑了源代码的结构信息.Liang等人[9]提出了一种基于RNN的AST编码器Code-RNN.Chen等人[10]提出了用于代码注释生成的树到序列模型Tree2Seq,Tree2Seq使用了一个基于AST的编码器代替RNN编码器.

这些方法没有充分利用代码的结构和语义信息在特征表示上,如程序语法、函数和方法调用[11],大大降低了代码表达的质量,这就需要模型隐式地重新学习编程语言的语法,浪费资源并且降低了准确性.其次在编码器解码器框架下,训练时模型的输入完全采用真实序列标记,而在推理时,解码器的当前输入是模型生成的上一个词.这使得在训练和推理时预测的词来自不同的分布,随着目标序列的增长,训练和推理之间的差异会导致错误的累积.

3 Code2Com模型设计

如图1所示,Code2Com主要包括数据处理,模型训练和代码注释生成.在该结构中,使用两个编码器获得源代码的表示:AST编码器负责读取AST序列,代码编码器负责读取代码token序列.解码器在生成目标序列时一次预测一个单词.

图1 Code2Com的整体流程Fig.1 Overview of Code2Com

3.1 代码预处理

为了利用Java的语法结构来更好地编码源代码,将代码片段表示为抽象语法树(AST,Abstract Syntax Tree).通过深度优先搜索对AST的节点进行遍历,可将Java源代码转化为序列化数据供神经网络学习.然而,AST的节点并不对应语法的所有细节,比如对于嵌套括号等没有对应的节点,这种模糊性可能会导致将不同的Java方法映射到相同的序列表示.为了保留结构信息,本文在遍历时使用“(”和“)”将语法树的中间结点包围起来,并在括号前面注明该结点本身.例如,考虑图2中的Java方法.

图2 Java示例Fig.2 Java example

从根节点MethodDeclaration开始,使用MethodDecla-ration()表示第一层树结构.接下来遍历MethodDeclaration节点的子树,将子树的叶子节点int放入括号中,即Metho-dDeclaration(int),依次递归遍历所有子树得到最终序列.

上述序列与AST的对应关系如图3所示.

图3 Java代码片段的AST和处理后的AST序列Fig.3 AST of the Java method and processed AST sequence

3.2 Code2Com编码和解码

Code2Com遵循NMT的标准编码器-解码器结构,该结构通常由两个RNN组成,一个称为编码器,编码器将不定长的输入数据X=(x1,…,xt)转换为定长的隐表征序列H=(h1,…,ht),其中t表示输入序列的长度.另一个称为解码器,根据从编码过程中获取的隐向量H,解码器预测生成任意长度的输出序列Y=(y1,…,yn),其中n表示解码器预测结果token的数量.

本文使用了一种新的神经注意网络结构.Bahdanau等人[12]提出的注意力机制被广泛应用于NMT,阅读理解,语音识别和计算机视觉.在基于注意的模型中,注意力机制负责将较高的权重值动态分配给解码器输入单词中更相关的token,这样就提高了解码器的效率.

如图4所示,Code2Com利用GRU的优点,设计了一种新的组合代码的表示方法.在给定源代码作为输入的情况下,将代码序列投影到连续向量空间,利用AST和代码编码器获得源代码的表示.一旦源代码被编码,注意解码器便开始逐词生成目标注释.为了减少训练和推理之间的差距,在模型训练阶段,通过在预测得分中添加干扰项来获得新的预测词,然后从数据分布和模型分布中选择解码器下一步的输入,让模型能够更稳健地纠正自己在推理时的错误.

图4 模型结构Fig.4 Model structure

3.2.1 AST编码器和代码编码器

Code2Com使用GRU作为编码器中的基本构建块.AST编码器负责学习代码的结构表示,具体地从一个相当常见的编码结构开始,包括为每个输入编码的嵌入层.AST编码器使用GRU逐个读取AST序列as=(as1,…,asn),通过embedding层将输出shape(batch_size,vocab_size,embd_dim),其中batch_size为批处理大小,vocab_size为词表大小,embd_dim为嵌入维度,也就是每个词对应的词嵌入维度的向量.通过对每个词的各个特征进行embedding,可以得到每个特征的向量化表示.接下来是用作AST编码的GRU单元,GRU将该层的输入(AS1,…,ASn)映射到隐藏状态(h1,…,hn).在时间步t,根据当前输入ASt和上一步隐藏层的状态ht-1按照式(3)递归地更新隐藏状态ht:

ht=fGRU(ht-1,ASt)

(3)

其中ASi为单词asi的embedding向量.编码器从源代码中学习标识符、控制结构等潜在的特征,并将这些特征编码到上下文向量中.代码编码器工作方式与AST编码器几乎相同.

3.2.2 组合注意

(4)

(5)

3.2.3 注释生成

在获得代码片段的表示之后,需要将其解码为注释.首先模型根据上一个词、上下文向量和当前步的隐藏状态通过线性变换生成目标词,接下来将目标词映射到分类得分中,产生对应的维度:

Ot=g(Yt-1,Ct,st)

(6)

Mt=WOt

(7)

其中,Mt为softmax操作前的预测得分.本文通过引入Gumbel[13]噪声实现多项分布采样的再参数化.具体地,对于解码的第t步,在式(7)的Mt中引入正则化项Gt:

Gt=-log(-log(ut))

(8)

(9)

(10)

其中pt表示目标单词yt的概率分布.最后模型根据pt选择预测词:

(11)

(12)

4 实验分析

在这部分,进行了大量的实验来证明本文提出的模型在代码注释自动生成任务上的优势.实验比较了Code2Com与3种典型方法Seq2Seq+Attn、CodeNN和TreeLSTM的性能,同时还通过性能对比实验进行了组件分析,以评估每个组件在Code2Com中的贡献.

4.1 实验数据和参数配置

实验使用Allamanis等人[6]提供的数据集,该数据集中包含11个相对较大的Java项目,最初用于同一项目范围内的11个不同的模型.以往的研究使用的数据集没有按项目划分,同一项目中几乎相同的代码同时出现在训练集和测试集中,这让模型严重过拟合了训练数据,导致BLEU得分虚高.通过按项目划分数据集,进行跨项目训练,对不同的项目进行预测任务,以获得更真实的预测结果:将数据集分成3组,9个项目用于训练,1个项目用于验证,1个项目用于测试.这个数据集包含大约70万个示例.

Code2Com使用TensorFlow框架在GPU上进行训练.对于编码器和解码器,都使用了256个隐藏单元的GRU.单词嵌入的维数为256,我们发现两个独立的嵌入比单一的嵌入空间有更好的性能.在训练过程中,用Adam优化了模型,使用了默认的超参数:学习率α=0.01,β1=0.9,β2=0.999,β2=0.999,=1e-8,其他参数在[-0.1,0.1]范围内随机初始化.对于解码,使用贪婪搜索算法进行推理,以最小化实验变量和计算成本.在式(9)中设置temp=0.5,在式(12)中设置μ=12.代码序列和AST序列大小为100,注释的序列大小为13,每个序列至少覆盖80%的训练集.低于最小长度的短序列用零填充,超过最大长度的长序列将被截断.

4.2 实验结果及分析

在代码到注释任务上评估本文的模型:根据一个Java方法来预测它的注释.实验结果证明本文提出方法可以产生注释,并且在性能上有一定的提高.

4.2.1 性能分析

模型使用BLEU得分[15]进行评估.BLEU是评估模型预测注释和参考注释之间文本相似度的指标.本节将我们的方法与Iyer等人[5]提出的注释生成模型CodeNN进行比较,后者是一种最先进的代码摘要方法,该方法使用带有注意力机制的LSTM直接从源代码的token嵌入生成注释.此外,我们还比较了两个将输入源代码作为token流读取的NMT基线:基于注意力的Seq2Seq模型(Seq2Seq+Attn),编码器和解码器都采用GRU,Seq2Seq+Attn代表了NLP研究领域强大的现有方法的应用;Tai等人提出的带有注意输入子树,并采用LSTM解码器的TreeLSTM[16];代码注释自动生成任务的性能如表1所示.

表1 Code2Com及相关方法的BLEU得分(%)Table 1 BLEU scores of Code2Com and related methods(%)

表1总结了代码注释生成的每种方法的BLEU得分.由于BLEU的关键评价内容是N-gram精度,实验分析了n取不同值时的BLEU得分.Seq2Seq模型在Java方法注释方面表现优于CodeNN,CodeNN直接从源代码的token生成注释时无法学习Java方法的语义,而Seq2Seq模型可以利用GRU为源代码建立语言模型,有效地学习Java方法的语义.尽管TreeLSTM也利用了语法,但本文的模型同时考虑了代码的结构和序列信息,并且通过解决曝光偏差问题增强了模型的容错能力.本文的模型达到了21.37的BLEU分数,与TreeLSTM模型相比,BLEU得分提高了2.97分(相对16.1%),并且高于所有对比模型.这表明我们的方法适用于代码注释.我们在表2中展示了测试集中的Java示例,Code2Com生成的注释最接近正确结果,虽然TreeLSTM和Seq2Seq+Attn也生成了部分真实序列中的token,但是它们不能预测那些在训练集中不常出现的token.在大多数情况下,模型非常依赖明确的方法名,比如setter和getter方法.如图5所示,方法名没有明确说明该方法需要做什么,其他模型便很难正确地注释.相反,本文的模型可以生成更接近真实结果的token.

表2 代码注释预测结果Table 2 Code comment prediction results

图5 Java示例Fig.5 Java example

4.2.2 组件分析

为了评估理解各组件的贡献,对模型进行了消融实验.首先,只考虑代码的顺序表示,在不使用AST的情况下实现了该模型;其次,实现了一个只采用AST节点作为输入的模型;最后,通过训练了一个使用计划采样的模型,以验证模型性能的提升不仅仅是一种简单的正则化形式.模型评估结果见表3.

表3 Code2Com中每个组件的有效性Table 3 Effectiveness of each component in Code2Com

如表3所示,不编码AST会导致BLEU得分下降,通过对AST分配权重,可以增加对命名token的注意力,并有效地忽略括号、分号等功能性token.其次,简单地在模型中使用AST而不使用token会大大降低分数,因为不使用token相当于对没有标识符名称、类型、APIs,和常量值的代码进行推理,这非常困难.源码结构和语义相结合能够为注释生成更好的分数.另外,我们观察到使用Gumbel采样的模型可以提高源代码注释生成的性能,BLEU得分提高了1.17.输入Code2Com的预测词可以有效地减轻曝光偏差,这优势背后的原因可能是Gumbel噪声的采样效果更接近真实分布的样本,可以帮助选择更有效而健壮的预测词.此外,参数值的设置直接影响到模型生成质量.本文对temp进行了参数分析,当temp在0.5时得到了最佳模型,如图6所示.

图6 Code2Com在不同temp设置下的性能Fig.6 Code2Com performance under different temp settings

4.2.3 句长分析

本节研究了每个模型的性能如何随着代码长度而变化.如图7所示,随着输入代码长度的增加,所有模型BLEU得分都显示出自然下降,与其他基线相比,在不同代码长度的度量上,Code2Com性能最好.此外,我们的模型在处理长句方面显示出稳定的性能.

图7 BLEU分数与代码长度的比较Fig.7 Comparison of BLEU score and code length

5 结束语

提出了一种新的自动生成Java代码注释模型,考虑了源代码独特的语法结构和顺序信息.核心思想是通过对代码的结构和顺序内容进行编码,并在训练时使用采样方案将真实词或优化生成的前一个预测词作为上下文.实验结果表明,Code2Com优于现有的方法.在未来的工作中,计划设计一个复制机制来处理词汇表外特殊的未知token,同时研究如何来捕获输入结构,并将提出的方法扩展到其他机器翻译问题的软件工程任务,例如代码迁移.

猜你喜欢

解码器源代码编码器
基于ResNet18特征编码器的水稻病虫害图像描述生成
WV3650M/WH3650M 绝对值旋转编码器
WDGP36J / WDGA36J编码器Wachendorff自动化有限公司
基于TXL的源代码插桩技术研究
基于Beaglebone Black 的绝对式编码器接口电路设计*
基于Android环境下的数据包校验技术分析
浅谈SCOPUS解码器IRD—2600系列常用操作及故障处理
保护好自己的“源代码”
解密别克安全“源代码”
做一个二进制解码器