使用TensorFlow搭建神經(jīng)網(wǎng)絡(luò)——手寫數(shù)字識(shí)別
之前又有很長(zhǎng)一段時(shí)間在講理論讯壶,上次實(shí)踐還是用python實(shí)現(xiàn)Logistic regression料仗。那是一個(gè)很有意義的嘗試湾盗,雖然Logistic regression簡(jiǎn)單伏蚊,但是真的親手手動(dòng)實(shí)現(xiàn)并不容易(我指的是在沒有任何框架的加成下),但我們也深刻理解了內(nèi)部的原理格粪,而這么原理是模型再怎么復(fù)雜也不變的躏吊。
但是想構(gòu)建更加復(fù)雜的網(wǎng)絡(luò)氛改,用純python+numpy恐怕就很不容易了,主要是反向傳播比伏,涉及到大量的求導(dǎo)胜卤,十分麻煩。針對(duì)這種痛點(diǎn)赁项,各種深度學(xué)習(xí)框架出現(xiàn)了葛躏,他們基本上都是幫我們自動(dòng)地進(jìn)行反向傳播的過程,我們只用把正向傳播的“圖”構(gòu)建出來即可悠菜。
所以舰攒,今天,我會(huì)介紹如何用TensorFlow這個(gè)深度學(xué)習(xí)最有名的的框架(之一吧悔醋,免得被杠)摩窃,來實(shí)現(xiàn)一個(gè)3層的神經(jīng)網(wǎng)絡(luò),來對(duì)MNIST手寫數(shù)字進(jìn)行識(shí)別芬骄,并且達(dá)到95%以上的測(cè)試集正確率猾愿。
一、TensorFlow的運(yùn)行機(jī)制和基本用法
TensorFlow運(yùn)行機(jī)制:
剛開始接觸TensorFlow的同學(xué)可能會(huì)發(fā)現(xiàn)它有點(diǎn)奇怪账阻,跟我們一般的計(jì)算過程似乎不同蒂秘。
首先我們要明確TensorFlow中的幾個(gè)基本概念:
- Tensor 張量,是向量宰僧、矩陣的延伸材彪,是tf中的運(yùn)算的基本對(duì)象
- operation 操作,簡(jiǎn)稱op琴儿,即加減乘除等等對(duì)張量的操作
- graph 圖段化,由tensor和tensor之間的操作(op)搭建而成
- session 會(huì)話,用于啟動(dòng)圖造成,將數(shù)據(jù)feed到圖中显熏,然后運(yùn)算得到結(jié)果
其他的概念先放一邊,我們先搞清楚上面這幾個(gè)玩意兒的關(guān)系晒屎。
在TF中構(gòu)建一個(gè)神經(jīng)網(wǎng)絡(luò)并訓(xùn)練的過程喘蟆,是這樣的:
先用tensor和op來搭建我們的graph,也就是要定義神經(jīng)網(wǎng)絡(luò)的各個(gè)參數(shù)鼓鲁、變量蕴轨,并給出它們之間是什么運(yùn)算關(guān)系,這樣我們就搭建好了圖(graph)骇吭,可以想象是我們搭建好了一個(gè)管道橙弱。
然后我們啟動(dòng)session(想象成一個(gè)水泵)棘脐,給參數(shù)斜筐、變量初始化,并把我們的訓(xùn)練集數(shù)據(jù)注入到上面構(gòu)建好的圖(graph)中蛀缝,讓我們的數(shù)據(jù)按照我們搭建好的管道去流動(dòng)(flow)顷链,并得到最終的結(jié)果。
一句話,先搭建數(shù)據(jù)流圖在讶,然后啟動(dòng)會(huì)話注入數(shù)據(jù)潭苞。TF自動(dòng)完成梯度下降及相應(yīng)的求導(dǎo)過程。
TensorFlow基本用法:
-
定義變量
一般用下面兩種方法來定義:
w = tf.Variable(<initial-value>, name=<optional-name>)
或者用:
w = tf.get_variable(<name>, <shape>, <initializer>)
我更常用后一種方法真朗,因?yàn)榭梢灾苯又付╥nitializer來賦值此疹,比如我們常用的Xavier-initializer,就可以直接調(diào)用tf.contrib.layers.xavier_initializer(),不用我們自己去寫函數(shù)進(jìn)行初始化遮婶。
-
placeholder
我們一般給X蝗碎、Y定義成一個(gè)placeholder,即占位符旗扑。也就是在構(gòu)建圖的時(shí)候蹦骑,我們X、Y的殼子去構(gòu)建臀防,因?yàn)檫@個(gè)時(shí)候我們還沒有數(shù)據(jù)眠菇,但是X、Y是我們圖的開端袱衷,所以必須找一個(gè)什么來代替捎废。這個(gè)placeholder就是代替真實(shí)的X、Y來進(jìn)行圖的構(gòu)建的致燥,它擁有X登疗、Y一樣的形狀。
等session開啟之后嫌蚤,我們就把真實(shí)的數(shù)據(jù)注入到這個(gè)placeholder中即可辐益。
定義placeholder的方法:
X = tf.placeholder(<dtype>,<shape>,<name>)
-
operation
op就是上面定義的tensor的運(yùn)算。比如我們定義了W和b脱吱,并給X定義了一個(gè)placeholder智政,那么Z和A怎么計(jì)算呢:
Z = tf.matmul(X,W)+b
A = tf.nn.relu(Z)
上面兩個(gè)計(jì)算都屬于op,op的輸入為tensor箱蝠,輸出也為tensor续捂,因此Z猜年、A為兩個(gè)新的tensor。
同樣的疾忍,我們可以定義cost,然后可以定義一個(gè)optimizer來minimize這個(gè)cost(optimizer怎么去minimize cost不用我們操心了床三,我們不用去設(shè)計(jì)內(nèi)部的計(jì)算過程一罩,TF會(huì)幫我們計(jì)算,我們只用指定用什么優(yōu)化器撇簿,去干什么工作即可)聂渊。這里具體就留給今天的代碼實(shí)現(xiàn)了。
-
session
我們構(gòu)建了圖之后四瘫,就知道了cost是怎么計(jì)算的汉嗽,optimizer是如何工作的。
然后我們需要啟動(dòng)圖找蜜,并注入數(shù)據(jù)饼暑。
啟動(dòng)session有兩種形式,本質(zhì)上是一樣的:
sess = tf.Session()
sess.run(<tensor>,<feed_dic>)
...
sess.close()
或者:
with tf.Session() as sess:
sess.run(<tensor>,<feed_dic>)
...
后者就是會(huì)自動(dòng)幫我們關(guān)閉session來釋放資源洗做,不用我們手動(dòng)sess.close()弓叛,因?yàn)檫@個(gè)經(jīng)常被我們忘記。
我們需要計(jì)算什么诚纸,就把相關(guān)的tensor寫進(jìn)<tensor>中去撰筷,計(jì)算圖中的placeholder需要什么數(shù)據(jù),我們就用feed_dic={X:...,Y:...}的方法來傳進(jìn)去畦徘。具體我們下面的代碼實(shí)現(xiàn)部分見毕籽!
上面就是最基本的TensorFlow的原理和用法了,我們下面開始搭建神經(jīng)網(wǎng)絡(luò)井辆!好戲現(xiàn)在開始~
二关筒、開始動(dòng)手,搭建神經(jīng)網(wǎng)絡(luò)杯缺,識(shí)別手寫數(shù)字
我們要面對(duì)的問題是啥呢平委?以前銀行收到支票呀,都要人工去看上面的金額夺谁、日期等等手寫數(shù)字廉赔,支票多了,工作量就很大了匾鸥,而且枯燥乏味蜡塌。那我們就想,能不能用機(jī)器是識(shí)別這些數(shù)字呢勿负?
深度學(xué)習(xí)領(lǐng)域的大佬Yann LeCun(CNN的發(fā)明者)提供了一個(gè)手寫數(shù)字?jǐn)?shù)據(jù)集MNIST馏艾,可以說是深度學(xué)習(xí)的hello world了劳曹。數(shù)字長(zhǎng)這樣:
其中每個(gè)圖片的大小是 28×28,我們的 數(shù)據(jù)集已經(jīng)將圖片給扁平化了琅摩,即由28×28铁孵,壓扁成了784,也就是輸入數(shù)據(jù)X的維度為784.
我們今天就設(shè)計(jì)一個(gè)簡(jiǎn)單的 3-layer-NN房资,讓識(shí)別率達(dá)到95%以上蜕劝。
假設(shè)我們的網(wǎng)絡(luò)結(jié)構(gòu)是這樣的:
第一層 128個(gè)神經(jīng)元,第二層 64個(gè)神經(jīng)元轰异,第三層是 Softmax輸出層岖沛,有 10個(gè)神經(jīng)元,因?yàn)槲覀円R(shí)別的數(shù)組為0~9搭独,共10個(gè)婴削。網(wǎng)絡(luò)結(jié)構(gòu)如下(數(shù)字代表維度):
好了,我們下面一步步地實(shí)現(xiàn):
(1)加載數(shù)據(jù)牙肝,引入相關(guān)的包
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 下面這一行代碼就可以直接從官網(wǎng)下載數(shù)據(jù)唉俗,下載完之后,你應(yīng)該可以在目錄中發(fā)現(xiàn)一個(gè)新文件夾“MNIST_data”
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
下面我們從數(shù)據(jù)集中配椭,把我們的訓(xùn)練集互躬、測(cè)試集都導(dǎo)出:
X_train,Y_train = mnist.train.images,mnist.train.labels
X_test,Y_test = mnist.test.images,mnist.test.labels
# 不妨看看它們的形狀:
print(X_train.shape) # (55000, 784)
print(Y_train.shape) # (55000, 10)
print(X_test.shape) # (10000, 784)
print(Y_test.shape) # (10000, 10)
可以看出,我們的訓(xùn)練樣本有55000個(gè)颂郎,測(cè)試集有10000個(gè)吼渡。
(2)根據(jù)網(wǎng)絡(luò)結(jié)構(gòu),定義各參數(shù)乓序、變量寺酪,并搭建圖(graph)
tf.reset_default_graph() # 這個(gè)可以不用細(xì)究,是為了防止重復(fù)定義報(bào)錯(cuò)
# 給X替劈、Y定義placeholder寄雀,要指定數(shù)據(jù)類型、形狀:
X = tf.placeholder(dtype=tf.float32,shape=[None,784],name='X')
Y = tf.placeholder(dtype=tf.float32,shape=[None,10],name='Y')
# 定義各個(gè)參數(shù):
W1 = tf.get_variable('W1',[784,128],initializer=tf.contrib.layers.xavier_initializer())
b1 = tf.get_variable('b1',[128],initializer=tf.zeros_initializer())
W2 = tf.get_variable('W2',[128,64],initializer=tf.contrib.layers.xavier_initializer())
b2 = tf.get_variable('b2',[64],initializer=tf.zeros_initializer())
W3 = tf.get_variable('W3',[64,10],initializer=tf.contrib.layers.xavier_initializer())
b3 = tf.get_variable('b3',[10],initializer=tf.zeros_initializer())
這里需要說明的有幾點(diǎn)呢:
- 最好給每個(gè)tensor 都取個(gè)名字(name屬性)陨献,這樣報(bào)錯(cuò)的時(shí)候盒犹,我們可以方便地知道是哪個(gè)
- 形狀的定義要一致,比如這里的W的形狀眨业,我們之前在講解某些原理的時(shí)候急膀,使用的是(當(dāng)前層維度,上一層維度),但是 這里我們采用的是(上一層維度龄捡,當(dāng)前層維度),所以分別是(784,128),(128,64),(64,10). 另外卓嫂,X、Y的維度中的None聘殖,是樣本數(shù)晨雳,由于我們同一個(gè)模型不同時(shí)候傳進(jìn)去的樣本數(shù)可能不同行瑞,所以這里可以寫 None,代表可變的餐禁。
- W的初始化血久,可以直接用tf自帶的initializer,但是注意不能用0給W初始化帮非,這個(gè)問題我在之前的“參數(shù)初始化”的文章里面講過氧吐。b可以用0初始化。
接著喜鼓,我們根據(jù)上面的變量,來 計(jì)算網(wǎng)絡(luò)中間的logits(就是我們常用的Z)衔肢、激活值:
A1 = tf.nn.relu(tf.matmul(X,W1)+b1,name='A1')
A2 = tf.nn.relu(tf.matmul(A1,W2)+b2,name='A2')
Z3 = tf.matmul(A2,W3)+b3
為什么我們只用算到Z3就行了呢庄岖,因?yàn)門ensorFlow中,計(jì)算損失有專門的函數(shù)角骤,一般都是直接用Z的值和標(biāo)簽Y的值來計(jì)算隅忿,比如
對(duì)于sigmoid函數(shù),我們有:
tf.nn.sigmoid_cross_entropy_with_logits(logits=,labels=)來計(jì)算邦尊,
對(duì)于Softmax背桐,我們有:
tf.nn.softmax_cross_entropy_with_logits(logits=,labels=)來計(jì)算。
這個(gè)logits蝉揍,就是未經(jīng)激活的Z链峭;labels,就是我們的Y標(biāo)簽又沾。
因此我們?nèi)绾?定義我們的cost呢:
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))
注意弊仪,為什么要用 reduce_mean()函數(shù)呢?因?yàn)榻?jīng)過softmax_cross_entropy_with_logits計(jì)算出來是杖刷,是所有樣本的cost拼成的一個(gè)向量励饵,有m個(gè)樣本,它就是m維滑燃,因此我們需要去平均值來獲得一個(gè)整體的cost役听。
定義好了cost,我們就可以 定義optimizer來minimize cost了:
trainer = tf.train.AdamOptimizer().minimize(cost)
也是一句話的事兒表窘,賊簡(jiǎn)單了典予。這里我們采用Adam優(yōu)化器,用它來minimize cost乐严。當(dāng)然熙参,我們可以在AdamOptimizer()中設(shè)置一些超參數(shù),比如leaning_rate麦备,但是這里我直接采用它的默認(rèn)值了孽椰,一般效果也不錯(cuò)昭娩。
至此,我們的整個(gè)計(jì)算圖黍匾,就搭建好了栏渺,從X怎么一步步的加上各種參數(shù),并計(jì)算cost锐涯,以及optimizer怎么優(yōu)化cost磕诊,都以及明確了。接下來纹腌,我們就可以啟動(dòng)session霎终,放水了!
(3)啟動(dòng)圖升薯,注入數(shù)據(jù)莱褒,進(jìn)行迭代
廢話不多說,直接上代碼:
with tf.Session() as sess:
# 首先給所有的變量都初始化(不用管什么意思涎劈,反正是一句必須的話):
sess.run(tf.global_variables_initializer())
# 定義一個(gè)costs列表广凸,來裝迭代過程中的cost,從而好畫圖分析模型訓(xùn)練進(jìn)展
costs = []
# 指定迭代次數(shù):
for it in range(1000):
# 這里我們可以使用mnist自帶的一個(gè)函數(shù)train.next_batch蛛枚,可以方便地取出一個(gè)個(gè)地小數(shù)據(jù)集谅海,從而可以加快我們的訓(xùn)練:
X_batch,Y_batch = mnist.train.next_batch(batch_size=64)
# 我們最終需要的是trainer跑起來,并獲得cost蹦浦,所以我們r(jià)un trainer和cost扭吁,同時(shí)要把X、Y給feed進(jìn)去:
_,batch_cost = sess.run([trainer,cost],feed_dict={X:X_batch,Y:Y_batch})
costs.append(batch_cost)
# 每100個(gè)迭代就打印一次cost:
if it%100 == 0:
print('iteration%d ,batch_cost: '%it,batch_cost)
# 訓(xùn)練完成盲镶,我們來分別看看來訓(xùn)練集和測(cè)試集上的準(zhǔn)確率:
predictions = tf.equal(tf.argmax(tf.transpose(Z3)),tf.argmax(tf.transpose(Y)))
accuracy = tf.reduce_mean(tf.cast(predictions,'float'))
print("Training set accuracy: ",sess.run(accuracy,feed_dict={X:X_train,Y:Y_train}))
print("Test set accuracy:",sess.run(accuracy,feed_dict={X:X_test,Y:Y_test}))
運(yùn)行智末,查看輸出結(jié)果:
iteration0 ,batch_cost: 2.3507476
iteration100 ,batch_cost: 0.32707167
iteration200 ,batch_cost: 0.571893
iteration300 ,batch_cost: 0.2989539
iteration400 ,batch_cost: 0.1347334
iteration500 ,batch_cost: 0.24421218
iteration600 ,batch_cost: 0.13563904
iteration700 ,batch_cost: 0.26415896
iteration800 ,batch_cost: 0.1695988
iteration900 ,batch_cost: 0.17325541
Training set accuracy: 0.9624182
Test set accuracy: 0.9571
嚯,感覺不錯(cuò)徒河!訓(xùn)練很快系馆,不到5秒,已經(jīng)達(dá)到我們的要求了顽照,而且我們僅僅是迭代了1000次啊由蘑。
我們不妨將結(jié)果可視化一下,隨機(jī)抽查一些圖片代兵,然后輸出對(duì)應(yīng)的預(yù)測(cè):
將下列代碼放到上面的session中(不能放在session外部尼酿,否則沒法取出相應(yīng)的值),重新運(yùn)行:
# 這里改了一點(diǎn)上面的預(yù)測(cè)集準(zhǔn)確率的代碼植影,因?yàn)槲覀冃枰李A(yù)測(cè)結(jié)果裳擎,所以這里我們單獨(dú)把Z3的值給取出來,這樣通過分析Z3思币,即可知道預(yù)測(cè)值是什么了鹿响。
z3,acc = sess.run([Z3,accuracy],feed_dict={X:X_test,Y:Y_test})
print("Test set accuracy:",acc)
# 隨機(jī)從測(cè)試集中抽一些圖片(比如第i*10+j張圖片)羡微,然后取出對(duì)應(yīng)的預(yù)測(cè)(即z3[i*10+j]):
fig,ax = plt.subplots(4,4,figsize=(15,15))
fig.subplots_adjust(wspace=0.1, hspace=0.7)
for i in range(4):
for j in range(4):
ax[i,j].imshow(X_test[i*10+j].reshape(28,28))
# 用argmax函數(shù)取出z3中最大的數(shù)的序號(hào),即為預(yù)測(cè)結(jié)果:
predicted_num = np.argmax(z3[i*10+j])
# 這里不能用tf.argmax惶我,因?yàn)樗械膖f操作都是在圖中妈倔,沒法直接取出來
ax[i,j].set_title('Predict:'+str(predicted_num))
ax[i,j].axis('off')
得到結(jié)果:
可見,我們的模型是真的訓(xùn)練出來了绸贡,而且效果不錯(cuò)盯蝴。這個(gè)圖中,右下角的那個(gè)奇怪的“4”都給識(shí)別出來了听怕。唯一有爭(zhēng)議的是第三排第三個(gè)的那個(gè)數(shù)字捧挺,我感覺是4,不過也確實(shí)有點(diǎn)像6尿瞭,結(jié)果模式識(shí)別它為6闽烙。
總的來說還是很棒的,接下來我覺得增大迭代次數(shù)筷厘,迭代它個(gè)10000次鸣峭!然后看更多的圖片(100張圖片)宏所。效果如下:
可見酥艳,準(zhǔn)確率提高到了97%以上!
再展示一下圖片:
至此爬骤,我們的實(shí)驗(yàn)就完成了充石。我們成功地利用TensorFlow搭建了一個(gè)三層神經(jīng)網(wǎng)絡(luò),并對(duì)手寫數(shù)字進(jìn)行了出色的識(shí)別霞玄!
對(duì)于TensorFlow更豐富更相信的使用骤铃,大家可以去TensorFlow中文社區(qū)或者TensorFlow官網(wǎng)了解。這里也推薦大家試試TensorFlow的高度封裝的api——Keras坷剧,也是一個(gè)深度學(xué)習(xí)框架惰爬,它可以更加輕松地搭建一個(gè)網(wǎng)絡(luò)。之后的文章我也會(huì)介紹keras的使用惫企。
歡迎關(guān)注我的專欄:
【DeepLearning學(xué)習(xí)筆記】
和我一起一步步學(xué)習(xí)深度學(xué)習(xí)撕瞧。
專欄其他文章:
【DL筆記1】Logistic回歸:最基礎(chǔ)的神經(jīng)網(wǎng)絡(luò)
【DL筆記2】神經(jīng)網(wǎng)絡(luò)編程原則&Logistic Regression的算法解析
【DL筆記3】一步步親手用python實(shí)現(xiàn)Logistic Regression
【DL筆記4】神經(jīng)網(wǎng)絡(luò)詳解,正向傳播和反向傳播
【DL碎片1】神經(jīng)網(wǎng)絡(luò)參數(shù)初始化的學(xué)問
【DL碎片2】神經(jīng)網(wǎng)絡(luò)中的優(yōu)化算法
【DL碎片3】神經(jīng)網(wǎng)絡(luò)中的激活(Activation)函數(shù)及其對(duì)比
【DL碎片4】深度學(xué)習(xí)中的的超參數(shù)調(diào)節(jié)
【DL碎片5】深度學(xué)習(xí)中的正則化(Regularization))