文本多分類踩過的坑
Part I:背景
Part II:數據不均衡問題
Part III:過擬合問題
Part IV:Attention機制
Part V:調參技巧
Part I:背景
文本多分類是我大四入門NLP時的第一個任務,做了以下工作:
1. 基於CNN的方法:普通CNN分類[1],我自己改進的UCN
2. 基於LSTM的方法:GRU,LSTM,BLSTM,Hierarchy-LSTM,RCNN
3. 結合CNN和LSTM的方法,主要在C-LSTM[2]模型上進行改進。
4. 最後跑了MLP進行對照,用TF-IDF特徵和LSI特徵。(漏了SVM大佬)
這篇文章僅針對模型不work的一些問題進行總結,不會對模型進行詳細介紹,相關綜述可以看這裡:基於深度學習的文本分類綜述
分類函數:
我研究的是一對多的文本多分類問題,二分類任務通常使用sigmoid函數進行分類,多分類任務通常使用softmax函數,使得所有類別的概率之和為1:
從這個公式可以看出,指數函數會使得原本就大的數值會變得更大,而原本小的數值會變得更小。所以softmax比較適合一對一的多分類問題,也就是類與類之間是互斥的,但是並不適用於一對多的多分類問題。
所以我把這個多分類問題轉化成K個二分類問題,假設類別數量為K,這裡我用K個sigmoid函數來代替softmax函數,分別對應K個類別的分類概率。這個任務應用場景是AI+法律,類別數接近800,倒也不是什麼大問題,比較難的是類別不均衡問題。
衡量指標:
在分類問題中,最常見的評估指標是準確率(Accuracy),定義為在測試集中預測正確的樣本佔總樣本的比例。但是對於不均衡數據,有些類別樣本非常多,有些類別樣本非常少,那麼樣本中佔大部分的類別就會對Accuracy造成很大的影響,比如,模型可能直接全部預測為這個類別,Accuracy依然還是很高,難以很好地評估模型的好壞。
因此對於不均衡數據,這裡介紹精確率(Precision),召回率(Recall)與F1值。
Precision定義為預測正確的真正樣本占所有被預測為正樣本的比例,Recall定義為預測正確的真正樣本占所有真正樣本的比例,而F1值定義為Precision和Recall的調和平均值:
可以看到F1值會更接近P和R中較小的值,我們也可以自己設置Precision和Recall的重要性:
>1的時候Recall影響比較大, <1的時候Precision影響比較大。
我當時用的衡量指標是F1值,當然也可以用ROC曲線或AUC曲線來衡量。
Part II:數據不均衡問題
數據不均衡問題在這裡體現為類別不均衡,假設正例很少,負例很多,我們可以從數據本身和模型兩方面來解決這個問題:
一. 數據方面
既然正負例數據比重失調,那我們可以對原始數據集進行採樣調整,使得調整後的數據集正負例數據接近,再進行訓練。常見的採樣方法包括欠採樣和過採樣兩種。
1. 欠採樣方法
欠採樣是丟棄訓練集的一些負例,那麼經過調整後的數據集往往會遠小於原始數據集。
但是欠採樣往往要丟棄很多負例,可能會損失很多重要信息,因此我們在採樣的時候,可以盡量選取比較重要的核心負例,丟棄沒那麼重要的負例。
2. 過採樣方法
過採樣是對正例進行擴充(數據增強),增加正例使得正反例數據接近,那麼經過調整後的數據集往往會遠大於原始數據集,增加了時間開銷。
在NLP中我了解到的數據增強方法主要有:
1)加入雜訊:在正例中加入一些雜訊,比如隨機刪掉某些詞,隨機打亂詞語順序(在文本分類任務上或許可以,但是在一些有語序要求的任務上就不行),生成新的數據。
2)複述生成(paraphrasing):屬於seq2seq任務了,在問答系統有一個領域叫做問題複述,根據原始問題生成格式更好的問題,相當於修正不規範的問題,將新問題代替舊問題輸入到問答系統中,我覺得的也算是一種數據增強方法了吧。
其實我自己平時並不用數據增強方法,因為我覺得文本和圖像不一樣,圖像對某一些像素加入雜訊,影響可能不是很大,但是在文本中一點小的改動可能就會帶來很大的影響,你刪掉的詞很有可能就是比較重要的詞,如果你刪掉的詞不重要那相當於刪去停詞,打亂順序在很多任務上就更不行了,因為句子本身是有語序的,有上下文關係。文本生成倒是一種 我認為相對比較好的數據增強方式。
二. 模型方面
這種方法直接基於原始訓練集來學習,對原始模型進行改進。
1. 閾值移動
這是很常見的一種做法,對演算法的決策過程做了改進。在使用sigmoid函數來預測正負例的時候,我們通常設置閾值為0.5,激活值大於0.5的時候判定為正例,否則為負例。
如今我們正負例比例失調,可以調低閾值,相當於放寬對正例的分類要求,提高對負例的分類要求。比如將閾值設置成0.3,那麼激活值大於0.3就可以判定為正例了。
我當時也是用的這種方法,手動改閾值,這也是比較麻煩的一點,不好調超參,我專門設置了好幾組超參閾值來自動選擇最優的那組。
2. 加權Loss
這種方法不需要調整sigmoid閾值,而是修改了Loss函數。
原始的Loss函數是binary cross entropy,極小化負對數似然函數:
y是label,取值為1或者0,a是sigmoid激活值,從公式中可以看到正例loss和負例loss是等權平均的,因此我們可以提高正例loss比重,使得正例更加重要,強迫模型去學好正例。
另外,我做序列標註任務也遇到過數據不均衡問題,在對一段文本進行標註的時候,某些tag非常非常多,某些tag非常非常少,這裡用的是softmax+交叉熵,pytorch提供了一個函數,可以直接設置正確tag所對應的權重。
在設置權重的時候也有一些方法,可以根據這些tag在整個序列中的標註次數進行設置。比如,我有兩個tag,tag a出現了100次,tag b出現了2次,那麼我可以設置tag a的比重是2/100,tag b的比重是98/100,或者直接設置一個固定的比重。
如果權重設置得太狠了,最終也可能導致tag b預測太多,又需要強行調整很麻煩,所以我會對Loss進行動態調整,如果這個位置的tag預測對了,我就恢復正常Loss比重,否則設置加權Loss進行懲罰。
Part III:過擬合問題
過擬合的表現為在訓練集上表現很好,在測試集上表現不好。過擬合一般是因為數據太少,或者模型太複雜,對數據中的一些誤差也進行了擬合,對這個訓練集擬合得非常好,但是改了一下數據集,就不work了。在很多文本分類任務上,fastText其實已經足夠了,其實不需要太過於複雜的模型。(我做這個任務的時候fastText好像還沒有出來)
我們依然可以從數據和模型兩方面出發來解決。
關於數據,我們可以增加標註數據(深度學習比較依賴於大量的數據,從大量的數據中學習數據特徵),或者進行數據增強(Part II有提到),讓模型見到更多的數據去學習。
關於模型,我在用LSTM的時候出現了過擬合,因為用到了兩層雙向LSTM(相當於四層普通LSTM的參數量),Attention機制(擬合得更好了)。
過擬合一方面體現在,在訓練集上效果有提升,但是在測試集上效果反而降了。我當時主要從減少參數,簡化模型這方面入手,比如:減小hidden state維度,使用GRU來代替LSTM,使用一層雙向疊加一層單向LSTM,使用CNN疊加一層LSTM。
另外關於雙向LSTM,我現在有一種思路是,可以在正向LSTM後直接接上反向LSTM,雖然做法略顯粗糙,但是相比原始雙向LSTM可以減小一倍的參數量。
過擬合另一方面體現在,在訓練集上,同一段文本,我稍微修改了一些詞語,刪掉或者加入「的」這類詞語,預測結果就有改變。這說明,LSTM把不重要的詞語也擬合進去了,這些詞語本來是不重要的,但是LSTM依然進行了學習。
比較粗暴的方法是加入去停詞,進行特徵工程,不讓LSTM學習這些詞。
或者一種更優雅的方式是使用stop,skip或者skim reading,進行信息過濾,在我另一個回答中第二點有提到 利用RL輔助學習語義向量。
對付過擬合問題,還有一些其它的tricks:
1)正則項,在Loss中加入權重大小,限制權重變大,限制模型的學習能力。
2)Dropout,我一般設置dropout的閾值是0.3,對於每一個神經元,按照一定的概率暫時丟棄,相當於每個batch都在訓練不同的網路。需要在訓練的時候對權重進行放大,或者在測試的時候對權重進行縮小,使得dropout後的分布可以擬合之前的分布。
3)加雜訊,對輸入或者權重加高斯雜訊,目的也是為了減小權值。我之前做過一個序列預測任務,對輸入加了雜訊後效果是有提升的。
4)Ensemble模型
集成學習可以構建或者結合多個學習器進行學習,分類問題一般由這些個體學習器進行投票來決定,在一定程度上可以降低過擬合。個體學習器應該好而不同,具有一定的準確率,又要有多樣性,如果個體學習器之間差異很小,集成就沒有多大作用了。
我在文本分類任務上沒有進行ensemble,但是我在別的任務上有用到。
集成學習根據個體學習器之間的依賴關係,可以分成Bagging和Boosting兩大類:
Bagging:並行化學習方法,個體學習器之間沒有依賴關係,可以降低方差。
主要思想是:對數據集進行回放採樣,產生k個子訓練集集,用這k個子訓練集分別訓練出k個學習器,最後使用這些學習器進行投票或者加權平均,得到最終結果。
Boosting:串列化學習方法,可以降低偏差(描述預測結果和真實結果的差距)。
主要思想是:基於整個訓練集先學第一個學習器,然後根據前面的學習結果來調整樣本發布,再訓練第二個學習器,以此類推,最後再結合所有學習器。
經典代表是xgboost,可以在降低偏差的基礎上,又可以降低方差,因為它借鑒了隨機森林,隨機選擇特徵(通過隨機性降低過擬合),也在Loss上加入了正則項。我之前做過一個kaggle小比賽,正負例比重達到1:3000,直接上xgboost跑出了一個不錯的效果。
Part IV:Attention機制
Attention機制最初是用於機器翻譯(seq2seq)中,在Decoder每一步的詞語c上,用該步的信息對Encoder所有步的詞語進行對齊,捕捉跟這個詞c比較有關的詞語,這種利用到外部信息的方式我們叫做align attention,只利用自身信息的方式叫做self attention。
Align Attention又可以分成soft attention,hard attention和local attention幾種形式,soft方式對所有詞進行加權對齊,而hard方式直接對齊到某個詞,local方式結合了soft和hard,對齊到某一個區域。
那麼Attention機制用在文本分類中,我們可以看做是對句子進行加權,提高重要詞語的注意力,減小其它詞語的關注度。後面也可以對Attention進行可視化,可以比較直觀得看到哪些詞比較重要,哪些詞沒有關注到,比較具有可解釋性。需要注意過擬合。
我當時自己實現了一個粗暴的Attention,直接用最後一個step的hidden state經過全連接層,生成所有step權重(也就是一個維度為句子長度的向量,再進行softmax歸一化),再對所有step的hidden state進行加權平均,得到最終的一個語義向量,來表徵整個文本。我當時考慮到,建模到最後一個step的時候,這個hidden state其實能包含前面所有信息(當然會有信息衰減),那麼用這個state去生成權重應該算是合理。
現在看來,這種方式無法從直觀上解釋,向量中的這個權重到底是怎麼對應到那個詞的,因為我僅僅用最後一個state去生成所有的權重,沒有和每一步的state做對齊。
正確的打開方式應該是:用每一步的信息來算該步的權重,保證了每個權重值是根據這個詞來算的,比較合理也比較直觀。
用每一步的hidden state通過一個共享的全連接層,生成向量ut:
每一步對ut和uw進行點乘可以得到一個數,那我們把所有step的這些數值拼接成一個向量,再進行softmax歸一化就得到權重了:
注意:這裡uw也是一個權重,我覺得或許可以直接把所有ht等權平均作為uw。
另外從層次上看,Attention機制在文本上又可以分成單詞級別和句子級別,在單詞級別上挑選重要的單詞,在句子級別上挑選重要的句子。
Part V:調參經驗
最後放上一些我自己的調參經驗。
1. 數據預處理:數據歸一化,權重初始化,在詞典中去掉詞頻太低的詞(因為這些詞本身出現很少,我們不需要特意進行擬合),用word2vec預訓練詞向量,我喜歡把數據保存成H5py文件,再寫個DataLoader讀batch數據,數據也要進行shuffle。
2. Hidden state維度,我在分類任務,閱讀理解任務上一般設置維度是128,在seq2seq任務上會設置比較大一些,512或者1024都可以有。
3. Batch Normalization你值得擁有,保你梯度無憂,又可以加快訓練速度。
4. Adam你也值得擁有,收斂也很快,但是最後的準確率可能比不上SGD+Momentum,你可以先用Adam,下降到一定範圍了再換SGD。
5. TensorBoard也要擁有,方便調參。
6. Relu也是一個好東西,除了最後一層分類層以外(或者其它需要限制範圍到[0, 1]之間),激活函數我都用Relu,收斂速度也很快,保證了一定的梯度。
7. 到底要不要分詞?其實直接用字向量也可以,因為現有的分詞工具可能也會分得不準確。我做過實驗,字向量其實也可以比較好地表徵文本。
8. ..........
引用文獻:
[1] Chen, Yahui. "Convolutional neural network for sentence classification." (2015).
[2] Zhou, Chunting, et al. "A C-LSTM neural network for text classification." arXiv preprint arXiv:1511.08630 (2015).
推薦閱讀:
※基於隱馬爾科夫(HMM)模型的中文分詞實踐
※《Dialogue Act Sequence Labeling using Hierarchical encoder with CRF》閱讀筆記
※Python3 環境下的 NLTK學習(第一章)
※Learning to Skim Text 閱讀筆記
※人語機器
TAG:自然語言處理 |