【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

醫工薈萃,不是蘿蔔開會,融合創新才是硬道理!

預計閱讀時間: 18 分鐘

上一講薈薈帶大家瞭解了什麼叫神經網絡模型訓練、感知機、權值、非線性激活函數、損失函數、梯度下降、鏈式求導法則。下面薈薈將使用Python的Keras庫來實現手寫數字的分類。Keras薈薈在

“[AI學習篇]Keras+Tensorflow:GPU加速環境搭建與測試

”一文中已經介紹過了,是一個高層的神經網絡API(應用程序編程接口),它的後臺可以使用常見的深度學習框架Tensorflow、Theano以及CNTK,可以從頂層模塊化設計整個神經網絡而不必過多地在細節上費心,特別適合初學者,也適合新模型架構的研究。

本文要解決的問題是將28*28像素的手寫數字灰度圖像識別為10個類別(0-9),採用的數據集是美國國家標準與技術研究院(National Institute of Standards and Technology,NIST)於20世紀80年代收集的MNIST數據集,共有60000張訓練圖像和10000張測試圖像,如圖1所示。這個數據集經常被拿來測試機器學習算法,屬於機器學習領域的“Hello,World!”

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖1 MNIST數據集範例

我們將使用帶有一個隱層的全連接神經網絡,來實現這一識別任務。當然在開始之前,還是需要配置一下開發環境,為了後序一系列深度學習模型的順利實現,請大家還是參照“[AI學習篇]Keras+Tensorflow:GPU加速環境搭建與測試”一文配置好GPU加速的Tensorflow和Keras開發環境(畢竟沒有GPU跑深度學習的訓練,慢的要死要活的,對於預算有限的同學,Nvidia GTX 1060就足夠用了)。為了方便展示,薈薈將在這一開發環境中再安裝一個Jupyter Notebook,至於啥叫Jupyter Notebook, 請參考

“走到哪學到哪的python筆記本”

一文,簡言之是一個既能使用Markdown做筆記,又能跑代碼的學習神器,多數著名的機器學習課程都在使用它,比如上次提到的MIT 6.S191, 又如吳恩達同志在Coursera上的DeepLearning.AI都在用。安裝方法很簡單,還是在Pycharm裡的File>>Settings>>當前Project>>Project Interpreter 裡點小加號,搜索 jupyter 安裝即可(如圖2所示,看了前面的文章安裝這個應該是小菜一碟)。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖2 在pycharm裡安裝Jupyter Notebook

安裝好環境之後,左側Project目錄內右擊當前工程,我這裡是DPWP(工程名)>>New>>Jupyter Notebook,創建一個JupyterNotebook文件,以.ipynb為後綴。當然你可以雙擊運行這個筆記本,但是我不大喜歡(字體背景不好看),薈薈還是比較喜歡在瀏覽器裡運行它,可以在下方Terminal中輸入jupyter notebook(如圖3), 這時會跳出瀏覽器,選擇剛建好的.ipynb文件(圖4)就可以賞心悅目地跑咱的python代碼了。注意既然用了瀏覽器就不要再雙擊打開.ipynb咯,不然兩邊沒同步好,誤點了存儲,敲了半天的代碼灰飛煙滅哦!更詳細的配置安裝過程請參考Pycharm官方文檔,https://www.jetbrains.com/help/pycharm/using-ipython-notebook-with-product.html

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖3 Terminal裡輸入 jupyter notebook

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖4 在瀏覽器裡運行jupyter notebook

環境配置好就可以開始構建神經網絡識別手寫數字了,這裡我們分成6個步驟分別講解一下:

1. 加載MNIST數據集:

#加載Keras中的Minst數據集
from keras.datasets import mnist
(train_images,train_labels),(test_images,test_labels) = mnist.load_data()

加載完成以後,可以研究一下這些數據的類型和結構,如圖5所示這對初學者是一個好習慣,我們需要了解數據結構)。

  • 輸入 type(train_images) 並運行(運行的快捷鍵是shift+enter),發現它是一個Numpy數組(NumPy是Python的一種開源的數值計算擴展。這種工具可用來存儲和處理大型矩陣, 有興趣的同學可以深入瞭解一下)
  • 輸入train_images.shape運行,發現有60000個樣本,每個樣本是一個28*28的矩陣
  • 輸入len(train_labels)發現有60000個標籤
  • 輸入train_labels,發現是一個取值範圍為0-9的Numpy數組,數據類型為uint8

類似地可以看看test_images和test_labels的類型和結構,和訓練集基本一致,數量只有10000個。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖5 MNIST數據類型和結構

2. 構建網絡架構

構建網絡結構的代碼為:

from keras import models 
from keras import layers

network = models.Sequential() #建立Sequential 模型
network.add(layers.Dense(512,activation='relu',input_shape=(28*28,))) #添加隱層
network.add(layers.Dense(10,activation='softmax'))#添加輸出層。

首先加載Keras中名為models和layers的包,構建一個叫network的模型,是一個Sequential模型,也就是多個網絡層的線性堆疊,信息從前一直向後傳導,“一路走到黑”。可以看出我們上一講中的單隱層全連接網絡就是這樣的一路向黑網絡。

接著加兩個層,首先是隱層layers.Dense說明是一個密集(全)連接的層,第一個參數說明有512個神經元;第二個參數activation指定了激活函數是前文講過的大名鼎鼎的深度學習英雄ReLU;第三個參數

input_shape是該層接受的輸入形狀, 這裡是(28*28,), 而我們的圖像應該是(1,28,28)這樣的形狀,其中的1是樣本維度,也叫批量維度,因為網絡並不care你輸入多少個樣本,所以在input_shape中不用寫,但我們的圖像是(28,28)的矩陣,而這裡寫的是28*28,所以說對於該層我們需要把圖像矩陣排列成長度為28*28的向量,才可以輸入。

第二個添加的層是輸出層,類似上面的分析,有10個輸出的神經元,激活函數為softmax函數。softmax是一個常見的多分類激活函數,可將多個神經元的輸出映射到(0,1)之內,並且使得所有神經元的輸出總和為1。假設我們有一個數組,Z,Zi表示Z中的第i個元素,那麼這個元素的Softmax值就是

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

從圖6中,我們可以看出將輸入3,1,-3通過softmax函數激活後,就映射成了0.88,0.12和0,而這些值的和為1,這正好滿足了概率的定義,因此在作為輸出層時,其中概率最大的值,就是分類的目標類別。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖6 softmax函數示意

3. 編譯(配置優化器、損失函數、訓練和測試中需要監控的指標)

模型編譯的代碼為:

network.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])

這一步我們配置了三個參數:

  1. 優化器:這裡使用的rmsprop優化器,是由傳奇人物Geoffrey Hinton提出的,當時也只是在課堂上是隨意提出的一個想法。假想一個球在損失函數形成的山谷裡運動,山谷會將其彈來彈去,而不是按著理想的最快方向下降(如圖7所示)。為了減小這種鋸齒下降,有人想出了慣性的方法,引入慣性項保持初始的速度方向。rmsprop是這種方法的發展,它比較牛的地方在於其不需要手動配置學習率超參數,可以由算法自己完成,而且可以為每個參數選擇不同的學習率。更詳細的介紹可參見(https://zhuanlan.zhihu.com/p/42495844)。rmsprop是一種久經考驗的方法,效果槓槓地,看下下面的動圖(圖8)會有更深的體會, 很光滑的梯度下降有木有。
【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖 7 鋸齒形的梯度下降

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖8 幾種優化器的梯度下降效果

2. 損失函數:

這裡損失函數採用的是交叉熵(crossentropy), 來自於KL散度(相對熵),

KL散度是衡量兩個分佈之間的差異大小的,KL散度大於等於0,並且越接近0說明p與q這兩個分佈越像,當且僅當p與q相等時KL散度取0, KL散度(Kullback-Leibler Divergence)公式為,其中CE就代表crossentropy:

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

假設已知輸入x的標籤是p(x), 模型的預測輸出是q(x)。訓練時,針對某一個標籤信息p(x)是已知的,所以KL(p(x)||q(x))中的H(p(x))是個常數,此時KL散度等價於交叉熵,所以交叉熵可以衡量標籤信息p(x)與預測信息q(x)的差異,

我們希望q(x)儘可能地接近p(x),等價於最小化交叉熵。由於交叉熵涉及到計算每個類別的概率,所以交叉熵幾乎每次都和softmax函數一起出現。當然如果上面的內容暫時無法理解也不礙事,只要知道做多分類任務時,輸出用softmax,損失函數用交叉熵crossentropy,輸出採用one-hot編碼(後面說),優化器用rmsprop,通常能獲得不錯的效果就可以啦。

3. 在訓練和測試中需要監控的指標(metrics):這個例子裡我們只關心一下精度(accuracy),就是正確分類的比例有多高。

4. 準備圖像數據、準備標籤

對於圖像,步驟2的結果表明,每一個圖像是一個(28,28)的矩陣,而且dtype是uint8,說明取值範圍是[0,255]。但是根據我們網絡的架構,輸入應該是一個長度為28*28的向量,而且輸出的激活函數是softmax函數,取值範圍是0~1之間,因此我們的輸入取值範圍也應該是0~1比較好,但為了保持圖像分辨率(0~1中間可以用小數位數來展示分辨率),因此以train_image來說所以我們可以

將其轉化成(60000,28*28)的浮點(float32)數組,取值範圍為0~1。代碼如下:

#準備圖像數據
train_images = train_images.reshape((60000,28*28))
train_images =train_images.astype('float32')/255 #float32, 取值範圍0~1
test_images =test_images.reshape((10000,28*28))
test_images =test_images.astype('float32')/255

對於標籤,我們發現train_labels和test_labels,對於每一個樣本來說就是一個0~9之間的數(見圖5)。但是輸出是softmax,有10個輸出節點,根據前面的分析,在判別階段哪個輸出節點數字最大(接近1)就應該判別為那個類別,因此比如對於已知的數字5,針對softmax輸出,它的標籤就應該是[0,0,0,0,0,1,0,0,0,0],即從0開始的第6個數最大為1。在機器學習裡,這樣的標籤編碼方式也成為one-hot編碼, keras裡的to_categorical可以將普通的整數編碼很輕鬆的轉為one-hot編碼。標籤的處理代碼如下:

#準備標籤
from keras.utils import to_categorical
train_labels = to_categorical(train_labels) #將標籤變為one-hot編碼
test_labels =to_categorical(test_labels)

再來看一下轉換後的編碼

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖 9 one-hot編碼

5. 模型訓練

調用fit方法在訓練機上進行模型訓練, 代碼如下:

network.fit(train_images, train_labels,epochs = 5, batch_size=128)

這句話除了輸入輸出分別為train_images和train_labels以外,還有兩個參數epochs和batch_sizeepochs代表在全部數據集上進行一遍迭代

,本例是60000個樣本上都進行一遍迭代,因此這裡的5就代表把60000個樣本過5遍,我們可戲稱其為對所有樣本過一(e)波(pochs)。batch_size則代表對所有權值,進行一次梯度下降使用的樣本數量。那為什麼要設置batch_size呢? 上一講中我們知道,損失函數一般是各樣本損失下的綜合平均效果(見上一講的J(w)公式),如果對於每個樣本分別進行梯度下降,很難找到一個近似樣本整體的梯度下降方向(A樣本的梯度往東,B樣本的梯度往西,總之就是找不著北),最後難以達到收斂。使用全數據集(full-batch)進行梯度下降可以更準確地找到整體梯度下降的方向,但是實際使用中並不現實,除非你的數據集很小,而深度學習中數據集往往是非常巨大的,這裡60000個樣本都不算啥,這樣試問要多大的內存才能將這麼多數據全部加載進來求梯度。所以設定一定的batch-size可以在準確找到梯度下降方向--減小訓練震盪和提高內存利用率兩者間尋求折中。這裡我們將batch-size設置為128,也就是說每個epoch,要進行60000/469次梯度更新,5個epoch共進行了2345次梯度更新。我們運行一下程序發現,隨著梯度的更新,損失函數越來越小,訓練集上的準確率越來越高(圖10)。而且由於我們設置了batch-size可以更好地利用gpu的並行計算能力,這個訓練的速度是非常快的。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖10 訓練過程

到底這麼短的時間內我們訓練了多少參數呢,可以鍵入network.summary看一下(這個方法會經常使用,是我們瞭解網絡架構的神器,結果見圖11)。我們發現第一層有401920個權值需要更新,這個數字是怎麼來的呢,其實也很簡單,我們的輸入向量長度是28*28的,第一層的神經元個數是512,而且是全連接的(每個輸入值和神經元間都有一個連接),因此就有28*28*512個權值向量,每個神經元又有一個偏移量(bias), 所以共有28*28*512+512=401920個權值。同理第二層還有5130個權值需要更新。這麼看來,需要訓練的權值還是相當多的,但是用gpu的話也就是轉瞬之間的事(GTX1060 6s完成),所以還是看看薈薈前面的文章,花點時間配置一下gpu吧,呵呵。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖11 網絡架構

6. 檢驗效果

最後,我們看看訓練好的模型在測試集上的表現如何,鍵入下面的代碼:

test_loss,test_acc =network.evaluate(test_images,test_labels)

print('test_acc:',test_acc)

print('test_loss:',test_loss)

圖12的結果發現,測試集的精度只有98.05%,雖然挺不錯的,但是相對於訓練集上的98.89%來說還是低了一丟丟,損失也稍微大了一丟丟。這中訓練精度和測試精度的差異,是由於

過擬合(overfit)造成的, 也就是模型對訓練集的匹配度要更高,這種現象也叫模型的泛化能力不夠強。至於怎麼克服過擬合,後面的文章我們再仔細研討。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖12 測試集測試結果

接下來的這段代碼,讓我們以具體的實例看看預測的對不對。隨便從test_images中挑出第89個圖片,注意這裡的test_images[88]是長度28*28的向量,網絡要求的輸入是一個(1,28*28)的numpy數組,所以要把它變成(1,28*28)的numpy數組作為網絡的輸入。使用network.predict對其進行預測,預測結果應該是一個10維的向量(softmax的輸出),挑出其中最大值的索引就應該是預測值,這裡的結果是6。為了少敲兩行代碼(薈薈比較懶),這裡就不把整形成float32向量的圖片變回原來的圖片,再load一下mnist數據集(這些數據通常放在亞馬遜的aws服務器上,第一次加載賊慢,加載完了後面就很快了)。把圖片顯示出來看看, 果然是6 ,這個模型果然還挺6,那這個帥帥的模型長啥樣呢,圖14是它的自拍,密集恐懼症爆發了有木有。

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖13 看看預測效果

【AI學習篇】實戰深度學習(2):初識神經網絡(實踐篇)

圖 14 密集連接神經網絡識別手寫數字

這一講,薈薈用了一個密集連接的神經網絡,帶大家瞭解了一下怎麼用keras構建簡單的ANN模型(淺薄學習)。模型雖然很淺薄,但這裡講的6個步驟是非常通用的步驟,監督學習的神經網絡構建基本都要用到。下一講開始,將逐漸轉向深度學習,在正式進入deep learning之前,還有一些重要且常用的基礎概念需要講解,同志們靜候下一期的到來吧,哈哈!

需要文章源代碼的同學,請關注醫工薈微信公眾號後,在系統中留言哦,小編看到會發給您的,謝謝!


分享到:


相關文章: