Python-Tensorflow-循環神經網絡

循環神經網絡(Recurrent Neural Network,RNN)很多實時情況都能通過時間序列模型來描述。

例如,如果你想寫一個文檔,單詞的順序很重要,當前的單詞肯定取決於以前的單詞。如果把注意力放在文字寫作上...一個單詞中的下一個字符取決於之前的字符(例如,The quick brown f...,下一個字母是 o 的概率很高),如下圖所示。關鍵思想是在給定上下文的情況下產生下一個字符的分佈,然後從分佈中取樣產生下一個候選字符:

 

關於“The quick brown fox”句子的預測示例

圖 1 關於“The quick brown fox”句子的預測示例


一個簡單的變體是存儲多個預測值,並創建一個預測擴展樹,如下圖所示:


關於“The quick brown fox”句子的預測樹示例
圖 2 關於“The quick brown fox”句子的預測樹示例


基於序列的模型可以用在很多領域中。在音樂中,一首曲子的下一個音符肯定取決於前面的音符,而在視頻領域,電影中的下一幀肯定與先前的幀有關。此外,在某些情況下,視頻的當前幀、單詞、字符或音符不僅僅取決於過去的信號,而且還取決於未來的信號。

基於時間序列的模型可以用RNN來描述,其中,時刻 i 輸入爲 Xi,輸出爲 Yi,時刻 [0,i-1] 區間的狀態信息被反饋至網絡。這種反饋過去狀態的思想被循環描述出來,如下圖所示:


反饋的描述

圖 3 反饋的描述


展開(unfolding)網絡可以更清晰地表達循環關係,如下圖所示:


循環單元的展開
圖 4 循環單元的展開


最簡單的 RNN 單元由簡單的 tanh 函數組成,即雙曲正切函數,如下圖所示:

簡單的 tanh 單元
圖 5 簡單的 tanh 單元

梯度消失與梯度爆炸

由於存在兩個穩定性問題,訓練 RNN 是很困難的。由於反饋環路的緣故,梯度可以很快地發散到無窮大,或者迅速變爲 0。如下圖所示:


梯度示例
圖 6 梯度示例


在這兩種情況下,網絡將停止學習任何有用的東西。梯度爆炸的問題可以通過一個簡單的策略來解決,就是梯度裁剪。梯度消失的問題則難以解決,它涉及更復雜的 RNN 基本單元(例如長短時記憶(LSTM)網絡或門控循環單元(GRU))的定義。先來討論梯度爆炸和梯度裁剪:

梯度裁剪包括對梯度限定最大值,以使其不能無界增長。如下圖所示,該方法提供了一個解決梯度爆炸問題的簡單方案:


梯度裁剪示例
圖 7 梯度裁剪示例


解決梯度消失需要一個更復雜的記憶模型,它可以有選擇地忘記以前的狀態,只記住真正重要的狀態。如下圖所示,將輸入以概率 p∈[0,1] 寫入記憶塊 M,並乘以輸入的權重。

以類似的方式,以概率 p∈[0,1] 讀取輸出,並乘以輸出的權重。再用一個概率來決定要記住或忘記什麼:


記憶單元示例
圖 8 記憶單元示例

長短時記憶網絡(LSTM)

長短時記憶網絡可以控制何時讓輸入進入神經元,何時記住之前時序中學到的東西,以及何時讓輸出傳遞到下一個時間戳。所有這些決策僅僅基於輸入就能自我調整。

乍一看,LSTM 看起來很難理解,但事實並非如此。我們用下圖來解釋它是如何工作的:


一個 LSTM 單元的示例
圖 9 一個 LSTM 單元的示例


首先,需要一個邏輯函數 σ 計算出介於 0 和 1 之間的值,並且控制哪個信息片段流經 LSTM 門。請記住,logisitic 函數是可微的,所以它允許反向傳播。

然後需要一個運算符 ⊗ 對兩個相同維數的矩陣進行點乘產生一個新矩陣,其中新矩陣的第 ij 個元素是兩個原始矩陣第 ij 個元素的乘積。同樣,需要一個運算符 ⊕ 將兩個相同維數的矩陣相加,其中新矩陣的第 ij 個元素是兩個原始矩陣第 ij 個元素的和。在這些基本模塊中,將 i 時刻的輸入 xi 與前一步的輸出 yi放在一起。

方程 fi=σ(Wf·[yi-1,xi]+bf) 是邏輯迴歸函數,通過控制激活門 ⊗ 決定前一個單元狀態 Ci-1 中有多少信息應該傳輸給下一個單元狀態 Ci(Wf 是權重矩陣,bf是偏置)。邏輯輸出 1 意味着完全保留先前單元狀態 Ct-1,輸出 0 代表完全忘記 Ci-1 ,輸出(0,1)中的數值則代表要傳遞的信息量。

接着,方程根據當前輸入產生新信息,方程 si=σ(Wc·[Yi-1,Xi]+bc) 則能控制有多少新信息通過運算符 ⊕ 被加入到單元狀態 Ci 中。利用運算符 ⊗ 和 ⊕,給出公式對單元狀態進行更新。

最後,需要確定當前單元狀態的哪些信息輸出到 Yi。很簡單,再次採用邏輯迴歸方程,通過 ⊗ 運算符控制候選值的哪一部分應該輸出。在這裏有一點需要注意,單元狀態是通過 tanh 函數壓縮到 [-1,1]。這部分對應的方程是 Yi=ti*tanh(Ci)。

這看起來像很多數學理論,但有兩個好消息。首先,如果你明白想要達到的目標,那麼數學部分就不是那麼難;其次,你可以使用 LSTM 單元作爲標準 RNN 元的黑盒替換,並立即解決梯度消失問題。因此你真的不需要知道所有的數學理論,你只需從庫中取出 TensorFlow LSTM 並使用它。

門控循環單元和窺孔LSTM

近年來已經提出了許多 LSTM 的變種模型,其中有兩個很受歡迎:窺孔(peephole)LSTM 允許門層查看單元狀態,如下圖中虛線所示;而門控循環單元(GRU)將隱藏狀態和單元狀態合併爲一個信息通道。

同樣,GRU 和窺孔 LSTM 都可以用作標準 RNN 單元的黑盒插件,而不需要知道底層數學理論。這兩種單元都可以用來解決梯度消失的問題,並用來構建深度神經網絡。


標準LTSM、窺孔LTSM、GRU示例

圖 10 標準LTSM、窺孔LTSM、GRU示例

處理向量序列

真正使 RNN 強大的是它能夠處理向量序列,其中 RNN 的輸入和輸出可以是序列,下圖很好地說明了這一點,最左邊的例子是一個傳統(非遞歸)網絡,後面跟着一個序列輸出的 RNN,接着跟着一個序列輸入的 RNN,其次跟着序列輸入和序列輸出不同步的 RNN,最後是序列輸入和序列輸出同步的 RNN。


RNN序列示例
圖 11 RNN序列示例


機器翻譯是輸入序列和輸出序列中不同步的一個例子:網絡將輸入文本作爲一個序列讀取,讀完全文後輸出目標語言。

視頻分類是輸入序列和輸出序列同步的一個例子:視頻輸入是一系列幀,對於每一幀,在輸出中提供分類標籤。

長短期記憶網絡(LSTM)

LSTM的網絡機構圖如下所示:

LSTM的網絡結構

與傳統的循環神經網絡相比,LSTM仍然是基於xt和ht−1來計算ht,只不過對內部的結構進行了更加精心的設計,加入了輸入門it 、遺忘門ft以及輸出門ot三個門和一個內部記憶單元ct。輸入門控制當前計算的新狀態以多大程度更新到記憶單元中;遺忘門控制前一步記憶單元中的信息有多大程度被遺忘掉;輸出門控制當前的輸出有多大程度上取決於當前的記憶單元。

在經典的LSTM模型中,第t層的更新計算公式爲

輸入門計算公式

遺忘門計算公式

輸出門計算公式

候選層計算公式

記憶單元更新公式

每層輸出的信息的計算公式

其中it是通過輸入xt和上一步的隱含層輸出ht−1進行線性變換,再經過激活函數σ得到的。輸入門it的結果是向量,其中每個元素是0到1之間的實數,用於控制各維度流過閥門的信息量;Wi 、Ui兩個矩陣和向量bi爲輸入門的參數,是在訓練過程中需要學習得到的。遺忘門ft和輸出門ot的計算方式與輸入門類似,它們有各自的參數W、U和b。與傳統的循環神經網絡不同的是,從上一個記憶單元的狀態ct−1到當前的狀態ct的轉移不一定完全取決於激活函數計算得到的狀態,還由輸入門和遺忘門來共同控制。

在一個訓練好的網絡中,當輸入的序列中沒有重要信息時,LSTM的遺忘門的值接近於1,輸入門的值接近於0,此時過去的記憶會被保存,從而實現了長期記憶功能;當輸入的序列中出現了重要的信息時,LSTM應當把其存入記憶中,此時其輸入門的值會接近於1;當輸入的序列中出現了重要信息,且該信息意味着之前的記憶不再重要時,輸入門的值接近1,而遺忘門的值接近於0,這樣舊的記憶被遺忘,新的重要信息被記憶。經過這樣的設計,整個網絡更容易學習到序列之間的長期依賴。

作者:Evermemo
鏈接:https://www.jianshu.com/p/0cf7436c33ae
來源:簡書

下面跑一下簡單的循環神經網絡的代碼,同樣以手寫識別爲例:

import os
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
#讀取mnist數據集 如果沒有則會下載
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
#輸入圖片是28*28
n_inputs = 28 #輸入一行,一行有28個數據
max_time = 28 #一共28行
lstm_size = 100 #隱藏單元
n_classes = 10 #10個分類
batch_size = 50 #每批次50個樣本
n_batch = mnist.train.num_examples//batch_size #計算一共多少批次

#這裏表示第一個維度可以是任意的長度
x = tf.placeholder(tf.float32,[None,784])
#正確的標籤
y = tf.placeholder(tf.float32,[None,10])

#初始化權值
weights = tf.Variable(tf.truncated_normal([lstm_size,n_classes],stddev=0.1))
#初始化偏置值
biases = tf.Variable(tf.constant(0.,shape=[n_classes]))

#定義RNN網絡
def RNN(X, weights, biases):
    # inputs=[batch_size, max_time, n_inputs]
    inputs = tf.reshape(X, [-1, max_time, n_inputs])
    # 定義LSTM基本CELL
    lstm_cell = tf.contrib.rnn.BasicLSTMCell(lstm_size)
    # final_state[0]是cell state
    # final_state[1]是hidden_state
    outputs, final_state = tf.nn.dynamic_rnn(lstm_cell, inputs, dtype=tf.float32)
    results = tf.nn.softmax(tf.matmul(final_state[1], weights) + biases)
    return results


#計算RNN的返回結果
prediction = RNN(x,weights,biases)
#損失函數
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=y))
#使用AdamOptimizer進行優化
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#結果存放在一個布爾型的列表中
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(prediction,1))#argmax返回一維張量中最大值所在位置
#求準確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
#初始化
init = tf.global_variables_initializer()
# 進行訓練
with tf.Session() as sess:
    sess.run(init)
    for epoch in range(6):  # 週期
        for batch in range(n_batch):  # 批次
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})

        acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})
        print("週期 :" + str(epoch) + "準確率:" + str(acc))

 

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