生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

參與:思源

大家都說深度神經網絡能力很強,那麼從函數註釋生成函數代碼,以及從函數代碼總結函數註釋這種最基礎的代碼任務到底能不能行?像 Python、Java 這樣的通用高級語言,到底在代碼生成上能達到什麼水平?本文介紹的就是這樣一篇北大前沿研究。

開發者寫代碼,和數學家寫公式一樣是非常自然的一件事。開發者將完成某個任務的步驟和邏輯,一行行寫成代碼,並期待達到預定的效果。數學家從某個事實出發,將思考過程一行行寫成表達式,並期待找到複雜邏輯背後的簡單關係。

這兩者經常會有交叉,也會有融合。數學推導結果可以大量簡化代碼,並提供新的解決路徑;而代碼可以快速驗證推導過程,並用於實際的生活中。代碼和表達式都是一種形式化語言,而另一種必不可少的是用來描述它的自然語言,也就是註釋或文檔。

通過註釋,我們能知道這段代碼幹了什麼,甚至很自然地想到「如果是我,這段代碼該怎麼寫」。通過閱讀代碼,我們能沿著開發者的思路走一遍,總結出它到底幹了什麼。這兩者似乎是一種對偶關係,從代碼到註釋、從註釋到代碼,這就是代碼生成與代碼總結兩大任務。

在這篇文章中,我們將介紹代碼生成與總結的最新進展,北大 Bolin Wei、李戈等研究者提出的對偶學習在 Python 和 Java 代碼生成上獲得了新的 SOTA,並且被接收為 NeurIPS 2019 論文。

如下是北大新研究根據註釋生成的兩段代碼,其中 dcsp 表示 tab 鍵、dcnl 表示換行符,它們控制 Python 代碼的縮進結構。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


值得注意的是,在 Python 語言上,根據註釋這種自然語言,生成有效的代碼已經達到了 51.9% 的準確率。也就是說,生成的一半代碼能通過詞法分析、語法分析,並生成正確的抽象語法樹

代碼生成與總結,是一對兄弟

之前這兩項研究大多都是獨立的,代碼總結會利用 Encoder-Decoder、抽象語法樹和 Tree RNN 等技術生成意圖,代碼生成會利用 Seq2Seq、語法規則和基於語法的結構化 CNN 來生成代碼,這些研究並沒有深入挖掘它們之間的關係。

而北大的這一項研究從對偶學習出發,探索如何利用它們之間的關係促進提升學習效果。

具體而言,研究者考慮了概率與注意力權重中的對偶性,從而設計了一種正則項來約束對偶性。更直觀而言,這種「對偶性」表示代碼生成任務的輸入"意圖"同樣是代碼總結的輸出,反之亦然。其中意圖指開發者寫這一段代碼的目的,一般而言會通過註釋的方式用自然語言表達。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


利用對偶學習,研究者獲得了當前最優的效果。其實這種提升也非常合理,例如當前效果最好的神經機器翻譯模型 Transformer Big + BT,它就大量採用回譯機制,希望根據原語與目標語之間的相互翻譯,從而得到更好的最終模型。

統一的聯合訓練框架

如下所示為代碼生成、總結的對偶學習框架,總體上生成與總結兩條路徑都非常容易理解,它們都採用了常規基於注意力機制的 Seq2Seq 模型。現在重要的是理解中間的對偶約束,該約束用於給損失函數加正則項,從而令它們之間相互促進。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

對偶訓練的整體過程,代碼生成模塊與總結模塊會聯合訓練。

上面 Seq2Seq 的過程就不再贅述了,它們採用的損失函數也是常規將所有時間步上的損失相加。不過需要注意的是,源代碼的詞彙量要比註釋更大一些,因此代碼生成模塊輸出層的參數量要大於代碼總結的輸出層參數量。

聯合概率來約束

如前所述,對偶訓練框架包含了非常重要的對偶約束,它由兩個對偶正則項組成,分別用於約束兩個模型的對偶性。這兩種正則項受到了注意力權重具有對稱性的啟發,也受到了兩種模型之間概率相關性的啟發。

若現在給定輸入樣本,其中假設 x 為代碼,y 為對應的代碼註釋。那麼代碼生成可以描述為 p(x|y)、代碼總結可以描述為 p(y|x)。現在如果要找到它們之間的概率相關性,那麼根據聯合概率與條件概率之間的關係式就可以快速得出:

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


也就是說,logP(x) + logP(y|x) 需要等於 logP(y) + logP(x|y),這是代碼生成與總結的內在聯繫。如果兩項差別很大,那麼至少可以判定代碼生成與總結都沒有達到最優。所以,常規的做法就是把這個約束構建為損失函數:

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


其中 P(x) 和 P(y) 分別是針對代碼和註釋的語言模型,它們都是邊緣分佈。這個損失有點類似於迴歸模型常用的均方誤差,如上所示,只要兩個子模型不滿足理論上的概率條件,那麼肯定會產生損失,在訓練中就會建立起代碼生成與總結的關係。

注意力權重也來約束

上面是其中一個正則項,另一個正則項主要是考慮兩個子模型之間的對稱性。在北大的這一項研究中,他們考慮了注意力權重的對稱性。研究者表明,因為注意力權重能度量源代碼 Token 與註釋 Token 之間的匹配關係,而這種匹配關係又是對稱的,所以注意力權重也需要是對稱的。

研究者舉了一個例子,例如代碼註釋為「find the position of a character inside a string」,那麼對應源代碼可能為「string . find ( character )」。現在,不論是從代碼到註釋還是從註釋到代碼,源代碼中的「find」一定需要匹配到註釋中的「find」,它們之間的關係是不變的。

所以,現在最直觀的思想是,

我們希望兩個注意力權重矩陣 A_xy 和 A_yx,它們之間對應的元素儘可能相等

因為 A_xy 表示代碼部分注意到註釋部分的程度,所以,A_xy 矩陣的每一行表示代碼的某個 Token,與註釋的所有 Tokens 之間的關係。同理 A_yx 表示註釋部分注意到代碼部分的程度,A_yx 的每一列表示代碼的某個 Token,和註釋的所有 Tokens 之間的關係。

具體而言,如果

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

,其中 i 表示 A_xy 的第 i 行;

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

,其中 i 表示 A_yx 的第 i 列。那麼很明顯,我們需要令 b_i 儘可能等於 b_i'。如果它們非常相近,那麼可以表明注意力權重矩陣是對稱的,源代碼和代碼註釋之間的匹配是成功的。因為經過 softmax 的 b_i 和 b_i'都是一種概率分佈,所以北大研究者通過 JS 散度度量這兩類分佈之間的距離。

最常見的 KL 散度是不對稱的,也就是說 KL(b_i || b_i') 不等於 KL(b_i' || b_i),而 JS 散度是 KL 散度的「對稱版」,所以採用 JS 散度非常合理。此外,因為 JS 散度是對稱的,所以代碼生成模型與代碼總結模型都能採用這樣的距離度量作為約束條件。

最後,以注意力權重的對稱性作為正則項,JS 散度可以表述為:

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


偽代碼帶你走近聯合訓練

現在兩種正則項都已經完成了,只需要聯合訓練兩個子模型就行了。如下算法 1 所示,輸入兩種數據源的語言模型預計對應的數據,模型就能開始學。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


如上所示,對於每一個批量數據,模型會計算兩個子模型各自的預測損失,並同時計算兩個公共的對偶正則項。這樣的損失能算出對應的梯度,並分別更新兩個子模塊的權重。

目前該研究的開源實現已經放到了 GitHub,研究者使用 PyTorch 實現了整個模型的訓練過程。如上偽代碼所示,模型架構方面,Seq2Seq 大家已經比較熟了,我們需要重點理解的是目標函數。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?


如上代碼片段所示,損失函數主要由三部分組成:即常規的交叉熵損失函數,它度量生成序列與標註序列間的距離;對偶損失函數,它度量的是代碼與註釋的概率相關性;最後是注意力損失,它度量的是兩組注意力權重之間的分佈距離。

通過這些訓練目標的約束,代碼生成與總結才會真正地相輔相成。

真實的 GitHub 代碼生成

這種最正統的代碼生成與總結無疑是非常困難的,它遠遠不能像 UI 界面那樣生成簡易的代碼。也許藉助卷積神經網絡,UI 界面的代碼生成已經能用於實際的界面設計,但是對於「更正統」的純代碼生成,目前的準確度還遠遠不能滿足我們的要求。

在這篇論文中,北大研究者在 Java 與 Python 兩個數據集,測試了代碼生成與總結的效果。其中 Java 數據集是從 GitHub Java 項目中抽取的 Java 方法,以及對應的自然語言註釋,該自然語言了這個方法的用途。與 Java 類似,Python 數據集也是從 GitHub 中抽取的。兩種數據集的統計信息如下所示:

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

論文表 1,我們可以看到,訓練集有 5 萬到 7 萬段代碼,且確實一段 Python 代碼平均長度要遠遠少於 Java 代碼。

最後,我們可以看看北大研究者得出的最終效果。他們主要通過 BLEU 值、METEOR 和 ROUGE-L 三種度量方法評估模型生成的代碼註釋,這對於自然語言生成來說是比較常規的度量標準;此外,研究者通過 BLEU 值與有效代碼率(PoV)來評估代碼生成的效果,其中 PoV 指生成代碼能解析為抽象語法樹的比例。

生成Python函數一半沒問題,當前最「正統」的代碼生成是什麼樣?

如上所示為代碼生成與總結的總體效果,我們可以發現對偶訓練效果要超過其它方法,且相比獨立訓練的 Basic Model,效果也要更好一些。

值得注意的是,在代碼生成中,Java 和 Python 的 PoV 分別只有 27.4 與 51.9%。也就是說,生成的代碼首先不管是不是完成了自然語言描述的功能,它能通過詞法分析、語法分析,最終成功地構建成抽象語法樹,佔比並不高。

這樣的效果,也許代表著正統代碼生成,最前沿的水平。它離生成合理的代碼,輔助開發者完成實戰開發還太遠了。正如該論文作者李戈教授所說,程序的數據空間非常稀疏,而自然語言數據空間也比較稀疏,這兩個稀疏空間的變換肯定會比較困難。它並不能像圖像生成這種連續空間的變換,程序的生成還有很長的路要走。


分享到:


相關文章: