LSTM原理及實現

 

前面我們介紹了RNN,現在我們來介紹一種特殊的RNN結構,LSTM網絡。我們將逐步介紹LSTM的結構,原理,以及利用LSTM識別手寫數字的demo跟深刻的理解LSTM。

LSTM網絡

long short term memory,即我們所稱呼的LSTM,是爲了解決長期以來問題而專門設計出來的,所有的RNN都具有一種重複神經網絡模塊的鏈式形式。在標準RNN中,這個重複的結構模塊只有一個非常簡單的結構,例如一個tanh層。

LSTM 同樣是這樣的結構,但是重複的模塊擁有一個不同的結構。不同於單一神經網絡層,這裏是有四個,以一種非常特殊的方式進行交互。

不必擔心這裏的細節。我們會一步一步地剖析 LSTM 解析圖。現在,我們先來熟悉一下圖中使用的各種元素的圖標。

在上面的圖例中,每一條黑線傳輸着一整個向量,從一個節點的輸出到其他節點的輸入。粉色的圈代表 pointwise 的操作,諸如向量的和,而黃色的矩陣就是學習到的神經網絡層。合在一起的線表示向量的連接,分開的線表示內容被複制,然後分發到不同的位置。

LSTM核心思想

LSTM的關鍵在於細胞的狀態整個(綠色的圖表示的是一個cell),和穿過細胞的那條水平線。

細胞狀態類似於傳送帶。直接在整個鏈上運行,只有一些少量的線性交互。信息在上面流傳保持不變會很容易。

若只有上面的那條水平線是沒辦法實現添加或者刪除信息的。而是通過一種叫做 門(gates) 的結構來實現的。

門 可以實現選擇性地讓信息通過,主要是通過一個 sigmoid 的神經層 和一個逐點相乘的操作來實現的。

sigmoid 層輸出(是一個向量)的每個元素都是一個在 0 和 1 之間的實數,表示讓對應信息通過的權重(或者佔比)。比如, 0 表示“不讓任何信息通過”, 1 表示“讓所有信息通過”。

LSTM通過三個這樣的本結構來實現信息的保護和控制。這三個門分別輸入門、遺忘門和輸出門。

逐步理解LSTM

現在我們就開始通過三個門逐步的瞭解LSTM的原理

遺忘門

在我們 LSTM 中的第一步是決定我們會從細胞狀態中丟棄什麼信息。這個決定通過一個稱爲忘記門層完成。該門會讀取ht−1ht−1和xtxt,輸出一個在 0到 1之間的數值給每個在細胞狀態 Ct−1Ct−1 中的數字。1 表示“完全保留”,0 表示“完全捨棄”。

讓我們回到語言模型的例子中來基於已經看到的預測下一個詞。在這個問題中,細胞狀態可能包含當前主語的性別,因此正確的代詞可以被選擇出來。當我們看到新的主語,我們希望忘記舊的主語。

其中ht−1ht−1表示的是上一個cell的輸出,xtxt表示的是當前細胞的輸入。σσ表示sigmod函數。

輸入門

下一步是決定讓多少新的信息加入到 cell 狀態 中來。實現這個需要包括兩個 步驟:首先,一個叫做“input gate layer ”的 sigmoid 層決定哪些信息需要更新;一個 tanh 層生成一個向量,也就是備選的用來更新的內容,C^tC^t 。在下一步,我們把這兩部分聯合起來,對 cell 的狀態進行一個更新。

現在是更新舊細胞狀態的時間了,Ct−1Ct−1更新爲CtCt。前面的步驟已經決定了將會做什麼,我們現在就是實際去完成。

我們把舊狀態與ftft相乘,丟棄掉我們確定需要丟棄的信息。接着加上it∗C~tit∗C~t。這就是新的候選值,根據我們決定更新每個狀態的程度進行變化。

在語言模型的例子中,這就是我們實際根據前面確定的目標,丟棄舊代詞的性別信息並添加新的信息的地方。

輸出門

最終,我們需要確定輸出什麼值。這個輸出將會基於我們的細胞狀態,但是也是一個過濾後的版本。首先,我們運行一個 sigmoid 層來確定細胞狀態的哪個部分將輸出出去。接着,我們把細胞狀態通過 tanh 進行處理(得到一個在 -1 到 1 之間的值)並將它和 sigmoid 門的輸出相乘,最終我們僅僅會輸出我們確定輸出的那部分。

在語言模型的例子中,因爲他就看到了一個 代詞,可能需要輸出與一個 動詞 相關的信息。例如,可能輸出是否代詞是單數還是負數,這樣如果是動詞的話,我們也知道動詞需要進行的詞形變化。

LSTM變體

原文這部分介紹了 LSTM 的幾個變種,還有這些變形的作用。在這裏我就不再寫了。有興趣的可以直接閱讀原文。

下面主要講一下其中比較著名的變種 GRU(Gated Recurrent Unit ),這是由 Cho, et al. (2014) 提出。在 GRU 中,如下圖所示,只有兩個門:重置門(reset gate)和更新門(update gate)。同時在這個結構中,把細胞狀態和隱藏狀態進行了合併。最後模型比標準的 LSTM 結構要簡單,而且這個結構後來也非常流行。

其中, rtrt表示重置門,ztzt表示更新門。重置門決定是否將之前的狀態忘記。(作用相當於合併了 LSTM 中的遺忘門和傳入門)當rtrt趨於0的時候,前一個時刻的狀態信息ht−1ht−1會被忘掉,隱藏狀態h^th^t會被重置爲當前輸入的信息。更新門決定是否要將隱藏狀態更新爲新的狀態h^th^t(作用相當於 LSTM 中的輸出門) 。

和 LSTM 比較一下: 
- GRU 少一個門,同時少了細胞狀態CtCt。 
- 在 LSTM 中,通過遺忘門和傳入門控制信息的保留和傳入;GRU 則通過重置門來控制是否要保留原來隱藏狀態的信息,但是不再限制當前信息的傳入。 
- 在 LSTM 中,雖然得到了新的細胞狀態 Ct,但是還不能直接輸出,而是需要經過一個過濾的處理:ht=ot∗tanh(Ct)ht=ot∗tanh(Ct);同樣,在 GRU 中, 雖然我們也得到了新的隱藏狀態h^th^t, 但是還不能直接輸出,而是通過更新門來控制最後的輸出:ht=(1−zt)∗ht−1+zt∗h^tht=(1−zt)∗ht−1+zt∗h^t

多層LSTM

多層LSTM是將LSTM進行疊加,其優點是能夠在高層更抽象的表達特徵,並且減少神經元的個數,增加識別準確率並且降低訓練時間。具體信息參考[3]

LSTM實現手寫數字

這裏我們利用的數據集是tensorflow提供的一個手寫數字數據集。該數據集是一個包含n張28*28的數據集。

設置LSTM參數

# -*- coding: utf-8 -*-
import tensorflow as tf
from tensorflow.contrib import rnn

import numpy as np
import input_data

# configuration
#                        O * W + b -> 10 labels for each image, O[? 28], W[28 10], B[10]
#                       ^ (O: output 28 vec from 28 vec input)
#                       |
#      +-+  +-+       +--+
#      |1|->|2|-> ... |28| time_step_size = 28
#      +-+  +-+       +--+
#       ^    ^    ...  ^
#       |    |         |
# img1:[28] [28]  ... [28]
# img2:[28] [28]  ... [28]
# img3:[28] [28]  ... [28]
# ...
# img128 or img256 (batch_size or test_size 256)
#      each input size = input_vec_size=lstm_size=28

# configuration variables
input_vec_size = lstm_size = 28 # 輸入向量的維度
time_step_size = 28 # 循環層長度

batch_size = 128
test_size = 256

這裏設置將batch_size設置爲128,time_step_size表示的是lstm神經元的個數,這裏設置爲28個(和圖片的尺寸有關?),input_vec_size表示一次輸入的像素數。

初始化權值參數

def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

def model(X, W, B, lstm_size):
    # X, input shape: (batch_size, time_step_size, input_vec_size)
    # XT shape: (time_step_size, batch_size, input_vec_size)
    #對這一步操作還不是太理解,爲什麼需要將第一行和第二行置換
    XT = tf.transpose(X, [1, 0, 2])  # permute time_step_size and batch_size,[28, 128, 28]
    # XR shape: (time_step_size * batch_size, input_vec_size)
    XR = tf.reshape(XT, [-1, lstm_size]) # each row has input for each lstm cell (lstm_size=input_vec_size)

    # Each array shape: (batch_size, input_vec_size)
    X_split = tf.split(XR, time_step_size, 0) # split them to time_step_size (28 arrays),shape = [(128, 28),(128, 28)...]
    # Make lstm with lstm_size (each input vector size). num_units=lstm_size; forget_bias=1.0
    lstm = rnn.BasicLSTMCell(lstm_size, forget_bias=1.0, state_is_tuple=True)

    # Get lstm cell output, time_step_size (28) arrays with lstm_size output: (batch_size, lstm_size)
    # rnn..static_rnn()的輸出對應於每一個timestep,如果只關心最後一步的輸出,取outputs[-1]即可
    outputs, _states = rnn.static_rnn(lstm, X_split, dtype=tf.float32)  # 時間序列上每個Cell的輸出:[... shape=(128, 28)..]
    # tanh activation
    # Get the last output
    return tf.matmul(outputs[-1], W) + B, lstm.state_size # State size to initialize the state

init_weigths函數利用正態分佈隨機生成參數的初始值,model的四個參數分別爲:X爲輸入的數據,W表示的是28*10的權值(標籤爲0-9),B表示的是偏置,維度和W一樣。這裏首先將一批128*(28*28)的圖片放進神經網絡。然後進行相關的操作(註釋已經寫得很明白了,這裏就不再贅述),然後利用WX+B求出預測結果,同時返回lstm的尺寸

訓練

py_x, state_size = model(X, W, B, lstm_size)

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels=Y))
train_op = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost)

然後通過交叉熵計算誤差,反覆訓練得到最優值。

源代碼: 
https://github.com/geroge-gao/deeplearning/tree/master/LSTM

參考資料

[1].https://www.jianshu.com/p/9dc9f41f0b29

[2].http://blog.csdn.net/Jerr__y/article/details/58598296 
[3].Stacked Long Short-Term Memory Networks 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章