長短期記憶(LSTM)是目前迴圈神經網路最普遍使用的型別,在處理時間序列資料時使用最為頻繁。關於 LSTM 的更加深刻的洞察可以看看這篇優秀的部落格:http://colah.github.io/posts/2015-08-Understanding-LSTMs/。
本文的主要目的是使讀者熟悉在 TensorFlow 上實現基礎 LSTM 神經網路的詳細過程。我們將選用 MNIST 作為資料集。
MNIST 資料集包括手寫數字的影象和對應的標籤。我們可以根據以下內建功能從 TensorFlow 上下載並讀取資料。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
資料被分成 3 個部分:
1. 訓練資料(mnist.train):55000 張影象
2. 測試資料(mnist.test):10000 張影象
3. 驗證資料(mnist.validation):5000 張影象
資料的形態
訓練資料集包括 55000 張 28x28 畫素的影象,這些 784(28x28)畫素值被展開成一個維度為 784 的單一向量,所有 55000 個畫素向量(每個影象一個)被儲存為形態為 (55000,784) 的 numpy 陣列,並命名為 mnist.train.images。
所有這 55000 張影象都關聯了一個類別標籤(表示其所屬類別),一共有 10 個類別(0,1,2...9),類別標籤使用獨熱編碼的形式表示。因此標籤將作為形態為 (55000,10) 的陣列儲存,並命名為 mnist.train.labels。
LSTM 通常用來解決複雜的序列處理問題,比如包含了 NLP 概念(詞嵌入、編碼器等)的語言建模問題。這些問題本身需要大量理解,那麼將問題簡化並集中於在 TensorFlow 上實現 LSTM 的細節(比如輸入格式化、LSTM 單元格以及網路結構設計),會是個不錯的選擇。
MNIST 就正好提供了這樣的機會。其中的輸入資料是一個畫素值的集合。我們可以輕易地將其格式化,將注意力集中在 LSTM 實現細節上。
在動手寫程式碼之前,先規劃一下實現的藍圖,可以使寫程式碼的過程更加直觀。
VANILLA RNN
迴圈神經網路按時間軸展開的時候,如下圖所示:
圖中,
1.x_t 代表時間步 t 的輸入;
2.s_t 代表時間步 t 的隱藏狀態,可看作該網路的「記憶」;
3.o_t 作為時間步 t 時刻的輸出;
4.U、V、W 是所有時間步共享的引數,共享的重要性在於我們的模型在每一時間步以不同的輸入執行相同的任務。
當把 RNN 展開的時候,網路可被看作每一個時間步都受上一時間步輸出影響(時間步之間存在連線)的前饋網路。
兩個注意事項
為了更順利的進行實現,需要清楚兩個概念的含義:
1.TensorFlow 中 LSTM 單元格的解釋;
2. 資料輸入 TensorFlow RNN 之前先格式化。
TensorFlow 中 LSTM 單元格的解釋
在 TensorFlow 中,基礎的 LSTM 單元格宣告為:
tf.contrib.rnn.BasicLSTMCell(num_units)
這裡,num_units 指一個 LSTM 單元格中的單元數。num_units 可以比作前饋神經網路中的隱藏層,前饋神經網路的隱藏層的節點數量等於每一個時間步中一個 LSTM 單元格內 LSTM 單元的 num_units 數量。下圖可以幫助直觀理解:
每一個 num_units LSTM 單元都可以看作一個標準的 LSTM 單元:
以上圖表來自部落格(地址:http://colah.github.io/posts/2015-08-Understanding-LSTMs/),該部落格有效介紹了 LSTM 的概念。
資料輸入 TensorFlow RNN 之前先格式化
在 TensorFlow 中最簡單的 RNN 形式是 static_rnn,在 TensorFlow 中定義如下:
tf.static_rnn(cell,inputs)
雖然還有其它的注意事項,但在這裡我們僅關注這兩個。
inputs 引數接受形態為 [batch_size,input_size] 的張量列表。列表的長度為將網路展開後的時間步數,即列表中每一個元素都分別對應網路展開的時間步。比如在 MNIST 資料集中,我們有 28x28 畫素的影象,每一張都可以看成擁有 28 行 28 個畫素的影象。我們將網路按 28 個時間步展開,以使在每一個時間步中,可以輸入一行 28 個畫素(input_size),從而經過 28 個時間步輸入整張影象。給定影象的 batch_size 值,則每一個時間步將分別收到 batch_size 個影象。詳見下圖說明:
由 static_rnn 生成的輸出是一個形態為 [batch_size,n_hidden] 的張量列表。列表的長度為將網路展開後的時間步數,即每一個時間步輸出一個張量。在這個實現中我們只需關心最後一個時間步的輸出,因為一張影象的所有行都輸入到 RNN,預測即將在最後一個時間步生成。
現在,所有的困難部分都已經完成,可以開始寫程式碼了。只要理清了概念,寫程式碼過程是很直觀的。
在開始的時候,先匯入一些必要的依賴關係、資料集,並宣告一些常量。設定 batch_size=128 、 num_units=128。
import tensorflow as tffrom tensorflow.contrib import rnn
#import mnist datasetfrom tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets("/tmp/data/",one_hot=True)#define constants#unrolled through 28 time steps
time_steps=28#hidden LSTM units
num_units=128#rows of 28 pixels
n_input=28#learning rate for adam
learning_rate=0.001#mnist is meant to be classified in 10 classes(0-9).
n_classes=10#size of batch
batch_size=128
現在設定佔位、權重以及偏置變數(用於將輸出的形態從 [batch_size,num_units] 轉換為 [batch_size,n_classes]),從而可以預測正確的類別。
#weights and biases of appropriate shape to accomplish above task
out_weights=tf.Variable(tf.random_normal([num_units,n_classes]))
out_bias=tf.Variable(tf.random_normal([n_classes]))#defining placeholders#input image placeholder
x=tf.placeholder("float",[None,time_steps,n_input])#input label placeholder
y=tf.placeholder("float",[None,n_classes])
現在我們得到了形態為 [batch_size,time_steps,n_input] 的輸入,我們需要將其轉換成形態為 [batch_size,n_inputs] 、長度為 time_steps 的張量列表,從而可以將其輸入 static_rnn。
#processing the input tensor from [batch_size,n_steps,n_input] to "time_steps" number of [batch_size,n_input] tensors
input=tf.unstack(x ,time_steps,1)
現在我們可以定義網路了。我們將利用 BasicLSTMCell 的一個層,將我們的 static_rnn 從中提取出來。
#defining the network
lstm_layer=rnn.BasicLSTMCell(n_hidden,forget_bias=1)
outputs,_=rnn.static_rnn(lstm_layer,input,dtype="float32")
我們只考慮最後一個時間步的輸入,從中生成預測。
#converting last output of dimension [batch_size,num_units] to [batch_size,n_classes] by out_weight multiplication
prediction=tf.matmul(outputs[-1],out_weights)+out_bias
定義損失函式、最佳化器和準確率。
#loss_function
loss=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=y))#optimization
opt=tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)#model evaluation
correct_prediction=tf.equal(tf.argmax(prediction,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
現在我們已經完成定義,可以開始運行了。
#initialize variables
init=tf.global_variables_initializer()with tf.Session() as sess:
sess.run(init)
iter=1while iter<800:
batch_x,batch_y=mnist.train.next_batch(batch_size=batch_size)
batch_x=batch_x.reshape((batch_size,time_steps,n_input))
sess.run(opt, feed_dict={x: batch_x, y: batch_y})if iter %10==0:
acc=sess.run(accuracy,feed_dict={x:batch_x,y:batch_y})
los=sess.run(loss,feed_dict={x:batch_x,y:batch_y})print("For iter ",iter)print("Accuracy ",acc)print("Loss ",los)print("__________________")
iter=iter+1
需要注意的是我們的每一張影象在開始時被平坦化為 784 維的單一向量,函式 next_batch(batch_size) 必須返回這些 784 維向量的 batch_size 批次數。因此它們的形態要被改造成 [batch_size,time_steps,n_input],從而可以被我們的佔位符接受。我們還可以計算模型的準確率:
#calculating test accuracy
test_data = mnist.test.images[:128].reshape((-1, time_steps, n_input))
test_label = mnist.test.labels[:128]print("Testing Accuracy:", sess.run(accuracy, feed_dict={x: test_data, y: test_label}))
在執行的時候,模型的測試準確率為 99.21%。
長短期記憶(LSTM)是目前迴圈神經網路最普遍使用的型別,在處理時間序列資料時使用最為頻繁。關於 LSTM 的更加深刻的洞察可以看看這篇優秀的部落格:http://colah.github.io/posts/2015-08-Understanding-LSTMs/。
目的本文的主要目的是使讀者熟悉在 TensorFlow 上實現基礎 LSTM 神經網路的詳細過程。我們將選用 MNIST 作為資料集。
MNIST 資料集MNIST 資料集包括手寫數字的影象和對應的標籤。我們可以根據以下內建功能從 TensorFlow 上下載並讀取資料。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
資料被分成 3 個部分:
1. 訓練資料(mnist.train):55000 張影象
2. 測試資料(mnist.test):10000 張影象
3. 驗證資料(mnist.validation):5000 張影象
資料的形態
訓練資料集包括 55000 張 28x28 畫素的影象,這些 784(28x28)畫素值被展開成一個維度為 784 的單一向量,所有 55000 個畫素向量(每個影象一個)被儲存為形態為 (55000,784) 的 numpy 陣列,並命名為 mnist.train.images。
所有這 55000 張影象都關聯了一個類別標籤(表示其所屬類別),一共有 10 個類別(0,1,2...9),類別標籤使用獨熱編碼的形式表示。因此標籤將作為形態為 (55000,10) 的陣列儲存,並命名為 mnist.train.labels。
為什麼要選擇 MNIST?LSTM 通常用來解決複雜的序列處理問題,比如包含了 NLP 概念(詞嵌入、編碼器等)的語言建模問題。這些問題本身需要大量理解,那麼將問題簡化並集中於在 TensorFlow 上實現 LSTM 的細節(比如輸入格式化、LSTM 單元格以及網路結構設計),會是個不錯的選擇。
MNIST 就正好提供了這樣的機會。其中的輸入資料是一個畫素值的集合。我們可以輕易地將其格式化,將注意力集中在 LSTM 實現細節上。
實現在動手寫程式碼之前,先規劃一下實現的藍圖,可以使寫程式碼的過程更加直觀。
VANILLA RNN
迴圈神經網路按時間軸展開的時候,如下圖所示:
圖中,
1.x_t 代表時間步 t 的輸入;
2.s_t 代表時間步 t 的隱藏狀態,可看作該網路的「記憶」;
3.o_t 作為時間步 t 時刻的輸出;
4.U、V、W 是所有時間步共享的引數,共享的重要性在於我們的模型在每一時間步以不同的輸入執行相同的任務。
當把 RNN 展開的時候,網路可被看作每一個時間步都受上一時間步輸出影響(時間步之間存在連線)的前饋網路。
兩個注意事項
為了更順利的進行實現,需要清楚兩個概念的含義:
1.TensorFlow 中 LSTM 單元格的解釋;
2. 資料輸入 TensorFlow RNN 之前先格式化。
TensorFlow 中 LSTM 單元格的解釋
在 TensorFlow 中,基礎的 LSTM 單元格宣告為:
tf.contrib.rnn.BasicLSTMCell(num_units)
這裡,num_units 指一個 LSTM 單元格中的單元數。num_units 可以比作前饋神經網路中的隱藏層,前饋神經網路的隱藏層的節點數量等於每一個時間步中一個 LSTM 單元格內 LSTM 單元的 num_units 數量。下圖可以幫助直觀理解:
每一個 num_units LSTM 單元都可以看作一個標準的 LSTM 單元:
以上圖表來自部落格(地址:http://colah.github.io/posts/2015-08-Understanding-LSTMs/),該部落格有效介紹了 LSTM 的概念。
資料輸入 TensorFlow RNN 之前先格式化
在 TensorFlow 中最簡單的 RNN 形式是 static_rnn,在 TensorFlow 中定義如下:
tf.static_rnn(cell,inputs)
雖然還有其它的注意事項,但在這裡我們僅關注這兩個。
inputs 引數接受形態為 [batch_size,input_size] 的張量列表。列表的長度為將網路展開後的時間步數,即列表中每一個元素都分別對應網路展開的時間步。比如在 MNIST 資料集中,我們有 28x28 畫素的影象,每一張都可以看成擁有 28 行 28 個畫素的影象。我們將網路按 28 個時間步展開,以使在每一個時間步中,可以輸入一行 28 個畫素(input_size),從而經過 28 個時間步輸入整張影象。給定影象的 batch_size 值,則每一個時間步將分別收到 batch_size 個影象。詳見下圖說明:
由 static_rnn 生成的輸出是一個形態為 [batch_size,n_hidden] 的張量列表。列表的長度為將網路展開後的時間步數,即每一個時間步輸出一個張量。在這個實現中我們只需關心最後一個時間步的輸出,因為一張影象的所有行都輸入到 RNN,預測即將在最後一個時間步生成。
現在,所有的困難部分都已經完成,可以開始寫程式碼了。只要理清了概念,寫程式碼過程是很直觀的。
程式碼在開始的時候,先匯入一些必要的依賴關係、資料集,並宣告一些常量。設定 batch_size=128 、 num_units=128。
import tensorflow as tffrom tensorflow.contrib import rnn
#import mnist datasetfrom tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets("/tmp/data/",one_hot=True)#define constants#unrolled through 28 time steps
time_steps=28#hidden LSTM units
num_units=128#rows of 28 pixels
n_input=28#learning rate for adam
learning_rate=0.001#mnist is meant to be classified in 10 classes(0-9).
n_classes=10#size of batch
batch_size=128
現在設定佔位、權重以及偏置變數(用於將輸出的形態從 [batch_size,num_units] 轉換為 [batch_size,n_classes]),從而可以預測正確的類別。
#weights and biases of appropriate shape to accomplish above task
out_weights=tf.Variable(tf.random_normal([num_units,n_classes]))
out_bias=tf.Variable(tf.random_normal([n_classes]))#defining placeholders#input image placeholder
x=tf.placeholder("float",[None,time_steps,n_input])#input label placeholder
y=tf.placeholder("float",[None,n_classes])
現在我們得到了形態為 [batch_size,time_steps,n_input] 的輸入,我們需要將其轉換成形態為 [batch_size,n_inputs] 、長度為 time_steps 的張量列表,從而可以將其輸入 static_rnn。
#processing the input tensor from [batch_size,n_steps,n_input] to "time_steps" number of [batch_size,n_input] tensors
input=tf.unstack(x ,time_steps,1)
現在我們可以定義網路了。我們將利用 BasicLSTMCell 的一個層,將我們的 static_rnn 從中提取出來。
#defining the network
lstm_layer=rnn.BasicLSTMCell(n_hidden,forget_bias=1)
outputs,_=rnn.static_rnn(lstm_layer,input,dtype="float32")
我們只考慮最後一個時間步的輸入,從中生成預測。
#converting last output of dimension [batch_size,num_units] to [batch_size,n_classes] by out_weight multiplication
prediction=tf.matmul(outputs[-1],out_weights)+out_bias
定義損失函式、最佳化器和準確率。
#loss_function
loss=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction,labels=y))#optimization
opt=tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)#model evaluation
correct_prediction=tf.equal(tf.argmax(prediction,1),tf.argmax(y,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
現在我們已經完成定義,可以開始運行了。
#initialize variables
init=tf.global_variables_initializer()with tf.Session() as sess:
sess.run(init)
iter=1while iter<800:
batch_x,batch_y=mnist.train.next_batch(batch_size=batch_size)
batch_x=batch_x.reshape((batch_size,time_steps,n_input))
sess.run(opt, feed_dict={x: batch_x, y: batch_y})if iter %10==0:
acc=sess.run(accuracy,feed_dict={x:batch_x,y:batch_y})
los=sess.run(loss,feed_dict={x:batch_x,y:batch_y})print("For iter ",iter)print("Accuracy ",acc)print("Loss ",los)print("__________________")
iter=iter+1
需要注意的是我們的每一張影象在開始時被平坦化為 784 維的單一向量,函式 next_batch(batch_size) 必須返回這些 784 維向量的 batch_size 批次數。因此它們的形態要被改造成 [batch_size,time_steps,n_input],從而可以被我們的佔位符接受。我們還可以計算模型的準確率:
#calculating test accuracy
test_data = mnist.test.images[:128].reshape((-1, time_steps, n_input))
test_label = mnist.test.labels[:128]print("Testing Accuracy:", sess.run(accuracy, feed_dict={x: test_data, y: test_label}))
在執行的時候,模型的測試準確率為 99.21%。