在《神經網路的梯度推導與程式碼驗證》之LSTM的前向傳播和反向梯度推導 中,我們學習了LSTM的前向傳播和反向梯度求導,但知識仍停留在紙面。本篇章將基於深度學習框架tensorflow驗證我們所得結論的準確性,以便將抽象的數學符號和實際資料結合起來,將知識固化。更多相關內容請見《神經網路的梯度推導與程式碼驗證》系列介紹。
提醒:
- 後續會反覆出現$\boldsymbol{\delta}^{l}$這個(類)符號,它的定義為$\boldsymbol{\delta}^{l} = \frac{\partial l}{\partial\boldsymbol{z}^{\boldsymbol{l}}}$,即loss $l$對$\boldsymbol{z}^{\boldsymbol{l}}$的導數
- 其中$\boldsymbol{z}^{\boldsymbol{l}}$表示第$l$層(DNN,CNN,RNN或其他例如max pooling層等)未經過啟用函式的輸出。
- $\boldsymbol{a}^{\boldsymbol{l}}$則表示$\boldsymbol{z}^{\boldsymbol{l}}$經過啟用函式後的輸出。
這些符號會貫穿整個系列,還請留意。
需要用到的庫有tensorflow和numpy,其中tensorflow其實版本>=2.0.0就行
import tensorflow as tf import numpy as np np.random.seed(0)
然後是定義交叉熵損失函式:
def get_crossentropy(y_pred, y_true): return -tf.reduce_sum(y_true * tf.math.log(y_pred))
--------前向傳播驗證---------
下面開始實現前向傳播:
我們先來看如果拿tensorflow快速實現前向傳播是什麼樣的:
y_true = np.array([[[0.3, 0.5, 0.2], [0.2, 0.3, 0.5], [0.5, 0.2, 0.3]]]).astype(np.float32) inputs = np.random.random([1, 3, 4]).astype(np.float32) # 初始化c和h狀態: 第一個是h狀態,第二個是c init_state = [tf.constant(np.random.random((inputs.shape[0], 2)).astype(np.float32)), tf.constant(np.random.random((inputs.shape[0], 2)).astype(np.float32))] # 定義LSTM layer lstm_cell = tf.keras.layers.LSTMCell(2) lstm = tf.keras.layers.RNN(lstm_cell, return_sequences=True, return_state=True) lstm_seq, last_h, last_c = lstm(inputs=inputs, initial_state=init_state) # fnn layer dense = tf.keras.layers.Dense(3) # 最後得到y output_seq = tf.math.softmax(dense(lstm_seq))
我們隨便造一條樣本(inputs, y_ture),跟vanilla RNN的程式碼驗證一樣,輸入inputs是一條步長為3的有4維特徵的資料;標籤資料步長也為3,每個步長上是長度為3的概率向量。
inputs.shape Out[3]: (1, 3, 4) y_true.shape Out[4]: (1, 3, 3)
至於初始狀態init_state,區別於vanilla RNN,LSTM的內部狀態不僅有h,還有c狀態。
init_state Out[5]: [<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.56804454, 0.92559665]], dtype=float32)>, <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.07103606, 0.0871293 ]], dtype=float32)>]
它們的dim都是2,意味著接下來定義LSTM layer的時候,units等於2,也就是第10行程式碼。
程式碼12行輸出了inputs經過LSTM layer的h狀態序列,並返回了最後一個time step的h和c 狀態:
lstm_seq Out[6]: <tf.Tensor: shape=(1, 3, 2), dtype=float32, numpy= array([[[0.0734415 , 0.0337011 ], [0.12446897, 0.04911498], [0.08435698, 0.1005574 ]]], dtype=float32)> last_h Out[7]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.08435698, 0.1005574 ]], dtype=float32)> last_c Out[8]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[0.4424333 , 0.25549415]], dtype=float32)>
顯然lstm_seq[0, -1, :] 就是last_h,這點在上面這段程式碼得到了驗證。
lstm_seq經過全連線最後得到shape和y_true一樣的輸出序列:
output_seq Out[9]: <tf.Tensor: shape=(1, 3, 3), dtype=float32, numpy= array([[[0.33866003, 0.33178926, 0.32955068], [0.34256846, 0.3297772 , 0.3276543 ], [0.3379959 , 0.3387031 , 0.32330096]]], dtype=float32)>
通過呼叫上面幾行程式碼,我們似乎能夠實現上圖的操作。但尚未確定程式碼lstm = tf.keras.layers.RNN(lstm_cell, return_sequences=True, return_state=True)是否按正真按照LSTM的前傳公式進行計算的。
從下面tensorflow的LSTM原始碼上看其實不太好對應上前向傳播公式,因為kernel和bias實際上是包含了四個門的kernel和bias的大tensor,如果想要對照著前傳公式並配合原始碼一起消化的話,可以參考這篇博文的原始碼解讀。
def step(cell_inputs, cell_states): """Step function that will be used by Keras RNN backend.""" h_tm1 = cell_states[0] # previous memory state c_tm1 = cell_states[1] # previous carry state z = K.dot(cell_inputs, kernel) z += K.dot(h_tm1, recurrent_kernel) z = K.bias_add(z, bias) z0, z1, z2, z3 = array_ops.split(z, 4, axis=1) i = nn.sigmoid(z0) f = nn.sigmoid(z1) c = f * c_tm1 + i * nn.tanh(z2) o = nn.sigmoid(z3) h = o * nn.tanh(c) return h, [h, c]
但我們最好還是動手實操一下,這樣印象可以更加深刻,所以接下來我們手動按照LSTM的前傳公式實現一遍前向傳播看跟tensorflow給出的輸出結果是否一致。
下面這段程式碼看似很長,但實際上只是反覆做同樣的事情而已,它實現了LSTM在時間上展開3步的前向操作,並計算了loss,所以真正的程式碼量你可以認為大約只有1/3這樣。
這裡 tf.GradientTape(persistent=True) ,t.watch()是用於後面計算變數的導數用的,不太熟悉的可參考tensorflow官方給出的關於這部分的教程(自動微分)。
1 # 提取lstm的變數 2 kernel = lstm.weights[0] 3 recurrent_kernel = lstm.weights[1] 4 bias = lstm.weights[2] 5 6 with tf.GradientTape(persistent=True) as t: 7 h0, c0 = init_state 8 # --------stpe1----------- 9 z1 = tf.matmul(inputs[:, 0, :], kernel) 10 z1 += tf.matmul(init_state[0], recurrent_kernel) 11 z1 += bias 12 13 i1 = tf.math.sigmoid(z1[:, 0:2]) 14 f1 = tf.math.sigmoid(z1[:, 2:4]) 15 c1 = f1 * init_state[1] + i1 * tf.math.tanh(z1[:, 4:6]) 16 t.watch(c1) 17 o1 = tf.math.sigmoid(z1[:, 6:8]) 18 19 h1 = o1 * tf.math.tanh(c1) 20 t.watch(h1) 21 out1 = dense(h1) 22 t.watch(out1) 23 a1 = tf.math.softmax(out1) 24 t.watch(a1) 25 # --------stpe2------------- 26 z2 = tf.matmul(inputs[:, 1, :], kernel) 27 z2 += tf.matmul(h1, recurrent_kernel) 28 z2 += bias 29 30 i2 = tf.math.sigmoid(z2[:, 0:2]) 31 f2 = tf.math.sigmoid(z2[:, 2:4]) 32 c2 = f2 * c1 + i2 * tf.math.tanh(z2[:, 4:6]) 33 t.watch(c2) 34 o2 = tf.math.sigmoid(z2[:, 6:8]) 35 36 h2 = o2 * tf.math.tanh(c2) 37 t.watch(h2) 38 out2 = dense(h2) 39 t.watch(out2) 40 a2 = tf.math.softmax(out2) 41 t.watch(a2) 42 # ---------step3--------------- 43 z3 = tf.matmul(inputs[:, 2, :], kernel) 44 z3 += tf.matmul(h2, recurrent_kernel) 45 z3 += bias 46 47 i3 = tf.math.sigmoid(z3[:, 0:2]) 48 f3 = tf.math.sigmoid(z3[:, 2:4]) 49 c3 = f3 * c2 + i3 * tf.math.tanh(z3[:, 4:6]) 50 t.watch(c3) 51 o3 = tf.math.sigmoid(z3[:, 6:8]) 52 53 h3 = o3 * tf.math.tanh(c3) 54 t.watch(c3) 55 out3 = dense(h3) 56 t.watch(out3) 57 a3 = tf.math.softmax(out3) 58 t.watch(a3) 59 # ---------loss---------------- 60 my_seqout = tf.stack([a1, a2, a3], axis=1) 61 my_loss = get_crossentropy(y_pred=my_seqout, y_true=y_true) 62 # -------L(t)--------- 63 loss_1 = get_crossentropy(y_pred=my_seqout[:, 0, :], y_true=y_true[:, 0, :]) 64 loss_2 = get_crossentropy(y_pred=my_seqout[:, 1, :], y_true=y_true[:, 1, :]) 65 loss_3 = get_crossentropy(y_pred=my_seqout[:, 2, :], y_true=y_true[:, 2, :])
為方便結合公式理解,下面是LSTM前傳的核心公式:
前向傳播過程在每個時間步$t$上發生的順序為:
1)更新遺忘門輸出:
$\boldsymbol{f}^{(t)} = \sigma\left( {\boldsymbol{W}_{f}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{f}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{f}} \right)$
2)更新輸入門和其控制物件:
$\boldsymbol{i}^{(t)} = \sigma\left( {\boldsymbol{W}_{i}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{i}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{i}} \right)$
$\boldsymbol{a}^{(t)} = tanh\left( {\boldsymbol{W}_{a}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{a}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{a}} \right)$
3)更新細胞狀態,從而$\left. \boldsymbol{C}^{(t - 1)}\longrightarrow\boldsymbol{C}^{(t)} \right.$:
$\boldsymbol{C}^{(t)} = \boldsymbol{C}^{(t - 1)}\bigodot\boldsymbol{f}^{(t)} + \boldsymbol{a}^{(t)}\bigodot\boldsymbol{i}^{(t)}$
4)更新輸出門和其控制物件,從而$\left. \boldsymbol{h}^{(t - 1)}\longrightarrow\boldsymbol{h}^{(t)} \right.$:
$\boldsymbol{o}^{(t)} = \sigma\left( {\boldsymbol{W}_{o}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{o}\boldsymbol{x}^{(t - 1)} + \boldsymbol{b}_{o}} \right)$
$\boldsymbol{h}^{(t)} = \boldsymbol{o}^{(t)}\bigodot tanh\left( \boldsymbol{C}^{(t)} \right)$
5)得到當前時間步$t$的預測輸出:
${\hat{\boldsymbol{y}}}^{(t)} = \sigma\left( {\boldsymbol{V}\boldsymbol{h}^{(t)} + \boldsymbol{c}} \right)$
接下來解釋上述程式碼,首先是看看lstm.weights都有什麼:
lstm.weights Out[11]: [<tf.Variable 'rnn/lstm_cell/kernel:0' shape=(4, 8) dtype=float32, numpy= array([[-0.20611948, -0.13917124, -0.4464248 , -0.5220002 , -0.07079256, -0.11765444, -0.6348312 , -0.09944093], [-0.36945656, 0.49826902, -0.38441902, 0.5155092 , 0.43278998, -0.6451189 , 0.25279564, -0.5977526 ], [-0.16671354, -0.34911466, -0.36100882, -0.441833 , 0.22657168, 0.6603723 , -0.64222896, 0.39119774], [ 0.50059 , -0.46351027, 0.21986896, 0.5533599 , 0.34416074, 0.46849674, -0.6191046 , -0.6988822 ]], dtype=float32)>, <tf.Variable 'rnn/lstm_cell/recurrent_kernel:0' shape=(2, 8) dtype=float32, numpy= array([[ 0.0145992 , -0.7155518 , -0.02687099, -0.21511525, -0.5779975 , 0.2309799 , -0.06609378, -0.22130255], [-0.06615384, -0.62732273, -0.27503067, 0.35276616, 0.4759796 , -0.19590497, -0.18337685, 0.3216232 ]], dtype=float32)>, <tf.Variable 'rnn/lstm_cell/bias:0' shape=(8,) dtype=float32, numpy=array([0., 0., 1., 1., 0., 0., 0., 0.], dtype=float32)>]
跟vanilla RNN類似,也是有3類變數。但注意這裡3類變數的shape中都有一個維度等於8,因為這是4種門的kernel,recurrent_kernel和bias拼在一起的結果,這樣方便進行批量矩陣乘法。
回到程式碼,程式碼9~11實現的就是上面前傳公式組中的:
$\boldsymbol{W}_{f}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{f}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{f}$
$\boldsymbol{W}_{i}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{i}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{i}$
$\boldsymbol{W}_{a}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{a}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{a}$
$\boldsymbol{W}_{o}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{o}\boldsymbol{x}^{(t - 1)} + \boldsymbol{b}_{o}$
只不過這四組運算的結果都合一塊變成了z1,如果要專門提取某個門的z結果,需要對z1進行索引操作。例如i1 = tf.math.sigmoid(z1[:, 0:2])在做的就是:
$\boldsymbol{i}^{(t)} = \sigma\left( {\boldsymbol{W}_{i}\boldsymbol{h}^{(t - 1)} + \boldsymbol{U}_{i}\boldsymbol{x}^{(t)} + \boldsymbol{b}_{i}} \right)$
其他3個門的計算以此類推。
c1 = f1 * init_state[1] + i1 * tf.math.tanh(z1[:, 4:6])實現的是c狀態的遞推,即:
$\boldsymbol{C}^{(t)} = \boldsymbol{C}^{(t - 1)}\bigodot\boldsymbol{f}^{(t)} + \boldsymbol{a}^{(t)}\bigodot\boldsymbol{i}^{(t)}$
h1 = o1 * tf.math.tanh(c1)則實現了上述公式組的h狀態的計算,即:
$\boldsymbol{h}^{(t)} = \boldsymbol{o}^{(t)}\bigodot tanh\left( \boldsymbol{C}^{(t)} \right)$
21+23行程式碼則實現了LSTM的輸出計算,即:
${\hat{\boldsymbol{y}}}^{(t)} = \sigma\left( {\boldsymbol{V}\boldsymbol{h}^{(t)} + \boldsymbol{c}} \right)$
其中desne layer的kernel和bias分別對應$\boldsymbol{V}$和$\boldsymbol{c}$
為突出重點這裡不會去驗證前面定義好了的dense layer是否真的按照FNN的前向傳播公式在做,這部分驗證可參考FNN(DNN)前向和反向傳播過程的程式碼驗證。
至此,我們完成了c和h狀態在時間步上的遞進,也完成了當前time step的輸出,剩下的程式碼就是繼續迴圈再進行多兩次。注意在計算time step2的c和h狀態時,遞推公式用到的就是上一個time step的c和h狀態init_state了。
最後我們看看3個time step上的前向輸出跟tensorflow給出rnn layer的輸出output_seq 是否一致:
output_seq Out[12]: <tf.Tensor: shape=(1, 3, 3), dtype=float32, numpy= array([[[0.33866003, 0.33178926, 0.32955068], [0.34256846, 0.3297772 , 0.3276543 ], [0.3379959 , 0.3387031 , 0.32330096]]], dtype=float32)> my_seqout Out[13]: <tf.Tensor: shape=(1, 3, 3), dtype=float32, numpy= array([[[0.33866003, 0.33178926, 0.32955068], [0.34256846, 0.3297772 , 0.3276543 ], [0.3379959 , 0.3387031 , 0.32330096]]], dtype=float32)>
看來並沒有問題。
--------反向傳播驗證---------
我們先將4個門的recurrent_kernel,即前傳公式中組中的$\boldsymbol{W}_{i}$,$\boldsymbol{W}_{f}$,$\boldsymbol{W}_{a}$和$\boldsymbol{W}_{o}$分別提取出來:
w_i = recurrent_kernel[:, 0:2] w_f = recurrent_kernel[:, 2:4] w_a = recurrent_kernel[:, 4:6] w_o = recurrent_kernel[:, 6:8]
因為是BPTT,所以首先我們驗證$\boldsymbol{\delta}_{h}^{(T)}$和$\boldsymbol{\delta}_{C}^{(T)}$,根據公式,它們滿足:
$\boldsymbol{\delta}_{h}^{(T)} = \boldsymbol{V}^{T}\left( {{\hat{\boldsymbol{y}}}^{(T)} - \boldsymbol{y}^{(T)}} \right)$
$\boldsymbol{\delta}_{C}^{(T)} = \left( \frac{\partial\boldsymbol{h}^{(T)}}{\partial\boldsymbol{C}^{(T)}} \right)^{T}\frac{\partial L}{\partial\boldsymbol{h}^{(T)}} = \boldsymbol{\delta}_{h}^{(T)}\bigodot\boldsymbol{o}^{(T)}\bigodot{tanh}^{'}\left( \boldsymbol{C}^{(T)} \right)$
下面是對比結果,其中delta_hT = t.gradient(my_loss, h3)表示這是通過tensorflow自動微分工具求得的$\boldsymbol{\delta}_{h}^{(T)}$,而帶my_字首的則是根據LSTM的前向傳播和反向梯度推導 中的公式(就是上面的式子)手動實現的結果。後續的符號同樣沿用這樣的命名規則。
(.transpose()的作用和意義見FNN(DNN)前向和反向傳播過程的程式碼驗證 給出的解釋,這裡不再贅述)
# --------to validate delta_hT--------- delta_hT = t.gradient(my_loss, h3) my_delta_hT = tf.matmul((a3 - y_true[:, 2, :]), tf.transpose(dense.kernel)) # --------to validate delta_cT--------- delta_cT = t.gradient(my_loss, c3) my_delta_cT = delta_hT * o3 * (1 - tf.math.tanh(c3)**2)
delta_hT Out[14]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-0.07147076, 0.0525395 ]], dtype=float32)> my_delta_hT Out[15]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-0.07147078, 0.05253952]], dtype=float32)> delta_cT Out[16]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-0.01199877, 0.01980529]], dtype=float32)> my_delta_cT Out[17]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-0.01199877, 0.01980529]], dtype=float32)>
驗證無誤,公式是正確的。
接下來是驗證兩個重要的遞推公式:
第一個遞推公式,從$\boldsymbol{\delta}_{C}^{(t + 1)}$和$\boldsymbol{\delta}_{h}^{(t + 1)}$推得$\boldsymbol{\delta}_{h}^{(t)}$:
$\boldsymbol{\delta}_{h}^{(t)} = \frac{\partial l\left( t \right)}{\partial\boldsymbol{h}^{(t)}} + \left( \frac{\partial\boldsymbol{C}^{(t + 1)}}{\partial\boldsymbol{h}^{(t)}} \right)^{T}\boldsymbol{\delta}_{C}^{(t + 1)} + \left( {\frac{\partial\boldsymbol{h}^{(t + 1)}}{\partial\boldsymbol{o}^{(t)}}\frac{\partial\boldsymbol{o}^{(t + 1)}}{\partial\boldsymbol{h}^{(t)}}} \right)^{T}\boldsymbol{\delta}_{h}^{(t + 1)}$
其中,
$\frac{\partial\boldsymbol{C}^{(t + 1)}}{\partial\boldsymbol{h}^{(t)}} = diag\left( {\boldsymbol{C}^{(t)}\bigodot\boldsymbol{f}^{({t + 1})}\bigodot\left( {1 - \boldsymbol{f}^{({t + 1})}} \right)} \right)\boldsymbol{W}_{f} + diag\left( {\boldsymbol{a}^{({t + 1})}\bigodot\boldsymbol{i}^{({t + 1})}\bigodot\left( {1 - \boldsymbol{i}^{({t + 1})}} \right)} \right)\boldsymbol{W}_{i} + diag\left( {\boldsymbol{i}^{({t + 1})}\bigodot\left( {1 - {\boldsymbol{a}^{({t + 1})}}^{2}} \right)} \right)\boldsymbol{W}_{a}$
$\frac{\partial\boldsymbol{h}^{(t + 1)}}{\partial\boldsymbol{o}^{(t)}}\frac{\partial\boldsymbol{o}^{(t + 1)}}{\partial\boldsymbol{h}^{(t)}} = diag\left( {tanh\left( \boldsymbol{C}^{({t + 1})} \right)\bigodot\boldsymbol{o}^{({t + 1})}\bigodot\left( {1 - \boldsymbol{o}^{({t + 1})}} \right)} \right)$
驗證程式碼如下:
# -----------from delta_c2 and delta_h2 to delta_h1---------- a_2 = tf.math.tanh(z2[:, 4:6]) delta_h2 = t.gradient(my_loss, h2) delta_c2 = t.gradient(my_loss, c2) dc2_dh1 = tf.matmul(w_f, np.diag(tf.squeeze(c1 * f2 * (1 - f2)))) + \ tf.matmul(w_i, np.diag(tf.squeeze(a_2 * i2 * (1 - i2)))) + \ tf.matmul(w_a, np.diag(tf.squeeze(i2 * (1 - a_2**2)))) delta_h1 = t.gradient(my_loss, h1) my_delta_h1 = tf.matmul((a1 - y_true[:, 0, :]), tf.transpose(dense.kernel)) + \ tf.matmul(delta_c2, tf.transpose(dc2_dh1)) + \ tf.matmul(delta_h2, tf.transpose(tf.matmul(w_o, np.diag(tf.squeeze(tf.math.tanh(c2) * o2 * (1 - o2))))))
delta_h1 Out[18]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.0437731 , -0.09992676]], dtype=float32)> my_delta_h1 Out[19]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.0437731 , -0.09992676]], dtype=float32)>
沒有問題,遞推公式正確。
再是另外一個遞推公式, 從$\boldsymbol{\delta}_{h}^{(t)}$和$\boldsymbol{\delta}_{C}^{(t + 1)}$推得$\boldsymbol{\delta}_{C}^{(t)}$:
$\boldsymbol{\delta}_{C}^{(t)} = \left( \frac{\partial\boldsymbol{h}^{(t)}}{\partial\boldsymbol{c}^{(t)}} \right)^{T}\boldsymbol{\delta}_{h}^{(t)} + \left( \frac{\partial\boldsymbol{c}^{(t + 1)}}{\partial\boldsymbol{c}^{(t)}} \right)^{T}\boldsymbol{\delta}_{C}^{(t + 1)} = \boldsymbol{o}^{(t)} \odot \left( {1 - {tanh}^{2}\left( \boldsymbol{C}^{(t)} \right)} \right)^{2}{\odot \boldsymbol{\delta}}_{h}^{(t)} + \boldsymbol{f}^{({t + 1})} \odot \boldsymbol{\delta}_{C}^{(t + 1)}$
驗證程式碼如下:
# --------------from delta_h1 and delta_c2 to delta_c1--------- delta_c1 = t.gradient(my_loss, c1) my_delta_c1 = o1 * (1 - tf.math.tanh(c1)**2) * delta_h1 + f2 * delta_c2
delta_c1 Out[20]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.01076444, -0.01738559]], dtype=float32)> my_delta_c1 Out[21]: <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[ 0.01076444, -0.01738559]], dtype=float32)>
沒問題+1。
最後是驗證$\frac{\partial L}{\partial\boldsymbol{W}_{f}}$,根據公式,它滿足:
$\frac{\partial L}{\partial\boldsymbol{W}_{f}} = {\sum\limits_{t = 1}^{T}\left\lbrack {\boldsymbol{\delta}_{C}^{(t)} \odot \boldsymbol{C}^{(t - 1)} \odot \boldsymbol{f}^{(t)} \odot \left\lbrack {1 - \boldsymbol{f}^{(t)}} \right\rbrack} \right\rbrack}\left( \boldsymbol{h}^{(t - 1)} \right)^{T}$
程式碼驗證如下:
# -------------to validate dl_dW_f--------------- dl_dW_f = t.gradient(my_loss, recurrent_kernel)[:, 2:4] my_dl_dW_f = tf.matmul(tf.transpose(h2), (delta_cT * c2 * f3 * (1 - f3))) + \ tf.matmul(tf.transpose(h1), (delta_c2 * c1 * f2 * (1 - f2))) + \ tf.matmul(tf.transpose(h0), (delta_c1 * c0 * f1 * (1 - f1)))
dl_dW_f Out[22]: <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[-6.2376050e-05, -2.1518332e-05], [ 1.0945055e-04, -1.8339025e-04]], dtype=float32)> my_dl_dW_f Out[23]: <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[-6.2376050e-05, -2.1518332e-05], [ 1.0945055e-04, -1.8339025e-04]], dtype=float32)>
沒問題+1。
至此,LSTM的所有前向和部分反向傳播公式已驗證完,剩下的反向傳播公式例如$\frac{\partial L}{\partial\boldsymbol{W}_{i}}$和$\frac{\partial L}{\partial\boldsymbol{W}_{o}}$等,都是類似這樣的操作罷了。
如果本文對您有所幫助的話,還請點下“推薦”讓它能幫到更多的人,謝謝。
(歡迎轉載,轉載請註明出處。歡迎留言或溝通交流: lxwalyw@gmail.com)