蔡瑞初,張盛強(qiáng),許柏炎
(廣東工業(yè)大學(xué) 計(jì)算機(jī)學(xué)院,廣州 510006)
代碼注釋是程序源代碼功能含義的可讀注解,在軟件維護(hù)過(guò)程中起到關(guān)鍵作用[1-2],然而由于編寫(xiě)代碼注釋的時(shí)間成本較高,軟件項(xiàng)目中經(jīng)常出現(xiàn)注釋不全、不清晰,甚至無(wú)注釋等情況。為了解決上述問(wèn)題,軟件工程社區(qū)提出了代碼注釋自動(dòng)生成任務(wù),其目標(biāo)是通過(guò)機(jī)器學(xué)習(xí)的方法自動(dòng)將程序代碼轉(zhuǎn)化為自然語(yǔ)言注釋。代碼注釋生成作為自然語(yǔ)言生成[3-5]的一項(xiàng)重要任務(wù),在近年來(lái)受到廣泛關(guān)注[6-7]。
現(xiàn)有代碼注釋生成方法一般將源代碼表示為序列形式或者抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST)形式。早期經(jīng)典的研究方法[8]使用循環(huán)神經(jīng)網(wǎng)絡(luò)作為編碼器對(duì)源代碼序列進(jìn)行建模。這種方法在建模過(guò)程中只關(guān)注源代碼的序列信息,忽略了源代碼的結(jié)構(gòu)信息,導(dǎo)致生成的注釋質(zhì)量較差。為了捕獲源代碼中的結(jié)構(gòu)信息,文獻(xiàn)[9]將源代碼表示為AST 中的一系列組合路徑,并對(duì)每條路徑進(jìn)行編碼。文獻(xiàn)[10]提出一種基于結(jié)構(gòu)的遍歷方式來(lái)遍歷AST,以獲得更多的結(jié)構(gòu)信息。文獻(xiàn)[11]將源代碼轉(zhuǎn)化為多視角圖,并將結(jié)構(gòu)信息融入深度自注意力網(wǎng)絡(luò)(Transformer)[12]。以上方法雖然從不同結(jié)構(gòu)形式對(duì)源代碼的結(jié)構(gòu)特征進(jìn)行建模,但是缺少對(duì)不同結(jié)構(gòu)形式的融合過(guò)程,也缺乏充分考慮程序代碼的語(yǔ)法結(jié)構(gòu)的類型和層次信息,使得在面對(duì)復(fù)雜程序代碼時(shí)生成的注釋存在準(zhǔn)確率低和可讀性差的問(wèn)題。
針對(duì)上述結(jié)構(gòu)編碼方面的缺陷,本文建立一種結(jié)構(gòu)感知的混合編碼(Structure-aware Hybrid Encoding,SHE)模型,包括序列編碼、語(yǔ)法結(jié)構(gòu)編碼和聚合編碼三層編碼過(guò)程,能夠同時(shí)捕獲源代碼的節(jié)點(diǎn)序列信息和語(yǔ)法結(jié)構(gòu)信息。設(shè)計(jì)一種結(jié)構(gòu)化感知的圖注意力(Structure-aware Graph Attention,SGAT)網(wǎng)絡(luò)作為SHE 的語(yǔ)法結(jié)構(gòu)編碼層,考慮了源代碼的語(yǔ)法結(jié)構(gòu)的層次和類型信息,以提升模型對(duì)復(fù)雜代碼的結(jié)構(gòu)學(xué)習(xí)能力。
根據(jù)對(duì)源代碼的處理方式,可將現(xiàn)有的研究方法分為三類:基于序列的方法,基于樹(shù)的方法和基于圖的方法。
基于序列的方法直接將源代碼轉(zhuǎn)化為序列結(jié)構(gòu)形式。文獻(xiàn)[8]將源代碼轉(zhuǎn)化為序列,并使用帶有注意力機(jī)制[13]的循環(huán)神經(jīng)網(wǎng)絡(luò)來(lái)生成注釋。文獻(xiàn)[14]使用兩個(gè)編碼器分別學(xué)習(xí)源代碼的序列信息和API的序列信息,并在解碼時(shí)通過(guò)API 知識(shí)輔助注釋的生成。
基于樹(shù)的方法通過(guò)語(yǔ)法解析器將源代碼轉(zhuǎn)化為AST。為了捕獲源代碼中的結(jié)構(gòu)信息,文獻(xiàn)[9]將源代碼表示為AST 中的一系列組合路徑,并在解碼時(shí)使用注意力機(jī)制來(lái)選擇相關(guān)路徑。文獻(xiàn)[15]所提出的模型采用兩個(gè)門(mén)控循環(huán)單元(Gated Recurrent Unit,GRU)[16]分別對(duì)源代碼序列和AST 進(jìn)行編碼,并在解碼時(shí)通過(guò)融合源代碼的序列信息和AST 的結(jié)構(gòu)信息來(lái)構(gòu)建上下文向量。文獻(xiàn)[17]提出一種用于代碼注釋生成的編碼器-解碼器框架,該框架將源代碼視為N 叉樹(shù)并通過(guò)與類型相關(guān)的編碼器和受類型限制的解碼器以捕獲更多的結(jié)構(gòu)信息。
基于圖的方法將源代碼轉(zhuǎn)化為圖的形式。文獻(xiàn)[18]將門(mén)控圖神經(jīng)網(wǎng)絡(luò)(Gated Graph Neural Network,GGNN)[19]融入現(xiàn)有的序列編碼器,使得模型能夠捕獲弱結(jié)構(gòu)化數(shù)據(jù)中的長(zhǎng)距離依賴關(guān)系。文獻(xiàn)[20]使用圖神經(jīng)網(wǎng)絡(luò)(Graph Neural Network,GNN)[21]來(lái)提取源代碼的結(jié)構(gòu)特征。文獻(xiàn)[22]將源代碼轉(zhuǎn)化為基于注意力機(jī)制的動(dòng)態(tài)圖,并設(shè)計(jì)一種混合型GNN 來(lái)捕獲局部和全局結(jié)構(gòu)信息。
與上述三種結(jié)構(gòu)編碼研究方法不同,本文將源代碼轉(zhuǎn)化為程序圖,并提出一種基于結(jié)構(gòu)感知的混合編碼模型(SHE)。通過(guò)融合序列編碼層和圖編碼層以更好地捕獲源代碼的節(jié)點(diǎn)序列信息和語(yǔ)法結(jié)構(gòu)信息。
在代碼修復(fù)任務(wù)上,文獻(xiàn)[23-24]均采用了序列模型和圖模型堆疊的三層編碼過(guò)程。但是,本文與上述工作采用了不同的序列編碼層或圖網(wǎng)絡(luò)編碼層。更進(jìn)一步地,代碼注釋生成對(duì)比代碼修復(fù)需要更好地理解代碼的語(yǔ)法結(jié)構(gòu)中蘊(yùn)含的語(yǔ)義信息。本文提出的結(jié)構(gòu)化感知的圖注意力網(wǎng)絡(luò)(SGAT)通過(guò)更好地考慮源代碼的語(yǔ)法結(jié)構(gòu)的層次和類型信息,以對(duì)復(fù)雜代碼有更好的學(xué)習(xí)能力。
首先對(duì)代碼注釋生成任務(wù)進(jìn)行必要的公式化定義。當(dāng)給定一段源代碼C時(shí),代碼注釋生成任務(wù)的目標(biāo)是生成能夠準(zhǔn)確描述該源代碼的自然語(yǔ)言注釋Y={y1,y2,…,y||M}。為了更好地捕獲源 代碼的上下文信息和結(jié)構(gòu)信息,同時(shí)考慮了源代碼的序列表示和抽象語(yǔ)法樹(shù)(AST)表示。長(zhǎng)度為N的源代碼序列表示為C={w1,w2,…,w|N|},其中wi代表源代碼中的第i個(gè)詞。源代碼C的AST 表示需要通過(guò)語(yǔ)法解析器得到。將AST 樹(shù)形表示進(jìn)一步構(gòu)建成程序圖表示G。
程序圖G={V,E}。在程序圖G上,V表示節(jié)點(diǎn)vi的集合,節(jié)點(diǎn)vi={wi,τi}具有節(jié)點(diǎn)值wi和節(jié)點(diǎn)類型τi。節(jié)點(diǎn)構(gòu)建過(guò)程如下:首先,通過(guò)語(yǔ)法解析器將源代碼轉(zhuǎn)化為AST 樹(shù)形表示,AST 包含非終端節(jié)點(diǎn)和終端節(jié)點(diǎn),其中,非終端節(jié)點(diǎn)代表特定的語(yǔ)法規(guī)則,終端節(jié)點(diǎn)代表文本標(biāo)記(token),如標(biāo)識(shí)符、字符串和數(shù)字;然后,所有AST 上的節(jié)點(diǎn)都分配有對(duì)應(yīng)的節(jié)點(diǎn)值和節(jié)點(diǎn)類型,即wi和τi。值得注意的是,當(dāng)終端節(jié)點(diǎn)的節(jié)點(diǎn)值由多個(gè)單詞構(gòu)成時(shí),將該終端節(jié)點(diǎn)拆分為多個(gè)類型相同的子節(jié)點(diǎn)。在程序圖G上,E表示邊{vi,vj,lij}的集合,lij表示節(jié)點(diǎn)vi和vj所連接的邊類型。不同類型的邊構(gòu)建過(guò)程如下:首先,將default 語(yǔ)法邊表示為AST 中節(jié)點(diǎn)的連接關(guān)系,用于語(yǔ)法信息的學(xué)習(xí);然后,在AST 中引入多種具有語(yǔ)義依賴信息的邊。使用NextNode 邊連接兄弟節(jié)點(diǎn),以便信息能夠在兄弟節(jié)點(diǎn)之間以更近的距離傳播。使用SameToken 邊連接所有具有相同節(jié)點(diǎn)值的節(jié)點(diǎn),使得信息能夠跨長(zhǎng)距離傳播。此外,為每條邊添加相應(yīng)的反向邊(reverse),并為每個(gè)節(jié)點(diǎn)添加相應(yīng)的自循環(huán)邊(self-loop),以便對(duì)反向依賴和自依賴進(jìn)行建模。
節(jié)點(diǎn)類型和語(yǔ)法邊是由對(duì)應(yīng)編程語(yǔ)言的語(yǔ)法解析器自動(dòng)生成的,而具有語(yǔ)義依賴信息的邊的構(gòu)建過(guò)程是通用的。因此,本文提出的程序圖構(gòu)建過(guò)程具有通用性,可快速擴(kuò)展到不同的編程語(yǔ)言。以Python 源代碼為例,其對(duì)應(yīng)的程序圖和注釋如圖1所示。
圖1 Python 源代碼對(duì)應(yīng)的程序圖和注釋Fig.1 Corresponding program graph and comment for Python source code
本文提出的SHE 模型整體架構(gòu)如圖2 所示,該模型包含以下三部分:程序圖向量化,結(jié)構(gòu)感知混合編碼器和解碼器。
圖2 SHE 模型的整體架構(gòu)Fig.2 Overall architecture of SHE model
在程序圖G向量化階段,主要對(duì)程序圖的節(jié)點(diǎn)進(jìn)行初始向量化操作,并作為結(jié)構(gòu)感知混合編碼器的輸入。
在結(jié)構(gòu)感知混合編碼器階段,主要包括了序列編碼、語(yǔ)法結(jié)構(gòu)編碼和聚合編碼三層編碼過(guò)程。其核心是通過(guò)融合序列編碼層和圖編碼層以更好地捕獲源代碼的上下文信息和語(yǔ)法結(jié)構(gòu)信息。同時(shí),為了更好地學(xué)習(xí)語(yǔ)法結(jié)構(gòu)信息,本文設(shè)計(jì)的結(jié)構(gòu)化感知的圖注意力(SGAT)網(wǎng)絡(luò)將節(jié)點(diǎn)類型和邊類型的信息引入網(wǎng)絡(luò)結(jié)構(gòu)。
在解碼器階段,主要應(yīng)用Transformer 和復(fù)制機(jī)制[25]有效避免自然語(yǔ)言生成任務(wù)中經(jīng)典的未登錄詞(Out of Vocabulary,OOV)問(wèn)題。
首先,創(chuàng)建詞典的詞嵌入矩陣D,該詞典可通過(guò)詞id 進(jìn)行查找。其次,將節(jié)點(diǎn)值映射到詞典中的詞id。根據(jù)詞id 可得到節(jié)點(diǎn)值嵌入:
其中:Map 表示將詞wi映射為詞id 的函數(shù);Lookup表示詞嵌入查找函數(shù)。
SHE 的Transformer 序列編碼層通過(guò)注意力機(jī)制學(xué)習(xí)節(jié)點(diǎn)序列的表示,但無(wú)法自動(dòng)捕獲節(jié)點(diǎn)序列的位置信息。因此,需要將位置信息融入輸入特征。根據(jù)節(jié)點(diǎn)vi所在的位置i可得到節(jié)點(diǎn)位置嵌入。
其中:dp表示位置嵌入的維 度;表示節(jié)點(diǎn)vi的位置嵌入在第k維的值,當(dāng)k為偶數(shù)時(shí)使用式(2)計(jì)算,當(dāng)k為奇數(shù)時(shí)使用式(3)計(jì)算。
將節(jié)點(diǎn)值嵌入和位置嵌入相加后,可得到節(jié)點(diǎn)的輸入特征X={x1,x2,…,x||N}。
2.5.1 Transformer 序列編碼層
源代碼片段長(zhǎng)度一般較長(zhǎng),經(jīng)典的長(zhǎng)短期記憶網(wǎng)絡(luò)無(wú)法很好地捕獲源代碼序列中的長(zhǎng)期依賴關(guān)系。因此,采用Transformer 來(lái)提取源代碼的上下文信息。Transformer 序列編碼層由多個(gè)相同的層堆疊而成,并在每一層中應(yīng)用多頭注意力機(jī)制(multi-head attention)。將節(jié)點(diǎn)輸入特征X={x1,x2,…,x||N}輸送到Transformer序列編碼層,可得到節(jié)點(diǎn)的輸出特征P={p1,p2,…,p||N}。計(jì)算過(guò)程如式(6)~式(10)所示:
Transformer 的計(jì)算公式可簡(jiǎn)化如下:
2.5.2 SGAT 語(yǔ)法結(jié)構(gòu)編碼層
SHE 的語(yǔ)法結(jié)構(gòu)編碼層負(fù)責(zé)捕獲源代碼的復(fù)雜結(jié)構(gòu)信息。現(xiàn)有的工作一般只考慮抽象語(yǔ)法樹(shù)(AST)中節(jié)點(diǎn)的連接關(guān)系和層次信息,而忽略編碼中的語(yǔ)法類型信息,導(dǎo)致無(wú)法很好地區(qū)分結(jié)構(gòu)相似但語(yǔ)義不同的子樹(shù)或者子圖。源代碼的語(yǔ)法類型包含不同的語(yǔ)義信息。通過(guò)引入語(yǔ)法類型信息,使得模型能夠區(qū)分上述復(fù)雜語(yǔ)法結(jié)構(gòu)。由于現(xiàn)有的圖注意力網(wǎng)絡(luò)無(wú)法很好地適用于多節(jié)點(diǎn)類型和多邊類型的情況,因此本文提出結(jié)構(gòu)化感知的圖注意力網(wǎng)絡(luò)(SGAT)用于程序圖的結(jié)構(gòu)學(xué)習(xí)。
將節(jié)點(diǎn)類型信息和邊類型信息作為SGAT 語(yǔ)法結(jié)構(gòu)編碼層的可學(xué)習(xí)參數(shù),而不是直接將節(jié)點(diǎn)類型信息和邊類型信息作為特征輸入編碼器,使得SGAT能夠更好地學(xué)習(xí)不同的語(yǔ)法結(jié)構(gòu)表示,提升模型對(duì)復(fù)雜程序的結(jié)構(gòu)學(xué)習(xí)能力。具體地,對(duì)于程序圖G中的節(jié)點(diǎn)vi,分別計(jì)算其與鄰居節(jié)點(diǎn)vj的相關(guān)系數(shù)oij:
其中:pi和pj表示經(jīng)過(guò)Transformer 序列編碼層優(yōu)化后的 節(jié)點(diǎn)特 征;τi和τj表示節(jié)點(diǎn)的類型;lij表示節(jié)點(diǎn)之間的邊類型;Ni表示節(jié)點(diǎn)vi的鄰居節(jié)點(diǎn)集合;Wτi和Wτj表示與節(jié)點(diǎn)類型相關(guān)的權(quán)重矩陣;rlij表示與邊類型對(duì)應(yīng)的向量;a表示注意力機(jī)制,用于計(jì)算相關(guān)系數(shù)。
進(jìn)一步地,將相關(guān)系數(shù)oij進(jìn)行歸一化,可得到歸一化注意力系數(shù)gij:
其中:LeakyReLU 表示非線性激活函數(shù);exp 表示以自然常數(shù)e 為底的指數(shù)函數(shù)。
在得到歸一化注意力系數(shù)gij后,對(duì)特征進(jìn)行加權(quán)求和操作,可得到節(jié)點(diǎn)的輸出特征Q={q1,q2,…,q||N}。
其中:Wτj和rlij表示注意力機(jī)制所對(duì)應(yīng)的權(quán)重矩陣和向量;σ表示非線性激活函數(shù)。
同時(shí),采用多頭注意力機(jī)制進(jìn)一步提升模型的表現(xiàn)能力,具體如下:
其中:H表示多頭注意力機(jī)制的頭數(shù);‖表示拼接操作。
SGAT 的計(jì)算公式可簡(jiǎn)化如下:
SGAT 通過(guò)對(duì)程序圖中不同的節(jié)點(diǎn)類型和邊類型引入可學(xué)習(xí)的參數(shù),使得模型能夠區(qū)分不同的節(jié)點(diǎn)類型和邊類型的語(yǔ)義信息,因此能夠更準(zhǔn)確地捕獲源代碼中的結(jié)構(gòu)信息。同時(shí),通過(guò)多頭注意力機(jī)制使得學(xué)習(xí)過(guò)程更加穩(wěn)定,進(jìn)一步提升了模型性能。
2.5.3 Transformer 聚合編碼層
Transformer 序列編碼層通過(guò)多頭注意力機(jī)制來(lái)感知節(jié)點(diǎn)序列的全局信息,SGAT 語(yǔ)法結(jié)構(gòu)編碼層基于圖的信息傳播來(lái)捕獲程序圖的局部結(jié)構(gòu)信息。為了聚合代碼的上下文信息和語(yǔ)法結(jié)構(gòu)信息,SHE 采用Transformer 聚合編碼層有效地聚合前兩層編碼的結(jié)構(gòu)表示并輸出到解碼器。通過(guò)該聚合編碼層,可獲得節(jié)點(diǎn)的最終輸出特征U={u1,u2,…,u||N}。
首先,將節(jié)點(diǎn)序列的初始輸入特征輸送到Transformer 序列編碼層,以提取源代碼中的序列信息。其次,將優(yōu)化后的特征輸送到SGAT 語(yǔ)法結(jié)構(gòu)編碼層,以捕獲源代碼的復(fù)雜結(jié)構(gòu)信息。為了防止網(wǎng)絡(luò)退化問(wèn)題,式(17)使用了殘差連接。最后,采用Transformer 聚合編碼層聚合前兩層的信息并輸出到解碼器。
解碼階段采用Transformer 解碼器來(lái)生成代碼注釋。同時(shí),引入復(fù)制機(jī)制以提高生成的注釋的質(zhì)量。該解碼器包含兩種注意力機(jī)制:一種是基于掩碼的多頭注意力機(jī)制;另一種是基于編碼器-解碼器的多頭注意力機(jī)制?;谘诖a的多頭注意力機(jī)制僅用于解碼器,其允許解碼器結(jié)合之前輸出的單詞序列來(lái)決定當(dāng)前階段所輸出的單詞。在訓(xùn)練過(guò)程中,為了防止未來(lái)信息的泄露,需要遮掩未來(lái)階段的單詞?;诰幋a器-解碼器的多頭注意力機(jī)制則用于編碼器和解碼器之間,其能夠讓解碼器捕捉到編碼器的輸出信息。
通過(guò)Transformer 解碼器,可得到解碼階段t的目標(biāo)詞典的概率分布Pv:
其中:Softmax 表示歸一化指數(shù)函數(shù);Wgen表示可學(xué)習(xí)的參數(shù)矩陣;st表示解碼階段輸出的隱藏狀態(tài)。
在構(gòu)建目標(biāo)詞典時(shí),通常會(huì)過(guò)濾掉出現(xiàn)頻次較低的單詞,以限制目標(biāo)詞典的大小。這些出現(xiàn)頻次較低的單詞不存在于目標(biāo)詞典中,被稱為未登錄詞(OOV)。但是,源代碼中表示數(shù)值或字符的低頻詞包含重要的信息,其經(jīng)常出現(xiàn)在目標(biāo)注釋中。如果忽略了包含重要信息的低頻詞,將使得生成的注釋出現(xiàn)準(zhǔn)確率降低的問(wèn)題。為了緩解OOV 現(xiàn)象,采用復(fù)制機(jī)制以一定的概率從輸入中復(fù)制單詞。
在解碼階段t,計(jì)算隱藏狀態(tài)st與聚合編碼層的輸出特征U={u1,u2,…,u||N}的歸一化注意力系數(shù)fti:
其中:score 表示計(jì)算相關(guān)系數(shù)的函數(shù)。
在得到歸一化注意力系數(shù)fti后,計(jì)算生成概率pg∈[0,1]:
其中:Wptr表示可學(xué)習(xí)的參數(shù)矩陣;ct表示通過(guò)加權(quán)求和得到的上下文向量;dt表示當(dāng)前解碼階段的輸入;Sigmoid 表示非線性激活函數(shù)。
由生成概率pg可得到解碼階段t的目標(biāo)詞典的概率分布Pfin(m):
其中:m表示目標(biāo)詞典中的某個(gè)單詞;wi表示節(jié)點(diǎn)vi的節(jié)點(diǎn)值。因此,每個(gè)單詞的最終輸出概率由其生成概率和復(fù)制概率共同決定。
通過(guò)最小化交叉熵?fù)p失函數(shù)對(duì)模型進(jìn)行訓(xùn)練:
其中:yt表示解碼階段t的目標(biāo)單詞。
實(shí)驗(yàn)數(shù)據(jù)采用文獻(xiàn)[26]提供的Python 數(shù)據(jù)集和文獻(xiàn)[14]提供的Java 數(shù)據(jù)集。Python 數(shù)據(jù)集包含55 538 個(gè)訓(xùn)練樣本、18 505 個(gè)驗(yàn)證樣本和18 502 個(gè)測(cè)試樣本。Java 數(shù)據(jù)集包含69 708 個(gè)訓(xùn)練樣本、8 714 個(gè)驗(yàn)證樣本和8 714 個(gè)測(cè)試樣本。使用相應(yīng)的語(yǔ)法解析器將源代碼轉(zhuǎn)化為AST。對(duì)于Python 源代碼,生成的AST 包含14 種節(jié)點(diǎn)類型。對(duì)于Java 源代碼,生成的AST 包含15 種節(jié)點(diǎn)類型。在AST 的基礎(chǔ)上,通過(guò)添加多種具有語(yǔ)義依賴信息的邊來(lái)構(gòu)建程序圖。
為了更準(zhǔn)確地評(píng)估模型效果,實(shí)驗(yàn)采用BLEU[27]、ROUGE[28]和METEOR[29]三種被廣泛使用的評(píng)價(jià)指標(biāo)從不同角度考慮生成的注釋的質(zhì)量。BLEU 與ROUGE類似,但BLEU側(cè)重于計(jì)算準(zhǔn)確率,而ROUGE 更加關(guān)注召回率。METEOR 通過(guò)引入同義詞集,在計(jì)算得分時(shí)考慮了同義詞及詞性變換的情況。
實(shí)驗(yàn)基于PyTorch 框架在RTX 3090 GPU 上進(jìn)行訓(xùn)練。在訓(xùn)練過(guò)程中,使用Adam[30]作為優(yōu)化器,并使用0.000 05 作為初始學(xué)習(xí)率。在權(quán)重初始化過(guò)程中,使用Xavier[31]作為初始化器。對(duì)于編碼器和解碼器,多頭注意力機(jī)制的隱藏層單元數(shù)設(shè)置為64,頭數(shù)設(shè)置為8,單詞嵌入的維度設(shè)置為512。在編碼過(guò)程中,節(jié)點(diǎn)的最大數(shù)量設(shè)置為400。在解碼過(guò)程中,生成的注釋的最大長(zhǎng)度設(shè)置為50。
為了全面評(píng)估SHE 的性能,選用多個(gè)具有代表性的模型進(jìn)行對(duì)比和分析。
1)CODE-NN[8]是第一個(gè)使用端到端的方式將源代碼轉(zhuǎn)化為注釋的模型,通過(guò)使用帶有注意力機(jī)制的LSTM[32]來(lái)解決代碼注釋生成任務(wù),并取得了顯著的效果。
2)DeepCom[10]通過(guò)基于結(jié)構(gòu)的遍歷方式獲得模型所需的輸入序列。相比于通過(guò)前序遍歷方式所獲得的序列,基于結(jié)構(gòu)的遍歷方式所獲得的序列可還原為原始的AST,在提取結(jié)構(gòu)信息上更加有效。
3)Tree2Seq[33]使用基于樹(shù) 的LSTM 作為編碼器,以顯式地捕獲結(jié)構(gòu)信息。
4)RL+Hybrid2Seq[26]利用LSTM 和基于AST 的LSTM 來(lái)分別提取源代碼的序列特征和結(jié)構(gòu)特征,并采用強(qiáng)化學(xué)習(xí)的方式來(lái)訓(xùn)練模型。
5)API+CODE[14]通過(guò)源代碼中的API 知識(shí)來(lái)輔助代碼注釋生成。
6)Dual Model[34]將代碼生成和代碼注釋生成作為對(duì)偶任務(wù),進(jìn)一步提升了生成的注釋的質(zhì)量。
7)Trans[35]是一種用于表示源代碼中的標(biāo)記之間的相對(duì)位置的方法,并在解碼過(guò)程中應(yīng)用了復(fù)制機(jī)制來(lái)輔助注釋生成。
8)SiT[11]是一 種Transformer的變體。SiT 將源代碼轉(zhuǎn)化為多視角圖,并將結(jié)構(gòu)信息融入Transformer。
3.5.1 模型性能分析
不同模型在Python 數(shù)據(jù)集和Java 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果如表1 和表2 所示,其中加粗表示最優(yōu)的結(jié)果。CODE-NN 的評(píng)價(jià)指標(biāo)得分低于其他模型,因?yàn)槠錄](méi)有建立語(yǔ)言模型,不能很好地捕獲源代碼的序列信息和結(jié)構(gòu)信息。DeepCom、Tree2Seq 和RL+Hybrid2Seq 通過(guò)不同方式對(duì)源代碼進(jìn)行建模,使得模型能夠捕獲源代碼中的結(jié)構(gòu)信息。API+CODE 通過(guò)API 知識(shí)指導(dǎo)注釋的生成,在Java 數(shù)據(jù)集上取得了較高的得分。Dual Model 將代碼生成任務(wù)和代碼注釋生成任務(wù)視為對(duì)偶任務(wù),進(jìn)一步提升了生成的注釋的質(zhì)量。Trans 和SiT 使用Transformer 來(lái)學(xué)習(xí)源代碼的表示,提升了模型的表現(xiàn)能力。
表1 不同模型在Python 數(shù)據(jù)集上的性能對(duì)比結(jié)果 Table 1 Performance comparison results of different models on Python dataset %
表2 不同模型在Java 數(shù)據(jù)集上的性能對(duì)比結(jié)果 Table 2 Performance comparison results of different models on Java dataset %
由表1 和表2 可知,SHE 在Python 數(shù)據(jù)集和Java數(shù)據(jù)集上的評(píng)價(jià)指標(biāo)得分均高于所有基準(zhǔn)模型。與SiT 模型相比,SHE 在Python 數(shù)據(jù)集上的BLEU、ROUGE-L 和METEOR 得分分別提高了2.68%、1.47%和3.82%,在Java數(shù)據(jù)集上的BLEU、ROUGE-L 和METEOR 得分分別提高 了2.51%、2.24% 和3.55%。實(shí)驗(yàn)結(jié)果表明,SHE 能夠更好地捕獲源代碼的節(jié)點(diǎn)序列信息和復(fù)雜的語(yǔ)法結(jié)構(gòu)信息。
3.5.2 模型收斂速度分析
模型的收斂速度決定模型達(dá)到最佳性能時(shí)所需時(shí)間的長(zhǎng)短。不同模型在Java 訓(xùn)練集上的收斂速度對(duì)比結(jié)果如圖3 所示。通過(guò)設(shè)置ROUGE-L 和BLEU性能參考線觀察,SHE 需要約90 個(gè)訓(xùn)練輪數(shù)到達(dá)參考線,而SiT 則需要約175 個(gè)訓(xùn)練輪數(shù)??梢?jiàn),SHE具有結(jié)構(gòu)化感知的混合編碼結(jié)構(gòu),對(duì)復(fù)雜語(yǔ)法結(jié)構(gòu)有更高效的學(xué)習(xí)能力,模型有更快的收斂速度。
圖3 不同模型在Java 訓(xùn)練集上的收斂速度對(duì)比結(jié)果Fig.3 Comparison results of convergence speed of different models on Java training set
3.5.3 模型參數(shù)分析
多頭注意力機(jī)制的頭數(shù)和隱藏層單元數(shù)是影響模型性能的重要參數(shù)。為方便對(duì)比,將所有編碼層的多頭注意力機(jī)制的頭數(shù)和隱藏層單元數(shù)均設(shè)置為相同的值。
不同頭數(shù)的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果如表3 所示,當(dāng)多頭注意力機(jī)制的隱藏層單元數(shù)設(shè)置為64時(shí),隨著頭數(shù)的增加,模型的性能越來(lái)越好,這說(shuō)明在一定程度上增加頭數(shù)能夠使得網(wǎng)絡(luò)從不同子空間捕獲到更豐富的特征。
表3 不同頭數(shù)的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果 Table 3 Experimental results of models with different number of heads on Python dataset %
如表4 所示,在多頭注意力機(jī)制的頭數(shù)設(shè)置為8的情況下,隨著隱藏層單元數(shù)的增加,模型在不同評(píng)價(jià)指標(biāo)上的得分也越來(lái)越高,這說(shuō)明在一定程度上增加隱藏層單元數(shù)能夠提升網(wǎng)絡(luò)的表現(xiàn)能力。
表4 不同隱藏層單元數(shù)的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果 Table 4 Experimental results of models with different number of hidden layer units on Python dataset %
3.5.4 代碼長(zhǎng)度分析
代碼長(zhǎng)度是衡量模型表現(xiàn)能力的重要因素之一。不同代碼長(zhǎng)度的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果如圖4 所示。
圖4 不同代碼長(zhǎng)度的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果Fig.4 Experimental results of models with different code lengths on Python dataset
隨著代碼長(zhǎng)度的增加,不同模型所取得的BLEU 得分和ROUGE_L 得分均呈下降趨勢(shì),這說(shuō)明代碼長(zhǎng)度越長(zhǎng),模型越難捕獲源代碼的完整信息。在相同代碼長(zhǎng)度的情況下,SHE 所取得的BLEU 得分和ROUGE_L 得分高于SiT,這說(shuō)明SHE 對(duì)于更復(fù)雜的程序代碼仍然具有良好的結(jié)構(gòu)學(xué)習(xí)能力。
3.5.5 節(jié)點(diǎn)排列順序分析
程序圖中節(jié)點(diǎn)的排列順序與AST 中節(jié)點(diǎn)的排列順序一致。通過(guò)不同的遍歷方式可得到不同的節(jié)點(diǎn)排列順序。為了探究程序圖中節(jié)點(diǎn)的排列順序?qū)δP托阅艿挠绊?,設(shè)計(jì)相關(guān)對(duì)比實(shí)驗(yàn)。不同的節(jié)點(diǎn)排列順序在Python 數(shù)據(jù)集和Java 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果如表5 和表6 所示。由表5 和表6 可知,在Python 數(shù)據(jù)集和Java 數(shù)據(jù)集上,對(duì)于SiT 和SHE,按不同遍歷方式排列所取得的指標(biāo)得分在相應(yīng)的模型中均沒(méi)有明顯的差距,這可能是因?yàn)榘床煌闅v方式所得到的節(jié)點(diǎn)序列的排列順序雖然不同,但仍然包含AST 中的有效信息。
表5 不同節(jié)點(diǎn)排列順序的模型在Python 數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果 Table 5 Experimental results of models with different node arrangement orders on Python dataset %
表6 不同節(jié)點(diǎn)排列順序的模型在Java數(shù)據(jù)集上的實(shí)驗(yàn)結(jié)果Table 6 Experimental results of models with different node arrangement orders on Java dataset %
為了進(jìn)一步探究模型各組成部分的作用,設(shè)計(jì)消融實(shí)驗(yàn)。在Python 數(shù)據(jù)集上的消融實(shí)驗(yàn)結(jié)果如表7 所示,其中,加粗表示最優(yōu)的結(jié)果,No-SGAT 表示僅考慮源代碼的節(jié)點(diǎn)序列信息,No-ET-NT 表示不區(qū)分程序圖的節(jié)點(diǎn)類型和邊類型,No-ET 表示不考慮程序圖的邊類型信息,No-NT 表示不考慮程序圖的節(jié)點(diǎn)類型信息,No-AEL 表示不采用Transformer聚合編碼層。
表7 在Python 數(shù)據(jù)集上的消融實(shí)驗(yàn)結(jié)果 Table 7 Results of ablation experiments on Python dataset %
由表7 可以看出:僅考慮源代碼的節(jié)點(diǎn)序列信息,將導(dǎo)致模型的性能下降;在不區(qū)分程序圖的節(jié)點(diǎn)類型和邊類型的情況下,模型取得的評(píng)價(jià)指標(biāo)得分均有所提升,這說(shuō)明將源代碼轉(zhuǎn)化為程序圖能夠提高模型的表現(xiàn)能力;在考慮節(jié)點(diǎn)類型信息或邊類型信息的情況下,模型的性能得到進(jìn)一步提升,這表明節(jié)點(diǎn)類型信息和邊類型信息能夠提升編碼模型對(duì)復(fù)雜程序的結(jié)構(gòu)學(xué)習(xí)能力;不采用Transformer 聚合編碼層使得模型無(wú)法有效聚合前兩層的信息,降低了模型捕獲源代碼的節(jié)點(diǎn)序列信息和語(yǔ)法結(jié)構(gòu)信息的能力。
為了進(jìn)一步分析模型所生成的注釋的質(zhì)量,進(jìn)行案例展示。Python 示例和Java 示例具體如下:
由上述可以看出,SHE 所生成的注釋更準(zhǔn)確和流暢。對(duì)于Python 示例,雖然源代碼中沒(méi)有出現(xiàn)單詞“binary”,但SHE 所生成的注釋仍然能夠準(zhǔn)確地預(yù)測(cè)出單詞“binary”。對(duì)于Java 示例,由于源代碼中的方法名不足以描述該代碼段的主要功能,因此SiT 生成的注釋的質(zhì)量較差。但是,SHE 生成的注釋仍然具有較高的可讀性和準(zhǔn)確性。
本文針對(duì)代碼注釋生成任務(wù),提出一種結(jié)構(gòu)感知的混合編碼(SHE)模型,通過(guò)融合序列編碼層和圖編碼層,能夠同時(shí)捕獲程序代碼的節(jié)點(diǎn)序列信息和語(yǔ)法結(jié)構(gòu)信息。針對(duì)程序代碼的語(yǔ)法類型信息,設(shè)計(jì)一種結(jié)構(gòu)化感知的圖注意力(SGAT)網(wǎng)絡(luò),提升了編碼模型對(duì)于復(fù)雜程序代碼的結(jié)構(gòu)學(xué)習(xí)能力。實(shí)驗(yàn)通過(guò)包含多種編程語(yǔ)言的代碼注釋生成的數(shù)據(jù)集驗(yàn)證了SHE 模型對(duì)比基準(zhǔn)模型的提升效果,且能生成更準(zhǔn)確的代碼注釋,同時(shí)消融實(shí)驗(yàn)也驗(yàn)證了SGAT網(wǎng)絡(luò)的有效性。在未來(lái)的工作中,將進(jìn)一步改進(jìn)程序圖的構(gòu)建方式,使得程序圖包含更多的語(yǔ)義信息,以對(duì)復(fù)雜程序代碼有更好的結(jié)構(gòu)學(xué)習(xí)能力。同時(shí),將進(jìn)一步優(yōu)化序列編碼層與圖編碼層的融合方式,以更好地捕獲程序代碼的序列信息和語(yǔ)法結(jié)構(gòu)信息。