---
# System prepended metadata

title: Untitled

---

【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%) 呢？這其實是因為此資料集在網路上是公開的，而這類練習競賽又只檢查答案，所以這類練習競賽都非常容易作弊。因此若看到競賽中有一堆滿分的成績，也不用太難過──你只不過是輸給了「工人智慧」而已 (笑)。



