Day 14:循環神經網路(Recurrent Neural Network, RNN) – iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

前言

上一篇我們對『自然語言處理』(Natural Language Processing, NLP)有一個初步的認識,現在,我們再進一步認識,如何以 Neural Network 處理語言的辨識。

循環神經網路(Recurrent Neural Network, RNN)

語言通常要考慮前言後語,以免斷章取義,也就是說,建立語言的相關模型,如果能額外考慮上下文的關係,準確率就會顯著提高,因此,學者提出『循環神經網路』(Recurrent Neural Network, RNN)演算法,它是『自然語言處理』領域最常使用的 Neural Network 模型,簡單的RNN模型(Vanilla RNN)額外考慮前文的關係,把簡單迴歸的模型 (y=W*x+b) ,改為下列公式,其中h就是預測值(y),同樣,將它加一個 Activation Function(通常使用 tanh),就變成第二個公式:
https://ithelp.ithome.com.tw/upload/images/20171210/20001976JvMnANowAr.jpg
https://ithelp.ithome.com.tw/upload/images/20171210/20001976JwABjxzSAr.jpg

模型如下圖:
https://ithelp.ithome.com.tw/upload/images/20171210/20001976y5kxBTjmM7.jpg
圖. RNN 模型,圖片來源 Ashing’s Blog_ 深度學習(3)–循環神經網絡(RNN, Recurrent Neural Networks)

也就是說,當前 output 不只受上一層輸入的影響,也受到同一層前一個 output 的影響(即前文),類似統計學的『時間數列』(Time Series),這個假設很合理,例如下面這兩個句子:

  • 我不吃辣,所以,我點『蝦仁豆腐』
  • 我吃辣 ,所以,我點『麻婆豆腐』

在分析『我點』的下一個詞時,若不考慮上下文, 『蝦仁豆腐』、 『麻婆豆腐』的機率是相等的,反之,考慮『我吃辣』,選『麻婆豆腐』的機率應該就會大於『蝦仁豆腐』。

另外,上述公式中的 W、U、V 都是權重,對應不同的輸入來源,與CNN一樣,基於『權重共享』(Shared Weights)或稱『參數共享』(Parameter Sharing)的假設,W、U、V 對每一點推導都共用,即使用相同的值,以簡化計算,之後同樣依據梯度下降法優化求解,這個模型就可以運作了。

另外,依據 input 及 output 的數目,RNN 可以有很多的變化與應用:

  1. 一對一(one to one):固定長度的輸入(input)及輸出(output),即一般的 Neural Network 模型。
  2. 一對多(one to many):單一輸入、多個輸出,例如影像標題(Image Captioning),輸入一個影像,希望偵測影像內多個物體,並一一給予標題,這稱之為『Sequence output』。
  3. 多對一(many to one):多個輸入、單一輸出,例如情緒分析(Sentiment Analysis),輸入一大段話,判斷這段話是正面或負面的情緒表達,這稱之為『Sequence input』。
  4. 多對多(many to many):多個輸入、多個輸出,例如語言翻譯(Machine Translation),輸入一段英文句子,翻譯成中文,這稱之為『Sequence input and sequence output 』。
  5. 另一種多對多(many to many):『同步』(Synchronize)的多個輸入、多個輸出,例如視訊分類(Video Classification),輸入一段影片,希望為每一幀(Frame)產生一個標題,這稱之為『Synced sequence input and output』。

https://ithelp.ithome.com.tw/upload/images/20171210/200019764hsWa3ZvMN.jpg
圖. RNN 模型,圖片來源 The Unreasonable Effectiveness of Recurrent Neural Networks

實作

為了先熟悉RNN的運作,我們還是用『阿拉伯數字辨識』來說明RNN函數,程式範例來自莫烦Python,同樣,把部分參數固定(Hard code)並加了一些註解,也可自這裡下載,檔名為RNN.py。

# 導入函式庫
import numpy as np
from keras.datasets import mnist
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import SimpleRNN, Activation, Dense
from keras.optimizers import Adam

# 固定亂數種子,使每次執行產生的亂數都一樣
np.random.seed(1337)


# 載入 MNIST 資料庫的訓練資料,並自動分為『訓練組』及『測試組』
(X_train, y_train), (X_test, y_test) = mnist.load_data()

# 將 training 的 input 資料轉為3維,並 normalize 把顏色控制在 0 ~ 1 之間
X_train = X_train.reshape(-1, 28, 28) / 255.      
X_test = X_test.reshape(-1, 28, 28) / 255.
y_train = np_utils.to_categorical(y_train, num_classes=10)
y_test = np_utils.to_categorical(y_test, num_classes=10)


# 建立簡單的線性執行的模型
model = Sequential()
# 加 RNN 隱藏層(hidden layer)
model.add(SimpleRNN(
    # 如果後端使用tensorflow,batch_input_shape 的 batch_size 需設為 None.
    # 否則執行 model.evaluate() 會有錯誤產生.
    batch_input_shape=(None, 28, 28), 
    units= 50,
    unroll=True,
)) 
# 加 output 層
model.add(Dense(units=10, kernel_initializer='normal', activation='softmax'))

# 編譯: 選擇損失函數、優化方法及成效衡量方式
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) 

# 一批訓練多少張圖片
BATCH_SIZE = 50     
BATCH_INDEX = 0     
# 訓練模型 4001 次
for step in range(1, 4001):
    # data shape = (batch_num, steps, inputs/outputs)
    X_batch = X_train[BATCH_INDEX: BATCH_INDEX+BATCH_SIZE, :, :]
    Y_batch = y_train[BATCH_INDEX: BATCH_INDEX+BATCH_SIZE, :]
    # 逐批訓練
    loss = model.train_on_batch(X_batch, Y_batch)
    BATCH_INDEX += BATCH_SIZE
    BATCH_INDEX = 0 if BATCH_INDEX >= X_train.shape[0] else BATCH_INDEX

    # 每 500 批,顯示測試的準確率
    if step % 500 == 0:
        # 模型評估
        loss, accuracy = model.evaluate(X_test, y_test, batch_size=y_test.shape[0], 
            verbose=False)
        print("test loss: {}  test accuracy: {}".format(loss,accuracy))
        

# 預測(prediction)
X = X_test[0:10,:]
predictions = model.predict_classes(X)
# get prediction result
print(predictions)

# 模型結構存檔
from keras.models import model_from_json
json_string = model.to_json()
with open("SimpleRNN.config", "w") as text_file:
    text_file.write(json_string)
    
# 模型訓練結果存檔
model.save_weights("SimpleRNN.weight")

程式執行

在DOS下,執行下列指令:
python RNN.py

程式說明

程式架構與之前程式大致相同,最大差別是隱藏層是採用 SimpleRNN,它的準確率達93%,因為,它額外考慮前面的像素關係,準確率自然比單純的 Neural Network 高一些,表示RNN確實有效果,另外,還有幾點要注意:

  1. 如果使用 Sequential 模型,且設定 batch_input_shape 維度,表示當批的output作為下一批的樣本初始狀態( initial states for the samples in the next batch),稱之為『stateful』。
  2. 如果要在後續的隱藏層要重置狀態(state),可呼叫.reset_states()。
  3. RNN 會考慮同一層前面的輸出,當參數 unroll=True 時,表示計算時會先展開結構,如下圖一,它會使用較多的記憶體,但會縮短計算時間,反之 unroll=False,如下圖二。

https://ithelp.ithome.com.tw/upload/images/20171219/20001976FONRwXnjoS.jpg
圖一. unroll=True,計算時會先展開結構,圖片來源 Ashing’s Blog_ 深度學習(3)–循環神經網絡(RNN, Recurrent Neural Networks)
https://ithelp.ithome.com.tw/upload/images/20171219/20001976StUxGxNqOX.png
圖二. unroll=False,計算時不會先展開結構,資料來源:A Gentle Introduction to RNN Unrolling

眼尖的讀者可能會發覺,我們不是說過『要考慮前言後語,以免斷章取義』,而RNN只考慮前面的資料,沒有考慮後面的資料啊,是的,所以,另外有學者提出『雙向』(Bidirectional) RNN,見下圖,就是再加一層由後向前推估的模型,兩者綜合,即為雙向,Keras提供 Bidirectional() 函數實現此一功能,如下:
model.add(Bidirectional(SimpleRNN(10)))
https://ithelp.ithome.com.tw/upload/images/20171224/20001976sZDcxoDYNb.png
圖. 『雙向』(Bidirectional) RNN,資料來源:Recurrent Neural Networks (UPC 2016)

由於,單純使用RNN的應用不多,我們先介紹完另一個相關的演算法 LSTM 後,再一起討論相關應用,所以下一篇先討論『長短期記憶網路』(Long Short Term Memory Network, LSTM)吧。