rnn主要是用來解決時間序列信息的問題,當一個信息在不同的t上具有前後關係時,使用rnn可以學習到前後信息之間的聯繫。比如針對語音識別、文字識別這種具有上下文聯繫的問題,我們就可以利用rnn來進行學習。
rnn如何將上下文的信息聯繫起來呢?這裏可以聯繫到之前學習到的時間序列分析的思想,一個簡單的時間序列模型MA模型:就是根據之前p個時刻的信息來建立線性方程,從而預測t+1時刻的結果。rnn同樣藉助了這樣的思路,t時刻的結果根據前一段時刻的特徵以及t時刻的特徵來進行預測,如此就利用到了上下文的信息。
但要利用到前多少個時刻的信息呢?這裏如果要是像MA模型一樣,對前面的每一個時刻都計算一個參數,顯然是不好設計網絡結構的,那麼一個巧妙地思路就是構造一個輔助變量
每個都是根據上一個和該時間上的特徵計算出來的,因此輔助變量就一輪一輪地結合到了之前每個時間上的特徵,這樣我們就可以根據來計算該時間上的預測結果了,也就是。
這裏同樣存在一個巧妙的思路就是,如果我們對每一個t都使用不同的W、U、V,那麼如果我們的訓練數據是長短不同的序列(比如語音識別、文字識別),那麼這個模型就難以訓練這樣的數據,但事實上長短不同的序列是非常常見的,所以這裏將每一個t時刻的W、U、V都設計成了相同的,這樣的話不僅減少了使用的參數,而且適用於長短不同的序列。
但是這個簡單的rnn結構存在一個問題,由於序列間是權重共享的,在t時刻對W進行求導時,需要用到1至t-1時刻的W的梯度信息,當時間序列較長時,梯度相乘的過程中這部分的梯度容易趨於0,也就是不能利用到較長時態的信息。
問:如何同時利用到較長時態和較短時態的信息?
答:可以對較長時態信息和較短時態信息分別進行計算,這樣長時態信息就不會被短時信息所淹沒。
如此就產生了lstm網絡結構,lstm保留一個長時態信息在所有t之間進行傳輸,同時向其中添加短時態信息。
lstm有三個門來幫助結合長短信息,分別爲遺忘門,輸出門和輸入門。
遺忘門: 將上一個時刻計算得到的和該時刻的結合起來計算一個[0,1]區間內的值,這個值代表了我們要保留多少長時態信息的百分比。 如果遺忘門得到的值爲1,則表示所有長時態信息都可以通過,若爲0,則表示所有的長時態信息都不通過。
輸入門:輸入門計算當前的短時態的信息。輸入們的計算分成兩部分。
根據遺忘門和輸入門的結果我們可以對長時態信息進行更新:長時態信息一部分通過遺忘門沒有被保留,另外根據輸入門添加了一部分新的信息。從而我們對長時態信息進行了更新。
那麼該時刻的輸出是如何計算的呢?輸出一方面根據更新後的長時態信息,一方面根據當前時態信息進行計算。
整體來說就是這樣一個結構:
多層lstm:
下圖來自https://blog.csdn.net/xiewenbo/article/details/80210850
多層lstm就是把第一層lstm在每一個t上得到的結果再輸入第二層,如此構成多層lstm。
通過tensorflow建立一個基本的lstm單元:
這裏的num_units代表隱藏層輸出的值的維度,比如每個t輸入的array的大小爲3*1,我們設置num_units爲5,那麼lstm輸出的array大小就是5*1。
lstm_cell = rnn.BasicLSTMCell(num_units=hidden_size, forget_bias=1.0, state_is_tuple=True)
如果要構造多層lstm,可以通過如下的函數,這裏的 layer_num就是我們的sltm層數。
mlstm_cell = rnn.MultiRNNCell([lstm_cell] * layer_num, state_is_tuple=True)
lstm的函數源碼:
'''code: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/rnn/python/ops/core_rnn_cell_impl.py'''
def __call__(self, inputs, state, scope=None):
"""Long short-term memory cell (LSTM)."""
with vs.variable_scope(scope or "basic_lstm_cell"):
# Parameters of gates are concatenated into one multiply for efficiency.
if self._state_is_tuple:
c, h = state
else:
c, h = array_ops.split(value=state, num_or_size_splits=2, axis=1)
concat = _linear([inputs, h], 4 * self._num_units, True, scope=scope)
# ** 下面四個 tensor,分別是四個 gate 對應的權重矩陣
# i = input_gate, j = new_input, f = forget_gate, o = output_gate
i, j, f, o = array_ops.split(value=concat, num_or_size_splits=4, axis=1)
# ** 更新 cell 的狀態:
# ** c * sigmoid(f + self._forget_bias) 是保留上一個 timestep 的部分舊信息
# ** sigmoid(i) * self._activation(j) 是有當前 timestep 帶來的新信息
new_c = (c * sigmoid(f + self._forget_bias) + sigmoid(i) *
self._activation(j))
# ** 新的輸出
new_h = self._activation(new_c) * sigmoid(o)
if self._state_is_tuple:
new_state = LSTMStateTuple(new_c, new_h)
else:
new_state = array_ops.concat([new_c, new_h], 1)
# ** 在(一般都是) state_is_tuple=True 情況下, new_h=new_state[1]
# ** 在上面博文中,就有 cell_output = state[1]
return new_h, new_state
雙向lstm:
先從相對簡單的單層雙向lstm說起,下面爲示意圖:
前向和後向分別進行計算,最後的輸出結果y是綜合了前後向的輸出結果(前向,後向)
如果是多層的雙向lstm,其實就是單層雙向lstm的組合,將第一層雙向lstm的結果作爲下一層雙向lstm的輸入,如此進行第二層的計算。