1. 程式人生 > >Tensorflow —— RNN學習筆記

Tensorflow —— RNN學習筆記

1. 寫在前面

最近在看莫煩PYTHON的Tensorflow教程,個人比較喜歡他的視訊風格,也推薦大家可以去他的部落格[CLICK HERE]進行學習。不過我自己看到RNN部分時遇到了一些麻煩,所以在這裡記錄一下自己的理解,如果你對RNN和LSTM有所疑惑也可以看一下這篇文章。

這篇文章將會結合莫煩PYTHON中使用RNN和MNIST實現手寫數字分類的程式碼對Tensorflow實現RNN的一些細節做一個簡單的解釋和說明。

2. 程式碼講解

2.1 資料讀入

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import
input_data # 讀入資料 mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

2.2 設定引數

# 設定隨機數種子(之後的程式碼會用到隨機初始化)
tf.set_random_seed(1)

# 訓練引數
lr = 0.001              # 學習率
training_iters = 100000 # 迭代次數
batch_size = 128        # 每次迭代處理的照片數量

n_inputs = 28           # 對於一次(最小)學習過程輸入資料的大小
n_steps = 28
# 對於一次(次小)學習過程所包含的學習過程數量 n_hidden_units = 128 # 全連線層(隱藏層)的節點數量 n_classes = 10 # 最終分類結果數(0-9) # 定義整體輸入資料型別 # x <-- 圖片張數 * 每張圖片行數 * 每行列數 x = tf.placeholder(tf.float32, [None, n_steps, n_inputs]) y = tf.placeholder(tf.float32, [None, n_classes])

對上面哪些引數的理解非常重要,如果在這裡的理解出現錯誤很可能導致後面的程式碼看不懂。這裡再做一個詳細的說明:

  • batch_size:我們知道MNIST裡有很多張圖片,batch_size就是我們每次迭代讀入的照片數量。
  • n_inputs:這裡n_inputs的大小可以看作全連線層每個的輸入資料大小(其實在進入RNN前確實需要一個全連線層),因為MNIST中每張圖片的大小為28x28,而在RNN中每個時間點只處理每張圖片的一行,即28個畫素。
  • n_steps:類比n_inputs,這裡n_steps表示每張圖片有28行畫素。

2.3 定義Weights和biases

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]))
}
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後都需要進過一個全連線層,所以這裡用兩個字典定義了全連線層的Weights和biases。

整體來看,圖片的每一行畫素(28個畫素點)進過一個全連線層將資訊儲存到128個結點中,再到進入RNN處理,經過RNN學習後再通過一個全連線層將資訊儲存到10個輸出結點(識別結果)。

2.4 RNN的定義

對RNN定義中程式碼的解讀以註釋的形式在文中說明,程式碼如下:

def RNN(X, weights, biases):
    # 隱藏層定義(資料輸入處理)
    # 輸入資料X為每次迭代的所有圖片資訊
    # 每次迭代包括128張圖片(batch_size == 128)
    # 每張圖片大小為28x28
    # 輸入X的大小為(128, 28, 28)
    # 將X轉化為2維陣列送入全連線層
    # X的大小轉化為(128*28, 28)
    X = tf.reshape(X, [-1, n_inputs])

    # 在隱藏層進行處理
    X_in = tf.matmul(X, weights['in']) + biases['in']

    # 處理完成將X恢復為三維陣列結構
    # X_in的大小為(128, 28, 128)
    X_in = tf.reshape(X_in, [-1, n_steps, n_hidden_units])

    # 至此隱藏層的工作就結束了
    # X從(128, 28, 28) --> (128, 28, 128)
    # 前兩個維度的含義沒有改變
    # 最後一個維度從28變化到了128
    # 也就是隱藏層完成的工作
    # 對圖片每一行的28個畫素資訊進行處理得到的資訊儲存到128個結點中

    #########################################################

    # RNN cell定義
    # basic LSTM Cell
    cell = tf.contrib.rnn.BasicLSTMCell(n_hidden_units)
    # cell中包括兩層資訊(c_state, h_state)
    # c_state 主線state
    # h_state 支線state
    # 關於如上兩部分的解釋將在程式碼後面給予解釋
    init_state = cell.zero_state(batch_size, dtype=tf.float32)

    # RNN的學習結果
    # 因為在輸入X_in中第一維變數是batch_size
    # 所以這裡time_major需要設為False
    # 若X_in中第一維變數是n_step
    # 則time_major設為True
    outputs, final_state = tf.nn.dynamic_rnn(cell, X_in, initial_state=init_state, time_major=False)
    # 這裡outputs的大小為(128, 28, 128)
    # 第一個128 表示一次迭代處理128張圖片
    # 28 表示每張圖片分28步處理(28行)
    # 第二個128 表示學習到圖片資訊的128個結點
    outputs = tf.unstack(tf.transpose(outputs, [1,0,2]))
    # tf.transpose 將outputs大小轉為(28, 128, 128)
    # 即交換第一和第二維度
    # tf.unstack 將outputs轉化為28個二維列表
    # 每個列表的大小為(128, 128)
    # 這裡這樣做是因為我們只需要RNN學習的最後結果而不關心它每一步的過程

    #########################################################

    # 輸出層定義
    # 我們只關心RNN最後一步的學習結果
    # 所以這裡取outputs[-1] 其大小為(128, 128)
    # 第一個128 表示每次迭代處理128張照片
    # 第二個128 表示學習到圖片資訊的128個結點
    results = tf.matmul(outputs[-1], weights['out']) + biases['out']
    # 最後的results 大小為(128, 10)
    # 儲存了一次迭代對128張圖片的最終分類結果

    return results

下面就一副圖片對程式碼做進一步說明:

上圖是RNN中LSTM的詳細過程解釋圖,Xt-1,Xt Xt+1 就對應了每張圖片一行的畫素資訊(28個畫素,在輸入RNN時資訊被儲存到128個結點中)。

對上圖中間放大:

c_stateh_state已經在圖中標明,從圖中可以發現Xt時刻的h_state正好就是該時刻的輸出。對應到程式碼中,就是對一張圖片的一行畫素學習的結果(由128個結點所儲存的資訊組成)。

所以在RNN最後只需要取到對每張圖片最後一行的學習結果即可,因為對最後一行畫素的學習結果是參考了前面27行的學習結果做出的總彙,而c_stateh_state就起到了資訊傳遞和更新的作用。

2.5 訓練過程

# pred 大小為(128 * 10)
# 儲存了每次迭代中128張圖片的分類結果
pred = RNN(x, weights, biases)
# cost儲存分類結果與實際標籤的差異
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
# 訓練過程使用adam方法進行優化
train_op = tf.train.AdamOptimizer(lr).minimize(cost)

# 計算學習準確率
# tf.argmax 返回 pred每一行中最大值的下標
# 即是分類結果中可能性最大的數的下標
correct_pred = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# 訓練
with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)
    step = 0
    while step * batch_size < training_iters:
        # 每次取出batch_size(128)張圖片進行訓練
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # 對取出的圖片資訊進行變形以適應我們的網路
        batch_xs = batch_xs.reshape([batch_size, n_steps, n_inputs])
        sess.run([train_op], feed_dict={
            x: batch_xs,
            y: batch_ys,
        })
        # 每迭代20次輸出訓練結果(準確度)
        if step % 20 == 0:
            print(sess.run(accuracy, feed_dict={
            x: batch_xs,
            y: batch_ys,
            }))
        step += 1

3. 實驗結果

從圖中可以看到,僅僅經過5次迭代(5*128張圖片的學習),分類準確率已經可以達到90%+了,這樣的速度還是非常讓人滿意的。

4. 參考

文章中貼出的程式碼都是來自莫煩PYTHON的教程程式碼(註釋是我自己的理解)。[Github]

如果大家對RNN和LSTM的理解還存在一些問題的話,我會推薦這篇文章[CLICK HERE],本文中也使用了這篇文章中的圖片。

5. 寫在最後

把程式碼中的問題弄清楚時內心的機動驅使我完成了這篇文章,如果大家發現內容中存在的錯誤還請指出。

再次安利[莫煩PYTHON],也非常佩服他能做到現在這樣!