1 INTRODUCTION
软件维护blablabla……代码注释blablabla……
好的代码注释应具有以下特征:(1) 正确性,正确地阐明代码的意图;(2) 流利,使维护者易于阅读和理解;(3) 一致性,遵循标准的样式/格式。
现有的研究:统计语言模型,模板和规则,神经机器翻译等。
研究的局限性和作者的一些见解:
- 直接输入代码作为文本,不考虑代码的层次结构(能够通过不同上下文的不同token为注释生成提供更全面的表征)。
- 只用简单的时序特征来表示代码,如token 序列,而其他能够捕获注释和程序之间相关性的代码特性并未探索(控制流图CFGs,抽象语法树AST,程序变量的类型等)
- 现有的训练方法为“teacher-forcing”模型,受到Exposure Bias的影响(在测试阶段无法获得ground truth,并且之前生成的词作为输入用于预测后续单词),模型仅基于ground truth来进行训练,无法暴露自身的错误。
上述问题解决方案:基于层次attention的学习方式+actor-critic强化学习
- 使用类型增强的AST序列来替代基于AST的树结构表示,并用控制流补全代码表示;解决限制(2)。
- 采用hierarchical attention network (HAN)对不同的代码序列进行编码;解决限制(1)。
框架概述:
- 离线训练阶段:
- 大规模带注释的<code, comment>pair语料库
- 三种序列:非结构化级别 —— xTXT (纯代码序列), 结构化级别 —— xAST(类型增强AST),xCFG (控制流)(图1(a))
- hierarchical attention network (HAN) 进行编码和集成 (图1(b))
- 注释对输入深度强化学习模型进行训练 (图1©)
- 在线总结阶段:
- 向actor网络中输入一个给定代码片段,生成相应注释。
贡献点:
-
新的思路:提出了一种深度强化学习框架Actor-Critic网络来生成注释
-
广泛的算法:提出HAN学习方法,总结利用多个代码特性反映代码层次结构
-
评估:在真实数据集上达成了最优的性能
2 PRELIMINARIES
预备知识:
2.1 语言模型
概率预测
2.2 RNN 编码器-解码器模型
- 编码器
- 解码器
- 训练目标(loss)
2.3 强化学习
强化学习与环境相互作用,从奖励信号中学习最优策略,潜在地解决极大似然引入的Exposure Bias问题。
在基于强化学习框架中,除了生成序列的概率, 还会在训练模型时计算reward作为反馈,以减轻Exposure Bias问题。文本生成过程可以视作马尔科夫决策过程(MDP){S, A, P, R, γ },在MDP设置中,时刻t的状态st由代码片段x和预测的单词y0, y1, … , yt组成,动作空间定义为描绘单词的词典У,yt∈У。相应的,状态转移函数P定义为s{t+1} = {st, yt},动作(即单词)yt 成为后续状态s{t+1} 的一部分,并得到奖励rt+1 。这一过程的目标是找到一个策略,最大化模型生成语句的期望奖励:
θ \theta θ 是需要学习的策略参数,D是训练集,y_hat 是预测的动作(单词),R是奖励函数。
总体目标仍然是对给定代码片段x生成单词序列,并最大化期望奖励。学习策略的方法主要分两种:(1) 基于策略,通过策略梯度直接优化策略;(2) 基于值,学习Q-函数,每次选择具有最高Q-值的动作。由于基于策略的方法存在方差问题,基于值的方法存在偏差问题,因此本文采取二者相结合的actor-critc学习方法。
3 ILLUSTRATIVE EXAMPLE
展示例子:
图3(a)是一个简单的Python代码示例,通过递归函数获得整数的阶乘,图3(b)是图3(a)中代码的AST,图3©是程序执行顺序的控制流程图。该代码的理想注释(绿色)如图3(a)所示,三个突出显示的单词语义可以由不同的代码表示精确地捕获,例如,plain text (用于 multiplying), type-augmented AST (用于integer) 和 CFG (用于 recursive)。
在不同的代码表示中,token和语句的顺序会有所不同。本文使用了代码的三种结构化和非结构化信息,纯文本、AST和CFG。基于纯文本,代码表示为 “def fact ( i ) : if i == 0 : ……”;基于AST,代码表示为{ stmt = FunctionDef ( identifier fact, arguments i, stmt body ) ; body = IfExp (expr test, expr body , expr orelse) ; …};基于CFG,由于递归调用,后续的token从def开始。
图3(a)中可以观察到语句1由def、fact、和 i 组成,而整个函数由语句1-5组成,这样的层次结构能够由一个两层的注意力网络进行捕获(包括token层和statement层),如图4所示。底层对每个语句 s i s_i si的token x i t x_{it} xit进行编码即这一条语句的向量, α i \alpha_i αi和 α i t \alpha_{it} αit分别表示第i个语句和第i个语句的第t个token的权重。
本文就是利用这三种代码表示以及HAN,分别对不同的token和语句序列生成三种不同的向量,最后把三个向量连接起来产生最后的代码表示,精确地捕获token之间和语句之间的关系。
4 THE DRL-GUIDED CODE SUMMARIZATION VIA HIERARCHICAL ATTENTION NETWORK
通过层次注意力网络实现深度强化学习引导的代码摘要。
本文方法遵循已成功应用于AlpahGo的Actor-Critic网络框架,并把框架分为四个子模块,如图5:
- 用于解释程序的非结构化和结构化信息的代码表示
- 用于将代码表示编码为隐藏空间中的向量的混合分层注意力网络
- 文本生成,根据前一词生成后续词的基于LSTM的生成网络
- 用于评价生成词的质量的critic网络
4.1 Source Code Representations
源代码表示,首先用一组符号 {. , ” ’ ( ) { } : ! - (space) } 对代码进行分割和token化,全部字母小写,并使用Word2Vec对token进行词嵌入,未定义的token作为未知单词处理。
有以下三种代码表示:纯文本、类型增强的抽象语法树和控制流图。
4.1.1 Plain Text
纯文本
最直接普通的文本表示,对于代码的词法级表示的关键见解:注释总是从源代码的词法项中提取,比如函数名、变量名等等。
4.1.2 Type-augmented Abstract Syntax Tree
类型增强的抽象语法树
本文首先利用python的AST模块获取AST序列,然后,为了利用额外的类型信息来增加派生的AST序列,抽象了token的类型信息,并将它们与代码的AST序列集成。例如,图3(a)中,通过在变量“1”上注释“integer”类,将第2行表示为“if integer i == integer 1”。
总之就是对每个节点都增加了类型信息。
4.1.3 Control Flow Graph
控制流图
本文提取了控制流图(CFG)作为代码的另一种语法级表示,CFG上的每个节点代表一条由一系列token组成的语句,连接两个节点的每条边表示程序的控制流。
某种意义上应该跟type-augemented AST一样,作为AST的一种补充。
4.2 Hybrid Hierarchical Attention Network
每个代码部分都对生成注释有自己的贡献,而token和语句高度依赖上下文,具体为,相同的token或语句在不同的上下文中可能有不同的重要性,而代码又具有层次结构(token形成语句,语句形成函数),因此,在NLP领域获得成功的HAN自然地用在代码表示上,分别为单个token和语句分配权重(attention)。注意力不仅能带来更好的性能,而且还可以洞察token/语句和相应的摘要之间的相关性,这有利于生成高质量的注释。
本文采用两层attention网络(一个token layer和一个statement layer),如图5(b)所示。该网络由四个部分组成:token序列编码器、token级注意层、语句编码器和语句级注意层。
假设 d T X T 、 d A S T 和 d C F G d^{TXT}、d^{AST}和d^{CFG} dTXT、dAST和dCFG是通过编码纯文本、AST和CFG这三种代码表示形式得到的向量,把它们合并到一个混合向量 d d d来表示代码。该网络的具体内容如下:
token编码器:给定一个语句 s i s_i si,它有 T i T_i Ti个token, x i 0 , . . . , x i T i − 1 x_{i0},...,x_{iT_{i-1}} xi0,...,xiTi−1。本文首先用嵌入矩阵 W i W_i Wi把所有token 嵌入到词向量中,即 v i t = W i x i t v_{it}=W_{ix_{it}} vit=Wixit,然后用LSTM从 x i 0 x_{i0} xi0到 x i T i − 1 x_{iT_{i-1}} xiTi−1读取语句 s i s_i si来获得相应的token注释:
v i t = W i x i t , t ∈ [ 0 , T i ) v_{it}=W_{i}x_{it},t∈[0,T_i) vit=Wixit,t∈[0,Ti)
h i t = l s t m ( v i t ) , t ∈ [ 0 , T I ) h_{it}=lstm(v_{it}),t∈[0,T_I) hit