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

39 則留言:

  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裡面的值嗎?

    回覆刪除
  8. 您好,我初次接觸AI相關領域,用您的手寫辨識範例程式在學習,
    我想把後面的"進行預測"改成餵入自己的".jpg數字圖檔"做辨識測試,
    但是不知道要怎麼改寫??

    回覆刪除
    回覆
    1. 你可以參考輸入資料格式:
      * 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(高) 的影像單位.

      接著將你的 ".jpg數字圖檔" 轉成上面的格式, 再餵進去程式看看. 如果還是不清楚, 可以使用 print 將:
      # Read MNIST data
      (X_Train, y_Train), (X_Test, y_Test) = mnist.load_data()

      的 X_Train, y_Train 輸出來看看, 應該就會有感覺. FYI

      刪除
  9. 您好,真的是很新的新手,可能我敘述不清楚,
    我想把訓練和測試分開寫,希望將測試的部分獨立成一個.py,且餵入自己用小畫家寫的數字圖做測試,
    先開啟順練的.py執行訓練獲得模型資料且儲存,之後再開啟執行測試的.py檔,匯入已經訓練儲存模型資料,
    然後餵入自己要測試的數字圖,訓練和測試兩個檔案是獨立的,以上是我想做的。
    然後有幾個主要問題
    1.要如何在測試的.py檔引入訓練好的模型?
    2.要如何餵入自己的".jpg數字圖"且進行辨識?

    回覆刪除
    回覆
    1. Hi,
      Please check below repo:
      https://github.com/johnklee/keras_mnist_cnn

      刪除
    2. 不好意思,現在才有時間回頭看這程式,
      請問train.py這是完整的程式嗎?
      我把keras_mnist_cnn.h5刪掉,
      重新做train,但是在資料夾內沒看到產生"keras_mnist_cnn.h5"檔案,
      還是這部分我要自己改寫?

      另外想請問"Keras_MNIST_CNN.ipynb"檔案的作用是什麼?
      這是訓練產生的?還是自己寫的?

      test_my_images.py執行顯示如下
      if 'class_name' not in config[0] or config[0]['class_name']==
      'Merge':
      KeyError:0

      刪除
    3. train.py 如果有成功跑完, 應該有下面訊息:
      Use tf.where in 2.0, which has the same broadcast rule as np.where
      10000/10000 [==============================] - 1s 131us/step

      如果沒有, 應該是哪出錯, 可以貼出你的輸出, 這樣方便 debugging.

      另外 "Keras_MNIST_CNN.ipynb" 可以使用 jupyter notebook 打開, 是用來幫助你理解 train.py 的過程. FYI

      刪除

[Git 文章收集] Differences between git merge and git rebase

Source From  Here Preface Merging and rebasing are the two most popular way to applying changes from one branch into another one. They bot...