1. 程式人生 > >Tensorflow(2):MNIST識別自己手寫的數字--進階篇(CNN)

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

   本文利用卷積神經網路(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)訓練集是西方的手寫數字,和中國的手寫數字習慣不同。下面是官方的訓練資料中的部分數字。


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