【NLP】知乎文字分類比賽第一名筆記
知乎“看山杯” 奪冠記
研究僧
537 人讚了該文章
知乎看山杯奪冠記
Update:2017-09-03: 新增2.6訓練方法說明
七月,酷暑難耐,認識的幾位同學參加知乎看山杯,均取得不錯的排名。當時天池AI醫療大賽初賽結束,官方正在為複賽進行平臺除錯,複賽時間一拖再拖。看著幾位同學在比賽中排名都還很不錯,於是決定抽空試一試。結果一發不可收拾,又找了兩個同學一起組隊(隊伍init)以至於整個暑假都投入到這個比賽之中,並最終以一定的優勢奪得第一名(參見最終排名 )。
1. 比賽介紹
這是一個文字多分類的問題:目標是“參賽者根據知乎給出的問題及話題標籤的繫結關係的訓練資料,訓練出對未標註資料自動標註的模型”。通俗點講就是:當用戶在知乎上提問題時,程式要能夠根據問題的內容自動為其新增話題標籤。一個問題可能對應著多個話題標籤,如下圖所示。
這是一個文字多分類,多label的分類問題(一個樣本可能屬於多個類別)。總共有300萬條問題-話題對,超過2億詞,4億字,共1999個類別。
1.1 資料介紹
總的來說就是:
- 資料經過脫敏處理,看到的不是“如何評價2017知乎看山杯機器學習比賽”,而是“w2w34w234w54w909w2343w1"這種經過對映的詞的形式,或者是”c13c44c4c5642c782c934c02c2309c42c13c234c97c8425c98c4c340"這種經過對映的字的形式。
- 因為詞和字經過脫敏處理,所以無法使用第三方的詞向量,官方特地提供了預訓練好的詞向量,即char_embedding.txt和word_embedding.txt ,都是256 維。
- 主辦方提供了1999個類別的描述和類別之間的父子關係(比如機器學習的父話題是人工智慧,統計學和電腦科學),但這個知識沒有用上。
- 訓練集包含300萬條問題的標題(title),問題的描述(description)和問題的話題(topic)
- 測試集包含21萬條問題的標題(title),問題的描述(description),需要給出最有可能的5個話題(topic)
1.2 資料處理
資料處理主要包括兩部分:
- char_embedding.txt 和 word_embedding.txt 轉為numpy格式,這個很簡單,直接使用word2vec的python工具即可
- 對於不同長度的問題文字,pad和截斷成一樣長度的(利用pad_sequence 函式,也可以自己寫程式碼pad)。太短的就補空格,太長的就截斷。操作圖示如下:
1.3 資料增強
文字中資料增強不太常見,這裡我們使用了shuffle和drop兩種資料增強,前者打亂詞順序,後者隨機的刪除掉某些詞。效果舉例如圖:
1.4 評價指標
每個預測樣本,提供最有可能的五個話題標籤,計算加權後的準確率和召回率,再計算F1值。注意準確率是加權累加的,意味著越靠前的正確預測對分數貢獻越大,同時也意味著準確率可能高於1,但是F1值計算的時候分子沒有乘以2,所以0.5是很難達到的。
具體評價指標說明請參照
2 模型介紹
2.1 通用模型結構
文字分類的模型很多,這次比賽中用到的模型基本上都遵循以下的架構:
基本思路就是,詞(或者字)經過embedding層之後,利用CNN/RNN等結構,提取區域性資訊、全域性資訊或上下文資訊,利用分類器進行分類,分類器的是由兩層全連線層組成的。
在開始介紹每個模型之前,這裡先下幾個結論:
- 如果你的模型分數不夠高,試著把模型變得更深更寬更復雜
- 當模型複雜到一定程度的時候,不同模型的分數差距很小
- 當模型複雜達到一定程度,繼續變複雜難以繼續提升模型的分數
2.2 TextCNN
這是最經典的文字分類模型,這裡就不細說了,模型架構如下圖:
和原始的論文的區別就在於:
- 使用兩層卷積
- 使用更多的卷積核,更多尺度的卷積核
- 使用了BatchNorm
- 分類的時候使用了兩層的全連線
總之就是更深,更復雜。不過卷積核的尺寸設計的不夠合理,導致感受野差距過大。
2.3 TextRNN
沒找到論文,我就憑感覺實現了一下:
相比於其他人的做法,這裡的不同點在於:
- 使用了兩層的雙向LSTM。
- 分類的時候不是隻使用最後一個隱藏元的輸出,而是把所有隱藏元的輸出做K-MaxPooling再分類。
2.4 TextRCNN
參考原論文的實現,和RNN類似,也是兩層雙向LSTM,但是需要和Embedding層的輸出Concat(類似於resnet的shortcut直連)。
2.5 TextInception
這個是我自己提出來的,參照TextCNN的思想(多尺度卷積核),模仿Inception的結構設計出來的,一層的Inception結構如下圖所示,比賽中用了兩層的Inception結構,最深有4層卷積,比TextCNN更深。
2.6 訓練方法
要點:
- 基於詞和基於字的模型要分開訓,然後融合,一起訓的效果不好
- 使用官方給的word-embedding.txt和char-embedding.txt初始化Embedding層的權重
- 剛開始訓練的時候Embedding層的學習率為0,其它層的學習率為1e-3,採用Adam優化器(一開始的時候卷積層都是隨機初始化的,反向傳播得到的Embedding層的梯度受到卷積層的影響,相當於噪聲)
- 訓練1-2個epoch之後,Embedding層的學習率設為2e-4
- 每個epoch或者半個epoch統計一次在驗證集的分數
- 如果分數上升,儲存模型,並記下儲存路徑
- 如果分數下降,載入上一個模型的儲存路徑,並降低學習率為一半(重新初始化優化器,清空動量資訊,而不是隻修改學習率----使用PyTorch的話新建一個新優化器即可)
2.7 各個模型分數計算
訓練的時候,每個模型要麼只訓練基於詞(word)的模型,要麼只訓練基於字(char)的模型。各個模型的分數都差不多,這裡不再單獨列出來了,只區分訓練的模型的型別和資料增強與否。
可以看出來
- 基於詞的模型效果遠遠好於基於字的(說明中文分詞很有必要)。
- 資料增強對基於詞(word)的模型有一定的提升,但是對於基於字(char)的模型主要是起到副作用。
- 各個模型之間的分數差距不大。
2.8 模型融合
像這種模型比較簡單,資料量相對比較小的比賽,模型融合是比賽獲勝的關鍵。
在這裡,我只使用到了最簡單的模型融合方法-----概率等權重融合。對於每個樣本,單模型會給出一個1999維的向量,代表著這個模型屬於1999個話題的概率。融合的方式就是把每一個模型輸出的向量直接相加,然後選擇概率最大的5個話題提交。結構如圖所示:
下面我們再來看看兩個模型融合的分數:
第一列的對比模型採用的是RNN(不採用資料增強,使用word作為訓練資料),第二列是四個不同的模型(不同的結構,或者是不同的資料)。
我們可以得出以下幾個結論:
- 從第一行和第二行的對比之中我們可以看出,模型差異越大提升越多(RNN和RCNN比較相似,因為他們底層都採用了雙向LSTM提取特徵),雖然RCNN的分數比Inception要高,Inception對模型融合的提升更大。
- 從第一行和第四行的對比之中我們可以看出,資料的差異越大,融合的提升越多,雖然基於字(char)訓練的模型分數比較低,但是和基於詞訓練的模型進行融合,還是能有極大的提升。
- 採用資料增強,有助於提升資料的差異性,對模型融合的提升幫助也很大。
總結: 差異性越大,模型融合效果越好。沒有差異性,創造條件也要製造差異性。
另外模型融合還有個規律:越往上越難提升,有些模型在你分數較低的時候,對融合提升很明顯,當你分數較高的時候就沒什麼幫助,甚至會有干擾
2.9 MultiModel
其實模型融合的方式,我們換一種角度考慮,其實就是一個很大的模型,每一個分支就像多通道的TextCNN一樣。那麼我們能不能訓練一個超級大的模型?答案是可以的,但是效果往往很差。因為模型過於複雜,太難以訓練。這裡我嘗試了兩種改進的方法。
第一種方法,利用預訓練好的單模型初始化複雜模型的某一部分引數,模型架構如圖所示:
但是這種做法會帶來一個問題: 模型過擬合很嚴重,難以學習到新的東西。因為單模型在訓練集上的分數都接近0.5,已經逼近理論上的極限分數,這時候很難接著學習到新的內容。這裡採取的應對策略是採用較高的初始學習率,強行把模型從過擬合點拉出來,使得模型在訓練集上的分數迅速降低到0.4左右,然後再降低學習率,緩慢學習,提升模型的分數。
第二種做法是修改預訓練模型的embedding矩陣為官方給的embedding權重。這樣共享embedding的做法,能夠一定程度上抑制模型過擬合,減少引數量。雖然CNN/RNN等模型的引數過擬合,但是由於相對應的embedding沒有過擬合,所以模型一開始分數就會下降許多,然後再緩慢提升。這種做法更優。在最後提交模型復現成績的時候,我只提交了七個這種模型,裡面包含著不同子模型的組合,一般包含3-4個子模型。這種方式生成的權重檔案也比較小(600M-700M左右),上傳到網盤相對來說更方便。
2.10 失敗的模型或沒什麼用的方法
MultiMode只是我諸多嘗試的方法中比較成功的一個,其它方法大多以失敗告終(或者效果不明顯)
- 資料多折訓練:因為過擬合嚴重,想著先拿一半資料訓,允許它充分過擬合,然後再拿另外一半資料訓。效果不如之前的模型。
- Attention Stack,參考了這篇文章,其實本質上相當於調權重,但是效果有限,還麻煩,所以最後直接用等權重融合(權重全設為1)。
- Stack,太費時費力,浪費了不少時間,也有可能是實現有誤,提升有限,沒有繼續研究下去。
- Boost,和第二名Koala的方法很像,先訓一個模型,然後再訓第二個模型和第一個模型的輸出相加,但是固定第一個模型的引數。相當於不停的修正上一個模型誤判的(可以嘗試計算一下梯度,你會發現第一個模型已經判對的樣本,即使第二個模型判別錯了,第二個模型的梯度也不會很大,即第二個模型不會花費太多時間學習這個樣本)。但是效果不好,原因:過擬合很嚴重,第一個模型在訓練集上的分數直接就逼近0.5,導致第二個模型什麼都沒學到。Koala隊伍最終就是憑藉著這個Boost模型拿到了第二名,我過早放棄,沒能在這個方法上有所突破十分遺憾。
- TTA(測試時資料增強),相當於在測試的時候人為的製造差異性,對單模型的效果一般,對融合幾乎沒有幫助。
- Hyperopt進行超引數查詢,主要用來查詢模型融合的權重,效果一般,最後就也沒有使用了,就手動稍微調了一下。
- label設權重,對於正樣本給予更高的權重,訓練模型,然後和正常權重的模型進行融合,在單模型上能夠提升2-3個千分點(十分巨大),但是在最後的模型融合是效果很有限(0.0002),而且需要調整權重比較麻煩,遂捨棄。
- 用分類得到的詞向量作為下一個模型的embedding的初始值,因為官方給的word embedding是用無監督的word2vec訓練的,和有監督的分類問題還是有一定偏差的。沒有深入研究下去,對單模型應該是有提升,但是對融合可能沒什麼幫助。