循環神經網絡背景這里先不介紹了。本文暫時先記錄RNN和LSTM的原理。
首先RNN。RNN和LSTM都是參數復用的,然后每個時間步展開。
RNN的cell比較簡單,我們用Xt表示t時刻cell的輸入,Ct表示t時刻cell的狀態,ht表示t時刻的輸出(輸出和狀態在RNN里是一樣的)。
那么其前向傳播的公式也很簡單:$h_t=C_t=[h_{t-1},X_t]*W+b$
其中[,]表示concat。W和b分別為RNN的kernel和bias。
然后LSTM,是RNN的升級版,加入了forget、input、output三個門,包含3個門,5對參數,兩次更新。賦予了RNN選擇性記憶的能力,一定程度解決了RNN中Long Term Dependency(長期依賴)的問題。
從左向右,三個sigmoid分別對應三個門:forget,input,output,后面用f,i,o代替。
按從左至右順序:
首先是forget gate:$f_t=\sigma([h_{t-1},X_t]*W_f+b_f)$
forget gate用sigmoid函數激活,得到一個0~1的數,來決定$S_{t-1}$的“記憶”留着哪些,忘記哪些。剩下兩個gate用法也類似。
然后是input gate:$i_t=\sigma([h_{t-1},X_t]*W_i+b_i)$
更新細胞狀態C:$C_t=f_t\otimes C_{t-1}+i_t\otimes tanh(([h_{t-1},X_t]*W_s+b_s)$
之后是output gate:$o_t=\sigma([h_{t-1},X_t]*W_o+b_o)$
最后更新輸出h:$h_t=o_t\otimes tanh(C_{t})$
整個流程大概就這樣,方便記憶,我們可以把前向公式整理成下面順序:
$f_t=\sigma([h_{t-1},X_t]*W_f+b_f)$
$i_t=\sigma([h_{t-1},X_t]*W_i+b_i)$
$o_t=\sigma([h_{t-1},X_t]*W_o+b_o)$
$C_t=f_t\otimes C_{t-1}+i_t\otimes tanh(([h_{t-1},X_t]*W_s+b_s)$
$h_t=o_t\otimes tanh(C_{t})$
可以方便的看出5對參數(kernel+bias),3個門,2次更新。
以上是LSTM比較通用的原理,在tensorflow實現中,和上面略有不同,稍微做了簡化。
具體源碼可參見:https://github.com/tensorflow/tensorflow/blob/r1.12/tensorflow/python/ops/rnn_cell_impl.py L165:BasicLSTMCell
源碼延續了f,i,o,c,h的表示
#build
self._kernel = self.add_variable( _WEIGHTS_VARIABLE_NAME, shape=[input_depth + h_depth, 4 * self._num_units]) self._bias = self.add_variable( _BIAS_VARIABLE_NAME, shape=[4 * self._num_units], initializer=init_ops.zeros_initializer(dtype=self.dtype))
gate_inputs = math_ops.matmul( array_ops.concat([inputs, h], 1), self._kernel) gate_inputs = nn_ops.bias_add(gate_inputs, self._bias) # i = input_gate, j = new_input, f = forget_gate, o = output_gate i, j, f, o = array_ops.split( value=gate_inputs, num_or_size_splits=4, axis=one)
在build方法中,將門和狀態更新的參數們一起定義了。后面在call方法中,直接相乘再給split開,化簡了操作。
forget_bias_tensor = constant_op.constant(self._forget_bias, dtype=f.dtype) # Note that using `add` and `multiply` instead of `+` and `*` gives a # performance improvement. So using those at the cost of readability. add = math_ops.add multiply = math_ops.multiply new_c = add(multiply(c, sigmoid(add(f, forget_bias_tensor))), multiply(sigmoid(i), self._activation(j))) new_h = multiply(self._activation(new_c), sigmoid(o))
之后在更新操作中,將該用sigmoid的三個門的激活函數寫死,兩個更新操作的激活函數則隨LSTM初始化參數改變。並多加了一個forget_bias_tensor,這個和原LSTM原理略有不同。
除此之外,LSTM還有幾種變體:
1.peephole connection:
思路很簡單,也就是我們讓 門層 也會接受細胞狀態的輸入。
其他沒有改變。注意outputgate接受的是更新后的細胞狀態。
2.coupled
思路也很簡單
inputgate forgetgate復用一個門,其他一樣。
3.GRU
Gated Recurrent Unit (門控循環單元,GRU)是一個改動比較大的變體。這是由 Cho, et al. (2014) 提出。它將忘記門和輸入門合成了一個單一的 更新門。同樣還混合了細胞狀態和隱藏狀態,和其他一些改動。最終的模型比標准的 LSTM 模型要簡單,也是非常流行的變體。
參考資料:
https://www.jianshu.com/p/9dc9f41f0b29