本文章的例子來自於WILDML
vanillaRNN是相比於LSTMs和GRUs簡單的循環神經網絡,可以說是最簡單的RNN。
RNN結構
RNN的一個特點是所有的隱層共享參數
RNN前向傳導
本文例子介紹:RNN語言模型
關於語言模型的介紹就不說了,是NLP基礎。這裏只說說輸入和輸出的內容。
語言模型的生成屬於無監督學習,只需要大量的文本即可生成。我們只需要做的是構造訓練數據。
構造過程:
1. 生成詞典vocab。(分詞、去掉低頻詞)
2. 將語料中的句子轉爲word_id序列,並在頭尾加上開始和結束id。
3. 生成訓練數據:對於每個句子,輸入爲前len(sent)-1的序列,輸出爲後len(sent)-1的序列(也就是輸入一個詞就預測下一個詞)
如,“我 在 沙灘 上 玩耍”輸入的向量爲
假設我們的詞彙有8000個,採用one-hot向量,則每個輸入
則列出網絡所有參數和輸入輸出的shape,方便推導:
總參數量爲
損失函數(loss function)採用交叉熵:
其中
反向傳播
反向傳播目的就是求預測誤差
如下圖所示,每個時刻t預測的詞都有相應的誤差,我們需要求這些誤差關於參數的所有梯度,最後進行參數的下降調整操作(由於目標是降低Loss function,所以是梯度下降,如果是目標是最大似然,則爲梯度上升)。
我們這裏以計算
爲8000x100的向量,其中
可見關於V的梯度用不到上一層的狀態值,所以不需要累計。
BPTT(Backpropagation Through Time)
下面來求解關於W的梯度:
由於
下圖爲鏈式關係:
所以,
可見由於W在所有隱層中共享,許多變量都依賴W,導致求導鏈變長,這就是BPTT的特點,將每層的影響都累計起來。
下圖爲各鏈接之間的導數,在所有層中不會改變,也體現了傳播的路徑。
跟一般的反向傳播一樣,這裏也定義一個Delta 向量:
其中
所以
爲100x100的矩陣。
同理
爲100x8000的矩陣。
至此,關於
下面,用代碼來解釋這個過程會更加清晰明瞭:
def bptt(self, x, y):
T = len(y)
# Perform forward propagation
o, s = self.forward_propagation(x)
# We accumulate the gradients in these variables
dLdU = np.zeros(self.U.shape)
dLdV = np.zeros(self.V.shape)
dLdW = np.zeros(self.W.shape)
delta_o = o
delta_o[np.arange(len(y)), y] -= 1.
# For each output backwards...
for t in np.arange(T)[::-1]:
dLdV += np.outer(delta_o[t], s[t].T)
# Initial delta calculation: dL/dz
delta_t = self.V.T.dot(delta_o[t]) * (1 - (s[t] ** 2))
# Backpropagation through time (for at most self.bptt_truncate steps)
for bptt_step in np.arange(max(0, t-self.bptt_truncate), t+1)[::-1]:
# print "Backpropagation step t=%d bptt step=%d " % (t, bptt_step)
# Add to gradients at each previous step
dLdW += np.outer(delta_t, s[bptt_step-1])
dLdU[:,x[bptt_step]] += delta_t
# Update delta for next step dL/dz at t-1
delta_t = self.W.T.dot(delta_t) * (1 - s[bptt_step-1] ** 2)
return [dLdU, dLdV, dLdW]
delta_o爲
從T-1時刻開始計算直到0時刻。
梯度消失問題
tanh函數及其導數的圖像:
可見tanh導數的值域是(0,1],兩端都非常平緩並趨於0。
再看我們的梯度公式:
由於遠距離的時刻的梯度貢獻接近於0,因此很難學習到遠距離的依賴關係。
也很容易想象到當導數都很大的時候,就會出現梯度爆炸的情況,但是它的受重視程度不如梯度消失問題,原因有二:
1. 梯度爆炸很明顯,梯度值會變成NaN,程序會崩潰。
2. 用一個預定義值來裁剪梯度值是解決梯度爆炸的一個非常簡單實用的辦法,而梯度消失問題則很難解決。
幸好還有一些辦法來解決梯度消失問題。
1. 合適的參數初始化可以減少梯度消失的影響。
2. 使用ReLU激活函數
3. LSTM和GRU架構。
Reference
http://www.wildml.com/2015/10/recurrent-neural-networks-tutorial-part-3-backpropagation-through-time-and-vanishing-gradients/
http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-2-implementing-a-language-model-rnn-with-python-numpy-and-theano/