TensorFlow系列專題(六):實戰項目Mnist手寫數據集識別

編輯 | 安可

出品 | 磐創AI技術團隊

一. 導讀

就像我們在學習一門編程語言時總喜歡把“Hello World!”作爲入門的示例代碼一樣,MNIST手寫數字識別問題就像是深度學習的“Hello World!”。通過這個例子,我們將瞭解如何將數據轉化爲神經網絡所需要的數據格式,以及如何使用TensorFlow搭建簡單的單層和多層的神經網絡。

二. MNIST數據集

MNIST數據集可以從網站http://yann.lecun.com/exdb/mnist/上下載,需要下載的數據集總共有4個文件,其中“train-images-idx3-ubyte.gz”是訓練集的圖片,總共有60000張,“train-labels-idx1-ubyte.gz”是訓練集圖片對應的類標(0~9)。“t10k-images-idx3-ubyte.gz”是測試集的圖片,總共有10000張,“t10k-labels-idx1-ubyte.gz”是測試集圖片對應的類標(0~9)。TensorFlow的示例代碼中已經對MNIST數據集的處理進行了封裝,但是作爲第一個程序,我們希望帶着讀者從數據處理開始做,數據處理在整個機器學習項目中是很關鍵的一個環節,因此有必要在第一個項目中就讓讀者體會到它的重要性。

我們將下載的壓縮文件解壓後會發現數據都是以二進制文件的形式存儲的,以訓練集的圖像數據爲例:

表1 訓練集圖像數據的文件格式

如表1所示,解壓後的訓練集圖像數據“train-images-idx3-ubyte”文件,其前16個字節的內容是文件的基本信息,分別是magic number(又稱爲幻數,用來標記文件的格式)、圖像樣本的數量(60000)、每張圖像的行數以及每張圖像的列數。由於每張圖像的大小是28*28,所以我們從編號0016的字節開始,每次讀取28*28=784個字節,即讀取了一張完整的圖像。我們讀取的每一個字節代表一個像素,取值範圍是[0,255],像素值越接近0,顏色越接近白色,像素值越接近255,顏色越接近黑色。

訓練集類標文件的格式如下:

表2 訓練集類標數據的文件格式

如上表所示,訓練集類標數據文件的前8個字節記錄了文件的基本信息,包括magic number和類標項的數量(60000)。從編號0008的字節開始,每一個字節就是一個類標,類標的取值範圍是[0,9],類標直接標明瞭對應的圖像樣本的真實數值。如圖1所示,我們將部分數據進行了可視化。測試集的圖像數據和類標數據的文件格式與訓練集一樣。

圖1 訓練集圖像數據可視化效果

三. 數據處理

在開始實現神經網絡之前,我們要先準備好數據。雖然MNIST數據集本身就已經處理過了,但是我們還是需要做一些封裝以及簡單的特徵工程。我們定義一個MnistData類用來管理數據:

在“__init__”方法中初始化了“MnistData”類相關的一些參數,其中“train_image_path”和“train_label_path”分別是訓練集數據和類標的文件路徑,“test_image_path”和“test_label_path”分別是測試集數據和類標的文件路徑。

接下來我們要實現“MnistData”類的另一個方法“get_data”,該方法實現了Mnist數據集的讀取以及數據的預處理。

由於Mnist數據是以二進制文件的形式存儲,所以我們需要用到struct模塊來處理文件,uppack_from函數用來解包二進制文件,第42行代碼中,參數“>IIII”指定讀取16個字節的內容,這正好是文件的基本信息部分。其中“>”代表二進制文件是以大端法存儲,“IIII”代表四個int類型的長度,這裏一個int類型佔4個字節。參數“image_file”是堯都區的文件,“image_index”是偏移量。如果要連續的讀取文件內容,每讀取一部分數據後就要增加相應的偏移量。

第51行代碼中,我們對數據進行了歸一化處理,關於歸一化我們在第一章中有介紹。在後面兩節實現神經網絡模型的時候,讀者可以嘗試註釋掉歸一化的這行代碼,比較一下做了歸一化和不做歸一化,模型的效果有什麼差別。

最後,我們要實現一個“get_batch”方法。在訓練模型的時候,我們通常會用訓練集數據訓練多個回合(epoch),每個回合都會用且只用一次訓練集中的每一條數據。因爲我們使用隨機梯度下降的方式來更新參數,所以每個回合中,我們會把訓練集數據分爲多個批次(batch)送進模型中去訓練,每次送進模型的數據量的大小爲“batch_size”。因此,我們需要將數據按“batch_size”進行劃分。

在第68行代碼中,我們使用了“random”模塊的“shuffle”方法對數據進行了“洗牌”,即打亂了數據原來的順序,“shuffle”操作的目的是爲了讓各類樣本數據儘可能混合在一起,從而在模型訓練的過程中,各類樣本都可以對模型的參數變化產生影響。不過需要記住的是,“shuffle”操作並不總是必須的,而且是否可以使用“shuffle”操作也要看具體的數據來定。

到這裏我們已經實現了Mnist數據的讀取和預處理,在後面兩小節的內容裏,我們會分別實現一個單層的神經網絡和一個多層的前饋神經網絡模型,實現Mnist手寫數字的識別問題。

四. 單層隱藏層神經網絡的實現

介紹完MNIST數據集之後,我們現在可以開始動手實現一個神經網絡來解決手寫數字識別的問題了,我們先從一個簡單的兩層(一層隱藏層)神經網絡開始。

本小節所實現的單層神經網絡結構如圖2所示。每張圖片的大小爲[28*28],我們將其轉爲長度爲784的向量作爲網絡的輸入。隱藏層有10個神經元,在這個簡單的神經網絡中我們沒有在隱藏層中使用激活函數。在隱藏層後面我們加了一個Softmax層,用來將隱藏層的輸出直接轉化爲模型的預測結果。

圖2 實現Mnist手寫數字識別的兩層神經網絡結構

接下來我們實現具體的代碼,首先導入上一小節中我們實現的數據處理的類以及TensorFlow的包:

創建一個Session會話,並定義好相關的變量:

“epochs”是我們想要訓練的總輪數,每一輪都會使用訓練集的所有數據去訓練一遍模型。由於我們使用隨機梯度下降方法更新參數,所以不會一次把所有的數據送進模型去訓練,而是按批次訓練,“batch_size”是我們定義的一個批次的數據量的大小,這裏我們設定了100,那麼每個“batch”就會送100個樣本到模型中去訓練,一輪訓練的“batch”數等於總的訓練集數量除以“batch_size”。“learning_rate”是我們定義的學習率,即模型參數更新的速率。

接下來我們定義模型的參數:

第16行代碼定義了輸入樣本的placeholder,第18和第19行代碼定義了該單層神經網絡隱藏層的權重矩陣和偏置項。根據圖3-16所示的網絡結構,輸入向量長度爲784,隱藏層有10個神經元,因此我們定義權重矩陣的大小爲784行10列,偏置項的向量長度爲10。在第24行代碼中,我們先將輸入的樣本數據轉換爲一維的向量,然後進行的運算,計算的結果再經由Softmax計算得到最終的預測結果。

定義完網絡的參數後我們還需要定義損失函數和優化器:

第28行我們定義了交叉熵損失函數,關於交叉熵損失函數在本章第三小節中我們已經做了介紹,“”計算的是一個“batch”的訓練樣本數據的交叉熵,每個樣本數據都有一個值,TensorFlow的“reduce_mean”方法將這個“batch”的數據的交叉熵求了平均值,作爲這個“batch”最終的交叉熵損失值。

第29和30行代碼中,我們定義了一個梯度下降優化器“GradientDescentOptimizer”,並設定了學習率爲“learning_rate”以及優化目標爲“cross_entropy”。

接下來我們還需要實現模型的評估:

“tf.equal()”方法用於比較兩個矩陣或向量相應位置的元素是否相等,相等爲“True”,不等爲“False”。“tf.cast”用於將“True”和“False”轉換爲“1”和“0”,“tf.reduce_mean”對轉換後的數據求平均值,該值即爲模型在測試集上預測結果的準確率。最後,我們實現模型的訓練和預測:

因爲我們的“batch_size”設置爲100,Mnist數據集的訓練數據有60000條,因此我們訓練600個“batch”正好是一輪。第50行代碼中,我們訓練完的模型對測試集數據進行了預測,並輸出了預測的準確率,結果爲0.9228。

五. 多層神經網絡的實現

多層神經網絡的實現也很簡單,我們只需要在上一小節的代碼基礎上對網絡的結構稍作修改即可,我們先來看一下這一小節裏要實現的多層(兩層隱藏層)神經網絡的結構:

圖3 實現Mnist手寫數字識別的多層神經網絡結構

如上圖所示,這裏我們增加了一層隱藏層,實現的是一個三層神經網絡。與上一小節的兩層神經網絡不同的是,除了增加了一層隱藏層,在第一層隱藏層中我們還是用了“Sigmoid”激活函數。

實現三層神經網絡我們只需要在上一小節的代碼基礎上對網絡的參數做一些修改:

因爲網絡中有兩層隱藏層,所以我們要爲每一層隱藏層都定義一個權重矩陣和偏置項,我們設置第一層隱藏層的神經元數量爲200,第二次隱藏層的神經元數量爲10。這裏我們初始化權重矩陣的時候沒有像之前那樣直接賦值爲0,而是使用“tf.truncated_normal”函數爲其賦初值,當然全都賦值爲0也可以,不過需要訓練較多輪,模型的參數纔會慢慢接近較優的值。爲參數初始化一個非零值,在網絡層數較深,模型較複雜的時候,可以加快參數收斂的速度。

定義好模型參數之後,就可以實現網絡的具體結構了:

這裏具體的計算和上一節內容一樣,不過因爲有兩層隱藏層,因此我們需要將第一層隱藏層的輸出再作爲第二層隱藏層的輸入,並且第一層隱藏層使用了“Sigmoid”激活函數。第二層隱藏層的輸出經過“Softmax”層計算後,直接輸出預測的結果。最終在測試集上的準確率爲0.9664。

到這裏我們已經介紹完基本的前饋神經網絡的內容了,這一章的內容是深度神經網絡的基礎,理解本章的內容對於後續內容的學習會很有幫助。從下一章開始,我們要正式開始深度神經網絡的學習了。

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