2018年3月7日 星期三

使用 Keras 多層感知器 MLP 辨識手寫數字 (一)

上週 228 假期那天我用 Keras 框架對 MNIST 手寫數字資料集的存取方式做了一番測試後, 對此資料集算是有了進一步了解, 接下來就要往 "TensorFlow+Keras 深度學習人工智慧實務應用" 這本書的第七章邁進, 探討如何使用 Keras 以多層感知器 (MLP, Multi-Layer Perceptron) 類神經網路進行手寫數字之辨識.

前篇測試紀錄參考 :

使用 Keras 測試 MNIST 手寫數字辨識資料集

以下就照書上程序來測試 MINST 的 MLP 多層感知器的辨識效果.

甚麼是感知器? 感知器是人類對生物神經系統的一種人工模擬, 最早是美國神經生理學家 Frank Rosenblatt 於1957 年在 Cornell 大學航空實驗室工作時所發明 (此實驗室經費為萊特飛機等公司捐資支持), 可用來識別英文字母, 預測天氣與分析心電圖等等蔚為風潮, 為人工智慧領域點燃一盞明燈.

但是明斯基等人在 1969 年的著作 "Perceptron" 中以嚴謹的數學論證, 證明單層感知器構成的類神經網路無法求解許多簡單問題, 例如互斥互閘 (XOR) 就無法用單層感知器實現. 這使得類神經領域的發展因此停滯多年. 理論上而言, 單層感知器無法分離非線性區域, 而互斥或閘的值域分布正好就是非線性, 因此無法使用單層感知器分割, 但只要使用 NAND, OR, AND 閘做成兩層感知器就可以解決此問題, 在齋藤康毅寫的 "Deep Learning" 第二章有詳細說明. 關於感知器, 詳見 :

https://zh.wikipedia.org/wiki/感知器

多層感知器是由多層人工神經元組成的類神經網路, 在 MINST 資料集的手寫數字辨識中要用到的 MLP 為如下具有輸入層, 一個隱藏層, 以及輸出層的類神經網路 :




雖然實際節點有三層, 但在齋藤康毅寫的 "Deep Learning" 一書的第三章談到,  應該以擁有權重的層數來算, 準此, 由於上圖含有權重的部分是在介於輸入層與隱藏層, 以及隱藏層與輸出層之間, 因此應該稱為兩層網路.

以多層感知器 MLP 構成之類神經網路會從輸入資料的特徵值 (features) 與對應之標籤 (labels) 學習, 具體過程就是透過自動調整權重設定來學習 features 與 labels 的對應關係, 因此 MLP 屬於機器學習中的監督式學習. 經過學習而得之權重網路會對沒有 labels 的測試資料做出預測 (predicts), 類似大腦皮質的特徵映射功能. 注意, 每個神經元還包含一個調整激活程度的偏權值 (bias) 參數, 但上面的 MLP 結構並未顯示.

以多層感知器對 MNIST 資料集進行手寫辨識測試的程序如下 :


1. Features 資料預處理 :

預處理包括 features (數字圖片) 與 labels (答案標籤) 兩部分. 由於 MNIST 資料集裡面的圖片都是解析度 28*28 的數字資料 (陣列), 而 MLP 多層感知器是要針對圖片中的每一個畫素進行學習, 因此須將 28*28 的陣列轉換成 1* 784 的一維陣列 (向量) 較好處理, 亦即將 28 維陣列予以 "扁平化". 這也就是為何上面 MLP 的輸入層為 784 個神經元的原因 (28 * 28=784).

圖片的預處理將 28 * 28 陣列轉成 1 * 784 的向量可用 Numpy 的 reshape() 函數處理,

先載入 MINST 資料集 :

D:\Python\test>python
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keras.utils import np_utils     #匯入 Keras 的 Numpy 工具   
Using TensorFlow backend.
>>> import numpy as np                         #匯入 Numpy
>>> np.random.seed(10)                          #設定隨機種子, 以便每次執行結果相同
>>> from keras.datasets import mnist    #匯入 mnist 模組後載入資料集
>>> (x_train_image, y_train_label), (x_test_image, y_test_label)=mnist.load_data()

看看訓練集的第一個樣本圖片 x_train_image[0] 及其對應的標籤 y_train_label[0] :

>>> x_train_image[0]                               #訓練集的第一個樣本圖片 (2 維陣列)
array([[  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   3,
         18,  18,  18, 126, 136, 175,  26, 166, 255, 247, 127,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  30,  36,  94, 154, 170,
        253, 253, 253, 253, 253, 225, 172, 253, 242, 195,  64,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,  49, 238, 253, 253, 253, 253,
        253, 253, 253, 253, 251,  93,  82,  82,  56,  39,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,  18, 219, 253, 253, 253, 253,
        253, 198, 182, 247, 241,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  80, 156, 107, 253, 253,
        205,  11,   0,  43, 154,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,  14,   1, 154, 253,
         90,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 139, 253,
        190,   2,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  11, 190,
        253,  70,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  35,
        241, 225, 160, 108,   1,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
         81, 240, 253, 253, 119,  25,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,  45, 186, 253, 253, 150,  27,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,  16,  93, 252, 253, 187,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0, 249, 253, 249,  64,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,  46, 130, 183, 253, 253, 207,   2,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  39,
        148, 229, 253, 253, 253, 250, 182,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  24, 114, 221,
        253, 253, 253, 253, 201,  78,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,  23,  66, 213, 253, 253,
        253, 253, 198,  81,   2,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,  18, 171, 219, 253, 253, 253, 253,
        195,  80,   9,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,  55, 172, 226, 253, 253, 253, 253, 244, 133,
         11,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0, 136, 253, 253, 253, 212, 135, 132,  16,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0],
       [  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
          0,   0]], dtype=uint8)
>>> y_train_label[0]                     #第一個樣本圖片對應的標籤為 5
5   

可見圖片樣本是以一個 28 * 28 的數字陣列表示畫素明亮度, 0 為亮點, 255 為暗點, 0~255 間為灰階, 第一其對應之標籤為 5.

接下來要用 Numpy 的 reshape() 將訓練與測試樣本圖片的二維陣列都轉成 1 維向量, 其中訓練樣本有 6 萬個, 測試樣本有 1 萬個. 接著是呼叫 astype() 將數值型態轉成 32-bit 浮點數, 方便後面正規化 :

>>> x_train=x_train_image.reshape(60000,784).astype('float32')    #轉成 1 維向量
>>> x_test=x_test_image.reshape(10000,784).astype('float32')         #轉成 1 維向量
>>> x_train[0]            #訓練集的第一個樣本圖片 (轉成 1*784 的一維向量)
array([  0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   3.,  18.,
        18.,  18., 126., 136., 175.,  26., 166., 255., 247., 127.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
        30.,  36.,  94., 154., 170., 253., 253., 253., 253., 253., 225.,
       172., 253., 242., 195.,  64.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,  49., 238., 253., 253., 253., 253.,
       253., 253., 253., 253., 251.,  93.,  82.,  82.,  56.,  39.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
        18., 219., 253., 253., 253., 253., 253., 198., 182., 247., 241.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,  80., 156., 107., 253.,
       253., 205.,  11.,   0.,  43., 154.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,  14.,   1., 154., 253.,  90.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
       139., 253., 190.,   2.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,  11., 190., 253.,  70.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,  35., 241., 225., 160., 108.,   1.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,  81., 240.,
       253., 253., 119.,  25.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,  45., 186., 253., 253., 150.,  27.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,  16.,  93., 252., 253., 187.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0., 249., 253.,
       249.,  64.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,  46., 130., 183., 253., 253., 207.,   2.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,  39., 148., 229., 253., 253., 253.,
       250., 182.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,  24., 114.,
       221., 253., 253., 253., 253., 201.,  78.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,  23.,  66., 213., 253., 253., 253., 253., 198.,  81.,
         2.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,  18., 171., 219., 253., 253.,
       253., 253., 195.,  80.,   9.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,  55.,
       172., 226., 253., 253., 253., 253., 244., 133.,  11.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0., 136., 253., 253., 253., 212., 135.,
       132.,  16.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,   0.,
         0.,   0.,   0.], dtype=float32)

可見圖片樣本已轉成 1*784 的一維向量了. 為了提高訓練模型之精確度, 必須將此一維向量正規化 (normalization), 正規化可以用值域的最大值, 或者整體平均值做分母去除, 此處用除以最大值的方式, 這樣原來 0~255 的像素值就變成 0~1 之間了 :

>>> x_train_normalize=x_train/255      #訓練樣本除以最大值正規化
>>> x_test_normalize=x_test/255           #測試樣本除以最大值正規化
>>> x_train_normalize[0]                       #訓練樣本的第一個正規化圖片
array([0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.01176471, 0.07058824, 0.07058824,
       0.07058824, 0.49411765, 0.53333336, 0.6862745 , 0.10196079,
       0.6509804 , 1.        , 0.96862745, 0.49803922, 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.11764706, 0.14117648, 0.36862746, 0.6039216 ,
       0.6666667 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
       0.99215686, 0.88235295, 0.6745098 , 0.99215686, 0.9490196 ,
       0.7647059 , 0.2509804 , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.19215687, 0.93333334,
       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.99215686,
       0.99215686, 0.99215686, 0.99215686, 0.9843137 , 0.3647059 ,
       0.32156864, 0.32156864, 0.21960784, 0.15294118, 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.07058824, 0.85882354, 0.99215686, 0.99215686,
       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.7137255 ,
       0.96862745, 0.94509804, 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.3137255 , 0.6117647 , 0.41960785, 0.99215686, 0.99215686,
       0.8039216 , 0.04313726, 0.        , 0.16862746, 0.6039216 ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.05490196,
       0.00392157, 0.6039216 , 0.99215686, 0.3529412 , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.54509807,
       0.99215686, 0.74509805, 0.00784314, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.04313726, 0.74509805, 0.99215686,
       0.27450982, 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.13725491, 0.94509804, 0.88235295, 0.627451  ,
       0.42352942, 0.00392157, 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.31764707, 0.9411765 , 0.99215686, 0.99215686, 0.46666667,
       0.09803922, 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.1764706 ,
       0.7294118 , 0.99215686, 0.99215686, 0.5882353 , 0.10588235,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.0627451 , 0.3647059 ,
       0.9882353 , 0.99215686, 0.73333335, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.9764706 , 0.99215686,
       0.9764706 , 0.2509804 , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.18039216, 0.50980395,
       0.7176471 , 0.99215686, 0.99215686, 0.8117647 , 0.00784314,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.15294118,
       0.5803922 , 0.8980392 , 0.99215686, 0.99215686, 0.99215686,
       0.98039216, 0.7137255 , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.09411765, 0.44705883, 0.8666667 , 0.99215686, 0.99215686,
       0.99215686, 0.99215686, 0.7882353 , 0.30588236, 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.09019608, 0.25882354, 0.8352941 , 0.99215686,
       0.99215686, 0.99215686, 0.99215686, 0.7764706 , 0.31764707,
       0.00784314, 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.07058824, 0.67058825, 0.85882354,
       0.99215686, 0.99215686, 0.99215686, 0.99215686, 0.7647059 ,
       0.3137255 , 0.03529412, 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.21568628, 0.6745098 ,
       0.8862745 , 0.99215686, 0.99215686, 0.99215686, 0.99215686,
       0.95686275, 0.52156866, 0.04313726, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.53333336, 0.99215686, 0.99215686, 0.99215686,
       0.83137256, 0.5294118 , 0.5176471 , 0.0627451 , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        ], dtype=float32)

以上 features 的預處理部分就完成了.


2. Labels 資料預處理 :

如上所示, 訓練樣本中的標籤只是單純的一個 0~9 的整數, 但 MLP 每一個神經元的輸出是 0 或 1, 故辨識後的輸出是標示 0~9 的 10 個神經元, 因此原始的樣本標籤數值須經過 One-hot encoding 編碼 (獨熱編碼), 例如第一個訓練樣本的標籤為 5, 則經過 One-hot encoding 之後變成 0000100000, 亦即只有第五個 bit 為 1, 其餘 bits 均為 0. 參考 :

機器學習實戰:數據預處理之獨熱編碼

One-hot encoding 最常用來表示類別型變數 (Categorical variables), 又稱為虛擬變數. 在 Numpy 中 One-hot encoding 是透過 to_categorical() 函數達成 :

>>> y_train_onehot=np_utils.to_categorical(y_train_label)    #訓練樣本之標籤編碼
>>> y_test_onehot=np_utils.to_categorical(y_test_label)         #測試樣本之標籤編碼
>>> y_train_label[0]                #訓練樣本的第一個標籤為 5 (原始標籤)
5
>>> y_train_onehot[0]             #訓練樣本的第一個標籤編碼後 (One-hot)
array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.])     #僅第五個位元為 1, 其餘為 0

這樣 labels 的預處理也完成了, 然後即可開始建立 MLP 模型, 這才是 Keras 真正派上用場的時候.


3. 建立 MLP 模型 :

按照上面的 MLP 多重感知器結構, 各層神經元是一層一層前後堆疊起來, 所以要從 keras.models 匯入 Sequential 模組, 並從 keras.layers 匯入 Dense 模組 (緊密連接), 先呼叫 Sequenctial() 建立線性堆疊模型 :

model=Sequential()

然後利用 model.add() 方法依序將輸入層-隱藏層, 以及隱藏層-輸出層的神經元連接網路逐一加入網路中, 這就是齋藤康毅在他的書中所謂的雙層權重網路 (含偏權值 bias) :

model.add(Dense(units=256,input_dim=784,kernel_initializer='normal',activation='relu'))
model.add(Dense(units=10,kernel_initializer='normal',activation='softmax'))

Dense 建立的網路其上下層是完全連接的.

>>> from keras.models import Sequential    #匯入線性堆疊模型之模組
>>> from keras.layers import Dense              #匯入緊密連接模組
>>> model=Sequential()                                   #建立線性堆疊模型
>>> model.add(Dense(units=256,                    #建立輸入層至隱藏層連接
                                           input_dim=784,                      #輸入神經元數目=784
                                           kernel_initializer='normal',  #以常態分佈亂數初始化參數
                                           activation='relu'))                  #使用 ReLu 激活函數
>>> model.add(Dense(units=10,                       #建立隱藏層至輸出層連接
                                           kernel_initializer='normal',  #以常態分佈亂數初始化參數
                                           activation='softmax'))            #使用 SoftMax 激活函數

上面第二個 add() 在建立輸出層時可以省略 input_dim 參數, 因為 keras 會自動以上一層 (隱藏層) 的 unit=256 當作其輸入神經元之數目. 注意, 建立隱藏層時使用的激活函數 (activation function) 是 ReLu (整流線性單元, REctified Linear Unit); 而建立輸出層使用的激活函數則是 SoftMax. 關於 SoftMax 激活函數參考 :

https://zh.wikipedia.org/wiki/Softmax函数

建好模型後呼叫 summary() 函數會顯示摘要 :

>>> model.summary()                                        #顯示模型的摘要
_________________________________________________________________
Layer (type)                 Output Shape              Param #
==============================================================
dense_1 (Dense)              (None, 256)               200960
_________________________________________________________________
dense_2 (Dense)              (None, 10)                2570
==============================================================
Total params: 203,530
Trainable params: 203,530
Non-trainable params: 0
_________________________________________________________________
None




摘要表顯示兩層權重網路的輸出神經元與參數的數目,

dense_1 層 : 輸入-隱藏層, 有 256 個神經元與 200960 個參數 (=784*256+256)
dense_2 層 : 隱藏-輸出層, 有 10 個神經元與 2570 個參數 (=256*10+10)

全部參數合計為 200960+2570=203530 個, 這是根據上面 MLP 架構得到的 :

輸入層至隱藏層 (dense_1) : h1=relu(X*W1+b1)
隱藏層至輸出層 (dense_2) : y=softmax(h1*W2+b2)

其中 :

X : 輸入層矩陣
W1 : 輸入層至隱藏層權值參數矩陣
b1 : 隱藏層的偏權值參數矩陣
h1 : 隱藏層輸出矩陣
W2 : 隱藏層至輸出層權值參數矩陣
b2 : 輸出層的偏權值參數矩陣

因為隱藏層有 256 個神經元, 因此就有 256 個偏權值參數; 而輸出層有 10 個神經元, 因此就有 10 個偏權值參數.


4. 訓練 MLP 模型 :

建好 MLP 模型後就可以用 MNIST 資料集來訓練此模型. 進行訓練之前必須先用 compile() 函數設定模型 :

>>> model.compile(loss='categorical_crossentropy',   #設定損失函數
                                                optimizer='adam',                     #設定最佳化方法
                                                metrics=['accuracy'])                #設定評估模型之方式

這裡要設定 MLP 模型的三個參數 :

loss : 設定交叉熵 (音同低, cross entropy) 為損失函數
optimizer : 最佳化方法
metrics : 評估模型之方式

呼叫 fit() 函數即開始訓練模型 :

>>> train_history=model.fit(x=x_train_normalize,    #正規化的訓練樣本圖片
                                                                y=y_train_onehot,          #One-hot 編碼
                                                                validation_split=0.2,      #分出 20% 做驗證
                                                                epochs=10,                      #設定訓練週期 (輪)
                                                                batch_size=200,              #每批次訓練筆數
                                                                verbose=2)                       #=2 顯示訓練過程

其中前兩個參數分別傳入正規化的訓練樣本數字圖片向量與其對應之 One-hot 編碼標籤. validation_split (0~1) 是設定要從訓練資料中分出多少比率做驗證, 0.2 表示從 60000 筆訓練樣本中分割出 20% 即 60000*0.2=12000 筆作驗證, 而其餘 80% 即 60000*0.8=48000 做訓練. epochs 用來設定整個訓練要跑幾輪, 而 batch_size 則是每個批次要取多少筆資料來訓練, 亦即 fit() 並非將 60000 筆訓練資料一次跑完, 而是一次跑 200 筆.

上面指令執行後會輸出訓練過程如下 :

Train on 48000 samples, validate on 12000 samples          #60000*20%=12000 個樣本
Epoch 1/10
 - 4s - loss: 0.4381 - acc: 0.8831 - val_loss: 0.2183 - val_acc: 0.9407
Epoch 2/10
 - 3s - loss: 0.1911 - acc: 0.9453 - val_loss: 0.1557 - val_acc: 0.9556
Epoch 3/10
 - 3s - loss: 0.1356 - acc: 0.9616 - val_loss: 0.1260 - val_acc: 0.9650
Epoch 4/10
 - 3s - loss: 0.1028 - acc: 0.9703 - val_loss: 0.1120 - val_acc: 0.9683
Epoch 5/10
 - 3s - loss: 0.0811 - acc: 0.9774 - val_loss: 0.0980 - val_acc: 0.9718
Epoch 6/10
 - 2s - loss: 0.0660 - acc: 0.9817 - val_loss: 0.0935 - val_acc: 0.9722
Epoch 7/10
 - 2s - loss: 0.0546 - acc: 0.9851 - val_loss: 0.0911 - val_acc: 0.9737
Epoch 8/10
 - 3s - loss: 0.0459 - acc: 0.9878 - val_loss: 0.0826 - val_acc: 0.9761
Epoch 9/10
 - 2s - loss: 0.0381 - acc: 0.9902 - val_loss: 0.0821 - val_acc: 0.9763
Epoch 10/10
 - 3s - loss: 0.0317 - acc: 0.9919 - val_loss: 0.0807 - val_acc: 0.9765 

其中 loss 與 acc 分別為使用 80% 訓練樣本之損失與精確率; 而 val_loss 與 val_acc 分別為使用 20% 訓練樣本驗證之損失與精確率, 可見經過十輪訓練後兩部分的損失都越來越小; 而精確率則越來越高. 這十輪訓練的結果會以 dict 型態儲存在 train_history 變數的 history 屬性中, 可用 "loss", "acc", "val_loss", 與 "val_acc" 等 key 存取, 例如 :

>>> train_history.history["loss"]
[0.4380574059362213, 0.19102665071065228, 0.13551683425903321, 0.102751036516080
3, 0.08117408608086407, 0.06605853635507325, 0.05443554993253201, 0.045982492210
65392, 0.038081311973898364, 0.03167477259800459]
>>> train_history.history["acc"]
[0.8828958327881992, 0.9452916629612446, 0.9615624991556009, 0.970229172706604,
0.9771666782597701, 0.9820208465059598, 0.9850416786968708, 0.987458345045646, 0
.9901458424826463, 0.9918541744351387]
>>> train_history.history["val_loss"]   
[0.2183456058303515, 0.15563931701083977, 0.1258994841016829, 0.1117374999138216
2, 0.09836705892036358, 0.09351347321644425, 0.09076916289826234, 0.083234302691
80486, 0.08224408410023898, 0.08080933935319384]
>>> train_history.history["val_acc"]   
[0.9405833333730698, 0.9554166674613953, 0.9649166703224182, 0.967916672428449,
0.971666673819224, 0.9724166770776113, 0.9740000089009603, 0.9759166747331619, 0
.9756666759649912, 0.9761666764815649]

訓練結果可以用 matplotlib 繪製圖形更能讓人對這些數據有感, 我將書中的範例改編為如下程式 show_train_history.py, 直接執行呼叫此函數並傳入訓練結果 train_history 即可繪製圖形了 :

#show_train_history.py
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import np_utils
import matplotlib.pyplot as plt
import numpy as np

def show_train_history(train_history):
    fig=plt.gcf()
    fig.set_size_inches(16, 6)
    plt.subplot(121)
    plt.plot(train_history.history["acc"])
    plt.plot(train_history.history["val_acc"])
    plt.title("Train History")
    plt.xlabel("Epoch")
    plt.ylabel("Accuracy")
    plt.legend(["train", "validation"], loc="upper left")
    plt.subplot(122)
    plt.plot(train_history.history["loss"])
    plt.plot(train_history.history["val_loss"])
    plt.title("Train History")
    plt.xlabel("Epoch")
    plt.ylabel("Loss")
    plt.legend(["train", "validation"], loc="upper left")
    plt.show()

#pre-processing
np.random.seed(10)
(x_train_image, y_train_label), (x_test_image, y_test_label)=mnist.load_data()
x_train=x_train_image.reshape(60000,784).astype('float32')
x_test=x_test_image.reshape(10000,784).astype('float32')
x_train_normalize=x_train/255
x_test_normalize=x_test/255
y_train_onehot=np_utils.to_categorical(y_train_label)
y_test_onehot=np_utils.to_categorical(y_test_label)

#create model
model=Sequential()
model.add(Dense(units=256, input_dim=784, kernel_initializer='normal', activation='relu'))
model.add(Dense(units=10, kernel_initializer='normal', activation='softmax'))
model.summary()

#train model
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
train_history=model.fit(x=x_train_normalize, y=y_train_onehot, validation_split=0.2, epochs=10, batch_size=200, verbose=2)

#show train history
show_train_history(train_history)

此程式中我使用 matplotlib.pyplot 的 subplot() 來繪製 1 列 2 行的左右子圖, 左方是準確度子圖; 右方則是誤差子圖. 呼叫 plot() 繪製子圖前需先用 subplot() 指定是要繪製之子圖位置, 傳入值是一個三位數的整數, 例如 121 表示是 1 列 2 行中的第 1 行位置; 而 122 則是 1 列 2 行中的第 2 行位置, 參考 :

用python 畫圖 -- matplotlib -- 騙錢教學 (一)

執行 python show_train_history.py 繪製之圖形如下 :




從左邊的準確度圖形可知, 不論是藍色的 48000 筆訓練樣本 (acc) 或紅色的 12000 筆的驗證樣本 val_acc), 其準確度會隨著訓練輪次增加而增加; 而右邊的誤差子圖則顯示誤差 (loss 與 val_loss) 越來越小. 不過, 驗證樣本因為沒有參加訓練, 故其準確度在第二輪以後較訓練樣本來得低.


5. 以測試樣本評估訓練後的 MLP 模型準確率 :

完成 MLP 模型之訓練後, 可將正規化後的數字圖片測試樣本與其 One-hot 編碼之測試標籤傳入 evaluate() 函數以評估模型的準確率 :

>>> scores=model.evaluate(x_test_normalize, y_test_onehot)   #評估準確率
>>> print("Accuracy=", scores[1])                                                #顯示準確率
10000/10000 [==============================] - 0s 42us/step
Accuracy= 0.9769 

這個 0.9769 的準確率比上面訓練樣本最後的 0.9919 要低, 這是因為測試樣本沒有參加訓練, 不可能比訓練結果高.


6. 以測試樣本評估訓練後的 MLP 模型準確率 :

最後終於來對 10000 筆測試樣本進行預測了, 只要將轉成一維向量的 10000 筆測試樣本 x_test 傳給 predict_classes() 即可, 預測結果會以一個 1*10000 的一維陣列回傳 :

>>> prediction=model.predict_classes(x_test)   #預測測試樣本
>>> print(prediction)                                            #顯示預測結果
Accuracy= 0.9762                      #測試樣本辨識準確度
[7 2 1 ... 4 5 6]                            #測試樣本預測結果  (1 維陣列)

因為測試結果是 1*10000 的一維陣列, 因此它只顯示前後各三個結果, 可知測試樣本前三個圖片被 MLP 分別辨識為 7, 2, 1; 而最後三張圖片則辨識為 4, 5, 6.  可以用索引取得這 10000 個圖片的預測值 :

>>> prediction[0]
7
>>> prediction[1]
2
>>> prediction[2]
1
>>> prediction[9997]
4
>>> prediction[9998]
5
>>> prediction[9999]
6
>>> prediction[:5]
array([7, 2, 1, 0, 4], dtype=int64)
>>> prediction[9995:]
array([2, 3, 4, 5, 6], dtype=int64)

這個預測結果陣列 prediction 便可以放在前一篇顯示樣本圖片程式中的自訂函數 plot_images_labels_prediction() 中以顯示預測值了, 參考 :

使用 Keras 測試 MNIST 手寫數字辨識資料集

我把 plot_images_labels_prediction() 加入上面程式中改寫為如下之 show_test_images_with_prediction.py 程式 :


#show_test_images_with_prediction.py
import sys
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import np_utils
import matplotlib.pyplot as plt
import numpy as np

def plot_images_labels_prediction(images,labels,prediction,idx,num=10):
    fig=plt.gcf()
    fig.set_size_inches(12, 14)
    if num > 25: num=25
    for i in range(0, num):
        ax=plt.subplot(5, 5, i+1)
        ax.imshow(images[idx], cmap='binary')
        title="label=" + str(labels[idx])
        if len(prediction) > 0:
            title += ",predict=" + str(prediction[idx])
        ax.set_title(title, fontsize=10)
        ax.set_xticks([]);
        ax.set_yticks([]);
        idx += 1
    plt.show()

#pre-processing
np.random.seed(10)
(x_train_image, y_train_label), (x_test_image, y_test_label)=mnist.load_data()
x_train=x_train_image.reshape(60000,784).astype('float32')
x_test=x_test_image.reshape(10000,784).astype('float32')
x_train_normalize=x_train/255
x_test_normalize=x_test/255
y_train_onehot=np_utils.to_categorical(y_train_label)
y_test_onehot=np_utils.to_categorical(y_test_label)

#create model
model=Sequential()
model.add(Dense(units=256, input_dim=784, kernel_initializer='normal', activation='relu'))
model.add(Dense(units=10, kernel_initializer='normal', activation='softmax'))
model.summary()

#train model
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
train_history=model.fit(x=x_train_normalize, y=y_train_onehot, validation_split=0.2, epochs=10, batch_size=200, verbose=2)

#show test images with prediction
scores=model.evaluate(x_test_normalize, y_test_onehot)
print("Accuracy=", scores[1])
prediction=model.predict_classes(x_test)
print(prediction)
i=int(sys.argv[1])
j=int(sys.argv[2])
plot_images_labels_prediction(x_test_image,y_test_label,prediction,i,j)


此程式執行時需帶兩個命令列參數, 第一個是測試樣本的起始索引, 第二個是要顯示圖片之數目 (最多 25 張), 執行結果如下 :

D:\Python\test>python show_test_images_with_prediction.py 0 25 
Using TensorFlow backend.
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 256)               200960
_________________________________________________________________
dense_2 (Dense)              (None, 10)                2570
=================================================================
Total params: 203,530
Trainable params: 203,530
Non-trainable params: 0
_________________________________________________________________
Train on 48000 samples, validate on 12000 samples
Epoch 1/10
 - 3s - loss: 0.4381 - acc: 0.8829 - val_loss: 0.2182 - val_acc: 0.9406
Epoch 2/10
 - 3s - loss: 0.1908 - acc: 0.9455 - val_loss: 0.1556 - val_acc: 0.9556
Epoch 3/10
 - 3s - loss: 0.1353 - acc: 0.9617 - val_loss: 0.1260 - val_acc: 0.9647
Epoch 4/10
 - 3s - loss: 0.1026 - acc: 0.9701 - val_loss: 0.1119 - val_acc: 0.9681
Epoch 5/10
 - 3s - loss: 0.0810 - acc: 0.9775 - val_loss: 0.0982 - val_acc: 0.9716
Epoch 6/10
 - 3s - loss: 0.0660 - acc: 0.9820 - val_loss: 0.0936 - val_acc: 0.9722
Epoch 7/10
 - 3s - loss: 0.0545 - acc: 0.9850 - val_loss: 0.0919 - val_acc: 0.9733
Epoch 8/10
 - 3s - loss: 0.0459 - acc: 0.9876 - val_loss: 0.0830 - val_acc: 0.9758
Epoch 9/10
 - 3s - loss: 0.0381 - acc: 0.9903 - val_loss: 0.0826 - val_acc: 0.9758
Epoch 10/10
 - 3s - loss: 0.0317 - acc: 0.9916 - val_loss: 0.0806 - val_acc: 0.9759
10000/10000 [==============================] - 0s 44us/step
Accuracy= 0.9764
[7 2 1 ... 4 5 6]

顯示的前 25 張測試樣本圖片如下 :




可見在 label 後面都有 predict 值了, 而且這 25 張全部準確. 第一個辨識錯誤的樣本出現在測試樣本索引為 340 的圖片, 執行下列指令會顯示自索引 340 起的 10 張測試樣本與其預測結果 :

D:\Python\test>python show_test_images_with_prediction.py 340 10 
Using TensorFlow backend.




左上角第一個圖片就是索引 340 的測試樣本, 寫的像 3 又像 5 (實際上是 5), MLP 根據訓練模型預測此為 3, 結果答錯了.


7. 製作混淆矩陣 (Confusion matrix) :

有些手寫數字譬如 5 與 3, 8 與 3, 2 與 7 等很容易被誤認, 這可以用 pandas 套件的 crosstab() 函數來建立混淆矩陣, 將測試樣本標籤 (答案) 與預測結果以特殊的表格來顯示兩者那些成員容易被混淆.

製作混淆矩陣先匯入 pandas 套件, 再將測試樣本標籤 y_test_label 與預測結果 prediction 傳給 crosstab() 方法即可 :

>>> import pandas as pd 
>>> pd.crosstab(y_test_label, prediction, rownames=['label'],colnames=['predict'
]) 

完整的程式如下 :

#show_confusion_matrix.py
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense
from keras.utils import np_utils
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

#pre-processing
np.random.seed(10)
(x_train_image, y_train_label), (x_test_image, y_test_label)=mnist.load_data()
x_train=x_train_image.reshape(60000,784).astype('float32')
x_test=x_test_image.reshape(10000,784).astype('float32')
x_train_normalize=x_train/255
x_test_normalize=x_test/255
y_train_onehot=np_utils.to_categorical(y_train_label)
y_test_onehot=np_utils.to_categorical(y_test_label)

#create model
model=Sequential()
model.add(Dense(units=256, input_dim=784, kernel_initializer='normal', activation='relu'))
model.add(Dense(units=10, kernel_initializer='normal', activation='softmax'))
model.summary()

#train model
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
train_history=model.fit(x=x_train_normalize, y=y_train_onehot, validation_split=0.2, epochs=10, batch_size=200, verbose=2)
scores=model.evaluate(x_test_normalize, y_test_onehot)
print("Accuracy=", scores[1])
prediction=model.predict_classes(x_test)
pd.crosstab(y_test_label, prediction, rownames=['label'],colnames=['predict'])

將上面的程式貼到 IDLE 介面執行結果如下  :

>>> pd.crosstab(y_test_labelprediction, rownames=['label'],colnames=['predict'
])

predict    0     1     2    3    4    5    6    7    8    9
label
0        971     0     1    1    1    0    2    1    3    0
1          0  1124     4    0    0    1    2    0    4    0
2          6     0  1008    0    2    0    2    4   10    0
3          0     0     2  995    0    0    0    3    6    4
4          1     0     5    1  961    0    3    0    3    8
5          3     0     0   15    1  854    7    1    8    3
6          6     3     2    1    3    3  938    0    2    0
7          0     5    13    7    1    0    0  986    6   10
8          4     0     3    7    2    1    1    2  953    1
9          3     5     0   10    8    2    1    4    4  972




注意, 在命令提示字元視窗用 Python 指令執行指令檔時, 最後一個 crosstab() 指令不知為何沒有輸出, 因此要貼到 IDLE 執行.

對角線的統計數字是測試樣本中被正確預測的筆數, 可見 0~9 數字中以 5 的 854 次最低, 可見 5 應該是最容易被混淆的數字. 非對角線的統計數字代表該標籤被錯誤地預測為其他標籤的次數, 其中以標籤為 5 的圖片被誤認為 3 的次數最高, 達 15 次之多, 其次是標籤 7 被誤認為 2, 也有 13 次之多. 值得一提的是, 同樣的設定, 雖然結論一樣, 但我測試的數據與書上的有點不一樣, 好像在每一台電腦上跑出來的數據都稍有不同, Why?


8. 利用資料框找出哪些測試樣本被誤認 :

利用 pandas 的 DataFrame 可以找出被誤認是出現在那些測試樣本, 例如上面所示測試樣本索引 340 出現了標籤 5 被誤認為標籤 3 的情形. 將測試樣本標籤陣列 y_test_label 與預測結果 prediction 傳入 pandas.DataFrame() 建立資料框, 再用條件式篩選資料框即可得到這些被誤認的樣本位置.

>>> df=pd.DataFrame({'label':y_test_label, 'predict':prediction})   #建立資料框
>>> df      #顯示資料框內容
      label  predict
0         7        7
1         2        2
2         1        1
3         0        0
4         4        4
5         1        1
6         4        4
7         9        9
8         5        5
9         9        9
10        0        0
11        6        6
12        9        9
13        0        0
14        1        1
15        5        5
16        9        9
17        7        7
18        3        3
19        4        4
20        9        9
21        6        6
22        6        6
23        5        5
24        4        4
25        0        0
26        7        7
27        4        4
28        0        0
29        1        1
...     ...      ...
9970      5        5
9971      2        2
9972      4        4
9973      9        9
9974      4        4
9975      3        3
9976      6        6
9977      4        4
9978      1        1
9979      7        7
9980      2        2
9981      6        6
9982      5        6
9983      0        0
9984      1        1
9985      2        2
9986      3        3
9987      4        4
9988      5        5
9989      6        6
9990      7        7
9991      8        8
9992      9        9
9993      0        0
9994      1        1
9995      2        2
9996      3        3
9997      4        4
9998      5        5
9999      6        6

[10000 rows x 2 columns]

由於資料框內容長達 1 萬筆, 因此輸出時 Python 會自動省略中間資料, 只輸出前後 30 筆. 要篩選標籤 5 被誤認為標籤 3 是哪些測試樣本, 可以在資料框中輸入如下條件式 :

>>> df[(df.label==5) & (df.predict==3)]    #篩選標籤 5 被誤認為標籤 3 之樣本
      label  predict
340       5        3
1003      5        3
1393      5        3
2035      5        3
2526      5        3
2597      5        3
2810      5        3
3117      5        3
4271      5        3
4355      5        3
4360      5        3
5937      5        3
5972      5        3
6028      5        3
6043      5        3

可見標籤 5 被誤認為標籤 3 的第一筆樣本索引是 340 沒錯.

2018-03-08 補充 :

由於篇幅太長, 第七章尾巴的三項測試我另起爐灶了 :

使用 Keras 多層感知器 MLP 辨識手寫數字 (二)

沒有留言 :