前言
鑒于國內(nèi)使用卷積網(wǎng)絡(luò)實現(xiàn)MNIST數(shù)據(jù)集識別的源碼(基于Tensorflow實現(xiàn))解析資料很少栅受,本人對最新的代碼進(jìn)行學(xué)習(xí)钩乍,現(xiàn)將筆記心得分享导饲。由于自身水平有限讯嫂,理解不當(dāng)之處請大家批評指正浇垦。本文不會對算法進(jìn)行詳細(xì)介紹炕置,主要針對代碼中所使用的一些函數(shù)定義與用法進(jìn)行解釋,并給出最終運行代碼男韧。本教程假設(shè)讀者已經(jīng)了解MNIST和卷積網(wǎng)絡(luò)讹俊,若未了解,可先看這兩篇我寫的文章:
Tensorflow- MNIST機(jī)器學(xué)習(xí)入門
Tensorflow- CNN卷積神經(jīng)網(wǎng)絡(luò)的MNIST手寫數(shù)字識別
數(shù)據(jù)集
數(shù)據(jù)集是MNIST煌抒,一個入門級的計算機(jī)視覺數(shù)據(jù)集仍劈,它包含各種手寫數(shù)字圖片:
每張圖片包含28X28個像素點,標(biāo)簽即為圖片中的數(shù)字寡壮。
問題
使用MNIST數(shù)據(jù)集進(jìn)行訓(xùn)練贩疙,識別圖片中的手寫數(shù)字(0到9,共10類)况既。
思路
使用一個簡單的CNN網(wǎng)絡(luò)結(jié)構(gòu)如下这溅,括號里邊表示tensor經(jīng)過本層后的輸出shape:
輸入層(28 * 28 * 1)-->卷積層1(28 * 28 * 32)-->pooling層1(14 * 14 * 32)-->卷積層2(14 * 14 * 64)-->池化層2(7 * 7 * 64)-->全連接層(1 * 1024)-->softmax層(10)
函數(shù)說明
在給出完整代碼前,先對幾個的主要函數(shù)中的主要參數(shù)進(jìn)行說明棒仍。也可以先運行代碼悲靴,不懂的部分在看這里的函數(shù)解釋。
tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
隨機(jī)產(chǎn)生一個形狀為shape的服從截斷正態(tài)分布(均值為mean莫其,標(biāo)準(zhǔn)差為stddev)的tensor癞尚。截斷的方法根據(jù)官方API的定義為耸三,如果單次隨機(jī)生成的值偏離均值2倍標(biāo)準(zhǔn)差之外,就丟棄并重新隨機(jī)生成一個新的數(shù)浇揩。
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
-
input:一個形狀為[batch, in_height, in_width, in_channels]的tensor:
- batch:每次batch數(shù)據(jù)的數(shù)量仪壮。
- in_height,in_width:輸入矩陣的高和寬胳徽,如輸入層的圖片是28*28积锅,則in_height和in_width就都為28。
- in_channels:輸入通道數(shù)量养盗。如輸入層的圖片經(jīng)過了二值化缚陷,則通道為1,如果輸入層的圖片是RGB彩色的往核,則通道為3箫爷;再如卷積層1有32個通道,則pooling層1的輸入(卷積層1的輸出)即為32通道铆铆。
-
filterfilter:一個形狀為[filter_height, filter_width, in_channels, out_channels]的tensor:
- filter_height, filter_width:卷積核的高與寬。如卷積層1中的卷積核丹喻,filter_height, filter_width都為28薄货。
- in_channels:輸入通道數(shù)量。
- out_channels:輸出通道的數(shù)量碍论。如輸入數(shù)據(jù)經(jīng)過卷積層1后谅猾,通道數(shù)量從1變?yōu)?2。
strides:滑動窗口(卷積核)的滑動規(guī)則鳍悠,包含4個維度税娜,分別對應(yīng)input的4個維度,即每次在input tensor上滑動時的步長藏研。其中batch和in_channels維度一般都設(shè)置為1敬矩,所以形狀為[1, stride, stride, 1]。
tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
- value:以tf.nn.conv2d()函數(shù)的參數(shù)input理解即可蠢挡。
- ksize:滑動窗口(pool)的大小尺寸弧岳,這里注意這個大小尺寸并不僅僅指2維上的高和寬,ksize的每個維度同樣對應(yīng)input的各個維度(只是大小业踏,不是滑動步長)禽炬,同樣的,batch和in_channels維度多設(shè)置為1勤家。如pooling層1的ksize即為[1, 2, 2, 1]腹尖,即用一個2*2的窗口做pooling。
- strides:同tf.nn.conv2d()函數(shù)的參數(shù)strides伐脖。
tf.nn.dropout(x, keep_prob, noise_shape=None, seed=None, name=None)
- x:輸入tensor热幔。
- keep_probx:每個元素的輸出概率乐设,輸出為原值或0。
代碼
'''
Created on 2017年10月11日
@author: zhoucheng
'''
from tensorflow.examples.tutorials.mnist import input_data
"""自動創(chuàng)建一個'MNIST_data'的目錄來存儲數(shù)據(jù)断凶,將下載的文件解壓伤提,圖像轉(zhuǎn)化為4D向量[index, y, x, depth],
分別存儲在'train', 'validation', 'test'的Dataset中认烁。將標(biāo)簽one-hot編碼后肿男,就可以跟對連續(xù)型特征的
歸一化方法一樣,對每一維特征進(jìn)行歸一化
"""
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
import tensorflow as tf
#Weight Initialization
def weight_variable(shape):
'''tf.truncated_normal(shape, mean, stddev) :shape表示生成張量的維度却嗡,mean是均值舶沛,stddev是標(biāo)
準(zhǔn)差。這個函數(shù)產(chǎn)生正太分布窗价,均值和標(biāo)準(zhǔn)差自己設(shè)定如庭。這是一個截斷的產(chǎn)生正太分布的函數(shù),就是說產(chǎn)生
正太分布的值如果與均值的差值大于兩倍的標(biāo)準(zhǔn)差撼港,那就重新生成坪它。
'''
initial = tf.truncated_normal(shape = shape, stddev=0.1)
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
'''輸入圖片x是一個2維的浮點數(shù)張量。這里帝牡,分配給它的shape為[None, 784]往毡,其中784是一張展平的MNIST圖片
的維度(28X28)。None表示其值大小不定靶溜,在這里作為第一個維度值开瞭, 用以指代batch的大小,意即x的數(shù)量不
定罩息。輸出類別值y_也是一個2維張量嗤详,其中每一行為一個10維的one-hot向量,用于代表對應(yīng)某一MNIST圖片的類別。
'''
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
#Convolution and Pooling
'''TensorFlow在卷積和池化上有很強(qiáng)的靈活性瓷炮。我們怎么處理邊界葱色?步長應(yīng)該設(shè)多大?在這個實例里娘香,我們會一直
使用vanilla版本冬筒。我們的卷積使用1步長(stride size),0邊距(padding size)的模板茅主,通過填充“零”保
證輸出和輸入是同一個大小舞痰。我們的池化用簡單傳統(tǒng)的2x2大小的模板做max pooling。為了代碼更簡潔诀姚,我們把
這部分抽象成一個函數(shù)响牛。
'''
def conv2d(x, W):
return tf.nn.conv2d(input = x, filter = 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')
#First Convolutional Layer
'''現(xiàn)在我們可以開始實現(xiàn)第一層了。它由一個卷積接一個max pooling完成。卷積在每個5x5的patch中算出32個特征
(32個卷積核呀打,可以學(xué)習(xí)32種特征)矢赁。卷積的權(quán)重張量形狀是[5, 5, 1, 32],前兩個維度是patch的大小贬丛,接著
是輸入的通道數(shù)目撩银,最后是輸出的通道數(shù)目。 而對于每一個輸出通道都有一個對應(yīng)的偏置量豺憔。
'''
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
'''為了用這一層额获,我們把x變成一個4d向量,其第2恭应、第3維對應(yīng)圖片的寬抄邀、高,最后一維代表圖片的顏色通道數(shù)(因為
是灰度圖所以這里的通道數(shù)為1昼榛,如果是rgb彩色圖境肾,則為3)。將784X1的向量形式轉(zhuǎn)化成28X28的矩陣形式進(jìn)行卷積運算
'''
x_image = tf.reshape(x, [-1, 28, 28, 1])
'''我們把x_image和權(quán)值向量進(jìn)行卷積胆屿,加上偏置項奥喻,然后應(yīng)用ReLU激活函數(shù),最后進(jìn)行max pooling非迹。同理环鲤,可以
建立結(jié)構(gòu)不變,輸入32個通道彻秆,輸出64個通道的第二層卷積層楔绞。
'''
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)#32 map:14X14
h_pool1 = max_pool_2x2(h_conv1)
#Second Convolutional Layer
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)#64 map:7X7
h_pool2 = max_pool_2x2(h_conv2)
'''現(xiàn)在结闸,圖片尺寸減小到7x7唇兑,我們加入一個有1024個神經(jīng)元的全連接層,用于處理整個圖片桦锄。我們把池化層輸出的張
量reshape成向量扎附,乘上權(quán)重矩陣,加上偏置结耀,然后對其使用ReLU留夜。
'''
#Densely Connected 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])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)#matrix 1X1024
'''為了減少過擬合,我們在輸出層之前加入dropout图甜。我們用一個placeholder來代表一個神經(jīng)元的輸出在dropout中
保持不變的概率碍粥。這樣我們可以在訓(xùn)練過程中啟用dropout,在測試過程中關(guān)閉dropout黑毅。 TensorFlow的
tf.nn.dropout操作除了可以屏蔽神經(jīng)元的輸出外嚼摩,還會自動處理神經(jīng)元輸出值的scale。所以用dropout的時候
可以不用考慮scale。
'''
#dropout
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)#matrix 1X1024
'''最后枕面,我們添加一個softmax層愿卒,把向量化后的圖片x和權(quán)重矩陣W相乘,加上偏置b潮秘,然后計算每個分類的softmax概率值琼开。
'''
#Readout Layer
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2#matrix 1X10
'''可以很容易的為訓(xùn)練過程指定最小化誤差用的損失函數(shù),我們的損失函數(shù)是目標(biāo)類別和預(yù)測類別之間的交叉熵枕荞。
'''
#loss function
"""
softmax_cross_entropy_with_logits返回1X10的向量柜候,代表每個維度預(yù)測越準(zhǔn)確,結(jié)果的值越新虿(別忘了前面還有負(fù)號)改橘,
最后reduce_mean求一個平均,得到我們想要的loss
"""
cross_entropy = tf.reduce_mean( #a number
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
'''我們已經(jīng)定義好模型和訓(xùn)練用的損失函數(shù)玉控,那么用TensorFlow進(jìn)行訓(xùn)練就很簡單了飞主。因為TensorFlow知道整個計算圖,它
可以使用自動微分法找到對于各個變量的損失的梯度值高诺。TensorFlow有大量內(nèi)置的優(yōu)化算法 這個例子中碌识,我們用最速下降
法讓交叉熵下降,步長為0.0001.這一行代碼實際上是用來往計算圖上添加一個新操作虱而,其中包括計算梯度筏餐,計算每個參數(shù)
的步長變化,并且計算出新的參數(shù)值牡拇。
返 回的train_step操作對象魁瞪,在運行時會使用梯度下降來更新參數(shù)。整個模型的訓(xùn)練可以通過反復(fù)地運行train_step來完成惠呼。
'''
#Train the Model
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
'''首先讓我們找出那些預(yù)測正確的標(biāo)簽导俘。tf.argmax 是一個非常有用的函數(shù),它能給出某個tensor對象在某一維上的其數(shù)據(jù)最
大值所在的索引值剔蹋。由于標(biāo)簽向量是由0,1組成旅薄,因此最大值1所在的索引位置就是類別標(biāo)簽,比如tf.argmax(y,1)返回的是
模型對于任一輸入x預(yù)測到的標(biāo)簽值泣崩,而 tf.argmax(y_,1) 代表正確的標(biāo)簽少梁,我們可以用 tf.equal 來檢測我們的預(yù)測是否
真實標(biāo)簽匹配(索引位置一樣表示匹配)。
tf.argmax(input, axis=None, name=None, dimension=None)
此函數(shù)是對矩陣按行或列計算最大值
參數(shù)
input:輸入Tensor
axis:0表示按列矫付,1表示按行
這里返回一個布爾數(shù)組凯沪。為了計算我們分類的準(zhǔn)確率,我們通過tf.cast將布爾值轉(zhuǎn)換為浮點數(shù)來代表對买优、錯妨马,然后取平均值樟遣。
例如:[True, False, True, True]變?yōu)閇1,0,1,1],計算出平均值為0.75身笤。
'''
#Evaluate the Model
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))#a list of boolean
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))#bool->float,然後求均值
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
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({x: batch[0], y_: batch[1], keep_prob: 0.5})
print('test accuracy %g' % accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))