興奮
去年, Google 的 BERT 模型一發布出來,我就很興奮。
因為我當時正在用 fast.ai 的 ULMfit 做自然語言分類任務(還專門寫了《如何用 Python 和深度遷移學習做文本分類?》一文分享給你)。ULMfit 和 BERT 都屬于預訓練語言模型(Pre-trained Language Modeling),具有很多的相似性。
所謂語言模型,就是利用深度神經網絡結構,在海量語言文本上訓練,以抓住一種語言的通用特征。
上述工作,往往只有大機構才能完成。因為花費實在太大了。
這花費包括但不限于:
- 存數據
- 買(甚至開發)運算設備
- 訓練模型(以天甚至月計)
- 聘用專業人員
- ……
預訓練就是指他們訓練好之后,把這種結果開放出來。我們普通人或者小型機構,也可以借用其結果,在自己的專門領域文本數據上進行微調,以便讓模型對于這個專門領域的文本有非常清晰的認識。
所謂認識,主要是指你遮擋上某些詞匯,模型可以較準確地猜出來你藏住了什么。
甚至,你把兩句話放在一起,模型可以判斷它倆是不是緊密相連的上下文關系。
這種“認識”有用嗎?
當然有。
BERT 在多項自然語言任務上測試,不少結果已經超越了人類選手。
BERT 可以輔助解決的任務,當然也包括文本分類(classification),例如情感分類等。這也是我目前研究的問題。
痛點
然而,為了能用上 BERT ,我等了很久。
Google 官方代碼早已開放。就連 Pytorch 上的實現,也已經迭代了多少個輪次了。
但是我只要一打開他們提供的樣例,就頭暈。
單單是那代碼的行數,就非常嚇人。
而且,一堆的數據處理流程(Data Processor) ,都用數據集名稱命名。我的數據不屬于上述任何一個,那么我該用哪個?
還有莫名其妙的無數旗標(flags) ,看了也讓人頭疼不已。
讓我們來對比一下,同樣是做分類任務,Scikit-learn 里面的語法結構是什么樣的。
from sklearn.datasets import load_iris
from sklearn import tree
iris = load_iris()
clf = tree.DecisionTreeClassifier()
clf = clf.fit(iris.data, iris.target)
即便是圖像分類這種數據吞吐量大,需要許多步驟的任務,你用 fast.ai ,也能幾行代碼,就輕輕松松搞定。
!git clone https://github.com/wshuyi/demo-image-classification-fastai.git
from fastai.vision import *
path = Path("demo-image-classification-fastai/imgs/")
data = ImageDataBunch.from_folder(path, test='test', size=224)
learn = cnn_learner(data, models.resnet18, metrics=accuracy)
learn.fit_one_cycle(1)
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_top_losses(9, figsize=(8, 8))
別小瞧這幾行代碼,不僅幫你訓練好一個圖像分類器,還能告訴你,那些分類誤差最高的圖像中,模型到底在關注哪里。
對比一下,你覺得 BERT 樣例和 fast.ai 的樣例區別在哪兒?
我覺得,后者是給人用的。
教程
我總以為,會有人把代碼重構一下,寫一個簡明的教程。
畢竟,文本分類任務是個常見的機器學習應用。應用場景多,也適合新手學習。
但是,這樣的教程,我就是沒等來。
當然,這期間,我也看過很多人寫的應用和教程。
有的就做到把一段自然語言文本,轉換到 BERT 編碼。戛然而止。
有的倒是認真介紹怎么在官方提供的數據集上,對 BERT 進行“稍微修改”使用。所有的修改,都在原始的 Python 腳本上完成。那些根本沒用到的函數和參數,全部被保留。至于別人如何復用到自己的數據集上?人家根本沒提這事兒。
我不是沒想過從頭啃一遍代碼。想當年讀研的時候,我也通讀過仿真平臺上 TCP 和 IP 層的全部 C 代碼。我確定眼前的任務,難度更低一些。
但是我真的懶得做。我覺得自己被 Python 機器學習框架,特別是 fast.ai 和 Scikit-learn 寵壞了。
后來, Google 的開發人員把 BERT 弄到了 Tensorflow Hub 上。還專門寫了個 Google Colab Notebook 樣例。
看到這個消息,我高興壞了。
我嘗試過 Tensorflow Hub 上的不少其他模型。使用起來很方便。而 Google Colab 我已在《如何用 Google Colab 練 Python?》一文中介紹給你,是非常好的 Python 深度學習練習和演示環境。滿以為雙劍合璧,這次可以幾行代碼搞定自己的任務了。
且慢。
真正打開一看,還是以樣例數據為中心。
普通用戶需要什么?需要一個接口。
你告訴我輸入的標準規范,然后告訴我結果都能有什么。即插即用,完事兒走人。
一個文本分類任務,原本不就是給你個訓練集和測試集,告訴你訓練幾輪練多快,然后你告訴我準確率等結果嗎?
你至于讓我為了這么簡單的一個任務,去讀幾百行代碼,自己找該在哪里改嗎?
好在,有了這個樣例做基礎,總比沒有好。
我耐下心來,把它整理了一番。
聲明一下,我并沒有對原始代碼進行大幅修改。
所以不講清楚的話,就有剽竊嫌疑,也會被鄙視的。
這種整理,對于會 Python 的人來說,沒有任何技術難度。
可正因為如此,我才生氣。這事兒難做嗎?Google 的 BERT 樣例編寫者怎么就不肯做?
從 Tensorflow 1.0 到 2.0,為什么變動會這么大?不就是因為 2.0 才是給人用的嗎?
你不肯把界面做得清爽簡單,你的競爭者(TuriCreate 和 fast.ai)會做,而且做得非常好。實在坐不住了,才肯降尊紆貴,給普通人開發一個好用的界面。
教訓啊!為什么就不肯吸取呢?
我給你提供一個 Google Colab 筆記本樣例,你可以輕易地替換上自己的數據集來運行。你需要去理解(包括修改)的代碼,不超過10行。
我先是測試了一個英文文本分類任務,效果很好。于是寫了一篇 Medium 博客,旋即被 Towards Data Science 專欄收錄了。
Towards Data Science 專欄編輯給我私信,說:
Very interesting, I like this considering the default implementation is not very developer friendly for sure.
有一個讀者,居然連續給這篇文章點了50個贊(Claps),我都看呆了。
看來,這種忍受已久的痛點,不止屬于我一個人。
估計你的研究中,中文分類任務可能遇到得更多。所以我干脆又做了一個中文文本分類樣例,并且寫下這篇教程,一并分享給你。
咱們開始吧。
代碼
請點擊這個鏈接,查看我在 Github 上為你做好的 IPython Notebook 文件。
Notebook 頂端,有個非常明顯的 "Open in Colab" 按鈕。點擊它,Google Colab 就會自動開啟,并且載入這個 Notebook 。
我建議你點一下上圖中紅色圈出的 “COPY TO DRIVE” 按鈕。這樣就可以先把它在你自己的 Google Drive 中存好,以便使用和回顧。
這件事做好以后,你實際上只需要執行下面三個步驟:
- 你的數據,應該以 Pandas 數據框形式組織。如果你對 Pandas 不熟悉,可以參考我的這篇文章。
- 如有必要,可以調整訓練參數。其實主要是訓練速率(Learning Rate)和訓練輪數(Epochs)。
- 執行 Notebook 的代碼,獲取結果。
當你把 Notebook 存好之后。定睛一看,或許會覺得上當了。
老師你騙人!說好了不超過10行代碼的!
別急。
在下面這張圖紅色圈出的這句話之前,你不用修改任何內容。
請你點擊這句話所在位置,然后從菜單中如下圖選擇 Run before
。
下面才都是緊要的環節,集中注意力。
第一步,就是把數據準備好。
!wget https://github.com/wshuyi/demo-chinese-text-binary-classification-with-bert/raw/master/dianping_train_test.pickle
with open("dianping_train_test.pickle", 'rb') as f:
train, test = pickle.load(f)
這里使用的數據,你應該并不陌生。它是餐飲點評情感標注數據,我在《如何用Python和機器學習訓練中文文本情感分類模型?》和《如何用 Python 和循環神經網絡做中文文本分類?》中使用過它。只不過,為了演示的方便,這次我把它輸出為 pickle 格式,一起放在了演示 Github repo 里,便于你下載和使用。
其中的訓練集,包含1600條數據;測試集包含400條數據。標注里面1代表正向情感,0代表負向情感。
利用下面這條語句,我們把訓練集重新洗牌(shuffling),打亂順序。以避免過擬合(overfitting)。
train = train.sample(len(train))
這時再來看看我們訓練集的頭部內容。
train.head()
如果你后面要替換上自己的數據集,請注意格式。訓練集和測試集的列名稱應該保持一致。
第二步,我們來設置參數。
myparam = {
"DATA_COLUMN": "comment",
"LABEL_COLUMN": "sentiment",
"LEARNING_RATE": 2e-5,
"NUM_TRAIN_EPOCHS":3,
"bert_model_hub":"https://tfhub.dev/google/bert_chinese_L-12_H-768_A-12/1"
}
前兩行,是把文本、標記對應的列名,指示清楚。
第三行,指定訓練速率。你可以閱讀原始論文,來進行超參數調整嘗試。或者,你干脆保持默認值不變就可以。
第四行,指定訓練輪數。把所有數據跑完,算作一輪。這里使用3輪。
最后一行,是說明你要用的 BERT 預訓練模型。咱們要做中文文本分類,所以使用的是這個中文預訓練模型地址。如果你希望用英文的,可以參考我的 Medium 博客文章以及對應的英文樣例代碼。
最后一步,我們依次執行代碼就好了。
result, estimator = run_on_dfs(train, test, **myparam)
注意,執行這一句,可能需要花費一段時間。做好心理準備。這跟你的數據量和訓練輪數設置有關。
在這個過程中,你可以看到,程序首先幫助你把原先的中文文本,變成了 BERT 可以理解的輸入數據格式。
當你看到下圖中紅色圈出文字時,就意味著訓練過程終于結束了。
然后你就可以把測試的結果打印出來了。
pretty_print(result)
跟咱們之前的教程(使用同一數據集)對比一下。
當時自己得寫那么多行代碼,而且需要跑10個輪次,可結果依然沒有超過 80% 。這次,雖然只訓練了3個輪次,但準確率已經超過了 88% 。
在這樣小規模數據集上,達到這樣的準確度,不容易。
BERT 性能之強悍,可見一斑。
小結
講到這里,你已經學會了如何用 BERT 來做中文文本二元分類任務了。希望你會跟我一樣開心。
如果你是個資深 Python 愛好者,請幫我個忙。
還記得這條線之前的代碼嗎?
能否幫我把它們打個包?這樣咱們的演示代碼就可以更加短小精悍和清晰易用了。
歡迎在咱們的 Github 項目上提交你的代碼。如果你覺得這篇教程對你有幫助,歡迎給這個 Github 項目加顆星。謝謝!
祝深度學習愉快!
延伸閱讀
你可能也會對以下話題感興趣。點擊鏈接就可以查看。
喜歡請點贊和打賞。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)。
如果你對 Python 與數據科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數據科學?》,里面還有更多的有趣問題及解法。