CNN卷積神經網絡_MNIST手寫數字識別代碼實現


環境:Win8.1 TensorFlow1.0.0

軟件:Anaconda3 (集成Python3及開發環境)

TensorFlow安裝:pip install tensorflow (CPU版) pip install tensorflow-gpu (GPU版)

 

TensorFlow是一個非常強大的用來做大規模數值計算的庫。其所擅長的任務之一就是實現以及訓練深度神經網絡。

在博文中,將構建一個TensorFlow模型的基本步驟,並將通過這些步驟為MNIST構建一個深度卷積神經網絡。

 

加載MNIST數據

為了方便起見,准備一個腳本來自動下載和導入MNIST數據集。它會自動創建一個'MNIST_data'的目錄來存儲數據。

 

 
import tensorflow.examples.tutorials.mnist.input_data as input_data

 

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

 

運行TensorFlow的Session

Tensorflow依賴於一個高效的C++后端來進行計算。與后端的這個連接叫做session。一般而言,使用TensorFlow程序的流程是先創建一個圖,然后在session中啟動它。

 

import tensorflow as tf
sess = tf.Session()
sess.run(tf.global_variables_initializer())

 

 

構建Softmax 回歸模型

建立一個擁有一個線性層的softmax回歸模型。

 

占位符

通過為輸入圖像和目標輸出類別創建節點,來開始構建計算圖。

這里的x和y並不是特定的值,相反,他們都只是一個占位符,可以在TensorFlow運行某一計算時根據該占位符輸入具體的值。
輸入圖片x是一個2維的浮點數張量。這里,分配給它的shape為[None, 784],其中784是一張展平的MNIST圖片的維度。None表示其值大小不定,在這里作為第一個維度值,用以指代batch的大小,意即x的數量不定。輸出類別值y_也是一個2維張量,其中每一行為一個10維的one-hot向量,用於代表對應某一MNIST圖片的類別。
雖然placeholder的shape參數是可選的,但有了它,TensorFlow能夠自動捕捉因數據維度不一致導致的錯誤。

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

 

 

變量

現在為模型定義權重W和偏置b。可以將它們當作額外的輸入量,但是TensorFlow有一個更好的處理方式:變量。一個變量代表着TensorFlow計算圖中的一個值,能夠在計算過程中使用,甚至進行修改。在機器學習的應用過程中,模型參數一般用Variable來表示。

為了創建這個模型,我們需要創建大量的權重和偏置項。這個模型中的權重在初始化時應該加入少量的噪聲來打破對稱性以及避免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) # 常量0.1
    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])

 

我們把x_image和權值向量進行卷積,加上偏置項,然后應用ReLU激活函數,最后進行max pooling。

 

同理,可以建立結構不變,輸入32個通道,輸出64個通道的第二層卷積層。

 

全連接層

現在,圖片尺寸減小到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(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

 

 

輸出層

最后,我們添加一個softmax層,把向量化后的圖片x和權重矩陣W相乘,加上偏置b,然后計算每個分類的softmax概率值。

 

 
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))  # 計算交叉熵

 

 

訓練模型

我們已經定義好模型和訓練用的損失函數,那么用TensorFlow進行訓練就很簡單了。因為TensorFlow知道整個計算圖,它可以使用自動微分法找到對於各個變量的損失的梯度值。TensorFlow有大量內置的優化算法 這個例子中,我們用最速下降法讓交叉熵下降,步長為0.0001.

這一行代碼實際上是用來往計算圖上添加一個新操作,其中包括計算梯度,計算每個參數的步長變化,並且計算出新的參數值。
返回的train_step操作對象,在運行時會使用梯度下降來更新參數。因此,整個模型的訓練可以通過反復地運行train_step來完成。

 

train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

 

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

  

每一步迭代,我們都會加載50個訓練樣本,然后執行一次train_step,並通過feed_dict將x 和 y_張量占位符用訓練訓練數據替代。
注意,在計算圖中,你可以用feed_dict來替代任何張量,並不僅限於替換占位符。

 

評估模型

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

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

 

 
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

 

這里返回一個布爾數組。為了計算我們分類的准確率,我們將布爾值轉換為浮點數來代表對、錯,然后取平均值。例如:[True, False, True, True]變為[1,0,1,1],計算出平均值為0.75。

 

至此我們的模型建立完畢。

 

完整代碼:

# -*- coding: utf-8 -*-  
  
import time  
import tensorflow.examples.tutorials.mnist.input_data as input_data  
import tensorflow as tf  
  
'''''  
權重w和偏置b 
初始化為一個接近0的很小的正數 
'''  
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) # 常量0.1  
    return tf.Variable(initial)  
  
''''' 
卷積和池化,卷積步長為1(stride size),0邊距(padding size) 
池化用簡單傳統的2x2大小的模板max pooling 
'''  
def conv2d(x, W):  
    # strides[1,,,1]默認為1,中間兩位為size,padding same為0,保證輸入輸出大小一致  
    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')  
  
# 計算開始時間  
start = time.clock()  
# MNIST數據輸入  
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])  
  
# 第一層,由一個卷積層加一個maxpooling層  
# 卷積核的大小為5x5,個數為32  
# 卷積核張量形狀是[5, 5, 1, 32],對應size,輸入通道為1,輸出通道為32  
# 每一個輸出通道都有一個對應的偏置量  
W_conv1 = weight_variable([5, 5, 1, 32])  
b_conv1 = bias_variable([32])  
# 把x變成一個4d向量,其第2、第3維對應圖片的寬、高,最后一維代表圖片的顏色通道數  
x_image = tf.reshape(x, [-1, 28, 28, 1]) # -1代表None  
# x_image權重向量卷積,加上偏置項,之后應用ReLU函數,之后進行max_polling  
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1)  
h_pool1 = max_pool_2x2(h_conv1)  
  
# 第二層,結構不變,輸入32個通道,輸出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(28/2/2=7),加入有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,測試過程中關閉  
keep_prob = tf.placeholder(tf.float32)  
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)  
  
# 輸出層, 添加softmax層,類別數為10  
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)  
  
# 訓練和評估模型  
''''' 
ADAM優化器來做梯度最速下降,feed_dict加入參數keep_prob控制dropout比例 
'''  
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))  # 計算交叉熵  
# 使用adam優化器來以0.0001的學習率來進行微調  
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))  
  
# 啟動創建的模型,並初始化變量  
sess = tf.Session()  
sess.run(tf.global_variables_initializer())  
  
# 開始訓練模型,循環訓練1000次  
for i in range(1000):  
    batch = mnist.train.next_batch(50) # batch 大小設置為50  
    if i%100 == 0:  
        train_accuracy = accuracy.eval(session=sess,  
                                 feed_dict={x:batch[0], y_:batch[1], keep_prob:1.0})  
        print("step %d, train_accuracy %g" %(i,train_accuracy))  
    # 神經元輸出保持keep_prob為0.5,進行訓練  
    train_step.run(session=sess, feed_dict={x:batch[0], y_:batch[1], keep_prob:0.5})  
  
# 神經元輸出保持keep_prob為1.0,進行測試  
print("test accuracy %g" %accuracy.eval(session=sess,  
                                        feed_dict={x:mnist.test.images, y_:mnist.test.labels, keep_prob:1.0}))  
      
# 計算程序結束時間  
end = time.clock()  
print("running time is %g s" %(end-start))  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM