周培君,吳軍華
(南京工業大學 計算機科學與技術學院,南京 211816) E-mail:413402140@qq.com
代碼注釋又稱程序注釋,是對源代碼的一種簡單易讀的自然語言描述.在實際應用中,大多數程序員只關注代碼而忽略了注釋,這使得程序的可讀性和可維護性大大降低[1].開發人員平均花費超過50%的時間閱讀和理解他人編寫的代碼[2].好的代碼注釋可以幫助開發和維護人員更準確、更快地理解程序,從而節省他們額外的閱讀時間.
隨著軟件代碼規模的不斷擴大,如何幫助開發人員在軟件開發過程中理解、編寫或維護代碼已成為軟件工程領域的重要課題.Java作為最流行的主要編程語言被提出了許多方法來生成注釋,以減輕對代碼進行注釋的人工工作.信息檢索是自動代碼注釋生成研究中最早使用的技術.基于信息檢索技術的評論算法一般采用基于向量空間模型(VSM)、潛在語義索引(LSI)、潛在狄利克雷分配(LDA)或代碼克隆檢測等相關技術.近年來,隨著深度神經網絡在自然語言處理、機器翻譯、圖像識別和語音處理等方面的發展,軟件工程領域中也引入了深度神經網絡來解決代碼注釋問題.絕大多數基于深度神經網絡的代碼注釋工作的靈感來源于自然語言處理(NLP)的最先進的神經機器翻譯(NMT),即將源代碼到注釋的轉換問題表示為編程語言與自然語言之間的翻譯問題.
本文提出了一種組合源碼結構和語義的神經模型Code2Com(Code to Comment),一種基于深度學習的為給定代碼生成注釋的方法.工作主要集中在模型輸入和注釋生成.模型組合了兩種關于源代碼的信息:1)代碼的順序表示;2)抽象語法樹表示.與之前的方法不同,Code2Com以不同的方式對待每種輸入,增加了方法的靈活性.為了克服曝光偏差,模型訓練與推理應在相同的條件下進行預測,Code2Com在訓練過程中提供預測詞序列作為下一步的輸入,以便逐步迫使模型處理自己的錯誤,讓模型在訓練過程中進行更多探索,以此減小訓練和推理之間的差異.
作為自然語言處理(NLP)系統的核心組成部分,語言模型可以提供詞表征和單詞序列的極大似然.語言模型描述了單詞出現在序列中的概率.對于一個由n個按順序構成的自然語言序列x=(x1,…,xn),語言模型根據已知序列(x1,…,xk-1)預測下一個詞語xk的概率,即:
(1)
對語言模型建模時,為了減少建模時的維數災難,Brown等人提出一種N元(N-gram)語言模型的近似方法,即預測的第k個詞的出現僅依賴于前面的k-1個詞:
p(xk|x1,…,xk-1)≈p(xk|xk-n+1,…,xk-1)
(2)
然而,這種N-gram方法有明顯的局限性,例如未考慮自然語言的離散型、組合性和稀疏性.為了解決這個問題,神經網絡(NN)被引入到語言模型的訓練中,例如循環神經網絡(RNN,Recurrent Neural Networks)、長短期記憶網絡(LSTM,Long Short-Term Memory)、門控循環單元網絡(GRU,Gated Recurrent Unit)等.RNN包括3層,將每個輸入映射到向量中的輸入層,在讀取每個輸入向量后循計算并更新隱藏狀態的循環隱藏層,以及利用隱藏狀態計算預測token概率的輸出層.RNN在訓練過程中,每層的梯度大小會在長序列上呈指數增長或衰減[3].梯度爆炸或消失的問題使RNN模型難以學習序列中的長距離相關性.LSTM通過引入一個能夠長時間保存狀態的記憶單元,有效解決學習長期依賴關系的問題.在本文中,我們使用Chung等人提出的GRU,因為它在序列建模方面的性能與LSTM相當[4],但參數較少,易于訓練.
對源代碼和注釋之間的關系進行建模可用于自動代碼注釋.一種直接的方法是將問題轉換為機器翻譯問題,其中源句子是代碼中的token序列,目標句子是相應的注釋序列.Iyer等人[5]提出了一種基于LSTM的注釋生成模型CodeNN,它使用具有注意機制的LSTM為C#和SQL生成注釋.CodeNN通過加入注意力機制,直接將注釋中的單詞與相關代碼token對齊.Allamanis等人[6]使用卷積神經網絡(CNN)和注意力機制生成源代碼的摘要.該方法使用卷積注意模塊對輸入的源代碼提取特征,并確定序列中應注意的重要token.此外,一些論文也將源代碼建模為一系列token[7]和字符[8].這些工作在各種生成代碼注釋和文檔任務中都取得了最先進的性能.還有一些方法考慮了源代碼的結構信息.Liang等人[9]提出了一種基于RNN的AST編碼器Code-RNN.Chen等人[10]提出了用于代碼注釋生成的樹到序列模型Tree2Seq,Tree2Seq使用了一個基于AST的編碼器代替RNN編碼器.
這些方法沒有充分利用代碼的結構和語義信息在特征表示上,如程序語法、函數和方法調用[11],大大降低了代碼表達的質量,這就需要模型隱式地重新學習編程語言的語法,浪費資源并且降低了準確性.其次在編碼器解碼器框架下,訓練時模型的輸入完全采用真實序列標記,而在推理時,解碼器的當前輸入是模型生成的上一個詞.這使得在訓練和推理時預測的詞來自不同的分布,隨著目標序列的增長,訓練和推理之間的差異會導致錯誤的累積.
如圖1所示,Code2Com主要包括數據處理,模型訓練和代碼注釋生成.在該結構中,使用兩個編碼器獲得源代碼的表示:AST編碼器負責讀取AST序列,代碼編碼器負責讀取代碼token序列.解碼器在生成目標序列時一次預測一個單詞.

圖1 Code2Com的整體流程Fig.1 Overview of Code2Com
為了利用Java的語法結構來更好地編碼源代碼,將代碼片段表示為抽象語法樹(AST,Abstract Syntax Tree).通過深度優先搜索對AST的節點進行遍歷,可將Java源代碼轉化為序列化數據供神經網絡學習.然而,AST的節點并不對應語法的所有細節,比如對于嵌套括號等沒有對應的節點,這種模糊性可能會導致將不同的Java方法映射到相同的序列表示.為了保留結構信息,本文在遍歷時使用“(”和“)”將語法樹的中間結點包圍起來,并在括號前面注明該結點本身.例如,考慮圖2中的Java方法.

圖2 Java示例Fig.2 Java example
從根節點MethodDeclaration開始,使用MethodDecla-ration()表示第一層樹結構.接下來遍歷MethodDeclaration節點的子樹,將子樹的葉子節點int放入括號中,即Metho-dDeclaration(int),依次遞歸遍歷所有子樹得到最終序列.
上述序列與AST的對應關系如圖3所示.

圖3 Java代碼片段的AST和處理后的AST序列Fig.3 AST of the Java method and processed AST sequence
Code2Com遵循NMT的標準編碼器-解碼器結構,該結構通常由兩個RNN組成,一個稱為編碼器,編碼器將不定長的輸入數據X=(x1,…,xt)轉換為定長的隱表征序列H=(h1,…,ht),其中t表示輸入序列的長度.另一個稱為解碼器,根據從編碼過程中獲取的隱向量H,解碼器預測生成任意長度的輸出序列Y=(y1,…,yn),其中n表示解碼器預測結果token的數量.
本文使用了一種新的神經注意網絡結構.Bahdanau等人[12]提出的注意力機制被廣泛應用于NMT,閱讀理解,語音識別和計算機視覺.在基于注意的模型中,注意力機制負責將較高的權重值動態分配給解碼器輸入單詞中更相關的token,這樣就提高了解碼器的效率.
如圖4所示,Code2Com利用GRU的優點,設計了一種新的組合代碼的表示方法.在給定源代碼作為輸入的情況下,將代碼序列投影到連續向量空間,利用AST和代碼編碼器獲得源代碼的表示.一旦源代碼被編碼,注意解碼器便開始逐詞生成目標注釋.為了減少訓練和推理之間的差距,在模型訓練階段,通過在預測得分中添加干擾項來獲得新的預測詞,然后從數據分布和模型分布中選擇解碼器下一步的輸入,讓模型能夠更穩健地糾正自己在推理時的錯誤.

圖4 模型結構Fig.4 Model structure
3.2.1 AST編碼器和代碼編碼器
Code2Com使用GRU作為編碼器中的基本構建塊.AST編碼器負責學習代碼的結構表示,具體地從一個相當常見的編碼結構開始,包括為每個輸入編碼的嵌入層.AST編碼器使用GRU逐個讀取AST序列as=(as1,…,asn),通過embedding層將輸出shape(batch_size,vocab_size,embd_dim),其中batch_size為批處理大小,vocab_size為詞表大小,embd_dim為嵌入維度,也就是每個詞對應的詞嵌入維度的向量.通過對每個詞的各個特征進行embedding,可以得到每個特征的向量化表示.接下來是用作AST編碼的GRU單元,GRU將該層的輸入(AS1,…,ASn)映射到隱藏狀態(h1,…,hn).在時間步t,根據當前輸入ASt和上一步隱藏層的狀態ht-1按照式(3)遞歸地更新隱藏狀態ht:
ht=fGRU(ht-1,ASt)
(3)
其中ASi為單詞asi的embedding向量.編碼器從源代碼中學習標識符、控制結構等潛在的特征,并將這些特征編碼到上下文向量中.代碼編碼器工作方式與AST編碼器幾乎相同.
3.2.2 組合注意

(4)

(5)
3.2.3 注釋生成
在獲得代碼片段的表示之后,需要將其解碼為注釋.首先模型根據上一個詞、上下文向量和當前步的隱藏狀態通過線性變換生成目標詞,接下來將目標詞映射到分類得分中,產生對應的維度:
Ot=g(Yt-1,Ct,st)
(6)
Mt=WOt
(7)
其中,Mt為softmax操作前的預測得分.本文通過引入Gumbel[13]噪聲實現多項分布采樣的再參數化.具體地,對于解碼的第t步,在式(7)的Mt中引入正則化項Gt:
Gt=-log(-log(ut))
(8)
(9)

(10)
其中pt表示目標單詞yt的概率分布.最后模型根據pt選擇預測詞:
(11)

(12)
在這部分,進行了大量的實驗來證明本文提出的模型在代碼注釋自動生成任務上的優勢.實驗比較了Code2Com與3種典型方法Seq2Seq+Attn、CodeNN和TreeLSTM的性能,同時還通過性能對比實驗進行了組件分析,以評估每個組件在Code2Com中的貢獻.
實驗使用Allamanis等人[6]提供的數據集,該數據集中包含11個相對較大的Java項目,最初用于同一項目范圍內的11個不同的模型.以往的研究使用的數據集沒有按項目劃分,同一項目中幾乎相同的代碼同時出現在訓練集和測試集中,這讓模型嚴重過擬合了訓練數據,導致BLEU得分虛高.通過按項目劃分數據集,進行跨項目訓練,對不同的項目進行預測任務,以獲得更真實的預測結果:將數據集分成3組,9個項目用于訓練,1個項目用于驗證,1個項目用于測試.這個數據集包含大約70萬個示例.
Code2Com使用TensorFlow框架在GPU上進行訓練.對于編碼器和解碼器,都使用了256個隱藏單元的GRU.單詞嵌入的維數為256,我們發現兩個獨立的嵌入比單一的嵌入空間有更好的性能.在訓練過程中,用Adam優化了模型,使用了默認的超參數:學習率α=0.01,β1=0.9,β2=0.999,β2=0.999,=1e-8,其他參數在[-0.1,0.1]范圍內隨機初始化.對于解碼,使用貪婪搜索算法進行推理,以最小化實驗變量和計算成本.在式(9)中設置temp=0.5,在式(12)中設置μ=12.代碼序列和AST序列大小為100,注釋的序列大小為13,每個序列至少覆蓋80%的訓練集.低于最小長度的短序列用零填充,超過最大長度的長序列將被截斷.
在代碼到注釋任務上評估本文的模型:根據一個Java方法來預測它的注釋.實驗結果證明本文提出方法可以產生注釋,并且在性能上有一定的提高.
4.2.1 性能分析
模型使用BLEU得分[15]進行評估.BLEU是評估模型預測注釋和參考注釋之間文本相似度的指標.本節將我們的方法與Iyer等人[5]提出的注釋生成模型CodeNN進行比較,后者是一種最先進的代碼摘要方法,該方法使用帶有注意力機制的LSTM直接從源代碼的token嵌入生成注釋.此外,我們還比較了兩個將輸入源代碼作為token流讀取的NMT基線:基于注意力的Seq2Seq模型(Seq2Seq+Attn),編碼器和解碼器都采用GRU,Seq2Seq+Attn代表了NLP研究領域強大的現有方法的應用;Tai等人提出的帶有注意輸入子樹,并采用LSTM解碼器的TreeLSTM[16];代碼注釋自動生成任務的性能如表1所示.

表1 Code2Com及相關方法的BLEU得分(%)Table 1 BLEU scores of Code2Com and related methods(%)
表1總結了代碼注釋生成的每種方法的BLEU得分.由于BLEU的關鍵評價內容是N-gram精度,實驗分析了n取不同值時的BLEU得分.Seq2Seq模型在Java方法注釋方面表現優于CodeNN,CodeNN直接從源代碼的token生成注釋時無法學習Java方法的語義,而Seq2Seq模型可以利用GRU為源代碼建立語言模型,有效地學習Java方法的語義.盡管TreeLSTM也利用了語法,但本文的模型同時考慮了代碼的結構和序列信息,并且通過解決曝光偏差問題增強了模型的容錯能力.本文的模型達到了21.37的BLEU分數,與TreeLSTM模型相比,BLEU得分提高了2.97分(相對16.1%),并且高于所有對比模型.這表明我們的方法適用于代碼注釋.我們在表2中展示了測試集中的Java示例,Code2Com生成的注釋最接近正確結果,雖然TreeLSTM和Seq2Seq+Attn也生成了部分真實序列中的token,但是它們不能預測那些在訓練集中不常出現的token.在大多數情況下,模型非常依賴明確的方法名,比如setter和getter方法.如圖5所示,方法名沒有明確說明該方法需要做什么,其他模型便很難正確地注釋.相反,本文的模型可以生成更接近真實結果的token.

表2 代碼注釋預測結果Table 2 Code comment prediction results

圖5 Java示例Fig.5 Java example
4.2.2 組件分析
為了評估理解各組件的貢獻,對模型進行了消融實驗.首先,只考慮代碼的順序表示,在不使用AST的情況下實現了該模型;其次,實現了一個只采用AST節點作為輸入的模型;最后,通過訓練了一個使用計劃采樣的模型,以驗證模型性能的提升不僅僅是一種簡單的正則化形式.模型評估結果見表3.

表3 Code2Com中每個組件的有效性Table 3 Effectiveness of each component in Code2Com
如表3所示,不編碼AST會導致BLEU得分下降,通過對AST分配權重,可以增加對命名token的注意力,并有效地忽略括號、分號等功能性token.其次,簡單地在模型中使用AST而不使用token會大大降低分數,因為不使用token相當于對沒有標識符名稱、類型、APIs,和常量值的代碼進行推理,這非常困難.源碼結構和語義相結合能夠為注釋生成更好的分數.另外,我們觀察到使用Gumbel采樣的模型可以提高源代碼注釋生成的性能,BLEU得分提高了1.17.輸入Code2Com的預測詞可以有效地減輕曝光偏差,這優勢背后的原因可能是Gumbel噪聲的采樣效果更接近真實分布的樣本,可以幫助選擇更有效而健壯的預測詞.此外,參數值的設置直接影響到模型生成質量.本文對temp進行了參數分析,當temp在0.5時得到了最佳模型,如圖6所示.

圖6 Code2Com在不同temp設置下的性能Fig.6 Code2Com performance under different temp settings
4.2.3 句長分析
本節研究了每個模型的性能如何隨著代碼長度而變化.如圖7所示,隨著輸入代碼長度的增加,所有模型BLEU得分都顯示出自然下降,與其他基線相比,在不同代碼長度的度量上,Code2Com性能最好.此外,我們的模型在處理長句方面顯示出穩定的性能.

圖7 BLEU分數與代碼長度的比較Fig.7 Comparison of BLEU score and code length
提出了一種新的自動生成Java代碼注釋模型,考慮了源代碼獨特的語法結構和順序信息.核心思想是通過對代碼的結構和順序內容進行編碼,并在訓練時使用采樣方案將真實詞或優化生成的前一個預測詞作為上下文.實驗結果表明,Code2Com優于現有的方法.在未來的工作中,計劃設計一個復制機制來處理詞匯表外特殊的未知token,同時研究如何來捕獲輸入結構,并將提出的方法擴展到其他機器翻譯問題的軟件工程任務,例如代碼遷移.