2018年4月16日 星期一

好書 : 網站擷取-使用 Python

這本書我在市圖預約了快半年才到館, 可見非常搶手. 不過書到後都沒時間研讀, 這兩天才抽空翻閱, 馬上被作者 Ryan Mitchell  (是個女生) 的生花妙筆迷住, 細讀第一章發覺 Python 在網路爬蟲工具方面比 PHP 好用, 例如 BeautifulSoup 4 (BS4) 的資訊擷取功能非常方便, 很多在 PHP 需以字串處理剖析的在 BS4 只要呼叫函數即可.


Source : 誠品


此書是 Studio Tib. 譯自歐萊里出版的 "Web Scraping with Python-Collecting Data from the Modern Web", 書中範例程式碼可在作者為此書設立之網站下載 :

# http://www.pythonscraping.com/code/

另外作者也將補充資料放在 Github :

# https://github.com/REMitchell/python-scraping

作者在前言中的比喻對熟知網路爬蟲的人來說應該會會心一笑 :

"如果寫程式是魔法的話, 網路爬蟲就是巫術了"
"擷取網頁是件美妙的工作, 您不必投入太多資源就能獲得巨大的回報"

用巫術來形容 Web scraping 實在太貼切了, 因為此技術需要用到資料庫, 網頁伺服器, HTTP, HTML, 影像處理, 資料科學等領域的知識與工具.

2018 年第 15 周記事

小舅與小舅媽本周去澎湖玩, 爸因割白內障休息, 菜園看來非常乾涸, 唉, 我的菜園灌溉系統何時才能完工呢? 同事一直在問我完成了沒有, 實在說時間有限, 興趣無限, 真的要心無旁鶩專心做一件事才能有所成.

最近午飯後都會到公司後面河南路的公園走路散步 10 分鐘, 上週五看到旁邊一戶人家門口對聯右聯貼的是 "一日平安一日福", 大哉斯言! 吾人常怨自己福氣甚薄, 彩券從未中過, 卻不知平安卻是最大福氣. 平安即無任何焦慮煎迫, 身心自在啊!

左聯因角度光影甚暗看不清楚, 今日散步時特意走近一瞧, 原來是 "一堂和氣一堂春", 對得真好. 回來查詢網路, 原來客家諺語即有 "一日平安一日福 一日清閒一日仙" 之說, 講的是平安即是福, 無求清閒如神仙.

2018年4月15日 星期日

使用 Keras 卷積神經網路 (CNN) 辨識 Cifar-10 圖片 (一)

在上一篇 Keras 機器學習的測試中直接使用 MLP 分類模型對 Cifar-10 資料集進行圖片辨識預測, 準確率是奇慘的 0.48, 主要是因為將 32*32 彩色圖片拉平為 1 維向量時會失去影像的空間特徵所致. 如果使用卷積神經網路搭配 MLP 分類模型的話, 卷積層透過濾鏡擷取影像特徵後再通過分類模型去學習, 辨識準確率將大幅提高,

本系列之前的測試紀錄參考 :

# Windows 安裝深度學習框架 TensorFlow 與 Keras
# 使用 Keras 測試 MNIST 手寫數字辨識資料集
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (一)
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (二)
# 使用 Keras 卷積神經網路 (CNN) 辨識手寫數字
# 使用 Keras 測試 Cifar-10 圖片資料集
# 使用 Keras 多層感知器 MLP 辨識 Cifar-10 圖片

以下根據林大貴寫的 "TensorFlow+Keras 深度學習人工智慧實務應用" 一書第 10 章進行測試並紀錄測試結果, 模型之結構如下圖所示 :



為了改善 Overfitting 問題, 每一個參數層後面都加一個放棄層丟棄 25% 的神經元.


1. 載入資料集 :

匯入 numpy 與 cifar10 套件並載入 Cifar-10 資料集 :

D:\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> np.random.seed(10)
>>> from keras.datasets import cifar10
Using TensorFlow backend.
>>> (x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data()

顯示資料集的結構 (shape) :

>>> x_train_image.shape
(50000, 32, 32, 3)
>>> x_test_image.shape 
(10000, 32, 32, 3)
>>> y_train_label.shape
(50000, 1)
>>> y_test_label.shape 
(10000, 1)


2. 資料預處理 : 

數字圖片預處理 :

>>> x_train_normalize=x_train_image.astype('float32')/255.0
>>> x_test_normalize=x_test_image.astype('float32')/255.0 

標籤預處理 (需 keras.utils.np_utils) :

>>> from keras.utils import np_utils
>>> y_train_onehot=np_utils.to_categorical(y_train_label)
>>> y_test_onehot=np_utils.to_categorical(y_test_label)


3. 建立模型 : 

匯入 keras.models 與 keras.layers 下的相關模組 :

>>> from keras.models import Sequential 
>>> from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D 
>>> from keras.layers import ZeroPadding2D,Activation 

建立線性堆疊模型, 加入兩層卷積 (丟棄 25% 神經元) + 池化層 :

>>> model=Sequential()
>>> model.add(Conv2D(filters=32, 
...                  kernel_size=(3,3),
...                  padding='same', 
...                  input_shape=(32,32,3), 
...                  activation='relu')) 
>>>
>>> model.add(Dropout(0.25)) 
>>> model.add(MaxPooling2D(pool_size=(2, 2))) 
>>> model.add(Conv2D(filters=64,
...                  kernel_size=(3,3),
...                  padding='same',
...                  activation='relu'))
>>>
>>> model.add(Dropout(0.25)) 
>>> model.add(MaxPooling2D(pool_size=(2, 2))) 

建立分類模型 (MLP) : 平坦層 + 隱藏層 (1024 神經元) + 輸出層 (10 神經元)

>>> model.add(Flatten())
>>> model.add(Dropout(0.25))   
>>> model.add(Dense(1024,activation='relu')) 
>>> model.add(Dropout(0.25)) 
>>> model.add(Dense(10,activation='softmax')) 

檢視模型摘要 :

>>> print(model.summary())
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 32)        896
_________________________________________________________________
dropout_1 (Dropout)          (None, 32, 32, 32)        0
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 16, 32)        0
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 16, 16, 64)        18496
_________________________________________________________________
dropout_2 (Dropout)          (None, 16, 16, 64)        0
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 8, 64)          0
_________________________________________________________________
flatten_1 (Flatten)          (None, 4096)              0
_________________________________________________________________
dropout_3 (Dropout)          (None, 4096)              0
_________________________________________________________________
dense_1 (Dense)              (None, 1024)              4195328
_________________________________________________________________
dropout_4 (Dropout)          (None, 1024)              0
_________________________________________________________________
dense_2 (Dense)              (None, 10)                10250
=================================================================
Total params: 4,224,970
Trainable params: 4,224,970
Non-trainable params: 0
_________________________________________________________________
None


3. 編譯與訓練模型 :


>>> 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=128,verbose=2)
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
2018-04-15 16:53:40.208119: I C:\tf_jenkins\workspace\rel-win\M\windows\PY\36\tensorflow\core\platform\cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX
 - 481s - loss: 1.5019 - acc: 0.4593 - val_loss: 1.2868 - val_acc: 0.5823
Epoch 2/10
 - 461s - loss: 1.1382 - acc: 0.5950 - val_loss: 1.1190 - val_acc: 0.6325
Epoch 3/10
 - 460s - loss: 0.9844 - acc: 0.6561 - val_loss: 1.0265 - val_acc: 0.6568
Epoch 4/10
 - 462s - loss: 0.8762 - acc: 0.6896 - val_loss: 0.9486 - val_acc: 0.6900
Epoch 5/10
 - 459s - loss: 0.7851 - acc: 0.7235 - val_loss: 0.8822 - val_acc: 0.7088
Epoch 6/10
 - 460s - loss: 0.7018 - acc: 0.7546 - val_loss: 0.8280 - val_acc: 0.7274
Epoch 7/10
 - 468s - loss: 0.6218 - acc: 0.7801 - val_loss: 0.8152 - val_acc: 0.7258
Epoch 8/10
 - 622s - loss: 0.5555 - acc: 0.8041 - val_loss: 0.7733 - val_acc: 0.7435
Epoch 9/10
 - 563s - loss: 0.4856 - acc: 0.8307 - val_loss: 0.7948 - val_acc: 0.7310
Epoch 10/10
 - 554s - loss: 0.4308 - acc: 0.8474 - val_loss: 0.7525 - val_acc: 0.7431

呵呵, CNN 果然管用, 訓練來到 0.8474.

繪製訓練結果 :

>>> 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()
...
>>> import matplotlib.pyplot as plt 
>>> show_train_history(train_history) 





4. 評估預測準確率 : 

>>> scores=model.evaluate(x_test_normalize, y_test_onehot)
10000/10000 [==============================] - 35s 3ms/step
>>> print("Accuracy=", scores) 
Accuracy= [0.7632420552253724, 0.74]
>>> print("Accuracy=", scores[1])
Accuracy= 0.74

跟驗證集的結果差不多.


5. 預測測試集圖片 : 

>>> prediction=model.predict_classes(x_test_normalize) 
>>> print(prediction) 
[3 8 8 ... 5 4 7]
>>> print(prediction[:10]) 
[3 8 8 0 6 6 1 6 3 1]
>>> print(y_test_label[:10]) 
[[3]
 [8]
 [8]
 [0]
 [6]
 [6]
 [1]
 [6]
 [3]
 [1]]

將標籤攤平以利比較 :

標籤 : 3 8 8 0 6 6 1 6 3 1
預測 : 3 8 8 0 6 6 1 6 3 1

可見前 10 張測試集圖片預測完全正確. 

將以上指令整合為如下可在命令列執行之程式 :

#show_cifar10_cnn_predict.py
#載入資料集
import time
start=time.time()
import numpy as np
np.random.seed(10)
from keras.datasets import cifar10
(x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data()

#資料預處理
x_train_normalize=x_train_image.astype('float32')/255.0
x_test_normalize=x_test_image.astype('float32')/255.0
from keras.utils import np_utils
y_train_onehot=np_utils.to_categorical(y_train_label)
y_test_onehot=np_utils.to_categorical(y_test_label)

#建立模型
#建立兩層卷積 (丟棄 25% 神經元) + 池化層
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D
from keras.layers import ZeroPadding2D,Activation
model=Sequential()
model.add(Conv2D(filters=32,
                 kernel_size=(3,3),
                 padding='same',
                 input_shape=(32,32,3),
                 activation='relu'))
model.add(Dropout(0.25))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=64,
                 kernel_size=(3,3),
                 padding='same',
                 activation='relu'))
model.add(Dropout(0.25))
model.add(MaxPooling2D(pool_size=(2, 2)))
#建立分類模型 MLP
model.add(Flatten())
model.add(Dropout(0.25))
model.add(Dense(1024,activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(10,activation='softmax'))
model.summary()

#訓練模型
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=128,verbose=2)
elapsed=time.time()-start
print("Training time=" + str(elapsed) + " Seconds")
show_train_history(train_history)
#繪製訓練結果
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()
import matplotlib.pyplot as plt 
show_train_history(train_history)

#評估預測準確率
scores=model.evaluate(x_test_normalize, y_test_onehot) 
print("Accuracy=", scores[1])
prediction=model.predict_classes(x_test_normalize)
print(prediction)

#預測測試集圖片
prediction=model.predict_classes(x_test_normalize)
print(prediction)


6. 製作混淆矩陣 :

注意這裡 y_test_label 是 2 維陣列, 須用 reshape(-1) 轉成 1 維陣列 :

>>> import pandas as pd
>>> pd.crosstab(y_test_label.reshape(-1), prediction, rownames=['label'],colnames=['predict'])
predict    0    1    2    3    4    5    6    7    8    9
label
0        804    9   64   14   11    7   15    4   45   27
1         16  808   21   20    4    7   15    2   21   86
2         47    2  678   31   75   69   71   14    8    5
3         22    6   98  478   44  213   99   20    7   13
4         18    1   86   51  708   38   61   29    7    1
5         12    1   64  121   32  692   46   24    4    4
6          4    5   39   27   16   24  883    1    1    0
7         16    1   66   26   67   84   12  720    1    7
8         52   29   29   14    7   10   17    3  822   17
9         39   55   24   14    1   11   10   10   29  807
>>> y_test_label.reshape(-1) 
array([3, 8, 8, ..., 5, 1, 7]) 




可見 Cifar-10 測試集圖片中, 類別 3 (cat) 被誤認為類別 5 (dog) 次數最高 (213 次); 其次是反過來類別 5 (dog) 被誤認為類別 3 (cat) 達 12 次; 第三名是類別 3 (cat) 被誤認為類別 6 (frog) 達 99 次, 第四名是類別 3 (cat) 被誤認為類別 2 (bird) 有 98 次.

以上測試顯示先利用 CNN 卷積運算擷取圖片平面特徵後再用 MLP 來學習分類大幅提升了預測準確率, 從大約 0.45 提升到 0.74 左右, 提升幅度約 64% .

參考 :

# Deep learning for complete beginners: convolutional neural networks with keras

2018年4月14日 星期六

訂製臥室窗簾

過年後決心整理臥室, 首先上網訂購 DIY 五斗櫃將衣物做妥善收納, 接著將靠窗書櫃上的百科全書, 童書等現在小狐狸們不再看的套書分批搬回鄉下二樓存放 (另找時間整理上櫃或送親友小朋友), 這周書櫃騰空後擦拭整理窗戶紗窗完畢, 打算來更換窗簾, 自 86 年交屋後至今已逾 20 年, 窗簾邊邊都被太陽曬壞, 還是換過新的較賞心悅目.

我大致量了窗框大小約 142*149, 前天下班經過民族路的窗簾店 "布置家 (民族一路 363 號, 07-3850866)", 進去請老闆估價, 特價素色布估 2500 元; 花色布 2900, 徵得店家同意拍照回家給水某看, 決定 2900 元左邊黃綠色花布這款 :




昨天下班後請王老闆來丈量精確尺寸, 她說滑軌太舊拉線困難應更換, 原價 400 優惠 300, 合計 3200, 先付訂金 1000 元, 下周做好再通知我安裝.

不知不覺我這房子已住了 21 年, 算是老舊房子了. 這幾年同棟鄰居陸續貼出屋內裝修公告, 大都是修地板, 浴室馬桶更換, 甚至重新裝潢等. 我家除了臥室地板去年膨起來更換了 7, 8 塊外屋況還不錯. 我主要是書太多沒地方擺, 室內東西太凌亂, 今年以來決心一步步來收拾整理, 等菁菁上高中, 學校用書清掉後應該就會清爽多了.

2018年4月13日 星期五

使用 Keras 多層感知器 MLP 辨識 Cifar-10 圖片

做完 Cifar-10 資料集測試後, 接下來我想用 MLP 來測試看看 Cifar-10 的辨識率有多少. 由於直接用 MLP 這種 DNN 網路做類別辨識須將圖片樣本拉平, 這會失去圖像的空間特徵, 辨識率可能不會很好, 測試過程紀錄如下.

本系列之前的測試紀錄參考 :

# Windows 安裝深度學習框架 TensorFlow 與 Keras
# 使用 Keras 測試 MNIST 手寫數字辨識資料集
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (一)
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (二)


1. 載入 Cifar-10 資料集 :

匯入 cifar10 與 numpy 模組後呼叫 load_data() 載入 Cifar-10 資料集 :

D:\Python\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keras.datasets import cifar10     
Using TensorFlow backend.
>>> from keras.utils import np_utils        #轉換標籤為 Oonehot 用
>>> import numpy as np     
>>> np.random.seed(10) 
>>> (x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data() 

函數 load_data() 會傳回兩個 tuple, 分別是訓練集與測試集的圖片與標籤陣列 :

x_train_image : 訓練集數字圖片陣列 (3 維)
y_train_label : 訓練集標籤陣列 (2 維)
x_test_image : 測試集數字圖片陣列 (3 維)
y_test_label : 測試集標籤陣列 (2 維)


2. 圖片資料預處理  :

Cifar-10 資料集包含訓練集 50000 筆, 測試集 10000 筆. 其中圖片是以三維陣列儲存的 32*32 解析度的 RGB 三原色數字影像, 索引存取方式為 [i][j][k], 第一維 i 表示圖片編號, 範圍 0~49999 共 5 萬張; 第二維 j 表示圖片的畫素列編號, 範圍 0~31 共 32 列, 第 3 維 k 代表行編號, 實際上儲存的是每一個畫素的 RGB 三原色的 1*3 行向量, 每一種顏色數值範圍 0~255.

顯示陣列之 shape 屬性 :

>>> print('x_train_image:', x_train_image.shape)   #顯示訓練集圖片之 shape
x_train_image: (50000, 32, 32, 3)     #訓練集為 5 萬筆之 32*32 RGB 彩色圖片
>>> print('y_train_label:', y_train_label.shape)       #顯示訓練集標籤之 shape
y_train_label: (50000, 1)                   #訓練集標籤為 5 萬筆 0~9 數字
>>> print('x_test_image:', x_test_image.shape)        #顯示測試集圖片之 shape
x_test_image: (10000, 32, 32, 3)      #測試集為 1 萬筆之 32*32 RGB 彩色圖片
>>> print('y_test_label:', y_test_label.shape)            #顯示測試集標籤之 shape
y_test_label: (10000, 1)                    #測試集標籤為 1 萬筆 0~9 數字

數字圖片之資料結構如下 :


例如訓練集第一張圖片之內容節略如下 :

x_train_image[0]= [
 [[ 59  62  63]       #第 1 列畫素開始 (0)
  [ 43  46  45]
  [ 50  48  43]
  ...
  [158 132 108]
  [152 125 102]
  [148 124 103]]    #第 1 列畫素結束 (31)

 [[ 16  20  20]        #第 2 列畫素開始 (0)
  [  0   0   0]
  [ 18   8   0]
  ...
  [123  88  55]
  [119  83  50]
  [122  87  57]]      #第 2 列畫素結束 (31)

......

 [[177 144 116]     #第 32 列畫素開始 (0)
  [168 129  94]
  [179 142  87]
  ...
  [216 184 140]
  [151 118  84]
  [123  92  72]]]     #第 32 列畫素結束 (0)

訓練集第一張圖片的各列開頭畫素擷取方式舉例如下 :

>>> print('x_train_image[0][0][0]=', x_train_image[0][0][0])   #第 1 列第 1 個畫素
x_train_image[0][0][0]= [59 62 63]
>>> print('x_train_image[0][1][0]=', x_train_image[0][1][0])   #第 2 列第 1 個畫素
x_train_image[0][1][0]= [16 20 20]
>>> print('x_train_image[0][31][0]=', x_train_image[0][31][0]  #第 32 列第 1 個畫素
x_train_image[0][31][0]= [177 144 116]

這些以三維陣列表示的圖片在送入 MLP 做訓練或預測之前必須拉平展開為一維向量, 一張 32*32 解析度具有 RGB 三色版的圖片總共有 32*32*3=3072 個數字, 拉平後會變成 1*3072 的行向量, 呼叫陣列的 reshape() 函數即可將 5 萬筆訓練集與 1 萬筆測試集的圖片平坦化 (flatten), 接著將數值型態由整數轉成浮點數, 後面正規化要用到 :

>>> x_train=x_train_image.reshape(50000,3072).astype('float32')
>>> x_test=x_test_image.reshape(10000,3072).astype('float32') 
>>> x_train 
array([[ 59.,  62.,  63., ..., 123.,  92.,  72.],
       [154., 177., 187., ..., 143., 133., 144.],
       [255., 255., 255., ...,  80.,  86.,  84.],
       ...,
       [ 35., 178., 235., ...,  12.,  31.,  50.],
       [189., 211., 240., ..., 195., 190., 171.],
       [229., 229., 239., ..., 163., 163., 161.]], dtype=float32)
>>> x_train.ndim      #顯示訓練集陣列維度 (2 維)
2
>>> x_test.ndim        #顯示測試集陣列維度 (2 維)
2
>>> x_train.size        #訓練集全部畫素數目
153600000
>>> x_test.size          #測試集全部畫素數目
30720000
>>> x_train[0]          #訓練集第一張圖片
array([ 59.,  62.,  63., ..., 123.,  92.,  72.], dtype=float32)
>>> x_train[0].size   #訓練集第一張圖片之大小 (3072 個畫素)
3072
>>> x_test[0]             #測試集第一張圖片
array([158., 112.,  49., ...,  21.,  67., 110.], dtype=float32)
>>> x_test[0].size      #測試集第一張圖片之大小 (3072 個畫素)
3072

由 ndim 屬性可知, 平坦化後的數字圖片都從 3 維變成 2 維, 第 1 維是圖片索引; 第 2 維是每個圖片的 3072 個畫素之索引. 由 size 屬性可知訓練集全部畫素數目為 50000*3072=15360000 個; 而測試集全部畫素數目為 10000*3072=30720000 個.

平坦化後接著是正規化, 仍然採用除以最大值 255 予以正規化的方法 :

>>> x_train_normalize=x_train/255     #訓練集正規化
>>> x_test_normalize=x_test/255         #測試集正規化
>>> x_train_normalize[0]                     #正規化之訓練集第一張圖片
array([0.23137255, 0.24313726, 0.24705882, ..., 0.48235294, 0.36078432,
       0.28235295], dtype=float32)

這樣數字圖片的預處理就完成了.


3. 標籤資料預處理  :

標籤本身為 2 維陣列, 可利用陣列之 shape, ndime, size 等屬性查詢其結構與元素個數 :

>>> y_train_label                #訓練集標籤
array([[6],
       [9],
       [9],
       ...,
       [9],
       [1],
       [1]], dtype=uint8)
>>> y_train_label[0]           #訓練集第 1 個標籤
array([6], dtype=uint8)
>>> y_train_label.shape     #訓練集標籤為 50000 個 1 維向量
(50000, 1)
>>> y_train_label.ndim      #2 維陣列
2
>>> y_train_label.size         #訓練集標籤大小 50000
50000
>>> y_test_label                   #測試集標籤 
array([[3],
       [8],
       [8],
       ...,
       [5],
       [1],
       [7]])
>>> y_test_label[0]             #測試集第 1 個標籤
array([3])
>>> y_test_label.shape       #測試集標籤為 10000 個 1 維向量
(10000, 1)
>>> y_test_label.ndim        #2 維陣列
2
>>> y_test_label.size           #測試集標籤大小 10000
10000

標籤的預處理對於類別型變數而言其實就是進行 Onehot (獨熱) 編碼, 例如將原本之標籤 6 轉換為 0000001000, 亦即對於 0~9 這 10 種類別只有一個位元會輸出 1 (One hot). 這要利用 keras.util.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]
array([6], dtype=uint8)
>>> y_train_onehot[0] 
array([0., 0., 0., 0., 0., 0., 1., 0., 0., 0.])

這樣就完成標籤資料的預處理了.


4. 建立 MLP 模型 :

針對 Cifar-10 的全連接 MLP 模型結構為 3 層的人工神經網路 (但參數只有 2 層) 如下所示, 輸入層具有 3072 個神經元用來接取每張圖片的 32*32*3=3072 個畫素; 隱藏層具有 256 個神經元, 輸出層有 10 個神經元, 分別對應 Cifar-10 的 10 種圖片分類 :




利用 Keras 的線性堆疊 (Sequential) 來建立 MLP 模型, 將兩層的 Dense 神經網路層以 add() 方法一層一層疊起來, 每一個 Dense 層都採用線性 + 非線性激活結構, 其中 Dense 1 層使用 ReLu 激活函數; 而 Dense 2 則使用 SoftMax 函數, 上面網路的內部結構如下所示 :




其數學運算式如下 :

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

其中括號內為線性運算, 括號外的 relu() 與 softmax() 為非線性運算.

依上述架構用 Keras 來建立 MLP 網路並顯示模型之摘要 :

>>> from keras.models import Sequential 
>>> from keras.layers import Dense 
>>> model=Sequential() 
>>> model.add(Dense(units=256, input_dim=3072, kernel_initializer='normal', activation='relu'))   
>>> model.add(Dense(units=10, kernel_initializer='normal', activation='softmax')) 
>>> model.summary()   
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 256)               786688
_________________________________________________________________
dense_2 (Dense)              (None, 10)                2570
=================================================================
Total params: 789,258
Trainable params: 789,258
Non-trainable params: 0
_________________________________________________________________

此處 Dense_1 層的參數有 786688 個=3072*256 + 256 (offset), 而 Dense_2 層參數有 2570 個=256*10 + 10, 全部參數合計 786688 + 2570=789258 個.


5. 編譯與訓練 MLP 模型 :

建好 MLP 模型後須先呼叫 compile() 函數, 需指定損失函數, 最佳化方法, 以及評估訓練的方式等進行模型編譯, 然後呼叫 fit() 函數使用 Cifar-10 的訓練集資料 (正規化圖片與 Onehot 編碼之標籤) 來訓練 MLP 網路 :

>>> 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)   
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
 - 9s - loss: 1.9405 - acc: 0.3059 - val_loss: 1.8187 - val_acc: 0.3533
Epoch 2/10
 - 9s - loss: 1.7431 - acc: 0.3832 - val_loss: 1.7223 - val_acc: 0.3923
Epoch 3/10
 - 9s - loss: 1.6760 - acc: 0.4062 - val_loss: 1.6724 - val_acc: 0.4136
Epoch 4/10
 - 9s - loss: 1.6216 - acc: 0.4271 - val_loss: 1.6379 - val_acc: 0.4261
Epoch 5/10
 - 9s - loss: 1.5843 - acc: 0.4390 - val_loss: 1.6030 - val_acc: 0.4382
Epoch 6/10
 - 9s - loss: 1.5581 - acc: 0.4503 - val_loss: 1.5694 - val_acc: 0.4487
Epoch 7/10
 - 9s - loss: 1.5298 - acc: 0.4600 - val_loss: 1.5572 - val_acc: 0.4498
Epoch 8/10
 - 9s - loss: 1.5141 - acc: 0.4649 - val_loss: 1.5846 - val_acc: 0.4436
Epoch 9/10
 - 9s - loss: 1.4816 - acc: 0.4773 - val_loss: 1.5673 - val_acc: 0.4457
Epoch 10/10
 - 9s - loss: 1.4686 - acc: 0.4810 - val_loss: 1.5680 - val_acc: 0.4478

可見隨著訓練週期增加, 預測準確率逐步提升至 0.4843. 如前所述, 由於平坦化喪失了圖片的空間特徵, 因此預測準確率不到一半. 這裡仍然使用 20% 的訓練集資料 (10000 筆) 做驗證, 亦即實際使用 80% (40000 筆) 分批做訓練, 每批 200 筆, 全部資料反覆做 10 遍訓練, 結果 (準確度與誤差) 儲存在陣列中傳回, 可用 acc, loss 取得 40000 筆的訓練結果, 用 val_acc 與 val_loss 取得 10000 筆之驗證結果 :  

>>> train_history.history["loss"]
[1.9404615819454194, 1.7430958902835847, 1.6760325831174852, 1.6215702134370804, 1.5842689234018326, 1.558070672750473, 1.529754489660263, 1.5140713691711425, 1.4815812402963637, 1.4686381608247756]
>>> train_history.history["acc"]
[0.3058749999850988, 0.38319999903440477, 0.4062249992787838, 0.4270999994874001, 0.4390000009536743, 0.4502999997138977, 0.46004999950528147, 0.46487499997019766, 0.47727499932050704, 0.4810249993205071]
>>> train_history.history["val_loss"]
[1.8187400770187379, 1.7222569155693055, 1.6723521327972413, 1.6378972125053406, 1.6029685401916505, 1.5694254302978516, 1.557159855365753, 1.5845503187179566, 1.5673147130012512, 1.5679782795906068]
>>> train_history.history["val_acc"]
[0.35330000162124636, 0.3922999995946884, 0.4135999995470047, 0.42610000014305116, 0.43820000052452085, 0.4486999982595444, 0.4498000007867813, 0.4435999995470047, 0.44569999814033506, 0.44779999732971193]

訓練結果可用 matplotlib 繪製圖形 :

>>> import matplotlib.pyplot as plt     
>>> 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()   
...
>>> show_train_history(train_history)   





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

完成 MLP 模型訓練後, 可呼叫 evaluate() 來評估此模型之參數對測試集資料之準確率 :

>>> scores=model.evaluate(x_test_normalize, y_test_onehot) 
10000/10000 [==============================] - 1s 124us/step
>>> print("Accuracy=", scores[1])   
Accuracy= 0.4526

此準確率 0.4526 一定會比最後一周訓練結果的 (0.4810) 低.


7. 以測試樣本進行預測 :

呼叫 predict_classes() 即可對測試集的 10000 張數字圖片進行預測, 結果將以二維陣列傳回, 每個預測值以 1*1 陣列表示 :

>>> prediction=model.predict_classes(x_test_normalize)   
>>> print(prediction) 
[5 8 0 ... 3 4 7]
>>> print(prediction[:10]) 
[5 8 0 0 4 6 5 4 5 1]
>>> print(y_test_label[:10]) 
[[3]
 [8]
 [8]
 [0]
 [6]
 [6]
 [1]
 [6]
 [3]
 [1]]

我將標籤攤平以利比較 :

標籤 : 5 8 0 0 4 6 5 4 5 1
預測 : 3 8 8 0 6 6 1 6 3 1

前十張圖片只有四張預測結果正確, 正確率真的只有 40% 左右呢.

我將上面的指令寫成如下程式以便在命令列中執行 :

#show_cifar10_mlp_prediction.py
from keras.datasets import cifar10
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 time

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
start=time.time()
np.random.seed(10)
(x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data()
x_train=x_train_image.reshape(50000,3072).astype('float32')
x_test=x_test_image.reshape(10000,3072).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=3072, 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)
end=time.time()
elapsed=end-start
print("Training time=" + str(elapsed) + " seconds")
show_train_history(train_history)

#evaluate and predict the test data
scores=model.evaluate(x_test_normalize, y_test_onehot)
print("Accuracy=", scores[1])
prediction=model.predict_classes(x_test_normalize)
print(prediction)


8. 增加隱藏層神經元以提高準確率

上面以 256 個神經元建構的隱藏層預測效果不佳, 如果增加隱藏層神經元應該會提升準確率. 我將上面的程式改為如下可以透過命令列參數改變隱藏層神經元數目的版本, 看看若提升隱藏層神經元數目為 512 與 1024, 是否準確率顯著提升?

#show_cifar10_mlp_prediction2.py
import sys
import time
from keras.datasets import cifar10
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
start=time.time()
np.random.seed(10)
(x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data()
x_train=x_train_image.reshape(50000,3072).astype('float32')
x_test=x_test_image.reshape(10000,3072).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
h=int(sys.argv[1])
model=Sequential()
model.add(Dense(units=h, input_dim=3072, 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)
elapsed=time.time()-start
print("Training time=" + str(elapsed) + " Seconds")
show_train_history(train_history)

#evaluate and predict the test data
scores=model.evaluate(x_test_normalize, y_test_onehot)
print("Accuracy=", scores[1])
prediction=model.predict_classes(x_test_normalize)
print(prediction)

512 個隱藏層神經元的訓練與預測結果如下 :

D:\Python\test>show_cifar10_mlp_prediction2.py 512 
Using TensorFlow backend.
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 512)               1573376
_________________________________________________________________
dense_2 (Dense)              (None, 10)                5130
=================================================================
Total params: 1,578,506
Trainable params: 1,578,506
Non-trainable params: 0
_________________________________________________________________
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
 - 15s - loss: 2.1757 - acc: 0.2925 - val_loss: 1.8484 - val_acc: 0.3424
Epoch 2/10
 - 15s - loss: 1.7530 - acc: 0.3792 - val_loss: 1.7572 - val_acc: 0.3874
Epoch 3/10
 - 17s - loss: 1.6737 - acc: 0.4080 - val_loss: 1.6924 - val_acc: 0.3993
Epoch 4/10
 - 15s - loss: 1.6178 - acc: 0.4320 - val_loss: 1.6377 - val_acc: 0.4261
Epoch 5/10
 - 17s - loss: 1.5797 - acc: 0.4439 - val_loss: 1.6295 - val_acc: 0.4192
Epoch 6/10
 - 14s - loss: 1.5479 - acc: 0.4559 - val_loss: 1.5920 - val_acc: 0.4462
Epoch 7/10
 - 14s - loss: 1.5198 - acc: 0.4647 - val_loss: 1.5590 - val_acc: 0.4541
Epoch 8/10
 - 15s - loss: 1.4961 - acc: 0.4738 - val_loss: 1.5816 - val_acc: 0.4496
Epoch 9/10
 - 16s - loss: 1.4715 - acc: 0.4835 - val_loss: 1.5654 - val_acc: 0.4498
Epoch 10/10
 - 15s - loss: 1.4505 - acc: 0.4885 - val_loss: 1.5466 - val_acc: 0.4516
Training time=155.61666822433472 Seconds
10000/10000 [==============================] - 2s 201us/step
Accuracy= 0.4564
[3 9 0 ... 5 4 2]

與 256 個神經元結果比起來, 準確度有提升一些. 訓練結果圖如下 :




隱藏層神經元增加為 1024 的訓練與預測結果如下 :

D:\Python\test>show_cifar10_mlp_prediction2.py 1024
Using TensorFlow backend.
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 1024)              3146752
_________________________________________________________________
dense_2 (Dense)              (None, 10)                10250
=================================================================
Total params: 3,157,002
Trainable params: 3,157,002
Non-trainable params: 0
_________________________________________________________________
Train on 40000 samples, validate on 10000 samples
Epoch 1/10
 - 29s - loss: 6.3627 - acc: 0.2266 - val_loss: 6.0748 - val_acc: 0.2522
Epoch 2/10
 - 29s - loss: 3.4349 - acc: 0.3037 - val_loss: 1.7876 - val_acc: 0.3688
Epoch 3/10
 - 28s - loss: 1.7216 - acc: 0.3904 - val_loss: 1.7223 - val_acc: 0.3940
Epoch 4/10
 - 28s - loss: 1.6453 - acc: 0.4212 - val_loss: 1.6823 - val_acc: 0.4084
Epoch 5/10
 - 28s - loss: 1.5977 - acc: 0.4381 - val_loss: 1.6285 - val_acc: 0.4328
Epoch 6/10
 - 27s - loss: 1.5622 - acc: 0.4492 - val_loss: 1.6211 - val_acc: 0.4273
Epoch 7/10
 - 27s - loss: 1.5284 - acc: 0.4610 - val_loss: 1.5811 - val_acc: 0.4424
Epoch 8/10
 - 27s - loss: 1.5040 - acc: 0.4700 - val_loss: 1.5773 - val_acc: 0.4487
Epoch 9/10
 - 28s - loss: 1.4814 - acc: 0.4756 - val_loss: 1.5819 - val_acc: 0.4403
Epoch 10/10
 - 28s - loss: 1.4498 - acc: 0.4880 - val_loss: 1.5422 - val_acc: 0.4462
Training time=281.99816966056824 Seconds
10000/10000 [==============================] - 3s 330us/step
Accuracy= 0.4502
[3 8 8 ... 5 4 7]

我把上面三種隱藏層神經元之訓練與預測準確率整理如下表 :


 hidden  acc (10th train)  val_acc (10th train) acc (predict)
 256 0.4810 0.4478 0.4526
 512 0.4885 0.4516 0.4564 (+0.84%)
 1024 0.4880 0.4462 0.4502 (-1.36%)


訓練結果圖形比較如下 :


隱藏層神經元數=256

隱藏層神經元數=512

隱藏層神經元數=1024


可見即使隱藏層神經元數目倍增, 準確率並未顯著提升, 甚至增至 1024 個隱藏層神經元時準確率竟然倒退.

參考 :

# How to give CIFAR-10 as an input to MLP
# Numpy Quickstart tutorial

2018年4月12日 星期四

機電整合小木屋

最近在臉書上看到同學分享的 Youtube 影片 :

# 機電整合小木屋

這是一位熱衷於物聯網推廣的連宏城在群眾募資平台 Flyingv 上推出的提案, 目前已募到 128100 元, 遠超過原先設定的 50000 元, 達 2 倍之多.




2 組 35000 元的早鳥優惠已額滿, 剩 1 組 2100 的. 此提案將於 4/23 日結束, 還有 11 天, 欲贊助者從速.

2018年4月11日 星期三

使用 Keras 測試 Cifar-10 圖片資料集

做完 MNIST 資料集的 CNN 辨識測試後, 接下來要改用較複雜的 Cifar-10 資料集. Cifar-10 據書上說是由深度學習大師 Geoffrey Hinton 教授與其在加拿大多倫多大學的學生 Alex Krixhevsky 與 Vinoid Nair 所整理之影像資料集, 包含 6 萬筆 32*32 低解析度之彩色圖片, 其中 5 萬筆為訓練集; 1 萬筆為測試集, 是機器學習中常用的圖片辨識資料集 :




Cifar-10 的所有圖片被分為 10 個類別 (以 0~9 數字作為 Label 之編碼) :
  • 0 : airplain (飛機)
  • 1 : automobile (汽車)
  • 2 : bird (鳥)
  • 3 : cat (貓)
  • 4 : deer (鹿)
  • 5 : dog (狗)
  • 6 : frog (青蛙)
  • 7 : horse (馬)
  • 8 : ship (船)
  • 9 : truck (卡車)
參考 :

# https://en.wikipedia.org/wiki/CIFAR-10
# https://www.cs.toronto.edu/~kriz/cifar.html  (可下載 Cifar-10 資料集)

Cifar-10 名稱來自加拿大高等研究院 (Canadian Institute For Advanced Research), 10 表示其包含 10 種類別圖片. Cifar-10 事實上是一個包含 8000 萬個已標記 (Labeled) 圖庫的子集合, 它還有一個更大的姊妹 Cifar-100 資料集, 同樣包含 60000 個圖片, 但有100 種類別.

Keras 有提供處理 Cifar-10 資料集之模組 cifar10, 可利用 Keras 建構機器學習模型, 利用 5 萬筆訓練集圖片訓練模型中之參數, 然後用訓練好的模型來預測 1 萬筆測試集中的圖片屬於 10 種類別中的哪一種. 以下是依據林大貴的 "TensorFlow+Keras 深度學習人工智慧實務應用" 第 9 章進行測試並記錄結果.

本系列之前的測試紀錄參考 :

# Windows 安裝深度學習框架 TensorFlow 與 Keras
# 使用 Keras 測試 MNIST 手寫數字辨識資料集
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (一)
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (二)
# 使用 Keras 卷積神經網路 (CNN) 辨識手寫數字


1. 匯入 Keras 的 cifar10 模組


D:\Python\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keras.datasets import cifar10   
Using TensorFlow backend.
>>> import numpy as np   
>>> np.random.seed(10) 


2. 載入 Cifar-10 資料集

呼叫 cifar10.load_data() 即自動從 Alex Krixhevsky 在多倫多大學的 Cifar-10 網站下載資料集檔案 cifar-10-python.tar.gz 至 C:\使用者目錄的 .keras 子目錄下, 並自動將資料集解壓縮至 cifar-10-batches-py 子目錄下 :

>>> (x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data() 
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 371s 2us/step

在呼叫 load_data() 後觀察 Windows C:\使用者目錄 (此處為 Tony) 下的 .keras 目子目錄, 可以發現下載完成後會在 .keras 下自動產生一個子目錄 cifar-10-batches-py 來存放解出來的資料集:




切換至 cifar-10-batches-py 子目錄可見有 8 個檔案, 其中 data_batch_1~5 為總數 5 萬筆之訓練集 (每一個檔案各 1 萬筆), 而 test_batch 為 1 萬筆之測試集 : 




cifar10.load_data() 的傳回值為訓練集/測試集數字圖片陣列與其標籤陣列所組成之 tuple, 利用陣列的方法就可以取用 Cifar-10 資料集中的圖片了.

與 mnist.load_data() 不同的是, cifar10.load_data() 會固定去 Cifar-10 網頁下載資料集, 如果因為防火牆的阻擋而無法下載資料集的話, 就會出現 "連線嘗試失敗" 的錯誤訊息. 即使在 Cifar-10 網頁下載資料集的壓縮檔 (有 Python, Matlab, 以及 C 語言用的二進檔等三種版本, 我下載的是 Python 版的 cifar-10-python.tar.gz, 大約 163 MB) 自行解壓縮到上述之 .keras 目錄, 呼叫 load_data() 不會去檢查 .keras 目錄下是否已有 Cifar-10 資料集, 因此還是出現連線錯誤訊息 :

>>> (x_train_image, y_train_label), (x_test_image, y_test_label)=cifar10.load_data()
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
Traceback (most recent call last):
  File "C:\Python36\lib\urllib\request.py", line 1318, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "C:\Python36\lib\http\client.py", line 1239, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "C:\Python36\lib\http\client.py", line 1285, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "C:\Python36\lib\http\client.py", line 1234, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "C:\Python36\lib\http\client.py", line 1026, in _send_output
    self.send(msg)
  File "C:\Python36\lib\http\client.py", line 964, in send
    self.connect()
  File "C:\Python36\lib\http\client.py", line 1392, in connect
    super().connect()
  File "C:\Python36\lib\http\client.py", line 936, in connect
    (self.host,self.port), self.timeout, self.source_address)
  File "C:\Python36\lib\socket.py", line 722, in create_connection
    raise err
  File "C:\Python36\lib\socket.py", line 713, in create_connection
    sock.connect(sa)
TimeoutError: [WinError 10060] 連線嘗試失敗,因為連線對象有一段時間並未正確回應,或是連線建立失敗,因為連線的主機無法回應。


3. 查詢資料集

利用陣列的 len() 函數可以查詢陣列長度 :

>>> print('train image numbers=', len(x_train_image))  #顯示訓練圖片筆數 : 5 萬筆
train image numbers= 50000
>>> print('train label numbers=', len(y_train_label))      #顯示訓練標籤筆數 : 5 萬筆
train label numbers= 50000
>>> print('test image numbers=', len(x_test_image))       #顯示訓練標籤筆數 : 1 萬筆
test image numbers= 10000
>>> print('test label numbers=', len(y_test_label))           #顯示測試標籤筆數 : 1 萬筆
test label numbers= 10000

利用陣列的 shape 屬性可查詢文字圖片的外型屬性, 包含筆數, 解析度, 以及色版數目 :

>>> print('x_train_image:', x_train_image.shape)   #顯示訓練集圖片之 shape
x_train_image: (50000, 32, 32, 3)     #訓練集為 5 萬筆之 32*32 RGB 彩色圖片
>>> print('y_train_label:', y_train_label.shape)       #顯示訓練集標籤之 shape
y_train_label: (50000, 1)                   #訓練集標籤為 5 萬筆 0~9 數字
>>> print('x_test_image:', x_test_image.shape)        #顯示測試集圖片之 shape
x_test_image: (10000, 32, 32, 3)      #測試集為 1 萬筆之 32*32 RGB 彩色圖片
>>> print('y_test_label:', y_test_label.shape)            #顯示測試集標籤之 shape
y_test_label: (10000, 1)                    #測試集標籤為 1 萬筆 0~9 數字

可見訓練集之數字圖片陣列共有 50000 筆 32*32 解析度的彩色 RGB 圖片 (色版=3); 而測試集則有 10000 筆 32*32 解析度的彩色 RGB 圖片, 不論是訓練集還是測試集, 傳回值 x_train_image 與 x_test_image 均為 3 維陣列, 利用索引 0~49999 可以查詢 5 萬筆訓練集圖片之內容, 測試集圖片的索引範圍則為 0~9999. 例如訓練集的第一張圖片資料為陣列 x_train_image[0] :

>>> print('x_train_image[0]=', x_train_image[0])   
x_train_image[0]= [
 [[ 59  62  63]                   #第 1 列畫素開始 (0)
  [ 43  46  45]
  [ 50  48  43]
  ...
  [158 132 108]
  [152 125 102]
  [148 124 103]]               #第 1 列畫素結束 (31)

 [[ 16  20  20]                   #第 2 列畫素開始 (0)
  [  0   0   0]
  [ 18   8   0]
  ...
  [123  88  55]
  [119  83  50]
  [122  87  57]]                 #第 2 列畫素結束 (31)

 [[ 25  24  21]
  [ 16   7   0]
  [ 49  27   8]
  ...
  [118  84  50]
  [120  84  50]
  [109  73  42]]

 ...

 [[208 170  96]
  [201 153  34]
  [198 161  26]
  ...
  [160 133  70]
  [ 56  31   7]
  [ 53  34  20]]

 [[180 139  96]
  [173 123  42]
  [186 144  30]
  ...
  [184 148  94]
  [ 97  62  34]
  [ 83  53  34]]

 [[177 144 116]                    #第 32 列畫素開始 (0)
  [168 129  94]
  [179 142  87]
  ...
  [216 184 140]
  [151 118  84]
  [123  92  72]]]                   #第 32 列畫素結束 (0)

由於資料太長, 所以輸出被自動節略了. 不過從輸出可知, 每一個畫素以 1*3 向量 [R G B] 形式來表示, 第一維表示列, 後兩維表示每一列畫素, 因此一張圖總共有 32*32=1024 個 1 維向量, 有 32*32*3=3072 個數字. 訓練集第一張圖的第一列畫素為 x_train_image[0][0], 由 32 個一維向量組成 :

>>> print('x_train_image[0][0]=', x_train_image[0][0])   
x_train_image[0][0]= [[ 59  62  63]
 [ 43  46  45]
 [ 50  48  43]
 [ 68  54  42]
 [ 98  73  52]
 [119  91  63]
 [139 107  75]
 [145 110  80]
 [149 117  89]
 [149 120  93]
 [131 103  77]
 [125  99  76]
 [142 115  91]
 [144 112  86]
 [137 105  79]
 [129  97  71]
 [137 106  79]
 [134 106  76]
 [124  97  64]
 [139 113  78]
 [139 112  75]
 [133 105  69]
 [136 105  74]
 [139 108  77]
 [152 120  89]
 [163 131 100]
 [168 136 108]
 [159 129 102]
 [158 130 104]
 [158 132 108]
 [152 125 102]
 [148 124 103]]

訓練集第一張圖片的第一個畫素 x_train_image[0][0][0] 如下 :

>>> print('x_train_image[0][0][0]=', x_train_image[0][0][0])   
x_train_image[0][0][0]= [59 62 63]

向量中的 3 個數值分別為 RGB 顏色之色碼 (0~255), 一張圖片由三個色版相疊而成, 訓練集第一張圖的數字圖片結構之示意圖如下 : 


標籤 (Label) 是二維陣列結構, 訓練集之標籤如下 (共 5 萬筆), 可以用單索引或雙索引擷取 :

>>> print('y_train_label=', y_train_label)    #顯示全部訓練集標籤
y_train_label= [[6]
 [9]
 [9]
 ...
 [9]
 [1]
 [1]]
>>> print('y_train_label[0][0]=', y_train_label[0][0])   #第一張圖片是分類 6 (青蛙)
y_train_label[0][0]= 6
>>> print('y_train_label[49999][0]=', y_train_label[49999][0])  #雙索引
y_train_label[49999][0]= 1
>>> print('y_train_label[49999]=', y_train_label[49999])   #單索引
y_train_label[49999]= [1]


4. 顯示訓練集圖片 

顯示 Cifar-10 資料集中的圖片可利用 matplotlib.pyplot 模組的 imshow() 函數, 參考之前 MNIST 測試中以 imshow() 為主的自訂繪圖函數 plot_image() :

# 使用 Keras 測試 MNIST 手寫數字辨識資料集  (步驟 6)

先自訂 plot_image() 函數再呼叫它來顯示訓練集的第一張圖片與其標籤 :

>>> import matplotlib.pyplot as plt   
>>> def plot_image(image):                       
...  fig=plt.gcf()                                       
...  fig.set_size_inches(2, 2)                                 
...  plt.imshow(image, cmap='binary')   
...  plt.show() 
...
>>> print(y_train_label[0])       #第一張圖片為類別 6 之青蛙 
[6]
>>> plot_image(x_train_image[0])       #顯示第一張圖片

將此圖放大後可清楚看出此青蛙圖片是由 32*32 的畫素組成, 每一個畫素之顏色即由上述之一維向量中的 RGB 三原色所決定 :


我將上面的顯示圖片指令寫成如下可在命令列執行之程式檔 :

#show_cifar10_train_image0.py
from keras.datasets import cifar10
import matplotlib.pyplot as plt

def plot_image(image):                         #自訂繪圖函數
 fig=plt.gcf()                                          #取得 pyplot 物件參考
 fig.set_size_inches(2, 2)                       #設定畫布大小為 2 吋*2吋
 plt.imshow(image, cmap='binary')       #以 binary (灰階) 顯示 28*28 圖形
 plt.show()                                             #顯示圖形

(x_train_image, y_train_label), \
(x_test_image, y_test_label)=cifar10.load_data()  #載入 MNIST 資料集
print(y_train_label[0])                           #顯示第一筆樣本之標籤 (label)
plot_image(x_train_image[0])               #繪製第一筆樣本之圖形

執行結果如上圖 :

D:\Python\test>python show_cifar10_train_image0.py
Using TensorFlow backend.
[6]


可同時顯示多張 Cifar-10 圖片的程式改編如下 :

#show_cifar10_train_images.py
import sys
from keras.datasets import cifar10
import matplotlib.pyplot as plt

label_dict={0:"airplain",1:"automobile",2:"bird",3:"cat",4:"deer",5:"dog",
            6:"frog",7:"horse",8:"ship",9:"truck"}  #轉換標籤為類別名稱用

def plot_images_labels_prediction(images,labels,prediction,idx,num=10):
   fig=plt.gcf()                                           #取得 pyplot 物件參考
   fig.set_size_inches(12, 14)                    #設定畫布大小為 12 吋*14吋
   if num > 25: num=25                       #限制最多顯示 25 個子圖
   for i in range(0, num):                            #依序顯示 num 個子圖
       ax=plt.subplot(5, 5, i+1)                     #建立 5*5 個子圖中的第 i+1 個
       ax.imshow(images[idx], cmap='binary')      #顯示子圖
       title=str(idx) + "." + label_dict[labels[idx][0]] + str(labels[idx]) 
       if len(prediction) > 0:                    #有預測值就加入標題中
           title += ",predict=" + str(prediction[idx])
       ax.set_title(title, fontsize=10)            #設定標題
       ax.set_xticks([]);                                #不顯示 x 軸刻度
       ax.set_yticks([]);                                #不顯示 y 軸刻度
       idx += 1                                              #樣本序號增量 1
   plt.show()                                                #繪製圖形

(x_train_image, y_train_label), \
(x_test_image, y_test_label)=cifar10.load_data() #載入 Cifar-10 資料集
i=int(sys.argv[1])                    #取得第一個命令列參數 ()
j=int(sys.argv[2])                    #取得第二個命令列參數
plot_images_labels_prediction(x_train_image,y_train_label,[],i,j)    #無預測值

與之前 MNIST 時不同之處是 title 顯示的部分, 串接了圖片索引, 類別名稱, 以及標籤, 其中類別名稱是利用 label_dict 字典將標籤轉成名稱.

以下是顯示訓練集第一張開始的 25 張圖片 :;

D:\Python\test>python show_cifar10_train_images.py 0 25
Using TensorFlow backend.




訓練集最後 25 張圖片開始索引為 49975 :

D:\Python\test>python show_cifar10_train_images.py 49975 25
Using TensorFlow backend.




Cifar-10 的彩色圖片比單調的 MNIST 要賞心悅目多了.

2018年4月9日 星期一

向博客來購書三本

過年時在博客來尋找關於意識與認知方面書籍, 找到 "意識究竟從何而來?" 改版新書 (圖書館有此書之舊版書), 以及 "章魚,心智,演化:探尋大海及意識的起源", 這兩本好書七折優惠到明天為止, 還好今天突然想起, 否則過明天優惠就沒了. 順便買了今日 66 折溫國信的 "2018雪球股年報", 三本合計 837 元 :




我長久以來對人類大腦功能及意識能力非常有興趣, 最近整理臥室書櫥找到好幾年前買的傑夫霍金斯寫的 "創智慧", 看了其中一章談哺乳類大腦皮質的記憶與預測功能令我驚豔, 原來爬蟲類沒有皮質, 所以智慧無法進化! 等看完再來整理書摘.

本來還想買下列兩本, 但查詢市圖有書就用借的 :
  1. 超值投資:價值投資贏家的選股策略:價值投資贏家的選股策略 (市圖)
  2. 決斷的演算:預測、分析與好決定的11堂邏輯課 (市圖)
第一本是談戰勝大盤的投資策略, 是價值投資的好書. 第二本是演算法的科普書, 書評極好, 聽說即使是非理工背景也可看懂.

2018年4月8日 星期日

使用 Keras 卷積神經網路 (CNN) 辨識手寫數字

三月初做完 MLP 測試後, 接著就開始看 CNN (卷積神經網路), 但原理部分需要時間消化, 加上又分心去學習 Node.js, 使得機器學習的進度緩了下來. 好消息是, 兩周前在鄉下的圖書館挖到寶-找到文魁出版的 "TensorFlow 之外的好選擇 : Keras Caffe SK-Learn 機器學習實作" :
Source : 天瓏

此書為大陸簡體書的繁體版, 內容比林大貴寫的 "TensorFlow+Keras 深度學習人工智慧實務應用" 在原理說明上要詳細些, 雖然數學不多, 但有些章節也不是我這初學者看一遍就能懂的. 此書除了講 Keras 外還介紹了 Caffe, 這是以 Shell Script + 設定檔為主的框架, 比起 Keras 的過度包裝 (但簡單好用), Caffe 在模型參數選項較詳細較自由, 執行速度也較快. 等 Keras 摸熟了再來試試 Caffe.

此書範例程式可在 GitHub 下載 (作者阿布微博 : abu_quant) :

# http://github.com/bbfamily/abu


本系列之前的測試紀錄參考 :

# Windows 安裝深度學習框架 TensorFlow 與 Keras
# 使用 Keras 測試 MNIST 手寫數字辨識資料集
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (一)
# 使用 Keras 多層感知器 MLP 辨識手寫數字 (二)

卷積神經網路 CNN 是法國人 Yann LaCun (揚-勒丘恩) 於貝爾實驗室任職時所提出, 是一種深度學習結構, 他也是在那時蒐集與建立了 MNIST 手寫辨識資料集. 卷積 (Convolution, 又譯為旋積或摺積) 是一種線性運算,  兩個函數 f 與 g 之卷積是將 f 與經過平移與翻轉的 g 相乘後生成的新函數 f*g. 若將 g 視為一個加權函數, 則卷積 f *g 相當於 f 與 g 的滑動加權總和. 卷積與傅立葉轉換有密切關係, 在快速傅立葉轉換中, 兩個離散訊號在時域的卷積相當於它們的傅立葉轉換在頻域的乘積, 參考 :

# Wiki : 卷積
# Wiki : 深度學習

而卷積神經網路 CNN 則是將離散卷積運算應用在人工神經網路, 藉由導入多個卷積層擷取空間特徵來提升辨識率, 透過隨機產生的卷積核心 (Kernel) 將一張圖片轉換成多張圖片, 這些卷積核心如同濾鏡 (filter) 一般, 卷積運算相當於將原始圖片通過不同濾鏡的過濾效果來提取或突顯各種空間特徵, 例如邊緣與輪廓等等資訊, 參考 :

# Wiki : 卷積神經網絡
# 卷積神經網路的運作原理

下列影片演示了卷積神經網路運作之原理 :

# How Convolutional Neural Networks work




多層卷積層神經元對於具體特徵不明確的影像或語音辨識很有用, CNN 的每一層都在做模式比對, 後面的 Layer 是對前面 Layer 比對出來的 Pattern 再做比對, 使得特徵越來越具體. 在監督式學習中, 通過 Pre-training 將樣本的特徵編碼為各層神經元中的權重 (weight) 與偏權值 (bias), 所謂的學習其實就是一種編碼 (Encoding) 動作 (稱為 Autoencoder), 將輸入訊息的特徵儲存於網路參數裡, 是對資料表達形式的一種結構性轉換.

但是多層結構也會產生過度擬合 (Overfitting) 與運算量膨脹問題, 需要 Dropout (部分丟棄) 與 Denoising (去雜訊) 來彌補, 參考 Fukuba (林志傑) 的學習筆記 :

# 林軒田教授機器學習技法 MACHINE LEARNING TECHNIQUES 第 13 講學習筆記

卷積神經網路主要是在分類模型之前導入卷積層與池化層. 卷積層用來對圖像進行卷積運算, 卷積運算保留了圖像的位置關係; 而池化層則用來降低圖像解析度, 減少後面神經網路之運算量. 具體的離散卷積運算, 在齋藤康毅寫的 "Deep Learning" 一書中有一個簡明的範例, 使用兩個矩陣說明如何計算其卷積 :




左邊的 4*4 矩陣為簡化的二維圖像資料, 右邊的 3*3 矩陣為一種濾鏡 (稱為卷積核心), 它攜帶了卷積運算所需之參數. 卷積運算是讓濾鏡在圖像矩陣內以固定間隔移動 (稱為 stride, 步幅), 並與重疊之圖像做積和運算, 亦即相對元素相乘後結果相加, 以上面範例來說, 3*3 濾鏡只能在 4*4 圖像矩陣中移動 4 次, 因此計算結果是一個 2*2 矩陣. 注意, 不要把矩陣的卷積與內積搞混了, 內積是列乘行之積和運算, 卷積是重疊對應元素之積和運算.

注意, 作為卷積核心 (Kernel) 的濾鏡通常為單數因次, 例如 3*3, 5*5, 7*7 ... 等, 雖然這並非必要 (複數因次的濾鏡也是可以運算), 但二維圖像在做卷積運算的重疊與平移時需要一個卷積中心點, 複數因次沒有一個中心點, 要單數因次才有.

卷積計算步驟如下 :

首先將濾鏡套在圖像的左上角, 然後將重疊的 3*3 矩陣相對元素做積和運算得到 15 :

第一列 : 1*2+2*0+3*1=5
第二列 : 0*0+1*1+2*2=5
第三列 : 3*1+0*0+1*2=5

全部相加和為 15. 接著將濾鏡向右移動一格 (stride=1),  卷積運算值為 16 :

第一列 : 2*2+3*0+0*1=4
第二列 : 1*0+2*1+3*2=8
第三列 : 0*1+1*0+2*2=4

然後將濾鏡向下移動一格到左下角,  卷積運算值為 6 :

第一列 : 0*2+1*0+2*1=2
第二列 : 3*0+0*1+1*2=2
第三列 : 2*1+3*0+0*2=2

最後將濾鏡向右移動一格到右下角,  卷積運算值為 15 :

第一列 : 1*2+2*0+3*1=5
第二列 : 0*0+1*1+2*2=5
第三列 : 3*1+0*0+1*2=5

不過上面這個計算卷積的方式會造成資料衰減, 讓原來的 4*4 圖像資料解析度縮減為 2*2, 經過多層卷積運算後將失去部分特徵. 為了不在卷積運算中喪失資訊, 可以在原圖像周圍先進行 "填補" (Padding, 填補值一般用 0) 以擴充解析度, 例如上面的 4*4 圖像經過四周填補一格即變成 5*5 解析度 :


濾鏡在此經過填補後的 5*5 圖像內以步幅 Stride=1 遊走進行卷積計算, Padding 的部分以 0 填補 , 得到的結果圖像解析度還是一樣 4*4, 計算程序如下所示 :



















可見使用這種填補方式進行步幅為 1 的卷積濾鏡運算後得到的圖像矩陣解析度仍然是 4*4, 這種 Stride=1 的移動方式在 Keras 中稱為 "same" 模式. 利用不同濾鏡從原始圖像就可以產生解析度一樣的多張圖像, 分別呈現原始圖像的各種空間資訊, 例如強化輪廓等等.

注意, 維持解析度不變的條件是步幅為 1 格, 上述運算中如果使用 2 格步幅的話, 卷積運算後將得到 2*2 的圖像輸出. 在齋藤康毅寫的 "Deep Learning" 一書中有列出輸入解析度 (W, H), 填補格數 P, 濾鏡大小 (FW, FH), 步幅 S, 與輸出解析度 (OW, OH) 之關係如下 :

OW=(W + 2P - FW) / S + 1
OH=(H + 2P - FH) / S + 1

例如上面的例子, 輸入解析度 (W=4, H=4), 填補格數 P=1, 濾鏡大小 (FW=3, FH=3), 步幅 S=1, 因此輸出解析度 OW=(4 + 2 * 1 - 3) / 1 + 1=4, OH=(4 + 2 * 1 - 3) / 1 + 1=4.

除了卷積層外, 卷積神經網路還導入池化層 (Pooling layer), 其主要目的是 :
  1. 減少資料量與參數數目, 從而減少計算量與過擬合 (Over fitting) 現象
  2. 降低圖像之位置差異
簡而言之, 池化層最主要的目的就是降低圖像資料的解析度來減少計算量, 亦即對原圖進行縮減取樣 (Down-sampling). 方法是利用一個稱為池化核心 (Pooling kernel) 的矩陣疊在原圖上, 如同卷積層的卷積核心 (濾鏡) 一樣在原圖中平移並計算輸出圖像, 步幅則為池化核心的寬度或高度 (即核心平移時不重疊). 注意, 池化核心不攜帶參數, 而是攜帶最大值或平均值等運算. 

常用的池化計算為 Max pooling (最大池化), 亦即在池化核心範圍內選取最大值輸出. 例如解析度 28*28 的圖像使用 2*2 的池化核心將得到 14*14, 也就是解析度降為原來的 1/4 (由 784 個畫素降為 196 個). 池化函數有多種, 除了最大池化外, 還有平均池化 (Average pooling), 亦即在池化核心範圍內計算圖像平均值作為輸出. 最大池化會突出最顯著的特徵像素; 而平均池化則會使圖像勻化.

以上面卷積層的輸出為例, 經過 2*2 池化核心的最大池化與平均池化 (相加後除以 4 再取四捨五入) 的結果如下 :





可見最大池化會挑出比較突出之圖像特徵; 而平均池化則是產出較勻化之輸出. 池化層除了是將圖像解析度縮減外還有兩個特性 :
  1. 池化層只是進行簡單的最大值運算, 故沒有學習參數
  2. 池化層對輸入資料的微小差異很穩健 (robust)
所謂穩健的意思就是抗雜訊效果, 例如將上面的圖像資料向右平移一格並填入其他值, 最大池化結果僅一個或兩個有變化, 甚至可能完全不變, 例如 :





上面第一個只有左上角的畫素不同, 而第二個則是池化後完全不變.

以上關於 CNN 卷積層與池化層的結構摘要整理如下 :
  1. 卷積核心的尺寸通常為單數; 而池化核心的尺寸通常為偶數.
  2. 卷積核心有攜帶參數進行積和運算; 池化核心不帶參數只帶最大值或平均值運算.
  3. 卷積核心平移之步幅為 1 (重疊); 池化核心平移之步幅為其尺寸 (不重疊).
深度學習使用多重的卷積-池化層來深化特徵擷取, 在林大貴寫的 "TensorFlow+Keras 深度學習人工智慧實務應用" 第 8 章使用雙重的卷積-池化層來擷取 MNIST 手寫數字資料集圖片之空間特徵. 兩個卷積層都使用 ReLu 非線性激活函數, 其結構如下 :




上圖描述雙重的卷積-池化層模型結構, 輸入層為 28*28 解析度之灰階 MNIST 圖片, 經過卷積層 1 隨機產生的 16 個 5*5 濾鏡 (卷積核心) 進行卷積運算後產生 16 個 28*28 解析度之圖片 (步幅=1 解析度不變), 然後經過池化層 1 以 2*2 的最大值池化核心將 16 張 28*28 解析度圖片轉成 16 張 14*14 解析度圖片 (步幅=2, 尺寸減半, 解析度降為 1/4). 

卷積層 2 則用隨機產生的 36 個 5*5 濾鏡 (卷積核心) 對池化層 1 輸出的 16 張 14*14 解析度圖片進行卷積運算, 這會產生 36 個 14*14 解析度之圖片 (步幅=1 解析度不變), 然後經過池化層 2 以 2*2 的最大值池化核心將 36 張 14*14 解析度圖片轉成 36 張 7*7 解析度圖片 (步幅=2, 尺寸減半, 解析度降為 1/4). 池化層的輸出經過 flatten() 函數全部展成 1*1764 的一維特徵向量, 然後送進 MLP 全連接神經網路分類模型進行學習與辨識, 結構如下 : 




注意, 此處第一層改稱為平坦層, 因為輸入層已經往前移到卷積層 1 了. 由於池化層 2 輸出 36 張 7*7 的圖片, 經 flatten() 展開後變成 1*1764 的一維向量, 因此平坦層為 1764 個神經元, 使用 ReLu 激活函數; 隱藏層則改為 128 個神經元, 使用 Softmax 激活函數到輸出層.

以下就根據林大貴的 "TensorFlow+Keras 深度學習人工智慧實務應用" 第 8 章內容來測試 CNN 網路的 MNIST 辨識, 其程序與之前 MLP 測試一樣 :
  1. 資料預處理 (Reshape 為 28*28 單色)
  2. 建立模型 (雙重卷積-池化層 + 平坦層 + 隱藏層 + 輸出層)
  3. 訓練模型 (使用 60000 訓練集)
  4. 評估模型準確率 (使用 10000 訓練集)
  5. 進行預測 (使用 10000 訓練集)
注意, 在上兩篇 Keras 的 MLP 測試中, 預處理階段是將每一張 28*28 灰階解析度的 MNIST 圖片利用 reshape() 函數拉平為 1*768 的一維向量, 這樣其實會失去一些圖像特徵. 在 CNN 中則是利用卷積層來擷取圖片之空間特徵, 因此饋入卷積層的圖片資料必須保持原本的 28*28 解析度, 不可拉平.

以下為 CNN 測試紀錄, 此書範例程式可在博碩網站下載 (IPython 範例檔) :

# www.drmaster.com.tw/download/example/MP21710_example.zip



1. 匯入套件與資料集 : 

首先是匯入 keras 的 Numpy 工具集, Numpy 套件, 以及 MNIST 手寫辨識資料集 :

D:\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [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()

函數 mnist.load_data() 傳回兩組 tuple (其元素均為陣列), 前者為 60000 筆之訓練資料集之圖片與標籤, 後者為 10000 筆測試資料集之圖片與標籤. 圖片皆為 28*28 的二維陣列, 用來表示一張解析度為 28*28 的灰階圖片, 每一個元素代表 0~255 的畫素值. 標籤為一維陣列, 表示圖片所對應之答案 (0~9 之整數), 這些資訊都放在陣列之 shape 屬性中 :

>>> x_train_image.shape
(60000, 28, 28)
>>> x_test_image.shape 
(10000, 28, 28)
>>> y_train_label.shape 
(60000,)
>>> y_test_label.shape 
(10000,)

可見圖片陣列的 shape 屬性值為三元素的 tuple, 第一個元素表示此陣列的元素個數, 訓練集有 60000 個; 測試集有 10000 個. 第二與第三元素表示此二維陣列因次為 28*28, 分別為 x 軸與 y 軸畫素個數. 用索引即可取得圖片內容, 例如 x_train_image[0] 為訓練集第一張圖片; x_train_image[59999] 為訓練集最後一張圖片. 標籤陣列為一維陣列, 故僅有一個元素, 訓練集有 60000 個標籤; 測試集有 10000 個標籤. 用索引即可取得標籤內容, 例如 y_train_label[0] 為訓練集第一張圖片之標籤;  y_train_label[59999] 為訓練集最後圖片之標籤.


2. 資料預處理 :

接著是呼叫 reshape() 函數將陣列轉換成 float32 浮點數, 此處與之前 MLP 時將圖片轉成一維向量拉平作法不同, 為了保存圖片的空間資訊必須保留其二維結構, 因此 reshape() 需傳入 4 個參數 : 第一參數仍是陣列元素個數, 第二與第三參數是二維陣列之因次 (即列與行解析度), 第四參數是色版數目, 因 MNIST 資料集是單色灰階, 故傳入 1 :

>>> x_train=x_train_image.reshape(60000,28,28,1).astype('float32') 
>>> x_test=x_test_image.reshape(10000,28,28,1).astype('float32') 

接下來與 MLP 一樣是用除以畫素最大值 255 的方法將圖片正規化 (Normalization), 亦即 0~255 的像素值就全部變成 0~1 之值了; 標籤部分則呼叫 Keras 的 np_utils.to_categorical() 函數將 0~9 的數值經過 One-hot encoding 編碼 (獨熱編碼) 轉成 10 位元二進碼, 例如 5 變成  0000010000 :

>>> x_train=x_train_image.reshape(60000,28,28,1).astype('float32')
>>> x_test=x_test_image.reshape(10000,28,28,1).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)

這樣資料預處理就完成了.


3. 建立 CNN 模型

接下來是利用 Keras 的線性堆疊模型 Sequential 一層層地建構 CNN 卷積神經網路與其分類模型. 首先須匯入 Sequential 模組, 平面卷積模組 Conv2D, 平面池化模組 MaxPooling2D, 完全連接模組 Dense, 以及放棄模組 Dropout :

>>> from keras.models import Sequential 
>>> from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D 

然後呼叫 Sequential() 建立空的模型物件, 再用其 add() 方法將卷積層 1 與池化層 1, 使用 16 個5*5 的隨機卷積核心 (濾鏡), 以 "same" 邊界模式將一張 28*28 圖片產生 16 層 28*28 圖片 (same 方式不改變影像大小), 並使用 ReLu 非線性函數激活, 然後用 2*2 的池化核心將其解析度降為 14*14 :

>>> model=Sequential()                            #建立空的線性堆疊模組
>>> model.add(Conv2D(filters=16,          #加入卷積層 1 (16 個隨機卷積核心)
...                  kernel_size=(5,5),                                  #卷積核心尺寸 5*5
...                  padding='same',                                    #邊界模式=same (填補=0, 步幅=1)
...                  input_shape=(28,28,1),                          #輸入圖片為 28*28 單色
...                  activation='relu'))                                  #激活函數=ReLu
>>> model.add(MaxPooling2D(pool_size=(2, 2)))   #加入池化層 1 (池化核心 2*2)

然後加入卷積層 2 與池化層 2, 使用 36 個5*5 的隨機卷積核心 (濾鏡), 以 "same" 邊界模式從 16 層之 14*14 圖片產生 36 層的 14*14 圖片 (same 方式不改變影像大小), 並使用 ReLu 非線性函數激活, 然後用 2*2 的池化核心將其解析度降為 7*7 :

>>> model.add(Conv2D(filters=36,           #加入卷積層 2 (36 個隨機卷積核心)
...                  kernel_size=(5,5),                                   #卷積核心尺寸 5*5
...                  padding='same',                                     #邊界模式=same (填補=0, 步幅=1)
...                  activation='relu'))                                   #激活函數=ReLu
>>> model.add(MaxPooling2D(pool_size=(2, 2)))        #加入池化層 2 (池化核心 2*2)

不過這裡有個疑問, 卷積層 2 是如何從 16 層的池化層 1 圖像輸出產生 36 層的卷積層 2 輸出? 由於實際做法被 Keras 封裝起來無法探知, 只能以後再研究了.

接下來為了避免過擬合 (Over fitting) 問題, 最後加上 Dropout 層在每次訓練中放棄部分神經元 (此處為放棄 25% 神經元) :

>>> model.add(Dropout(0.25)) 

這樣便完成了 CNN 網路模型之建置.


4. 建立分類模型 : 

上面經過 CNN 網路學習得到之圖像空間特徵需經過分類模型辨識圖片屬於 0~9 的哪一個, 使用的分類模型是之前的 MLP 多層感知器, MLP 為全連接網路, 是 DNN (Deep Neural Network) 網路的一種. 除了 MLP 外, 也可以使用其他分類模型例如隨機森林等.

此處所用之 MLP 分類模型為三層模型 :
  1. 平坦層 
  2. 隱藏層 
  3. 輸出層 
首先呼叫 Flatten() 建立平坦層, 由於前面 CNN 模型最後池化層 2 之輸出為 36 層的 7*7 圖像, 因此平坦層會將其依序拉平展開為 36*7*7=1764 個一維特徵向量 (畫素), 並建立 1764 個神經元來接收這些特徵值.

其次呼叫 Dense() 建立隱藏層, 具有 128 個以 ReLu 為激活函數的神經元. 同樣地為避免過擬合, 在隱藏層後面會加上一個 Dropout 層, 此處設為 0.5 表示在每次訓練時會放棄 50% 的神經元.

最後呼叫 Dense() 建立輸出層, 具有 10 個以 Softmax 為激活函數之輸出神經元, 其 One-hot 的 0/1 輸出代表 0~9 之數字識別結果 :

>>> model.add(Flatten())                                      #建立平坦層 (36*7*7=1764 個神經元)
>>> model.add(Dense(128, activation='relu'))     #建立隱藏層 (128 個神經元)
>>> model.add(Dropout(0.5))                                 #放棄 50% 神經元
>>> model.add(Dense(10,activation='softmax'))  #建立輸出層 (10 個神經元)


5. 顯示模型摘要 :

建立好 CNN 與分類模型之後可呼叫模型之 summary() 顯示摘要如下 :

>>> print(model.summary())                                  #顯示摘要

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 28, 28, 16)        416
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 16)        0
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 14, 14, 36)        14436
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 7, 7, 36)          0
_________________________________________________________________
dropout_1 (Dropout)          (None, 7, 7, 36)          0
_________________________________________________________________
flatten_1 (Flatten)          (None, 1764)              0
_________________________________________________________________
dense_1 (Dense)              (None, 128)               225920
_________________________________________________________________
dropout_2 (Dropout)          (None, 128)               0
_________________________________________________________________
dense_2 (Dense)              (None, 10)                1290
=================================================================
Total params: 242,062
Trainable params: 242,062
Non-trainable params: 0
_________________________________________________________________
None

從摘要表可知只有卷積層, 隱藏層與輸出層有參數, 而池化層與放棄層都是沒有參數的.


6. 進行神經元訓練 :

模型建好後即可進行訓練 (使用 60000 筆訓練集), 訓練前須呼叫 compile() 函數進行設定 :

>>> model.compile(loss='categorical_crossentropy', 
...               optimizer='adam',metrics=['accuracy']) 

傳入三個參數 :

loss : 損失函數使用交叉熵
optimizer : 訓練時之最佳化方法使用 "adam" (快速收斂且準確度高)
metrics : 評估模型之方式為 "accuracy" (準確度)

設定好訓練參數後即可呼叫 fit() 函數開始訓練, 方式是從 60000 筆訓練集中取 80% (48000 筆) 做訓練, 20% (12000 筆) 做驗證, 共執行 10 次訓練週期 (epoches), 每次訓練時並非一次將 48000 訓練集全部丟進去, 而是分批次, 每批次取 300 筆資料, 因此 48000 筆要分 160 批次才完成一個epoch (訓練週期 ). 注意, 以 CPU 執行 fit() 較耗時, 我的桌上型電腦跑了大約  8 分鐘 :

>>> train_history=model.fit(x=x_train_normalize,
...                         y=y_train_onehot,validation_split=0.2, 
...                         epochs=10, batch_size=300,verbose=2)

此處傳入 6 個參數 :

x=正規化後的 28*28 圖片特徵向量
y=One-hot 編碼的圖片標籤 (答案)
validation_split=驗證資料集占訓練集之比率
epochs=訓練週期
batch_size=每一批次之資料筆數
verbose=顯示選項 (2=顯示訓練過程)

十個 epoches 的訓練過程如下 :

Train on 48000 samples, validate on 12000 samples
Epoch 1/10
2018-04-07 10:46:32.609924: I C:\tf_jenkins\workspace\rel-win\M\windows\PY\36\tensorflow\core\platform\cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX
 - 216s - loss: 0.4859 - acc: 0.8479 - val_loss: 0.0965 - val_acc: 0.9728
Epoch 2/10
 - 215s - loss: 0.1409 - acc: 0.9586 - val_loss: 0.0629 - val_acc: 0.9810
Epoch 3/10
 - 182s - loss: 0.1017 - acc: 0.9694 - val_loss: 0.0514 - val_acc: 0.9841
Epoch 4/10
 - 183s - loss: 0.0841 - acc: 0.9752 - val_loss: 0.0463 - val_acc: 0.9859
Epoch 5/10
 - 186s - loss: 0.0715 - acc: 0.9782 - val_loss: 0.0396 - val_acc: 0.9875
Epoch 6/10
 - 185s - loss: 0.0638 - acc: 0.9809 - val_loss: 0.0401 - val_acc: 0.9877
Epoch 7/10
 - 192s - loss: 0.0556 - acc: 0.9832 - val_loss: 0.0428 - val_acc: 0.9873
Epoch 8/10
 - 185s - loss: 0.0503 - acc: 0.9846 - val_loss: 0.0339 - val_acc: 0.9891
Epoch 9/10
 - 179s - loss: 0.0456 - acc: 0.9863 - val_loss: 0.0340 - val_acc: 0.9897
Epoch 10/10
 - 178s - loss: 0.0431 - acc: 0.9866 - val_loss: 0.0339 - val_acc: 0.9902

與之前 MLP 的訓練結果 (約 0.9765) 比較 , 準確率提高了 1.4%. 十個 epoches 的訓練結果會以 loss (訓練集損失), acc (訓練集準確度), val_loss (驗證集損失), 以及 val_acc (訓練集準確度) 為鍵儲存在 dict 型態變數中回傳, 查詢訓練結果如下 :

>>> train_history.history["loss"]   
[0.48587896302342415, 0.14091337535064669, 0.10172861367464066, 0.08413523898925632, 0.07145443321787752, 0.06379812054801733, 0.05555043590720743, 0.05027689200942405, 0.04557928261347115, 0.043080858269240706]
>>> train_history.history["acc"]   
[0.8479166681412608, 0.9585624933242798, 0.9693541698157787, 0.9751666773110628, 0.9782291784882545, 0.9809375133365392, 0.9831875145435334, 0.9846250142902135, 0.9862500127404928, 0.9866041786968708]
>>> train_history.history["val_loss"]   
[0.0964868551120162, 0.06290977750904858, 0.05137179638259113, 0.04628049440216273, 0.03960396841866896, 0.040075597888790074, 0.042766022717114535, 0.03386718961992301, 0.034033434221055356, 0.03393690842203796] 
>>> train_history.history["val_acc"] 
[0.9727500036358834, 0.9810000091791153, 0.9840833440423011, 0.9859166786074638, 0.9875000104308128, 0.9876666769385338, 0.9873333424329758, 0.989083343744278, 0.9896666765213012, 0.9901666760444641]

可使用前次測試 MLP 時所寫的 matplotlib 函數來繪製訓練紀錄, 描繪誤差值 (loss) 與準確度 (acc) 隨 epoch 變化的情形 :

>>> import matplotlib.pyplot as plt 
>>> 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()
...
>>> show_train_history(train_history) 




可見隨著訓練週期增加, 誤差值越來越低, 而準確率越來越高.


7. 評估模型準確率 : 

以 60000 筆訓練集完成模型的 10 個訓練週期後, 最後的準確度可達 99%, 接下來可呼叫 evaluate() 函數來評估此訓練過的模型對 10000 筆測試集之準確度有多少 ? 傳入參數為測試集之正規化數字圖片以及其 onehot 編碼之標籤, 傳回值型態為串列, 其中準確率放在索引 1 :

>>> scores=model.evaluate(x_test_normalize, y_test_onehot)   
10000/10000 [==============================] - 16s 2ms/step
>>> print("Accuracy=", scores) 
Accuracy= [0.02592632946753947, 0.9909] 
>>> print("Accuracy=", scores[1]) 
Accuracy= 0.9909 

可見與訓練集 10 週期訓練結果之 0.9902 相近.


8. 以測試集進行預測 : 

最後就是呼叫 predict_classes() 傳入正規化後的測試集陣列 (10000 筆資料) 進行預測, 結果會放在一個陣列中傳回 :

>>> prediction=model.predict_classes(x_test_normalize) 
>>> print(prediction)     #只列出前後各 3 個辨識結果
[7 2 1 ... 4 5 6]                         
>>> 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)


9. 顯示混淆矩陣 : 

使用 pandas 套件的 crosstab() 函數可用來建立混淆矩陣以觀察那些數字比較會被誤認, 傳入參數為測試集的 10000 個標籤 (答案) y_test_label 以及上面得到預測結果 prediction :

>>> import pandas as pd 
>>> pd.crosstab(y_test_label, prediction, rownames=['label'],colnames=['predict'])
predict    0     1     2     3    4    5    6     7    8    9
label
0        977     0     0     0    0    0    2     1    0    0
1          0  1130     1     0    0    1    1     1    1    0
2          2     0  1027     0    1    0    0     2    0    0
3          0     0     1  1004    0    3    0     2    0    0
4          0     0     0     0  974    0    1     0    1    6
5          1     1     0     3    0  884    2     0    0    1
6          5     2     0     1    1    1  948     0    0    0
7          0     1     3     3    0    0    0  1018    1    2
8          4     0     3     2    1    1    0     2  957    4
9          1     3     2     2    3    2    0     4    0  992

從對角線來看, 5 的辨識率最低, 只有 884 次, 最容易被混淆; 而 1 的辨識率最高, 達 1130 次. 另外, 4 最容易被誤認為 9 達 6 次, 其次是 6 被誤認為 0 達 5 次, 比起純粹使用 MLP 分類模型辨識, 誤認的次數都有降低, 因此辨識成功率提高了.




10. 利用 DataFrame 找出哪些測試樣本被誤認 :

上面的混淆矩陣顯示 4 被誤認為 6 次數最高 (6 次), 6 被誤認為 0 次高 (5 次), 可以利用 pandas 的 DataFrame 找出被誤認的測試樣本是哪些索引, 首先將測試集標籤 y_test_label 與預測結果 prediction 組成 DataFrame 物件, 利用欄位的條件式即可找出被誤認的測試樣本 :

>>> df=pd.DataFrame({'label':y_test_label, 'predict':prediction}) 
>>> df[(df.label==4) & (df.predict==9)]      #標籤為 4 被誤認為 9 者 (6 次)
      label  predict
740       4        9
1242      4        9
2130      4        9
4265      4        9
8520      4        9
9792      4        9
>>> df[(df.label==6) & (df.predict==0)]      #標籤為 6 被誤認為 0 者 (5 次)
      label  predict
259       6        0
445       6        0
965       6        0
2118      6        0
3422      6        0

我將以上指令整合為如下程式 :

#show_cnn_training_history_and_prediction.py
import sys
from keras.datasets import mnist
from keras.utils import np_utils
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D
import pandas as pd

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(x_train_image.shape[0],28,28,1).astype('float32')
x_test=x_test_image.reshape(x_test_image.shape[0],28,28,1).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(Conv2D(filters=16,
                 kernel_size=(5,5),
                 padding='same',
                 input_shape=(28,28,1),
                 activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(filters=36,
                 kernel_size=(5,5),
                 padding='same',
                 activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))
print(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=300,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_normalize)
print(prediction)
pd.crosstab(y_test_label, prediction,rownames=['label'],colnames=['predict'])
df=pd.DataFrame({'label':y_test_label, 'predict':prediction})
df[(df.label==6) & (df.predict==0)]
df[(df.label==4) & (df.predict==9)]


參考 :

# 熵 (Entropy)
# Numpy Quick Tutorial
# 吳老師教學中心 (Python & 機器學習)
# Manning : Deep Learning with Python
# 用tflearn來做深度學習辨識初音
# (台大李宏毅教授教學網站)
# Deep learning for complete beginners: convolutional neural networks with keras
# CNN(卷積神經網絡)、RNN(循環神經網絡)、DNN(深度神經網絡)的內部網絡結構的區別