張世琨 謝 睿,2 葉 蔚 陳 龍,2
1(北京大學軟件工程國家工程研究中心 北京 100871) 2(北京大學軟件與微電子學院 北京 100871)ruixie@pku.edu.cn)
代碼摘要(code summary)是對一段源代碼簡短的自然語言描述[1],高質(zhì)量的代碼摘要能夠有效幫助代碼的理解和軟件的維護工作.而代碼自動摘要(code summarization)技術(shù)通過自動化地生成代碼摘要來輔助程序理解和軟件維護,可以有效地降低軟件的開發(fā)維護成本[2].因此,自動化地生成高質(zhì)量代碼摘要一直是軟件工程研究的重要任務(wù)之一[3-4].
據(jù)統(tǒng)計,在軟件開發(fā)生命周期中,將近59%的時間是用于程序理解及其相關(guān)活動的[5].然而由于人工編寫代碼摘要過程乏味、耗時較長,代碼摘要的編寫工作在實際軟件開發(fā)過程中往往不受重視,在大部分軟件項目中,代碼摘要常常出現(xiàn)不匹配、過時甚至丟失的問題.因此,代碼自動摘要工具的引入,可以將軟件開發(fā)人員從乏味的代碼摘要編寫工作中解脫出來,極大地提高軟件開發(fā)效率、降低軟件開發(fā)成本.
代碼自動摘要在軟件開發(fā)生命周期的很多過程中都具有潛在的應(yīng)用價值,例如:
1) 代碼理解.當軟件開發(fā)人員加入現(xiàn)有項目或者了解已有開源代碼庫時,代碼摘要可以幫助開發(fā)人員快速熟悉該代碼庫的核心部分.
2) 代碼審查.審查人員在審查具體代碼時,代碼摘要可以幫助審查人員快速理解修改代碼的主要功能,使代碼審查人員更快地找到本次修改的關(guān)鍵部分.
3) 代碼定位.在程序維護期間,開發(fā)人員在搜索感興趣的代碼區(qū)域時,通常會跳躍式地閱讀代碼,一次只讀取幾行代碼[6],此時,代碼摘要可以幫助快速理解局部代碼的主要功能,快速定位其需要的代碼.
早期的代碼自動摘要主要使用信息檢索(infor-mation retrieval)的方法來生成摘要.文獻[6-7]通過搜索相似的代碼來生成摘要;文獻[8]則通過從給定代碼的符號序列中抽取關(guān)鍵字的方式來生成摘要.雖然這些方法取得了一定的效果,但是它們過于依賴命名的規(guī)范性,并且,如果代碼庫中沒有和給定代碼相似的代碼,這些方法將無法工作.
隨著深度學習的興起,越來越多的學者利用神經(jīng)網(wǎng)絡(luò)對代碼的符號序列建模.例如,Allamanis等人[9]利用卷積神經(jīng)網(wǎng)絡(luò)和注意力機制從代碼符號中捕獲信息,預(yù)測函數(shù)的變量名;Iyer等人[10]提出了CodeNN模型,使用長短時記憶網(wǎng)絡(luò)對代碼和摘要進行建模,實現(xiàn)了代碼自動摘要和代碼搜索;Loyola等人[11]則利用最先進的機器翻譯模型——帶有注意力機制的序列-序列模型,自動地生成代碼提交評論(git commit message).
在自然語言處理中,機器翻譯和文本摘要是2個和代碼自動摘要相似的任務(wù),它們都能看成是序列到序列(sequence to sequence, Seq2Seq)的生成任務(wù).代碼自動摘要的輸入是代碼,輸出是自然語言,輸入輸出不在一個空間里面,從這個角度講,代碼自動摘要像機器翻譯.但機器翻譯中輸入、輸出的序列幾乎是一一對應(yīng)的,而代碼摘要通常是源代碼的高度概括,代碼摘要的長度通常也遠小于代碼長度,從這一特點來說,代碼摘要與文本摘要更加接近.因此,代碼自動摘要的難點不僅僅在于如何利用程序語言和自然語言之間的差異性,更好地捕獲代碼中的語義,還在于如何更好地篩選信息,更好地找到代碼關(guān)鍵信息進行歸納總結(jié),因而代碼自動摘要是一項非常具有挑戰(zhàn)性的任務(wù).
除此之外,和自然語言處理領(lǐng)域的機器翻譯和文本摘要任務(wù)相比,代碼自動摘要任務(wù)還具有2個特點:
1) 代碼的結(jié)構(gòu)化特性
自然語言結(jié)構(gòu)化特性相對較弱,相比之下,代碼不僅具有結(jié)構(gòu)性,而且其結(jié)構(gòu)是明確的、沒有二義性.并且,和自然語言的語法解析樹相比,代碼的抽象語法樹層次更深、結(jié)構(gòu)更復(fù)雜[12].代碼的這種結(jié)構(gòu)化特性對于代碼摘要任務(wù)而言既是挑戰(zhàn)也是機遇.現(xiàn)有的基于深度學習的代碼自動摘要技術(shù)通常簡單地把代碼看成符號的序列并使用簡單的神經(jīng)網(wǎng)絡(luò)模型進行處理,顯然,這種方式無法捕獲代碼復(fù)雜的結(jié)構(gòu)信息,而這些復(fù)雜的結(jié)構(gòu)信息里包含了豐富的代碼語義.因此,在大代碼(big code)時代,如何對代碼建模才能更加高效地捕獲代碼的語義信息已經(jīng)成為軟件工程領(lǐng)域的研究熱點.
2) 代碼符號詞匯表的無限性
源代碼主要由關(guān)鍵字、標識符、操作符、分隔符、字面常量組成,其中,標識符占代碼符號的絕大多數(shù)[13-14].而編程人員可以任意為函數(shù)、變量、類等標識符進行命名,即使遵循一定的命名規(guī)范,理論上代碼符號的可能性也是無窮的.代碼符號詞匯表的無限性給代碼建模帶來了巨大的挑戰(zhàn)[15].相比之下,自然語言中詞匯的產(chǎn)生需要經(jīng)過較長時間的演化,其詞匯表大小是有限的.通常情況下,3萬左右的詞匯表即可滿足自然語言處理相關(guān)任務(wù)的需要.而面對詞頻較低的未登錄詞,自然語言處理領(lǐng)域通常使用UNK符號進行替換,替換后的語句與原語句相比并不會存在較大的語義偏差.然而,在代碼自動摘要任務(wù)中,由于低頻詞數(shù)量過多,簡單地使用UNK符號進行替換低頻詞將造成代碼中的關(guān)鍵信息丟失.例如,在本文實驗使用的Java代碼摘要數(shù)據(jù)集[16]中,不同的代碼符號(區(qū)分大小寫)總數(shù)約為19.2萬個,其數(shù)量遠遠超過常用的自然語言詞匯數(shù)量.代碼符號詞匯表的無限性給我們帶來了另外一個巨大的挑戰(zhàn):如果采用較大的詞匯表,模型訓(xùn)練的難度和時間會大大增加,且大量低頻詞并不能得到充分訓(xùn)練,導(dǎo)致最終模型效果較差;如果采用較小的詞匯表,則很多符號都會被替換成UNK,替換后的代碼將丟失大量關(guān)鍵信息,顯然,在此基礎(chǔ)上,模型不可能學習到很好的代碼語義表示.
本文在第2節(jié)會深入探討如何應(yīng)對“代碼符號詞匯表的無限性”這個挑戰(zhàn).在總結(jié)已有解決方案的基礎(chǔ)上,我們提出了自己的解決方案——符號部分拆分算法.需要說明的是,如何對代碼的結(jié)構(gòu)建模并不是本文關(guān)注的重點.但事實上,代碼的語義信息同時來自于符號和結(jié)構(gòu),因而我們提出的部分拆分算法對改進代碼的建模依然是有意義的.
本文的另一個關(guān)注點,也是最主要的關(guān)注點,在于如何讓模型從很長的代碼符號序列中篩選出真正有用的信息,從而更好地生成摘要.文獻[4]提出了類似的想法,他們的靈感來自編程時編輯器折疊代碼的功能;而我們的靈感則來自于人類寫代碼摘要時的習慣——當我們針對某個函數(shù)編寫代碼摘要時,我們會有意識地尋找代碼中的關(guān)鍵信息,進而輔助我們寫出更高質(zhì)量的摘要.
圖1給出了短文本摘要任務(wù)常用的數(shù)據(jù)集Gigaword[17]中的一個例子.在這個例子中,原文中所有加下劃線的詞完全覆蓋了摘要里的信息,也就是說,我們只需要關(guān)注原文中的部分關(guān)鍵信息,就能寫出其對應(yīng)的高質(zhì)量摘要結(jié)果.從這個例子可以看出原文中有大量的詞對最終生成的摘要貢獻很小.可想而知,如果我們事先能知道這些是關(guān)鍵詞,那么寫摘要的時候我們就能更專注于這些主要信息;相反,如果我們把注意力平均分散到每個詞,則可能會寫出較差的摘要.

Fig. 1 An example from Gigaword dataset圖1 Gigaword數(shù)據(jù)集中的一個例子
類似地,圖2給出了Java代碼摘要數(shù)據(jù)集[18]中的一個例子.

Fig. 2 An example from Java method documentation dataset圖2 Java代碼摘要數(shù)據(jù)集中的一個例子
從技術(shù)的角度講,在使用經(jīng)典的序列到序列模型生成文本摘要的實驗中,我們發(fā)現(xiàn)當使用帶有注意力機制的解碼器解碼時,其注意力的權(quán)重并不是特別集中,相當一部分注意力權(quán)重之和會分散到次要信息上去.這個現(xiàn)象隨著輸入序列長度的增加而變得更明顯.
這個問題在代碼自動摘要任務(wù)中則會更加嚴重.據(jù)統(tǒng)計,Gigaword中平均文本長度為31.4,平均摘要長度為8.3(1)http://nlpprogress.com/english/summarization.html;而在Java函數(shù)文檔數(shù)據(jù)集[18]的訓(xùn)練集中代碼的平均長度為99.94,而摘要的平均長度為17.72.可見,在代碼自動摘要任務(wù)中,代碼符號中包含的次要信息的比例更高.圖3展示了代碼自動摘要實驗中生成摘要的BLEU-4分數(shù)和代碼序列長度的關(guān)系[16].顯然,隨著代碼長度的增加,生成的代碼摘要的評分在降低,該現(xiàn)象表明代碼序列長度的增加確實會限制模型的最終效果.

Fig. 3 BLEU-4 score of generated summary for different code lengths[16]圖3 BLEU-4分數(shù)和代碼序列長度的關(guān)系[16]
針對該現(xiàn)象,很多學者提出了應(yīng)對的方案,如局部注意力機制(local attention)[19]、選擇門機制(selec-tive gate)[20]和多層自注意力機制(stacking self-attention)[21].在已有研究的基礎(chǔ)上,本文結(jié)合代碼本身的特性,提出了一種基于關(guān)鍵詞的代碼自動摘要方法(keyword-based source code summarization, KBCoS).針對代碼,本文將函數(shù)簽名(method sig-nature)和API(application programming interface)視為關(guān)鍵詞,目的是使得解碼器注意力的分布更加集中.我們將在本文第3節(jié)詳細介紹這個模型,并在第4節(jié)、第5節(jié)中通過實驗驗證、分析此模型的效果.
為什么要把函數(shù)簽名和API調(diào)用作為關(guān)鍵詞呢?文獻[22]中研究者跟蹤了若干程序員在閱讀函數(shù)并寫注釋的過程中視線聚焦點的移動軌跡,通過實驗發(fā)現(xiàn),程序員對函數(shù)簽名的關(guān)注度高于對函數(shù)調(diào)用的關(guān)注度高于對控制流的關(guān)注度.這一研究不僅表明了函數(shù)簽名的重要性,也表明了API調(diào)用的重要性——API調(diào)用本身也是函數(shù)調(diào)用.此外,API調(diào)用的序列存在一定的模式[23],其包含的內(nèi)在規(guī)律可以輔助其他任務(wù),如代碼搜索[16]和代碼自動摘要[18].綜上,我們認為函數(shù)簽名和API調(diào)用是函數(shù)中的關(guān)鍵信息,因而把它們作為關(guān)鍵詞序列.圖2中的例子也佐證了我們的判斷:加下劃線部分基本上都在函數(shù)簽名或API調(diào)用里面.
我們相信,利用基于關(guān)鍵詞的注意力機制改進模型解碼時的注意力分布,這一方法和改進代碼的編碼器是正交的——我們的方法也能提升使用了其他復(fù)雜編碼器的模型的性能.
本文的主要貢獻包括:
1) 針對“代碼符號的詞匯表大小沒有上限”這一問題,提出了符號部分拆分的算法.該算法簡單有效,能很好地平衡代碼符號序列長度和未登錄詞數(shù)目之間的矛盾.
2) 為了讓模型在生成摘要時更集中地關(guān)注代碼中的關(guān)鍵信息,提出了KBCoS模型,將函數(shù)簽名和API調(diào)用視為關(guān)鍵詞,并利用關(guān)鍵詞序列來優(yōu)化解碼器注意力機制中的權(quán)重分布.實驗評測結(jié)果和注意力權(quán)重的熱力圖都表明了我們模型的有效性.
3) 與現(xiàn)有模型TL-CodeSum相結(jié)合,KBCoS在Java代碼摘要數(shù)據(jù)集上取得當前最優(yōu)的結(jié)果.
代碼自動摘要(code summarization)是典型的序列到序列的生成式任務(wù):若C={C(i)}表示源代碼,D={D(i)}表示源代碼對應(yīng)的摘要,則代碼自動摘要任務(wù)是讓機器自動從源代碼生成摘要C→D.
在相關(guān)文獻中,Code Summarization一詞的使用比較混亂,可能被用來表示3種相似又略有差異的任務(wù):
1) 函數(shù)名預(yù)測.即給定一個函數(shù),隱去函數(shù)名,要求根據(jù)函數(shù)中其他的信息預(yù)測函數(shù)的名稱.Allamanis等人[12]提出了代碼的自然性假說,并指出代碼的似然性與這樣一個事實有著很強的聯(lián)系,即開發(fā)人員更喜歡編寫[24]和閱讀[25]符合規(guī)范的、符合習慣的和熟悉的代碼,良好的代碼風格有助于軟件系統(tǒng)的理解和維護.而接近自然語言的命名方式,能讓人更加容易理解名字背后表達的含義,是一種良好的命名規(guī)范.一個良好的命名通常能拆成若干個單詞,組成一個單詞序列,該單詞序列即為函數(shù)名預(yù)測的最終目標[9,26-27],因此,函數(shù)名預(yù)測也能看成是一種特殊的代碼自動摘要任務(wù),如文獻[9]提供的Java函數(shù)名預(yù)測數(shù)據(jù)集中,函數(shù)名對應(yīng)的單詞序列長度大約在3左右,因而也被稱為是Extreme Summarization[9].
2) 文檔注釋自動化生成.該任務(wù)通常選取函數(shù)文檔中的第一句話作為生成目標,如Python函數(shù)的docstring[28]或Java函數(shù)的Javadoc[16,18].這也是本文所關(guān)注的任務(wù),該任務(wù)又被稱為Code Docu-mentation[26].和任務(wù)1)相比,文檔注釋長度較長,在本文使用的Java自動摘要數(shù)據(jù)集中[18],摘要的平均長度是17.72,是函數(shù)名長度的6倍以上.
3) 代碼功能描述自動化生成[26].該任務(wù)主要針對StackOverflow網(wǎng)站上的提問和被采納的答案中的代碼片段,如文獻[10]中的Python和SQL這2個數(shù)據(jù)集.該任務(wù)和2)類似,任務(wù)2)中的代碼主要針對函數(shù),而任務(wù)3)中的代碼可能只是一個包含幾行代碼的代碼片段.
本文主要針對任務(wù)2)進行了實驗與研究,但對于其他任務(wù),本文提出的模型經(jīng)過簡單的調(diào)整同樣也能夠勝任,本文將在未來的工作中對其他任務(wù)進行實驗與驗證.
如何處理未登錄詞一直是自然語言處理和程序語言理解領(lǐng)域所關(guān)注的主要問題.在自然語言處理領(lǐng)域,通常通過在字符層面對文本建模來降低未登錄詞的影響,如文獻[29]提出了用字符層面的卷積神經(jīng)網(wǎng)絡(luò)(character-level convolutional network)對文本進行分類.在代碼領(lǐng)域,由于代碼符號詞匯表的無限性特點,未登錄詞對模型的影響遠大于自然語言處理的相關(guān)任務(wù),因此,很多研究者提出了自己的解決方案:
1) 文獻[16]提出用未登錄詞對應(yīng)的抽象語法樹結(jié)點的類型代替UNK,針對的任務(wù)是代碼自動摘要.
2) 文獻[30]設(shè)計了一種復(fù)制(copy)機制,即在解碼時讓模型額外預(yù)測是選擇當前解碼器輸出的詞,還是復(fù)制輸入符號序列中的符號,針對的任務(wù)是程序修復(fù).
3)文獻[31]則首次提出利用圖結(jié)構(gòu)處理未登錄詞,借鑒了文獻[32]中提出的方法,在抽象語法樹的基礎(chǔ)上添加一些關(guān)于變量使用、符號組合的邊,將樹結(jié)構(gòu)轉(zhuǎn)為了圖結(jié)構(gòu),并利用GGNN(gated graph neural network)[33]在圖結(jié)構(gòu)中傳遞信息.針對的任務(wù)是變量名預(yù)測和變量填空[32].
上述方法利用了代碼的結(jié)構(gòu)信息來學習未登錄詞的語義表示,忽略了未登錄詞本身所包含的語義信息.例如,對于登錄詞addListenerToList,可以拆分成多個子符號(sub-token):add,listener,to,list.拆分后的子符號都屬于高頻詞匯,且子符號序列顯然可以充分地表示未登錄詞addListenerToLis的語義信息,這種簡單的方法可能比復(fù)雜的模型更加簡潔有效.因此,很多學者都采用了這種簡單有效的處理方式.同時,在將代碼符號全部拆成子符號之后,不同文獻有著不同的處理方式:
1) 將拆分后的結(jié)果簡單地看成子符號序列或詞袋[20].
2) 利用指針網(wǎng)絡(luò)(pointer network)[34]實現(xiàn)復(fù)制機制,用于變量名預(yù)測[9,35]或生成代碼文檔[35].
3) 利用字符層級的卷積神經(jīng)網(wǎng)絡(luò)[29]結(jié)合子符號的向量表示得到原符號的向量表示[31],針對的任務(wù)是變量名預(yù)測和變量填空.
4) 對子符號的向量取均值得到原符號的向量表示[32],針對的任務(wù)是變量誤用檢測和變量填空.
5) 對子符號的向量求和得到原符號的向量表示[26],針對的任務(wù)主要是變量名預(yù)測.
注意,在上述處理方式中,3)~5)沒有改變編碼器輸入的序列長度,解碼器注意力機制需要關(guān)注的還是原始序列中各個符號的向量表示.而處理方式1)和2)則將輸入序列完全轉(zhuǎn)化為了子符號序列,我們稱為全部拆分.
我們對Java代碼自動摘要數(shù)據(jù)集[18]訓(xùn)練集中的代碼也進行了子符號的全部拆分,并統(tǒng)計了拆分前后代碼符號子符號序列的長度,結(jié)果如表1所示.
由于訓(xùn)練集中約90%的代碼原始序列長度不超過200(詳見表2),本文選取200作為代碼符號長度的分界點.從表1我們可以看到,如果將所有符號全部拆分為子符號,代碼符號序列的平均長度會增加41%,長度超過200的數(shù)據(jù)數(shù)目會增加5 751,增加了75%,過長的代碼長度將導(dǎo)致模型注意力分散,降低最終生成摘要的效果.這一推測在實驗中也得到了驗證,當代碼符號全部拆分后,基準模型的性能反而降低了,因此處理方式1)不可取.而我們的目標是生成用自然語言寫的一句話,因而也不需要引入處理方式2)中的指針網(wǎng)絡(luò)對每個變量名進行復(fù)制.而處理方式3)~5)不僅增加了模型的復(fù)雜度,而且多個子符號的向量表示融合成一個向量表示容易造成含義的混淆,實驗效果也不是特別理想.
Table 1 Statistics of Code TokenSub-token Sequence Length in the Training Set
表1 訓(xùn)練集中代碼符號子符號序列長度統(tǒng)計

Table 1 Statistics of Code TokenSub-token Sequence Length in the Training Set
SequenceNumber of TokensLength>200Length≤200MeanLengthOriginal76686204099.94All Splitting1341956289140.78Partial Splitting919460514110.40
為了更有效地處理未登錄詞,針對代碼自動摘要的場景,我們提出了代碼符號部分拆分的算法:只將非頻繁出現(xiàn)的符號拆分為子符號序列.對于一個高頻的可拆分符號,其本身便包含獨立的豐富的語義信息,因此,本文希望模型能夠?qū)W習該符號整體的向量表示.例如,對于高頻詞toString,其具有明確的語義信息,完全沒有拆分成to,string這2個子符號的必要,簡單地對所有詞進行拆分只會增加模型學習的難度.
如表1所示,部分拆分之后,代碼符號序列的平均長度增加了10%,長度超過200的代碼增加了20%,在可以接受的范圍之內(nèi).采用符號部分拆分,既可以減少未登錄詞,又不會特別增加代碼符號序列的長度,不失為一種簡單、有效的策略.
在Java編程規(guī)范中,一般都要求采用駝峰命名的方法,如在阿里巴巴的Java編程規(guī)范中要求:“類名使用UpperCamelCase風格;方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用LowerCamelCase風格,必須遵從駝峰形式.”[36]在后續(xù)的實驗中,我們根據(jù)駝峰命名法將代碼符號的序列進行了部分拆分,并在部分拆分的過程中生成了詞表.算法1詳細描述了此算法:
算法1.代碼符號部分拆分算法.
1) 初始化符號計數(shù)器和詞表;
2) 對訓(xùn)練集中的每個函數(shù):
① 將代碼解析為符號序列;
② 如果一個符號可以按駝峰拆分為若干子符號,則將這些子符號添加到代碼符號的序列里面,同時保留原有的符號;
③ 將添加了子符號的代碼符號序列添加進符號計數(shù)器里面;
3) 根據(jù)符號計數(shù)器的計數(shù),選擇合適的閾值,將詞頻大于或等于閾值的符號添加進詞表;
5) 對數(shù)據(jù)集里面的每個函數(shù):
① 將代碼解析為符號序列;
② 在詞表中查找每一個符號;
若遇到一個非登錄詞,
若該符號可以按駝峰拆分,
將符號拆分為子符號序列;
在詞表中查找每一個子符號;
若其在詞表里,輸出子符號;
否則,輸出該符號;
6) 輸出預(yù)處理完的代碼數(shù)據(jù)集以及詞表.
編碼器-解碼器模型是一個廣泛運用于文本摘要任務(wù)的深度學習模型.本文在編碼器-解碼器模型的基礎(chǔ)上,提出了一種基于關(guān)鍵詞的代碼自動摘要模型KBCoS,利用基于關(guān)鍵詞信息的注意力機制使模型更加關(guān)注輸入中的關(guān)鍵信息,提高代碼自動摘要的性能.
KBCoS主要包括代碼編碼器、關(guān)鍵詞編碼器、基于關(guān)鍵詞的解碼器3個部分.KBCoS的整體結(jié)構(gòu)如圖4所示.

Fig. 4 The architecture of KBCoS圖4 KBCoS的架構(gòu)
代碼編碼器將輸入的代碼視為代碼符號的序列,并編碼為語義向量序列.語義向量中編碼了代碼的語義信息,解碼器進而對這些信息進行解碼,得到最終的代碼摘要.代碼編碼器主要包括代碼嵌入層、代碼編碼層和分段最大池化層3個部分.
1) 代碼嵌入層

2) 代碼編碼層
得到符號向量序列后,代碼編碼層使用循環(huán)神經(jīng)網(wǎng)絡(luò)(recurrent neural network, RNN)將其編碼為符號語義向量序列h={h1,h2,…,hn},其中符號語義向量hi編碼了第i個代碼符號以及之前代碼符號{token0,token1,…,tokeni-1,tokeni}的語義信息.
循環(huán)神經(jīng)網(wǎng)絡(luò)是針對序列形式提出的網(wǎng)絡(luò)結(jié)構(gòu).序列數(shù)據(jù)具有長度不固定的特點,普通神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)難以處理變長數(shù)據(jù).并且,由于序列數(shù)據(jù)往往具有前后依賴的特性,即一個序列中當前輸出依賴于之前的輸出,普通神經(jīng)網(wǎng)絡(luò)無法對這種依賴關(guān)系進行建模.而循環(huán)神經(jīng)網(wǎng)絡(luò)使用隱狀態(tài)hi存儲第i步之前的信息,保存了序列之間的前后依賴關(guān)系.因此,本文選擇循環(huán)神經(jīng)網(wǎng)絡(luò)作為編碼層.
對于神經(jīng)循環(huán)網(wǎng)絡(luò)中的第i步,其隱狀態(tài)hi的計算公式為

其中,U,W,b為循環(huán)神經(jīng)網(wǎng)絡(luò)需要學習的參數(shù),Φ為激活函數(shù),通常選取tanh作為其激活函數(shù).
但是,最基本的循環(huán)神經(jīng)網(wǎng)絡(luò)單元在訓(xùn)練過程中會遇到梯度消失的問題.針對該問題,長短期記憶網(wǎng)絡(luò)(long short-term memory, LSTM)[37]、門控循環(huán)單元(gated recurrent unit, GRU)[38]等循環(huán)神經(jīng)網(wǎng)絡(luò)單元的變體被提出.經(jīng)過實驗證明,這些循環(huán)神經(jīng)網(wǎng)絡(luò)單元的變體在很多任務(wù)上性能都超過了最基本的循環(huán)神經(jīng)網(wǎng)絡(luò)單元.
本文選取GRU作為循環(huán)神經(jīng)網(wǎng)絡(luò)的基本單元,GRU使用更新門(update gate)控制信息的流動,使用重置門(reset gate)來決定組成激活候選狀態(tài)的信息.對于GRU中的第i步,其隱狀態(tài)hi的計算公式為
zι=σg(Wzxi+Uzhi-1+bz),
ri=σg(Wrxi+Urhi-1+br),
hi=(1-zi)hi-1+ziσh(Whxi+Uhrihi-1+bh),
其中,W,U,b為GRU需要學習的參數(shù),zi表示更新門,ri表示重置門,σg和σh為激活函數(shù),σg默認為sigmoid激活函數(shù),σh默認為雙曲線激活函數(shù).
本文將循環(huán)神經(jīng)網(wǎng)絡(luò)的隱狀態(tài)序列h={h1,h2,…,hn}作為代碼符號的語義向量序列.
3) 分段最大池化層
經(jīng)過代碼編碼層,可以得到代碼符號的語義向量序列.分段最大池化層的目標是將該序列轉(zhuǎn)化為一個長度固定的代碼語義向量,其中編碼了代碼的語義信息.
語義向量通常可以通過3種方式得到:
① 將循環(huán)神經(jīng)網(wǎng)絡(luò)的最后一個隱狀態(tài)作為語義向量;
② 將循環(huán)神經(jīng)網(wǎng)絡(luò)的所有隱狀態(tài)經(jīng)過最大池化層得到語義向量;
③ 將循環(huán)神經(jīng)網(wǎng)絡(luò)的所有隱狀態(tài)經(jīng)過平均池化層得到語義向量.
方式①只取了循環(huán)神經(jīng)網(wǎng)絡(luò)的最后一個隱狀態(tài),由于循環(huán)神經(jīng)網(wǎng)絡(luò)的每一步都會不可避免地遺忘掉前一步的語義信息,最后一個隱狀態(tài)很容易丟失符號向量序列中靠前的元素的大部分語義信息,導(dǎo)致代碼語義向量語義信息不夠準確.
方式②③雖然考慮了序列中所有元素的語義向量,但在該過程中,所有元素的權(quán)重都是一樣的,最后的代碼語義向量沒有關(guān)注代碼中的關(guān)鍵信息,導(dǎo)致了關(guān)鍵語義信息的丟失.
從引言分析中我們可以知道,對于一個函數(shù),它的函數(shù)簽名包含了其代碼摘要所需要的很多關(guān)鍵信息,而函數(shù)體中包含了大量對代碼自動摘要貢獻很少的符號,且結(jié)構(gòu)復(fù)雜,模型學習較為困難.
本文提出了一種分段最大池化方法,該方法針對代碼符號語義向量序列進行分段池化.具體而言,將代碼符號語義向量序列h分為函數(shù)簽名符號語義向量序列hsignature和函數(shù)體符號語義向量序列hbody兩部分,并分別進行最大池化操作,得到函數(shù)簽名語義向量esignature和函數(shù)體語義向量ebody,最后將這2個向量連接起來得到最終的代碼語義向量ecode.
本文將函數(shù)簽名對應(yīng)的符號和API調(diào)用對應(yīng)的符號組合為關(guān)鍵詞序列,并將其作為關(guān)鍵詞編碼器的輸入.關(guān)鍵詞編碼將關(guān)鍵詞序列編碼為對應(yīng)的關(guān)鍵詞語義向量,該語義向量包含了本文希望解碼器重點關(guān)注的關(guān)鍵語義信息,并通過注意力機制在解碼過程中幫助解碼器更關(guān)注輸入中的關(guān)鍵信息.
1) 關(guān)鍵詞嵌入層
假設(shè)從代碼中提取的關(guān)鍵詞序列長度為m:keyword={keyword1,keyword2,…,keywordm}.

2) 關(guān)鍵詞編碼層

傳統(tǒng)的編碼器-解碼器模型使用循環(huán)神經(jīng)網(wǎng)絡(luò)作為解碼器,并將上一時刻的輸出作為當前時刻的輸入對語義向量進行解碼.此時,解碼器只能通過一個語義向量獲取輸入序列的語義信息,而每一時刻解碼器需要關(guān)注的語義信息是不一樣的,因此,為了進一步挖掘輸入序列的語義信息,提高模型對關(guān)鍵信息的關(guān)注程度,本文使用了帶有注意力機制的解碼器,并提出了一種基于關(guān)鍵詞的解碼器,該解碼器在注意力機制中引入了關(guān)鍵詞的信息,使解碼的每一步都專注于更重要的信息,提高解碼器的準確率.
1) 解碼層
解碼層將代碼編碼器得到的代碼語義向量ecode作為初始隱狀態(tài)s0,并且,在解碼的每一步,利用注意力機制將編碼器的所有隱狀態(tài)序列h={h1,h2,…,hn}(即代碼符號的語義向量序列)加權(quán)得到當前關(guān)注的動態(tài)代碼語義向量.
其第t步概率公式可寫作:
其中,yt表示當前時刻的輸出;st表示當前時刻解碼器的隱狀態(tài),其計算公式為

通過查表的方式可以由yt找到對應(yīng)的自然語言詞表中的單詞wordt.當時刻T輸出的單詞為EOS這個特殊符號時,表示輸出序列的結(jié)束,此時由word1,word2,…,wordT組成代碼的摘要.
此外,在預(yù)測生成摘要的過程中,還采用了集束搜索(beam search)的機制,得到若干候選摘要,從中選取概率最大的摘要.原因是解碼時每一步都選取概率最大的詞,并不意味著最終生成的序列的概率最大.
2) 基于關(guān)鍵詞的注意力機制
基于關(guān)鍵詞的解碼器利用關(guān)鍵詞信息對注意力機制進行增強,計算公式為:


實驗中使用的Java自動摘要數(shù)據(jù)集來自文獻[18],其中包含了87 136個代碼,API,摘要的三元組,并按照8∶1∶1的比例劃分成訓(xùn)練集、校驗集和測試集.訓(xùn)練集中69 708條數(shù)據(jù)的統(tǒng)計信息如表2所示:

Table 2 Statistics of API, Code and Comment Sequence Length
Java自動摘要數(shù)據(jù)集中的數(shù)據(jù)包含了Github上收藏數(shù)量超過20的Java項目的代碼.數(shù)據(jù)集以函數(shù)為單位,選取函數(shù)文檔中的第1句話作為代碼摘要,并抽取代碼中調(diào)用的JDK API作為輔助信息,幫助代碼摘要的生成.其預(yù)處理的方法和文獻[39]保持一致,并且,如果某個函數(shù)缺少函數(shù)文檔,或者其函數(shù)文檔的第1句話是無意義的,該函數(shù)將從數(shù)據(jù)集中刪除.
Java函數(shù)中API調(diào)用的信息是代碼的關(guān)鍵信息之一,而大部分的代碼摘要數(shù)據(jù)集都忽略了這一信息.并且,這些數(shù)據(jù)集經(jīng)過預(yù)處理后只包含函數(shù)本身的代碼,而丟失了上下文特別是包導(dǎo)入的信息,導(dǎo)致我們不能還原函數(shù)所對應(yīng)的API調(diào)用信息.因此,本文采用上述數(shù)據(jù)集作為本文的主要數(shù)據(jù)集.
為了驗證實驗的有效性,我們將帶有注意力的序列到序列模型(編碼器-解碼器模型)作為基準模型.實驗中使用的超參數(shù)如表3所示:

Table 3 Hyper-Parameters Used in Experiments表3 實驗中模型的超參數(shù)列表
在后續(xù)的實驗中,為了公平起見,將每個模型都訓(xùn)練50個回合(epoch)[16],根據(jù)校驗集上的評測結(jié)果選擇BLEU指標最高的一組參數(shù),然后在測試集上評測使用了這組參數(shù)的模型并記錄各項指標.訓(xùn)練時初始學習率為0.5,前5個回合保持不變,之后每過3個回合乘以0.95.本文使用隨機梯度下降法(SGD)對參數(shù)進行優(yōu)化.
我們使用BLEU-4,METEOR,ROUGE-L作為實驗的評價指標.
1) BLEU-4.BLEU-4是一種基于精確度的相似性度量方法[40],用于分析2個文本間n元組(n-gram)的共現(xiàn)程度,經(jīng)常被用于機器翻譯結(jié)果的評估.
2) METEOR.METEOR是一種基于準確率和召回率的評價指標,其考慮了基于整個語料庫的準確率和召回率,是另一種常見的評價語料相似度的指標,其目的是解決一些BLEU-4中固有的缺陷[41].并且,METEOR還考慮了同義詞匹配等,首先基于WordNet的同義詞庫給定一組校準m,通過最小化對應(yīng)語句中連續(xù)有序單詞形成的塊的數(shù)目ch來得出,然后計算對應(yīng)最佳候選譯文和參考譯文之間的準確率和召回率的加權(quán)調(diào)和平均.
3) ROUGE-L.ROUGE是一種基于召回率的相似度度量方法[42],主要考察了預(yù)測結(jié)果的充分性和忠實性.ROUGE共包含4種評價指標:ROUGE-N,ROUGE-L,ROUGE-W,ROUGE-S.本文選ROUGE-L作為評價指標.
為了驗證部分拆分機制的可行性,本文首先對部分拆分前后的詞表信息和未登錄詞信息進行了統(tǒng)計(保留原有的大小寫,即符號大小寫敏感),統(tǒng)計信息如表4和表5所示:

Table 4 Number of Code Tokens for Different Frequencies表4不同詞頻的符號數(shù)量統(tǒng)計

Table 5 Number of Out-of-Vocabulary Tokens表5 未登錄詞數(shù)量統(tǒng)計
由表4中的統(tǒng)計信息可知,代碼符號中有大量符號只出現(xiàn)了不到4次,如果將這些符號加入詞表,模型將無法充分訓(xùn)練提取這些符號的語義信息,導(dǎo)致出現(xiàn)大量噪聲.由于文獻[18]使用的代碼符號詞表大小為5萬,因此,本文選取詞頻大于等于4的符號構(gòu)建詞表.對于符號部分拆分前后的詞表,詞表大小由57 493變?yōu)?3 204,增長不到10%,訓(xùn)練難度不會因此而提升太多.與此同時,由表5中的統(tǒng)計信息可知,符號部分拆分后,未登錄詞總數(shù)由222 349下降為27 416,下降幅度接近90%.這極大減小了未登錄詞對于模型的影響,提高了代碼自動摘要的準確性和可靠性.
為了驗證符號大小寫敏感和符號部分拆分機制的實際效果,根據(jù)是否保留符號的大小寫、是否使用部分拆分算法,我們實現(xiàn)了基準模型的4個變體,在測試集上的評測結(jié)果如表6所示:

Table 6 Experimental Results for Baselines表6 基準模型的實驗結(jié)果
對比基準模型3,4的結(jié)果,在大小寫敏感的前提下,經(jīng)部分拆分后基準模型的所有評測指標都得到了提升,且大小寫敏感+符號部分拆分的基準模型4在所有4個基準模型的3個評測指標中都獲得了最高的得分,這表明部分拆分確實是一種有效的提升代碼自動摘要效果的方法.
對比基準模型1,3以及基準模型2,4的結(jié)果,將大小寫不敏感改為大小寫敏感后,所有指標都得到了提升或者基本保持一致.實驗結(jié)果證明,大小寫敏感可以更有效地保留符號的語義信息.而且從提升的效果來看,在符號部分拆分以后,大小寫是否敏感導(dǎo)致的差異尤為明顯.特別是,在大小寫不敏感且符號部分拆分的情況下,基準模型3個指標在4個基準模型中都是最低的.我們猜想,部分拆分后,將符號全部小寫歸一化會導(dǎo)致子符號序列邊界的消失.
在大小寫敏感且符號部分拆分的情況下,基準模型的效果最好,因此,本文將這2種策略作為默認設(shè)置,后續(xù)實驗如無特別說明,基準模型的默認設(shè)置為大小寫敏感且符號部分拆分.
我們還嘗試了別的文獻中采用的將子符號的向量求和或取平均得到代碼符號向量的應(yīng)對未登錄詞的策略,同樣采用基礎(chǔ)模型,在測試集上的評估結(jié)果如表7所示:

Table 7 Experimental Results for Other Strategies to Deal with OOV
KBCoS將函數(shù)簽名+API調(diào)用序列作為關(guān)鍵詞,讓解碼器在解碼生成摘要時每一步的注意力分布相對更集中在關(guān)鍵信息上.為了驗證KBCoS模型的有效性,我們將KBCoS與基準模型進行比較,在測試集上的評測結(jié)果如表8所示:

Table 8 Experimental Results for KBCoS表8 KBCoS的實驗結(jié)果
實驗結(jié)果表明,和基準模型相比,KBCoS在3個評測指標上都有較大的提升,因此,基于關(guān)鍵詞的代碼自動摘要模型是有效的.
此外,我們還嘗試了僅僅使用API調(diào)用或僅僅使用函數(shù)簽名作為關(guān)鍵詞,實驗結(jié)果表明分別使用這2種關(guān)鍵詞的KBCoS模型在測試集上的評測結(jié)果雖然都比使用了符號部分拆分的基準模型要好,但都不如同時使用API調(diào)用和函數(shù)簽名作為關(guān)鍵詞的KBCoS模型效果好.
文獻[18]提出基于API調(diào)用序列的遷移學習模型TL-CodeSum來提升代碼自動摘要的性能,并取得了較好的效果,是當前在這個數(shù)據(jù)集上State-of-the-Art(SOTA)的結(jié)果.
為了和文獻[18]的方法更好地比較,我們將KBCoS與TL-CodeSum相結(jié)合,使得KBCoS也能夠利用API調(diào)用序列中的信息輔助代碼摘要的生成.從技術(shù)角度上說,我們?yōu)镵BCoS添加了一個API調(diào)用序列編碼器,該編碼器輸入為API調(diào)用序列,輸出為API調(diào)用序列的語義表示,表示編碼了API調(diào)用序列的語義信息,可以很好地輔助代碼摘要的自動生成.
我們在測試集上評估了添加API調(diào)用序列對基準模型和KBCoS的影響,結(jié)果如表9所示:

Table 9 Experimental Results for API表9 API信息的實驗結(jié)果
根據(jù)實驗結(jié)果,我們可以得出3個結(jié)論:
1) 添加一個編碼器對API調(diào)用序列單獨編碼,能進一步提升模型的性能,無論是基準模型和KBCoS.這點和文獻[18]的結(jié)論是一致的.
2) BKBCoS+API、基準模型+API在3個評測指標上均超過了基準模型(原始序列)+API,而文獻[18]的模型與本文中基準模型(原始序列)+API的模型是一致的,因而KBCoS+API、基準模型+API在這個數(shù)據(jù)集上均超過了SOTA的結(jié)果(由于不同工具計算各個指標的結(jié)果會有差異,因此我們沒有直接比較文獻[18]中的測試結(jié)果,而是復(fù)現(xiàn)了他們的實驗,然后用一個工具計算了各個指標).
3) 我們提出利用函數(shù)簽名和API調(diào)用作為關(guān)鍵詞來改善模型解碼時注意力的分布以及部分拆分的算法,都能進一步提升別的模型的效果,具有一定的通用性.
為了進一步驗證本文提出算法的有效性,我們又在一個Python數(shù)據(jù)集[28]上重復(fù)了部分實驗.該數(shù)據(jù)集是從Github上的項目中構(gòu)建而成的,包含了108 726個函數(shù),摘要對.我們將數(shù)據(jù)集按3∶1∶1的比例劃分為訓(xùn)練集、校驗集和測試集.實驗中其他超參數(shù)的設(shè)置和Java數(shù)據(jù)集上的實驗一致,同時,由于沒有得到Python代碼的API調(diào)用信息,我們使用函數(shù)調(diào)用序列代替API調(diào)用序列作為關(guān)鍵詞.實驗結(jié)果如表10所示,相比原始符號序列的基準模型,采用了部分拆分算法的基準模型在3個評測指標上均有略微的提升;進一步采用函數(shù)簽名+API調(diào)用序列作為關(guān)鍵詞,KBCoS比基準模型在3個評測指標上又有了明顯的提升.

Table 10 Experimental Results for Python Dataset表10 Python數(shù)據(jù)集上的實驗結(jié)果

Table 11 An Example of Generated Summary表11 代碼自動摘要樣例
對比生成的摘要,我們可以看到KBCoS預(yù)測的結(jié)果和人工摘要是一致的.基準模型生成的摘要也不錯,add,listener,list這3個關(guān)鍵詞都準確地被預(yù)測,只不過略有冗余.相比之下,基準模型(原始序列)的預(yù)測結(jié)果就差強人意,由于addSearchListener,m_SearchListeners在沒有經(jīng)過部分拆分的情況下都是未登錄詞,模型只學到了add,SearchListener這2個符號中所包含的信息,而“to the buffer”可能是模型根據(jù)“經(jīng)驗”猜測得到的.
此外,注意力權(quán)重的熱力圖可以更好地分析基準模型與KBCoS之間的區(qū)別,如圖5、圖6所示,其中橫坐標上依次排列了經(jīng)部分拆分后的代碼符號序列,縱坐標上依次排列了模型預(yù)測的摘要序列,而顏色越深的方塊表示注意力的權(quán)重越大.
對比2張熱力圖,我們可以看到,在基準模型的熱力圖中,每一步解碼時(圖中每一行)基準模型關(guān)注的代碼符號多而分散.相比之下,KBCoS則可以很好地找到關(guān)鍵信息,函數(shù)名部分(“add search listener”)和關(guān)鍵代碼部分(“m,search,listeners,add”)都是其關(guān)注的重點.由于adds作為摘要中的第一個詞已經(jīng)輸出,故API調(diào)用“HashSet.add”并沒有得到太多的關(guān)注.特別一提的是,通過關(guān)注關(guān)鍵代碼符號“search,listeners”,模型推導(dǎo)出了摘要中包含的“l(fā)ist of listeners”.可見,關(guān)鍵詞信息可以幫助模型更好地關(guān)注代碼中的重要信息.

Fig. 5 The heat map of attention weights in baseline圖5 基準模型中注意力權(quán)重熱力圖

Fig. 6 The heat map of attention weights in KBCoS圖6 KBCoS中注意力權(quán)重熱力圖
本文針對代碼自動摘要任務(wù),提出了一種基于關(guān)鍵詞的代碼自動摘要方法KBCoS.實驗結(jié)果表明KBCoS能夠生成更好的代碼摘要,并且明顯提升了基準模型的評測指標,其主要原因有:
1) 通過符號部分拆分,一定程度上緩解了在給定合適大小的詞表的情況下代碼符號中存在大量未登錄詞的問題,使模型能更好地提取符號中的語義信息.
2) 通過基于關(guān)鍵詞的注意力機制,使解碼器解碼時更關(guān)注代碼中的關(guān)鍵信息,提高了摘要的質(zhì)量.
從本文的實驗結(jié)果可以看出,符號區(qū)分大小寫、符號部分拆分以及顯示地利用關(guān)鍵詞信息(函數(shù)簽名和API調(diào)用)能夠幫助模型更好地生成代碼摘要.
進一步在KBCoS中添加一個API調(diào)用序列的編碼器,模型能Java代碼自動摘要數(shù)據(jù)集中取得當下最優(yōu)的結(jié)果,表明我們提出的方法也能提升別的模型的性能.