NLP】Day 21: 手把手教你如何利用 Python 搭建 Tensorflow 的深度學習模型

啊!好久沒有實作了,不曉得各位還記不記得怎麼寫 Python 呢?今天的這篇文章將主要以實作為主,簡單地介紹如何搭建出我們在過去幾天的旅程中,學到的那些形形色色的深度學習模型:LSTM、GRU,以及兩者的雙向版本,也就是BiLSTM、以及BiGRU。不過,你可能已經發現,咦?那單純的 RNN 呢?事實上,在自然語言處理的實務中,由於先前提過的那些缺失,如記性不好等問題,已經很少人使用單純的 RNN 來完成任務了。欸!別人都是一篇一個神經網路,我直接一篇全部寫完,超值吧!還不趕快再點一下我其他文章!

不過在開始弄髒你的手實際上工之前,我們得先了解今天要用的工具有哪些。

今天要使用的工具箱

對於一些可能從未寫過程式的朋友,可能會有一些疑慮,擔心說「我們該不會要從零開始搭神經網路吧?先前介紹過的那些輸出層、輸入層、隱藏層殺毀的,我們都要從頭開始做起嗎?別吧?」接著擺出了 No No No 的手勢。

事實上,像這種這麼常用的深度學習工具,不可能沒有前人寫過。所謂前人種樹,後人乘涼,在過去就已經有程式大神代替我們將這些深度學習以及神經網路整理成 API 了。什麼?你說什麼是API?沒事,你只需要理解因為某邪惡大神的建樹,我們只需要簡單地引入(import)工具箱後,這些神經網路就可以隨取隨用了。我想,最多只需要調參數、以及疊各種隱藏層吧?而我今天就會一步一步地仔細帶領你撰寫搭建模型的程式,並逐句介紹這些程式語言在做什麼。至於是哪個大神如此邪惡,擁有幾乎全世界所有網頁的資料,又有極大量的運算資源?其實也沒有別人了,就是:

Tensorflow

Tensorflow 是一個開源的深度學習程式庫,並利用機器學習來完成各種人工智慧的下游任務,例如:圖像辨識,還有我們的重點,自然語言處理。你現在所用的幾乎所有 Google 的服務,舉凡如 Google 搜尋、Google 翻譯(乙定要有的吧!)、GOogle 語音辨識、Google 地圖、Google 相片,都是來自於利用 Tensorflow 的深度學習框架所打造而成的。換句話說,我們其實就是站在 Google 打造好的立足點來解決自然語言處理的應用。

LSTM

首先,在程式檔的頂端,我們得先引用搭建神經網路所需要的工具箱,也就是 Tensorflow。今天,我們會直接使用 tensorflow 內建的 imdb 資料集。資料科學的前輩對imdb資料一定又愛又恨吧!

1
2
3
4
5
6
7
8
9
import tensorflow as tf
from tensorflow.keras.datasets import imdb # 引入資料集
from tensorflow.keras.models import Sequential # 引入序列模型,記得我們在先前提到的 sequence model嗎?
from tensorflow.keras.layers import Dense # 一層全連接的神經網路
from tensorflow.keras.layers import LSTM # 長短期記憶(Long Short-term memory)
from tensorflow.keras.layers import Embedding # 使模型理解語意的 Embedding
from tensorflow.keras.preprocessing import sequence
# 在前處理會需要用到,將長短不一的資料補齊或截斷成相同長度(記得我們先前所說神經網路的長度可以彈性調整嗎?就是透過這裡將序列轉換成相同長度)
tf.random.set_seed(7) # 設定隨機種子

再來得將資料先處理好,我們在這邊先將資料處理好之後,後續在搭建無論是 GRU,甚至是BiLSTM、BiGRU,都會直接用這裡的資料,就不會再重複這裡的動作啦!

1
2
3
4
5
6
7
8
# 設定資料中字頻前五千的字
top_words = 5000
# 取得資料後,分成訓練資料集以及測試資料集
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=top_words)
# 在這裡就是要截斷或補齊輸入模型的句子,以保持所有句子長度相同。
max_review_length = 500
X_train = sequence.pad_sequences(X_train, maxlen=max_review_length)
X_test = sequence.pad_sequences(X_test, maxlen=max_review_length)

接著要先搭建模型,搭建完模型之後才會開始訓練。

1
2
3
4
5
6
7
8
9
10
11
12
13
embedding_vector_length = 32
# 首先設定模型為序列模型
lstm_model = Sequential()
# 將已經訓練好的 Embedding 加入神經網路模型中
lstm_model.add(Embedding(top_words, embedding_vector_length, input_length=max_review_length))
# LSTM函數中的值則是代表你要在模型中放入多少 LSTM 的神經元(有門閥的那個)
lstm_model.add(LSTM(100))
# 全連接的神經網路隱藏層,可以選擇想要的激勵函數
lstm_model.add(Dense(1, activation='sigmoid'))
# 這裡的compile是在將模型組合起來。
lstm_model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# 可以一起來看看模型長怎樣
print(lstm_model.summary())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, 500, 32) 160000

lstm (LSTM) (None, 100) 53200

dense (Dense) (None, 1) 101

=================================================================
Total params: 213,301
Trainable params: 213,301
Non-trainable params: 0
_________________________________________________________________
None

我們從params中可以看到模型總共有多少個參數。另外,在 LSTM 中的參數決定的是神經元數量,沒有一定的固定數字,但是有個模式可循。首先,RNN 的寬度代表特徵的多寡,由輸入以及篩選器的數量決定;深度代表的則是特徵的豐富度,由神經網路層數及步驟數決定的。若不需要從原資料產出大量特徵,那麼一般來說寬度會減少。若資料相對單純,那麼深度則需要調整的淺一點,這麼做可以節省訓練時間及資源。(source)

確認沒問題之後,就可以開始訓練模型了!這裡可能會需要跑比較久時間,像我試跑就跑了十分多鐘,所以才說深度學習模型的一個缺點就是速度比較慢一點。

1
2
3
model.fit(X_train, y_train, epochs=3, batch_size=64) # 訓練模型
scores = model.evaluate(X_test, y_test, verbose=0) # 測試模型
print("Accuracy: %.2f%%" % (scores[1]*100))
1
2
3
4
5
6
7
Epoch 1/3
391/391 [==============================] - 207s 525ms/step - loss: 0.4316 - accuracy: 0.7884
Epoch 2/3
391/391 [==============================] - 205s 525ms/step - loss: 0.2653 - accuracy: 0.8937
Epoch 3/3
391/391 [==============================] - 204s 522ms/step - loss: 0.2377 - accuracy: 0.9065
Accuracy: 86.72%

BiLSTM

其實雙向的 BiLSTM 也是大同小異,我們只需要關注模型的搭建上就可以了。資料一樣沿用在上面所整理好的訓練以及測試資料集。別忘了要先引進雙向的套件,至於其他的套件,在前面就已經引進過,就不需要再引進了。

1
from tensorflow.keras.layers import Bidirectional
1
2
3
4
5
6
bilstm_model = Sequential()
bilstm_model.add(Embedding(top_words, embedding_vetcor_length, input_length=max_review_length))
bilstm_model.add(Bidirectional(LSTM(100, dropout=0.2, recurrent_dropout=0.2)))
bilstm_model.add(Dense(1, activation='sigmoid'))
bilstm_model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
print(bilstm_model.summary()) # 同樣先來看模型參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, 500, 32) 160000

bidirectional (Bidirectiona (None, 200) 106400
l)

dense_1 (Dense) (None, 1) 201

=================================================================
Total params: 266,601
Trainable params: 266,601
Non-trainable params: 0
_________________________________________________________________
None
1
2
3
bilstm_model.fit(X_train, y_train, epochs=3, batch_size=64)
scores = bilstm_model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
1
2
3
4
5
6
7
Epoch 1/3
391/391 [==============================] - 805s 2s/step - loss: 0.4870 - accuracy: 0.7579
Epoch 2/3
391/391 [==============================] - 803s 2s/step - loss: 0.3148 - accuracy: 0.8726
Epoch 3/3
391/391 [==============================] - 799s 2s/step - loss: 0.2637 - accuracy: 0.8944
Accuracy: 87.89%

BiLSTM 因為參數更多,所以訓練時間又很明顯地變更久了。我在這裡花了快20多分鐘在訓練模型。

GRU

1
2
3
4
5
6
7
gru_model = Sequential()
gru_model.add(Embedding(top_words, embedding_vector_length, input_length=max_review_length))
gru_model.add(GRU(32))
gru_model.add(Dense(10, activation='relu'))
gru_model.add(Dense(1, activation='sigmoid'))
gru_model.compile(loss="binary_crossentropy", optimizer='adam',metrics=['accuracy'])
print(gru_model.summary())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_2 (Embedding) (None, 500, 32) 160000

gru (GRU) (None, 32) 6336

dense_2 (Dense) (None, 10) 330

dense_3 (Dense) (None, 1) 11

=================================================================
Total params: 166,677
Trainable params: 166,677
Non-trainable params: 0
_________________________________________________________________
1
2
3
gru_model.fit(X_train, y_train, epochs=3, batch_size=64)
scores = gru_model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
1
2
3
4
5
6
7
Epoch 1/3
391/391 [==============================] - 89s 222ms/step - loss: 0.4658 - accuracy: 0.7620
Epoch 2/3
391/391 [==============================] - 88s 226ms/step - loss: 0.2631 - accuracy: 0.8943
Epoch 3/3
391/391 [==============================] - 88s 224ms/step - loss: 0.2215 - accuracy: 0.9149
Accuracy: 87.74%

GRU 模型當初有說,其實設計架構上與 LSTM 差不了多少,但是由於 GRU 的參數相對比較少的緣故,所以運算時間還有資源都比原本的 LSTM 還要少。在準確度皆為接近 90% 的前提下,我在前面訓練 LSTM 的時間為十一分鐘左右,而這裡 GRU 我卻只需要五分多鐘就結束訓練了。

1
2
3
4
5
6
7
bigru_model = Sequential()
bigru_model.add(Embedding(top_words, embedding_vector_length, input_length=max_review_length))
bigru_model.add(Bidirectional(GRU(32)))
bigru_model.add(Dense(10, activation='relu'))
bigru_model.add(Dense(1, activation='sigmoid'))
bigru_model.compile(loss="binary_crossentropy", optimizer='adam',metrics=['accuracy'])
bigru_model.summary()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_3 (Embedding) (None, 500, 32) 160000

bidirectional_1 (Bidirectio (None, 64) 12672
nal)

dense_4 (Dense) (None, 10) 650

dense_5 (Dense) (None, 1) 11

=================================================================
Total params: 173,333
Trainable params: 173,333
Non-trainable params: 0
_________________________________________________________________
1
2
3
bigru_model.fit(X_train, y_train, epochs=3, batch_size=64)
scores = bigru_model.evaluate(X_test, y_test, verbose=0)
print("Accuracy: %.2f%%" % (scores[1]*100))
1
2
3
4
5
6
7
Epoch 1/3
391/391 [==============================] - 166s 414ms/step - loss: 0.4397 - accuracy: 0.7790
Epoch 2/3
391/391 [==============================] - 160s 408ms/step - loss: 0.2599 - accuracy: 0.8975
Epoch 3/3
391/391 [==============================] - 159s 407ms/step - loss: 0.2170 - accuracy: 0.9153
Accuracy: 87.74%

BiGRU 的模型訓練時間跟 BiLSTM 相比就更明顯了,BiLSTM 我當初花了 40 分鐘左右才訓練完成,反觀 BiGRU,只需要 9 分鐘左右就結束了,模型表現卻也都差不多。

結語

我們最後來看一下結果,可以發現四種表現都沒有差距太大,但訓練時間就有明顯的差異,也就是 GRU 的時間還要少於 LSTM;另外,雙向的神經網路模型表現也比單向還要好一些。

LSTM BiLSTM GRU BiGRU
準確度 (%) 86.72 87.89 87.74 87.74
時間 (min) 11 19 5.5 9

這也印證了在先前 【NLP】Day 17: 每天成為更好的自己!神經網路也是!深度學習模型 GRU 的文章中所說,參數的多寡會大大影響著運算時間以及資源。好,神經網路的實作就到這邊,是不是比想像中的還要簡單很多呢?明天開始就要進入 BERT 了喔!