機器之心報道
編輯:張倩
AlphaCode 到底是怎麼練成的?
春節期間,DeepMind 的程式設計版 AlphaGo——AlphaCode 一度火到刷屏。它可以編寫與普通程式設計師水平相媲美的計算機程式,在 Codeforces 網站的 10 項挑戰中總體排名前 54.3%,擊敗了 46% 的參賽者。
這一成績給程式設計師群體帶來了不小的壓力,彷彿紡織工被紡織機淘汰的歷史正在重演。
那麼,AlphaCode 是如何做到如此強大的?在最近的一個 YouTube 影片中,清華大學朱軍門下博士後 Tim Pearce 詳細解析了 AlphaCode 的系統架構、「解題」原理、訓練流程等內容。
原影片地址:https://www.youtube.com/watch?v=YjsoN5aJChA
AlphaCode 到底做了什麼?
前面提到,DeepMind 的研究者將 AlphaCode 放在 Codeforces 挑戰中進行了測試。Codeforces 是一個線上程式設計的平臺,裡面有各種各樣的程式設計題目,各種各樣的比賽。它類似於國際象棋中使用的 Elo 評級系統,每週分享程式設計挑戰和問題排名。不同於程式設計人員在打造商業應用程式時可能面臨的任務,Codeforces 的挑戰更加獨立,需要對計算機科學中的演算法和理論概念有更廣泛的瞭解,一般是結合邏輯、數學和編碼專業知識的非常專業的難題。
下圖展示了其中一個賽題的例子,包含賽題描述、輸入輸出示例等內容,挑戰者的任務就是根據這些內容寫出一段程式碼,使得其輸出符合要求。
以下是 AlphaCode 寫出的程式碼:
對於 AlphaCode 來說,這還只是中等難度的挑戰。
在類似的十項挑戰中,研究者將賽題輸入 AlphaCode。然後,AlphaCode 生成大量可能的答案,並透過執行程式碼和檢查輸出來篩選這些答案,就像人類競爭對手一樣。AlphaCode 論文的聯合負責人 Yujia Li 和 David Choi 表示:「整個過程是自動的,無需人工選擇最佳樣本。」
為什麼 AlphaCode 那麼厲害?
下圖是 AlphaCode 的概念圖。這是一個精心設計的系統,主要構建塊是基於 Transformer 的語言模型。但從本質上來說,沒有一個單獨的元件是全新的。
「考場」上的 AlphaCode
我們先來看一下上述系統在測試時是如何工作的。
在解決編碼問題時,AlphaCode 使用了一個非常具體的協議(protocol),這個協議決定了整個系統的 pipeline。協議規定,他們可以無限制地使用示例測試用例,因為這些是作為問題的一部分給出的。但在提交給隱藏測試用例時,他們將提交版本數限制在了 10 次以內(至多 10 次)。
在測試時,AlphaCode 經歷了三個階段。
在第一個階段,他們使用一個大型 Transformer 模型,該模型將問題描述示例測試和一些關於問題的元資料都放在一個字串中作為輸入。然後,他們從這個模型中取樣,生成大量的潛在解決方案。所以第一個階段得到的是 100 萬套可能的程式碼指令碼。
在第二個階段,他們用示例測試用例測試了得到的 100 萬套程式碼,其中 99% 的程式碼都沒有透過測試,可以直接排除。這就將可行的程式碼套數降到了 1000 個左右(具體數量取決於題目的難度)。
在第三個階段,他們使用了第二個 Transformer 模型。該模型將問題描述作為輸入,但它並沒有試圖生成程式碼來解決問題,而是生成測試用例輸入(每個問題對應 50 個輸入)。也就是說,他們並沒有選擇生成輸入和輸出對,而是生成了一些與問題相關的實際輸入。所以模型可能要生成字串、二進位制數或數列(具體生成形式取決於問題型別)。
這種做法好在哪兒呢?他們認為,如果兩個指令碼為所有 50 個測試返回相同的答案,那麼它們可能使用的是相同的演算法。這就可以避免浪費兩次提交機會把這兩個指令碼都測試一下。所以在第二步得到 1000 套指令碼後,他們就根據這 50 個生成的測試輸入的輸出對指令碼進行聚類,然後從每個聚類中選出一個示例指令碼,總共選出 10 個。如果這 10 個指令碼中有一個通過了所有的隱藏測試,那麼他們就成功地解決了這個程式設計問題,否則就宣告失敗。
以上就是 AlphaCode 在測試時的工作原理,其中用到了兩個 Transformer 模型。那麼這兩個模型是怎麼訓練的呢
AlphaCode 的訓練
AlphaCode 的訓練分為兩個階段:預訓練和微調。這一過程涉及兩個資料集:第一個是由各種程式語言組成的公共 GitHub 庫,用於預訓練,資料量高達 715GB;第二個是從各個程式設計挑戰網站(包括 codeforces)蒐集的賽題,用於微調,包括問題描述、測試用例和人類程式設計師編寫的答案。
資料集有了,接下來就是訓練了。
在預訓練階段,他們會抓取 GitHub 上的一些程式碼,然後隨機選擇他們所謂的「pivot point」。
pivot point 之前的所有東西都將被輸入到編碼器中,解碼器的目標則是重建 pivot point 以下的程式碼。編碼器輸出程式碼的向量表示,後續可用於整個解碼過程。
解碼器以自迴歸的方式工作。它從預測程式碼的第一個 token 開始。損失函式就是預測的 softmax 輸出和真實 token 之間的交叉熵。然後,第一個真實的 token 成為解碼器的輸入,第二個 token 隨之被預測出來。在解碼器被要求預測出一個特殊的程式碼結束標記前,這種情況會一直重複下去。現在,這些損失透過解碼器和編碼器反向傳播,但對於編碼器來說,增加第二個損失是非常重要的。這被稱為掩蔽語言建模損失:你將輸入到編碼器中的一些 token 留空,作為一種輔助任務,編碼器會試圖預測哪個 token 被掩蔽了。
預訓練結束之後就到了微調環節。在這個環節,他們將問題描述元資料和示例的輸入輸入到編碼器中,試著用解碼器生成人類編寫的程式碼。此時可以看到,這與編碼器 - 解碼器架構所規定的結構非常自然地吻合在一起。這一階段的損失與預訓練時完全相同。而且,這裡也有第二個 Transformer 用來生成測試輸入。這也是由相同的 GitHub 預訓練任務初始化的,但它被微調以生成測試輸入而不是程式碼。
除了上面提到的常規訓練步驟和架構,AlphaCode 還從最近的其他論文中借鑑了一些經驗。Tim Pearce 指出了其中比較不錯的一個:
AlphaCode 的元資料調節
除了問題描述,研究者還總是將元資料作為 Transformer 的輸入,包括程式語言、問題的難度等級、關於問題的一些標籤,以及解決方案是否正確等。在訓練時,模型顯然知道這些欄位的值是什麼,但在測試時,它們並不知道。非常有趣的是,他們可以在測試時向這些欄位中輸入不同的東西來影響生成的程式碼。例如,你可以控制系統將要生成的程式語言,甚至影響它試圖生成的解決方案型別,比如是嘗試動態程式設計方法還是進行窮舉搜尋。
他們在測試時發現,當他們對最初的 100 萬個程式碼指令碼進行取樣時,將很多欄位隨機化是非常有幫助的。因為,透過增加原始取樣池的多樣性,正確答案出現的可能性就會增加。
以上就是 Tim Pearce 對 AlphaCode 的全部解析。他認為,DeepMind 的團隊在這項工作中的確取得了一些進展。但他比較不解的是:為什麼他們在這些程式設計問題中取得的成就遠遠不及他們在圍棋、《星際爭霸》等遊戲中取得的超越人類的成果?Tim Pearce 初步猜測是因為程式設計問題比較難,而且資料比較難以獲取,因為在遊戲中你可以無限制地產生很多模擬資料,但在程式設計問題上卻不能這麼做。
參考連結:
https://www.youtube.com/watch?v=YjsoN5aJChA
https://storage.googleapis.com/deepmind-media/AlphaCode/competition_level_code_generation_with_alphacode.pdf
