董玉坤,李浩杰,位欣欣,唐道龍
中國(guó)石油大學(xué)(華東)計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院,山東 青島 266580
在軟件開(kāi)發(fā)過(guò)程中,不可避免地會(huì)引入各種缺陷,有些缺陷僅僅會(huì)造成軟件的部分中止,對(duì)整個(gè)系統(tǒng)的運(yùn)行影響不大,但有的缺陷則會(huì)造成嚴(yán)重的功能性、安全性等錯(cuò)誤,影響系統(tǒng)安全。因此,在軟件開(kāi)發(fā)過(guò)程中進(jìn)行軟件缺陷預(yù)測(cè)顯得至關(guān)重要。軟件缺陷預(yù)測(cè)需要全面分析程序的語(yǔ)法、語(yǔ)義等特征,如果進(jìn)行人工檢測(cè)會(huì)耗費(fèi)大量的人力和時(shí)間,因此構(gòu)建軟件缺陷預(yù)測(cè)模型自動(dòng)進(jìn)行軟件缺陷預(yù)測(cè)成為了一個(gè)熱門的研究方向。
傳統(tǒng)的機(jī)器學(xué)習(xí)方法將軟件缺陷預(yù)測(cè)問(wèn)題轉(zhuǎn)化為一個(gè)構(gòu)建分類器的問(wèn)題[1-6],通過(guò)手工定義特征訓(xùn)練分類器,用訓(xùn)練好的分類器預(yù)測(cè)可能包含缺陷的代碼區(qū)域,這個(gè)區(qū)域可以是文件、類或者方法。許多機(jī)器學(xué)習(xí)算法都可以完成這項(xiàng)分類工作,例如決策樹(shù)(decision tree)[7]、隨機(jī)森林(random forest)[8]、樸素貝葉斯(naive Bayes)[9],但均不能獲得較高的準(zhǔn)確率,并且手工定義特征也是一個(gè)費(fèi)時(shí)費(fèi)力的過(guò)程。
深度學(xué)習(xí)作為機(jī)器學(xué)習(xí)的一個(gè)分支,近年來(lái)被應(yīng)用于軟件缺陷預(yù)測(cè)領(lǐng)域,并且取得了良好的效果。與傳統(tǒng)的機(jī)器學(xué)習(xí)方法相比,深度學(xué)習(xí)通過(guò)搭建神經(jīng)網(wǎng)絡(luò)自動(dòng)提取代碼特征,可以獲得更高的準(zhǔn)確率。但是,現(xiàn)有的研究往往只在同一粒度上提取特征,比如對(duì)每行代碼做映射表示整個(gè)程序[10]。該方法可以將代碼轉(zhuǎn)換為輸入樣本,但是會(huì)丟失很多代碼的細(xì)粒度信息,不能預(yù)測(cè)出所有類型的缺陷。
程序的抽象語(yǔ)法樹(shù)(abstract syntax tree,AST)可以清晰地展示出程序各個(gè)節(jié)點(diǎn)之間的層次關(guān)系,利用這種層次關(guān)系對(duì)程序結(jié)構(gòu)的表示,可以精確地描述代碼的結(jié)構(gòu)信息,獲取結(jié)構(gòu)特征與簡(jiǎn)單的語(yǔ)義特征。同時(shí),程序的Token序列可以在更細(xì)的粒度上表示程序,其中蘊(yùn)含著豐富的語(yǔ)義信息,通過(guò)將程序轉(zhuǎn)換為Token 序列,對(duì)Token序列進(jìn)行特征提取,可以準(zhǔn)確地理解程序語(yǔ)義,獲取語(yǔ)義特征與部分結(jié)構(gòu)特征。如圖1 所示的Java 代碼片段,通過(guò)將其解析為AST,分析AST 上的節(jié)點(diǎn)可以清楚地發(fā)現(xiàn),在第3、4行的remove和add方法的先后順序會(huì)引起NoSuchElementException異常,但是并不能發(fā)現(xiàn)第5行for循環(huán)條件語(yǔ)句中“i--”引起的死循環(huán)缺陷,因?yàn)槭蹵ST上的節(jié)點(diǎn)粒度所限,并不能提取到循環(huán)條件中的語(yǔ)義信息。而通過(guò)將其解析為Token序列,可以有效地發(fā)現(xiàn)這個(gè)死循環(huán)缺陷,因?yàn)門oken 序列是在Token 級(jí)別表示程序,蘊(yùn)含了程序的語(yǔ)義特征。
圖1 一個(gè)有代表性的例子Fig.1 A representative example
軟件缺陷預(yù)測(cè)按照預(yù)測(cè)粒度劃分為方法級(jí)、類級(jí)、文件級(jí)等,本文在文件級(jí)進(jìn)行軟件缺陷預(yù)測(cè)。本文提出了一個(gè)軟件缺陷預(yù)測(cè)框架2T-CNN(TBCNN and TextCNN),使用該框架,可以在軟件版本迭代時(shí)快速預(yù)測(cè)新版本中可能包含代碼缺陷的文件,向代碼審閱者發(fā)出警告,縮短代碼審查的時(shí)間。2T-CNN 基于卷積方式的不同,通過(guò)樹(shù)卷積神經(jīng)網(wǎng)絡(luò)(tree-based convolutional neural network,TBCNN)[11]從程序的AST節(jié)點(diǎn)中提取結(jié)構(gòu)特征,通過(guò)文本卷積神經(jīng)網(wǎng)絡(luò)(text convolutional neural networks,TextCNN)[12]從程序的Token 序列中提取語(yǔ)義特征,將兩個(gè)模型提取到的特征融合后,輸入全連接神經(jīng)網(wǎng)絡(luò)(fully connected neural network,F(xiàn)CNN),經(jīng)過(guò)邏輯回歸分類器,輸出程序是否含有缺陷。本文對(duì)所提出的方法進(jìn)行實(shí)現(xiàn),并針對(duì)開(kāi)源Java項(xiàng)目進(jìn)行實(shí)驗(yàn)評(píng)估,結(jié)果表明,該方法能夠有效提升軟件缺陷預(yù)測(cè)的準(zhǔn)確率。
利用深度學(xué)習(xí)技術(shù)預(yù)測(cè)軟件缺陷是近幾年興起的新穎的方法,在預(yù)測(cè)方法的整體流程上和傳統(tǒng)機(jī)器學(xué)習(xí)方法相似。不同之處在于深度學(xué)習(xí)技術(shù)可以利用神經(jīng)網(wǎng)絡(luò)從程序代碼中自動(dòng)提取出隱藏特征,這些隱藏特征往往是設(shè)計(jì)判別特征的人想象不到且無(wú)法設(shè)計(jì)出來(lái)的。
使用深度學(xué)習(xí)技術(shù)預(yù)測(cè)程序缺陷的核心思想在于程序代碼已被證實(shí)具有自然語(yǔ)言的相似屬性[13],因此現(xiàn)有的大多數(shù)基于深度學(xué)習(xí)的軟件缺陷預(yù)測(cè)技術(shù)都將程序代碼視為自然語(yǔ)言文本,將軟件缺陷預(yù)測(cè)轉(zhuǎn)化為文本分類任務(wù),從自然語(yǔ)言處理的角度對(duì)文本進(jìn)行缺陷分類。例如,Li等人[14]提出了一個(gè)基于深度學(xué)習(xí)的軟件漏洞檢測(cè)系統(tǒng)VulDeePecker,該方法使用雙向長(zhǎng)短時(shí)記憶網(wǎng)絡(luò)(bi-directional long short-term memory,Bi-LSTM)在Token級(jí)別提取到程序的語(yǔ)義特征,但無(wú)法提取到代碼中其他豐富的特征,例如結(jié)構(gòu)特征。該文的另一個(gè)重大貢獻(xiàn)是提供了一個(gè)可供深度學(xué)習(xí)訓(xùn)練使用的軟件安全漏洞數(shù)據(jù)集[15]。Zou等人[16]在VulDeePecker的基礎(chǔ)上提出了μVulDeePecker,它基于代碼注意力和控制依賴,結(jié)合新的神經(jīng)網(wǎng)絡(luò)building-block Bi-LSTM融合不同的特征,實(shí)現(xiàn)了對(duì)具體漏洞類型的檢測(cè)。Duan等人[17]為了更好地捕捉程序源代碼的關(guān)鍵特征信息,提出了一種基于代碼屬性圖及注意力Bi-LSTM 的漏洞挖掘方法,與基于規(guī)則的靜態(tài)挖掘工具Flawfinder和RATS相比有顯著的提高。
為了更好地提取代碼的結(jié)構(gòu)特征,有很多研究將深度學(xué)習(xí)技術(shù)應(yīng)用在AST上。Wang等人[18]利用深度置信網(wǎng)絡(luò)(deep belief network,DBN)[19]從程序的AST 中提取符號(hào)向量來(lái)學(xué)習(xí)語(yǔ)義特征,在效果上優(yōu)于傳統(tǒng)的基于特征的方法,但并沒(méi)有考慮程序的結(jié)構(gòu)特征。Li等人[20]為了充分考慮程序代碼的結(jié)構(gòu)特征,對(duì)AST中具有代表性的節(jié)點(diǎn)進(jìn)行映射,結(jié)合傳統(tǒng)的缺陷特征訓(xùn)練判別器,取得了比DBN 更好的效果。但正如該文中的例子所示,將for循環(huán)的整個(gè)條件部分映射為一個(gè)整數(shù),顯然無(wú)法發(fā)現(xiàn)在條件語(yǔ)句中的缺陷。另外,這種方法仍然需要手工定義的特征。Dam 等人[21]的研究是對(duì)AST 中的每個(gè)節(jié)點(diǎn)進(jìn)行映射,將一個(gè)個(gè)AST分支輸入基于樹(shù)的LSTM單元中訓(xùn)練模型。這種方法可以充分考慮到AST 中的結(jié)構(gòu)特征,對(duì)于發(fā)現(xiàn)特定的序列模式也有很大的幫助。值得一提的是,該方法使用無(wú)監(jiān)督的方式訓(xùn)練LSTM單元,因此不需要大量的數(shù)據(jù)集標(biāo)記成本。Pan 等人[22]對(duì)DP-CNN[20]進(jìn)行改進(jìn),提出了Improved-CNN,獲得了更好的效果,并且探究了缺陷預(yù)測(cè)模型中超參數(shù)的不穩(wěn)定性。
下面詳細(xì)介紹了本文提出的2T-CNN,它可以自動(dòng)從源代碼中提取代碼的結(jié)構(gòu)和語(yǔ)義特征,對(duì)源代碼是否具有缺陷進(jìn)行預(yù)測(cè)。圖2 展示了2T-CNN 的整體框架,通過(guò)以下5 個(gè)步驟對(duì)程序代碼進(jìn)行缺陷預(yù)測(cè):(1)源代碼解析;(2)詞嵌入;(3)特征提?。唬?)特征融合;(5)缺陷預(yù)測(cè)。
圖2 2T-CNN整體工作流程圖Fig.2 Overall workflow of 2T-CNN
程序源代碼中蘊(yùn)含著豐富的特征,軟件缺陷預(yù)測(cè)就是從程序源代碼中挖掘這些特征用于預(yù)測(cè)程序缺陷。但是,從原始的源代碼結(jié)構(gòu)中提取特征是困難的,同時(shí)源代碼中所蘊(yùn)含的特征并不都是對(duì)缺陷預(yù)測(cè)有價(jià)值的,例如注釋文本。因此,需要將源代碼解析為合適的結(jié)構(gòu)和粒度,并且去除其中的冗余特征以提取有利于缺陷預(yù)測(cè)的特征。
2T-CNN 的源代碼解析分為兩部分,一部分是將源代碼解析為AST,另一部分是將源代碼的文本轉(zhuǎn)換為Token 序列,兩種程序表示形式分別對(duì)應(yīng)了兩個(gè)特征提取網(wǎng)絡(luò)的輸入,具體步驟將在下面的內(nèi)容中詳細(xì)介紹。
2.1.1 解析AST
程序代碼具有嚴(yán)格的結(jié)構(gòu)信息,這種結(jié)構(gòu)信息在AST上可以得到具體體現(xiàn)。AST是樹(shù)狀結(jié)構(gòu),可以準(zhǔn)確描述每個(gè)節(jié)點(diǎn)與其相關(guān)節(jié)點(diǎn)的結(jié)構(gòu)關(guān)系,因此本文將程序源代碼解析為AST來(lái)提取源代碼的結(jié)構(gòu)特征。
本文使用javalang[23]將源代碼解析為AST,該工具基于Java 語(yǔ)言規(guī)范對(duì)源代碼進(jìn)行解析。使用該工具可以方便地將源代碼解析為AST,并提取出AST上的各個(gè)節(jié)點(diǎn)用以解析程序。例如,ClassDeclaration節(jié)點(diǎn)代表類定義,F(xiàn)orStatement 節(jié)點(diǎn)代表for 循環(huán),它的兩個(gè)子節(jié)點(diǎn)ForControl和BlockStatement構(gòu)成了for循環(huán)的條件語(yǔ)句和循環(huán)體。
因?yàn)樾鑼?duì)完整的AST進(jìn)行卷積操作,為了盡可能保留程序中所有的結(jié)構(gòu)信息,舍棄了Import節(jié)點(diǎn)、Package節(jié)點(diǎn),還有注釋節(jié)點(diǎn)等對(duì)缺陷預(yù)測(cè)沒(méi)有作用的節(jié)點(diǎn),對(duì)其他所有的節(jié)點(diǎn)類型進(jìn)行了保留。此外,由于文獻(xiàn)[11]使用的方法僅僅關(guān)注了AST節(jié)點(diǎn)類型信息,沒(méi)有對(duì)不同的變量和方法作區(qū)分,因此本文對(duì)生成的AST進(jìn)行了更改。具體來(lái)說(shuō),將方法調(diào)用、變量引用替換為它們的值。例如add函數(shù)和remove函數(shù)都屬于MethodInvocation節(jié)點(diǎn),但它們具有不同的作用。
在獲得了程序的AST之后,構(gòu)建一個(gè)二維列表來(lái)表示整個(gè)AST。其中,第一維的索引代表相對(duì)應(yīng)的節(jié)點(diǎn),第二維中的元素代表該節(jié)點(diǎn)的所有子節(jié)點(diǎn)的映射,整個(gè)二維列表就可以用來(lái)表示一段代碼的AST。二維列表的表示形式如下:
其中,nodem表示AST 中的節(jié)點(diǎn),childn表示nodem的子節(jié)點(diǎn)。以圖1 中的代碼片段為例,圖3(a)展示了for循環(huán)的整個(gè)AST結(jié)構(gòu),圖3(b)是它所對(duì)應(yīng)的二維列表。
圖3 圖1中for循環(huán)的AST及其二維列表Fig.3 AST of for loop and its two-dimension list in Fig.1
2.1.2 解析Token序列
程序語(yǔ)言已經(jīng)被證實(shí)具有自然語(yǔ)言的特點(diǎn),因此可以將程序語(yǔ)言視為自然語(yǔ)言文本來(lái)提取代碼的語(yǔ)義特征。通過(guò)詞法分析將程序代碼解析為Token 序列可以最大程度地保留代碼中的語(yǔ)義信息,同時(shí)Token序列也是最符合自然語(yǔ)言文本的表示形式,使模型可以更有效地學(xué)習(xí)到代碼中的語(yǔ)義特征。
本文將程序代碼解析為Token 序列的偽代碼表示如下:
具體來(lái)說(shuō),刪除的冗余代碼包括導(dǎo)包、打包、單行注釋、多行注釋、行尾注釋等對(duì)于缺陷預(yù)測(cè)沒(méi)有任何幫助的代碼,并且它們本身的語(yǔ)義也會(huì)影響預(yù)測(cè)結(jié)果。借助javalang 工具中tokenizer 模塊對(duì)代碼進(jìn)行分割,得到程序的Token列表。本文舍棄了類型為Separator的Token,因?yàn)閷?duì)于TextCNN 來(lái)說(shuō),輸入樣本的長(zhǎng)度要一致,長(zhǎng)度過(guò)大會(huì)消耗更多資源,也不利于模型收斂,并且Separator類型的Token雖然蘊(yùn)含一定的程序結(jié)構(gòu)特征,但它沒(méi)有AST中蘊(yùn)含的結(jié)構(gòu)特征明顯,同時(shí)也會(huì)干擾TextCNN對(duì)代碼語(yǔ)義特征的提取。對(duì)于類型為Identifier 的自定義標(biāo)識(shí)符建立映射集M并進(jìn)行值替換,通過(guò)對(duì)其進(jìn)行標(biāo)準(zhǔn)化可以在不影響程序語(yǔ)義和結(jié)構(gòu)的情況下,減少詞匯量,加快詞嵌入的訓(xùn)練速度,同時(shí)有效降低不同自定義標(biāo)識(shí)符帶來(lái)的影響。映射機(jī)制描述如下:
對(duì)于圖1例子中的for循環(huán),舍棄其類型為Separator的Token,同時(shí)對(duì)自定義標(biāo)識(shí)符i、add、remove進(jìn)行替換,提取得到的Token序列為:<for,int,identifier1,=,Integer,identifier1,‘<’,Integer,identifier1,++,identifier2,identifier3,Integer,identifier2,identifier4,Integer>。
類比計(jì)算機(jī)視覺(jué)領(lǐng)域?qū)D片轉(zhuǎn)換為[0,255]的像素值矩陣作為神經(jīng)網(wǎng)絡(luò)的輸入,2T-CNN 也需要將程序代碼文本轉(zhuǎn)換為數(shù)值向量輸入到神經(jīng)網(wǎng)絡(luò)中,本文選用word2vec[24]來(lái)進(jìn)行詞嵌入處理。
word2vec是一種利用BP神經(jīng)網(wǎng)絡(luò)來(lái)自動(dòng)學(xué)習(xí)詞語(yǔ)表示的工具,區(qū)別于獨(dú)熱編碼,它可以將詞語(yǔ)映射到一個(gè)固定長(zhǎng)度的向量中,同時(shí)還可以保留詞語(yǔ)的語(yǔ)義特征,因?yàn)榻?jīng)過(guò)訓(xùn)練后,語(yǔ)義相近的詞語(yǔ)在向量空間的分布上很接近。本文使用Gensim工具包提供的word2vec模塊進(jìn)行詞嵌入處理。
對(duì)于程序的兩種表示方式,分別在兩個(gè)語(yǔ)料庫(kù)上進(jìn)行訓(xùn)練。在AST 表示方式中,將2.1.1 小節(jié)得到的AST中的每個(gè)節(jié)點(diǎn)替換為它的詞向量,構(gòu)建詞嵌入AST。在Token 序列表示方式中,訓(xùn)練獲取每個(gè)出現(xiàn)次數(shù)超過(guò)5次的Token 的詞向量,出現(xiàn)次數(shù)過(guò)少的Token 不會(huì)對(duì)預(yù)測(cè)結(jié)果造成較大影響。對(duì)于TextCNN來(lái)說(shuō),輸入矩陣的形狀必須相同,通過(guò)觀察數(shù)據(jù)集中所有文件的Token序列長(zhǎng)度,統(tǒng)計(jì)它們的平均數(shù)和中位數(shù),最終將Token 序列長(zhǎng)度設(shè)置為250,超過(guò)此長(zhǎng)度部分舍棄,不足此長(zhǎng)度的補(bǔ)0。設(shè)置詞向量長(zhǎng)度為50,最終構(gòu)建了大小為(250,50)的輸入矩陣。
經(jīng)過(guò)兩種訓(xùn)練方法訓(xùn)練后,獲得了具有詞嵌入編碼的AST和Token序列矩陣,根據(jù)源代碼文件編號(hào)將相同程序的詞嵌入AST 和Token 序列矩陣一一對(duì)應(yīng),構(gòu)建(tb_nodes,tb_ast,tx_input|label)輸入樣本。其中tb_nodes是AST節(jié)點(diǎn)的詞向量矩陣,tb_ast是代表整個(gè)AST的二維列表,tx_input是Token序列的向量矩陣,label是標(biāo)簽矩陣,表示程序是否具有缺陷。
2.3.1 基于TBCNN的結(jié)構(gòu)特征提取
本文使用的TBCNN 如圖4 所示,它包含一個(gè)基于樹(shù)的卷積層(tree-based convolution)、一個(gè)池化層(max pooling)以及一個(gè)全連接層(fully connected)。圖4(a)是經(jīng)過(guò)詞嵌入的AST,虛線三角形框表示卷積核,卷積核在AST 的每一個(gè)節(jié)點(diǎn)上滑動(dòng)后得到圖4(b)中的特征圖。
圖4 2T-CNN中提取結(jié)構(gòu)特征所用TBCNNFig.4 TBCNN for extracting structural features from 2T-CNN
在卷積層,卷積核的深度設(shè)置為2,權(quán)重矩陣Wconv由三個(gè)矩陣的線性組合構(gòu)成,它們的系數(shù)分別是使用LeakyReLU作為激活函數(shù),它可以有效解決Tanh 造成的梯度消失問(wèn)題,并且加快模型收斂速度。在一次卷積中,假設(shè)卷積窗口中有n個(gè)節(jié)點(diǎn)x1,x2,…,xn,卷積過(guò)程可以由式(3)來(lái)表示:
其中,權(quán)重系數(shù)wconv,i可由式(4)確定:
其中,d表示卷積核的深度,di表示節(jié)點(diǎn)xi在卷積核窗口中的深度,pi表示節(jié)點(diǎn)xi的位置,n表示節(jié)點(diǎn)xi的所有子節(jié)點(diǎn)數(shù)量。由式(4)可以看出權(quán)重系數(shù)是由節(jié)點(diǎn)在卷積核窗口中的相對(duì)位置計(jì)算得到,可以合理地反映出各節(jié)點(diǎn)間的位置關(guān)系。整個(gè)卷積層的參數(shù)量為15 100個(gè),復(fù)雜度為O( )nodes×d×c,其中nodes表示AST 節(jié)點(diǎn)數(shù)量,d表示詞向量長(zhǎng)度,c表示卷積核個(gè)數(shù)。
在池化層,使用MaxPooling 進(jìn)行池化,保留主要特征并減少參數(shù)。為了簡(jiǎn)化特征融合的過(guò)程,添加了一個(gè)全連接層使TBCNN 最終輸出的特征向量長(zhǎng)度和TextCNN相同,在本文中,TBCNN輸出的特征向量長(zhǎng)度被設(shè)置為100。
2.3.2 基于TextCNN的語(yǔ)義特征提取
程序代碼的Token序列是由源代碼解析而來(lái),相比于其他的程序表示方式,它在Token 級(jí)別表示程序,具有最細(xì)的粒度,因此其中蘊(yùn)含著豐富的語(yǔ)義信息。本文沒(méi)有選擇文獻(xiàn)[16]中使用的Bi-LSTM來(lái)提取Token序列中的語(yǔ)義特征,主要原因有兩點(diǎn):一是因?yàn)殡m然TextCNN在聯(lián)系文本上下文的表現(xiàn)上不如Bi-LSTM,但它可以通過(guò)設(shè)置多個(gè)不同大小的卷積核,抓住文本多個(gè)不同的ngram 特征,并且對(duì)于同一個(gè)n-gram 特征也會(huì)從不同角度去提取關(guān)鍵的信息;二是因?yàn)樵?.2節(jié),將Token序列長(zhǎng)度設(shè)置為250,Bi-LSTM在處理長(zhǎng)文本的訓(xùn)練速度上比TextCNN慢得多。因此在2T-CNN中選擇使用TextCNN來(lái)提取代碼的語(yǔ)義特征。
本文設(shè)計(jì)實(shí)現(xiàn)的TextCNN 如圖5 所示,它將Token序列的詞嵌入矩陣(embedding matrix)作為輸入,包含兩個(gè)卷積層(convolutional layer)和池化層(pooling layer),最后連接一個(gè)扁平層(flatten layer)。詞嵌入矩陣是一個(gè)s×d的矩陣,其中s代表樣本中Token 序列的長(zhǎng)度,d代表Token 的詞向量長(zhǎng)度,整個(gè)矩陣就表示了一條樣本。在卷積層中,通過(guò)設(shè)置多個(gè)寬度不同,長(zhǎng)度等于詞向量長(zhǎng)度d的卷積核,保證卷積核每次滑動(dòng)過(guò)的位置都是完整的單詞,從而更全面地提取Token序列中的語(yǔ)義信息,每個(gè)卷積核的卷積過(guò)程可以用式(5)表示:
圖5 2T-CNN中提取語(yǔ)義特征所用TextCNNFig.5 TextCNN for extracting semantic features from 2T-CNN
其中,W∈?h×d,A∈?s×d,A[i:i+h-1] 表示詞嵌入矩陣A的第i行到第i+h-1 行,卷積核在每一個(gè)嵌入矩陣上從上到下滑動(dòng)提取程序的語(yǔ)義特征。兩個(gè)卷積層的參數(shù)量為24 550 個(gè),復(fù)雜度為,其中l(wèi)表示卷積層數(shù),hl表示卷積核寬度,sl-1表示序列長(zhǎng)度,dl表示卷積核個(gè)數(shù)。
在池化層,采用MaxPooling進(jìn)行池化操作。在扁平層,使用Flatten方法將所有特征圖展開(kāi)堆疊成一個(gè)長(zhǎng)度固定的特征向量。在本文中,TextCNN輸出的特征向量長(zhǎng)度和TBCNN保持一致,設(shè)置為100。
使用TBCNN 和TextCNN 獲取到的不同層面的代碼特征均是高維且抽象的,雖然它們的側(cè)重點(diǎn)不同,但它們并不是相互獨(dú)立的,因此采用特征融合的方式將這些高維特征融合可以提高預(yù)測(cè)的準(zhǔn)確率。
常見(jiàn)的特征融合方式很多,例如取Average、Maximum,本文使用的特征融合方式是Concatenate。一種先驗(yàn)知識(shí)認(rèn)為Concatenate的融合方式可以將從相同程序的兩種表示中提取到的不同特征進(jìn)行強(qiáng)化互補(bǔ),從而提高預(yù)測(cè)的準(zhǔn)確率。在3.4.4小節(jié)通過(guò)實(shí)驗(yàn)驗(yàn)證了Concatenate融合方式相較于Average和Maximum,可以獲得更好的效果。
具體做法是,從TBCNN 和TextCNN 的最后一層輸出中得到特征向量,分別記作tbout、txout,對(duì)tbout和txout進(jìn)行Concatenate操作,將它們拼接為特征向量fuse_vec。
此外,正如圖1 中的例子所示,不同缺陷在不同特征表現(xiàn)上具有不同的強(qiáng)弱度。根據(jù)缺陷類型的不同,為提取到的結(jié)構(gòu)特征和語(yǔ)義特征分配不同的比重,可以提高預(yù)測(cè)的準(zhǔn)確率。人工定義這個(gè)比重缺乏合理性,因此在模型中實(shí)現(xiàn)一個(gè)自定義層,讓網(wǎng)絡(luò)自動(dòng)學(xué)習(xí)特征中每個(gè)元素的系數(shù)。添加的自定義層如圖6所示,分為系數(shù)分支和短路分支。
圖6 用于學(xué)習(xí)結(jié)構(gòu)和語(yǔ)義特征比重系數(shù)的自定義層Fig.6 Defined-self layer for learning structure and semantic features proportion coefficient
在系數(shù)分支,將權(quán)重矩陣Wp初始化為大小和fuse_vec相同的矩陣,將fuse_vec和Wp逐元素相乘,并通過(guò)Sigmoid 函數(shù)進(jìn)行歸一化并對(duì)其加1,防止梯度消失,通過(guò)反向傳播不斷更新Wp的參數(shù)。在短路分支,直接將fuse_vec作為輸出,從而將系數(shù)分支輸出的權(quán)重矩陣以逐元素相乘的方式作用在fuse_vec上。自定義層的輸出O可以用式(6)表示:
其中,?代表逐元素相乘,1代表元素全為1的矩陣。
模型使用小批量隨機(jī)梯度下降算法[25]和Adam優(yōu)化器[26]進(jìn)行訓(xùn)練,以加快訓(xùn)練速度,同時(shí)采用二進(jìn)制交叉熵作為損失函數(shù)。
考慮到邏輯回歸分類器在此類研究中有著廣泛的應(yīng)用[27],因此采用邏輯回歸作為最終的分類器。將融合后的特征向量輸入全連接層,并將全連接層的輸出向量輸入到邏輯回歸分類器中,得到最終的預(yù)測(cè)結(jié)果。
按照上述步驟對(duì)訓(xùn)練集的數(shù)據(jù)進(jìn)行處理,通過(guò)訓(xùn)練集訓(xùn)練模型之后,模型中的所有權(quán)重都是固定的。在缺陷預(yù)測(cè)階段,將沒(méi)有訓(xùn)練過(guò)的樣本經(jīng)過(guò)同樣的數(shù)據(jù)處理過(guò)程,輸入到模型中,通過(guò)分類器得到最終的分類結(jié)果,預(yù)測(cè)程序是否含有缺陷。
下面主要介紹了實(shí)驗(yàn)相關(guān)工作以及對(duì)2T-CNN 的評(píng)估。通過(guò)比較2T-CNN 與其他最新方法在軟件缺陷預(yù)測(cè)方面的準(zhǔn)確性來(lái)評(píng)估其有效性。
本實(shí)驗(yàn)的實(shí)驗(yàn)環(huán)境為:Linux 操作系統(tǒng),i7-10700 CPU,RTX3090 GPU。考慮到神經(jīng)網(wǎng)絡(luò)訓(xùn)練的隨機(jī)性,所有的實(shí)驗(yàn)數(shù)據(jù)均取10次實(shí)驗(yàn)結(jié)果的平均值。
本文使用的數(shù)據(jù)集是PSC(PROMISE source code)數(shù)據(jù)集和一個(gè)工程項(xiàng)目DTSC[28-30]。PSC 來(lái)自軟件工程領(lǐng)域的公共存儲(chǔ)庫(kù)PROMISE[31],是進(jìn)行跨版本軟件缺陷預(yù)測(cè)的常用公開(kāi)數(shù)據(jù)集[32-34]。為了與基線方法比較,使用PSC的子數(shù)據(jù)集SPSC(simplified PROMISE source code)進(jìn)行對(duì)比實(shí)驗(yàn)。SPSC中包含了PSC中7個(gè)項(xiàng)目的兩個(gè)相鄰版本,例如Camel-1.4、Camel-1.6。特別的是,實(shí)驗(yàn)中刪除了PSC中不能通過(guò)編譯的文件,并舍棄了項(xiàng)目名為Pbeans 的項(xiàng)目,因?yàn)樗奈募?shù)過(guò)少,無(wú)法使模型收斂。DTSC是一個(gè)基于Java語(yǔ)言編寫的C語(yǔ)言程序缺陷檢測(cè)工具,可以用于檢測(cè)軟件缺陷,在實(shí)驗(yàn)時(shí)從中挑選3個(gè)最新版本作為DTSC數(shù)據(jù)集。表1列出了兩個(gè)數(shù)據(jù)集的詳細(xì)信息。
將所有文件中的buggy 代碼(即具有缺陷的代碼)標(biāo)記為1記作正樣本,clean代碼(即沒(méi)有缺陷的代碼)標(biāo)記為0 記作負(fù)樣本。如表1 中缺陷文件占比一列所示,可以觀察到一些項(xiàng)目中的缺陷文件占比過(guò)小,例如項(xiàng)目Camel-1.4中缺陷文件占比僅為17.3%,這會(huì)使數(shù)據(jù)集中正負(fù)樣本差距過(guò)大,造成樣本不均衡的問(wèn)題,樣本不均衡會(huì)導(dǎo)致訓(xùn)練出來(lái)的模型泛化能力差并且容易過(guò)擬合。樣本不均衡問(wèn)題在數(shù)據(jù)層面的處理方法通常有三種:上采樣、下采樣和數(shù)據(jù)合成。本實(shí)驗(yàn)中,考慮到樣本數(shù)量較少且不易合成,故對(duì)樣本不均衡項(xiàng)目中的少量樣本進(jìn)行上采樣,隨機(jī)復(fù)制少量樣本,使正負(fù)樣本比例達(dá)到0.4至0.6。
表1 數(shù)據(jù)集詳細(xì)信息Table 1 Dataset details
為了更有效地評(píng)估2T-CNN,僅僅使用準(zhǔn)確率沒(méi)有太大的意義,因?yàn)獒槍?duì)軟件缺陷預(yù)測(cè)的問(wèn)題,通常更關(guān)注的是預(yù)測(cè)出缺陷的那一部分結(jié)果。因此,使用F1 分?jǐn)?shù)(F1-score)來(lái)評(píng)估2T-CNN,它是由精確率(Precision)和召回率(Recall)共同決定的。
在明確Precision、Recall 和F1-score 的定義之前,首先需要定義四個(gè)基本概念:
TP(true positive):預(yù)測(cè)值為正,標(biāo)簽值為正。
FP(false positive):預(yù)測(cè)值為正,標(biāo)簽值為負(fù)。
FN(false negative):預(yù)測(cè)值為負(fù),標(biāo)簽值為正。
TN(true negative):預(yù)測(cè)值為負(fù),標(biāo)簽值為負(fù)。
在本實(shí)驗(yàn)中,將存在缺陷的代碼文件標(biāo)記為正樣本,不存在缺陷的代碼文件標(biāo)記為負(fù)樣本。借助于以上4個(gè)指標(biāo),可以計(jì)算以下3個(gè)指標(biāo):
Precision:正確預(yù)測(cè)的正樣本個(gè)數(shù)與所有預(yù)測(cè)為正樣本個(gè)數(shù)之比。
Recall:正確預(yù)測(cè)的正樣本個(gè)數(shù)與所有標(biāo)簽為正樣本個(gè)數(shù)之比。
F1-score:精確率和召回率的調(diào)和平均值。
從式(7)可以看出Precision越高,誤報(bào)率越低,從式(8)可以看出Recall 越高,漏報(bào)率越低。雖然Precision和Recall 的值越高證明模型的預(yù)測(cè)效果越好,但由于Precision 和Recall 之間的相互影響,不能認(rèn)為其中單一指標(biāo)可以評(píng)估模型的效果,因此引入了F1-score,它是Precision 和Recall 的一個(gè)綜合度量,取值范圍是[0,1]。式(9)顯示了F1-score與Precision和Recall的關(guān)系,只有F1-score的值越高,才能綜合判斷模型的預(yù)測(cè)表現(xiàn)更好。
本文將提出的2T-CNN 軟件缺陷預(yù)測(cè)方法與以下基線方法進(jìn)行比較:
傳統(tǒng)機(jī)器學(xué)習(xí)模型:使用四種機(jī)器學(xué)習(xí)模型作為傳統(tǒng)模型進(jìn)行對(duì)比,分別是邏輯回歸(LR)、樸素貝葉斯(NB)、決策樹(shù)(DT)和隨機(jī)森林(RF),這是四種最常用的機(jī)器學(xué)習(xí)模型。與深度學(xué)習(xí)模型不同的是,傳統(tǒng)機(jī)器學(xué)習(xí)模型使用手工定義特征度量的方式作為輸入。
TBCNN:它取自2T-CNN 的一部分,即將源代碼的AST輸入TBCNN進(jìn)行特征提取并預(yù)測(cè)缺陷。
TextCNN:它同樣取自2T-CNN的一部分,即將源代碼解析為Token序列,通過(guò)TextCNN提取代碼特征輸入到分類器中,預(yù)測(cè)程序缺陷。
DP-CNN[20]:它是一種結(jié)合了手工定義傳統(tǒng)特征的CNN方法,效果優(yōu)于僅僅使用CNN的方法。
Improved-CNN[22]:它是一種改進(jìn)的CNN方法,摒棄了預(yù)測(cè)模型對(duì)手工定義特征的依賴,比原有的CNN 方法在結(jié)果上有一定提高。
Bi-LSTM[17]:它是一個(gè)結(jié)合了代碼屬性圖和注意力機(jī)制的雙向LSTM預(yù)測(cè)模型,可以有效地避免程序語(yǔ)義信息的丟失以及捕獲關(guān)鍵特征。
在探究2T-CNN 同基線方法的優(yōu)劣時(shí),使用SPSC數(shù)據(jù)集,這是Li等人[20]和Pan等人[22]使用的數(shù)據(jù)集,也是跨版本軟件缺陷預(yù)測(cè)常用的數(shù)據(jù)集。但僅僅只在SPSC上實(shí)驗(yàn)并不能獲得足夠多的樣本來(lái)驗(yàn)證2T-CNN 的泛化性,因此在探究2T-CNN 的泛化性時(shí),使用PSC 數(shù)據(jù)集和DTSC數(shù)據(jù)集。
本文設(shè)計(jì)了多組對(duì)比實(shí)驗(yàn),并依據(jù)實(shí)驗(yàn)數(shù)據(jù)和實(shí)驗(yàn)結(jié)果給出了以下四點(diǎn)分析和結(jié)論。
3.4.1 2T-CNN與基線方法的比較
表2展示了2T-CNN 與本文除TBCNN 和TextCNN之外的基線方法在SPSC 數(shù)據(jù)集上的表現(xiàn)對(duì)比。觀察表2可以得到以下兩點(diǎn)結(jié)論:
表2 2T-CNN與基線方法在SPSC數(shù)據(jù)集上的表現(xiàn)比較Table 2 Performance comparison between 2T-CNN and baseline models on SPSC dataset
首先,四種最常見(jiàn)的傳統(tǒng)機(jī)器學(xué)習(xí)方法的平均F1-score均低于四種深度學(xué)習(xí)方法,因此可以得出結(jié)論,在跨版本軟件缺陷預(yù)測(cè)中,深度學(xué)習(xí)方法普遍優(yōu)于傳統(tǒng)機(jī)器學(xué)習(xí)方法,并且可以節(jié)省手工定義特征花費(fèi)的時(shí)間。
其次,雖然基于深度學(xué)習(xí)方法的四種預(yù)測(cè)模型在不同項(xiàng)目上的表現(xiàn)互有優(yōu)劣,但2T-CNN取得了最高的F1-score 平均值,因此可以證明基于特征融合的方法在整體上優(yōu)于已有的深度學(xué)習(xí)方法。此外,區(qū)別于DP-CNN,2T-CNN 的特征提取完全由神經(jīng)網(wǎng)絡(luò)獲取,不需要手工定義特征,因此可以節(jié)省大量成本。同時(shí),在初始化2TCNN的超參數(shù)時(shí)借鑒了Improved-CNN的超參數(shù)設(shè)定,例如全連接層的節(jié)點(diǎn)數(shù)、卷積核的步長(zhǎng)和大小等,基于初始化的超參數(shù)再進(jìn)行超參數(shù)調(diào)優(yōu),最終獲得了使模型表現(xiàn)最好的超參數(shù)設(shè)定,這也證明了Pan等人[22]的研究,即超參數(shù)使模型具有不穩(wěn)定性,因?yàn)樵谡{(diào)參過(guò)程中,模型在不同超參數(shù)下的表現(xiàn)顯著不同。最后,結(jié)合代碼屬性圖的Bi-LSTM的表現(xiàn)比兩種CNN方法好,比2T-CNN稍差,這反映了在代碼解析中,綜合了多種數(shù)據(jù)結(jié)構(gòu)的代碼屬性圖相較于單一的代碼解析方式,可以蘊(yùn)含更豐富的特征,但Bi-LSTM要付出更多的訓(xùn)練時(shí)間,這是由于CNN在訓(xùn)練速度上普遍快于Bi-LSTM[35]。
3.4.2 2T-CNN的泛化能力
為了驗(yàn)證2T-CNN在軟件缺陷預(yù)測(cè)中的泛化能力,使用較SPSC更為完整的PSC數(shù)據(jù)集以及DTSC數(shù)據(jù)集進(jìn)行實(shí)驗(yàn),具體結(jié)果如表3所示。
對(duì)于PSC數(shù)據(jù)集,觀察2T-CNN一列,被*標(biāo)記的數(shù)據(jù)是顯著低于其他版本對(duì)的F1-score。除了帶*的版本對(duì),2T-CNN在其他所有版本對(duì)中都能獲得較好的表現(xiàn),平均F1-score為0.658。
對(duì)于表3中被*標(biāo)記的F1-score,考慮它們較低的原因主要受到兩方面的影響:一是因?yàn)樵谒鼈兊陌姹緦?duì)中,存在嚴(yán)重的樣本不均衡問(wèn)題,例如在<Camel-1.0,Camel-1.2>中,Camel-1.0 的缺陷文件數(shù)為13,缺陷文件占比僅有3.8%,神經(jīng)網(wǎng)絡(luò)不能充分學(xué)習(xí)到缺陷特征,F(xiàn)1-score 僅為0.396;二是因?yàn)楫?dāng)同一個(gè)項(xiàng)目的兩個(gè)版本之間進(jìn)行較大的更新時(shí),它們之間的參數(shù)分布會(huì)有較大差異,例如在<JEdit-4.2,JEdit-4.3>中,文件數(shù)量增加了約1倍,缺陷文件數(shù)量卻下降了75%,訓(xùn)練后的神經(jīng)網(wǎng)絡(luò)不能處理這種巨大差異,對(duì)于JEdit-4.3 中的11 個(gè)缺陷,在10次實(shí)驗(yàn)中2T-CNN只有2次找到了1個(gè)缺陷。在由這兩方面原因造成的樣本結(jié)構(gòu)差異較大時(shí),2T-CNN 的表現(xiàn)不佳。
表3 2T-CNN、TBCNN、TextCNN在兩個(gè)數(shù)據(jù)集上的表現(xiàn)Table 3 Performance of 2T-CNN,TBCNN and TextCNN on two datasets
對(duì)于DTSC 數(shù)據(jù)集,將DTSC 相鄰版本的前一個(gè)版本作為訓(xùn)練集,后一個(gè)版本作為測(cè)試集進(jìn)行實(shí)驗(yàn),最終得到了0.644的平均F1-score,結(jié)果略好于PSC數(shù)據(jù)集上的表現(xiàn)。這證明2T-CNN有著不錯(cuò)的泛化能力,可以在不同的數(shù)據(jù)集上獲得相似的表現(xiàn)。
3.4.3 特征融合的有效性
觀察表3中TBCNN、TextCNN和2T-CNN在各個(gè)項(xiàng)目上的表現(xiàn)可以看到,基于特征融合的2T-CNN在各個(gè)項(xiàng)目上的表現(xiàn)都比它的兩個(gè)子模型TBCNN和TextCNN好,這證明了特征融合的思想是正確的,即TBCNN 和TextCNN提取到的特征經(jīng)過(guò)融合后,可以獲得更好的分類效果。
同時(shí)應(yīng)注意到,在缺陷文件占比較小的項(xiàng)目中,F(xiàn)1-score在TBCNN和TextCNN模型上的最大值普遍較低,這是因?yàn)橛?xùn)練集中正樣本比例較低。雖然在訓(xùn)練集上進(jìn)行過(guò)樣本不均衡處理,但模型依然不能充分學(xué)習(xí)到缺陷特征。而經(jīng)過(guò)特征融合后,2T-CNN 在這些項(xiàng)目中的表現(xiàn)要優(yōu)于TBCNN 和TextCNN,這也說(shuō)明特征融合方法是有效的。
3.4.4 特征融合方式對(duì)模型表現(xiàn)的影響
在深度學(xué)習(xí)中,特征融合的方式有很多種,在本文中探討了三種基本的特征融合方式,分別是Average、Maximum和Concatenate。在保證模型超參數(shù)不變的情況下,分別對(duì)三種融合方式在SPSC 數(shù)據(jù)集上進(jìn)行實(shí)驗(yàn)驗(yàn)證,結(jié)果如表4所示。除了在Lucene項(xiàng)目中Maximum融合表現(xiàn)最好之外,其他項(xiàng)目中都是Concatenate 融合的表現(xiàn)最好,并且所有項(xiàng)目的F1-score 平均值也是Concatenate 融合最好。這也證明了通過(guò)TBCNN 和TextCNN 分別提取程序代碼的結(jié)構(gòu)和語(yǔ)義特征的合理性,因?yàn)檫@兩種特征是程序的不同特征,它們具有一定的互質(zhì)性和互補(bǔ)性,所以Average和Maximum這兩種融合方法會(huì)淡化這種差異,而Concatenate 融合方法可以充分考慮它們之間的這種差異,獲得最好的融合效果。
本文針對(duì)現(xiàn)有的軟件缺陷預(yù)測(cè)方法存在的特征提取不完全問(wèn)題,研究并實(shí)現(xiàn)了一種基于結(jié)構(gòu)與語(yǔ)義特征融合的軟件缺陷預(yù)測(cè)框架2T-CNN。該框架將程序源代碼分別解析為AST和Token序列,從兩種程序表示方式中提取代碼的結(jié)構(gòu)和語(yǔ)義特征,將兩種特征融合后經(jīng)過(guò)分類器輸出預(yù)測(cè)結(jié)果。該方法可以快速預(yù)測(cè)軟件新版本中可能存在的缺陷,實(shí)驗(yàn)結(jié)果表明相較于原有的軟件缺陷預(yù)測(cè)模型有一定的提高。
在未來(lái)的研究工作中,通過(guò)研究更多的程序表示方式,例如在程序依賴圖、程序調(diào)用圖上設(shè)計(jì)合適的神經(jīng)網(wǎng)絡(luò)提取特征,用于軟件缺陷預(yù)測(cè),以期望獲得更好的預(yù)測(cè)效果。此外,構(gòu)建高質(zhì)量的軟件缺陷預(yù)測(cè)數(shù)據(jù)集也是一個(gè)值得推進(jìn)的工作。最后,不同類型的缺陷模式往往具有不同的特征,針對(duì)各種缺陷模式的特點(diǎn),設(shè)計(jì)不同的神經(jīng)網(wǎng)絡(luò)模型可能會(huì)提高預(yù)測(cè)的準(zhǔn)確率,這也將是未來(lái)的研究方向。