Tensorflow(2):MNIST識別自己手寫的數字--進階篇(CNN)

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/u011389706/article/details/81455750

   本文利用卷積神經網絡(CNN)實現自己手寫的數字識別。主要參考自Tensorflow中文社區官方教程【Minst進階】

1.卷積神經網絡簡介

  卷積神經網絡是一種多層神經網絡,擅長處理圖像特別是大圖像的相關機器學習問題。
  卷積網絡通過一系列方法,將數據量龐大的圖像識別問題不斷降維,最終使其能夠被訓練。CNN最早由Yann LeCun提出並應用在手寫字體識別上(MINST),其提出的網絡稱爲LeNet-5,結構如下:
這裏寫圖片描述
  該網絡由卷積層、池化層、全連接層組成。其中卷積層與池化層配合,組成多個卷積組,逐層提取特徵,最終通過若干個全連接層完成分類。

 1.1 卷積(Convolutions)

  自然圖像有其固有特性,也就是說,圖像的一部分的統計特性與其他部分是一樣的,這也意味着我們在這一部分學習的特徵也能用在另一部分上。
這裏寫圖片描述
  總結下convolution的處理過程:
  假設給定了r * c的大尺寸圖像,將其定義爲xlarge。首先通過從大尺寸圖像中抽取的a * b的小尺寸圖像樣本xsmall訓練稀疏自編碼,得到了k個特徵(k爲隱含層神經元數量),然後對於xlarge中的每個a*b大小的塊,求激活值fs,然後對這些fs進行卷積。這樣得到(r-a+1)*(c-b+1)*k個卷積後的特徵矩陣。

 1.2 池化(又叫子採樣Subsampling)

  在通過卷積獲得了特徵(features)之後,下一步我們希望利用這些特徵去做分類。理論上講,人們可以把所有解析出來的特徵關聯到一個分類器,例如softmax分類器,但計算量非常大。例如:對於一個96X96像素的圖像,假設我們已經通過8X8個輸入學習得到了400個特徵。而每一個卷積都會得到一個(96 − 8 + 1) * (96 − 8 + 1) = 7921的結果集,由於已經得到了400個特徵,所以對於每個樣例(example)結果集的大小就將達到892 * 400 = 3,168,400 個特徵。這樣學習一個擁有超過3百萬特徵的輸入的分類器是相當不明智的,並且極易出現過度擬合(over-fitting).
這裏寫圖片描述
  所以就有了pooling這個方法,其實也就是把特徵圖像區域的一部分求個均值或者最大值,用來代表這部分區域。如果是求均值就是mean pooling,求最大值就是max pooling。

2.模型訓練及保存

   根據前面的原理,我們搭建CNN網絡對數據進行訓練,訓練數據來自【Yann LeCun’s MNIST page】。該訓練代碼改編自Tensorflow中文社區官方教程【Minst進階】

#-*- coding: UTF-8 -*-
import tensorflow as tf
import input_data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)   # one_hot 編碼 [1 0 0 0]
sess = tf.InteractiveSession()

x = tf.placeholder("float", shape=[None, 784], name='x')  # 輸入
y_ = tf.placeholder("float", shape=[None, 10], name='y_')  # 實際值

# 初始化權重
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)  # 產生正態分佈 標準差0.1
    return tf.Variable(initial)
# 初始化偏置
def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)  # 定義常量
    return tf.Variable(initial)
'''
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
input: 輸入圖像,張量[batch, in_height, in _width, in_channels]
filter: 卷積核, 張量[filter_height, filter _width, in_channels, out_channels]
strides: 步長,一維向量,長度4
padding:卷積方式,'SAME' 'VALID'
'''
# 卷積層
def conv2d(x,W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
'''
tf.nn.max_pool(value, ksize, strides, padding, name=None)
value: 輸入,一般是卷積層的輸出 feature map
ksize: 池化窗口大小,[1, height, width, 1]
strides: 窗口每個維度滑動步長 [1, strides, strides, 1]
padding:和卷積類似,'SAME' 'VALID'
'''
# 池化層
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')  # 最大池化

# 第一層卷積  卷積在每個5*5中算出32個特徵
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)

# 密集連接層  圖片尺寸縮減到了7*7, 本層用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 = tf.placeholder("float", name='keep_prob')
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# 輸出層  最後添加一個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, name='y_conv')

# 訓練和評估模型
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.global_variables_initializer())

saver = tf.train.Saver()

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(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

# 保存模型
saver.save(sess, "E:/MyPython/02MNIST_NN/mnist/minst_cnn_model.ckpt") 

   最後我們保存訓練好的模型到本地,模型的名稱爲minst_cnn_model.ckpt,包括所有訓練好的參數和模型結構

3.數據測試

  我們利用windows自帶的畫圖軟件,鼠標手寫數字,並保存成png圖片(這裏我保存成了270*270像素大小),如下圖所示:


     

  在代碼裏,首先定義函數imageprepare()對圖片數據進行預處理,轉換成28*28像素的灰度圖,並將像素值轉換到[0,1]範圍內。

from PIL import Image
import tensorflow as tf

def imageprepare():
    file_name = 'number28_28/5_270_270.png'
    myimage = Image.open(file_name)
    myimage = myimage.resize((28, 28), Image.ANTIALIAS).convert('L')  #變換成28*28像素,並轉換成灰度圖
    tv = list(myimage.getdata())  # 獲取像素值
    tva = [(255-x)*1.0/255.0 for x in tv]  # 轉換像素範圍到[0 1], 0是純白 1是純黑
    return tva

result = imageprepare()
init = tf.global_variables_initializer()
saver = tf.train.Saver 

with tf.Session() as sess:
    sess.run(init)
    saver = tf.train.import_meta_graph('minst_cnn_model.ckpt.meta')  # 載入模型結構
    saver.restore(sess,  'minst_cnn_model.ckpt')  # 載入模型參數

    graph = tf.get_default_graph()  # 加載計算圖
    x = graph.get_tensor_by_name("x:0")  # 從模型中讀取佔位符張量
    keep_prob = graph.get_tensor_by_name("keep_prob:0")
    y_conv = graph.get_tensor_by_name("y_conv:0")  # 關鍵的一句  從模型中讀取佔位符變量

    prediction = tf.argmax(y_conv, 1)
    predint = prediction.eval(feed_dict={x: [result], keep_prob: 1.0}, session=sess)  # feed_dict輸入數據給placeholder佔位符
    print(predint[0]) # 打印預測結果

  我們利用前面保存的訓練好的模型進行識別,在session裏,加載保存好的模型及其計算圖,同時讀取模型中的佔位符張量。最後將我們待測試的圖片輸入到模型裏,輸出識別結果。

4.識別結果


  

  

  

   根據官方實驗結果,若模型訓練20000次,測試時準確率基本能達到99.2%.

5.注意事項

   若識別準確率不高,一般是由於我們手寫數字和訓練數據相差太大導致的
  一般原因是:(1)自己手動畫的數字線條太細了;(2)畫的有些數字在圖片中的位置沒有位於中心;(3)訓練集是西方的手寫數字,和中國的手寫數字習慣不同。下面是官方的訓練數據中的部分數字。


  在畫圖時,數字效果(畫筆粗細等)儘量和上面訓練集保持一致,就會得到較高的識別率!
  是以爲記!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章