史志成,周 宇,3+
1.南京航空航天大學(xué) 計(jì)算機(jī)科學(xué)與技術(shù)學(xué)院,南京210016
2.南京航空航天大學(xué) 高安全系統(tǒng)的軟件開(kāi)發(fā)與驗(yàn)證技術(shù)工信部重點(diǎn)實(shí)驗(yàn)室,南京210016
3.南京大學(xué) 軟件新技術(shù)國(guó)家重點(diǎn)實(shí)驗(yàn)室,南京210023
在“大代碼”背景的驅(qū)動(dòng)下,如何高效地提取代碼特征并對(duì)其進(jìn)行編碼以快速地處理海量數(shù)據(jù)的需求已日益迫切。如今,深度學(xué)習(xí)在自然語(yǔ)言處理的很多領(lǐng)域已經(jīng)取得了突破性的進(jìn)展,人工智能也逐漸從“感知智能”邁向“認(rèn)知智能”,機(jī)器不僅能夠感知客觀世界所釋放的信息,更能夠?qū)@些信息像人一樣進(jìn)行理解和分析。Hindle等人[1]已經(jīng)論證了程序語(yǔ)言和自然語(yǔ)言類似,具備眾多可供分析的統(tǒng)計(jì)屬性,代碼語(yǔ)言可以和自然語(yǔ)言一樣能夠被機(jī)器理解和分析。因此,許多學(xué)者簡(jiǎn)單地將代碼作為自然語(yǔ)言來(lái)處理。例如,代碼被表示為一個(gè)字符串序列應(yīng)用在克隆檢測(cè)[2-3]、漏洞定位[4]以及代碼作者分類(code authorship classification)[5]的任務(wù)當(dāng)中。盡管代碼和自然語(yǔ)言有很多共性的特征,都是由一系列單詞組成且都能表示成語(yǔ)法樹的形式,然而代碼有許多自己專有的特性,代碼具有更強(qiáng)的邏輯結(jié)構(gòu),自定義的標(biāo)志符,且標(biāo)志符之間存在長(zhǎng)距離依賴等。簡(jiǎn)單地將代碼視作自然語(yǔ)言進(jìn)行處理難免會(huì)造成信息丟失。為了使模型更適合處理代碼語(yǔ)言,部分學(xué)者借助軟件工程的領(lǐng)域知識(shí),制定了一些啟發(fā)式規(guī)則來(lái)靜態(tài)地提取代碼特征,例如在應(yīng)用程序接口(application programming interface,API)推薦領(lǐng)域,Zhou 等人[6]就將用戶查詢后得到的反饋信息作為構(gòu)建API 向量的一維特征來(lái)提高查詢效果,然而這些靜態(tài)提取代碼特征的方式有以下三個(gè)弊端:
(1)完全依賴研究人員的先驗(yàn)知識(shí)來(lái)提取特征,所提取的特征數(shù)目有限。
(2)當(dāng)代碼庫(kù)過(guò)于龐大和復(fù)雜,特征規(guī)則的制定也會(huì)相應(yīng)變得復(fù)雜,難以適用于對(duì)海量且結(jié)構(gòu)復(fù)雜的代碼數(shù)據(jù)進(jìn)行處理。
(3)規(guī)則的制定往往是面向特定任務(wù)的,可遷移性差。
因此,近幾年許多研究使用深度學(xué)習(xí)模型來(lái)自動(dòng)提取代碼的特征[7-10],以擺脫對(duì)人工提取特征的依賴。這些模型很多借助代碼的抽象語(yǔ)法樹(abstract syntax tree,AST)來(lái)提取特征,通過(guò)對(duì)AST 內(nèi)部節(jié)點(diǎn)之間的依賴關(guān)系進(jìn)行提取,來(lái)獲得代碼的結(jié)構(gòu)信息,而不是簡(jiǎn)單地根據(jù)代碼的標(biāo)志符序列提取語(yǔ)義信息。然而這些方法有一個(gè)明顯的弊端,就是都將樹的二維結(jié)構(gòu)轉(zhuǎn)化成一維的序列結(jié)構(gòu)進(jìn)行預(yù)處理,如Alon 等人[10]將AST 處理成一個(gè)路徑集合,Hu 等人[8]以及White等人[9]直接使用先序遍歷的方式獲得AST節(jié)點(diǎn)的展開(kāi)序列作為模型的輸入,這些降維的處理方式一定程度上會(huì)造成節(jié)點(diǎn)之間某些依賴關(guān)系的丟失。
因此,為了更充分地提取代碼的結(jié)構(gòu)信息,部分學(xué)者提出了一些不降維而直接處理AST的樹形深度學(xué)習(xí)模型。如Wan等人[11]使用Tree-LSTM(tree-structured long short-term memory networks)進(jìn)行注釋生成,Mou等人[12]使用TBCNN(tree-based convolutional neural network)模型進(jìn)行代碼分類。然而這些樹形深度學(xué)習(xí)模型仍然有兩個(gè)局限性:(1)與自然語(yǔ)言的長(zhǎng)文本類似,在AST規(guī)模十分龐大的情境下,很容易在模型的訓(xùn)練過(guò)程中出現(xiàn)梯度消失的情況[13-15],因此無(wú)論是Wan等人使用Tree-LSTM或者M(jìn)ou等人使用的TBCNN都會(huì)很容易丟失那些存在長(zhǎng)期依賴關(guān)系節(jié)點(diǎn)之間的部分信息。(2)Wan 等人使用的Tree-LSTM 要將AST先轉(zhuǎn)化為二叉樹再進(jìn)行編碼,該預(yù)處理操作破壞了AST 原始的結(jié)構(gòu)且轉(zhuǎn)化后樹的深度將會(huì)大幅增加,加劇梯度消失問(wèn)題。
本文結(jié)合TBCNN以及Zhang等人[16]提出的ASTNN(AST-based neural network)模型構(gòu)建了一個(gè)基于卷積和循環(huán)神經(jīng)網(wǎng)絡(luò)的自動(dòng)代碼特征提取模型(convolutional and recurrent neural network,CVRNN)。模型的輸入是代碼的AST,為了提高AST 初始詞向量的質(zhì)量,還針對(duì)AST 設(shè)計(jì)了一個(gè)專門的詞向量預(yù)訓(xùn)練模型,預(yù)訓(xùn)練詞向量不僅能提升后面實(shí)驗(yàn)部分代碼分類以及相似代碼搜索的實(shí)驗(yàn)結(jié)果,還能在模型的訓(xùn)練過(guò)程中加速模型的收斂。借鑒ASTNN 切割A(yù)ST的思想,模型并未直接對(duì)整棵AST進(jìn)行處理,而是根據(jù)“if”“while”“for”以及“function”這4 個(gè)代表程序控制塊的AST 節(jié)點(diǎn)將AST 切割成一系列子樹,再利用TBCNN 模型對(duì)這些子樹進(jìn)行卷積運(yùn)算。子樹的規(guī)模遠(yuǎn)小于原先的AST,因此可以有效地解決節(jié)點(diǎn)長(zhǎng)期依賴導(dǎo)致的梯度消失問(wèn)題。之后,采用雙向循環(huán)神經(jīng)網(wǎng)絡(luò)[17],內(nèi)部神經(jīng)元采用LSTM[18],來(lái)提取代碼塊之間的序列信息,將每個(gè)時(shí)間步生成的代碼向量存放到一個(gè)向量矩陣中,最終采用最大池化得到代碼向量。為了驗(yàn)證CVRNN 模型生成的代碼向量的質(zhì)量,還在CVRNN模型基礎(chǔ)之上設(shè)計(jì)了兩個(gè)具體的應(yīng)用模型,代碼分類以及相似代碼搜索模型。實(shí)驗(yàn)結(jié)果表明,CVRNN模型不僅能夠高效地自動(dòng)提取代碼的特征,且所提取的特征具有很強(qiáng)的遷移能力,不僅適用于代碼分類任務(wù),其分類訓(xùn)練后得到的編碼器在相似代碼搜索任務(wù)上也有很好的效果。
本文的主要貢獻(xiàn)是:
(1)提出了一個(gè)基于AST的詞向量訓(xùn)練模型。
(2)提出了一個(gè)基于卷積和循環(huán)神經(jīng)網(wǎng)絡(luò)的自動(dòng)代碼特征提取的深度學(xué)習(xí)模型。
(3)構(gòu)建了一個(gè)高效的用于搜索相似代碼的系統(tǒng),且該項(xiàng)目所使用的數(shù)據(jù)集以及代碼已在github上開(kāi)源(https://github.com/zhichengshi/CVRNN)。
這部分主要分為兩個(gè)子模型進(jìn)行介紹:
(1)基于樹的詞向量訓(xùn)練模型。
(2)基于卷積和循環(huán)神經(jīng)網(wǎng)絡(luò)的特征提取模型。
詞嵌入技術(shù)是將自然語(yǔ)言文本中的單詞嵌入到低維空間的一種向量表示方法[19]。近幾年,許多學(xué)者也將該方法成功應(yīng)用在處理代碼的任務(wù)中,如API推薦[20-21]、漏洞檢測(cè)[22]等。然而這些詞嵌入技術(shù)簡(jiǎn)單地將代碼視為序列化的文本進(jìn)行處理,往往采用類似Word2vec[23]滑動(dòng)窗口的方式建立特定單詞的上下文情境,并以此為依據(jù)來(lái)生成相應(yīng)的詞向量,如Zhang等人[16]就通過(guò)先序遍歷的方式獲得AST的節(jié)點(diǎn)序列,然后使用Word2vec 生成詞向量,而代碼元素之間的依賴關(guān)系其實(shí)是一種二維的樹形結(jié)構(gòu),應(yīng)用這種方法獲取詞的情境難免會(huì)導(dǎo)致代碼的部分結(jié)構(gòu)信息難以編碼進(jìn)最終生成的詞向量中。Mou 等人[12]在對(duì)AST中節(jié)點(diǎn)進(jìn)行編碼時(shí)只將當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)作為情境,這種情境信息的提取方式仍然不完備。因此本文借鑒Pérez等人[24]構(gòu)造樹中節(jié)點(diǎn)上下文情境的方法,結(jié)合當(dāng)前節(jié)點(diǎn)的祖先節(jié)點(diǎn)、兄弟節(jié)點(diǎn)以及子孫節(jié)點(diǎn)作為情境,在提取祖先節(jié)點(diǎn)以及子孫節(jié)點(diǎn)作為情境的過(guò)程中分別設(shè)置一個(gè)向上以及向下探測(cè)的參數(shù)來(lái)控制提取情境的深度,并設(shè)計(jì)了一個(gè)詞向量訓(xùn)練模型根據(jù)提取的情境來(lái)生成AST 節(jié)點(diǎn)的詞向量。為了提高相似代碼搜索的準(zhǔn)確率以及提高模型的訓(xùn)練速度,將不考慮AST 葉子節(jié)點(diǎn)的信息即代碼中的標(biāo)志符信息,因此該模型更側(cè)重于對(duì)代碼結(jié)構(gòu)信息的提取。相似代碼搜索任務(wù)使用的數(shù)據(jù)集來(lái)源于leetcode 編程網(wǎng)站,代碼分類任務(wù)所使用的數(shù)據(jù)集是Mou 等人[12]提供的104 數(shù)據(jù)集,該數(shù)據(jù)集共包含104個(gè)類別的代碼,每個(gè)類別下包含500個(gè)樣本。在分類訓(xùn)練的過(guò)程中并沒(méi)有使用AST葉子節(jié)點(diǎn)的信息,原因有二:(1)由于代碼中的標(biāo)志符是程序員自定義的,而據(jù)觀察104數(shù)據(jù)集中的詞匯與leetcode上的詞匯并不重疊,使用葉子節(jié)點(diǎn)反而會(huì)引入噪音。(2)leetcode 上的代碼所使用的變量名僅僅起一個(gè)命名的作用并沒(méi)有語(yǔ)義表示的功能,更多的是采用a、b、temp 等不攜帶語(yǔ)義信息的單詞作為變量名。AST的生成工具使用的是srcml(https://www.srcml.org/),該工具不檢查代碼的正確性,因此即使編譯不能通過(guò)的代碼也能生成AST,提高了模型的容錯(cuò)性。在代碼分類以及相似代碼搜索的任務(wù)中所使用的代碼都是C/C++編寫的,而srcml 用來(lái)表示這兩種語(yǔ)言所定義的節(jié)點(diǎn)只有60 個(gè),加入葉子節(jié)點(diǎn)后詞匯表的大小將擴(kuò)充至上萬(wàn),因此不使用葉子節(jié)點(diǎn)將大大提高模型的收斂速度。
圖1展示了整個(gè)詞向量的訓(xùn)練過(guò)程。選取節(jié)點(diǎn)b作為當(dāng)前節(jié)點(diǎn),向上探測(cè)尋找祖先節(jié)點(diǎn)的深度設(shè)置為1,向下探測(cè)子孫節(jié)點(diǎn)的深度設(shè)置為2,因此得到祖先節(jié)點(diǎn)情境信息a,兄弟節(jié)點(diǎn)情境信息c,以及子孫節(jié)點(diǎn)情境信息(d,e,f,g,h),將這三類情境節(jié)點(diǎn)合并得到(a,c,d,e,f,g,h)作為節(jié)點(diǎn)b的情境信息。在模型的訓(xùn)練過(guò)程中,首先使用正態(tài)分布對(duì)詞匯表矩陣進(jìn)行初始化,得到矩陣D∈Rn×f,其中n表示詞匯表的大小,f表示詞向量的維度。模型的輸入分為兩部分,情境節(jié)點(diǎn)以及當(dāng)前節(jié)點(diǎn)在詞匯表中的索引,通過(guò)查找詞匯表可以得到情境節(jié)點(diǎn)以及當(dāng)前節(jié)點(diǎn)的詞向量表示,分別對(duì)應(yīng)圖1中的context embedding以及current vector,之后context embedding 經(jīng)過(guò)兩層全連接神經(jīng)網(wǎng)絡(luò)得到變換后的情境向量context vector,使用交叉熵作為損失函數(shù),Adam作為優(yōu)化器進(jìn)行梯度下降。為了使生成的context vector 能夠與current vector進(jìn)行交叉熵運(yùn)算,輸出層輸出向量的維度與詞匯表中向量的維度相同。整個(gè)計(jì)算過(guò)程的數(shù)學(xué)公式如下:
其中,v∈R1×f表示情境向量,h表示經(jīng)過(guò)隱層后得到的輸出向量,W1∈Rf×k是隱層的權(quán)值矩陣,用來(lái)將維度是f的向量映射成k維向量,b∈R1×k是隱層網(wǎng)絡(luò)的偏置項(xiàng)。W2∈Rk×v是輸出層的權(quán)值矩陣,y′∈R1×v是輸出層的輸出向量,y∈R1×v是當(dāng)前節(jié)點(diǎn)的詞向量,表示向量y′在第i個(gè)維度上的值,yi同理。
1.2.1 AST切割
為了防止AST 規(guī)模過(guò)大,帶來(lái)梯度消失問(wèn)題。并未直接將AST 作為模型的輸入,而是先將AST 進(jìn)行切割,得到AST的子樹序列輸入到模型之中,詳細(xì)步驟如下。
首先,定義一個(gè)用于存儲(chǔ)AST 分裂節(jié)點(diǎn)的集合S={if,while,for,function,unit}。其中“if”用來(lái)提取條件子樹,“while”“for”用于提取循環(huán)子樹,“function”用來(lái)提取方法體子樹,“unit”是未切割前AST的根節(jié)點(diǎn),AST 剩余的節(jié)點(diǎn)都在“unit”子樹上,如下展示了具體的切割算法:
算法1split AST
Fig.1 Node embedding training model圖1 詞向量訓(xùn)練模型
該算法以AST的根節(jié)點(diǎn)作為輸入。首先用根節(jié)點(diǎn)初始化一個(gè)數(shù)組T,然后通過(guò)深度優(yōu)先遍歷獲得節(jié)點(diǎn)序列N,對(duì)N中的節(jié)點(diǎn)進(jìn)行遍歷,若節(jié)點(diǎn)屬于集合{function,if,while,for},該節(jié)點(diǎn)連同其子節(jié)點(diǎn)將會(huì)被切割取出存儲(chǔ)到T中。值得注意的是,當(dāng)出現(xiàn)嵌套的情況,例如“if”子樹中還包含“for”子樹,“for”子樹將會(huì)從“if”子樹上提前切割下來(lái),因?yàn)樯疃葍?yōu)先遍歷將先訪問(wèn)“for”再訪問(wèn)“if”節(jié)點(diǎn),兩個(gè)獨(dú)立的子樹根節(jié)點(diǎn)將按序存儲(chǔ)到T中。
1.2.2 卷積以及循環(huán)神經(jīng)網(wǎng)絡(luò)模型
在介紹CVRNN 模型之前,先詳細(xì)地闡述一下Mou 等人[12]提出的基于樹的卷積神經(jīng)網(wǎng)絡(luò)TBCNN。圖2 展示了該模型執(zhí)行的具體流程。首先應(yīng)用詞嵌入技術(shù)將AST中的節(jié)點(diǎn)轉(zhuǎn)化成向量表示,不同的是,該方法只使用了被表示節(jié)點(diǎn)的孩子節(jié)點(diǎn)作為情境,沒(méi)有考慮到兄弟節(jié)點(diǎn)以及祖先節(jié)點(diǎn)的信息,具體的詞嵌入公式如下:
其中,p表示雙親節(jié)點(diǎn),ci表示p的第i個(gè)孩子節(jié)點(diǎn),Wi是ci的權(quán)值矩陣,(孫子節(jié)點(diǎn)的數(shù)目除以孩子節(jié)點(diǎn)的數(shù)目)是系數(shù),b是偏置項(xiàng)。然后通過(guò)設(shè)置一個(gè)固定深度的滑動(dòng)窗口遍歷整個(gè)AST,再引入最大池化得到一個(gè)形狀與原先一樣的AST,接著使用動(dòng)態(tài)池化[25]以及兩層全連接神經(jīng)網(wǎng)絡(luò)得到最終的代碼向量。圖3 展示了CVRNN 的模型架構(gòu),以下是該模型的算法執(zhí)行邏輯:
Fig.2 TBCNN model圖2 TBCNN模型
首先根據(jù)1.1 節(jié)得到的詞向量表將AST 中的節(jié)點(diǎn)表示為向量,再使用TBCNN模型對(duì)AST進(jìn)行卷積得到編碼后的向量,然后使用雙向循環(huán)神經(jīng)網(wǎng)絡(luò)并以LSTM 作為神經(jīng)元對(duì)得到的向量序列進(jìn)行編碼以提取代碼的序列信息。標(biāo)準(zhǔn)的循環(huán)神經(jīng)網(wǎng)絡(luò)(recurrent neural network,RNN)是單向的,其編碼受限于過(guò)去的信息,采用雙向RNN 則能夠同時(shí)使用過(guò)去和未來(lái)兩個(gè)方向的信息。在后面的實(shí)驗(yàn)中,將會(huì)對(duì)這兩種不同的策略進(jìn)行對(duì)比。給定一棵代碼的AST,假設(shè)該AST 被切割成n棵子樹,這些子樹經(jīng)過(guò)TBCNN編碼后將得到一個(gè)向量序列TV∈Rn×k,k表示向量的維度,n表示子樹的數(shù)目。在某個(gè)時(shí)間步t,LSTM的計(jì)算公式如下:
其中,σ是sigmoid 激活函數(shù),ct表示新?tīng)顟B(tài),ht∈R1×m表示輸出向量,由當(dāng)前時(shí)間步兩個(gè)方向的向量拼接而成的向量,Wi、Wf、Wo是權(quán)值矩陣,bt、bf、bo是偏置項(xiàng)。it表示輸入門,決定et中哪一部分將被保留添加到ct中,ft表示遺忘門,決定et的哪一部分將被遺忘,ot是輸出門用來(lái)計(jì)算輸出向量。整個(gè)雙向RNN的計(jì)算可以形式化如下:
Fig.3 CVRNN model圖3 CVRNN模型
至此,每棵子樹將會(huì)被轉(zhuǎn)化成一個(gè)向量b∈R1×2m。這些向量將會(huì)被存放到一個(gè)矩陣BV∈Rn×2m中,考慮到不同的子樹的重要程度可能不同,例如“if”代碼塊中的語(yǔ)句要多于“for”代碼塊中的語(yǔ)句,直觀上該“if”子樹所攜帶的信息將高于“for”子樹,采用均值池化則兩個(gè)代碼塊所賦予的權(quán)值相同,因此該模型使用最大池化得到代碼向量r∈R1×2m,而沒(méi)有選擇均值池化。
算法2CVRNN
這部分主要介紹兩個(gè)基于CVRNN 的具體的應(yīng)用模型,代碼分類模型以及相似代碼搜索模型,而相似代碼搜索模型所使用的編碼器是經(jīng)過(guò)代碼分類任務(wù)訓(xùn)練之后得到的CVRNN。
代碼分類所使用的數(shù)據(jù)集是Mou 等人[12]構(gòu)建的104 數(shù)據(jù)集。原始數(shù)據(jù)集共包含52 000 條數(shù)據(jù)。訓(xùn)練數(shù)據(jù)是隨機(jī)從中抽取的51 000 條數(shù)據(jù),剩余的1 000 條作為驗(yàn)證數(shù)據(jù),來(lái)監(jiān)控訓(xùn)練過(guò)程中是否發(fā)生過(guò)擬合。
代碼分類模型如圖4 所示,經(jīng)過(guò)CVRNN 的編碼,任意代碼段都能轉(zhuǎn)換成一個(gè)固定長(zhǎng)度的向量表示r,為了使模型適配104 代碼分類任務(wù),在該模型的基礎(chǔ)之上再添加了一層全連接網(wǎng)絡(luò),將r映射成維度是104的向量r′。使用one-hot方法生成每個(gè)代碼的標(biāo)記向量l,l的維度104,若代碼屬于第5類,則l位置5 上的值為1,其余位置為0。選取交叉熵作為損失函數(shù),使用Adam作為優(yōu)化器。
Fig.4 Code classification圖4 代碼分類
相似代碼搜索使用的數(shù)據(jù)集來(lái)源于leetcode,沒(méi)有在實(shí)際生產(chǎn)環(huán)境中去尋找功能相似的代碼的原因是這種人為的篩選方式具有很強(qiáng)的主觀性,功能劃分的粒度很難把握。比如同樣是一個(gè)排序代碼,一個(gè)使用快速排序?qū)崿F(xiàn),另一個(gè)使用歸并排序?qū)崿F(xiàn),很難判定兩個(gè)是否是同一個(gè)類別。而使用leetcode 編程網(wǎng)站上同一個(gè)題目的不同題解作為判斷功能是否相似是一個(gè)很客觀的標(biāo)準(zhǔn)。為了保證功能絕對(duì)一致,每個(gè)題解都輸入到在線的IDE(integrated development environment)中進(jìn)行了測(cè)試,該網(wǎng)站會(huì)提供幾百到上千個(gè)測(cè)試用例,所篩選得到的題解能通過(guò)全部的測(cè)試用例。選擇其中一個(gè)題解用作查詢,另一個(gè)用作匹配對(duì)象放入數(shù)據(jù)庫(kù)中,該數(shù)據(jù)庫(kù)對(duì)應(yīng)圖5中的正樣本。在真實(shí)環(huán)境下,數(shù)據(jù)庫(kù)中會(huì)包含更多的代碼,因此,為了使相似代碼搜索任務(wù)更貼近真實(shí)環(huán)境,該實(shí)驗(yàn)對(duì)代碼的搜索空間進(jìn)行了擴(kuò)充。使用了104 數(shù)據(jù)集中的52 000 條數(shù)據(jù)作為負(fù)樣本加入到待搜索的數(shù)據(jù)庫(kù)中。
圖5左邊是代碼向量的生成過(guò)程,應(yīng)用訓(xùn)練好的CVRNN模型對(duì)數(shù)據(jù)庫(kù)中的代碼進(jìn)行編碼,最后將代碼向量存放到一個(gè)數(shù)據(jù)庫(kù)中。圖5 右邊是代碼查詢的過(guò)程,模型的輸入是一個(gè)用作查詢的題解,經(jīng)過(guò)CVRNN 編碼后得到代碼向量。借助Facebook 所提供的faiss[26]工具將數(shù)據(jù)庫(kù)中的代碼向量基于與查詢向量之間的歐式距離進(jìn)行排序,選擇前10 個(gè)搜索結(jié)果作為結(jié)果統(tǒng)計(jì)樣本。因?yàn)樵谒阉鲿r(shí),數(shù)據(jù)庫(kù)中的代碼都已經(jīng)是向量的形式,所以搜索時(shí)間很快,時(shí)間可以忽略不計(jì),在普通筆記本上的搜索時(shí)間小于2 ms。
Fig.5 Similar code search圖5 相似代碼搜索
本章將基于4 個(gè)研究問(wèn)題對(duì)CVRNN 模型的性能進(jìn)行評(píng)估。
(1)CVRNN模型在分類任務(wù)上的效果如何?
(2)CVRNN生成的代碼向量是否滿足相似度越高,彼此之間的幾何距離就越短的性質(zhì)?
(3)CVRNN在相似代碼搜索任務(wù)上的實(shí)驗(yàn)結(jié)果如何?
(4)CVRNN預(yù)訓(xùn)練詞向量以及雙向RNN模塊對(duì)模型產(chǎn)生怎樣的影響?
TBCNN是第一個(gè)用于處理104代碼分類任務(wù)的深度學(xué)習(xí)模型。前文已經(jīng)詳細(xì)介紹過(guò)TBCNN 模型的執(zhí)行流程。因?yàn)門BCNN模型內(nèi)嵌在CVRNN模型之中,為了對(duì)比的公平性,在實(shí)驗(yàn)過(guò)程中,TBCNN與CVRNN 中使用的TBCNN 在參數(shù)配置上完全相同,卷積層的數(shù)目、卷積維度都相同。在相似代碼搜索任務(wù)中,TBCNN 模型生成的代碼向量對(duì)應(yīng)圖2 中倒數(shù)第二個(gè)全連接神經(jīng)網(wǎng)絡(luò)的輸出向量,維度是400。
ASTNN模型是當(dāng)下在104分類任務(wù)上取得最好結(jié)果的模型。該模型與CVRNN 模型的主要區(qū)別就是對(duì)AST序列進(jìn)行編碼的方法不同。該模型主要采用如下公式對(duì)AST中的節(jié)點(diǎn)進(jìn)行自下而上的編碼:
其中,h表示當(dāng)前節(jié)點(diǎn)更新后的狀態(tài),Wn是權(quán)值矩陣,是Wn的轉(zhuǎn)置矩陣,vn表示當(dāng)前節(jié)點(diǎn)的初始化詞向量,hi表示當(dāng)前節(jié)點(diǎn)第i個(gè)孩子節(jié)點(diǎn)的狀態(tài),C表示當(dāng)前節(jié)點(diǎn)孩子節(jié)點(diǎn)的數(shù)目,bn是偏置項(xiàng)。然后對(duì)AST中的所有節(jié)點(diǎn)使用最大池化獲得AST的向量表示。接著將得到的AST 向量序列輸入到雙向RNN 中,再經(jīng)過(guò)最大池化得到整棵AST 的向量表示。因?yàn)镃VRNN也使用雙向RNN來(lái)提取代碼的序列信息,為了對(duì)比的公平性,ASTNN、CVRNN均使用LSTM作為神經(jīng)元,且隱層單元都設(shè)置為200,因此兩個(gè)時(shí)間方向拼接之后得到的代碼向量維度也都是400。
為了對(duì)比公平,CVRNN、TBCNN、ASTNN 共享同一張預(yù)訓(xùn)練詞向量表。除了深度學(xué)習(xí)模型,CVRNN模型還與3個(gè)業(yè)界常用的相似代碼檢測(cè)工具進(jìn)行了對(duì)比,分別是moss(measure of software similarity)(https://theory.stanford.edu/~aiken/moss/)[27]、jplag(https://jplag.ipd.kit.edu/)以及sim(https://dickgrune.com/Programs/similarity_tester/)。moss是斯坦福開(kāi)發(fā)的利用文件指紋技術(shù)來(lái)確定程序相似性的系統(tǒng)。jplag 以程序覆蓋率作為代碼相似的度量指標(biāo),在覆蓋的過(guò)程中,代碼被分為多個(gè)相似的片段,通過(guò)計(jì)算平均覆蓋率和最大覆蓋率作為最終代碼相似度的值。sim 主要根據(jù)代碼所使用的詞匯來(lái)計(jì)算代碼之間的相似度,該工具廣泛應(yīng)用于檢查大型軟件項(xiàng)目中的重復(fù)代碼。此外數(shù)據(jù)還被部署在一個(gè)開(kāi)源的代碼搜索引擎search code(https://github.com/boyter/searchcode-server)上進(jìn)行測(cè)試,該搜索引擎也是以代碼作為查詢語(yǔ)句在數(shù)據(jù)庫(kù)中搜索相似的代碼,然而所有反饋結(jié)果都是0,因此沒(méi)有在后面的實(shí)驗(yàn)結(jié)果統(tǒng)計(jì)表中列出。
在代碼分類任務(wù)中,僅選取準(zhǔn)確率作為度量指標(biāo)。在相似代碼搜索中,應(yīng)用以下3個(gè)在搜索領(lǐng)域廣泛使用的度量指標(biāo)來(lái)衡量搜索結(jié)果:
(1)top(k)
式中,Q表示查詢樣本,|Q|表示查詢樣本的數(shù)目,若前k個(gè)候選樣本中有一個(gè)與查詢匹配,則認(rèn)為該查詢命中結(jié)果,rel(k)表示命中的查詢樣本的數(shù)目。
(2)MRR(mean reciprocal rank)
式中,F(xiàn)Ranki表示第i個(gè)樣本第一個(gè)命中的位置,Q以及|Q|的定義同上。
(3)NDCG(normalized discounted cumulative gain)
式中,n表示為限定反饋數(shù)目所設(shè)置的閾值,默認(rèn)為10。relij表示第j個(gè)候選樣本與查詢樣本i的相關(guān)程度,在相似代碼搜索任務(wù)中,若候選樣本與查詢樣本匹配則值為1,反之為0。Q以及|Q|的定義同上。IDCG(idea discounted cumulative gain)表示DCG的最優(yōu)計(jì)算結(jié)果,假設(shè)n為5,前5個(gè)搜索結(jié)果與查詢樣本的相關(guān)程度分別為(0,0,1,0,0),則使用(1,0,0,0,0)計(jì)算IDCG的值,(0,0,1,0,0)計(jì)算DCG的值,以此得到NDCG的值。
研究問(wèn)題1CVRNN 模型在分類任務(wù)上的效果如何?
圖6 展示了經(jīng)過(guò)每一輪訓(xùn)練之后,3 個(gè)深度學(xué)習(xí)模型在驗(yàn)證集上的分類準(zhǔn)確率??梢钥闯鯟VRNN模型相對(duì)于其他兩個(gè)模型收斂速度更快,經(jīng)過(guò)1輪訓(xùn)練之后,CVRNN模型分類的精度就達(dá)到了76.5%,而ASTNN 以及TBCNN 則分別是45.2%和65.0%(對(duì)應(yīng)圖6 縱軸的截距)。經(jīng)過(guò)30 輪訓(xùn)練之后,CVRNN 模型的分類精度達(dá)到94.4%,而ASTNN以及TBCNN則分別是90.7%和80.9%。在這30輪的訓(xùn)練過(guò)程中,可以看出,CVRNN的每一輪的驗(yàn)證精度均高于另外兩個(gè)深度學(xué)習(xí)模型。因此,CVRNN在代碼分類任務(wù)上對(duì)比這兩個(gè)深度學(xué)習(xí)模型有明顯的優(yōu)勢(shì)。
Fig.6 Valid accuracy of three deep learning models in each epoch圖6 3個(gè)深度學(xué)習(xí)模型每訓(xùn)練一輪的驗(yàn)證精度
研究問(wèn)題2 生成的代碼向量是否滿足相似度越高,彼此之間的幾何距離就越短的性質(zhì)?
圖7 為leetcode上100 個(gè)問(wèn)答對(duì)生成的代碼向量使用TSNE(t-distributed stochastic neighbor embedding)方法降維打印后的圖片,可以看出很多問(wèn)答對(duì)生成的代碼向量之間的距離很近,因此也回答了研究問(wèn)題2,使用CVRNN模型可以將功能相似的代碼向量聚集在一起,相似度越高,彼此之間的幾何距離就越短。
Fig.7 leetcode code vector圖7 leetcode代碼向量
研究問(wèn)題3 CVRNN 在相似代碼搜索任務(wù)上的實(shí)驗(yàn)結(jié)果如何?
表1 展示了各個(gè)模型在相似代碼搜索上的實(shí)驗(yàn)精度,3個(gè)深度學(xué)習(xí)均采用第30輪訓(xùn)練結(jié)束之后的編碼器。在使用jplag 模型進(jìn)行測(cè)試時(shí),本文統(tǒng)計(jì)了該模型由平均相似度以及最大相似度分別得到的實(shí)驗(yàn)結(jié)果。實(shí)驗(yàn)結(jié)果表明:深度學(xué)習(xí)模型的實(shí)驗(yàn)效果相對(duì)于3 個(gè)傳統(tǒng)的相似度檢測(cè)工具有明顯的優(yōu)勢(shì),CVRNN 在各項(xiàng)度量指標(biāo)上均高于其他任何一個(gè)模型。盡管在分類精度上TBCNN 要劣于ASTNN,ASTNN 要高出9.8 個(gè)百分點(diǎn),然而除了Top1 二者的精度相等之外,TBCNN 其他幾項(xiàng)的精度均高于ASTNN。這說(shuō)明,盡管可以借助代碼分類任務(wù)訓(xùn)練編碼器,然而模型在兩個(gè)任務(wù)上的性能并不是正相關(guān)的,即在代碼分類任務(wù)上精度越高并不代表相似代碼搜索的效果越好,然而從表1 以及圖6 可以看出,CVRNN模型在兩個(gè)任務(wù)上的效果都是最優(yōu)的。
Table 1 Similar code search accuracy表1 相似代碼搜索準(zhǔn)確率 %
研究問(wèn)題4CVRNN 預(yù)訓(xùn)練詞向量以及雙向RNN模塊對(duì)模型產(chǎn)生怎樣的影響?
定義如下4個(gè)模型:
模型1(without biRNN)將雙向RNN直接從模型移除。
模型2(RNN)將雙向RNN替換成雙層單向RNN。
模型3(random)使用正態(tài)分布隨機(jī)初始化詞向量。
模型4(CVRNN)默認(rèn)配置,使用雙向RNN以及預(yù)訓(xùn)練的詞向量。
模型1直接將雙向RNN移除,表示整個(gè)CVRNN模型將不提取代碼的序列信息,而模型2將雙向RNN替換成雙層單向RNN則表示削弱模型提取序列信息的能力。表2 展示了各個(gè)模型在代碼分類上取得的精度,可以看出模型1在失去提取代碼序列信息模塊之后,分類精度直接從94.4%降到了73.8%,對(duì)比模型2的92.5%有很大的差距。表3展示了各個(gè)模型在相似代碼搜索任務(wù)上的結(jié)果,可以發(fā)現(xiàn),模型1 雖然在失去提取序列信息的模塊之后在代碼分類任務(wù)上要明顯遜色于模型2,在相似代碼搜索各項(xiàng)度量指標(biāo)上的值卻都高于模型2,該結(jié)果也與問(wèn)題3 得到的結(jié)論相符合,模型在代碼分類以及相似代碼搜索上的性能并不是成正相關(guān)的。從表2以及表3可以看出,模型4 在兩個(gè)任務(wù)上的性能都要高于模型1 和模型2。圖8展示了模型3以及模型4在訓(xùn)練過(guò)程中每一輪在驗(yàn)證集上的分類精度。可以看出使用預(yù)訓(xùn)練詞向量的模型4將收斂得更快,經(jīng)過(guò)第一輪訓(xùn)練之后,模型3的分類精度為72.00%,而模型4精度能達(dá)到76.50%。并且在每一個(gè)訓(xùn)練輪次之中,使用預(yù)訓(xùn)練向量的模型都有更高的精度,使用預(yù)訓(xùn)練詞向量的模型4最終達(dá)到94.4%的分類精度,而隨機(jī)初始化詞向量的模型最終的分類精度是91.4%。從表3 也可以看出,預(yù)訓(xùn)練詞向量對(duì)提升相似代碼搜索的性能也有貢獻(xiàn)。
Table 2 CVRNN ablation research in code classification表2 CVRNN代碼分類消融實(shí)驗(yàn) %
Table 3 CVRNN ablation research in similar code search表3 CVRNN相似代碼搜索消融實(shí)驗(yàn) %
Fig.8 Valid accuracy comparison between random initializing node embedding and pre-training embedding in each epoch圖8 隨機(jī)初始化詞向量以及使用預(yù)訓(xùn)練詞向量每一輪驗(yàn)證精度對(duì)比
如何對(duì)代碼進(jìn)行表示一直是軟件工程領(lǐng)域研究的一個(gè)重要的問(wèn)題。
傳統(tǒng)的信息檢索以及機(jī)器學(xué)習(xí)方法主要將代碼作為純文本進(jìn)行處理。輸入到模型之中的是一個(gè)被規(guī)范化處理好的標(biāo)志符序列[2],SourcererCC[3]在此基礎(chǔ)上增強(qiáng)了對(duì)代碼語(yǔ)義的挖掘,使用一種反向索引(inverted index)技術(shù)對(duì)標(biāo)志符的序列信息進(jìn)行挖掘。與SourcererCC不同的是,Deckard[28]模型選擇增強(qiáng)對(duì)代碼結(jié)構(gòu)信息的挖掘,在輸入的數(shù)據(jù)中加入了代碼的語(yǔ)法結(jié)構(gòu)信息。
不少研究人員借助AST所提供的代碼元素之間的依賴關(guān)系來(lái)增強(qiáng)對(duì)代碼信息的提取。例如:Zhou等人[7]借助AST 來(lái)擴(kuò)充方法體內(nèi)部元素的情境信息以增強(qiáng)代碼段的語(yǔ)義,提高注釋生成的效果。Yan等人[29]應(yīng)用類似方法進(jìn)行代碼推薦工作。除了借助AST 擴(kuò)充代碼元素語(yǔ)義之外,更多的模型選擇將AST整體或切分后得到的子結(jié)構(gòu)序列輸入到模型之中。例如:Hu 等人[8]就通過(guò)添加括號(hào)的方式來(lái)限定AST 中節(jié)點(diǎn)的作用域,將AST 轉(zhuǎn)化成一個(gè)節(jié)點(diǎn)序列來(lái)生成代碼對(duì)應(yīng)的注釋;Alon 等人[10]借助函數(shù)體的AST 路徑來(lái)生成相應(yīng)的函數(shù)名;White 等人[9]則分別根據(jù)代碼的標(biāo)志符序列以及AST節(jié)點(diǎn)序列得到代碼的語(yǔ)義向量和結(jié)構(gòu)向量,根據(jù)這兩個(gè)向量對(duì)代碼克隆檢測(cè)展開(kāi)研究。
然而這些借助AST的方法都是將樹形結(jié)構(gòu)轉(zhuǎn)化為一維的序列結(jié)構(gòu),破壞了元素之間的依賴關(guān)系。因此,部分學(xué)者提出了不降維而直接處理AST 的樹形深度學(xué)習(xí)模型。與White 的方法類似,Wan 等人[11]也將代碼分為兩部分作為輸入,使用RNN 對(duì)代碼的標(biāo)志符序列進(jìn)行編碼得到語(yǔ)義向量;不同的是,Wan等人使用Tree-LSTM模型[30]去處理AST,而并沒(méi)有簡(jiǎn)單地通過(guò)將AST轉(zhuǎn)化成節(jié)點(diǎn)序列的方式得到代碼的結(jié)構(gòu)向量。Wei 等人[31]同樣使用Tree-LSTM 對(duì)克隆檢測(cè)展開(kāi)研究。Mou 等人[12]提出了一種基于樹的卷積神經(jīng)網(wǎng)絡(luò)模型TBCNN,該模型直接在AST上進(jìn)行卷積計(jì)算,然后使用動(dòng)態(tài)池化技術(shù)將不同規(guī)格的AST 壓縮成代碼向量,該向量能夠很好地提取代碼中的結(jié)構(gòu)信息,在104 類代碼分類任務(wù)上能夠達(dá)到94%的精度。為了解決上述樹神經(jīng)網(wǎng)絡(luò)的問(wèn)題,一種方法就是獲得代碼的控制流圖以及數(shù)據(jù)依賴圖,靜態(tài)地建立節(jié)點(diǎn)與節(jié)點(diǎn)之間的聯(lián)系,將代碼表示成一種圖的數(shù)據(jù)結(jié)構(gòu),最終使用圖嵌入技術(shù)[32]對(duì)代碼進(jìn)行表示。例如Allamanis等人[33]就通過(guò)相同的函數(shù)名以及變量名來(lái)靜態(tài)地建立這些函數(shù)以及變量之間存在的依賴關(guān)系,Tufano等人[34]則直接構(gòu)建程序的控制流圖來(lái)補(bǔ)充節(jié)點(diǎn)之間的控制關(guān)系。然而程序中元素的依賴關(guān)系往往要借助編譯后代碼的中間表示或者字節(jié)碼才能得到[35]。在現(xiàn)實(shí)環(huán)境中,很多代碼不能夠被編譯,因此這些方法所適用的范圍將會(huì)受到很大的限制,而且代碼圖結(jié)構(gòu)的定義以及對(duì)于圖特征的提取也十分依賴專家的領(lǐng)域知識(shí),模型往往很復(fù)雜,設(shè)計(jì)代價(jià)較高。
對(duì)比這些工作,本文的工作集中于借助AST 對(duì)代碼的結(jié)構(gòu)以及序列信息進(jìn)行提取。此外本文提出了一種新的獲得代碼向量的方法,該方法不使用完整的訓(xùn)練好的代碼分類模型,僅截取模型中的一部分來(lái)生成代碼向量,得到的代碼向量可滿足功能越相似,幾何距離就越短的性質(zhì)。
本文提出了一個(gè)基于卷積以及循環(huán)神經(jīng)網(wǎng)絡(luò)的自動(dòng)代碼特征提取模型CVRNN。通過(guò)對(duì)已經(jīng)標(biāo)記類別的代碼訓(xùn)練編碼器,編碼器將自動(dòng)地學(xué)會(huì)如何提取代碼特征,對(duì)比當(dāng)下兩個(gè)前沿的代碼分類模型有顯著的優(yōu)勢(shì),在此過(guò)程中嵌入的預(yù)訓(xùn)練詞向量不僅能提高分類的精度,更能夠加快整個(gè)模型的擬合速度。在相似代碼搜索任務(wù)上,CVRNN無(wú)論是對(duì)比近幾年提出的前沿的深度學(xué)習(xí)模型還是廣泛應(yīng)用于業(yè)界的代碼相似度檢測(cè)工具都有顯著的優(yōu)勢(shì)。