如何用 Python 和深度遷移學習做文字分類?

王樹義發表於2018-11-01

如何用 Python 和深度遷移學習做文字分類?

本文為你展示,如何用10幾行 Python 語句,把 Yelp 評論資料情感分類效果做到一流水平。

疑問

在《如何用 Python 和 fast.ai 做影象深度遷移學習?》一文中,我為你詳細介紹了遷移學習給影象分類帶來的優勢,包括:

  • 用時少
  • 成本低
  • 需要的資料量小
  • 不容易過擬合

有的同學,立刻就把遷移學習的這種優勢,聯絡到了自己正在做的研究中,問我:

老師,遷移學習能不能用在文字分類中呢?正在為資料量太小發愁呢!

好問題!

答案是可以

回顧《如何用機器學習處理二元分類任務?》一文,我們介紹過文字分類的一些常見方法。

首先,要把握語義資訊。方法是使用詞嵌入預訓練模型。代表詞語的向量,不再只是一個獨特序號,而能夠在一定程度上,刻畫詞語的意義(具體內容,請參見《如何用Python處理自然語言?(Spacy與Word Embedding)》和《如何用 Python 和 gensim 呼叫中文詞嵌入預訓練模型?》)。

如何用 Python 和深度遷移學習做文字分類?

其次,上述方法只能表徵單個詞語含義,因此需要通過神經網路來刻畫詞語的順序資訊。

例如可以使用一維卷積神經網路(One Dimensional Convolutional Neural Network, 1DCNN):

如何用 Python 和深度遷移學習做文字分類?

或者使用迴圈神經網路(Recurrent Neural Network, RNN):

如何用 Python 和深度遷移學習做文字分類?

還有的研究者,覺得為了表徵句子裡詞語順序,用上 CNN 或者 LSTM 這樣的複雜結構,有些浪費。

於是 Google 乾脆提出了 Universal Sentence Encoder ,直接接受你輸入的整句,然後把它統一轉換成向量形式。這樣可以大幅度降低使用者建模和訓練的工作量。

如何用 Python 和深度遷移學習做文字分類?

困難

這些方法有用嗎?當然有。但是 Jeremy Howard 指出,這種基於詞(句)嵌入預訓練的模型,都會有顯著缺陷,即領域上下文問題。

這裡為了簡化,我們們只討論英文這一種語言內的問題。

假設別人是在英文 Wikipedia 上面訓練的詞嵌入向量,你想拿過來對 IMDB 或 Yelp 上的文字做分類。這就有問題了。因為許多詞語,在不同的上下文裡面,含義是有區別的。直接拿來用的時候,你實際上,是在無視這種區別

那怎麼辦?直覺的想法,自然是退回去,我不再用別人的預訓練結果了。使用目前任務領域的文字,從頭來訓練詞嵌入向量。

可是這樣一來,你訓練工作量陡增。目前主流的 Word2vec , Glovefasttext 這幾個詞嵌入預訓練模型,都出自名門。其中 word2vec 來自於 Google,Glove 來自於史丹佛,fasttext 是 facebook 做的。因為這種海量文字的訓練,不僅需要掌握技術,還要有大量的計算資源。

同時,你還很可能遭遇資料不足的問題。這會導致你自行訓練的詞嵌入模型,表現上比之前拿來別人的,結果更差。維基百科之所以經常被使用來做訓練,就是因為文字豐富。而一些評論資料裡面,往往不具備如此豐富的詞彙。

怎麼辦呢?

遷移

Jeremy Howard 提出了一種方法,叫做“用於文字分類的通用語言模型微調(ULMFiT)”。論文在這裡:Howard, J., & Ruder, S. (2018). Universal language model fine-tuning for text classification. In Proceedings of the 56th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers) (Vol. 1, pp. 328-339).

如何用 Python 和深度遷移學習做文字分類?

在這篇文章裡,他提出了一個構想。

  • 有人(例如早期研究者,或者大機構)在海量資料集(例如Wikipedia)上訓練語言模型。之後釋出這個模型,而不只是詞嵌入向量的表達結果;
  • 普通使用者拿到這個模型後,把它在自己的訓練文字(例如 Yelp 或者 IMDB 評論)上微調,這樣一來,就有了符合自己任務問題領域上下文的語言模型;
  • 之後,把這個語言模型的頭部,加上一個分類器,在訓練資料上學習,這就有了一個針對當前任務的完整分類模型;
  • 如果效果還不夠好,可以把整個兒分類模型再進行微調。

文中用了下圖,表達了上述步驟。

如何用 Python 和深度遷移學習做文字分類?

注意在這個語言模型中,實際上也是使用了 AWD-LSTM 作為組塊的(否則無法處理詞語的順序資訊)。但是你根本就不必瞭解 AWD-LSTM 的構造,因為它已經完全模組化包裹起來了,對使用者透明。

再把我們那幾個比方拿出來說說,給你打打氣:

你不需要了解映象管的構造和無線訊號傳輸,就可以看電視和用遙控器換臺;

你不需要了解機械構造和內燃機原理,就可以開汽車。

用 Python 和 fast.ai 來做遷移學習,你需要的,只是看懂說明書而已。

如何用 Python 和深度遷移學習做文字分類?

下面,我們就來實際做一個文字分類任務,體會一下“通用語言模型微調”和深度遷移學習的威力。

資料

我們使用的文字資料,是 Yelp reviews Polarity ,它是一個標準化的資料集。許多文字分類的論文,都會採用它進行效果對比。

我們使用的版本,來自於 fast.ai 開放資料集,儲存在 AWS 上。它和 Yelp reviews Polarity 的原始版本在資料內容上沒有任何區別,只不過是提供的 csv ,從結構上符合 fast.ai 讀取的標準化需求(也就是每一行,都把標記放在文字前面)。

點選這個連結,你就能看到 fast.ai 全部開放資料內容。

如何用 Python 和深度遷移學習做文字分類?

其中很多其他資料類別,對於你的研究可能會有幫助。

我們進入“自然語言處理”(NLP)板塊,查詢到 Yelp reviews - Polarity

如何用 Python 和深度遷移學習做文字分類?

這個資料集有幾百兆。不算小,但是也算不上大資料。你可以把它下載到電腦中,解壓後檢視。

如何用 Python 和深度遷移學習做文字分類?

注意在壓縮包裡面,有2個 csv 檔案,分別叫做 train.csv(訓練集)和 test.csv(測試集)。

我們開啟 readme.txt 看看,其中資料集的作者提到:

The Yelp reviews polarity dataset is constructed by considering stars 1 and 2 negative, and 3 and 4 positive. For each polarity 280,000 training samples and 19,000 testing samples are take randomly. In total there are 560,000 trainig samples and 38,000 testing samples. Negative polarity is class 1, and positive class 2.

之所以叫做極性(Polarity)資料,是因為作者根據評論對應的打分,分成了正向和負向情感兩類。因此我們的分類任務,是二元的。訓練集裡面,正負情感資料各 280,000 條,而測試集裡面,正負情感資料各有 19,000 條。

網頁上面,有資料集作者的論文連結。該論文發表於 2015 年。這裡有論文的提要,包括了不同方法在相同資料集上的效能對比。

如何用 Python 和深度遷移學習做文字分類?

如圖所示,效能是用錯誤率來展示的。 Yelp reviews - Polarity 這一列裡面,最低的錯誤率已經用藍色標出,為 4.36, 那麼準確率(accuracy)便是 95.64%。

注意,寫學術論文的時候,一定要注意引用要求。如果你在自己的研究中,使用該資料集,那麼需要在參考文獻中,新增引用:

Xiang Zhang, Junbo Zhao, Yann LeCun. Character-level Convolutional Networks for Text Classification. Advances in Neural Information Processing Systems 28 (NIPS 2015).

環境

為了執行深度學習程式碼,你需要一個 GPU 。但是你不需要去買一個,租就好了。最方便的租用方法,就是雲平臺。

在《如何用 Python 和 fast.ai 做影象深度遷移學習?》一文中,我們提到了,建議使用 Google Compute Platform 。每小時只需要 0.38 美元,而且如果你是新使用者, Google 會先送給你300美金,1年內有效。

我為你寫了個步驟詳細的設定教程,請使用這個連結訪問。

如何用 Python 和深度遷移學習做文字分類?

當你的終端裡面出現這樣的提示的時候,就證明一切準備工作都就緒了。

如何用 Python 和深度遷移學習做文字分類?

我把教程的程式碼,已經放到了 github 上面,請使用以下語句,下載下來。

git clone https://github.com/wshuyi/demo-nlp-classification-fastai.git
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

之後,就可以呼叫 jupyter 出場了。

jupyter lab
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

注意因為你是在 Google Compute Platform 雲端執行 jupyter ,因此瀏覽器不會自動彈出。

你需要開啟 Firefox 或者 Chrome,在其中輸入這個連結http://localhost:8080/lab?)。

如何用 Python 和深度遷移學習做文字分類?

開啟左側邊欄裡面的 demo.ipynb

如何用 Python 和深度遷移學習做文字分類?

本教程全部的程式碼都在這裡了。當然,你如果比較心急,可以選擇執行Run->Run All Cells,檢視全部執行結果。

如何用 Python 和深度遷移學習做文字分類?

但是,跟之前一樣,我還是建議你跟著教程的說明,一步步執行它們。以便更加深刻體會每一條語句的含義。

載入

在 Jupyter Lab 中,我們可以使用 !+命令名稱 的方式,來執行終端命令(bash command)。我們下面就使用 wget 來從 AWS 下載 Yelp 評論資料集。

!wget https://s3.amazonaws.com/fast-ai-nlp/yelp_review_polarity_csv.tgz
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

在左邊欄裡,你會看到 yelp_review_polarity_csv.tgz 這個檔案,被下載了下來。

如何用 Python 和深度遷移學習做文字分類?

對於 tgz 格式的壓縮包,我們採用 tar 命令來解壓縮。

!tar -xvzf yelp_review_polarity_csv.tgz
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

左側邊欄裡,你會看到 yelp_review_polarity_csv 目錄解壓完畢。

如何用 Python 和深度遷移學習做文字分類?

我們雙擊它,看看內容。

如何用 Python 和深度遷移學習做文字分類?

檔案下載和解壓成功。下面我們從 fast.ai 呼叫一些模組,來獲得一些常見的功能。

from fastai import *
from fastai.text import *
from fastai.core import *
複製程式碼

我們設定 path 指向資料資料夾。

path = Path('yelp_review_polarity_csv')
複製程式碼

然後我們檢查一下訓練資料。

train_csv = path/'train.csv'
train = pd.read_csv(train_csv, header=None)
train.head()
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

每一行,都包括一個標籤,以及對應的評論內容。這裡因為顯示寬度的限制,評論被摺疊了。我們看看第一行的評論內容全文:

train.iloc[0][1]
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

對於驗證集,我們也仿照上述辦法檢視。注意這裡資料集只提供了訓練集和“測試集”,因此我們把這個“測試集”當做驗證集來使用。

valid_csv = path/'test.csv'
valid = pd.read_csv(valid_csv, header=None)
valid.head()
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

下面我們把資料讀入。

data_lm = TextLMDataBunch.from_csv(path, valid='test')
data_clas = TextClasDataBunch.from_csv(path, valid='test', vocab=data_lm.train_ds.vocab)
複製程式碼

注意,短短兩行命令,實際上完成了若干功能。

第一行,是構建語言模型(Language Model, LM)資料。

第二行,是構建分類模型(Classifier)資料。

它們要做以下幾個事兒:

  • 語言模型中,對於訓練集的文字,進行標記化(Tokenizing)和數字化(Numericalizing)。這個過程,請參考我在《如何用Python和機器學習訓練中文文字情感分類模型?》一文中的介紹;
  • 語言模型中,對於驗證集文字,同樣進行標記化(Tokenizing)和數字化(Numericalizing);
  • 分類模型中,直接使用語言模型中標記化(Tokenizing)和數字化(Numericalizing)之後的詞彙(vocabs)。並且讀入標籤(labels)。

因為我們的資料量有數十萬,因此執行起來,會花上幾分鐘。

如何用 Python 和深度遷移學習做文字分類?

結束之後,我們來看看資料載入是否正常。

data_lm.train_ds.vocab_size
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

訓練資料裡面,詞彙一共有60002條。

我們看看,詞彙的索引是怎麼樣的:

data_lm.train_ds.vocab.itos
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

分類器裡面,訓練集標籤正確載入了嗎?

data_lm.train_ds.labels
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

驗證集的呢?

data_lm.valid_ds.labels
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

資料載入後,我們就要開始借來預訓練語言模型,並且進行微調了。

語言模型

本文使用 fast.ai 自帶的預訓練語言模型 wt103_v1,它是在 Wikitext-103 資料集上訓練的結果。

我們把它下載下來:

model_path = path/'models'
model_path.mkdir(exist_ok=True)
url = 'http://files.fast.ai/models/wt103_v1/'
download_url(f'{url}lstm_wt103.pth', model_path/'lstm_wt103.pth')
download_url(f'{url}itos_wt103.pkl', model_path/'itos_wt103.pkl')
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

左側邊欄裡,在資料目錄下,我們會看到一個新的資料夾,叫做 models

如何用 Python 和深度遷移學習做文字分類?

其中包括兩個檔案:

如何用 Python 和深度遷移學習做文字分類?

好了,現在資料、語言模型預訓練引數都有了,我們要構建一個 RNNLearner ,來生成我們自己的語言模型。

learn = RNNLearner.language_model(data_lm, pretrained_fnames=['lstm_wt103', 'itos_wt103'], drop_mult=0.5)
複製程式碼

這裡,我們指定了語言模型要讀入的文字資料為 data_lm,預訓練的引數為剛剛下載的兩個檔案,第三個引數 drop_mult 是為了避免過擬合,而設定的 Dropout 比例。

下面,我們還是讓模型用 one cycle policy 進行訓練。如果你對細節感興趣,可以點選這個連結瞭解具體內容。

learn.fit_one_cycle(1, 1e-2)
複製程式碼

因為我們的資料集包含數十萬條目,因此訓練時間,大概需要1個小時左右。請保持耐心。

如何用 Python 和深度遷移學習做文字分類?

50多分鐘後,還在跑,不過已經可以窺見曙光了。

如何用 Python 和深度遷移學習做文字分類?

當命令成功執行後,我們可以看看目前的語言模型和我們的訓練資料擬合程度如何。

如何用 Python 和深度遷移學習做文字分類?

你可能會覺得,這個準確率也太低了!

沒錯,不過要注意,這可是語言模型的準確率,並非是分類模型的準確率。所以,它和我們之前在這張表格裡看到的結果,不具備可比性。

如何用 Python 和深度遷移學習做文字分類?

我們對於這個結果,不夠滿意,怎麼辦呢?

方法很簡單,我們微調它。

回顧下圖,剛才我們實際上是凍結了預訓練模型底層引數,只用頭部層次擬合我們自己的訓練資料。

如何用 Python 和深度遷移學習做文字分類?

微調的辦法,是不再對預訓練的模型引數進行凍結。“解凍”之後,我們依然使用“歧視性學習速率”(discriminative learning rate)進行微調。

如果你忘了“歧視性學習速率”(discriminative learning rate)是怎麼回事兒,請參考《如何用 Python 和 fast.ai 做影象深度遷移學習?》一文的“微調”一節。

注意這種方法,既保證靠近輸入層的預訓練模型結構不被破壞,又儘量讓靠近輸出層的預訓練模型引數儘可能向著我們自己的訓練資料擬合。

learn.unfreeze()
learn.fit_one_cycle(1, 1e-3)
複製程式碼

如何用 Python 和深度遷移學習做文字分類?

好吧,又是一個多小時。出去健健身,活動一下吧。

當你準時回來的時候,會發現模型的效能已經提升了一大截。

如何用 Python 和深度遷移學習做文字分類?

前前後後,你已經投入了若干小時的訓練時間,就為了打造這個符合任務需求的語言模型。

現在模型訓練好了,我們一定不能忘記做的工作,是把引數好好儲存下來。

learn.save_encoder('ft_enc')
複製程式碼

這樣,下次如果你需要使用這個任務的語言模型,就不必拿 wt103_v1 從頭微調了。而只需要讀入目前儲存的引數即可。

分類

語言模型微調好了,下面我們來構造分類器。

learn = RNNLearner.classifier(data_clas, drop_mult=0.5)
learn.load_encoder('ft_enc')
learn.fit_one_cycle(1, 1e-2)
複製程式碼

雖然名稱依然叫做 learn ,但注意這時候我們的模型,已經是分類模型,而不再是語言模型了。我們讀入的資料,也因應變化成了 data_clas ,而非 data_lm

這裡,load_encoder 就是把我們的語言模型引數,套用到分類模型裡。

我們還是執行 "one cycle policy" 。

如何用 Python 和深度遷移學習做文字分類?

這次,在20多分鐘的訓練之後,我們語言模型在分類任務上得出了第一次成績。

接近95%的準確率,好像很不錯嘛!

但是,正如我在《文科生用機器學習做論文,該寫些什麼?》一文中給你指出的那樣,對於別人已經做了模型的分類任務,你的目標就得是和別人的結果去對比了。

回顧別人的結果:

如何用 Python 和深度遷移學習做文字分類?

對,最高準確率是 95.64% ,我們的模型,還是有差距的。

怎麼辦?

很簡單,我們剛剛只是微調了語言模型而已。這回,我們要微調分類模型。

先做一個省事兒的。就是對於大部分層次,我們都保持凍結。只把分類模型的最後兩層解凍,進行微調。

learn.freeze_to(-2)
learn.fit_one_cycle(1, slice(5e-3/2., 5e-3))
複製程式碼

半小時以後,我們獲得了這樣的結果:

如何用 Python 和深度遷移學習做文字分類?

這次,我們的準確率,已經接近了97% ,比別人的 95.64% 要高了。

而且,請注意,此時訓練損失(train loss)比起驗證損失(valid loss)要高。沒有跡象表明過擬合發生,這意味著模型還有改進的餘地。

你如果還不滿意,那麼我們們就乾脆把整個兒模型解凍,然後再來一次微調。

learn.unfreeze()
learn.fit_one_cycle(1, slice(2e-3/100, 2e-3))
複製程式碼

因為微調的層次多了,引數自然也多了許多。因此訓練花費時間也會更長。大概一個小時以後,你會看到結果:

如何用 Python 和深度遷移學習做文字分類?

準確率已經躍升到了 97.28%。

再次提醒,此時訓練損失(train loss)依然比驗證損失(valid loss)高。模型還有改進的餘地……

對比

雖然我們的深度學習模型,實現起來非常簡單。但是把我們們2018年做出來的結果,跟2015年的文章對比,似乎有些不大公平。

於是,我在 Google Scholar 中,檢索 yelp polarity ,並且把檢索結果的年份限定在了2017年以後。

如何用 Python 和深度遷移學習做文字分類?

對第一屏上出現的全部文獻,我一一開啟,查詢是否包含準確率對比的列表。所有符合的結果,我都列在了下面,作為對比。

下表來自於:Sun, J., Ma, X., & Chung, T. S. (2018). Exploration of Recurrent Unit in Hierarchical Attention Neural Network for Sentence Classification. 한국정보과학회 학술발표논문집, 964-966.

如何用 Python 和深度遷移學習做文字分類?

注意這裡最高的數值,是 93.75 。

下表來自於:Murdoch, W. J., & Szlam, A. (2017). Automatic rule extraction from long short term memory networks. arXiv preprint arXiv:1702.02540.

如何用 Python 和深度遷移學習做文字分類?

這裡最高的數值,是 95.4 。

下表來自於:Chen, M., & Gimpel, K. (2018). Smaller Text Classifiers with Discriminative Cluster Embeddings. In Proceedings of the 2018 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 2 (Short Papers) (Vol. 2, pp. 739-745).

如何用 Python 和深度遷移學習做文字分類?

這裡最高的數值,是 95.8 。

下表來自於:Shen, D., Wang, G., Wang, W., Min, M. R., Su, Q., Zhang, Y., ... & Carin, L. (2018). Baseline needs more love: On simple word-embedding-based models and associated pooling mechanisms. arXiv preprint arXiv:1805.09843.

如何用 Python 和深度遷移學習做文字分類?

這裡最高的數值,是 95.81 。

這是一篇教程,並非學術論文。所以我沒有窮盡查詢目前出現的最高 Yelp Reviews Polarity 分類結果。

另外,給你留個思考題——我們們這種對比,是否科學?歡迎你在留言區,把自己的見解反饋給我。

不過,通過跟這些近期文獻裡面的最優分類結果進行比較,相信你對我們們目前達到的準確率,能有較為客觀的參照。

小結

本文我們嘗試把遷移學習,從影象分類領域搬到到了文字分類(自然語言處理)領域。

fast.ai 框架下,我們的深度學習分類模型程式碼很簡單。刨去那些預處理和展示資料的部分,實際的訓練語句,只有10幾行而已。

回顧一下,主要的步驟包括:

  • 獲得標註資料,分好訓練集和驗證集;
  • 載入語言模型資料,和分類模型資料,進行標記化和數字化預處理;
  • 讀入預訓練引數,訓練並且微調語言模型;
  • 用語言模型調整後的引數,訓練分類模型;
  • 微調分類模型

值得深思的是,在這種流程下,你根本不需要獲得大量的標註資料,就可以達到非常高的準確率。

在 Jeremy Howard 的論文裡,就有這樣一張對比圖,令人印象非常深刻。

如何用 Python 和深度遷移學習做文字分類?

同樣要達到 20% 左右的驗證集錯誤率,從頭訓練的話,你需要超過1000個資料,而如果使用半監督通用語言模型微調(ULMFiT, semi-supervised),你只需要100個資料。如果你用的是監督通用語言模型微調(ULMFiT, supervised),100個資料已經能夠直接讓你達到10%的驗證集錯誤率了。

這給那些小樣本任務,尤其是小語種上的自然語言處理任務,帶來了顯著的機遇。

Czapla 等人,就利用這種方法,輕鬆贏得了 PolEval'18 比賽的第一名,領先第二名 35% 左右。

如何用 Python 和深度遷移學習做文字分類?

感興趣的話,他們的論文在這裡

Google 給你的300美金,應該還剩餘一些吧?

找個自己感興趣的文字分類任務,實際動手跑一遍吧。

祝(深度)學習愉快!

喜歡請點贊和打賞。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)

如果你對 Python 與資料科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門資料科學?》,裡面還有更多的有趣問題及解法。

相關文章