機器之心編輯部、赤樂君。
最近 AllenNLP 在 EMNLP2018 上做了一個主題分享,名為「寫給 NLP 研究者的程式設計指南」(Writing Code for NLP Research)。該演講從寫原型和寫模組兩方面介紹了 NLP 研究該如何複製別人的程式碼、測試自己的程式碼塊、記錄及分享研究等,總之在研究者也要高效碼程式碼的年代,這是一份濃縮的實踐經驗。
這份內容乾貨滿滿,僅僅只是看了 slide 就知道是非常有意思的一次演講了。slide 共有 254 頁之多,在「赤樂君」知乎專欄分享內容的基礎上,機器之心為大家介紹 NLP 及深度學習研究者的程式設計指南。
讀者可以直接下載 PPT 瞭解詳細內容,其中每一頁 PPT 都帶有簡要的備註,根據這些備註可以將所有 PPT 以及整場演講串聯起來。
下面是整個分享的大綱。通過這次演講,你可以學到如何寫程式碼來促進你的研究,以及可復現的實驗。當然讀者最好還是知道一點 NLP 相關的知識,因為這一份分享會以深度學習中的 NLP 問題作為案例。此外,能夠大致讀懂 Python 程式碼也是很好的背景,這篇文章都是以 Python 介面呼叫 DL 框架為例。
這裡有兩種寫研究程式碼的模式,一種是寫原型,一種是寫元件。作為一名研究者,大多數時候我們都希望寫原型,但是在沒寫好元件前是寫不好原型的。而通過原型設計,有時候做出來的東西又是希望下次再複用的元件。因此這是編寫程式碼的兩種模式,它們並不獨立。
我們先從寫原型的方式開始介紹。
寫原型
當我們開始寫一個原型程式碼的時候,我們要做到下面三點。
1. 寫程式碼要快
2. 跟蹤實驗結果
3. 分析模型結果
快速開發
要做到快速程式設計,不要從頭開始寫所有內容,而是使用框架。這裡的框架不僅指 tensorflow 或 pytorch 之類的框架,也可以理解為模板。比如上圖中如果寫 training loop 的部分,已經有人寫好了。我們只要看懂後,直接拿來用就行,沒有必要從頭開始自己寫所有部分。
上面提到的一些內容,都是可以找到現成框架來套用的。很多時候我們在程式設計時遇到的問題不是構建模型,而是資料讀取、預處理和寫訓練迴圈等部分。如果有人把你想用的東西模組化了,還等什麼,直接拿來用啊!
當然拿來用也是有步驟的,首先我們應該獲得基線模型的效能,這也是一個很好的研究實踐。基線模型可能是別人的程式碼,你要是能修修改改就更好了。其次復現 SOTA 基線結果對於理解模型和做更多的研究是非常有幫助的。
要想快速開發,另一個建議就是先複製,再重構。要記住,我們是在寫原型,不用在乎什麼可用性,先把程式碼寫 work 了再說。如果實現的效果不錯的話,再回去重構。
另外,我們要有好的程式設計習慣。比如起有意義的變數名,寫註釋幫助理解。記住,我們是寫給人看的,不是機器!此外在使用基線模型做試驗的時候,我們可以現在小資料集上做測試,並確保模型能準確讀取資料。
如果在做原型設計時,我們將 LSTM 寫死了(hard-code),那麼在我們希望使用 Transformer 等模組的時候就需要重新改程式碼。因此使用多型可以藉助更高階的抽象擴充套件程式碼,這樣在換模組時就能只修改少量程式碼。
跟蹤實驗結果
在寫原型的時候你需要執行很多東西,這導致很難追蹤發生了什麼以及對應的程式碼部分。
可以準備一個 Excel 表格,來記錄實驗結果。
黑箱對比對於上下文理解有幫助,但不能深入理解兩個結果之間的關係,因為有太多的變數在同時變化。我們需要每次僅改變一個變數,可以在程式碼中設定「開關」,將開關配置一些全域性狀態/依賴注入。
每次只改變一個部分,方便跟蹤實驗結果的變化其原因在於哪裡。
這裡光是 embedder,我們就有很多種選擇。
使用設定檔案來記錄模型的改變,方便我們以後查詢當時的設定。
分析模型結果
在訓練的時候,視覺化對於分析模型表現是非常重要的。這個技能必須掌握。
Tensorboard 可以提供很多分析結果。
Tensorboard 能幫我們找到優化的 bug。比如上圖中的 embedding 梯度有兩個數量級的差別。
原因在於 embedding 的梯度是稀疏梯度,即只有一部分會被更新。但是 ADAM 中的動量係數是針對整個 embedding 計算的,所以解決方法是直接引入特定的優化器:DenseSparseAdam。
在解釋你的模型的預測輸出時,好的展示是靜態預測;更好的展示是互動地檢視預測;最好的展示是互動地檢視內部過程。
對於預測結果,如果可以做到互動式的方式來檢視的話,是最好的。
開發元件
與寫原型不同,開發可重複使用的元件有很多要注意的地方。我們的程式碼需要寫清楚,這樣就能聚焦於建模決策,而不考慮程式碼到底在做什麼。
Code Reveiw 是必不可少的。Review 的時候,不僅能發現錯誤,還能提高程式碼的可讀性。
如果我們不是軟體開發人員的話,對於持續整合 以及構建自動化 這兩個詞可能比較陌生。通常我們只說持續整合的時候,也包含了構建自動化的意思。想要做到這點,要多寫測試才行。
當然,如果我們不是開發一個很多人都會用到的庫,上面這些步驟是用不到的。不過測試很重要,如果是原型開發,也要做一些最基本的測試。
如上對讀取的資料進行測試,看是否正確。這裡在進行單元測試時常用的就是 assert 語句,如果程式有問題,執行到這邊就自然會報錯,這樣可以讓我們儘快找到錯誤。
如上所示,當然我們也可以使用 assert 語句檢查維度是否一致。這在模型運算部分經常會用到,例如判斷每個卷積層輸出結果的尺寸和深度等。可以看到這兩種測試的程式碼都不會很多。所以不要犯懶了,好好寫測試吧。
關於 AllenNLP 庫的一些介紹,這裡就不花時間討論了,感興趣的可以看 slide 中 p141~p205 的部分。下面直接進入分享的部分。
分享研究
簡化安裝的流程,令程式碼執行在任何平臺,使用隔離的環境。
下面是使用 Docker 的一些優點。
用 docker 開發的好處不用多說,大家想必也已經都知道了。當然,缺點也是有的。
至於 Python 的包管理系統,AllenNLP 採用了 ANACONDA。
Docker 是不錯,但不適合做本地開發,這樣的話,使用一些本地的包管理系統反而更方便。
最後做個總結。
快速開發原型(要安全)
寫安全的產品程式碼(要快)
好的流程有利於做出好的研究
使用正確的抽象
檢視 AllenNLP(廣告)
這次分享的 slide 看了幾遍,很多地方看得自己臉上發熱,不寫測試什麼的說到了痛處。現在人工智慧領域對於演算法工程師的要求已經不是能掉個包,談談研究那麼簡單了,工程實踐能力已經變得越來越重要。寫優秀的程式碼,做優秀的研究,二者是一個互相促進的過程。