蔡瑞初,張盛強,許柏炎
(廣東工業大學 計算機學院,廣州 510006)
代碼注釋是程序源代碼功能含義的可讀注解,在軟件維護過程中起到關鍵作用[1-2],然而由于編寫代碼注釋的時間成本較高,軟件項目中經常出現注釋不全、不清晰,甚至無注釋等情況。為了解決上述問題,軟件工程社區提出了代碼注釋自動生成任務,其目標是通過機器學習的方法自動將程序代碼轉化為自然語言注釋。代碼注釋生成作為自然語言生成[3-5]的一項重要任務,在近年來受到廣泛關注[6-7]。
現有代碼注釋生成方法一般將源代碼表示為序列形式或者抽象語法樹(Abstract Syntax Tree,AST)形式。早期經典的研究方法[8]使用循環神經網絡作為編碼器對源代碼序列進行建模。這種方法在建模過程中只關注源代碼的序列信息,忽略了源代碼的結構信息,導致生成的注釋質量較差。為了捕獲源代碼中的結構信息,文獻[9]將源代碼表示為AST 中的一系列組合路徑,并對每條路徑進行編碼。文獻[10]提出一種基于結構的遍歷方式來遍歷AST,以獲得更多的結構信息。文獻[11]將源代碼轉化為多視角圖,并將結構信息融入深度自注意力網絡(Transformer)[12]。以上方法雖然從不同結構形式對源代碼的結構特征進行建模,但是缺少對不同結構形式的融合過程,也缺乏充分考慮程序代碼的語法結構的類型和層次信息,使得在面對復雜程序代碼時生成的注釋存在準確率低和可讀性差的問題。
針對上述結構編碼方面的缺陷,本文建立一種結構感知的混合編碼(Structure-aware Hybrid Encoding,SHE)模型,包括序列編碼、語法結構編碼和聚合編碼三層編碼過程,能夠同時捕獲源代碼的節點序列信息和語法結構信息。設計一種結構化感知的圖注意力(Structure-aware Graph Attention,SGAT)網絡作為SHE 的語法結構編碼層,考慮了源代碼的語法結構的層次和類型信息,以提升模型對復雜代碼的結構學習能力。
根據對源代碼的處理方式,可將現有的研究方法分為三類:基于序列的方法,基于樹的方法和基于圖的方法。
基于序列的方法直接將源代碼轉化為序列結構形式。文獻[8]將源代碼轉化為序列,并使用帶有注意力機制[13]的循環神經網絡來生成注釋。文獻[14]使用兩個編碼器分別學習源代碼的序列信息和API的序列信息,并在解碼時通過API 知識輔助注釋的生成。
基于樹的方法通過語法解析器將源代碼轉化為AST。為了捕獲源代碼中的結構信息,文獻[9]將源代碼表示為AST 中的一系列組合路徑,并在解碼時使用注意力機制來選擇相關路徑。文獻[15]所提出的模型采用兩個門控循環單元(Gated Recurrent Unit,GRU)[16]分別對源代碼序列和AST 進行編碼,并在解碼時通過融合源代碼的序列信息和AST 的結構信息來構建上下文向量。文獻[17]提出一種用于代碼注釋生成的編碼器-解碼器框架,該框架將源代碼視為N 叉樹并通過與類型相關的編碼器和受類型限制的解碼器以捕獲更多的結構信息。
基于圖的方法將源代碼轉化為圖的形式。文獻[18]將門控圖神經網絡(Gated Graph Neural Network,GGNN)[19]融入現有的序列編碼器,使得模型能夠捕獲弱結構化數據中的長距離依賴關系。文獻[20]使用圖神經網絡(Graph Neural Network,GNN)[21]來提取源代碼的結構特征。文獻[22]將源代碼轉化為基于注意力機制的動態圖,并設計一種混合型GNN 來捕獲局部和全局結構信息。
與上述三種結構編碼研究方法不同,本文將源代碼轉化為程序圖,并提出一種基于結構感知的混合編碼模型(SHE)。通過融合序列編碼層和圖編碼層以更好地捕獲源代碼的節點序列信息和語法結構信息。
在代碼修復任務上,文獻[23-24]均采用了序列模型和圖模型堆疊的三層編碼過程。但是,本文與上述工作采用了不同的序列編碼層或圖網絡編碼層。更進一步地,代碼注釋生成對比代碼修復需要更好地理解代碼的語法結構中蘊含的語義信息。本文提出的結構化感知的圖注意力網絡(SGAT)通過更好地考慮源代碼的語法結構的層次和類型信息,以對復雜代碼有更好的學習能力。
首先對代碼注釋生成任務進行必要的公式化定義。當給定一段源代碼C時,代碼注釋生成任務的目標是生成能夠準確描述該源代碼的自然語言注釋Y={y1,y2,…,y||M}。為了更好地捕獲源 代碼的上下文信息和結構信息,同時考慮了源代碼的序列表示和抽象語法樹(AST)表示。長度為N的源代碼序列表示為C={w1,w2,…,w|N|},其中wi代表源代碼中的第i個詞。源代碼C的AST 表示需要通過語法解析器得到。將AST 樹形表示進一步構建成程序圖表示G。
程序圖G={V,E}。在程序圖G上,V表示節點vi的集合,節點vi={wi,τi}具有節點值wi和節點類型τi。節點構建過程如下:首先,通過語法解析器將源代碼轉化為AST 樹形表示,AST 包含非終端節點和終端節點,其中,非終端節點代表特定的語法規則,終端節點代表文本標記(token),如標識符、字符串和數字;然后,所有AST 上的節點都分配有對應的節點值和節點類型,即wi和τi。值得注意的是,當終端節點的節點值由多個單詞構成時,將該終端節點拆分為多個類型相同的子節點。在程序圖G上,E表示邊{vi,vj,lij}的集合,lij表示節點vi和vj所連接的邊類型。不同類型的邊構建過程如下:首先,將default 語法邊表示為AST 中節點的連接關系,用于語法信息的學習;然后,在AST 中引入多種具有語義依賴信息的邊。使用NextNode 邊連接兄弟節點,以便信息能夠在兄弟節點之間以更近的距離傳播。使用SameToken 邊連接所有具有相同節點值的節點,使得信息能夠跨長距離傳播。此外,為每條邊添加相應的反向邊(reverse),并為每個節點添加相應的自循環邊(self-loop),以便對反向依賴和自依賴進行建模。
節點類型和語法邊是由對應編程語言的語法解析器自動生成的,而具有語義依賴信息的邊的構建過程是通用的。因此,本文提出的程序圖構建過程具有通用性,可快速擴展到不同的編程語言。以Python 源代碼為例,其對應的程序圖和注釋如圖1所示。

圖1 Python 源代碼對應的程序圖和注釋Fig.1 Corresponding program graph and comment for Python source code
本文提出的SHE 模型整體架構如圖2 所示,該模型包含以下三部分:程序圖向量化,結構感知混合編碼器和解碼器。

圖2 SHE 模型的整體架構Fig.2 Overall architecture of SHE model
在程序圖G向量化階段,主要對程序圖的節點進行初始向量化操作,并作為結構感知混合編碼器的輸入。
在結構感知混合編碼器階段,主要包括了序列編碼、語法結構編碼和聚合編碼三層編碼過程。其核心是通過融合序列編碼層和圖編碼層以更好地捕獲源代碼的上下文信息和語法結構信息。同時,為了更好地學習語法結構信息,本文設計的結構化感知的圖注意力(SGAT)網絡將節點類型和邊類型的信息引入網絡結構。
在解碼器階段,主要應用Transformer 和復制機制[25]有效避免自然語言生成任務中經典的未登錄詞(Out of Vocabulary,OOV)問題。
首先,創建詞典的詞嵌入矩陣D,該詞典可通過詞id 進行查找。其次,將節點值映射到詞典中的詞id。根據詞id 可得到節點值嵌入:

其中:Map 表示將詞wi映射為詞id 的函數;Lookup表示詞嵌入查找函數。
SHE 的Transformer 序列編碼層通過注意力機制學習節點序列的表示,但無法自動捕獲節點序列的位置信息。因此,需要將位置信息融入輸入特征。根據節點vi所在的位置i可得到節點位置嵌入。

其中:dp表示位置嵌入的維 度;表示節點vi的位置嵌入在第k維的值,當k為偶數時使用式(2)計算,當k為奇數時使用式(3)計算。
將節點值嵌入和位置嵌入相加后,可得到節點的輸入特征X={x1,x2,…,x||N}。

2.5.1 Transformer 序列編碼層
源代碼片段長度一般較長,經典的長短期記憶網絡無法很好地捕獲源代碼序列中的長期依賴關系。因此,采用Transformer 來提取源代碼的上下文信息。Transformer 序列編碼層由多個相同的層堆疊而成,并在每一層中應用多頭注意力機制(multi-head attention)。將節點輸入特征X={x1,x2,…,x||N}輸送到Transformer序列編碼層,可得到節點的輸出特征P={p1,p2,…,p||N}。計算過程如式(6)~式(10)所示:


Transformer 的計算公式可簡化如下:

2.5.2 SGAT 語法結構編碼層
SHE 的語法結構編碼層負責捕獲源代碼的復雜結構信息。現有的工作一般只考慮抽象語法樹(AST)中節點的連接關系和層次信息,而忽略編碼中的語法類型信息,導致無法很好地區分結構相似但語義不同的子樹或者子圖。源代碼的語法類型包含不同的語義信息。通過引入語法類型信息,使得模型能夠區分上述復雜語法結構。由于現有的圖注意力網絡無法很好地適用于多節點類型和多邊類型的情況,因此本文提出結構化感知的圖注意力網絡(SGAT)用于程序圖的結構學習。
將節點類型信息和邊類型信息作為SGAT 語法結構編碼層的可學習參數,而不是直接將節點類型信息和邊類型信息作為特征輸入編碼器,使得SGAT能夠更好地學習不同的語法結構表示,提升模型對復雜程序的結構學習能力。具體地,對于程序圖G中的節點vi,分別計算其與鄰居節點vj的相關系數oij:

其中:pi和pj表示經過Transformer 序列編碼層優化后的 節點特 征;τi和τj表示節點的類型;lij表示節點之間的邊類型;Ni表示節點vi的鄰居節點集合;Wτi和Wτj表示與節點類型相關的權重矩陣;rlij表示與邊類型對應的向量;a表示注意力機制,用于計算相關系數。
進一步地,將相關系數oij進行歸一化,可得到歸一化注意力系數gij:

其中:LeakyReLU 表示非線性激活函數;exp 表示以自然常數e 為底的指數函數。
在得到歸一化注意力系數gij后,對特征進行加權求和操作,可得到節點的輸出特征Q={q1,q2,…,q||N}。

其中:Wτj和rlij表示注意力機制所對應的權重矩陣和向量;σ表示非線性激活函數。
同時,采用多頭注意力機制進一步提升模型的表現能力,具體如下:

其中:H表示多頭注意力機制的頭數;‖表示拼接操作。
SGAT 的計算公式可簡化如下:

SGAT 通過對程序圖中不同的節點類型和邊類型引入可學習的參數,使得模型能夠區分不同的節點類型和邊類型的語義信息,因此能夠更準確地捕獲源代碼中的結構信息。同時,通過多頭注意力機制使得學習過程更加穩定,進一步提升了模型性能。
2.5.3 Transformer 聚合編碼層
Transformer 序列編碼層通過多頭注意力機制來感知節點序列的全局信息,SGAT 語法結構編碼層基于圖的信息傳播來捕獲程序圖的局部結構信息。為了聚合代碼的上下文信息和語法結構信息,SHE 采用Transformer 聚合編碼層有效地聚合前兩層編碼的結構表示并輸出到解碼器。通過該聚合編碼層,可獲得節點的最終輸出特征U={u1,u2,…,u||N}。

首先,將節點序列的初始輸入特征輸送到Transformer 序列編碼層,以提取源代碼中的序列信息。其次,將優化后的特征輸送到SGAT 語法結構編碼層,以捕獲源代碼的復雜結構信息。為了防止網絡退化問題,式(17)使用了殘差連接。最后,采用Transformer 聚合編碼層聚合前兩層的信息并輸出到解碼器。
解碼階段采用Transformer 解碼器來生成代碼注釋。同時,引入復制機制以提高生成的注釋的質量。該解碼器包含兩種注意力機制:一種是基于掩碼的多頭注意力機制;另一種是基于編碼器-解碼器的多頭注意力機制。基于掩碼的多頭注意力機制僅用于解碼器,其允許解碼器結合之前輸出的單詞序列來決定當前階段所輸出的單詞。在訓練過程中,為了防止未來信息的泄露,需要遮掩未來階段的單詞。基于編碼器-解碼器的多頭注意力機制則用于編碼器和解碼器之間,其能夠讓解碼器捕捉到編碼器的輸出信息。
通過Transformer 解碼器,可得到解碼階段t的目標詞典的概率分布Pv:

其中:Softmax 表示歸一化指數函數;Wgen表示可學習的參數矩陣;st表示解碼階段輸出的隱藏狀態。
在構建目標詞典時,通常會過濾掉出現頻次較低的單詞,以限制目標詞典的大小。這些出現頻次較低的單詞不存在于目標詞典中,被稱為未登錄詞(OOV)。但是,源代碼中表示數值或字符的低頻詞包含重要的信息,其經常出現在目標注釋中。如果忽略了包含重要信息的低頻詞,將使得生成的注釋出現準確率降低的問題。為了緩解OOV 現象,采用復制機制以一定的概率從輸入中復制單詞。
在解碼階段t,計算隱藏狀態st與聚合編碼層的輸出特征U={u1,u2,…,u||N}的歸一化注意力系數fti:

其中:score 表示計算相關系數的函數。
在得到歸一化注意力系數fti后,計算生成概率pg∈[0,1]:

其中:Wptr表示可學習的參數矩陣;ct表示通過加權求和得到的上下文向量;dt表示當前解碼階段的輸入;Sigmoid 表示非線性激活函數。
由生成概率pg可得到解碼階段t的目標詞典的概率分布Pfin(m):

其中:m表示目標詞典中的某個單詞;wi表示節點vi的節點值。因此,每個單詞的最終輸出概率由其生成概率和復制概率共同決定。
通過最小化交叉熵損失函數對模型進行訓練:

其中:yt表示解碼階段t的目標單詞。
實驗數據采用文獻[26]提供的Python 數據集和文獻[14]提供的Java 數據集。Python 數據集包含55 538 個訓練樣本、18 505 個驗證樣本和18 502 個測試樣本。Java 數據集包含69 708 個訓練樣本、8 714 個驗證樣本和8 714 個測試樣本。使用相應的語法解析器將源代碼轉化為AST。對于Python 源代碼,生成的AST 包含14 種節點類型。對于Java 源代碼,生成的AST 包含15 種節點類型。在AST 的基礎上,通過添加多種具有語義依賴信息的邊來構建程序圖。
為了更準確地評估模型效果,實驗采用BLEU[27]、ROUGE[28]和METEOR[29]三種被廣泛使用的評價指標從不同角度考慮生成的注釋的質量。BLEU 與ROUGE類似,但BLEU側重于計算準確率,而ROUGE 更加關注召回率。METEOR 通過引入同義詞集,在計算得分時考慮了同義詞及詞性變換的情況。
實驗基于PyTorch 框架在RTX 3090 GPU 上進行訓練。在訓練過程中,使用Adam[30]作為優化器,并使用0.000 05 作為初始學習率。在權重初始化過程中,使用Xavier[31]作為初始化器。對于編碼器和解碼器,多頭注意力機制的隱藏層單元數設置為64,頭數設置為8,單詞嵌入的維度設置為512。在編碼過程中,節點的最大數量設置為400。在解碼過程中,生成的注釋的最大長度設置為50。
為了全面評估SHE 的性能,選用多個具有代表性的模型進行對比和分析。
1)CODE-NN[8]是第一個使用端到端的方式將源代碼轉化為注釋的模型,通過使用帶有注意力機制的LSTM[32]來解決代碼注釋生成任務,并取得了顯著的效果。
2)DeepCom[10]通過基于結構的遍歷方式獲得模型所需的輸入序列。相比于通過前序遍歷方式所獲得的序列,基于結構的遍歷方式所獲得的序列可還原為原始的AST,在提取結構信息上更加有效。
3)Tree2Seq[33]使用基于樹 的LSTM 作為編碼器,以顯式地捕獲結構信息。
4)RL+Hybrid2Seq[26]利用LSTM 和基于AST 的LSTM 來分別提取源代碼的序列特征和結構特征,并采用強化學習的方式來訓練模型。
5)API+CODE[14]通過源代碼中的API 知識來輔助代碼注釋生成。
6)Dual Model[34]將代碼生成和代碼注釋生成作為對偶任務,進一步提升了生成的注釋的質量。
7)Trans[35]是一種用于表示源代碼中的標記之間的相對位置的方法,并在解碼過程中應用了復制機制來輔助注釋生成。
8)SiT[11]是一 種Transformer的變體。SiT 將源代碼轉化為多視角圖,并將結構信息融入Transformer。
3.5.1 模型性能分析
不同模型在Python 數據集和Java 數據集上的實驗結果如表1 和表2 所示,其中加粗表示最優的結果。CODE-NN 的評價指標得分低于其他模型,因為其沒有建立語言模型,不能很好地捕獲源代碼的序列信息和結構信息。DeepCom、Tree2Seq 和RL+Hybrid2Seq 通過不同方式對源代碼進行建模,使得模型能夠捕獲源代碼中的結構信息。API+CODE 通過API 知識指導注釋的生成,在Java 數據集上取得了較高的得分。Dual Model 將代碼生成任務和代碼注釋生成任務視為對偶任務,進一步提升了生成的注釋的質量。Trans 和SiT 使用Transformer 來學習源代碼的表示,提升了模型的表現能力。

表1 不同模型在Python 數據集上的性能對比結果 Table 1 Performance comparison results of different models on Python dataset %

表2 不同模型在Java 數據集上的性能對比結果 Table 2 Performance comparison results of different models on Java dataset %
由表1 和表2 可知,SHE 在Python 數據集和Java數據集上的評價指標得分均高于所有基準模型。與SiT 模型相比,SHE 在Python 數據集上的BLEU、ROUGE-L 和METEOR 得分分別提高了2.68%、1.47%和3.82%,在Java數據集上的BLEU、ROUGE-L 和METEOR 得分分別提高 了2.51%、2.24% 和3.55%。實驗結果表明,SHE 能夠更好地捕獲源代碼的節點序列信息和復雜的語法結構信息。
3.5.2 模型收斂速度分析
模型的收斂速度決定模型達到最佳性能時所需時間的長短。不同模型在Java 訓練集上的收斂速度對比結果如圖3 所示。通過設置ROUGE-L 和BLEU性能參考線觀察,SHE 需要約90 個訓練輪數到達參考線,而SiT 則需要約175 個訓練輪數。可見,SHE具有結構化感知的混合編碼結構,對復雜語法結構有更高效的學習能力,模型有更快的收斂速度。

圖3 不同模型在Java 訓練集上的收斂速度對比結果Fig.3 Comparison results of convergence speed of different models on Java training set
3.5.3 模型參數分析
多頭注意力機制的頭數和隱藏層單元數是影響模型性能的重要參數。為方便對比,將所有編碼層的多頭注意力機制的頭數和隱藏層單元數均設置為相同的值。
不同頭數的模型在Python 數據集上的實驗結果如表3 所示,當多頭注意力機制的隱藏層單元數設置為64時,隨著頭數的增加,模型的性能越來越好,這說明在一定程度上增加頭數能夠使得網絡從不同子空間捕獲到更豐富的特征。

表3 不同頭數的模型在Python 數據集上的實驗結果 Table 3 Experimental results of models with different number of heads on Python dataset %
如表4 所示,在多頭注意力機制的頭數設置為8的情況下,隨著隱藏層單元數的增加,模型在不同評價指標上的得分也越來越高,這說明在一定程度上增加隱藏層單元數能夠提升網絡的表現能力。

表4 不同隱藏層單元數的模型在Python 數據集上的實驗結果 Table 4 Experimental results of models with different number of hidden layer units on Python dataset %
3.5.4 代碼長度分析
代碼長度是衡量模型表現能力的重要因素之一。不同代碼長度的模型在Python 數據集上的實驗結果如圖4 所示。

圖4 不同代碼長度的模型在Python 數據集上的實驗結果Fig.4 Experimental results of models with different code lengths on Python dataset
隨著代碼長度的增加,不同模型所取得的BLEU 得分和ROUGE_L 得分均呈下降趨勢,這說明代碼長度越長,模型越難捕獲源代碼的完整信息。在相同代碼長度的情況下,SHE 所取得的BLEU 得分和ROUGE_L 得分高于SiT,這說明SHE 對于更復雜的程序代碼仍然具有良好的結構學習能力。
3.5.5 節點排列順序分析
程序圖中節點的排列順序與AST 中節點的排列順序一致。通過不同的遍歷方式可得到不同的節點排列順序。為了探究程序圖中節點的排列順序對模型性能的影響,設計相關對比實驗。不同的節點排列順序在Python 數據集和Java 數據集上的實驗結果如表5 和表6 所示。由表5 和表6 可知,在Python 數據集和Java 數據集上,對于SiT 和SHE,按不同遍歷方式排列所取得的指標得分在相應的模型中均沒有明顯的差距,這可能是因為按不同遍歷方式所得到的節點序列的排列順序雖然不同,但仍然包含AST 中的有效信息。

表5 不同節點排列順序的模型在Python 數據集上的實驗結果 Table 5 Experimental results of models with different node arrangement orders on Python dataset %

表6 不同節點排列順序的模型在Java數據集上的實驗結果Table 6 Experimental results of models with different node arrangement orders on Java dataset %
為了進一步探究模型各組成部分的作用,設計消融實驗。在Python 數據集上的消融實驗結果如表7 所示,其中,加粗表示最優的結果,No-SGAT 表示僅考慮源代碼的節點序列信息,No-ET-NT 表示不區分程序圖的節點類型和邊類型,No-ET 表示不考慮程序圖的邊類型信息,No-NT 表示不考慮程序圖的節點類型信息,No-AEL 表示不采用Transformer聚合編碼層。

表7 在Python 數據集上的消融實驗結果 Table 7 Results of ablation experiments on Python dataset %
由表7 可以看出:僅考慮源代碼的節點序列信息,將導致模型的性能下降;在不區分程序圖的節點類型和邊類型的情況下,模型取得的評價指標得分均有所提升,這說明將源代碼轉化為程序圖能夠提高模型的表現能力;在考慮節點類型信息或邊類型信息的情況下,模型的性能得到進一步提升,這表明節點類型信息和邊類型信息能夠提升編碼模型對復雜程序的結構學習能力;不采用Transformer 聚合編碼層使得模型無法有效聚合前兩層的信息,降低了模型捕獲源代碼的節點序列信息和語法結構信息的能力。
為了進一步分析模型所生成的注釋的質量,進行案例展示。Python 示例和Java 示例具體如下:


由上述可以看出,SHE 所生成的注釋更準確和流暢。對于Python 示例,雖然源代碼中沒有出現單詞“binary”,但SHE 所生成的注釋仍然能夠準確地預測出單詞“binary”。對于Java 示例,由于源代碼中的方法名不足以描述該代碼段的主要功能,因此SiT 生成的注釋的質量較差。但是,SHE 生成的注釋仍然具有較高的可讀性和準確性。
本文針對代碼注釋生成任務,提出一種結構感知的混合編碼(SHE)模型,通過融合序列編碼層和圖編碼層,能夠同時捕獲程序代碼的節點序列信息和語法結構信息。針對程序代碼的語法類型信息,設計一種結構化感知的圖注意力(SGAT)網絡,提升了編碼模型對于復雜程序代碼的結構學習能力。實驗通過包含多種編程語言的代碼注釋生成的數據集驗證了SHE 模型對比基準模型的提升效果,且能生成更準確的代碼注釋,同時消融實驗也驗證了SGAT網絡的有效性。在未來的工作中,將進一步改進程序圖的構建方式,使得程序圖包含更多的語義信息,以對復雜程序代碼有更好的結構學習能力。同時,將進一步優化序列編碼層與圖編碼層的融合方式,以更好地捕獲程序代碼的序列信息和語法結構信息。