前言:今天我們要來仿做史上第一個卷積神經網路LeNet-5,並且加入一些我們前面學過的技巧。
LeNet-5 有兩個卷積層,分別有6與16個濾鏡,池化層也有兩個。這次架設卷積神經網路來辨識MNIST手寫數字,不過在結構上我們做了一些變化:卷積層的濾鏡分別增加到32與64個,以及只用一個池化層(池化層減少是深度學習的趨勢), 另外還使用了 LeNet 那時還沒有的 ReLu 激活函數、 dropout 及批次正規化。
老樣子我們先匯入模組,與以往不同的我們加上了三個新模組,在這段程式的最後一行有寫註解的地方。
#匯入模組
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import Dropout
from tensorflow.keras import optimizers
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten # Conv2D是卷積層、MaxPooling2D是池化層、Flatten可以把多維數據轉換為一維數據
在做資料處理的部分,CNN 的第一隱藏層通常就是卷積層,因此不需要把 2D 的輸入圖片展平為 1D。另外要注意,輸入資料應該要轉換成 4D 的張量(tensor),分別是「樣本數 × 寬 × 高 × 顏色通道」(batch_size, height, width, channels),由於MNIST數字為單色影像(灰度圖像),所以顏色通道就設為1
#載入資料集
(X_train, y_train), (X_test, y_test)=mnist.load_data()
#資料集預處理 :
X_train=X_train.reshape(60000, 28, 28, 1).astype('float32') #重塑資料形狀
X_test=X_test.reshape(10000, 28, 28, 1).astype('float32') #重塑資料形狀
X_train /= 255
X_test /= 255
y_train=to_categorical(y_train, 10)
y_test=to_categorical(y_test, 10)
這邊構建的卷積神經網路,我們用了兩個卷積層與一個池化層,輸出經過一個丟棄層丟掉 25% 的特徵後將 2D 陣列展平,然後傳給下一個隱藏層。經過批次正規化後,再經過一個丟棄層丟掉 50% 特徵後送到輸出層。 Conv2D() 是對 2D 陣列作卷積。其中Conv2D()的數字是可以改變的,例如:對 1D 做卷積的函數 Conv1D()、對 3D 做卷積函數 Conv3D()。
在這段程式的第五、第六行處,Conv2D() 沒有指定步長(stride)參數,則默認步長為 1
填補參數的部分我們沒有設定要填,會默認為:padding='valid' (不填補),若要填補則設為 'same',所以第一個卷積層特徵圖邊長為 (28-3+0)/1+1=26,輸出的特徵圖尺寸為 26 * 26 * 32。同理第二個卷積層特徵圖邊長就會是 (26-3+0)/1+1=24,輸出的特徵圖尺寸 24 * 24 * 64。
#架設 CNN 神經網路 :
model=Sequential()
#卷積層
model.add(Conv2D(32, kernel_size=(3, 3), activation='relu',input_shape=(28, 28, 1))) # 第一個卷積層
model.add(Conv2D(64, kernel_size=(3, 3), activation='relu',input_shape=(28, 28, 1))) # 第二個卷積層
# 池化層
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25)) # 第一個丟棄層 (25%)
model.add(Flatten()) # 展平為 1D 陣列
# 隱藏層
model.add(Dense(128, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.5)) #第二個丟棄層 (50%)
#輸出層
model.add(Dense(10, activation='softmax'))
conv2d 第一卷積層輸出的320個參數是這樣來的:權重數目 32 個濾鏡 * 每個濾鏡 9 個參數 (3x3 單一通道) = 288,加上偏值 32 個 (每個濾鏡一個),所以 288 + 32 = 320 個參數。
conv2d_1 第二卷積層輸出的18496個參數是這樣來的:權重數目 64 個濾鏡 * 每個濾鏡 9 個參數 (3x3 單一通道) * 深度 32 (前一層有 32 個濾鏡) = 18432,加上偏值 64 個(每個濾鏡一個),所以 18432 + 64 = 18496 個參數。
max_pooling2d 池化層窗口在尺寸 2x2 ,默認步長為 1 下特徵圖尺寸會縮小為 1/4,所以尺寸是 (12, 12, 64),深度不變為 64。 展平層(flatten)會把 3D 特徵圖展成 1D,故有 12 * 12 * 64 = 9216 個神經元。
dense 第一個密集層有 128 個神經元,與前一層展平層的 9216 個神經元相連,故密集層有 9216 * 128 = 1179776 個參數。
dense_1 第二個密集層是有 10 個神經元的輸出層,有 128 * 10 + 10 = 1290個參數。
整個卷積神經網路(CNN)縱共有 320 + 18496 + 1179776 + 1290 = 1199882 個參數載運作。
另外可以注意到池化、丟棄與展平這三層沒有參數。
#檢視神經網路模型摘要
model.summary()
#編譯模型
model.compile(loss='categorical_crossentropy', optimizer='adam',metrics=['accuracy'])
因 CNN 參數比密集層少很多,因此訓練週期不用太多,我們這邊設為 10就可以了
#訓練模型
model.fit(X_train, y_train, batch_size=128, epochs=10, verbose=1,validation_data=(X_test, y_test))
在第一次訓練準確率就跑到了98%,十次訓練下則有三次訓練到達99%,與我們之前寫的多層感知器(MLP)神經網路(或是密集神經網路(Dense Neural Network))相較而言,根本無法看到卷積神經網路(CNN) 的車尾燈! 不過在計算過程會花比較久的時間,大家需要耐心等一下。