TensorFlow的MNIST手寫數(shù)字分類問題 進(jìn)階篇

2018-08-21 11:49 更新

在本章中,我們將知道構(gòu)建一個(gè) TensorFlow 模型的基本步驟,并將通過這些步驟為 MNIST 構(gòu)建一個(gè)深度卷積神經(jīng)網(wǎng)絡(luò).

TensorFlow 是一個(gè)非常強(qiáng)大的用來做大規(guī)模數(shù)值計(jì)算的庫.其所擅長(zhǎng)的任務(wù)之一就是實(shí)現(xiàn)以及訓(xùn)練深度神經(jīng)網(wǎng)絡(luò).

這個(gè)教程假設(shè)你已經(jīng)熟悉神經(jīng)網(wǎng)絡(luò)和MNIST數(shù)據(jù)集.

安裝

在創(chuàng)建模型之前,我們會(huì)先加載 MNIST 數(shù)據(jù)集,然后啟動(dòng)一個(gè) TensorFlow 的 session.

加載MNIST數(shù)據(jù)

為了方便起見,我們已經(jīng)準(zhǔn)備了一個(gè)腳本來自動(dòng)下載和導(dǎo)入MNIST數(shù)據(jù)集.它會(huì)自動(dòng)創(chuàng)建一個(gè)'MNIST_data'的目錄來存儲(chǔ)數(shù)據(jù).

import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

這里,mnist是一個(gè)輕量級(jí)的類.它以 Numpy 數(shù)組的形式存儲(chǔ)著訓(xùn)練、校驗(yàn)和測(cè)試數(shù)據(jù)集.同時(shí)提供了一個(gè)函數(shù),用于在迭代中獲得 minibatch,后面我們將會(huì)用到.

運(yùn)行 TensorFlow 的 InteractiveSession

Tensorflow 依賴于一個(gè)高效的 C++ 后端來進(jìn)行計(jì)算.與后端的這個(gè)連接叫做 session.一般而言,使用 TensorFlow 程序的流程是先創(chuàng)建一個(gè)圖,然后在 session 中啟動(dòng)它.

這里,我們使用更加方便的InteractiveSession類.通過它,你可以更加靈活地構(gòu)建你的代碼.它能讓你在運(yùn)行圖的時(shí)候,插入一些計(jì)算圖,這些計(jì)算圖是由某些操作(operations)構(gòu)成的.這對(duì)于工作在交互式環(huán)境中的人們來說非常便利,比如使用 IPython.如果你沒有使用InteractiveSession,那么你需要在啟動(dòng) session 之前構(gòu)建整個(gè)計(jì)算圖,然后啟動(dòng)該計(jì)算圖.

import tensorflow as tf
sess = tf.InteractiveSession()

計(jì)算圖

為了在 Python 中進(jìn)行高效的數(shù)值計(jì)算,我們通常會(huì)使用像 NumPy 一類的庫,將一些諸如矩陣乘法的耗時(shí)操作在 Python 環(huán)境的外部來計(jì)算,這些計(jì)算通常會(huì)通過其它語言并用更為高效的代碼來實(shí)現(xiàn).

但遺憾的是,每一個(gè)操作切換回Python環(huán)境時(shí)仍需要不小的開銷.如果你想在 GPU 或者分布式環(huán)境中計(jì)算時(shí),這一開銷更加可怖,這一開銷主要可能是用來進(jìn)行數(shù)據(jù)遷移.

TensorFlow 也是在 Python 外部完成其主要工作,但是進(jìn)行了改進(jìn)以避免這種開銷.其并沒有采用在 Python 外部獨(dú)立運(yùn)行某個(gè)耗時(shí)操作的方式,而是先讓我們描述一個(gè)交互操作圖,然后完全將其運(yùn)行在 Python 外部.這與 Theano 或 Torch 的做法類似.

因此 Python 代碼的目的是用來構(gòu)建這個(gè)可以在外部運(yùn)行的計(jì)算圖,以及安排計(jì)算圖的哪一部分應(yīng)該被運(yùn)行.詳情請(qǐng)查看基本用法中的計(jì)算圖表一節(jié).

構(gòu)建 Softmax 回歸模型

在這一節(jié)中我們將建立一個(gè)擁有一個(gè)線性層的 softmax 回歸模型.在下一節(jié),我們會(huì)將其擴(kuò)展為一個(gè)擁有多層卷積網(wǎng)絡(luò)的 softmax 回歸模型.

占位符

我們通過為輸入圖像和目標(biāo)輸出類別創(chuàng)建節(jié)點(diǎn),來開始構(gòu)建計(jì)算圖.

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

這里的xy并不是特定的值,相反,他們都只是一個(gè)占位符,可以在 TensorFlow 運(yùn)行某一計(jì)算時(shí)根據(jù)該占位符輸入具體的值.

輸入圖片x是一個(gè)二維的浮點(diǎn)數(shù)張量.這里,分配給它的shape[None, 784],其中784是一張展平的 MNIST 圖片的維度.None表示其值大小不定,在這里作為第一個(gè)維度值,用以指代batch的大小,意即x的數(shù)量不定.輸出類別值y_也是一個(gè)二維張量,其中每一行為一個(gè)10維的 one-hot 向量,用于代表對(duì)應(yīng)某一 MNIST 圖片的類別.

雖然placeholdershape參數(shù)是可選的,但有了它,TensorFlow 能夠自動(dòng)捕捉因數(shù)據(jù)維度不一致導(dǎo)致的錯(cuò)誤.

變量

我們現(xiàn)在為模型定義權(quán)重W和偏置b.可以將它們當(dāng)作額外的輸入量,但是 TensorFlow 有一個(gè)更好的處理方式:變量.

一個(gè)變量代表著 TensorFlow 計(jì)算圖中的一個(gè)值,能夠在計(jì)算過程中使用,甚至進(jìn)行修改.

在機(jī)器學(xué)習(xí)的應(yīng)用過程中,模型參數(shù)一般用Variable來表示.

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

我們?cè)谡{(diào)用tf.Variable的時(shí)候傳入初始值.在這個(gè)例子里,我們把Wb都初始化為零向量.W是一個(gè)784x10的矩陣(因?yàn)槲覀冇?84個(gè)特征和10個(gè)輸出值).b是一個(gè)10維的向量(因?yàn)槲覀冇?0個(gè)分類).

變量需要通過 seesion 初始化后,才能在session中使用.這一初始化步驟為,為初始值指定具體值(本例當(dāng)中是全為零),并將其分配給每個(gè)變量,可以一次性為所有變量完成此操作.

sess.run(tf.initialize_all_variables())

類別預(yù)測(cè)與損失函數(shù)

現(xiàn)在我們可以實(shí)現(xiàn)我們的回歸模型了.這只需要一行!我們把向量化后的圖片x和權(quán)重矩陣W相乘,加上偏置b,然后計(jì)算每個(gè)分類的 softmax 概率值.

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

可以很容易的為訓(xùn)練過程指定最小化誤差用的損失函數(shù),我們的損失函數(shù)是目標(biāo)類別和預(yù)測(cè)類別之間的交叉熵.

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

注意,tf.reduce_sum把minibatch里的每張圖片的交叉熵值都加起來了.我們計(jì)算的交叉熵是指整個(gè)minibatch的.

訓(xùn)練模型

我們已經(jīng)定義好模型和訓(xùn)練用的損失函數(shù),那么用 TensorFlow 進(jìn)行訓(xùn)練就很簡(jiǎn)單了.因?yàn)?TensorFlow 知道整個(gè)計(jì)算圖,它可以使用自動(dòng)微分法找到對(duì)于各個(gè)變量的損失的梯度值.TensorFlow 有大量?jī)?nèi)置的優(yōu)化算法 這個(gè)例子中,我們用最速下降法讓交叉熵下降,步長(zhǎng)為0.01.

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

這一行代碼實(shí)際上是用來往計(jì)算圖上添加一個(gè)新操作,其中包括計(jì)算梯度,計(jì)算每個(gè)參數(shù)的步長(zhǎng)變化,并且計(jì)算出新的參數(shù)值.

返回的train_step操作對(duì)象,在運(yùn)行時(shí)會(huì)使用梯度下降來更新參數(shù).因此,整個(gè)模型的訓(xùn)練可以通過反復(fù)地運(yùn)行train_step來完成.

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

每一步迭代,我們都會(huì)加載50個(gè)訓(xùn)練樣本,然后執(zhí)行一次train_step,并通過feed_dictx 和 y_張量占位符用訓(xùn)練訓(xùn)練數(shù)據(jù)替代.

注意,在計(jì)算圖中,你可以用feed_dict來替代任何張量,并不僅限于替換占位符.

評(píng)估模型

那么我們的模型性能如何呢?

首先讓我們找出那些預(yù)測(cè)正確的標(biāo)簽.tf.argmax 是一個(gè)非常有用的函數(shù),它能給出某個(gè) tensor 對(duì)象在某一維上的其數(shù)據(jù)最大值所在的索引值.由于標(biāo)簽向量是由0,1組成,因此最大值1所在的索引位置就是類別標(biāo)簽,比如tf.argmax(y,1)返回的是模型對(duì)于任一輸入x預(yù)測(cè)到的標(biāo)簽值,而 tf.argmax(y_,1) 代表正確的標(biāo)簽,我們可以用 tf.equal 來檢測(cè)我們的預(yù)測(cè)是否真實(shí)標(biāo)簽匹配(索引位置一樣表示匹配).

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

這里返回一個(gè)布爾數(shù)組.為了計(jì)算我們分類的準(zhǔn)確率,我們將布爾值轉(zhuǎn)換為浮點(diǎn)數(shù)來代表對(duì)、錯(cuò),然后取平均值.例如:[True, False, True, True]變?yōu)?code>[1,0,1,1],計(jì)算出平均值為0.75.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最后,我們可以計(jì)算出在測(cè)試數(shù)據(jù)上的準(zhǔn)確率,大概是91%.

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

構(gòu)建一個(gè)多層卷積網(wǎng)絡(luò)

在 MNIST 上只有91%正確率,實(shí)在太糟糕.在這個(gè)小節(jié)里,我們用一個(gè)稍微復(fù)雜的模型:卷積神經(jīng)網(wǎng)絡(luò)來改善效果.這會(huì)達(dá)到大概99.2%的準(zhǔn)確率.雖然不是最高,但是還是比較讓人滿意.

權(quán)重初始化

為了創(chuàng)建這個(gè)模型,我們需要?jiǎng)?chuàng)建大量的權(quán)重和偏置項(xiàng).這個(gè)模型中的權(quán)重在初始化時(shí)應(yīng)該加入少量的噪聲來打破對(duì)稱性以及避免0梯度.由于我們使用的是 ReLU 神經(jīng)元,因此比較好的做法是用一個(gè)較小的正數(shù)來初始化偏置項(xiàng),以避免神經(jīng)元節(jié)點(diǎn)輸出恒為0的問題(dead neurons).為了不在建立模型的時(shí)候反復(fù)做初始化操作,我們定義兩個(gè)函數(shù)用于初始化.

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)

卷積和池化

TensorFlow 在卷積和池化上有很強(qiáng)的靈活性.我們?cè)趺刺幚磉吔??步長(zhǎng)應(yīng)該設(shè)多大?在這個(gè)實(shí)例里,我們會(huì)一直使用 vanilla 版本.我們的卷積使用1步長(zhǎng)(stride size),0邊距(padding size)的模板,保證輸出和輸入是同一個(gè)大小.我們的池化用簡(jiǎn)單傳統(tǒng)的2x2大小的模板做 max pooling.為了代碼更簡(jiǎn)潔,我們把這部分抽象成一個(gè)函數(shù).

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')

第一層卷積

現(xiàn)在我們可以開始實(shí)現(xiàn)第一層了.它由一個(gè)卷積接一個(gè) max pooling 完成.卷積在每個(gè)5x5的 patch 中算出32個(gè)特征.卷積的權(quán)重張量形狀是[5, 5, 1, 32],前兩個(gè)維度是 patch 的大小,接著是輸入的通道數(shù)目,最后是輸出的通道數(shù)目. 而對(duì)于每一個(gè)輸出通道都有一個(gè)對(duì)應(yīng)的偏置量.

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

為了用這一層,我們把x變成一個(gè)4d向量,其第2、第3維對(duì)應(yīng)圖片的寬、高,最后一維代表圖片的顏色通道數(shù)(因?yàn)槭腔叶葓D所以這里的通道數(shù)為1,如果是 rgb 彩色圖,則為3).

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

我們把x_image和權(quán)值向量進(jìn)行卷積,加上偏置項(xiàng),然后應(yīng)用ReLU激活函數(shù),最后進(jìn)行 max pooling.

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

第二層卷積

為了構(gòu)建一個(gè)更深的網(wǎng)絡(luò),我們會(huì)把幾個(gè)類似的層堆疊起來.第二層中,每個(gè)5x5的 patch 會(huì)得到64個(gè)特征.

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)

密集連接層

現(xiàn)在,圖片尺寸減小到7x7,我們加入一個(gè)有1024個(gè)神經(jīng)元的全連接層,用于處理整個(gè)圖片.我們把池化層輸出的張量 reshape 成一些向量,乘上權(quán)重矩陣,加上偏置,然后對(duì)其使用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

為了減少過擬合,我們?cè)谳敵鰧又凹尤?dropout.我們用一個(gè)placeholder來代表一個(gè)神經(jīng)元的輸出在 dropout 中保持不變的概率.這樣我們可以在訓(xùn)練過程中啟用 dropout,在測(cè)試過程中關(guān)閉 dropout. TensorFlow 的tf.nn.dropout操作除了可以屏蔽神經(jīng)元的輸出外,還會(huì)自動(dòng)處理神經(jīng)元輸出值的 scale.所以用 dropout 的時(shí)候可以不用考慮 scale.

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

輸出層

最后,我們添加一個(gè) softmax 層,就像前面的單層 softmax regression 一樣.

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

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

訓(xùn)練和評(píng)估模型

這個(gè)模型的效果如何呢?

為了進(jìn)行訓(xùn)練和評(píng)估,我們使用與之前簡(jiǎn)單的單層 SoftMax 神經(jīng)網(wǎng)絡(luò)模型幾乎相同的一套代碼,只是我們會(huì)用更加復(fù)雜的 ADAM 優(yōu)化器來做梯度最速下降,在feed_dict中加入額外的參數(shù)keep_prob來控制 dropout 比例.然后每100次迭代輸出一次日志.

cross_entropy = -tf.reduce_sum(y_*tf.log(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, "float"))
sess.run(tf.initialize_all_variables())
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})

以上代碼,在最終測(cè)試集上的準(zhǔn)確率大概是99.2%.

至此,我們對(duì)于用TensorFlow快捷地搭建、訓(xùn)練和評(píng)估一個(gè)復(fù)雜一點(diǎn)兒的深度學(xué)習(xí)模型已經(jīng)有了一定的掌握.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)