玩玩tensorflow-mnist手寫辨識

玩了一陣子tensorflow,也該來好好玩玩看官網提供的MNIST手寫數字辨識的範例
不知道tensorflow的,請參考上一篇玩玩Tensorflow-從logistic到神經網路

幾個可以拿來當參考的網站
Tensorflow 官方教學
Tensorflow 中文教學
Tensorflow 日文教學

這次想要玩一下卷積神機網路(Convolutional Neural Network),也就是俗稱的CNN
因為CNN牽扯的技術很廣,這邊留一些我實驗時參考的資料,細節我就不再特別留了

下面兩影片看完應該對CNN可以有一定程度的了解
https://www.youtube.com/watch?v=FrKWiRv254g
https://www.youtube.com/watch?v=FmpDIaiMIeA

下面這個網站介紹了使用CNN時候的一些心得
http://danielnouri.org/notes/2014/12/17/using-convolutional-neural-nets-to-detect-facial-keypoints-tutorial/

MNIST手寫辨識

回到正題來,MNIST手寫辨識這個範例,看tensorflow的範例就可以有一定的了解
https://www.tensorflow.org/tutorials/mnist/beginners/

我這邊跟的官網的範例手把手做個一遍

首先先載入MNIST的測試資料,tensorflow已經提供下載的套件了

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# number 1 to 10 data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

##output

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
...

上面程式執行後,執行目錄下會產生一個MNIST_data子目錄並且下載mnist的訓練資料跟測試資料
每一筆資料都是一個28x28x1=784像素的byte資料,後面的x1是圖片只有黑白兩色也就是channel=1
如果圖源是RGB彩色的話就會是28x28x3,這第三維用來表示圖片有幾個"channel"

而在traning set裡面有55000張手寫字的圖片可以拿來訓練
也就是traning dataset是一個55000x784的資料集

而label的部分使用one-hot encoding編碼,也就是假設數字2,他的期望output是[0,0,1,0,0,0,0,0,0,0],如果是數字3,他的期望output是[0,0,0,1,0,0,0,0,0,0],也就是在該數字所代表的index上填上1,其他位置填上0依此類推

輸入資料長相大概了解後,跑跑官方範例來訓練

# Create the model

xs = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(xs, W) + b

# Define loss and optimizer

ys = tf.placeholder(tf.float32, [None, 10])

# The raw formulation of cross-entropy,

#

#   tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.nn.softmax(y)),

#                                 reduction_indices=[1]))

#

# can be numerically unstable.

#

# So here we use tf.nn.softmax_cross_entropy_with_logits on the raw

# outputs of 'y', and then average across the batch.

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y, ys))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

官方範例裡面是使用softmax去做模型訓練,softmax可以看成logistic的多維版本,簡單來說logistic只能回答是非題(類別只有兩個),而softmax則可以回答選擇題(類別超過兩個)
一個例子,假設我今天有A,B,C三個類別,將某筆訓練資料加總丟給softmax之後,output會是A,B,C三個類別可能出現的機率
像是[A:30%, B:30%, C:40%],以這個例子來講C出現的機率最高,可以判定這筆資料最可能是類別C

最後開始試著跑訓練,我這邊是用tensorflow 0.11的版本,跟最新版會有些許不同

sess = tf.Session()
# important step

# tf.initialize_all_variables() no long valid from

# 2017-03-02 if using tensorflow >= 0.12

sess.run(tf.initialize_all_variables())

for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys})
    if i % 50 == 0:
        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(ys, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        print(sess.run(accuracy, feed_dict={xs: mnist.test.images,
                                      ys: mnist.test.labels}))
#Output

0.4075
0.8725
0.8948
0.8926
0.9031
0.8989
0.9074
0.9063
0.9037
0.9102
0.9125
0.9068
0.9179
0.916
0.9174
0.9173
0.9162
0.919
0.9185
0.9184

訓練次數是1000次,做正確率判定的時候,單純的softmax精準度接近到92%已經是極限了
現在號稱用Deep learning做辨識的話精准度可以高達99%,這也就是接下來我想試試看的範例

MNIST手寫辨識-使用CNN

在tensorflow官方的下一個範例裡面就是使用deep learning領域裡面大名鼎鼎卷積神經網路CNN來做訓練
https://www.tensorflow.org/tutorials/mnist/pros/

近年因為正規化方法的突破跟初始化方法的突破發展
越來越多人懂得用多層神經網路來設計深度學習架構來試著學習資料達到更好的表現
而神經網路的架構設計會深深影響表現的效果,如果設計不良的話就算用很多層神經網路有時候表現也不會改善
前人研究了許多神經網路架構來做深層學習,當中有失敗有成功的
現在一些資料都會直接拿前人實驗已經很成功的架構來介紹,不管是用於辨識的CNN網路還是處理序列資料的RNN網路等
這些已經有實績的深度學習網路可以直接幫助我們去處理一些相似的問題

CNN架構網路上有很多資源可以參考
http://cs231n.github.io/convolutional-networks/
個人整理一個CNN的基本架構如下

總括可以分成以下部分

  • 特徵抽取單元
    • Convolutional Layer
    • Activation Layer
    • Pooling Layer
  • 神經網路學習單元
    • Fully-Connected Layer
    • Regularization Layer
    • Output Layer

特徵抽取單元,通常由Convolutional, Activation, Pooling三個層為一組
而特徵抽取單元可以有很多組,以tensorflow教學的例子他用了兩組特徵抽取單元

Convolutional Layer
當輸入是一張圖片的時候,Convolutional Layer的作用是用一個filter去掃描圖片歸納圖片可能的特徵
我們俗稱feature map
他有幾個重要的參數

  • filter掃描圖片的矩形大小, 假設原始圖片是28x28,就可以用一個5x5的filter去掃描
  • stride(步伐): filter每次掃描移動的距離
  • zero-padding: filter的移動有時候會超出圖片原始大小,這時候可以決定要不要填0來幫助filter在圖片中掃描
  • depth: filter的個數,一個filter可以歸納出圖片的一種特徵,可以用很多filter去歸納出圖片的多種可能特徵

Activation Layer
當Convolutional Layer歸納出一些特徵後,還需要一個激勵函數去強化特徵的顯著性
在CNN我們最常用的是ReLU這個激勵函數

Pooling Layer
我稱作壓縮層,當Convolutional Layer歸納出feature map,我們希望可以降低特徵的維度
會再進一步使用pooling layer將feature map比較有顯著性的值抽出來
常用方法有mean或是max兩種,針對局部特徵抽出代表值
而局部的大小在tensorflow給的例子裡面是各個2x2的區域

這邊說的有點籠統,這部分還是建議去我一開始貼的影片看教學會更清楚

而神經網路學習單元就單純多了,他就是一個一般類神經網路學習的階段,當特徵抽取單元歸納出一張圖片的種種特徵之後,我們就將它攤平(flatten)之後送進一個一般神經網路去學習

這裡Fully-Connected Layer可以看作一個神經網路的hidden layer,在選擇性的接上dropout當作Regularization Layer之後就可以送出去output layer
這裡有一點讓我想了一點時間,dropout一般都是接再FC Layer後面,似乎很少人將dropout放在特徵抽取單元裡面
頂多也是放在pooling layer後面
http://stats.stackexchange.com/questions/147850/are-pooling-layers-added-before-or-after-dropout-layers
http://danielnouri.org/notes/2014/12/17/using-convolutional-neural-nets-to-detect-facial-keypoints-tutorial/
後來嘗試了一下MNIST這個例子將dropout放進特徵抽取單元並沒有任何好處,只有放在FC層後面的時候比較能起作用
這部分也是深度學習需要考慮的地方吧

開始實際跑跑tensorflow裡面的例子

一樣先把訓練跟測試資料抓下來

from __future__ import print_function
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# number 1 to 10 data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

然後定義一下等下會用的函數

def compute_accuracy(v_xs, v_ys):
    global prediction
    y_pre = sess.run(prediction, feed_dict={xs: v_xs, keep_prob: 1})
    correct_prediction = tf.equal(tf.argmax(y_pre,1), tf.argmax(v_ys,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    result = sess.run(accuracy, feed_dict={xs: v_xs, ys: v_ys, keep_prob: 1})
    return result

def weight_variable(shape):
    inital = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(inital)

def bias_variable(shape):
    inital = tf.constant(0.1, shape=shape)
    return tf.Variable(inital)

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')

因為CNN裡面的權重通常需要考慮四個維度
[資料量大小,圖片寬度,圖片高度,圖片channel數量]
圖片channel數量指的是圖片是黑白或RGB等等,所以將產生權重數量跟維度的任務交給人為控制
conv2d這邊用來產生Convolutional Layer,strides參數比較特別
頭跟尾巴固定是1,中間兩個才是決定步伐,格式如右[1, strideX, strideY, 1]

max_pool_2x2用來產生pooling layer,設定2x2個像素取一個代表值,這邊使用的是max的方式取值

接下來開始組織訓練模型
定義輸入用的參數

# define placeholder for inputs to network

xs = tf.placeholder(tf.float32, [None, 784]) # 28x28

ys = tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32)
x_image = tf.reshape(xs, [-1, 28, 28, 1])

定義第一個特徵抽取單元
filter的size是5x5,因為是圖片是黑白色所以channel是1,使用32個filter去抽取feature map
output會是28x28x32的feature map,之後透過pooling layer壓縮成14x14x32的feature map

## conv1 layer ##

W_conv1 = weight_variable([5, 5, 1, 32]) #patch 5x5, in channel size 1, out size 32

## pool1 layer ##

b_conv1 = bias_variable([32])
#Combine

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) #output size 28x28x32

h_pool1 = max_pool_2x2(h_conv1) #output size 14x14x32

定義第二個特徵抽取單元
filter的size是5x5,從上一層來的channel會有32個,使用64個filter去重新抽取feature map
output會是14x14x64的feature map,之後透過pooling layer壓縮成7x7x64的feature map

## conv2 layer ##

W_conv2 = weight_variable([5, 5, 32, 64]) #patch 5x5, in channel size 32, out size 64

## pool2 layer ##

b_conv2 = bias_variable([64])
#Combine

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) #output size 14x14x64

h_pool2 = max_pool_2x2(h_conv2) #output size 7x7x64

將抽取出來的feature map攤平(flatten)後送進類神經網路訓練
攤平可以用tf.reshape去達成
架構如下
Input -> reshape -> FC1(hidden layer) -> dropout -> output

## fc1 layer ##

W_fc1 = weight_variable([7*7*64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) #[n_samples, 7,7,64]  => [n_samples, 7*7*64]

h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

## output layer ##

W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

定義訓練方法,這邊一樣使用softmax的cross entropy當作成本函數
使用AdamOptimizer當作最佳化的方法

cross_entropy = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(prediction),
                                              reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

開始訓練

sess = tf.Session()
sess.run(tf.initialize_all_variables())
for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys, keep_prob:0.5})
    if i % 50 == 0:
        print(compute_accuracy(
            mnist.test.images, mnist.test.labels))
#output

0.1212
0.7325
0.8567
0.8942
0.9096
0.9212
0.9311
0.936
0.9412
0.9434
0.9474
0.945
0.951
0.9545
0.9578
0.9573
0.9611
0.9592
0.9625
0.9614

比較一下使用CNN跟單純使用softmax的精准度變化,雖然一開始CNN訓練次數少的時候效果很差
但是隨著訓練時間變長,單純softmax的精准度就上不去了,而CNN的精准度儘管成長幅度不高,其精准度卻也確實在成長

我這邊只有訓練1000次,精准度約來到了96%比起單純softmax的92%已經算改善
官方網站訓練了20000次宣稱精准度可以達到99%
這邊也順便測一下跑兩萬次的準度

for i in range(20001):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={xs: batch_xs, ys: batch_ys, keep_prob:0.5})
    if i % 1000 == 0:
        print(compute_accuracy(
            mnist.test.images, mnist.test.labels))
#output

0.0318
0.9714
0.9811
0.9847
0.9867
0.9884
0.9893
0.99
0.9912
0.991
0.9916
0.9908
0.9901
0.9908
0.992
0.9922
0.9925
0.9912
0.9925
0.9918
0.9921

準度來到99.1~99.2左右就上不去了,這兩萬次用我的mac跑了幾個小時
光靠筆電的CPU跑深度學習還是略感吃力啊

這次實驗我有試著增加特徵抽取單元或是FC Layer 效果都沒有比tensorflow官網給的範例好到哪去
反而單純增加了訓練所花的時間,深度學習網路也不是越深層就越有效果啊

comments powered by Disqus