本文是A deep tree-based model for software defect prediction论文的阅读笔记,此论文使用的仍然是LSTM网络,但是其又利用了代码的AST关系树,可以更好地捕获源码中的语义关系,为缺陷预测提高准确性与说服力。本篇论文仍然是不可多得的一篇好文,给我们提供了一种对于缺陷预测的新思路和新方法。下面就是本片论文的全部核心总结内容!
摘要部分内容总结为如下四点:
在引言部分作者首先还是谈论了一下老生常谈的问题,就是如今软件中的缺陷对我们产生的重大影响,我们如何准确高效的识别软件中的缺陷是我们目前关注的问题。
随后作者又简单介绍了当前机器学习是如何应用到缺陷检测的。主要是从中提取特征,定义为预测因子,将其提供给例如朴素贝叶斯、支持向量机或随机森林等分类器,从而判断有无缺陷。但是这些特征都是“表面特征”,并没有从代码的语法和语义的角度出发。所以最后导致检测结果在某个特定的数据集表现良好,而换个数据集的表现可能不尽人意。另外,自然语言处理技术也被应用到源代码的缺陷检测,虽然自然语言处理技术可能在一定程度上处理代码的语法和语义之间的关系,但是并没有对代码的结构和语义进行完全编码,例如。无法识别“for”和“while”之间的语义关系。虽然检测性能有一定程度的提高,但是仍没有达到人们的预期。
针对以上问题,本文提出了一种新的基于DeepTree的缺陷预测模型。利用LSTM长短期记忆网络(可以看到,我们最近精读的论文中,都使用了这种方法,所以这种方法值得学习,最近找时间好好学一下这个算法)。捕获代码中的长期上下文关系,因为有依赖的代码关系可能分散的很远。利用抽象语法树(AST)来表示代码中的语法和不同层次的语义。所以说LSTM的网络结构也是树状的,其中一个AST节点对应于一个LSTM单元。本文所作贡献如下:
本文随后结构如下:
下图显示了两个用Java编写的简单示例。这就是很简单的栈的弹出功能,可以看到列表1的缺陷:如果给定 s t a c k stack stack的大小小于 10 10 10,当 s t a c k stack stack为空并执行 p o p pop pop操作时,可能会发生下溢异常。而列表2通过在调用 p o p pop pop操作之前检查 s t a c k stack stack是否不是空的来纠正这个问题。
通过以上示例,可以看到现有的缺陷预测技术面临的问题:
类似的软件度量:两个代码列表在代码行、条件、变量、循环和分支的数量方面是相同的。所以无法将软件度量作为特征,因为我们无法判断是否有缺陷。
类似的代码标记和频率:此方法将术语-频率用作缺陷预测的预测因子。例如 i n t int int出现的频率。但是如上图所示:两个代码列表中的代码标记及其频率是相同的。所以我们也不能利用这个方法判断是否有缺陷。
句法和语义结构:我们应该如何构建分散距离较远的代码间的语义是目前面临的问题,因为代码元素不总是遵循特定的顺序,例如,在代码列表1中,可以在不改变代码行为的情况下交换第5行和第6行。
语义代码标记:每个代码元素都有自己的语义,而且有些代码元素在语义上是相似的,比如上述代码列表中的 w h i l e while while循环可以替换为 f o r for for循环,而不改变代码行为。而现有的方法经常忽略代码标记的那些语义。
针对以上问题,本文使用抽象语法树(AST)来表示源代码中的语法和不同层次的语义,利用基于DeepTree的LSTM神经网络,以对源代码的抽象语法树进行建模,可以有效地保留代码的语法和语义信息,从而用于缺陷检测。
最终训练出来的模型返回值还是和之前一样:1表示有缺陷,0表示无缺陷。也就是二分类分类函数 p r e d i c t ( x ) predict(x) predict(x)。
此模型基于长短期记忆网络,但是此模型为LSTM单元的树状结构网络,可以高效的反应源码中的语法和多层次语义。不仅可以在项目内预测还可以跨项目预测缺陷。此模型的关键步骤如下所示:
具体包括三个主要步骤:
在将源代码解析为抽象语法树(AST)时,对以下内容忽略:
对于如何解析源代码可见下图。其中AST的根表示一个完整的源文件,如图中的 w h i l e while while循环,循环内部元素和判断条件都作为子节点出现,其中要注意常量整数、实数、指数表示法、十六进制数和字符串表示为其类型的AST节点,而不是其本身,例如下图中的整数 10 10 10表示为 I n t e g e r L i t e r a l E x p r IntegerLiteralExpr IntegerLiteralExpr节点。
按照此标准对语料库中的所有源代码构建为AST树,从而形成词汇表。固定大小的词汇表 V \mathscr{V} V基于出现次数前 N N N个(流行)的标记构建,将出现次数少(不流行)的标记分配给 ⟨ u n k ⟩ ⟨unk⟩ ⟨unk⟩。
因为LSTM单元只接受向量,所以要将每个AST节点映射为固定长度的连续向量,此嵌入过程称为 a s t 2 v e c ast2vec ast2vec。此过程需要使用到嵌入矩阵:
M ∈ R d × ∣ V ∣ \mathcal{M} \in \mathbb{R}^{d \times|\mathscr{V}|} M∈Rd×∣V∣
可见图2, W h i l e S t m t WhileStmt WhileStmt节点被嵌入到向量 [ − 0.3 , − 0.6 , 0.7 ] [−0.3, −0.6,0.7] [−0.3,−0.6,0.7]中,而 I n t e g e r L i t e r a l E x p r IntegerLiteralExpr IntegerLiteralExpr映射到向量 [ 0.2 , 0.1 , 0.2 ] [0.2,0.1,0.2] [0.2,0.1,0.2]中。此过程包括两个优点:
缺陷预测的过程可见如下算法:
将源文件解析为AST(第2行)
将AST的根传入LSTM网络获得向量表示 h root \boldsymbol{h}_{\text {root }} hroot (第 3 3 3行)
将此向量传入到传统分类器,得到源文件有缺陷的概率(第4行)
其中的核心步骤为函数 t − l s t m ( ) t-lstm() t−lstm(),此过程的关键步骤可见下图:
获取AST节点 t t t的嵌入 w t \boldsymbol{w}_{t} wt,使用 a s t 2 v e c ast2vec ast2vec(第12行)
获取节点 t t t的所有子节点 C ( t ) C(t) C(t)(第13行)
每个子节点 k ∈ C ( t ) k∈ C(t) k∈C(t)被馈入LSTM单元以获得每个子节点的一对隐藏输出状态和上下文向量 ( h k , c k ) \left(\boldsymbol{h}_{k}, \boldsymbol{c}_{k}\right) (hk,ck)(第14行)
利用以上信息计算父节点的一对隐藏输出状态和上下文向量 ( h k , c k ) \left(\boldsymbol{h}_{k}, \boldsymbol{c}_{k}\right) (hk,ck)(第16行)
需要注意的是,一个LSTM单元由输入门(表示为 i t \boldsymbol{i}_{t} it)、输出门( o t \boldsymbol{o}_{t} ot)和多个遗忘门(每个子节点 k k k的一个 f t k \boldsymbol{f}_{tk} ftk)构成,其中的参数意义如下所示:
之后就要利用以上信息计算父节点的一对隐藏输出状态和上下文向量 ( h k , c k ) \left(\boldsymbol{h}_{k}, \boldsymbol{c}_{k}\right) (hk,ck):
使用无监督学习方式训练树-LSTM单元,利用子代的标签名称预测父代的标签名称。比如“<”和“VariableDeclarator”的父级是“WhileStmt”,而“x”和“IntegerLiteralExpr”的父亲是“<”。
在补充一下有监督学习和无监督学习的概念:
利用AST节点的 w t \boldsymbol{w}_{t} wt的每个子节点 c k ∈ C ( t ) c_{k}∈ C(t) ck∈C(t)的输出状态 h k h_{k} hk和以下公式来预测父节点的标签名称:
P ( w t = w ∣ w c 1.. c k ) = exp ( U t h ~ t ) ∑ w ′ exp ( U w ′ h ~ t ) P\left(w_{t}=w \mid w_{c 1 . . c k}\right)=\frac{\exp \left(U_{t} \tilde{h}_{t}\right)}{\sum_{w^{\prime}} \exp \left(U_{w^{\prime}} \tilde{h}_{t}\right)} P(wt=w∣wc1..ck)=∑w′exp(Uw′h~t)exp(Uth~t)
其中 U k U_k Uk是一个自由参数并且 h ~ t = 1 ∣ C ( t ) ∣ ∑ k = 1 ∣ C ( t ) ∣ h k \tilde{h}_{t}=\frac{1}{|C(t)|} \sum_{k=1}^{|C(t)|} h_{k} h~t=∣C(t)∣1∑k=1∣C(t)∣hk
后面的工作就是随机初始化参数进行模型的训练,我们需要随机初始化的参数包括:嵌入矩阵 M \mathcal{M} M和权重矩阵( W f o r W_{for} Wfor、 U f o r U_{for} Ufor、 b f o r b_{for} bfor)、( W i n W_{in} Win、 U i n U_{in} Uin、 b i n b_{in} bin)、( W c e W_{ce} Wce、 U c e U_{ce} Uce、 b c e b_{ce} bce)和( W o u t W_{out} Wout、 U o u t U_{out} Uout、$ b_{out}$)。训练过程具体分为三步:
为了使模型训练精度提高,作者定义损失函数 L ( θ ) L(θ) L(θ),如何将损失函数 L ( θ ) L(θ) L(θ)的值降到最小呢?本文为了解决这个问题采用了RMSprop梯度下降方法与反向传播,在训练的过程中需要注意:
为了防止过拟合,本方法还采用了dropout机制,最终确定了dropout率设置为0.5,也就是说:在前向传播的时候,让某个神经元的激活值以0.5的概率停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。最终使用困惑度来作为选择最佳模型和提前停止的评估指标。
在这里补充困惑度的概念:困惑度是用来衡量语言概率模型优劣的一个方法,困惑度越小,我们想要的句子出现的概率越大,语言模型越好。假设 W = w 1 w 2 . . . w N W=w_{1}w_{2}...w_{N} W=w1w2...wN为测试集,困惑度的计算公式如下所示:
PP ( W ) = P ( w 1 w 2 … w N ) − 1 N = 1 P ( w 1 w 2 … w N ) N = ∏ i = 1 N 1 P ( w i ∣ w 1 … w i − 1 ) N = ∏ i = 1 N 1 P ( w i ∣ w i − 1 ) N \begin{aligned}\operatorname{PP}(W) &=P\left(w_{1} w_{2} \ldots w_{N}\right)^{-\frac{1}{N}} \\&=\sqrt[N]{\frac{1}{P\left(w_{1} w_{2} \ldots w_{N}\right)}} \\&=\sqrt[N]{\prod_{i=1}^{N} \frac{1}{P\left(w_{i} \mid w_{1} \ldots w_{i-1}\right)}} \\&=\sqrt[N]{\prod_{i=1}^{N} \frac{1}{P\left(w_{i} \mid w_{i-1}\right)}}\end{aligned} PP(W)=P(w1w2…wN)−N1=NP(w1w2…wN)1=Ni=1∏NP(wi∣w1…wi−1)1=Ni=1∏NP(wi∣wi−1)1
通过以上过程已经可以自动为训练集中的所有源文件生成特征。然后使用这些特征训练分类器,从而得到缺陷预测模型,在这里选择Logistic回归和随机森林作为分类器,最终可以通过分类器的到缺陷预测结果。
本文所使用的数据集分别来自Samsung贡献的开源项目和PROMISE数据集。
需要了解的是,本文的数据集有缺陷的文件数量较少,导致数据集不平衡,所以最终评估的重点集中在缺陷类,因为预测有缺陷的文件更有意义。评测性能时我们要明白几个概念:
有了以上基本概念后,本文提出了以下四种评测指标用于评价最终训练出的模型的性能:
p r = t p t p + f p p r=\frac{t p}{t p+f p} pr=tp+fptp
r e = t p t p + f n r e=\frac{t p}{t p+f n} re=tp+fntp
F − measure = 2 ∗ p r ∗ r e p r + r e F-\text { measure }=\frac{2 * p r * r e}{p r+r e} F− measure =pr+re2∗pr∗re
对于同一Samsung项目数据集的训练和测试的性能如下所示,使用Logistic回归和随机森林作为分类器。可以看到使用随机森林(RF)作为分类器的四个性能指标都远高于0.9,而使用Logistic回归(LR)实现了非常高的召回率,两种分类器的AUC均远高于0.5阈值(RF为0.98,RF为0.60)。这表明了本文提出的模型性能较好。
对于只使用Logistic回归(LR)作为分类器在Samsung数据集的不同数据上进行的测试性能结果如下所示,可以看到此方法在每个数据集上的召回率都是较高的。作者也提出:在预测缺陷时,高召回率通常是优选的,因为遗漏缺陷的成本远高于误报。
对于跨项目预测的性能测试结果如下所示,可以看到22个案例的平均召回率为0.8,有15例召回率高于0.8,平均F-度量为0.5,平均AUC仍远高于0.5阈值。证明了本文提出的方法在预测缺陷方面的总体有效性。
在模型的测试阶段,遇到以下几方面问题:
这些问题都对本文提出的模型的有效性有一定的影响,当然,这也是未来要解决的问题。
在此部分作者总结了目前缺陷检测的一些研究,尤其是在关于缺陷检测的特征方面所做的研究内容,比如静态代码特征和过程特征
静态代码特征又可以细分为代码大小和代码复杂度,其中代码复杂度包括:
过程特征是指测量发布开发过程中的更改活动,以便构建更准确的缺陷预测模型。
同时作者对于项目内预测和跨项目预测也做了简要总结,这对未来的研究工作也十分重要
同时作者介绍了一种称为深度信念网络(DBN)的深度学习模型来自动学习缺陷预测的特征,虽然对于预测的性能有所改进,并且DBN方法优于软件度量和单词包方法。但是DBN不能自然地捕获源代码中的顺序排列和长期依赖关系,这也导致对预测的准确性有一定的影响。为了解决这个问题可以开发包含带有缺陷标签的方法和代码行的新数据集,这也指明了未来的工作方向。
在此部分作者主要说明了深度学习应用于软件缺陷检测的研究进展和前景,同时介绍了一些方法,比如:
以上方法都可以应用到软件缺陷检测,但是经过作者的工作表明,LSTM是一种更有效的软件缺陷检测模型。
本文中作者提出了一种新的模型,将源代码表示为抽象语法树(AST)作为长短期记忆(LSTM)网络的输入,再经过传统分类器进行分类以得到软件缺陷预测结果。同时使用Samsung和PROMISE两个不同的数据集对此模型进行性能评估,评测结果表示此模型性能良好,可以应用于实践。本论文所做的贡献包括:
同时作者也指明了未来工作的方向:
以上就是本篇阅读笔记的全部内容。本文使用源代码的抽象语法树(AST)结合长短期记忆(LSTM)网络的方法解决软件缺陷预测问题。基本思路就是将源代码表示为抽象语法树(AST)作为长短期记忆(LSTM)网络的输入,再经过传统分类器进行分类以得到软件缺陷预测结果。
本文所提出的方法优点包括:
本文所提出的方法缺点包括: