1. 程式人生 > >TensorFlow學習筆記(七)—— MNIST —— 進階

TensorFlow學習筆記(七)—— MNIST —— 進階

構建一個多層卷積網路

在MNIST上只有91%正確率,實在太糟糕。在這個小節裡,我們用一個稍微複雜的模型:卷積神經網路來改善效果。這會達到大概99.2%的準確率。雖然不是最高,但是還是比較讓人滿意。

權重初始化

為了建立這個模型,我們需要建立大量的權重和偏置項。這個模型中的權重在初始化時應該加入少量的噪聲來打破對稱性以及避免0梯度。由於我們使用的是ReLU神經元,因此比較好的做法是用一個較小的正數來初始化偏置項,以避免神經元節點輸出恆為0的問題(dead neurons)。為了不在建立模型的時候反覆做初始化操作,我們定義兩個函式用於初始化。

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在卷積和池化上有很強的靈活性。我們怎麼處理邊界?步長應該設多大?在這個例項裡,我們會一直使用vanilla版本。我們的卷積使用1步長(stride size),0邊距(padding size)的模板,保證輸出和輸入是同一個大小。我們的池化用簡單傳統的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')

第一層卷積

現在我們可以開始實現第一層了。它由一個卷積接一個max pooling完成。卷積在每個5x5的patch中算出32個特徵。卷積的權重張量形狀是[5, 5, 1, 32],前兩個維度是patch的大小,接著是輸入的通道數目,最後是輸出的通道數目。 而對於每一個輸出通道都有一個對應的偏置量。

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

為了用這一層,我們把x變成一個4d向量,其第2、第3維對應圖片的寬、高,最後一維代表圖片的顏色通道數(因為是灰度圖所以這裡的通道數為1,如果是rgb彩色圖,則為3)。

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

We then convolve x_image with the weight tensor, add the bias, apply the ReLU function, and finally max pool. 我們把x_image和權值向量進行卷積,加上偏置項,然後應用ReLU啟用函式,最後進行max pooling。

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個神經元的全連線層,用於處理整個圖片。我們把池化層輸出的張量reshape成一些向量,乘上權重矩陣,加上偏置,然後對其使用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。我們用一個placeholder來代表一個神經元的輸出在dropout中保持不變的概率。這樣我們可以在訓練過程中啟用dropout,在測試過程中關閉dropout。 TensorFlow的tf.nn.dropout操作除了可以遮蔽神經元的輸出外,還會自動處理神經元輸出值的scale。所以用dropout的時候可以不用考慮scale。

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

輸出層

最後,我們新增一個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)

訓練和評估模型

這個模型的效果如何呢?

為了進行訓練和評估,我們使用與之前簡單的單層SoftMax神經網路模型幾乎相同的一套程式碼,只是我們會用更加複雜的ADAM優化器來做梯度最速下降,在feed_dict中加入額外的引數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})

以上程式碼,在最終測試集上的準確率大概是99.2%。

目前為止,我們已經學會了用TensorFlow快捷地搭建、訓練和評估一個複雜一點兒的深度學習模型。

程式 

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

import tensorflow as tf

sess = tf.InteractiveSession()

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

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

sess.run(tf.initialize_all_variables())

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

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

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

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

# 評估模型
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, "float"))

# 最後,我們可以計算出在測試資料上的準確率,大概是91%。
print(accuracy.eval(feed_dict={x:mnist.test.images, y_:mnist.test.labels}))

"""
    權重初始化
"""
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')

# 第一層卷積
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

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

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)

# 密集連線層
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 = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 輸出層
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)

"""
    訓練和評估模型
"""
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}))

可選參考說明 

流程講解

大致流程分為三步:  1、構建CNN網路結構;  2、構建loss function,配置尋優器;  3、訓練、測試。

神經網路總體結構概覽:

本教程中使用了兩個卷積層+池化層,最後接上兩個全連線層。  第一層卷積使用32個3x3x1的卷積核,步長為1,邊界處理方式為“SAME”(卷積的輸入和輸出保持相同尺寸),激發函式為Relu,後接一個2x2的池化層,方式為最大化池化;  第二層卷積使用50個3x3x32的卷積核,步長為1,邊界處理方式為“SAME”,激發函式為Relu, 後接一個2x2的池化層,方式為最大化池化;  第一層全連線層:使用1024個神經元,激發函式依然是Relu。  第二層全連線層:使用10個神經元,激發函式為softmax,用於輸出結果。

程式碼

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
#讀取資料
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
sess=tf.InteractiveSession()
#構建cnn網路結構
#自定義卷積函式(後面卷積時就不用寫太多)
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')
#設定佔位符,尺寸為樣本輸入和輸出的尺寸
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])

#設定第一個卷積層和池化層
w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))
b_conv1=tf.Variable(tf.constant(0.1,shape=[32]))
h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)

#設定第二個卷積層和池化層
w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1))
b_conv2=tf.Variable(tf.constant(0.1,shape=[50]))
h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)

#設定第一個全連線層
w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1))
b_fc1=tf.Variable(tf.constant(0.1,shape=[1024]))
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1)

#dropout(隨機權重失活)
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)

#設定第二個全連線層
w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1))
b_fc2=tf.Variable(tf.constant(0.1,shape=[10]))
y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)

#建立loss function,為交叉熵
loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1]))
#配置Adam優化器,學習速率為1e-4
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)

#建立正確率計算表示式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

# 定義saver
saver = tf.train.Saver()

#開始喂資料,訓練
tf.global_variables_initializer().run()
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})
        print("step %d,train_accuracy= %g"%(i,train_accuracy))
    train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})#這裡才開始真正進行訓練計算
# 模型儲存位置
saver.save(sess, ".\\MNIST_data\\model.ckpt")

#訓練之後,使用測試集進行測試,輸出最終結果
print("test_accuracy= %g"% accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1}))

程式碼逐段解析:

1.

#自定義卷積函式(後面就不用寫太多)
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')

卷積步長為1,如要改成步長為2則strides=[1,2,2,1],只有中間兩個是有效的(對於二維圖來說),使用‘SAME’的padding方法(即輸出與輸入保持相同尺寸,邊界處少一兩個畫素則自動補上);池化層的設定也類似,池化尺寸為2X2。

2.

#設定佔位符,尺寸為樣本輸入和輸出的尺寸
x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])

設定輸入輸出的佔位符,佔位符是向一個會話中喂資料的入口,因為TensorFlow的使用中,通過構建計算圖來設計網路,而網路的執行計算則在會話中啟動,這個過程我們無法直接介入,需要通過placeholder來對一個會話進行資料輸入。  佔位符設定好之後,將x變形成為28x28是矩陣形式(tf.reshape()函式)。

3.

#設定第一個卷積層和池化層
w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))
b_conv1=tf.Variable(tf.constant(0.1,shape=[32]))
h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)

#設定第二個卷積層和池化層
w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1))
b_conv2=tf.Variable(tf.constant(0.1,shape=[50]))
h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)

第一層卷積使用3x3x1的卷積核,一共有32 個卷積核,權值使用方差為0.1的截斷正態分佈(指最大值不超過方差兩倍的分佈)來初始化,偏置的初值設定為常值0.1。  第二層卷積和第一層類似,卷積核尺寸為3x3x32(32是通道數,因為上一層使用32個卷積核,所以這一層的通道數就變成了32),這一層一共使用50個卷積核,其他設定與上一層相同。  每一層卷積完之後接上一個2x2的最大化池化操作。

4.

#設定第一個全連線層
w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1))
b_fc1=tf.Variable(tf.constant(0.1,shape=[1024]))
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1)+b_fc1)

#dropout(隨機權重失活)
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)

#設定第二個全連線層
w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1))
b_fc2=tf.Variable(tf.constant(0.1,shape=[10]))
y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)

卷積層之後就是兩個全連線層,第一個全連線層有1024個神經元,先將卷積層得到的2x2輸出展開成一長條,使用Relu啟用函式得到輸出,輸出為1024維。 Dropout:在這一層使用dropout(權值隨機失活),對一些神經元突觸連線進行強制的置零,這個trick可以防止神經網路過擬合。這裡的dropout的保留比例是0.5,即隨機地保留一半權值,刪除另外一半(不要覺得可惜,為了保證在測試集上的效果,這是必須的)。Dropout比例通過placeholder來設定,因為訓練過程中需要dropout,但是在最後的測試過程中,我們又希望使用全部的權值,所以dropout的比例要能夠改變,所以這裡使用placeholder。  第二個全連線層有10個神經元,分別對應0-9這10個數字(沒錯,這一層終於要得到最終的識別結果了),不過與之前的每一層不同的是,這裡使用的啟用函式是Softmax,關於softmax,我的個人理解是softmax是“以指數函式作為核函式的歸一化操作”,softmax與一般歸一化操作不同的是,指數函式能夠放大一個分佈內各個數值的差異,能夠使各個數值的“貧富差距”變大,“兩極分化”現象會更明顯(對同一個分佈進行一般的歸一化得到的分佈和softmax得到的分佈,softmax得到的分佈資訊熵要更大)

5.

#建立loss function,為交叉熵
loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1]))
#配置Adam優化器,學習速率為1e-4
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)

#建立正確率計算表示式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

建立loss function是很重要的一個過程,這裡使用交叉熵來作為loss,交叉熵是用來衡量兩個分佈的相似程度的,兩個分佈越接近,則交叉熵越小。  使用Adam優化器來最小化loss,配置學習速率為1e-4。然後建立正確率的計算表示式(注意,僅僅是建立而已,現在還沒有開始真正的計算),tf.argmax(y_,1),函式用來返回其中最大的值的下標,tf.equal()用來計算兩個值是否相等。tf.cast()函式用來實現資料型別轉換(這裡是轉換為float32),tf.reduce_mean()用來求平均(得到正確率)。

6.

#開始喂資料,訓練
tf.global_variables_initializer().run()
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})
        print "step %d,train_accuracy= %g"%(i,train_accuracy)
    train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})#這裡才開始真正進行訓練計算

接下來就是喂資料了,對網路進行訓練了,首先使用tf.global_variables_initializer().run()初始化所有資料,從mnist訓練資料集中一次取50個樣本作為一組進行訓練,一共進行20000組訓練,每100次就輸出一次該組資料上的正確率。  進行訓練計算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通過feed_dict來對會話輸送訓練資料(以及其他一些想在計算過程中實時調整的引數,比如dropout比例)。  這段程式碼中可以看到,訓練時dropout的保留比例是0.5,測試時的保留比例是1。

7.

#訓練之後,使用測試集進行測試,輸出最終結果
Print "test_accuracy= %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1})

最後輸入測試資料集進行測試驗證,把程式碼跑起來看看結果吧。

接下來就是喂資料了,對網路進行訓練了,首先使用tf.global_variables_initializer().run()初始化所有資料,從mnist訓練資料集中一次取50個樣本作為一組進行訓練,一共進行20000組訓練,每100次就輸出一次該組資料上的正確率。  進行訓練計算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通過feed_dict來對會話輸送訓練資料(以及其他一些想在計算過程中實時調整的引數,比如dropout比例)。  這段程式碼中可以看到,訓練時dropout的保留比例是0.5,測試時的保留比例是1。

執行結果

最後的執行結果長這樣(測試集上的準確率為99.21%,還不錯):

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
step 0,train_accuracy= 0.1
step 100,train_accuracy= 0.76
step 200,train_accuracy= 0.9
step 300,train_accuracy= 0.84
step 400,train_accuracy= 0.98
……
……
……
step 19600,train_accuracy= 1
step 19700,train_accuracy= 0.98
step 19800,train_accuracy= 1
step 19900,train_accuracy= 1
test_accuracy= 0.9921