關於rnn的學習

rnn主要是用來解決時間序列信息的問題,當一個信息在不同的t上具有前後關係時,使用rnn可以學習到前後信息之間的聯繫。比如針對語音識別、文字識別這種具有上下文聯繫的問題,我們就可以利用rnn來進行學習。

rnn如何將上下文的信息聯繫起來呢?這裏可以聯繫到之前學習到的時間序列分析的思想,一個簡單的時間序列模型MA模型:就是根據之前p個時刻的信息來建立線性方程,從而預測t+1時刻的結果。rnn同樣藉助了這樣的思路,t時刻的結果根據前一段時刻的特徵以及t時刻的特徵來進行預測,如此就利用到了上下文的信息。

但要利用到前多少個時刻的信息呢?這裏如果要是像MA模型一樣,對前面的每一個時刻都計算一個參數,顯然是不好設計網絡結構的,那麼一個巧妙地思路就是構造一個輔助變量_{_S{t}}

每個_{_S{t}}都是根據上一個_{_S{t}}和該時間上的特徵計算出來的,因此輔助變量_{_S{t}}就一輪一輪地結合到了之前每個時間上的特徵,這樣我們就可以根據_{_S{t}}來計算該時間上的預測結果了,也就是_{_O{t}}

這裏同樣存在一個巧妙的思路就是,如果我們對每一個t都使用不同的W、U、V,那麼如果我們的訓練數據是長短不同的序列(比如語音識別、文字識別),那麼這個模型就難以訓練這樣的數據,但事實上長短不同的序列是非常常見的,所以這裏將每一個t時刻的W、U、V都設計成了相同的,這樣的話不僅減少了使用的參數,而且適用於長短不同的序列。

但是這個簡單的rnn結構存在一個問題,由於序列間是權重共享的,在t時刻對W進行求導時,需要用到1t-1時刻的W的梯度信息,當時間序列較長時,梯度相乘的過程中這部分的梯度容易趨於0,也就是不能利用到較長時態的信息。

問:如何同時利用到較長時態和較短時態的信息?

答:可以對較長時態信息和較短時態信息分別進行計算,這樣長時態信息就不會被短時信息所淹沒。

如此就產生了lstm網絡結構,lstm保留一個長時態信息在所有t之間進行傳輸,同時向其中添加短時態信息。

lstm有三個門來幫助結合長短信息,分別爲遺忘門,輸出門和輸入門。

遺忘門: 將上一個時刻計算得到的_{_h{t-1}}和該時刻的_{_x{t}}結合起來計算一個[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的輸入,如此進行第二層的計算。

 

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