Keras深度學習實戰(7)——卷積神經網絡詳解與實現

Keras深度學習實戰(7)——卷積神經網絡詳解與實現

    • 0. 前言
    • 1. 傳統神經網絡的缺陷
      • 1.1 構建傳統神經網絡
      • 1.2 傳統神經網絡的缺陷
    • 2. 使用 Python 從零開始構建CNN
      • 2.1 卷積神經網絡的基本概念
      • 2.2 卷積和池化相比全連接網絡的優勢
    • 3. 使用 Keras 構建卷積神經網絡
      • 3.1 CNN 使用示例
      • 3.2 驗證 CNN 輸出
    • 4. 構建 CNN 模型識別 MNIST 手寫數字
      • 4.1 任務與模型分析
      • 4.2 CNN 模型構建與訓練
    • 相關鏈接

0. 前言

我們已經學習瞭傳統的深度前饋神經網絡(也可以稱為全連接神經網絡),傳統深度前饋神經網絡的局限性之一是它並不滿足平移不變性,也就是說,在傳統神經網絡看來,圖像右上角的貓與位於圖像中心的貓被視為不同對象,即使實際上這是同一隻貓。另外,傳統的神經網絡受對象大小的影響,如果訓練集中大多數圖像中的對象較大,而訓練數據集圖像中包含相同的對象但占據圖像畫面的比例較小,則傳統的神經網絡可能無法對圖像進行正確分類。
卷積神經網絡 (Convolutional Neural Network, CNN) 的提出正是用於解決傳統神經網絡的這些缺陷。鑒於即使對象位於圖片中的不同位置或其在圖像中具有不同占比,CNN 也能夠正確的處理這些圖像,因此在對象分類/檢測任務中更加有效。

1. 傳統神經網絡的缺陷

為瞭瞭解卷積神經網絡 (Convolutional Neural Network, CNN) 的優勢,我們首先瞭解為什麼在圖像中,如果對象平移或比例改變時前饋神經網絡 (Neural Network, NN) 性能欠佳,然後瞭解 CNN 對比傳統前饋神經網絡的改進。
我們首先考慮使用以下策略,以瞭解 NN 模型的缺陷:

  • 建立一個 NN 模型,以預測 MNIST 手寫數字標簽
  • 獲取所有標簽為 1 的圖像,並取這些圖像的均值生成新圖像
  • 使用構建的 NN 預測在上一步中生成的均值圖像的標簽
  • 將均值圖像向左或向右平移若幹個像素,生成新圖像,並使用 NN 模型對生成的新圖像進行預測

1.1 構建傳統神經網絡

接下來,根據上述策略,編寫代碼實現如下。

  1. 加載所需庫和 MNIST 數據集:
from keras.datasets import mnistfrom keras.layers import Flatten, Densefrom keras.models import Sequentialimport matplotlib.pyplot as pltfrom keras.utils import np_utilsimport numpy as np(x_train, y_train), (x_test, y_test) = mnist.load_data()
  1. 獲取訓練集中標簽為 1 的數字圖片:
x_train1 = x_train[y_train==1]
  1. 將訓練數據整形以符合網絡輸入尺寸要求,並進行數據規范化:
num_pixels = x_train.shape[1] * x_train.shape[2]x_train = x_train.reshape(x_train.shape[0], num_pixels).astype('float32')x_test = x_test.reshape(x_test.shape[0], num_pixels).astype('float32')x_train = x_train / 255.x_test = x_test / 255.
  1. 對圖像標簽進行獨熱編碼:
y_train = np_utils.to_categorical(y_train)y_test = np_utils.to_categorical(y_test)num_classes = y_train.shape[1]
  1. 然後,構建模型並進行擬合:
model = Sequential()model.add(Dense(1024, input_dim=num_pixels, activation='relu'))model.add(Dense(num_classes, activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])model.fit(x_train, y_train,            validation_data=(x_test, y_test),            epochs=5,            batch_size=1024,            verbose=1)
  1. 接下來,使用標簽為 1 的所有圖像的均值生成新圖像,並使用訓練後的模型預測此圖像的標簽。首先,生成圖像:
pic = np.zeros((x_train1.shape[1], x_train1.shape[2]))pic2 = np.copy(pic)for i in range(x_train1.shape[0]):    pic2 = x_train1[i,:,:]    pic = pic + pic2pic = pic / x_train1.shape[0]plt.imshow(pic, cmap='gray')plt.show()

在代碼中,我們初始化瞭一個尺寸為 28 x 28 的空白圖像,並通過循環遍歷 x_train1 中的所有值,即標簽為 1 的所有圖像,將圖像中各個像素位置值進行加和,求取平均像素值。生成的圖像顯示如下:

在上圖中,像素越白,表示人們在此位置書寫的頻率就越高;像素越黑的位置表示書寫頻率越低。可以看到,中間的像素是最白的,這是因為大多數人,都習慣於在中間位置書寫數字。

  1. 最後,查看神經網絡對於此圖像的預測:
p = model.predict(pic.reshape(1, -1)/255.)print(p)c = np.argmax(p)print('神經網絡預測結果:', c)

np.argmax() 函數用於返回一個 Numpy 數組中最大值的索引,在以上示例中,最大值的索引就是模型預測概率最大的類別。以上擬合後的模型,輸出的預測結果如下:

[[8.6497545e-05 9.2336720e-01 2.6006915e-03 5.4899454e-03 4.6638816e-04  8.7751285e-04 6.4074027e-04 2.3328003e-03 6.3134938e-02 1.0033193e-03]]神經網絡預測結果: 1

1.2 傳統神經網絡的缺陷

情景 1:創建一個新圖像,將上一節由所有標簽為 1 的圖像生成均值圖像向左平移 1 個像素。使用以下代碼,我們遍歷圖像的各列,並將下一列的像素值復制到當前列,從而完成向左平移:

for i in range(pic.shape[0]):    if i < 20:        pic[:,i]=pic[:,i+1]plt.imshow(pic, cmap='gray')plt.show()

向左平移 1 個像素後的均值圖像如下所示:

使用訓練完成的的模型預測圖像的標簽:

p = model.predict(pic.reshape(1, -1)/255.)print(p)c = np.argmax(p)print('神經網絡預測結果:', c)

該模型對平移後圖像的預測如下:

[[2.3171112e-03 5.3161561e-01 2.6453543e-02 8.1305495e-03 5.2826328e-04  3.4600161e-02 4.3771293e-02 3.4394194e-04 3.5101682e-01 1.2227822e-03]]神經網絡預測結果:1

我們可以看到盡管模型可以將其正確預測為 1,但是其預測概率要比未平移像素時的概率小的多。

情景 2:創建一個新圖像,將原始平均圖像的像素向右移動瞭 2 個像素:

pic=np.zeros((x_train1.shape[1], x_train1.shape[2]))pic2=np.copy(pic)for i in range(x_train1.shape[0]):    pic2=x_train1[i,:,:]    pic=pic+pic2pic=(pic/x_train1.shape[0])pic2=np.copy(pic)for i in range(pic.shape[0]):    if ((i>6) and (i<26)):        pic[:,i]=pic2[:,(i-3)]plt.imshow(pic, cmap='gray')plt.show()

平移後的平均圖像如下所示:

然後,對該圖像進行預測:

p = model.predict(pic.reshape(1, -1)/255.)print(p)c = np.argmax(p)print('神經網絡預測結果:', c)

該模型對平移後圖像的預測如下:

[[0.00519334 0.0018531  0.07164755 0.33244154 0.3407778  0.00380969  0.00090572 0.19745363 0.0096615  0.03625605]]神經網絡預測結果:4

可以看到模型輸出瞭錯誤的預測結果:4,以上這些問題的存在就是我們需要使用 CNN 的原因。

2. 使用 Python 從零開始構建CNN

在本節中,首先介紹卷積神經網絡 (CNN) 的相關概念與組成,以便瞭解CNN提高平移圖像預測準確率的原理。然後,我們將使用 NumPy 從零開始構建 CNN,來瞭解 CNN 的工作原理。

2.1 卷積神經網絡的基本概念

我們已經學習瞭如何構建經典神經網絡,在本節中,我們瞭詳細介紹 CNN 中卷積過程的工作原理和相關組件。

2.1.1 卷積

卷積是兩個矩陣間的乘法——通常一個矩陣具有較大尺寸,另一個矩陣則較小。要瞭解卷積,首先講解以下示例。給定矩陣 A 和矩陣 B 如下:

在進行卷積時,我們將較小的矩陣在較大的矩陣上滑動,在上述兩個矩陣中,當較小的矩陣 B 需要在較大矩陣 A 的整個區域上滑動時,會得到 9 次乘法運算,過程如下。
在矩陣 A 中從第 1 個元素開始選取與矩陣 B 相同尺寸的子矩陣 [ 1 2 0 1 1 1 3 3 2 ] \left[ \begin{array}{ccc} 1 & 2 & 0\\ 1 & 1 & 1\\ 3 & 3 & 2\\\end{array}\right] 113213012 和矩陣 B 相乘並求和:

1 × 3 + 2 × 1 + 0 × 1 + 1 × 2 + 1 × 3 + 1 × 1 + 3 × 2 + 3 × 2 + 2 × 3 = 29 1\times 3+2\times 1+0\times 1+1\times 2+1\times 3+1\times 1+3\times 2+3\times 2 + 2\times 3=29 1×3+2×1+0×1+1×2+1×3+1×1+3×2+3×2+2×3=29

然後,向右滑動一個窗口,選擇第 2 個與矩陣 B 相同尺寸的子矩陣 [ 2 0 2 1 1 2 3 2 1 ] \left[ \begin{array}{ccc} 2 & 0 & 2\\ 1 & 1 & 2\\ 3 & 2 & 1\\\end{array}\right] 213012221 和矩陣 B 相乘並求和:

2 × 3 + 0 × 1 + 2 × 1 + 1 × 2 + 1 × 3 + 2 × 1 + 3 × 2 + 2 × 2 + 1 × 3 = 28 2\times 3+0\times 1+2\times 1+1\times 2+1\times 3+2\times 1+3\times 2+2\times 2 + 1\times 3=28 2×3+0×1+2×1+1×2+1×3+2×1+3×2+2×2+1×3=28

然後,再向右滑動一個窗口,選擇第 3 個與矩陣 B 相同尺寸的子矩陣 [ 0 2 3 1 2 0 2 1 2 ] \left[ \begin{array}{ccc} 0 & 2 & 3\\ 1 & 2 & 0\\ 2 & 1 & 2\\\end{array}\right] 012221302 和矩陣 B 相乘並求和:

0 × 3 + 2 × 1 + 3 × 1 + 1 × 2 + 2 × 3 + 0 × 1 + 2 × 2 + 1 × 2 + 2 × 3 = 25 0\times 3+2\times 1+3\times 1+1\times 2+2\times 3+0\times 1+2\times 2+1\times 2 + 2\times 3=25 0×3+2×1+3×1+1×2+2×3+0×1+2×2+1×2+2×3=25

當向右滑到盡頭時,向下滑動一個窗口,並從矩陣 A 左邊開始,選擇第 4 個與矩陣 B 相同尺寸的子矩陣 [ 1 1 1 3 3 2 1 0 2 ] \left[ \begin{array}{ccc} 1 & 1 & 1\\ 3 & 3 & 2\\ 1 & 0 & 2\\\end{array}\right] 131130122 和矩陣 B 相乘並求和:

1 × 3 + 1 × 1 + 1 × 1 + 3 × 2 + 3 × 3 + 2 × 1 + 1 × 2 + 0 × 2 + 2 × 3 = 30 1\times 3+1\times 1+1\times 1+3\times 2+3\times 3+2\times 1+1\times 2+0\times 2 + 2\times 3=30 1×3+1×1+1×1+3×2+3×3+2×1+1×2+0×2+2×3=30

然後,繼續向右滑動,並重復以上過程滑動矩陣窗口,直到滑動到最後一個子矩陣為止,得到最終的結果 [ 29 28 25 30 30 27 20 24 34 ] \left[ \begin{array}{ccc} 29 & 28 & 25\\ 30 & 30 & 27\\ 20 & 24 & 34\\\end{array}\right] 293020283024252734

完整的卷積計算過程如以下動圖所示:

通常,我們把較小的矩陣 B 稱為濾波器 (filter) 或卷積核 (kernel),使用 ⊗ \otimes 表示卷積運算,較小矩陣中的值通過梯度下降被優化學習,卷積核中的值則為網絡權重。卷積後得到的矩陣,也稱為特征圖 (feature map)。
卷積核的通道數與其所乘矩陣的通道數相等。例如,當圖像輸入形狀為 5 x 5 x 3 時(其中 3 為圖像通道數),形狀為 3 x 3 的卷積核也將具有 3 個通道,以便進行矩陣卷積運算:

可以看到無論卷積核有多少通道,一個卷積核計算後都隻能得到一個通道。多為瞭捕獲圖像中的更多特征,通常我們會使用多個卷積核,得到多個通道的特征圖,當使用多個卷積核時,計算過程如下:

需要註意的是,卷積並不等同於濾波,最直觀的區別在於濾波後的圖像大小不變,而卷積會改變圖像大小,關於它們之間更詳細的計算差異,並非本節重點,因此不再展開介紹。

2.1.2 步幅

在前面的示例中,卷積核每次計算時在水平和垂直方向隻移動一個單位,因此可以說卷積核的步幅 (Strides) 為 (1, 1),步幅越大,卷積操作跳過的值越多,例如以下為步幅為 (2, 2) 時的卷積過程:

2.1.3 填充

在前面的示例中,卷積操作對於輸入矩陣的不同位置計算的次數並不相同,具體來說對於邊緣的數值在卷積時,僅僅使用一次,而位於中心的值則會被多次使用,因此可能導致卷積錯過圖像邊緣的一些重要信息。如果要增加對於圖像邊緣的考慮,我們將在輸入矩陣的邊緣周圍的填充 (Padding) 零,下圖展示瞭用 0 填充邊緣後的矩陣進行的卷積運算,這種填充形式進行的卷積,稱為 same 填充,卷積後得到的矩陣大小為 ⌊ d + s − 1 s ⌋ \lfloor\frac {d+s-1} s\rfloor sd+s1,其中 s s s 表示步幅。而未進行填充時執行卷積運算,也稱為 valid 填充。

2.1.4 激活函數

在傳統神經網絡中,隱藏層不僅將輸入值乘以權重,而且還會對數據應用非線性激活函數,將值通過激活函數傳遞。CNN 中同樣包含激活函數,CNN 支持我們已經學習的所有可用激活函數,包括 SigmoidReLUtanhLeakyReLU 等。

2.1.5 池化

研究瞭卷積的工作原理之後,我們將瞭解用於卷積操作之後的另一個常用操作:池化 (Pooling)。假設卷積操作的輸出如下,為 2 x 2

[ 29 28 20 24 ] \left[ \begin{array}{cc} 29 & 28\\ 20 & 24\\\end{array}\right] [29202824]

假設使用池化塊(或者類比卷積核,我們也可以稱之為池化核)為 2 x 2 的最大池化,那麼將會輸出 29 作為池化結果。假設卷積步驟的輸出是一個更大的矩陣,如下所示:
[ 29 28 25 29 20 24 30 26 27 23 26 27 24 25 23 31 ] \left[ \begin{array}{cccc} 29 & 28 & 25 & 29\\ 20 & 24 & 30 & 26\\ 27 & 23 & 26 & 27\\ 24 & 25 & 23 & 31\\\end{array}\right] 29202724282423252530262329262731
當池化核為 2 x 2,且步幅為 2 時,最大池化會將此矩陣劃分為 2 x 2 的非重疊塊,並且僅保留每個塊中最大的元素值,如下所示:

[ 29 28 ∣ 25 29 20 24 ∣ 30 26 — — — — — 27 23 ∣ 26 27 24 25 ∣ 23 31 ] = [ 29 30 27 31 ] \left[ \begin{array}{ccccc} 29 & 28 & | & 25 & 29\\ 20 & 24 & | & 30 & 26\\ —&—&—&—&—\\ 27 & 23 & | & 26 & 27\\ 24 & 25 & | & 23 & 31\\\end{array}\right]=\left[ \begin{array}{cc} 29 & 30\\ 27 & 31\\\end{array}\right] 29202724282423252530262329262731=[29273031]

從每個池化塊中,最大池化僅選擇具有最高值的元素。除瞭最大池化外,也可以使用平均池化,其將輸出每個池化塊中的平均值作為結果,在實踐中,與其他類型的池化相比,最常使用的池化為最大池化。

2.2 卷積和池化相比全連接網絡的優勢

在以上 MNIST 手寫數字識別示例中,可以看出傳統神經網絡的缺點之一是每個像素都具有不同的權重。因此,如果當這些權重用於除原始像素以外的相鄰像素,則神經網絡得到的輸出將不是非常準確(如情景 1 的示例,像素稍微向左側移動後,準確率就大幅下降)。
使用 CNN 可以解決這一問題,因為圖像中的像素共享由卷積核構成的權重。所有像素都乘以構成卷積核的所有權重;在池化層中,僅選擇卷積後具有最高值的輸出。這樣,無論要識別的對象像素是位於中心還是偏離中心,輸出通常都是期望值。但是,當要識別的像素距離中心很遠時,問題仍然存在,這是由於靠近中心的像素在 CNN 能夠會被卷積核更頻繁的劃過。

3. 使用 Keras 構建卷積神經網絡

為瞭進一步加深對卷積神經網絡 (Convolutional Neural Network, CNN) 的理解,我們將使用 Keras 構建基於 CNN 的體系結構,並通過使用 KerasNumpy 從輸入開始構建 CNN 前向傳播過程獲得輸出,來增強我們對 CNN 的理解。

3.1 CNN 使用示例

我們首先定義一個輸入和預期輸出數據的簡單示例來實現 CNN

  1. 創建輸入和輸出數據集:
import numpy as npx_train = np.array([[[1,2,3,3],                    [2,3,4,5],                    [4,5,6,7],                    [1,3,4,6]],                    [[-1,2,3,-5],                    [2,-2,4,5],                    [-3,5,-4,9],                    [-1,-3,-2,-5]]])y_train = np.array([0, 1])

在以上代碼中,我們創建瞭以下數據:輸入全為正數得到的輸出為 0,帶有負值的輸入則得到 1 作為輸出。

  1. 縮放輸入數據集:
x_train = x_train / 9
  1. 對輸入數據集的形狀進行整形,以使每個輸入圖像都以 (寬度 x 高度 x 通道數) 的格式表示:
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[1], 1)
  1. 準備數據後,構建模型架構,導入相關方法後實例化模型:
from keras.models import Sequentialfrom keras.layers import Conv2D, Flatten, MaxPooling2Dmodel = Sequential()
  1. 接下來,我們將執行卷積操作:
model.add(Conv2D(1, (3, 3), input_shape=(4, 4, 1), activation='relu'))

在上述代碼中,我們對輸入數據執行 2D 卷積 Conv2D,其中有 1 個大小為 3 x 3 的卷積核,由於這是實例化模型中的第一層,我們需要指定輸入形狀,為 (4, 4, 1);最後,我們在卷積的輸出之上使用 ReLU 激活函數。由於我們沒有對輸入進行填充,因此輸出的特征圖大小將縮小,卷積運算輸出的形狀為 2 x 2 x 1

  1. 接下來,我們將添加一個執行最大池化操作的網絡層 MaxPooling2D,如下所示:
model.add(MaxPooling2D(pool_size=(2, 2)))

我們在上一層獲得的輸出之上執行最大池化 (池化核大小為 2 x 2),這表示需要計算圖像每個滑動窗口 2 x 2 部分中的最大值,得到池化運算輸出的形狀為 1 x 1 x 1

  1. 接下來,展平池化層的輸出:
model.add(Flatten())

一旦執行瞭展平 Flatten 處理,展平層的操作十分簡單——將多維的輸入整形為一維數組,Flatten操作不影響第一維的 batch_size,在本例中,執行 Flatten 操作後,數據形狀由 (batch_size, 1,1,1) 變為 (batch_size, 1)。之後就與在標準前饋神經網絡中執行的過程非常相似,先連接到若幹隱藏層,最後連接到輸出層。在這裡,我們使用 sigmoid 激活函數,並直接將展平層的輸出連接到輸出層:

model.add(Dense(1, activation='sigmoid'))

查看該模型的相關信息:

model.summary()

輸出的模型簡要信息如下:

Model: "sequential"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================conv2d (Conv2D)              (None, 2, 2, 1)           10        _________________________________________________________________max_pooling2d (MaxPooling2D) (None, 1, 1, 1)           0         _________________________________________________________________flatten (Flatten)            (None, 1)                 0         _________________________________________________________________dense (Dense)                (None, 1)                 2         =================================================================Total params: 12Trainable params: 12Non-trainable params: 0_________________________________________________________________

如上所示,卷積層中有 10 個參數,因為一個 3 x 3 的卷積核,其具有 9 個權重和 1 個偏置項。池化層和展平層沒有任何參數,因為它們隻需某個區域中提取最大值(最大池化 MaxPooling2D),或者展平前一層的輸出(展平層 Flatten),因此不需要在其中一個權重進行修改的操作這些層。輸出層具有兩個參數,因為展平層隻有一個輸出,該輸出連接到具有一個值的輸出層,因此具有一個權重和一個偏置項來連接展平層和輸出層。

  1. 最後,編譯並擬合模型:
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['acc'])model.fit(x_train, y_train, epochs=50)

在上述代碼中,我們將損失指定為二進制交叉熵,因為輸出結果是 10

3.2 驗證 CNN 輸出

在上一節中,我們已經擬合瞭模型,接下來,通過實現 CNN 的前向傳播過程來驗證從模型中獲得的輸出。

  1. 首先,我們提取權重和偏置的相關信息:
print(model.get_weights())

輸出結果如下:

[<tf.Variable 'conv2d/kernel:0' shape=(3, 3, 1, 1) dtype=float32, numpy=array([[[[ 0.1985341 ]],        [[ 0.27599618]],        [[ 0.20717546]]],       [[[-0.41702896]],        [[-0.21289168]],        [[ 0.12980239]]],       [[[-0.14379142]],        [[ 0.55261314]],        [[-0.49706236]]]], dtype=float32)>,<tf.Variable 'conv2d/bias:0' shape=(1,) dtype=float32, numpy=array([0.04198299], dtype=float32)>,<tf.Variable 'dense/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[1.5805249]], dtype=float32)>,<tf.Variable 'dense/bias:0' shape=(1,) dtype=float32, numpy=array([-0.28272304], dtype=float32)>]

可以看到,首先顯示瞭卷積層的權重,然後是偏置,最後是輸出層中的權重和偏置。卷積層中權重的形狀為 (3, 3, 1, 1),因為卷積核的形狀為 3 x 3 x 1 (形狀中的前三個值),形狀中的第四個值 1 用於在卷積層中指定卷積核的數量。如果我們指定 64 作為卷積中的卷積核數量,則權重的形狀為 3 x 3 x 1 x 64,而如果對具有 3 個通道的圖像執行卷積運算,則每個卷積核的形狀將為 3 x 3 x 3

  1. 使用 modelweights 屬性提取各層的權重值:
print(model.weights)

接下來,我們使用模型計算第一個輸入 x_train[0] 的輸出,以便我們可以通過前向傳播查看 CNN 計算結果:

print(model.predict(x_train[0].reshape(1,4,4,1)))# 輸出如下# [[0.04299431]]

以上代碼對輸入數據整形,同時將其傳遞給預測方法,因為模型希望接受輸入的形狀為 (None, 4, 4, 1),其中 None 用於指定批大小,可以是任何數字。我們運行的模型預測輸出為 0.04299431

  1. 接下來,我們通過模擬卷積過程進行驗證,對輸入數據執行卷積,輸入圖像的形狀為 4 x 4,而卷積核的形狀為 3 x 3,在代碼中沿著行和列執行矩陣乘法(卷積):
sumprod = []for i in range(x_train[0].shape[0]-model.get_weights()[0].shape[0]+1):    for j in range(x_train[0].shape[0]-model.get_weights()[0].shape[0]+1):        img_subset = np.array(x_train[0,i:(i+3),j:(j+3),0])        filter = model.get_weights()[0].reshape(3,3)        val = np.sum(img_subset*filter) + model.get_weights()[1]        sumprod.append(val)

在以上代碼中,我們初始化一個名為 sumprod 的空列表,用於存儲卷積核與輸入數據的每個子矩陣卷積的輸出。

  1. 整形 sumprod 的輸出,以便將其傳遞到池化層:
sumprod = np.array(sumprod).reshape(2, 2, 1)
  1. 在卷積的輸出傳遞到池化層之前,先對其使用激活函數:
sumprod = np.where(sumprod>0, sumprod, 0)
  1. 將卷積輸出傳遞到池化層,根據以上模型定義,考慮到卷積的輸出為 2 x 2,根據最大池化層的定義我們取輸出中的最大值:
pooling_layer_output = np.max(sumprod)
  1. 將池化層的輸出連接到輸出層,將池化層的輸出乘以輸出層中的權重,然後在輸出層中加上偏置值:
intermediate_output_value = pooling_layer_output * model.get_weights()[2] + model.get_weights()[3]
  1. 計算 Sigmoid 輸出:
print(1/(1+np.exp(-intermediate_output_value)))

以上操作的輸出如下:

[[0.42994314]]

可以看到輸出與我們使用 model.predict 方法獲得的輸出相同,從而加深瞭我們對 CNN 工作流程的瞭解。

4. 構建 CNN 模型識別 MNIST 手寫數字 4.1 任務與模型分析

在前面的部分中,我們瞭解瞭傳統神經網絡的問題以及 CNN 的工作原理。在本節中,我們將構建 CNN 模型用來識別經過平移的 MNIST 手寫數字。我們采用的以下策略構建 CNN 模型:

  • 輸入形狀為 28 x 28 x 1,使用的卷積核尺寸為 3 x 3 x 1
    • 需要註意的是,卷積核的大小可以更改,但是通道數不能更改,需要與輸入通道數相同
    • 使用 10 個卷積核
  • 輸入圖像經過卷積層後,使用池化層:
    • 輸出的圖像尺寸減半
    • 展平池化後獲得的輸出
  • 展平層連接到一個具有 1000 個單位的隱藏層
  • 最後,將隱藏層連接到輸出層,輸出層中有 10 類(包括數字 0-9 )

建立模型後,我們使用所有標簽為 1 的圖像生成均值圖像,並平移 1 個像素,然後在平移後的圖像上測試 CNN 模型的性能;在第 1 節中,我們已經知道,全連接神經網絡無法爭取預測該均值圖像的類別。

4.2 CNN 模型構建與訓練

接下來,使用 Keras 實現上述定義的 CNN 架構,以瞭解如何在 MNIST數據上使用 CNN 模型。

  1. 加載並預處理數據:
from keras.layers import Dense,Conv2D,MaxPool2D,Flattenfrom keras.models import Sequentialfrom keras.datasets import mnistfrom keras.utils import np_utilsimport numpy as np(x_train, y_train),(x_test, y_test) = mnist.load_data()x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[1], 1)x_test = x_test.reshape(x_test.shape[0], x_test.shape[1], x_test.shape[1], 1)x_train = x_train.astype('float32') / 255.x_test = x_test.astype('float32') / 255.y_train = np_utils.to_categorical(y_train)y_test = np_utils.to_categorical(y_test)num_classes = y_test.shape[1]

預處理步驟與我們在《構建深度前饋神經網絡》中使用的方法完全相同。

  1. 建立並編譯模型:
model = Sequential()model.add(Conv2D(10, (3, 3), input_shape=(28, 28, 1), activation='relu'))model.add(MaxPool2D(pool_size=(2, 2)))model.add(Flatten())model.add(Dense(512, activation='relu'))model.add(Dense(num_classes, activation='softmax'))model.summary()model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

可以獲取我們在前面的代碼中初始化的模型的簡要架構信息:

model.summary()

輸出該模型的簡要架構信息如下:

Model: "sequential"_________________________________________________________________Layer (type)                 Output Shape              Param #   =================================================================conv2d (Conv2D)              (None, 26, 26, 10)        100       _________________________________________________________________max_pooling2d (MaxPooling2D) (None, 13, 13, 10)        0         _________________________________________________________________flatten (Flatten)            (None, 1690)              0         _________________________________________________________________dense (Dense)                (None, 512)               865792    _________________________________________________________________dense_1 (Dense)              (None, 10)                5130      =================================================================Total params: 871,022Trainable params: 871,022Non-trainable params: 0_________________________________________________________________

卷積層中總共有 100 個參數,因為卷積層中有 103 x 3 x 1 的卷積核,因此總共有 90 個權重參數和 10 個偏置項(每個卷積核中 1 個),共 100 個參數。最大池化層沒有任何參數,因為它隻需要計算每個大小為 2 x 2 的池化核中的最大值。可以看到使用 CNN 模型可以大幅降低網絡參數量。

  1. 最後,擬合模型:
model.fit(x_train, y_train,            validation_data=(x_test, y_test),            epochs=10,            batch_size=1024,            verbose=1)

以上模型在 10epoch 訓練後,可以達到 98% 的準確率:


4. 接下來,使用所有標簽為 1 的圖像生成均值圖像,並平移 1 個像素:

# 獲取標簽為1的所有圖像輸入x_test1 = x_test[y_test[:, 1]==1]# 利用所有標簽為1的圖像生成均值圖像pic = np.zeros((x_test.shape[1], x_test.shape[2]))pic2 = np.copy(pic)for i in range(x_test1.shape[0]):    pic2 = x_test1[i, :, :, 0]pic = pic + pic2pic = (pic / x_test1.shape[0])# 將均值圖像中的每個像素向左平移一個像素for i in range(pic.shape[0]):    if i < 21:        pic[:, i] = pic[:, i+1]# 對平移後的圖像進行預測p = model.predict(pic.reshape(1, x_test.shape[1], x_test.shape[2], 1))print(p)c = np.argmax(p)print('CNN預測結果:', c)

得到的模型輸出結果如下所示:

[[1.3430370e-05 9.9989212e-01 2.0535077e-05 2.6301734e-07 4.3278211e-05  5.9122913e-06 1.5874346e-05 6.2533190e-06 2.0079199e-06 4.1732173e-07]]CNN預測結果: 1

看以看到,與深度前饋神經網絡模型的情況相比,使用 CNN 架構得到的預測結果能夠以更大的輸出概率將平移後的圖像預測為 1

相關鏈接

Keras深度學習實戰(1)——神經網絡基礎與模型訓練過程詳解
Keras深度學習實戰(2)——使用Keras構建神經網絡
Keras深度學習實戰(3)——神經網絡性能優化技術
Keras深度學習實戰(4)——深度學習中常用激活函數和損失函數詳解
Keras深度學習實戰(5)——批歸一化詳解
Keras深度學習實戰(6)——深度學習過擬合問題及解決方法

本文來自網絡,不代表程式碼花園立場,如有侵權,請聯系管理員。https://www.codegarden.cn/article/31211/
返回顶部