【AutoML 自動化機器學習:用 AutoKeras 解 NLP──偵測災害相關的推特發文】 之前我們介紹的 AutoKeras 功能都是著重在它的影像模型,今天我們來看一個不一樣、但同樣在深度學習備受關注的領域:NLP (自然語言處理)。AutoKeras 目前預設擁有四種 NLP 相關模型: * 詞嵌入層 + CNN * Transformer (詞嵌入層 + 全連接層) * N-gram (N-gram 層 + 全連接層) * BERT 其中,CNN 和 Transformer 的詞嵌入層還可以搭配 Word2vec、Fasttext、GloVe 這三種詞向量模型,或者使用隨機權重。不過,這些模型各自適合怎樣的資料呢?NLP 領域博大精深,其模型反而沒有影像處理領域相對容易理解。 AutoKeras 內建的 TextClassifier 使用的前三個預設模型,分別是 CNN、Transformer 和 BERT。BERT 乃是 Google 於 2018 年推出的著名預訓練模型 Bidirectional Encoder Representations from Transformers (基於變換器的雙向編碼器表示技術),也是近年來在 NLP 界最熱門的模型之一。 問題就在於,BERT 實在是太吃記憶體、訓練起來也慢得多,因此第五章幾乎所有的範例都是直接用 AutoModel 類別來指定使用另外三種模型(理論上,你甚至可以用 AutoModel 去自己兜出像是 CNN + RNN 之類的模型)。反過來說, 打從 BERT 問世後,它也成了 Kaggle 參賽者最愛用的 NLP 模型。 **[Natural Language Processing with Disaster Tweets]** ![](https://i.imgur.com/ZtD7b3Z.png) 這個練習性質競賽要你解讀上千條推特貼文,判斷其內容是否在談論某種災害事件。許多推特貼文都有標註地點,這或許能協助相關單位快速偵測到災害事件發生。但這份資料有一個難處:當使用者使用一些像是「燃燒」之類的字眼時,並不見得是在描述火災。CNN 和 N-gram 之類的模型雖然很適合偵測關鍵字,但對於這類得解讀上下文的內容,BERT 的表現應該會更好。 以下程式小編是在自己的機器上運行,使用 GTX 1660 Ti 加速卡,訓練時間約 4.25 小時 (BERT 模型沒有辦法在 Google Colab 使用,因為光是下載模型就會讓執行階段的儲存空間爆掉!)。BERT 在一般 CPU 上也能跑,只是時間會拉長許多。而為了提高模型效能,我們也會使用 NLTK (自然語言處理工具包) 來做些預處理。 **[安裝套件與匯入資料集]** 首先安裝 AutoKeras (若沒有的話) 以及 NLTK: `!pip3 install autokeras nltk` 接著到 https://www.kaggle.com/c/nlp-getting-started/data 下載訓練集和測試集,用 Pandas 載入後檢視訓練集: ``` train = pd.read_csv('./train.csv') test = pd.read_csv('./test.csv') train ``` ![](https://i.imgur.com/TR4fyxC.png) 資料集有 id、keyword (推文的關鍵詞)、location (標記地點)、text (內文) 以及 target (分類)。仔細看了一下,keyword 比較像是人為標註的分類,而且資料還照 keyword 分成一組一組的,這在分割資料集時可能會有問題。為了簡化起見,這回我們只會用內文來做訓練。 但在開始訓練之前,我們還可以對內文做些預處理 (參考來源:https://monkeylearn.com/blog/text-cleaning/)。首先是把所有詞轉成小寫,免得大小寫不一的詞被視為不同: ``` train.text = train.text.str.lower() test.text = test.text.str.lower() ``` 接著用 Python 的正規化模組 re 將不是正常英數字詞的內容 (比如 Unicode 特殊字、網址等) 去掉: ``` import re rule = r'(@\[A-Za-z0-9]+)|([^0-9A-Za-z \t])|(\w+:\/\/\S+)|^rt|http.+?' f = lambda t: re.sub(rule, '', t) train.text = train.text.apply(f) test.text = test.text.apply(f) ``` 接著用 NLTK 工具將 stop words (可以忽略的基本字詞) 過濾掉: ``` import nltk.corpus from nltk.corpus import stopwords nltk.download('stopwords') stop = stopwords.words('english') f = lambda t: ' '.join([word for word in t.split() if word not in stop]) train.text = train.text.apply(f) test.text = test.text.apply(f) ``` 以上這三個步驟參閱自網路資料。我們在書中沒有特別提文字預處理,但既然我們希望能稍微提升 Kaggle 練習競賽的分數, 最後我們得將訓練集的內容順序打亂,以便後面模型在分割訓練集與驗證集時,兩者都能含有不同類型 (即含有不同 keyword) 的貼文: ``` from sklearn.utils import shuffle train = shuffle(train, random_state=0) train ``` ![](https://i.imgur.com/qY7gYJ6.jpg) **[建立與訓練模型]** 下面我們使用 ak.AutoModel 類別來建立一個 BERT 模型並訓練之: ``` input_node = ak.TextInput() output_node = ak.BertBlock()(input_node) output_node = ak.ClassificationHead()(output_node) clf = ak.AutoModel( inputs=input_node, outputs=output_node, max_trials=20, overwrite=True) clf.fit( train.text.to_numpy(), train.target.to_numpy(), batch_size=4, callbacks=[tf.keras.callbacks.EarlyStopping(patience=5)]) ``` 在替 AutoKeras 的 AutoModel 建立模型時,其語法和 Keras Functional API 極為相似,不過這裡定義的是搜尋空間 (search space) 而非一個明確的模型。AutoKeras 會試著根據你指定的架構調整當中的超參數。各位在本書第 5 章將會看到,AutoModel 的寫法可以非常簡單 (只定義輸入與輸出節點),也可以相當複雜 (明確地指定哪個位置要放什麼 block、彼此如何連接)。 要注意的是,BERT 很大而且需要可觀的記憶體量,小編在這裡必須將 fit() 的 batch_size (訓練批量) 設為 4 才有辦法在 GPU 執行。若使用預設的 32,Python 直譯器就會當場掛點了! 我們指定測試 20 個模型,每個模型的損失值停止進步 5 週期就換下一個。而 AutoKeras 在測試了 12 個模型後覺得夠好了,自動結束訓練、並用整個訓練集重新訓練一次最佳模型: Trial 12 Complete [00h 14m 06s] val_loss: 0.6860448718070984 Best val_loss So Far: 0.42517581582069397 Total elapsed time: 04h 12m 10s INFO:tensorflow:Oracle triggered exit Epoch 1/2 1904/1904 [] - 122s 60ms/step - loss: 0.4914 - accuracy: 0.7707 Epoch 2/2 1904/1904 [] - 116s 61ms/step - loss: 0.4044 - accuracy: 0.8257 INFO:tensorflow:Assets written to: .\auto_model\best_model\assets <tensorflow.python.keras.callbacks.History at 0x1e990ac4d00> 可見對訓練集的預測準確率為 82.57%。 下面來檢視這模型的架構: `model.summary()``model.summary()` ![](https://i.imgur.com/cb9JohL.png) [檢視預測效果] 有了模型之後,我們可對測試集的推特貼文產生預測分類,並印出一小部分來檢視其分類效果: predicted = clf.predict(test.text.to_numpy()).flatten().astype('uint8') predicted 接著: labels = ('NOT disaster', 'REAL disaster') for i in range(10): print('Test:', test.text.to_numpy()[i]) print('Predict:', labels[predicted[i]]) print('') 輸出結果如下: Test: happened terrible car crash Predict: REAL disaster Test: heard earthquake different cities stay safe everyone Predict: REAL disaster Test: forest fire spot pond geese fleeing across street cannot save Predict: REAL disaster Test: apocalypse lighting spokane wildfires Predict: REAL disaster Test: typhoon soudelor kills 28 china taiwan Predict: REAL disaster Test: shakingits earthquake Predict: REAL disaster Test: theyd probably still show life arsenal yesterday eh eh Predict: NOT disaster Test: hey Predict: NOT disaster Test: nice hat Predict: NOT disaster Test: f**k Predict: NOT disaster 就這 10 筆看來都是準確的,模型能夠判別推特的內容是否在談論災害事件。 [產生 Kaggle submission] 練習競賽要求解答包括測試集貼文的 id 與目標值 (分類)。所以最後我們將預測結果放回測試集中,並產生出 CSV 檔: test['target'] = pd.Series(predicted) submission = test[['id', 'target']] submission.to_csv('./submission.csv', index=False) submission 將答案上傳至 Kaggle,我們得到 0.82163 的分數: ![](https://i.imgur.com/ZZCuHR3.png) 這個分數其實是分類 1 的 F-score,也就是精準率 (預測的分類 1 為正確的比率) 和召回率 (分類 1 被正確預測出來的比率) 的調和平均。換言之,這代表你能正確預測出「有關於災害訊息的推文」的比率。 但是這個分數究竟算不算好呢?我們可以下載排行榜的分數排名並畫成直方圖,好檢視分數的分布狀況: ![](https://i.imgur.com/4TLD8wk.png) 可以發現其實多數集中在 0.75~0.80,另外有一小塊集中於0.82~0.85。看了競賽討論區的文章,也能得知多數 NLP 模型能取得 0.78 上下的分數,而若使用集成學習、調校過的模型或文字預處理,則可多提高幾個百分點。 以絕對排名來看,我們的分數在將近 900 組中可排 200 名出頭,而我們做的其實也只是一些標準預處理,然後丟給 AutoKeras 的標準 BERT 模型來跑而已。 話說,為什麼排行榜上會有 40 組左右的分數達到 1.0 (預測率 100%) 呢?這其實是因為此資料集在網路上是公開的,而這類練習競賽又只檢查答案,所以這類練習競賽都非常容易作弊。因此若看到競賽中有一堆滿分的成績,也不用太難過──你只不過是輸給了「工人智慧」而已 (笑)。