1. 程式人生 > >使用LeNet-5實現mnist手寫數字分類識別 TensorFlow

使用LeNet-5實現mnist手寫數字分類識別 TensorFlow

TensorFlow的學習材料很多,但很少有講得特別詳細,讓小白一看就懂的。我自己總結了cnn實現mnist分類識別的方法,希望能給TensorFlow初學者一些幫助,實測在python3下可以執行。

# -*- coding: utf-8 -*-
# 使用LeNet-5實現mnist手寫數字分類識別
import tensorflow as tf 
from tensorflow.examples.tutorials.mnist import input_data
import os
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "3"

# 獲取mnist資料
data_path = os.path.join('.', 'mnist')
mnist = input_data.read_data_sets(data_path, one_hot=True)  # 一定要加 one_hot
# 註冊預設session 後面操作無需指定session 不同sesson之間的資料是獨立的
sess = tf.InteractiveSession()  # 建立一個session物件,之後的運算都會跑在這個session裡

## 引數初始化
# 構造引數W函式 給一些偏差0.1防止死亡節點,標準差為0.1
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)  # 權重在初始化時應該加入少量的噪聲來打破對稱性以及避免0梯度  truncated_normal函式產生正態分佈
    return tf.Variable(initial)

# 構造偏差b函式 ,給偏置加了一個正值0.1來避免死亡節點
def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

## 定義卷積層和池化層函式
# x是輸入,W為卷積引數 如[5,5,1,30] 前兩個表示卷積核的尺寸 
# 第三個表示通道channel  第四個表示提取多少類特徵
# strides 表示卷積模板移動的步長,中間兩個引數都是1代表不遺漏的劃過圖片每一個點
# padding 表示邊界處理方式這裡的SAME代表給邊界加上padding讓輸出和輸入保持相同尺寸
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

# ksize 使用2x2最大池化即將一個2x2畫素塊變為1x1 最大池化保持畫素最高的點
# stride也橫豎兩個方向為2歩長,如果步長為1 得到尺寸不變的圖片
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

## 定義張量流輸入格式
# reshape變換張量shape 2維張量變4維 [None, 784] to [-1,28,28,1]  784=28*28
# [-1, 28, 28, 1] -1表示樣本數量不固定 28 28為尺寸 1為通道
x = tf.placeholder(tf.float32, [None, 784])  # placeholder 佔位符 此函式可以理解為形參,用於定義過程,在執行的時候再賦具體的值  [None, 784]表示列是784,行不定
y_ = tf.placeholder(tf.float32, [None, 10])  # 來自MNIST的訓練集,每一個圖片所對應的真實值
x_image = tf.reshape(x, [-1, 28, 28, 1])  # 第2、第3維對應圖片的寬、高,最後一維代表圖片的顏色通道數(因為是灰度圖所以這裡的通道數為1,如果是rgb彩色圖,則為3)

## 構建模型
# 第一次卷積池化 卷積層用ReLU啟用函式
# 權重這個值很重要,因為我們深度學習的過程,就是發現特徵,經過一系列訓練,從而得出每一個特徵對結果影響的權重,我們訓練,就是為了得到這個最佳權重值
W_conv1 = weight_variable([5, 5, 1, 32])  # 前兩個維度是patch的大小,接著是輸入的通道數目,最後是輸出的通道數目
b_conv1 = bias_variable([32])  # 對於每一個輸出通道都有一個對應的偏置量  這裡定義32維常量為0.1
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)  # 把x_image和權值向量進行卷積,加上偏置項,然後應用ReLU啟用函式  32*28*28
h_pool1 = max_pool_2x2(h_conv1)  # 最後進行max pooling  32*14*14

# 第二次卷積池化 卷積層用ReLU啟用函式
W_conv2 = weight_variable([5, 5, 32, 64])  # 每個5x5的patch會得到64個特徵
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)  # 64*14*14
h_pool2 = max_pool_2x2(h_conv2)  # 64*7*7

# 全連線層使用ReLU啟用函式  reshape改變張量結構 變成一維
W_fc1 = weight_variable([7 * 7 * 64, 1024])  # 圖片尺寸減小到7x7,我們加入一個有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)  # tf.matmul 矩陣乘法,表示全連線,而不是conv2d

# 為了減輕過擬合使用一個Dropout層,隨機丟掉一些神經元不參與運算
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# softmax層  第二個全連線層 分為十類資料 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)  # tf.nn.softmax 而不是 tf.nn.relu, y_conv是概率

## 儲存模型
# 建立saver的時候可以指明要儲存的tensor,如果不指明,就會全部存下來
# saves a model every 2 hours and maximum 4 latest models are saved.
#saver = tf.train.Saver(max_to_keep=4)
saver = tf.train.Saver()

# 儲存模型的路徑
ckpt_file_path = "./models/mnist"  # models是資料夾,mnist是檔案命名使用的
path = os.path.dirname(os.path.abspath(ckpt_file_path))
if os.path.isdir(path) is False:
    os.makedirs(path)

# loss函式  模型預測的類別概率輸出與真實類別的one hot形式進行cross entropy損失函式的計算。
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))  # 交叉熵  reduction_indices引數,表示函式的處理維度

# 優化演算法Adam函式
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)  #這裡用Adam優化器優化 也可以使用隨機梯度下降

#cross_entropy = -tf.reduce_sum(y_ * tf.log(y_conv),reduction_indices=[1])   #交叉熵
#train_step = tf.train.GradientDescentOptimizer(0.5*1e-4).minimize(cross_entropy)  # 梯度下降法

# accuracy函式 tf.equal(A, B)是對比這兩個矩陣或者向量的相等的元素,如果是相等的那就返回True,反之返回False
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))  # tf.argmax()返回最大數值的下標, 第二個引數 0按列找,1按行找
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  # 準確率  tf.cast是型別轉換函式,tf.float32是轉換目標型別,返回Tensor

tf.global_variables_initializer().run()  # 使用全域性引數初始化器 並呼叫run方法 來進行引數初始化

# 訓練1000次 每次大小為50的mini-batch 每100次訓練檢視訓練結果 用以實時監測模型效能  1000次是iteration,其實只有1個epoch???
for i in range(1000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:  # 每100次驗證一下準確率       
        # feed_dict:一個字典,用來表示tensor被feed的值(聯絡placeholder一起看)  
        train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})  # 評估模型,得出訓練的準確率  
        print("step %d, train_accuracy %g" % (i+1, train_accuracy))  # %g 指數(e) 或浮點數(根據顯示長度)
        
    if i % 200 == 0:
        tf.train.Saver().save(sess, ckpt_file_path, write_meta_graph=True)  # 儲存模型
        
    # batch[0]   [1] 分別指資料維度 和標記維度 將資料傳入定義好的優化器進行訓練
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})  # train_step是定義好的優化器
    
print("test accuracy %g" % accuracy.eval(feed_dict={               # 評估模型,得出測試的準確率
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0
}))