1. 程式人生 > >深度學習入門——利用卷積神經網路實現MNIST手寫數字識別

深度學習入門——利用卷積神經網路實現MNIST手寫數字識別

MNIST(Modified National Institute of Standards and Technology)資料庫是一個大型手寫數字資料庫,通常用於訓練各種影象處理系統。該資料庫還廣泛用於機器學習領域的培訓和測試。它是通過重新打亂來自NIST原始資料集的樣本而建立的。創作者認為,因為NIST的訓練資料集來自美國人口普查局的員工,而測試資料集來自美國高中學生,這不是非常適合於機器學習實驗。此外,來自NIST的黑白影象被歸一化以適合28x28畫素的邊界框和抗鋸齒,並引入了灰度級。

MNIST資料庫包含60,000個訓練影象和10,000個測試影象。訓練集的一半和測試集的一半來自NIST的訓練資料集,而訓練集的另一半和測試集的另一半來自NIST的測試資料集。 有許多關於試圖實現最低錯誤率的科學論文 ; 一篇論文使用卷積神經網路的分層系統,設法在MNIST資料庫上獲得0.23%的錯誤率。

同時,他也被Kaggle設立為入門級機器學習競賽Digit Recognizer(Learn computer vision fundamentals with the famous MNIST data),可謂是計算機視覺中的”hello world“資料集。

載入MNIST資料

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

使用單層神經網路

佔位符

我們通過為輸入影象和目標輸出類建立節點來開始構建計算圖。

x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])

輸入影象x將由浮點數的二維張量組成。這裡我們給它賦予一個[None,784]的大小,其中784是一個影象的28×28個畫素,None表示第一維度可以是任意大小。目標輸出y_也將由二維張量組成,其中每一行是一個唯一的10維向量,指示對應的MNIST影象是哪個數字(0到9)。

變數

我們現在定義權重W,併為我們的模型賦予偏差b

W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

我們將呼叫中的每個引數的初始值傳遞給tf.Variable。 在這種情況下,我們將Wb初始化為全0的張量。 W是784x10矩陣(因為我們有784個輸入特徵和10個輸出),b是10維向量(因為我們有10個數字)。

在會話中使用變數之前,必須使用該會話進行初始化變數。 這一步將已經指定的初始值(在這裡,張量全零),分配給每個變數。 這可以一次完成所有變數的賦值:

sess.run(tf.global_variables_initializer())

理論輸出為

y = tf.matmul(x,W) + b

在以後的訓練中要儘量減少損失函式的值。 定義損失函式是目標和應用於模型預測的softmax啟用函式之間的交叉熵。 

cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))

請注意,tf.nn.softmax_cross_entropy_with_logits在應用softmax啟用函式的交叉熵代價函式,並在所有類中進行求和,tf.reduce_mean表示取這些和的平均值。

訓練模型

現在我們已經定義了模型和訓練損失函式,之後使用梯度下降法,設定步長為0.5來最小化交叉熵。

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

執行train_step時返回的引數會用於下一步優化。 因此,訓練模型可以通過重複執行train_step來完成。

for _ in range(1000):
  batch = mnist.train.next_batch(100)
  train_step.run(feed_dict={x: batch[0], y_: batch[1]})

每次訓練迭代中載入100個訓練樣例。 然後我們執行train_step操作,使用feed_dict將訓練樣例中的佔位符張量xy_替換。 

準確度

可以使用tf.equal來檢查我們的預測是否符合事實。

correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

之後將布林值轉換為浮點數,然後取平均值。 例如,[True,False,True,True]將變成[1,0,1,1],這將變為0.75

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

最後這種方法準確率大約為92%。

print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

全部程式碼

#出自《21個專案玩轉深度學習》
# coding:utf-8
# 匯入tensorflow。
# 這句import tensorflow as tf是匯入TensorFlow約定俗成的做法,請大家記住。
import tensorflow as tf
# 匯入MNIST教學的模組
from tensorflow.examples.tutorials.mnist import input_data
# 與之前一樣,讀入MNIST資料
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

# 建立x,x是一個佔位符(placeholder),代表待識別的圖片
x = tf.placeholder(tf.float32, [None, 784])

# W是Softmax模型的引數,將一個784維的輸入轉換為一個10維的輸出
# 在TensorFlow中,變數的引數用tf.Variable表示
W = tf.Variable(tf.zeros([784, 10]))
# b是又一個Softmax模型的引數,我們一般叫做“偏置項”(bias)。
b = tf.Variable(tf.zeros([10]))

# y=softmax(Wx + b),y表示模型的輸出
y = tf.nn.softmax(tf.matmul(x, W) + b)

# y_是實際的影象標籤,同樣以佔位符表示。
y_ = tf.placeholder(tf.float32, [None, 10])

# 至此,我們得到了兩個重要的Tensor:y和y_。
# y是模型的輸出,y_是實際的影象標籤,不要忘了y_是獨熱表示的
# 下面我們就會根據y和y_構造損失

# 根據y, y_構造交叉熵損失
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y)))

# 有了損失,我們就可以用隨機梯度下降針對模型的引數(W和b)進行優化
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# 建立一個Session。只有在Session中才能執行優化步驟train_step。
sess = tf.InteractiveSession()
# 執行之前必須要初始化所有變數,分配記憶體。
tf.global_variables_initializer().run()
print('start training...')

# 進行1000步梯度下降
for _ in range(1000):
    # 在mnist.train中取100個訓練資料
    # batch_xs是形狀為(100, 784)的影象資料,batch_ys是形如(100, 10)的實際標籤
    # batch_xs, batch_ys對應著兩個佔位符x和y_
    batch_xs, batch_ys = mnist.train.next_batch(100)
    # 在Session中執行train_step,執行時要傳入佔位符的值
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

# 正確的預測結果
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
# 計算預測準確率,它們都是Tensor
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 在Session中執行Tensor可以得到Tensor的值
# 這裡是獲取最終模型的正確率
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))  # 0.9185

構建一個多層卷積神經網路

在MNIST上獲得92%的準確性並不是很高。下面使用卷積神經網路。 這將達到約99.2%的準確性 。

權重初始化

要建立這個模型需要建立很多權重和偏差。 一般應該用少量的噪聲初始化權重,以防止對稱性破壞,並防止0梯度。 由於使用的是ReLU神經元,為了避免“死神經元”,初始化這些神經元是一個很好的做法。 

def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)

def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)

卷積和池化

卷積設定步幅大小為1,並在周圍填充零,以便輸出與輸入大小相同。 池化為2x2的max pooling。 為程式碼更清晰將這些操作抽象為函式。

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')

第一卷積層

卷積將為每個5x5 patch計算32個特徵。 它的權重張量是[5,5,1,32]的形狀。 前兩個維度是patch大小,下一個是輸入通道的數量,最後一個是輸出通道的數量。 每個輸出通道還會有帶有一個偏差向量的分量。

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

為了應用該層首先將x重塑為4維張量,第二維和第三維對應於影象的寬度和高度,並且最後一個維度對應於色彩通道的數量。

x_image = tf.reshape(x, [-1, 28, 28, 1])

然後將x_image與權重張量進行卷積,加上偏差,應用ReLU函式,最後使用max pooling。 max_pool_2x2方法將影象大小減小到14x14。

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

第二卷積層

 第二層將為每個5x5 patch有64個特徵。

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

密集連線層

現在影象尺寸已經減小到7x7,下面新增一個1024個神經元的全連線圖層,以允許在整個影象上進行處理。 我們將pooling層中的張量重塑為一批向量,乘以權重矩陣,新增一個偏差,並應用一個ReLU。

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

Dropout

為了減少過擬合,在輸出層之前應用dropout。 建立一個佔位符,用於在dropout期間保持神經元輸出的概率。 這可以讓在訓練過程中關閉dropout,並在測試過程中將其關閉。 TensorFlow的tf.nn.dropout可以自動處理縮放神經元輸出和掩蔽它們,所以dropout只是在沒有任何附加縮放的情況下工作。

keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

輸出層

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

訓練和評估模型

不同之處在於:

  1. 我們將用更復雜的ADAM優化器替代最陡的梯度下降優化器。
  2. 我們將在feed_dict中包含附加引數keep_prob來控制丟失率。
  3. 我們將在訓練過程中每100次迭代新增一次記錄。

使用tf.Session而不是tf.InteractiveSession可以更好地分離了建立圖(模型說明)的過程和評估圖(模型擬合)的過程。 它通常使更清晰的程式碼。

tf.Session是在一個塊內建立的,所以一旦塊退出,它就會被自動銷燬。

cross_entropy = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
      train_accuracy = accuracy.eval(feed_dict={
          x: batch[0], y_: batch[1], keep_prob: 1.0})
      print('step %d, training accuracy %g' % (i, train_accuracy))
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

  print('test accuracy %g' % accuracy.eval(feed_dict={
      x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

執行此程式碼後的最終測試集精度應該約為99.2%。

卷積神經網路處理的全部程式碼

# coding: utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)


def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)


def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                          strides=[1, 2, 2, 1], padding='SAME')


if __name__ == '__main__':
    mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
    x = tf.placeholder(tf.float32, [None, 784])
    y_ = tf.placeholder(tf.float32, [None, 10])

    x_image = tf.reshape(x, [-1, 28, 28, 1])

    # 第一層卷積層
    W_conv1 = weight_variable([5, 5, 1, 32])
    b_conv1 = bias_variable([32])
    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)

    # 第二層卷積層
    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = bias_variable([64])
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)

    # 全連線層,輸出為1024維的向量
    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])
    h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
    # 使用Dropout,keep_prob是一個佔位符,訓練時為0.5,測試時為1
    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

    # 把1024維的向量轉換成10維,對應10種數字
    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])
    y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2


    cross_entropy = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
    # 同樣定義train_step
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

    # 定義測試的準確率
    correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    # 建立Session和變數初始化
    sess = tf.InteractiveSession()
    sess.run(tf.global_variables_initializer())

    # 訓練20000步
    for i in range(20000):
        batch = mnist.train.next_batch(50)
        # 每100步報告一次在驗證集上的準確度
        if i % 100 == 0:
            train_accuracy = accuracy.eval(feed_dict={
                x: batch[0], y_: batch[1], keep_prob: 1.0})
            print("step %d, training accuracy %g" % (i, train_accuracy))
        train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

    # 訓練結束後報告在測試集上的準確度
    print("test accuracy %g" % accuracy.eval(feed_dict={
        x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

參考

21個專案玩轉深度學習