王一凡,趙逢禹,艾 均
(上海理工大學(xué) 光電信息與計(jì)算機(jī)工程學(xué)院,上海 200093)
方法名是一種用于標(biāo)識(shí)程序方法的特殊標(biāo)識(shí)符,標(biāo)識(shí)符的質(zhì)量被證明對(duì)源代碼的可讀性和維護(hù)性有顯著影響[1].一個(gè)恰當(dāng)?shù)姆椒粌H要描述方法是什么,還要描述它的功能,因此方法的抽象命名通常具有挑戰(zhàn)性.
方法代碼抽象命名的相關(guān)研究已經(jīng)有許多成果,如基于啟發(fā)式規(guī)則的方法[2]和應(yīng)用文本分析技術(shù)的方法[3].文獻(xiàn)[2]提取方法代碼的數(shù)據(jù)流和控制流相關(guān)的特征,作為方法名模板對(duì)應(yīng)的規(guī)則,根據(jù)啟發(fā)式規(guī)則去檢測(cè)具有與規(guī)則類(lèi)似特征的方法代碼名稱(chēng)是否準(zhǔn)確.應(yīng)用文本分析技術(shù)的方法的基本思想是將方法代碼當(dāng)作自然語(yǔ)言文本進(jìn)行處理.文獻(xiàn)[3]將源代碼中關(guān)鍵字標(biāo)識(shí)符剔除,使用文本主題提取算法得到文本主題特征,將提取的文本摘要作為方法名.
最近,研究者將機(jī)器學(xué)習(xí)的技術(shù)引入到方法代碼的抽象命名中[4,7],這種方法的關(guān)鍵技術(shù)是將方法代碼抽象成新的代碼表示作為代碼特征,應(yīng)用機(jī)器學(xué)習(xí)技術(shù)自動(dòng)學(xué)習(xí)代碼特征,根據(jù)相似的代碼具有相似的特征的原則,從給定的方法名語(yǔ)料庫(kù)中匹配出已有類(lèi)似的方法名或者組合出高質(zhì)量新的方法名,因此抽象出的代碼表示要包含程序豐富的語(yǔ)義信息.文獻(xiàn)[4]引入log-bilinear神經(jīng)語(yǔ)言模型,捕獲源代碼中能夠表示長(zhǎng)距離上下文特征的token序列,將方法名以及方法名的子標(biāo)記嵌入到高維空間,推薦在空間中與新方法具有相似嵌入的方法名.文獻(xiàn)[5]引入卷積注意力神經(jīng)網(wǎng)絡(luò),對(duì)方法的token序列進(jìn)行卷積操作,將方法片段總結(jié)為具有描述性的方法名的過(guò)程看作一個(gè)翻譯任務(wù).然而token序列缺失了方法代碼的結(jié)構(gòu)信息,提取到的方法特征不能表示方法代碼的強(qiáng)結(jié)構(gòu)性,為了提取代碼語(yǔ)義信息,文獻(xiàn)[6]提出一種通用的基于路徑的方法,使用AST中葉子節(jié)點(diǎn)之間的路徑和葉子節(jié)點(diǎn)相關(guān)聯(lián)的標(biāo)識(shí)符作為一條路徑來(lái)表示方法,這使得深度學(xué)習(xí)模型能夠利用源代碼的結(jié)構(gòu)特性,而不是將其視為一個(gè)扁平的token序列.在此結(jié)構(gòu)基礎(chǔ)上,文獻(xiàn)[7]又提出code2vec,進(jìn)一步引入注意力神經(jīng)網(wǎng)絡(luò)將AST路徑集聚合成分布式向量,具有相似AST路徑的方法的分布式向量在高維連續(xù)空間彼此接近,從而捕獲方法之間以及方法名稱(chēng)之間的語(yǔ)義相似性.
文獻(xiàn)[6]和文獻(xiàn)[7]的研究,考慮到了程序的強(qiáng)結(jié)構(gòu)特性[8],然而對(duì)程序的控制流信息以及程序動(dòng)態(tài)可執(zhí)行性等特征的關(guān)注度不夠.
本文針對(duì)提取出的方法特征缺失控制流信息以及不能反映程序動(dòng)態(tài)可執(zhí)行的問(wèn)題,提出基于基本路徑學(xué)習(xí)的方法來(lái)對(duì)方法代碼抽象命名.首先研究了方法代碼控制流圖的構(gòu)建,接著從本文構(gòu)建的控制流圖中抽取基本路徑作為方法代碼的中間表示.然后將基本路徑集轉(zhuǎn)換成向量形式輸入到引入注意力機(jī)制[9]的LSTM神經(jīng)網(wǎng)絡(luò)[10],以方法標(biāo)簽為輸出目標(biāo)設(shè)計(jì)損失函數(shù).最后為了驗(yàn)證學(xué)習(xí)基本路徑訓(xùn)練出的模型能更好理解方法代碼的語(yǔ)義信息,構(gòu)建了基于文獻(xiàn)[6]的數(shù)據(jù)集的訓(xùn)練樣本,訓(xùn)練得到代碼自動(dòng)命名模型,并對(duì)實(shí)驗(yàn)結(jié)果進(jìn)行了分析.
神經(jīng)網(wǎng)絡(luò)是以向量作為輸入,所以需要將程序表示為向量,并且轉(zhuǎn)換出的向量能夠表示程序的語(yǔ)義信息.因此可以使用中間表示作為程序和轉(zhuǎn)換成的向量之間的橋梁.為了使神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)到程序的控制流以及動(dòng)態(tài)可執(zhí)行的語(yǔ)義信息,本文以方法作為程序大小的粒度,使用基本路徑作為方法代碼的中間表示,并將基本路徑轉(zhuǎn)換成向量形式輸入神經(jīng)網(wǎng)絡(luò),訓(xùn)練出基于基本路徑學(xué)習(xí)的程序方法名代碼自動(dòng)命名模型.
基于基本路徑學(xué)習(xí)的方法代碼自動(dòng)命名分為兩個(gè)階段:訓(xùn)練階段和自動(dòng)命名階段.如圖1所示給出了訓(xùn)練階段的流程,該流程包括構(gòu)建控制流圖,提取基本路徑集,將提取的基本路徑轉(zhuǎn)換成向量表示以及訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型4步.自動(dòng)命名階段將待命名的方法代碼按照訓(xùn)練階段前3步進(jìn)行處理,第4步直接將向量輸入到訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)中,解析出與方法名標(biāo)簽庫(kù)中語(yǔ)義最接近的方法名.下面詳細(xì)介紹訓(xùn)練階段的具體過(guò)程.
第1步.構(gòu)建方法代碼控制流圖
從源代碼中提取以方法為粒度的代碼段,通過(guò)編譯器將方法代碼分析優(yōu)化得到三地址碼中間表示[11],然后通過(guò)本文給出的控制流圖構(gòu)建算法,將三地址碼代碼段進(jìn)行代碼段分塊,并在基本塊之間添加控制流邊得到控制流圖.
第2步.提取基本路徑集
將控制流圖中的每個(gè)基本塊作為基本路徑中的路徑節(jié)點(diǎn),并且將路徑節(jié)點(diǎn)中變量的具體值抽象為具體的token以達(dá)到降低token數(shù)量級(jí)的目的,降低詞嵌入學(xué)習(xí)過(guò)程的復(fù)雜度[12].接著借鑒深度優(yōu)先搜索的思想,構(gòu)建算法提取出方法代碼的所有執(zhí)行路徑作為方法的基本路徑集合.
第3步.將基本路徑集轉(zhuǎn)進(jìn)行詞嵌入
首先將基本路徑集中的字符建立token詞表,此時(shí)基本路徑中的對(duì)應(yīng)的token使用詞表中的token編號(hào)進(jìn)行映射表示.然后將token通過(guò)word2vec技術(shù)[13]訓(xùn)練得到每個(gè)token的詞向量.最后通過(guò)字典表映射關(guān)系將基本路徑轉(zhuǎn)換成向量形式.
第4步.訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型
方法代碼的語(yǔ)義信息上下文依賴(lài)較長(zhǎng),本文選擇擁有記憶單元的LSTM神經(jīng)網(wǎng)絡(luò)進(jìn)行建模.首先將上一步得到的路徑向量輸入到LSTM神經(jīng)網(wǎng)絡(luò)中,得到每條路徑的上下文基本路徑向量.然后將每條路徑向量聚合為代碼向量,在聚合過(guò)程中使用注意力機(jī)制對(duì)每條路徑向量計(jì)算權(quán)重,其中的權(quán)重是訓(xùn)練過(guò)程中根據(jù)每條路徑的影響程度不斷更新.最后通過(guò)softmax層計(jì)算出代碼向量和方法名標(biāo)簽的分布概率,損失函數(shù)計(jì)算出正確分布與預(yù)測(cè)分布的差值,使用梯度優(yōu)化算法完成整個(gè)模型的閉環(huán)訓(xùn)練.最終得到LSTM神經(jīng)網(wǎng)絡(luò)以及注意力向量的參數(shù).
3.1.1 三地址碼與Soot
本文使用更加抽象和簡(jiǎn)潔的三地址碼作為方法代碼的中間表示.三地址碼由一組類(lèi)似于匯編語(yǔ)言的指令組成,每條指令最多由3個(gè)運(yùn)算分量組成,且最多只執(zhí)行一次運(yùn)算,這里的運(yùn)算通常是計(jì)算、比較或者分支跳轉(zhuǎn).表1給出了源代碼對(duì)應(yīng)的“三地址”指令序列的示例.
表1 三地址碼指令序列
考慮到模型的復(fù)用性,模型可以選擇特定于語(yǔ)言的編譯器來(lái)優(yōu)化處理源代碼,以得到方法代碼的三地址碼序列.本文選擇Java語(yǔ)言編寫(xiě)的源代碼作為研究對(duì)象,因此選擇分析Java程序的Soot靜態(tài)編譯器,生成Soot特有的三地址碼:Jimple[14].Soot能夠?qū)ava代碼中的循環(huán)結(jié)構(gòu)轉(zhuǎn)換成goto指令的形式,使代碼中只有分支和順序兩種控制流向,達(dá)到簡(jiǎn)化方法代碼控制邏輯的目的.
3.1.2 控制流圖構(gòu)建算法
傳統(tǒng)方法構(gòu)建的控制流圖分支結(jié)構(gòu)復(fù)雜,在此基礎(chǔ)上提取的基本路徑包含的路徑較長(zhǎng)[15],使用LSTM神經(jīng)網(wǎng)絡(luò)訓(xùn)練時(shí)會(huì)出現(xiàn)梯度消失與梯度爆炸的問(wèn)題.本文在三地址代碼序列基礎(chǔ)上構(gòu)建程序控制流圖,優(yōu)化程序控制結(jié)構(gòu),達(dá)到縮短基本路徑長(zhǎng)度的目的.控制流圖中的每一個(gè)節(jié)點(diǎn)表示一個(gè)基本塊(基本塊表示控制流只能從基本塊第一條指令進(jìn)入,除了基本塊的最后一條指令控制流在離開(kāi)基本塊前不發(fā)生跳轉(zhuǎn)),每一條邊表示兩個(gè)基本塊之間的控制流轉(zhuǎn)移.
構(gòu)建程序控制流圖的步驟如下:
1)通過(guò)靜態(tài)編譯器分析得到三地址代碼序列;
2)在三地址代碼序列的基礎(chǔ)上進(jìn)行基本塊劃分;
3)基本塊之間添加邊信息,構(gòu)建程序控制流程圖.
算法1.方法代碼控制流圖構(gòu)建算法
輸入:方法代碼序列,Method={token1,…,tokenn},其中Method為方法代碼,tokenn為方法中的詞法單元.
輸出:方法代碼對(duì)應(yīng)的控制流圖CFG={V,E},其中V={BasicBlock1,…,BasicBlockn},E={〈BasicBlocki,BasicBlockj〉,…}.
處理:
//Soot靜態(tài)分析器得到三地址碼序列
1.Jimple3AC←SootCompilerProcess(Method);
//每條三地址碼指令記錄在數(shù)組Stmts[]里
2.Stmts[]←Abstract3ACProcess(Jimple3AC);
3.V=φ;//控制流圖節(jié)點(diǎn)序列,每個(gè)節(jié)點(diǎn)代表一個(gè)基本塊BasicBlock
4.while(i new BasicBlock;//創(chuàng)建一個(gè)新的基本塊 BasicBlock.add(Stmts[i]); while(Stmts[i]與Stmts[i+1]能夠組成基本塊){ BasicBlock.add(Stmts[i+1]); i++; } V=V∪{BasicBlock}; i++; } 5.E=?//控制流圖邊序列 //添加基本塊之間的控制流邊 6.for each(BasicBlockiin V){ if(BasicBlocki有后繼基本塊) E=E∪{〈BasicBlocki,BasicBlocki+1〉}; if(BasicBlocki中存在跳轉(zhuǎn)指令){ for each(BasicBlockjin V){ if(BasicBlockj中存在BasicBlocki的跳轉(zhuǎn)目標(biāo)指令) E=E∪{〈BasicBlocki,BasicBlockj〉}; } } } 7.return new CFG = {V,E};//返回控制流圖 算法1首先將方法代碼通過(guò)Soot靜態(tài)分析器得到三地址碼指令序列,然后將指令信息封裝記錄在數(shù)組里.接著劃分本塊集合作為控制流圖的節(jié)點(diǎn)集,在劃分過(guò)程中,步驟4兩條指令是否能夠劃分到同一基本塊的依據(jù)是判斷Stmts[i+1]是否為跳轉(zhuǎn)指令的目標(biāo)指令或者是跳轉(zhuǎn)指令的下一條指令,如果是則不能劃分到同一基本塊.最后在基本塊與其后繼基本塊之間以及與其跳轉(zhuǎn)到的目標(biāo)基本塊之間添加控制流邊.基本塊之間的控制流信息包含順序流向和跳轉(zhuǎn)流向,分別反映出方法代碼中的順序和分支循環(huán)邏輯. 基本路徑的提取是在控制流圖的基礎(chǔ)上,通過(guò)深度優(yōu)先搜索算法提取所有的基本路徑,構(gòu)成基本路徑集[16].基本路徑集中的元素是基本路徑,基本路徑是指控制流圖中從起始結(jié)點(diǎn)到終止結(jié)點(diǎn)所經(jīng)過(guò)的一系列基本塊序列,且該路徑上至少有一條邊在其它路徑中沒(méi)有出現(xiàn)過(guò),即包含其它路徑未曾引入的基本塊.基本路徑滿(mǎn)足覆蓋方法代碼中的所有分支單元和循環(huán)單元,能夠覆蓋控制流圖的各個(gè)執(zhí)行路徑以及覆蓋所有邏輯控制條件,所以從側(cè)面反映出方法代碼的上下文語(yǔ)句執(zhí)行的因果關(guān)系,因此將基本路徑作為方法代碼的中間表示能夠表示程序的動(dòng)態(tài)可執(zhí)行的特征. 基本路徑集定義: 1)基本路徑集是由多條基本路徑組成,且每條基本路徑是集合中的獨(dú)立路徑; 2)集合中的路徑包含控制流圖的所有節(jié)點(diǎn)和邊; 3)每條獨(dú)立路徑至少包含一條在其它路徑中未出現(xiàn)過(guò)的邊. 算法2.基本路徑提取算法 輸入:方法代碼對(duì)應(yīng)的控制流圖CG={V,E},其中V={v0,…,vn},E={〈vi,vj〉,…〈vm,vn〉}. 輸出:基本路徑集BPS={path1,…,pathn},其中pathi= 〈vi,…,vm〉. 處理: 1.初始化: BasicPathSet=?;//基本路徑集 Stack.push(v0);//控制流圖開(kāi)始節(jié)點(diǎn)入棧 VisitedV[]=?;//記錄已被訪問(wèn)節(jié)點(diǎn) VisitedEdge[]=?;//記錄已被訪問(wèn)邊 2.while(Stack is not null){ curV = Stack.peek();//讀取當(dāng)前棧頂節(jié)點(diǎn) Visited[curV];//記錄訪問(wèn)過(guò)得節(jié)點(diǎn) if(curV是終結(jié)節(jié)點(diǎn)){ //返回一條基本路徑,該路徑序列為棧頂?shù)綏5椎哪嫘蛐蛄?/p> path←TraversalStack(); BasicPathSet=BasicPathSet∪{path}; while(Stack.peek()不存在未訪問(wèn)過(guò)的邊){ Stack.pop();//彈出棧中不存在未訪問(wèn)過(guò)邊的節(jié)點(diǎn) } }else if(curV不存在未訪問(wèn)過(guò)的邊){ Stack.pop();//回退狀態(tài) }else{ e=〈curV,nextV〉∈E且未訪問(wèn)過(guò); if(nextV未被訪問(wèn)過(guò)){ VisitedEdge[e]=true;//記錄訪問(wèn)過(guò)該條邊 Stack.push(nextV);//壓入堆棧 }else if(nextV不存在未訪問(wèn)過(guò)的邊){ VisitedEdge[e]= true; path1←TraversalStack(); path2←SearchExistedSubseq(nextV); //返回BasicPathSet中任意一條基本路徑中存在nextV節(jié)點(diǎn)的后繼序列 BasicPathSet=BasicPathSet∪{〈path1,path2〉}; } } } 算法從控制流圖入口節(jié)點(diǎn)開(kāi)始深度優(yōu)先遍歷,首先判斷棧頂節(jié)點(diǎn)curV是否為終結(jié)節(jié)點(diǎn),如果是則輸出棧的逆序序列作為一條基本路徑.不是則判斷curV是否存在未訪問(wèn)過(guò)的邊,如果不存在則彈出curV開(kāi)始下一次循環(huán).如果存在則取以curV為開(kāi)始節(jié)點(diǎn)的邊e且后繼節(jié)點(diǎn)為nextV,此時(shí)判斷nextV是否訪問(wèn)過(guò),如果未被訪問(wèn)過(guò)則記錄邊e被訪問(wèn)且將nextV壓入堆棧;否則如果nextV被訪問(wèn)過(guò)且不存在未訪問(wèn)過(guò)的邊,則此時(shí)基本路徑集中的某條基本路徑pathi必定存在nextV節(jié)點(diǎn),輸出棧的逆序序列并拼接pathi中從nextV開(kāi)始的序列作為一條基本路徑. 模型輸入:方法代碼對(duì)應(yīng)的基本路徑集BPS={path1,…,pathn}的向量表示,其中pathi=〈bi1,…,biε〉;因?yàn)槊織l基本路徑有不同數(shù)量的token,為了讓神經(jīng)網(wǎng)絡(luò)擁有相同長(zhǎng)度的向量輸入,引入超參數(shù)ε作為基本路徑向量的固定長(zhǎng)度.方法名標(biāo)簽序列嵌入向量y. 訓(xùn)練結(jié)果:得到LSTM神經(jīng)層和注意力層的權(quán)重參數(shù),以及方法代碼的向量. softmax層:調(diào)用TensorFlow框架的softmax層計(jì)算代碼向量v在方法名標(biāo)簽嵌入空間的預(yù)測(cè)概率分布,以cross-entropy損失函數(shù)[17]計(jì)算正確分布p和預(yù)測(cè)分布q之間的誤差,使用Adam梯度優(yōu)化算法更新模型參數(shù)訓(xùn)練網(wǎng)絡(luò)[18].其中正確分布是方法代碼的標(biāo)簽在標(biāo)簽庫(kù)的真實(shí)分布(是正確標(biāo)注值為1,否則為0),模型預(yù)測(cè)得到的概率分布q是由方法代碼向量v和標(biāo)簽庫(kù)中對(duì)應(yīng)標(biāo)簽嵌入向量的點(diǎn)積得到. 為了驗(yàn)證將基本路徑作為方法代碼的中間表示能夠包含更豐富的語(yǔ)義信息,訓(xùn)練得到的模型能夠更準(zhǔn)確的命名方法名,本文與先前工作進(jìn)行實(shí)驗(yàn)對(duì)比,并從學(xué)習(xí)開(kāi)銷(xiāo)的角度分析方法優(yōu)劣.為了統(tǒng)計(jì)分析模型在對(duì)不同復(fù)雜度的方法代碼命名的表現(xiàn),以圈復(fù)雜度作為衡量代碼復(fù)雜度的依據(jù)進(jìn)行實(shí)驗(yàn). 本文采用文獻(xiàn)[6]中的Java-med數(shù)據(jù)集,該數(shù)據(jù)集包含了1000個(gè)高質(zhì)量Java項(xiàng)目,約400萬(wàn)個(gè)方法,其中900個(gè)項(xiàng)目作為訓(xùn)練集,100個(gè)作為測(cè)試集,并將該數(shù)據(jù)集經(jīng)過(guò)如下處理構(gòu)建實(shí)驗(yàn)訓(xùn)練樣本. 首先將項(xiàng)目中的類(lèi)依據(jù)方法簽名(即返回值類(lèi)型、方法名、參數(shù)類(lèi)型)拆分出各個(gè)方法,并抽取方法名作為該方法的標(biāo)注,在拆分過(guò)程中過(guò)濾掉類(lèi)中的set、get類(lèi)型方法,這些方法對(duì)于模型訓(xùn)練沒(méi)有多少價(jià)值[19].然后,將拆分出的方法代碼按照3.1.2節(jié)的方法構(gòu)建出控制流圖.最后,將控制流圖按照3.2節(jié)中論述的方法提取出基本路徑集,以方法代碼的基本路徑集作為模型的輸入,方法名標(biāo)簽作為標(biāo)注,構(gòu)成如表2所示的訓(xùn)練數(shù)據(jù)格式. 表2 訓(xùn)練數(shù)據(jù)格式 本文在操作系統(tǒng)為Ubuntu 16.04.7 LTS的服務(wù)器上進(jìn)行相關(guān)實(shí)驗(yàn).設(shè)備硬件環(huán)境為:處理器是Intel?CoreTMi7-7700 CPU@4.20 GHz*8,內(nèi)存為64GB,顯卡為NVIDIA GeForce GTX 1080Ti.設(shè)備軟件環(huán)境為:深度學(xué)習(xí)框架是TensorFlow_gpu-1.4.0,Python使用Python3.7版本. 步驟1.構(gòu)建控制流圖 將數(shù)據(jù)集中預(yù)處理好的方法代碼依次保存在單獨(dú)文件中,通過(guò)處理程序調(diào)用soot工具包中的Main處理類(lèi)將方法源代碼文件轉(zhuǎn)換成Jimple三地址碼文件.編寫(xiě)B(tài)asicBlock類(lèi)作為抽象基本塊的數(shù)據(jù)結(jié)構(gòu),編寫(xiě)Edge類(lèi)記錄基本塊之間邊信息,編寫(xiě)CFG類(lèi)聚合基本塊以及邊信息作為控制流圖保存的數(shù)據(jù)結(jié)構(gòu). 步驟2.提取基本路徑集 編寫(xiě)處理程序讀入控制流圖數(shù)據(jù),調(diào)用3.2節(jié)設(shè)計(jì)的算法得到基本路徑集合,將每個(gè)方法代碼以{方法名標(biāo)注,基本路徑集}的數(shù)據(jù)組合形式輸出到具體的訓(xùn)練集和測(cè)試集的文件中. 步驟3.將基本路徑和方法名標(biāo)簽量化表示 將基本路徑集中的每條基本路徑的三地址碼序列使用moses切詞工具得到token,并對(duì)token建立字典庫(kù).使用word2vec詞向量嵌入工具對(duì)token嵌入學(xué)習(xí),得到每個(gè)token對(duì)應(yīng)的詞向量.根據(jù)字典映射關(guān)系將基本路徑中的每個(gè)token使用詞向量表示.方法名標(biāo)注組成的標(biāo)簽庫(kù)中的方法名token隨機(jī)生成一個(gè)唯一數(shù)字對(duì)標(biāo)注進(jìn)行量化表示,以便softmax計(jì)算方法代碼向量在標(biāo)簽庫(kù)中的概率分布. 步驟4.訓(xùn)練LSTM神經(jīng)網(wǎng)絡(luò) 將訓(xùn)練集采用十折交叉驗(yàn)證的方法用于訓(xùn)練LSTM神經(jīng)網(wǎng)絡(luò),網(wǎng)絡(luò)初始化參數(shù)設(shè)置為深度學(xué)習(xí)廣泛使用的值,并且設(shè)置不同LSTM神經(jīng)網(wǎng)絡(luò)的隱藏層數(shù),觀察其對(duì)結(jié)果的影響,最終選擇256個(gè)隱藏層數(shù)效果最好.基本路徑轉(zhuǎn)換成的向量固定長(zhǎng)度設(shè)置為200,能夠覆蓋大多數(shù)基本路徑序列長(zhǎng)度,因此LSTM網(wǎng)絡(luò)的時(shí)間步長(zhǎng)也設(shè)置為ε.dropout設(shè)置為0.25,更新模型參數(shù)的minibatch設(shè)置為64,迭代訓(xùn)練epoch設(shè)置為4,使用Adam梯度優(yōu)化算法進(jìn)行訓(xùn)練,學(xué)習(xí)速率初始設(shè)置為0.01,然后當(dāng)交叉驗(yàn)證時(shí)準(zhǔn)確率停止改善時(shí),讓學(xué)習(xí)速率減半繼續(xù)迭代,最終得到代碼自動(dòng)命名模型. 步驟5.模型評(píng)估對(duì)比 將本文構(gòu)建的訓(xùn)練集按照文獻(xiàn)[6,7]的方法訓(xùn)練得到對(duì)比模型.將測(cè)試集分別輸入到對(duì)比模型和本文得到的模型,采用文獻(xiàn)[7]中的評(píng)價(jià)指標(biāo)對(duì)模型結(jié)果統(tǒng)計(jì)分析. 實(shí)驗(yàn)1.模型對(duì)比結(jié)果分析 如表3所示本文提出基于基本路徑學(xué)習(xí)的代碼自動(dòng)命名模型與其它基線模型:Paths+CRFs[6]、Paths+Attn[7]的對(duì)比情況.本文提出的模型記為BasicPaths+Attn,其中BasicPaths表示不使用注意力機(jī)制進(jìn)行的對(duì)比實(shí)驗(yàn),具體做法是在聚合上下文基本路徑向量時(shí)直接使用求和取平均的方法得到方法代碼向量. 表3 模型得分評(píng)估 觀察實(shí)驗(yàn)結(jié)果得出本文提出的模型得分優(yōu)于其它模型.文獻(xiàn)[7]提出的AST路徑更多的表示上下文token的依賴(lài)關(guān)聯(lián),而本文提取的基本路徑不局限于單個(gè)上下文token,而是上下文關(guān)聯(lián)的基本塊之間的依賴(lài)關(guān)系,并且LSTM網(wǎng)絡(luò)能夠捕獲到長(zhǎng)距離的上下文依賴(lài),因此本文模型能夠更好理解方法代碼的語(yǔ)義.并且本文使用注意力機(jī)制作用于方法代碼的關(guān)鍵基本路徑,使聚合得到的方法代碼向量更能夠表示代碼的特征信息,從而訓(xùn)練得到的模型效果更好. 實(shí)驗(yàn)2.模型訓(xùn)練開(kāi)銷(xiāo)分析 將基線模型與本文模型在處理好的數(shù)據(jù)集上進(jìn)行訓(xùn)練,并且基線模型使用他們默認(rèn)的超參數(shù).對(duì)于本文模型,調(diào)整LSTM神經(jīng)網(wǎng)絡(luò)隱藏層以及基本路徑序列長(zhǎng)度等參數(shù),觀察對(duì)于F1值的影響,確定最佳的超參數(shù).在調(diào)整過(guò)程中神經(jīng)網(wǎng)絡(luò)超參數(shù)根據(jù)硬件性能從大往小進(jìn)行調(diào)整,基本路徑序列的長(zhǎng)度從短到長(zhǎng)進(jìn)行設(shè)置,最終確定LSTM隱藏層數(shù)為256,基本路徑序列長(zhǎng)度固定為200.本文也基于該超參數(shù)討論與基線模型的訓(xùn)練時(shí)間開(kāi)銷(xiāo)對(duì)比,統(tǒng)計(jì)結(jié)果如圖2所示. 圖2 模型訓(xùn)練時(shí)間開(kāi)銷(xiāo)對(duì)比 觀察訓(xùn)練時(shí)間開(kāi)銷(xiāo)可知,Paths+Attn模型在較短時(shí)間內(nèi)達(dá)到最佳效果,本文模型在27小時(shí)之后分?jǐn)?shù)超過(guò)基線模型并在42個(gè)小時(shí)達(dá)到最佳效果.Paths+Attn模型的輸入吞吐量達(dá)到每分鐘1000個(gè)方法,所以能夠在較短時(shí)間內(nèi)學(xué)習(xí)大量的方法代碼,因此能夠在短時(shí)間內(nèi)達(dá)到較好的效果.而本文模型采用的LSTM神經(jīng)網(wǎng)絡(luò)計(jì)算開(kāi)銷(xiāo)高于基線模型,模型輸入吞吐量較小,然而隨著學(xué)習(xí)大量數(shù)據(jù)網(wǎng)絡(luò)記憶單元的參數(shù)進(jìn)行調(diào)整,網(wǎng)絡(luò)輸出的上下文基本路徑向量包含更多的上下文信息,因此隨著訓(xùn)練時(shí)間的推移本文模型的效果優(yōu)于基線模型. 實(shí)驗(yàn)3.方法代碼復(fù)雜度影響分析 本文在構(gòu)建控制流圖過(guò)程中根據(jù)點(diǎn)邊計(jì)算法得到方法代碼的圈復(fù)雜度,以統(tǒng)計(jì)分析復(fù)雜度對(duì)模型的性能影響.針對(duì)本模型,方法代碼圈復(fù)雜度數(shù)量增大,其控制流圖包含的邊和節(jié)點(diǎn)線性增加,為了滿(mǎn)足邏輯覆蓋以及分支覆蓋,提取出的基本路基長(zhǎng)度也隨著線性增加,在訓(xùn)練過(guò)程中受LSTM的時(shí)間步的限制,基本路徑向量上下文關(guān)聯(lián)性會(huì)逐漸丟失,進(jìn)而影響模型效果.為了評(píng)估方法代碼邏輯復(fù)雜度對(duì)本模型的影響,本文選擇圈復(fù)雜度數(shù)在1-12之間的方法代碼檢驗(yàn)?zāi)P偷腇1得分情況,并且與基線模型進(jìn)行對(duì)比. 如圖3所示本文提出的模型與基線模型在方法代碼圈復(fù)雜度數(shù)相同時(shí)的對(duì)比情況.從圖中可以看出,隨著代碼復(fù)雜度的增加,模型性能均逐漸下降.在復(fù)雜度較低情況下,Paths+Attn模型由于模型設(shè)計(jì)簡(jiǎn)單且此時(shí)AST路徑較短,模型能更好的學(xué)習(xí)到代碼特征.然而隨著復(fù)雜度的提升,AST路徑增多且變長(zhǎng),基線模型效果降低,但是基本路徑的數(shù)量與控制結(jié)構(gòu)的數(shù)量相關(guān),因此本文模型所需要學(xué)習(xí)的路徑數(shù)量較少,并且本文模型中采用LSTM神經(jīng)網(wǎng)絡(luò)能夠捕獲更長(zhǎng)的上下文依賴(lài)性,因此在復(fù)雜度提升的情況下,本文模型表現(xiàn)的更好. 圖3 方法代碼復(fù)雜度影響對(duì)比 本研究提出基于基本路徑學(xué)習(xí)的代碼自動(dòng)命名模型.首先使用編譯器對(duì)源代碼優(yōu)化分析,得到方法代碼的三地址碼序列并構(gòu)建基于三地址碼的控制流圖.然后構(gòu)建算法提取基本路徑集合.最后構(gòu)建神經(jīng)網(wǎng)絡(luò)模型學(xué)習(xí)基本路徑集的特征,迭代訓(xùn)練得到自動(dòng)命名模型. 實(shí)驗(yàn)結(jié)果表明,本文提取能夠反映控制流信息以及方法代碼可執(zhí)行特征的基本路徑作為代碼中間表示,訓(xùn)練出的模型能夠更好的捕獲代碼語(yǔ)義信息,并且對(duì)復(fù)雜度更高的代碼,模型能更好學(xué)習(xí)到代碼特征.在未來(lái)的工作中,將繼續(xù)研究將得到的代碼向量應(yīng)用于代碼缺陷檢測(cè)的任務(wù)中.3.2 基本路徑提取算法
3.3 神經(jīng)網(wǎng)絡(luò)模型
4 實(shí)驗(yàn)與結(jié)果
4.1 數(shù)據(jù)集
4.2 實(shí)驗(yàn)環(huán)境
4.3 實(shí)驗(yàn)過(guò)程
4.4 實(shí)驗(yàn)結(jié)果與分析
5 結(jié) 論