前一篇:《用於自然語言處理的迴圈神經網路RNN》
序言:本節主要講解如何使用迴圈神經網路(RNN)建立一個文字分類器。RNN 是一類適合處理序列資料的神經網路的統稱,而我們將在本節中使用 RNN 的一種常見變體——LSTM(長短期記憶網路)來實現這一文字分類器。
使用RNN建立文字分類器
在第六章中,你嘗試使用嵌入層為諷刺資料集建立分類器。在那種情況下,單詞會先被轉換為向量,然後聚合後再輸入全連線層進行分類。而如果使用RNN層(例如LSTM),則不需要聚合,可以直接將嵌入層的輸出傳遞到迴圈層中。
關於迴圈層的維度,有一個常見的經驗法則是:它的大小通常和嵌入維度相同。這並不是必須的,但可以作為一個不錯的起點。注意,在第六章中我提到嵌入維度通常是詞彙量的四次方根,但在使用RNN時,這個規則往往會被忽略,因為如果遵循這個規則,迴圈層的維度可能會太小。
例如,第六章中開發的諷刺分類器的簡單模型架構,可以更新為如下形式,以使用雙向LSTM:
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim)),
tf.keras.layers.Dense(24, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
損失函式和分類器可以設定為以下內容(注意學習率為0.00001或1e–5):
adam = tf.keras.optimizers.Adam(learning_rate=0.00001,
beta_1=0.9, beta_2=0.999, amsgrad=False)
model.compile(loss='binary_crossentropy',
optimizer=adam, metrics=['accuracy'])
當列印出模型架構的摘要時,你會看到類似以下的內容。注意,詞彙量大小為20,000,嵌入維度為64。這會在嵌入層中產生1,280,000個引數,而雙向層會有128個神經元(64個前向,64個後向):
Layer (type) Output Shape Param #
=================================================================
embedding_11 (Embedding) (None, None, 64) 1280000
bidirectional_7 (Bidirection) (None, 128) 66048
dense_18 (Dense) (None, 24) 3096
dense_19 (Dense) (None, 1) 25
=================================================================
Total params: 1,349,169
Trainable params: 1,349,169
Non-trainable params: 0
圖7-9顯示了經過30個epoch的訓練結果。
正如你所見,網路在訓練資料上的準確率迅速超過90%,但在驗證資料上穩定在80%左右。這與我們之前得到的結果類似,但檢查圖7-10中的損失圖表可以發現,儘管驗證集的損失在15個epoch之後有所分歧,但它趨於平穩,且相比第六章中的損失圖表,值更低,即使使用了20,000個單詞,而不是2,000個。
圖7-9:LSTM在30個epoch中的準確率
圖7-10:LSTM在30個epoch中的損失
不過,這只是使用了單個LSTM層。在下一節中,你將看到如何使用堆疊的LSTM層,並探索其對該資料集分類準確率的影響。
堆疊 LSTM
在上一節中,你已經瞭解瞭如何在嵌入層後使用 LSTM 層來幫助對諷刺資料集進行分類。但實際上,LSTM 可以堆疊使用,這種方法在許多最先進的自然語言處理模型中被廣泛採用。
在 TensorFlow 中堆疊 LSTM 非常簡單。你可以像新增全連線層一樣新增額外的 LSTM 層,但有一個例外:除最後一層外,所有層都需要將 return_sequences 屬性設定為 True。以下是一個示例:
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim, return_sequences=True)),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim)),
tf.keras.layers.Dense(24, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
最後一層也可以將 return_sequences=True 設定為 True,這樣它會返回值序列供全連線層分類,而不是單個值。在解析模型輸出時,這種設定可能非常有用,我們稍後會討論這一點。
模型架構將會如下所示:
Layer (type) Output Shape Param #
=================================================================
embedding_12 (Embedding) (None, None, 64) 1280000
bidirectional_8 (Bidirection) (None, None, 128) 66048
bidirectional_9 (Bidirection) (None, 128) 98816
dense_20 (Dense) (None, 24) 3096
dense_21 (Dense) (None, 1) 25
=================================================================
Total params: 1,447,985
Trainable params: 1,447,985
Non-trainable params: 0
新增額外的 LSTM 層將增加大約 100,000 個需要學習的引數,總量增加了約 8%。雖然可能會稍微減慢網路速度,但如果帶來了合理的效能提升,這個代價還是可以接受的。
經過 30 個 epoch 的訓練後,結果如圖 7-11 所示。雖然驗證集的準確率表現平穩,但檢視損失(圖 7-12)會揭示一個不同的故事。
圖 7-11:堆疊 LSTM 架構的準確率
從圖 7-12 可以看出,儘管訓練和驗證的準確率表現良好,但驗證集的損失迅速上升,這是過擬合的明顯跡象。
圖 7-12:堆疊 LSTM 架構的損失
這種過擬合的表現為:訓練準確率逐漸接近 100%,損失平穩下降,而驗證準確率相對穩定,但驗證損失急劇上升。這說明模型對訓練集過於專注而產生了過擬合問題。正如第六章的例子所示,僅檢視準確率指標可能會讓人產生一種錯誤的安全感,因此必須結合損失圖表分析。
最佳化堆疊 LSTM
在第六章中,你已經看到一個非常有效的減少過擬合的方法是降低學習率。可以探索一下這個方法對迴圈神經網路是否也有積極影響。
例如,以下程式碼將學習率從 0.00001 降低了 20%,變為 0.000008:
adam = tf.keras.optimizers.Adam(learning_rate=0.000008, beta_1=0.9, beta_2=0.999, amsgrad=False)
model.compile(loss='binary_crossentropy', optimizer=adam, metrics=['accuracy'])
圖 7-13 展示了這種變化對訓練的影響。雖然差異不大,但曲線(尤其是驗證集)變得更加平滑了。
圖 7-13:降低學習率對堆疊 LSTM 準確率的影響
類似地,檢視圖 7-14 也顯示,雖然整體趨勢類似,但降低學習率使得損失增長速度明顯降低:在 30 個 epoch 後,損失約為 0.6,而更高學習率時接近 0.8。這表明調整學習率超引數是值得探索的。
圖 7-14:降低學習率對堆疊 LSTM 損失的影響
使用 Dropout
除了調整學習率,還可以考慮在 LSTM 層中使用 Dropout。正如第三章所討論的,Dropout 的作用是隨機丟棄一些神經元,以避免由於鄰近神經元的影響而產生的偏差。
在 LSTM 層中,可以透過一個引數直接實現 Dropout。例如:
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim, return_sequences=True, dropout=0.2)),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim, dropout=0.2)),
tf.keras.layers.Dense(24, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
需要注意的是,使用 Dropout 會顯著降低訓練速度。在我的實驗中,在 Colab 上訓練時間從每個 epoch 大約 10 秒增加到了 180 秒。
使用 Dropout 的準確率結果見圖 7-15。從圖中可以看到,Dropout 對網路的準確率幾乎沒有負面影響,這是一件好事!通常人們會擔心丟棄神經元會讓模型表現更差,但這裡顯然不是這樣。
圖 7-15:使用 Dropout 的堆疊 LSTM 的準確率
此外,對損失也有積極影響,如圖 7-16 所示。儘管曲線明顯分離,但相比之前,它們更接近了,並且驗證集的損失趨於平穩,約為 0.5,比之前的 0.8 要好得多。這個例子表明,Dropout 是一種能夠改善基於 LSTM 的 RNN 效能的實用技術。
圖 7-16:啟用 Dropout 的 LSTM 損失曲線
探索這些技術來避免資料過擬合是值得的,同時也要結合前幾節中介紹的資料預處理技術。但還有一種方法我們尚未嘗試——一種使用預訓練詞嵌入替代自學嵌入的遷移學習方法。我們將在下一節中探索這一內容。
總結:本篇文章詳細講解了如何利用 LSTM 神經網路構建高效的文字分類器,並透過最佳化學習率、堆疊層數及應用 Dropout 等方法,提升模型效能,避免過擬合,為文字處理任務提供了實用的實現方案。