Python人工智能 | 十二.循環神經網絡RNN和LSTM原理詳解及TensorFlow分類案例
一.循環神經網絡
在編寫代碼之前,我們需要介紹什么是RNN,RNN是怎樣運行的以及RNN的結構。
1.RNN原理
循環神經網絡英文是Recurrent Neural Networks,簡稱RNN。假設有一組數據data0、data1、data2、data3,使用同一個神經網絡預測它們,得到對應的結果。如果數據之間是有關系的,比如做菜下料的前后步驟,英文單詞的順序,如何讓數據之間的關聯也被神經網絡學習呢?這就要用到——RNN。

假設存在ABCD數字,需要預測下一個數字E,會根據前面ABCD順序進行預測,這就稱為記憶。預測之前,需要回顧以前的記憶有哪些,再加上這一步新的記憶點,最終輸出output,循環神經網絡(RNN)就利用了這樣的原理。
首先,讓我們想想人類是怎么分析事物之間的關聯或順序的。人類通常記住之前發生的事情,從而幫助我們后續的行為判斷,那么是否能讓計算機也記住之前發生的事情呢?

在分析data0時,我們把分析結果存入記憶Memory中,然后當分析data1時,神經網絡(NN)會產生新的記憶,但此時新的記憶和老的記憶沒有關聯,如上圖所示。在RNN中,我們會簡單的把老記憶調用過來分析新記憶,如果繼續分析更多的數據時,NN就會把之前的記憶全部累積起來。

RNN結構如下圖所示,按照時間點t-1、t、t+1,每個時刻有不同的x,每次計算會考慮上一步的state和這一步的x(t),再輸出y值。在該數學形式中,每次RNN運行完之后都會產生s(t),當RNN要分析x(t+1)時,此刻的y(t+1)是由s(t)和s(t+1)共同創造的,s(t)可看作上一步的記憶。多個神經網絡NN的累積就轉換成了循環神經網絡,其簡化圖如下圖的左邊所示。

總之,只要你的數據是有順序的,就可以使用RNN,比如人類說話的順序,電話號碼的順序,圖像像素排列的順序,ABC字母的順序等。在前面講解CNN原理時,它可以看做是一個濾波器滑動掃描整幅圖像,通過卷積加深神經網絡對圖像的理解。

而RNN也有同樣的掃描效果,只不過是增加了時間順序和記憶功能。RNN通過隱藏層周期性的連接,從而捕獲序列化數據中的動態信息,提升預測結果。

2.RNN應用
RNN常用于自然語言處理、機器翻譯、語音識別、圖像識別等領域,下面簡單分享RNN相關應用所對應的結構。
RNN情感分析: 當分析一個人說話情感是積極的還是消極的,就用如下圖所示的RNN結構,它有N個輸入,1個輸出,最后時間點的Y值代表最終的輸出結果。

RNN圖像識別: 此時有一張圖片輸入X,N張對應的輸出。

RNN機器翻譯: 輸入和輸出分別兩個,對應的是中文和英文,如下圖所示。

二.LSTM RNN原理詳解
接下來我們看一個更強大的結構,稱為LSTM。
1.為什么引入LSTM
RNN是在有序的數據上進行學習的,RNN會像人一樣對先前的數據發生記憶,但有時候也會像老爺爺一樣忘記先前所說。為了解決RNN的這個弊端,提出了LTSM技術,它的英文全稱是Long short-term memory,長短期記憶,也是當下最流行的RNN之一。

假設現在有一句話,如下圖所示,RNN判斷這句話是紅燒排骨,這時需要學習,而“紅燒排骨“在句子開頭。

"紅燒排骨"這個詞需要經過長途跋涉才能抵達,要經過一系列得到誤差,然后經過反向傳遞,它在每一步都會乘以一個權重w參數。如果乘以的權重是小于1的數,比如0.9,0.9會不斷地乘以誤差,最終這個值傳遞到初始值時,誤差就消失了,這稱為梯度消失或梯度離散。

反之,如果誤差是一個很大的數,比如1.1,則這個RNN得到的值會很大,這稱為梯度爆炸。

梯度消失或梯度爆炸:
在RNN中,如果你的State是一個很長的序列,假設反向傳遞的誤差值是一個小于1的數,每次反向傳遞都會乘以這個數,0.9的n次方趨向于0,1.1的n次方趨向于無窮大,這就會造成梯度消失或梯度爆炸。
這也是RNN沒有恢復記憶的原因,為了解決RNN梯度下降時遇到的梯度消失或梯度爆炸問題,引入了LSTM。
2.LSTM
LSTM是在普通的RNN上面做了一些改進,LSTM RNN多了三個控制器,即輸入、輸出、忘記控制器。左邊多了個條主線,例如電影的主線劇情,而原本的RNN體系變成了分線劇情,并且三個控制器都在分線上。

- 輸入控制器(write gate): 在輸入input時設置一個gate,gate的作用是判斷要不要寫入這個input到我們的內存Memory中,它相當于一個參數,也是可以被訓練的,這個參數就是用來控制要不要記住當下這個點。
- 輸出控制器(read gate): 在輸出位置的gate,判斷要不要讀取現在的Memory。
- 忘記控制器(forget gate): 處理位置的忘記控制器,判斷要不要忘記之前的Memory。
LSTM工作原理為:如果分線劇情對于最終結果十分重要,輸入控制器會將這個分線劇情按重要程度寫入主線劇情,再進行分析;如果分線劇情改變了我們之前的想法,那么忘記控制器會將某些主線劇情忘記,然后按比例替換新劇情,所以主線劇情的更新就取決于輸入和忘記控制;最后的輸出會基于主線劇情和分線劇情。
通過這三個gate能夠很好地控制我們的RNN,基于這些控制機制,LSTM是延緩記憶的良藥,從而帶來更好的結果。
三.Tensorflow編寫RNN代碼
接下來我們通過手寫數字圖片集數據編寫RNN代碼。RNN是基于順序的數據,想象下圖片的順序,它是一行一行像素組成的,最終判定圖片的數字屬于哪類。


第一步,打開Anaconda,然后選擇已經搭建好的“tensorflow”環境,運行Spyder。

第二步,導入擴展包。
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data
第三步,下載數據集。
由于MNIST數據集是TensorFlow的示例數據,所以我們只需要下面一行代碼,即可實現數據集的讀取工作。如果數據集不存在它會在線下載,如果數據集已經被下載,它會被直接調用。
# 下載手寫數字圖像數據集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

第四步,定義參數。
# 設置參數 learning_rate = 0.001 # 學習效率 train_iters = 100000 # 訓練次數 batch_size = 128 # 自定義 n_inputs = 28 # MNIST 輸入圖像形狀 28*28 黑白圖片高度為1 n_steps = 28 # time steps 輸入圖像的28行數據 n_hidden_units = 128 # 神經網絡隱藏層數量 n_classes = 10 # 分類結果 數字0-0
第五步,定義placeholder,用于傳入值xs和ys至神經網絡。
# 設置傳入的值xs和ys x = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) #每張圖片28*28=784個點 y = tf.placeholder(tf.float32, [None, n_classes]) #每個樣本有10個輸出
第六步,定義權重和誤差變量。
權重和偏置包括輸入和輸出值,需要注意其設置的形狀。
# 定義權重 進入RNN前的隱藏層 輸入&輸出
weights = {
# (28, 128)
'in': tf.Variable(tf.random_normal([n_inputs, n_hidden_units])),
# (128, 10)
'out': tf.Variable(tf.random_normal([n_hidden_units, n_classes])),
}
# 定義偏置 進入RNN前的隱藏層 輸入&輸出
biases = {
# (128, )
'in': tf.Variable(tf.constant(0.1, shape=[n_hidden_units, ])),
# (10, )
'out': tf.Variable(tf.constant(0.1, shape=[n_classes, ])),
}
第七步,定義RNN神經網絡。
RNN定義分別對應三層,X輸入、Cell為中心計算、H為最終輸出,需要注意數據形狀的變化。在RNN運算過程中,每一步的輸出都存儲在outputs序列中,LSTM包括c_state(主線)和m_state(分線)。最終輸出結果為Cell的輸出和權重輸出的乘積,再加上輸出偏置。(詳見注釋)
#---------------------------------定義RNN-------------------------------
def RNN(X, weights, biases):
# hidden layer for input to cell
#######################################################
# X (128 batch, 28 steps, 28 inputs) 28行*28列
# X ==> (128*28, 28 inputs)
X = tf.reshape(X, [-1, n_inputs])
# 隱藏層 輸入
# X_in ==> (128batch*28steps, 128 hidden)
X_in = tf.matmul(X, weights['in']) + biases['in']
# 二維數據轉換成三維數據
# 注意:神經網絡學習時要注意其形狀如何變化
# X_in ==> (128 batch, 28 steps, 128 hidden)
X_in = tf.reshape(X_in, [-1, n_steps, n_hidden_units]) # 128個隱藏層
# cell
#######################################################
# Cell結構 隱藏層數 forget初始偏置為1.0(初始時不希望forget)
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(n_hidden_units, forget_bias=1.0, state_is_tuple=True)
# RNN會保留每一步計算的結果state
# lstm cell is divided into two parts (c_state, m_state) 主線c_state 分線m_state
_init_state = lstm_cell.zero_state(batch_size, dtype=tf.float32)
# RNN運算過程 每一步的輸出都存儲在outputs序列中
# 常規RNN只有m_state LSTM包括c_state和m_state
outputs, states = tf.nn.dynamic_rnn(lstm_cell, X_in, initial_state=_init_state, time_major=False)
# hidden layer for output as final results
#######################################################
# 第三層加工最終的輸出
# 最終輸出=Cell的輸出*權重輸出+偏置數據
# states包含了主線劇情和分線劇情 states[1]表示分線劇情的結果 即為outputs[-1]最后一個輸出結果
results = tf.matmul(states[1], weights['out']) + biases['out']
# 第二種方法
# 解包 unpack to list [(batch, outputs)..] * steps
#outputs = tf.unstack(tf.transpose(outputs, [1,0,2])) # states is the last outputs
#results = tf.matmul(outputs[-1], weights['out']) + biases['out']
return results
第八步,定義誤差和準確度。
#---------------------------------定義誤差和訓練------------------------------- pre = RNN(x, weights, biases) # 預測值與真實值誤差 cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pre, labels=y)) # 訓練學習 學習效率設置為0.001 train_step = tf.train.AdamOptimizer(learning_rate).minimize(cost) #梯度下降減小誤差 # 預測正確個數 correct_pred = tf.equal(tf.argmax(pre, 1), tf.argmax(y, 1)) # 準確度 accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
第九步,初始化及訓練。
#---------------------------------初始化及訓練-------------------------------
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
step = 0
# 循環每次提取128個樣本
while step * batch_size < train_iters:
# 從下載好的數據集提取128個樣本
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# 形狀修改 [128, 28, 28]
batch_xs = batch_xs.reshape([batch_size, n_steps, n_inputs])
# 訓練
sess.run([train_step], feed_dict={
x: batch_xs,
y: batch_ys,
})
# 每隔20步輸出結果
if step % 20 == 0: # 20*128
print(sess.run(accuracy, feed_dict={
x: batch_xs,
y: batch_ys,
}))
step += 1
最終輸出結果如下所示,可以看到,最早預測的準確度結果非常低為2.187%,最后提升到了96.87%,其結果高于之前的一般神經網絡的結果87.79%(第六篇博客),由此可見TensorFlow RNN的分類學習效果還不錯,并且在不斷學習中。
Extracting MNIST_data\train-images-idx3-ubyte.gz Extracting MNIST_data\train-labels-idx1-ubyte.gz Extracting MNIST_data\t10k-images-idx3-ubyte.gz Extracting MNIST_data\t10k-labels-idx1-ubyte.gz 0.2187500 0.6796875 0.8281250 0.8203125 0.8359375 0.8984375 0.8828125 0.8359375 0.9062500 .... 0.9843750 0.9609375 0.9453125 0.9609375 0.9765625 0.9375000 0.9921875 0.9609375 0.9921875 0.9687500
完整代碼如下:
# -*- coding: utf-8 -*-
"""
Created on Fri Jan 3 11:50:33 2020
@author: xiuzhang Eastmount CSDN
"""
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 下載手寫數字圖像數據集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
# 設置參數
learning_rate = 0.001 # 學習效率
train_iters = 100000 # 訓練次數
batch_size = 128 # 自定義
n_inputs = 28 # MNIST 輸入圖像形狀 28*28 黑白圖片高度為1
n_steps = 28 # time steps 輸入圖像的28行數據
n_hidden_units = 128 # 神經網絡隱藏層數量
n_classes = 10 # 分類結果 數字0-0
#-----------------------------定義placeholder輸入-------------------------
# 設置傳入的值xs和ys
x = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) #每張圖片28*28=784個點
y = tf.placeholder(tf.float32, [None, n_classes]) #每個樣本有10個輸出
# 定義權重 進入RNN前的隱藏層 輸入&輸出
weights = {
# (28, 128)
'in': tf.Variable(tf.random_normal([n_inputs, n_hidden_units])),
# (128, 10)
'out': tf.Variable(tf.random_normal([n_hidden_units, n_classes])),
}
# 定義偏置 進入RNN前的隱藏層 輸入&輸出
biases = {
# (128, )
'in': tf.Variable(tf.constant(0.1, shape=[n_hidden_units, ])),
# (10, )
'out': tf.Variable(tf.constant(0.1, shape=[n_classes, ])),
}
#---------------------------------定義RNN-------------------------------
def RNN(X, weights, biases):
# hidden layer for input to cell
#######################################################
# X (128 batch, 28 steps, 28 inputs) 28行*28列
# X ==> (128*28, 28 inputs)
X = tf.reshape(X, [-1, n_inputs])
# 隱藏層 輸入
# X_in ==> (128batch*28steps, 128 hidden)
X_in = tf.matmul(X, weights['in']) + biases['in']
# 二維數據轉換成三維數據
# 注意:神經網絡學習時要注意其形狀如何變化
# X_in ==> (128 batch, 28 steps, 128 hidden)
X_in = tf.reshape(X_in, [-1, n_steps, n_hidden_units]) # 128個隱藏層
# cell
#######################################################
# Cell結構 隱藏層數 forget初始偏置為1.0(初始時不希望forget)
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(n_hidden_units, forget_bias=1.0, state_is_tuple=True)
# RNN會保留每一步計算的結果state
# lstm cell is divided into two parts (c_state, m_state) 主線c_state 分線m_state
_init_state = lstm_cell.zero_state(batch_size, dtype=tf.float32)
# RNN運算過程 每一步的輸出都存儲在outputs序列中
# 常規RNN只有m_state LSTM包括c_state和m_state
outputs, states = tf.nn.dynamic_rnn(lstm_cell, X_in, initial_state=_init_state, time_major=False)
# hidden layer for output as final results
#######################################################
# 第三層加工最終的輸出
# 最終輸出=Cell的輸出*權重輸出+偏置數據
# states包含了主線劇情和分線劇情 states[1]表示分線劇情的結果 即為outputs[-1]最后一個輸出結果
results = tf.matmul(states[1], weights['out']) + biases['out']
# 第二種方法
# 解包 unpack to list [(batch, outputs)..] * steps
#outputs = tf.unstack(tf.transpose(outputs, [1,0,2])) # states is the last outputs
#results = tf.matmul(outputs[-1], weights['out']) + biases['out']
return results
#---------------------------------定義誤差和訓練-------------------------------
pre = RNN(x, weights, biases)
# 預測值與真實值誤差
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pre, labels=y))
# 訓練學習 學習效率設置為0.001
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cost) #梯度下降減小誤差
# 預測正確個數
correct_pred = tf.equal(tf.argmax(pre, 1), tf.argmax(y, 1))
# 準確度
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
#---------------------------------初始化及訓練-------------------------------
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
step = 0
# 循環每次提取128個樣本
while step * batch_size < train_iters:
# 從下載好的數據集提取128個樣本
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# 形狀修改 [128, 28, 28]
batch_xs = batch_xs.reshape([batch_size, n_steps, n_inputs])
# 訓練
sess.run([train_step], feed_dict={
x: batch_xs,
y: batch_ys,
})
# 每隔20步輸出結果
if step % 20 == 0: # 20*128
print(sess.run(accuracy, feed_dict={
x: batch_xs,
y: batch_ys,
}))
step += 1
注意,在運行代碼過程中可能會報錯“ValueError: Variable rnn/basic_lstm_cell/kernel already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope?”
在Spyder中有kernel選項,點擊選擇 “ Restart & RunAll ” 重新運行代碼即可解決問題。

四.總結
寫到這里,這篇文章就講解完畢,更多TensorFlow深度學習文章會繼續分享,接下來我們會分享RNN回歸、文本識別、圖像識別、語音識別等內容。如果讀者有什么想學習的,也可以私聊我,我去學習并應用到你的領域。
最后,希望這篇基礎性文章對您有所幫助,如果文章中存在錯誤或不足之處,還請海涵~作為人工智能的菜鳥,我希望自己能不斷進步并深入,后續將它應用于圖像識別、網絡安全、對抗樣本等領域,指導大家撰寫簡單的學術論文,一起加油!