2017年7月3日 星期一

[Toolkit] Keras - MNIST 手寫數字辨識使用 CNN

CNN 卷積神經網路簡介 

STEP1. 卷積神經網路介紹 
CNN 卷積神經網路可以分成兩大部分: 
* 影像的特徵提取: 透過 Convolution 與 Max Pooling 提取影像特徵.
* Fully connected Feedforward network: Flatten layers, hidden layers and output layers


STEP2. 卷積運算 (Convolution) 
卷積運算的原理是將一個影像透過卷積運算的 Filter weight(s) 產生多個影像, 在上面第一層的 Convolution 為例: 
1. 先以隨機方式產生 16 個 3x3 的 filter weight(S) 

2. 要轉換的影像由左而右, 由上而下透過 filter weight 產生新影像的值: 

3. 使用 16 個 filter weight 產生 16 個影像 

STEP3. Max-Pooling 運算說明 
Max-Pool 運算可以將影像縮減取樣 (downsampling), 如下圖: 原本影像是 4x4, 經過 Max-Pool 運算後, 影像大小為 2x2: 

downsampling 有以下好處: 
* 減少需要處理的資料點: 減少後續運算所需時間.
* 讓影像位置的差異變小: 例如手寫數字 7, 位置上下左右可能不同, 但是位置不同可能影響辨識. 減少影像大小讓數字的位置差異變小.
* 參數的數量和計算量下降: 這在一定程度上也控制了 Overfitting 的狀況.


進行資料前處理 (Preprocess) 
CNN (Convolution Neural Network) 與 MLP 進行資料的前處理方式有所不同, 說明如下: 
* MLP : image reshape (60000, 784): MLP 因為直接送進神經元處理, 所以 60,000 筆轉換為一筆成 28x28 = 784 個神經元輸入.
* CNN : image reshape (60000, 28, 28, 1): CNN 因為必須先進行卷積與池化 (Max-Pool) 運算, 所以必須保留影像的維度. 因此 60,000 筆轉換成一筆成 28 (長) x 28(寬) x 1(高) 的影像單位.

STEP1. 資料讀取與轉換 
  1. #!/usr/bin/env python3  
  2. from keras.datasets import mnist  
  3. from keras.utils import np_utils  
  4. import numpy as np  
  5. np.random.seed(10)  
  6.   
  7. # Read MNIST data  
  8. (X_Train, y_Train), (X_Test, y_Test) = mnist.load_data()  
  9.   
  10. # Translation of data  
  11. X_Train40 = X_Train.reshape(X_Train.shape[0], 28281).astype('float32')  
  12. X_Test40 = X_Test.reshape(X_Test.shape[0], 28281).astype('float32')  
STEP2. 將 Features 進行標準化與 Label 的 Onehot encoding 
  1. # Standardize feature data  
  2. X_Train40_norm = X_Train40 / 255  
  3. X_Test40_norm = X_Test40 /255  
  4.   
  5. # Label Onehot-encoding  
  6. y_TrainOneHot = np_utils.to_categorical(y_Train)  
  7. y_TestOneHot = np_utils.to_categorical(y_Test)  
建立模型 
接著會依照下面流程圖建立模型: 

STEP1. 建立卷積層與池化層 
  1. from keras.models import Sequential  
  2. from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D  
  3.   
  4. model = Sequential()  
  5. # Create CN layer 1  
  6. model.add(Conv2D(filters=16,  
  7.                  kernel_size=(5,5),  
  8.                  padding='same',  
  9.                  input_shape=(28,28,1),  
  10.                  activation='relu'))  
  11. # Create Max-Pool 1  
  12. model.add(MaxPooling2D(pool_size=(2,2)))  
  13.   
  14. # Create CN layer 2  
  15. model.add(Conv2D(filters=36,  
  16.                  kernel_size=(5,5),  
  17.                  padding='same',  
  18.                  input_shape=(28,28,1),  
  19.                  activation='relu'))  
  20.   
  21. # Create Max-Pool 2  
  22. model.add(MaxPooling2D(pool_size=(2,2)))  
  23.   
  24. # Add Dropout layer  
  25. model.add(Dropout(0.25))  
STEP2. 建立神經網路 
- 建立平坦層 
下面程式碼建立平坦層, 將之前步驟已經建立的池化層2, 共有 36 個 7x7 維度的影像轉換成 1 維向量, 長度是 36x7x7 = 1764, 也就是對應到 1764 個神經元: 
  1. model.add(Flatten())  
- 建立 Hidden layer 
  1. model.add(Dense(128, activation='relu'))  
  2. model.add(Dropout(0.5))  
- 建立輸出層 
最後建立輸出層, 共有 10 個神經元, 對應到 0~9 共 10 個數字. 並使用 softmax 激活函數 進行轉換 (softmax 函數可以將神經元的輸出轉換成每一個數字的機率): 
  1. model.add(Dense(10, activation='softmax'))  
STEP3. 查看模型的摘要 
  1. model.summary()  
  2. print("")  

進行訓練 
接著我們使用 Back Propagation 進行訓練. 

STEP1. 定義訓練並進行訓練 
  1. # 定義訓練方式  
  2. model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])  
  3.   
  4. # 開始訓練  
  5. train_history = model.fit(x=X_Train4D_norm,  
  6.                           y=y_TrainOneHot, validation_split=0.2,  
  7.                           epochs=10, batch_size=300, verbose=2)  
在 compile 方法中: 
* loss: 設定 Loss Function, 這邊選定 Cross Entropy 作為 Loss Function.
* optimizer: 設定訓練時的優化方法, 在深度學習使用 adam (Adam: A Method for Stochastic Optimization) 可以更快收斂, 並提高準確率.
* metrics: 設定評估模型的方式是 accuracy 準確率.

訓練過程的輸出如下: 
22s - loss: 0.4872 - acc: 0.8478 - val_loss: 0.0968 - val_acc: 0.9722
Epoch 2/10
22s - loss: 0.1407 - acc: 0.9591 - val_loss: 0.0631 - val_acc: 0.9808
Epoch 3/10
22s - loss: 0.1029 - acc: 0.9689 - val_loss: 0.0516 - val_acc: 0.9838
...
Epoch 8/10
22s - loss: 0.0513 - acc: 0.9841 - val_loss: 0.0345 - val_acc: 0.9898
Epoch 9/10
22s - loss: 0.0454 - acc: 0.9866 - val_loss: 0.0342 - val_acc: 0.9902
Epoch 10/10
22s - loss: 0.0428 - acc: 0.9874 - val_loss: 0.0330 - val_acc: 0.9903

STEP2. 畫出 accuracy 執行結果 
之前的訓練步驟產生的 accuracy 與 loss 都會記錄在 train_history 變數. 底下將常用的函數定義在 utils.py
- utils.py 
  1. import os  
  2.   
  3. def isDisplayAvl():  
  4.     return 'DISPLAY' in os.environ.keys()  
  5.   
  6. import matplotlib.pyplot as plt  
  7. def plot_image(image):  
  8.     fig = plt.gcf()  
  9.     fig.set_size_inches(2,2)  
  10.     plt.imshow(image, cmap='binary')  
  11.     plt.show()  
  12.   
  13. def plot_images_labels_predict(images, labels, prediction, idx, num=10):  
  14.     fig = plt.gcf()  
  15.     fig.set_size_inches(1214)  
  16.     if num > 25: num = 25  
  17.     for i in range(0, num):  
  18.         ax=plt.subplot(5,51+i)  
  19.         ax.imshow(images[idx], cmap='binary')  
  20.         title = "l=" + str(labels[idx])  
  21.         if len(prediction) > 0:  
  22.             title = "l={},p={}".format(str(labels[idx]), str(prediction[idx]))  
  23.         else:  
  24.             title = "l={}".format(str(labels[idx]))  
  25.         ax.set_title(title, fontsize=10)  
  26.         ax.set_xticks([]); ax.set_yticks([])  
  27.         idx+=1  
  28.     plt.show()  
  29.   
  30. def show_train_history(train_history, train, validation):  
  31.     plt.plot(train_history.history[train])  
  32.     plt.plot(train_history.history[validation])  
  33.     plt.title('Train History')  
  34.     plt.ylabel(train)  
  35.     plt.xlabel('Epoch')  
  36.     plt.legend(['train''validation'], loc='upper left')  
  37.     plt.show()  
接著便可以使用函數 show_train_history 顯示 accuracy 在 train 與 evaluation 的差異與 loss 在 train 與 evaluation 的差異如下: 
  1. from utils import *  
  2. if isDisplayAvl():  
  3.     show_train_history(train_history, 'acc''val_acc')  
  4.     show_train_history(train_history, 'loss''val_loss')  
執行結果如下: 
- Training accuracy vs Evaluation accuracy 

- Training loss vs Evaluation loss 

評估模型準確率與進行預測 
我們已經完成訓練, 接下來要使用 test 測試資料集來評估準確率. 

STEP1. 評估模型準確率 
  1. scores = model.evaluate(X_Test4D_norm, y_TestOneHot)  
  2. print()  
  3. print("\t[Info] Accuracy of testing data = {:2.1f}%".format(scores[1]*100.0))  
STEP2. 預測結果 
  1. print("\t[Info] Making prediction of X_Test4D_norm")  
  2. prediction = model.predict_classes(X_Test4D_norm)  # Making prediction and save result to prediction  
  3. print()  
  4. print("\t[Info] Show 10 prediction result (From 240):")  
  5. print("%s\n" % (prediction[240:250]))  
STEP3. 顯示前 10 筆預測結果 
  1. if isDisplayAvl():  
  2.     plot_images_labels_predict(X_Test, y_Test, prediction, idx=240)  

STEP4. 顯示 Confusion Matrix 
  1. import pandas as pd  
  2. print("\t[Info] Display Confusion Matrix:")  
  3. print("%s\n" % pd.crosstab(y_Test, prediction, rownames=['label'], colnames=['predict']))  

完整代碼連結如下: 
ch8_1.py: 主程式
utils.py: 相關使用函式


Supplement 
ML Lecture 10: Convolutional Neural Network 
TensorFlow : Tutorials 02 - Convolutional Neural Network 
Save and Load Your Keras Deep Learning Models

31 則留言:

  1. 您好 !
    我想請教您
    我試著跑程式碼的時候
    跑到
    KERAS_MODEL_NAME = 'mnist_model_cnn.model'
    KERAS_MODEL_WEIG = 'mnist_model_cnn.h5'
    這部分的時候
    出現了錯誤
    是什麼問題呢

    環境是
    WIN7+Anaconda3+keras
    完整代碼有確實下載了 ~

    回覆刪除
    回覆
    1. 可以貼錯誤訊息與行數?

      刪除
    2. The updated version to stop serialization while the model is deserialized from file system:
      https://drive.google.com/open?id=0B3JEkc9JW7BON2w5SnVvbE9mRTA

      刪除
  2. 您好
    我在執行主程式時會有importError的錯誤 如下
    Traceback (most recent call last):
    File "ccc.py", line 69, in
    model.load_weights(KERAS_MODEL_WEIG)
    File "/Users/lichenyu/myvenv/lib/python3.6/site-packages/keras/models.py", line 701, in load_weights
    raise ImportError('`load_weights` requires h5py.')
    ImportError: `load_weights` requires h5py.

    回覆刪除
    回覆
    1. 所以後來我pip install h5py
      但安裝後好像也是在mnist_model_cnn.model及mnist_model_cnn.h5
      出現另一個錯誤
      Traceback (most recent call last):
      File "ccc.py", line 71, in
      model.load_weights(KERAS_MODEL_WEIG)
      File "/Users/lichenyu/myvenv/lib/python3.6/site-packages/keras/models.py", line 702, in load_weights
      f = h5py.File(filepath, mode='r')
      File "/Users/lichenyu/myvenv/lib/python3.6/site-packages/h5py/_hl/files.py", line 271, in __init__
      fid = make_fid(name, mode, userblock_size, fapl, swmr=swmr)
      File "/Users/lichenyu/myvenv/lib/python3.6/site-packages/h5py/_hl/files.py", line 101, in make_fid
      fid = h5f.open(name, flags, fapl=fapl)
      File "h5py/_objects.pyx", line 54, in h5py._objects.with_phil.wrapper (/private/var/folders/my/m6ynh3bn6tq06h7xr3js0z7r0000gn/T/pip-gkjbrkhs-build/h5py/_objects.c:2840)
      File "h5py/_objects.pyx", line 55, in h5py._objects.with_phil.wrapper (/private/var/folders/my/m6ynh3bn6tq06h7xr3js0z7r0000gn/T/pip-gkjbrkhs-build/h5py/_objects.c:2798)
      File "h5py/h5f.pyx", line 78, in h5py.h5f.open (/private/var/folders/my/m6ynh3bn6tq06h7xr3js0z7r0000gn/T/pip-gkjbrkhs-build/h5py/h5f.c:2117)
      OSError: Unable to open file (Unable to open file: name = 'mnist_model_cnn.h5', errno = 2, error message = 'no such file or directory', flags = 0, o_flags = 0)

      備註: ccc.py即是作者的主程式檔案
      感恩

      刪除
    2. 從錯誤訊息看起來是檔案 "mnist_model_cnn.h5" 打開時出了問題, 如果你目錄下有該檔案的話, 先刪除再重跑程式. 另外為了一致的環境與安裝套件, 可以參考 http://puremonkey2010.blogspot.tw/2017/07/python-virtual-environments.html 建立一個乾淨的 Python 執行環境 (我使用 Python 3.5), 接著下載 requirements.txt (紀錄需要的相關套件, 下載位置: https://drive.google.com/file/d/0B3JEkc9JW7BONjB6SExuUjg1dzQ/view?usp=sharing), 並使用下面命令進行安裝相關套件:
      # pip install -r requirements.txt

      最後在使用我的範例程式 (https://drive.google.com/file/d/0B3JEkc9JW7BON2w5SnVvbE9mRTA/view?usp=sharing) 跑跑看

      刪除
    3. 您好:
      請問下載requirements.txt後,再到終端機輸入pip install -r requirements.txt就能安裝了嗎?
      麻煩你了

      刪除
    4. Hi 呂侑達,
      安裝有遇到什麼問題嗎? 如果有錯誤訊息請貼上來, 這樣才能知道怎麼解決, 謝謝!

      刪除
    5. Jojn 您好:
      Using TensorFlow backend.
      Traceback (most recent call last):
      File "", line 1, in
      File "/usr/lib/python2.7/dist-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 540, in runfile
      execfile(filename, namespace)
      File "/home/aluds/文件/CNN1.py", line 11, in
      from utils import *
      ImportError: No module named utils
      >>>

      刪除
    6. Hi,

      由錯誤訊息得知你少了 module utils.py, 下載位置在:
      https://drive.google.com/open?id=0B3JEkc9JW7BOS19aSldNSW1CV0U

      請下載後放在與 CNN1.py 相同路徑下, 再執行看看.

      刪除
    7. John 您好:
      我剛剛發現我沒有pandas,但安裝後卻顯示
      Could not install packages due to an EnvironmentError: [Errno 13] 拒絕不符權限的操作: '/usr/local/lib/python2.7/dist-packages/pandas-0.23.0.dist-info'
      Consider using the `--user` option or check the permissions.
      不知道有沒有辦法解決
      麻煩你了

      刪除
    8. Hi,
      可以參考:
      https://github.com/googlesamples/assistant-sdk-python/issues/236

      最簡單的方法是使用 sudo:
      # sudo pip install pandas

      提供你參考

      刪除
    9. 更正, 應該是:
      # sudo python -m pip install pandas

      刪除
    10. John您好:
      謝謝你的幫忙,訓練有順利地跑完了
      不好意思一直麻煩你

      刪除
    11. 不好意思 最後他還跑出這個
      [Info] Serialized Keras model to mnist_model_cnn.model...
      Traceback (most recent call last):
      File "", line 1, in
      File "/usr/lib/python2.7/dist-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 540, in runfile
      execfile(filename, namespace)
      File "/home/aluds/文件/CNN1.py", line 151, in
      model.save_weights(KERAS_MODEL_WEIG)
      File "/usr/local/lib/python2.7/dist-packages/keras/models.py", line 742, in save_weights
      raise ImportError('`save_weights` requires h5py.')
      ImportError: `save_weights` requires h5py.
      >>>
      想請問一下這是什麼問題

      刪除
    12. 是安裝h5py就能存取權重的意思嗎?

      刪除
    13. Hi,
      You are welcome.
      另外請參考代碼中 (https://drive.google.com/file/d/0B3JEkc9JW7BON2w5SnVvbE9mRTA/view):
      =====================
      # Serialized model
      if not use_exist_model:
      print("\t[Info] Serialized Keras model to %s..." % (KERAS_MODEL_NAME))
      with open(KERAS_MODEL_NAME, 'w') as f:
      f.write(model.to_json())
      model.save_weights(KERAS_MODEL_WEIG)
      print("\t[Info] Done!")
      ======================
      是用來將訓練完的模型 (weighting) 存到檔案系統中 (這個步驟稱為 Serialization), 方便日後載入使用. 你可以使用 pip 安裝 h5py 讓 Serialization 順利進行:
      # pip install h5py

      Good luck!

      刪除
    14. John您好:
      我在安裝h5py後重新執行代碼後碰到了問題
      我原本用#pip install h5py結果顯示沒有權,後改成#sudo pip install h5py才安裝成功,請問是不是這邊發生了問題?

      Traceback (most recent call last):
      File "", line 1, in
      File "/usr/lib/python2.7/dist-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 540, in runfile
      execfile(filename, namespace)
      File "/home/aluds/文件/CNN1.py", line 116, in
      model.load_weights(KERAS_MODEL_WEIG)
      File "/usr/local/lib/python2.7/dist-packages/keras/models.py", line 724, in load_weights
      with h5py.File(filepath, mode='r') as f:
      File "/usr/local/lib/python2.7/dist-packages/h5py/_hl/files.py", line 269, in __init__
      fid = make_fid(name, mode, userblock_size, fapl, swmr=swmr)
      File "/usr/local/lib/python2.7/dist-packages/h5py/_hl/files.py", line 99, in make_fid
      fid = h5f.open(name, flags, fapl=fapl)
      File "h5py/_objects.pyx", line 54, in h5py._objects.with_phil.wrapper
      File "h5py/_objects.pyx", line 55, in h5py._objects.with_phil.wrapper
      File "h5py/h5f.pyx", line 78, in h5py.h5f.open
      IOError: Unable to open file (unable to open file: name = 'mnist_model_cnn.h5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)

      刪除
    15. 我猜可能是前一次的 Serialization 出了問題, 請將底下兩個檔案 (mnist_model_cnn.model 與 mnist_model_cnn.h5) 刪除後重跑:
      KERAS_MODEL_NAME = 'mnist_model_cnn.model'
      KERAS_MODEL_WEIG = 'mnist_model_cnn.h5'

      刪除
    16. John您好:
      剛剛刪除重新跑過後成功了
      真的很謝謝你的幫忙
      最後我想請問你執行Serialization後儲存的檔案是這兩個嗎?
      KERAS_MODEL_NAME = 'mnist_model_cnn.model'
      KERAS_MODEL_WEIG = 'mnist_model_cnn.h5'
      那麼日後要使用這個訓練好的模型是要用甚麼方式載入

      刪除
    17. Hi,

      請參考代碼 (https://drive.google.com/file/d/0B3JEkc9JW7BON2w5SnVvbE9mRTA/view):
      ================
      use_exist_model = False
      if os.path.isfile(KERAS_MODEL_NAME): # 如果 Serialized 模型存在, 則載入
      train_history = None
      with open(KERAS_MODEL_NAME, 'r') as f:
      loaded_model_json = f.read()
      model = model_from_json(loaded_model_json)
      model.load_weights(KERAS_MODEL_WEIG)
      model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
      use_exist_model = True
      else: # Serialized 模型不存在, 重新訓練模型
      model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
      train_history = model.fit(x=X_Train4D_norm,
      y=y_TrainOneHot, validation_split=0.2,
      epochs=10, batch_size=300, verbose=2)
      ================

      刪除
  3. 請問一下,計算第一列第一行
    1x1 + 0x0 + 0x0 + 1x1 =2
    應該是
    1x1 + 0x0 + 0x0 + 1x5 =6
    才對吧,因為右下角的1的值是5

    回覆刪除
    回覆
    1. You are right! It should be '1x1 + 0x2 + 0x4 + 1x5 = 6'
      Thanks for the feedback and correction.

      刪除
  4. 請問一下,為甚麼卷積層2的input_shape=(28,28,1);
    在池化層1的時候不是已經將影像大小變成14*14 ?

    回覆刪除
  5. 大家好我是個剛接觸深度學習的新手,使用的是keras, 想請問
    如果有個訓練好的模型已經model.save存起來了
    現在想要拿這個模型再訓練
    只要load.model再model.fit
    就是拿已經訓練好的模型再加入資料訓練了嗎?
    還是這模型變成只有最新一批訓練資料的權重而已嗎

    回覆刪除
    回覆
    1. Check this:
      https://stackoverflow.com/questions/42666046/loading-a-trained-keras-model-and-continue-training

      刪除
  6. 你好:
    我想請問X_Train4D = X_Train.reshape(X_Train.shape[0], 28, 28, 1).astype('float32') 之中的1和X_Train.shape[0]的0代表甚麼意思?

    回覆刪除
    回覆
    1. 原先的影像為 (60000, 784) -> 60000 筆 長度為 784 的向量.
      X_Train.shape[0] 指的就是 60000; 所以 X_Train.shape[0] 就是 784.
      你可以打印出來驗證看看.
      FYI

      刪除
  7. John你好:
    可以請教如何看到filters裡面的值嗎?

    回覆刪除

[Linux 常見問題] How to set a file size limit for a directory?

Source From   Here   Question   I   have a directory on my system which is used for a specific reason by applications and users , but I   d...