【DL筆記5】一文上手TensorFlow,並搭建神經網絡實現手寫數字識別

從【DL筆記1】到【DL筆記N】,是我學習深度學習一路上的點點滴滴的記錄,是從Coursera網課、各大博客、論文的學習以及自己的實踐中總結而來。從基本的概念、原理、公式,到用生動形象的例子去理解,到動手做實驗去感知,到著名案例的學習,到用所學來實現自己的小而有趣的想法......我相信,一路看下來,我們可以感受到深度學習的無窮的樂趣,並有興趣和激情繼續鑽研學習。 正所謂 Learning by teaching,寫下一篇篇筆記的同時,我也收穫了更多深刻的體會,希望大家可以和我一同進步,共同享受AI無窮的樂趣。


之前又有很長一段時間在講理論,上次實踐還是用python實現Logistic regression。那是一個很有意義的嘗試,雖然Logistic regression簡單,但是真的親手手動實現並不容易(我指的是在沒有任何框架的加成下),但我們也深刻理解了內部的原理,而這麼原理是模型再怎麼複雜也不變的。 但是想構建更加複雜的網絡,用純python+numpy恐怕就很不容易了,主要是反向傳播,涉及到大量的求導,十分麻煩。針對這種痛點,各種深度學習框架出現了,他們基本上都是幫我們自動地進行反向傳播的過程,我們只用把正向傳播的“圖”構建出來即可。 所以,今天,我會介紹如何用TensorFlow這個深度學習最有名的的框架(之一吧,免得被槓),來實現一個3層的神經網絡,來對MNIST手寫數字進行識別,並且達到95%以上的測試集正確率

一、TensorFlow的運行機制和基本用法

TensorFlow運行機制:

剛開始接觸TensorFlow的同學可能會發現它有點奇怪,跟我們一般的計算過程似乎不同。

首先我們要明確TensorFlow中的幾個基本概念:

  • Tensor 張量,是向量、矩陣的延伸,是tf中的運算的基本對象
  • operation 操作,簡稱op,即加減乘除等等對張量的操作
  • graph 圖,由tensor和tensor之間的操作(op)搭建而成
  • session 會話,用於啓動圖,將數據feed到圖中,然後運算得到結果

其他的概念先放一邊,我們先搞清楚上面這幾個玩意兒的關係。

在TF中構建一個神經網絡並訓練的過程,是這樣的: 先用tensor和op來搭建我們的graph,也就是要定義神經網絡的各個參數、變量,並給出它們之間是什麼運算關係,這樣我們就搭建好了圖(graph),可以想象是我們搭建好了一個管道。

然後我們啓動session(想象成一個水泵),給參數、變量初始化,並把我們的訓練集數據注入到上面構建好的圖(graph)中,讓我們的數據按照我們搭建好的管道去流動(flow),並得到最終的結果。

一句話,先搭建數據流圖,然後啓動會話注入數據。TF自動完成梯度下降及相應的求導過程。

TensorFlow基本用法:
  1. 定義變量 一般用下面兩種方法來定義: w = tf.Variable(<initial-value>, name=<optional-name>) 或者用: w = tf.get_variable(<name>, <shape>, <initializer>)

我更常用後一種方法,因爲可以直接指定initializer來賦值,比如我們常用的Xavier-initializer,就可以直接調用tf.contrib.layers.xavier_initializer(),不用我們自己去寫函數進行初始化。

  1. placeholder 我們一般給X、Y定義成一個placeholder,即佔位符。也就是在構建圖的時候,我們X、Y的殼子去構建,因爲這個時候我們還沒有數據,但是X、Y是我們圖的開端,所以必須找一個什麼來代替。這個placeholder就是代替真實的X、Y來進行圖的構建的,它擁有X、Y一樣的形狀。 等session開啓之後,我們就把真實的數據注入到這個placeholder中即可。 定義placeholder的方法: X = tf.placeholder(<dtype>,<shape>,<name>)
  2. operation op就是上面定義的tensor的運算。比如我們定義了W和b,並給X定義了一個placeholder,那麼Z和A怎麼計算呢: Z = tf.matmul(X,W)+b A = tf.nn.relu(Z) 上面兩個計算都屬於op,op的輸入爲tensor,輸出也爲tensor,因此Z、A爲兩個新的tensor。 同樣的,我們可以定義cost,然後可以定義一個optimizer來minimize這個cost(optimizer怎麼去minimize cost不用我們操心了,我們不用去設計內部的計算過程,TF會幫我們計算,我們只用指定用什麼優化器,去幹什麼工作即可)。這裏具體就留給今天的代碼實現了。
  3. session 我們構建了圖之後,就知道了cost是怎麼計算的,optimizer是如何工作的。 然後我們需要啓動圖,並注入數據。 啓動session有兩種形式,本質上是一樣的: sess = tf.Session() sess.run(<tensor>,<feed_dic>) ... sess.close() 或者: with tf.Session() as sess: sess.run(<tensor>,<feed_dic>) ... 後者就是會自動幫我們關閉session來釋放資源,不用我們手動sess.close(),因爲這個經常被我們忘記。

我們需要計算什麼,就把相關的tensor寫進中去,計算圖中的placeholder需要什麼數據,我們就用feed_dic={X:…,Y:…}的方法來傳進去。具體我們下面的代碼實現部分見!

上面就是最基本的TensorFlow的原理和用法了,我們下面開始搭建神經網絡!好戲現在開始~

二、開始動手,搭建神經網絡,識別手寫數字

我們要面對的問題是啥呢?以前銀行收到支票呀,都要人工去看上面的金額、日期等等手寫數字,支票多了,工作量就很大了,而且枯燥乏味。那我們就想,能不能用機器是識別這些數字呢?

深度學習領域的大佬Yann LeCun(CNN的發明者)提供了一個手寫數字數據集MNIST,可以說是深度學習的hello world了。數字長這樣:

其中每個圖片的大小是 28×28,我們的 數據集已經將圖片給扁平化了,即由28×28,壓扁成了784,也就是輸入數據X的維度爲784.

我們今天就設計一個簡單的 3-layer-NN,讓識別率達到95%以上。 假設我們的網絡結構是這樣的: 第一層 128個神經元,第二層 64個神經元,第三層是 Softmax輸出層,有 10個神經元,因爲我們要識別的數組爲0~9,共10個。網絡結構如下(數字代表維度):

好了,我們下面一步步地實現:

(1)加載數據,引入相關的包

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 下面這一行代碼就可以直接從官網下載數據,下載完之後,你應該可以在目錄中發現一個新文件夾“MNIST_data”
from tensorflow.examples.tutorials.mnist import input_data 
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

下面我們從數據集中,把我們的訓練集、測試集都導出:

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)

可以看出,我們的訓練樣本有55000個,測試集有10000個。

(2)根據網絡結構,定義各參數、變量,並搭建圖(graph)

tf.reset_default_graph() # 這個可以不用細究,是爲了防止重複定義報錯

# 給X、Y定義placeholder,要指定數據類型、形狀:
X = tf.placeholder(dtype=tf.float32,shape=[None,784],name='X')
Y = tf.placeholder(dtype=tf.float32,shape=[None,10],name='Y')

# 定義各個參數:
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())

這裏需要說明的有幾點呢:

  1. 最好給每個tensor 都取個名字(name屬性),這樣報錯的時候,我們可以方便地知道是哪個
  2. 形狀的定義要一致,比如這裏的W的形狀,我們之前在講解某些原理的時候,使用的是(當前層維度,上一層維度),但是 這裏我們採用的是(上一層維度,當前層維度),所以分別是(784,128),(128,64),(64,10). 另外,X、Y的維度中的None,是樣本數,由於我們同一個模型不同時候傳進去的樣本數可能不同,所以這裏可以寫 None,代表可變的
  3. W的初始化,可以直接用tf自帶的initializer,但是注意不能用0給W初始化,這個問題我在之前的“參數初始化”的文章裏面講過。b可以用0初始化。

接着,我們根據上面的變量,來 計算網絡中間的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就行了呢,因爲TensorFlow中,計算損失有專門的函數,一般都是直接用Z的值和標籤Y的值來計算,比如

對於sigmoid函數,我們有: tf.nn.sigmoid_cross_entropy_with_logits(logits=,labels=)來計算,

對於Softmax,我們有: tf.nn.softmax_cross_entropy_with_logits(logits=,labels=)來計算。 這個logits,就是未經激活的Z;labels,就是我們的Y標籤。

因此我們如何 定義我們的cost呢:

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))

注意,爲什麼要用 reduce_mean()函數呢?因爲經過softmax_cross_entropy_with_logits計算出來是,是所有樣本的cost拼成的一個向量,有m個樣本,它就是m維,因此我們需要去平均值來獲得一個整體的cost。

定義好了cost,我們就可以 定義optimizer來minimize cost了:

trainer = tf.train.AdamOptimizer().minimize(cost)

也是一句話的事兒,賊簡單了。這裏我們採用Adam優化器,用它來minimize cost。當然,我們可以在AdamOptimizer()中設置一些超參數,比如leaning_rate,但是這裏我直接採用它的默認值了,一般效果也不錯。

至此,我們的整個計算圖,就搭建好了,從X怎麼一步步的加上各種參數,並計算cost,以及optimizer怎麼優化cost,都以及明確了。接下來,我們就可以啓動session,放水了!

(3)啓動圖,注入數據,進行迭代

廢話不多說,直接上代碼:

with tf.Session() as sess:
    # 首先給所有的變量都初始化(不用管什麼意思,反正是一句必須的話):
    sess.run(tf.global_variables_initializer())

    # 定義一個costs列表,來裝迭代過程中的cost,從而好畫圖分析模型訓練進展
    costs = []

    # 指定迭代次數:
    for it in range(1000):
        # 這裏我們可以使用mnist自帶的一個函數train.next_batch,可以方便地取出一個個地小數據集,從而可以加快我們的訓練:
        X_batch,Y_batch = mnist.train.next_batch(batch_size=64)

        # 我們最終需要的是trainer跑起來,並獲得cost,所以我們run trainer和cost,同時要把X、Y給feed進去:
        _,batch_cost = sess.run([trainer,cost],feed_dict={X:X_batch,Y:Y_batch})
        costs.append(batch_cost)

        # 每100個迭代就打印一次cost:
        if it%100 == 0:
            print('iteration%d ,batch_cost: '%it,batch_cost)

    # 訓練完成,我們來分別看看來訓練集和測試集上的準確率:
    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}))

運行,查看輸出結果:

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

嚯,感覺不錯!訓練很快,不到5秒,已經達到我們的要求了,而且我們僅僅是迭代了1000次啊。

我們不妨將結果可視化一下,隨機抽查一些圖片,然後輸出對應的預測: 將下列代碼放到上面的session中(不能放在session外部,否則沒法取出相應的值),重新運行:

    # 這裏改了一點上面的預測集準確率的代碼,因爲我們需要知道預測結果,所以這裏我們單獨把Z3的值給取出來,這樣通過分析Z3,即可知道預測值是什麼了。
    z3,acc = sess.run([Z3,accuracy],feed_dict={X:X_test,Y:Y_test})
    print("Test set accuracy:",acc)

    # 隨機從測試集中抽一些圖片(比如第i*10+j張圖片),然後取出對應的預測(即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函數取出z3中最大的數的序號,即爲預測結果:
            predicted_num  = np.argmax(z3[i*10+j])        
            # 這裏不能用tf.argmax,因爲所有的tf操作都是在圖中,沒法直接取出來
            ax[i,j].set_title('Predict:'+str(predicted_num))
            ax[i,j].axis('off')

得到結果:

可見,我們的模型是真的訓練出來了,而且效果不錯。這個圖中,右下角的那個奇怪的“4”都給識別出來了。唯一有爭議的是第三排第三個的那個數字,我感覺是4,不過也確實有點像6,結果模式識別它爲6。

總的來說還是很棒的,接下來我覺得增大迭代次數,迭代它個10000次!然後看更多的圖片(100張圖片)。效果如下:

可見,準確率提高到了97%以上! 再展示一下圖片:

至此,我們的實驗就完成了。我們成功地利用TensorFlow搭建了一個三層神經網絡,並對手寫數字進行了出色的識別!


對於TensorFlow更豐富更相信的使用,大家可以去TensorFlow中文社區或者TensorFlow官網瞭解。這裏也推薦大家試試TensorFlow的高度封裝的api——Keras,也是一個深度學習框架,它可以更加輕鬆地搭建一個網絡。之後的文章我也會介紹keras的使用。


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