深度學習——手寫數字識別
學習深度學習的朋友應該對MNIST數據集不陌生吧,相信很多人在剛開始學習深度學習的時候都會用到MNIST數據集進行書寫數字識別。本篇文章參考魚書創建一個深度網絡來進行書寫數字識別的任務。
如上圖所示,這里使用的卷積層全都是 3 × 3 3 \times 3 3×3 的小型濾波器,特點是隨著層的加深,通道數變大(卷積層的通道數從前面的層開始按順序以16、16、32、32、64、64的方式增加)。此外,如圖所示,插入了池化層,以逐漸減小中間數據的空間大小;并且,后面的全連接層中還使用了Dropout層(為了防止過擬合)。
這個網絡使用He初始值(何愷明大神的提出的)作為權重的初始值,使用Adam更新權重參數。把上述內容總結起來,這個網絡有如下特點。
- 基于 3 × 3 3 \times 3 3×3的小型濾波器的卷積層。
- 激活函數是 ReLU。
- 全連接層的后面使用 Dropout 層。
- 基于 Adam 的最優化。
- 使用 He 初始值作為權重初始值。
權重初始化對訓練深度網絡很重要,特別是ReLU激活函數流行之后。Xavier 初始化是針對Sigmoid和Tanh設計的,Sigmoid函數和Tanh函數左右對稱,且中央附近可以視作線性函數,但ReLU的非線性特性導致前向傳播時輸出方差會變化。
而He初始化主要是為了解決ReLU激活函數在初始化時方差縮小的問題。He初始值一種專門為使用ReLu激活函數及其變體(如Leaky ReLU, PPeLU)的神經網絡層設計的權重初始化方法。它是由何愷明(Kaiming He)等人在2015年的論文《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》中提出的。ReLU會把負數部分歸零,所以輸出的方差會比輸入小一半。如果每一層都這樣,深度網絡的方差會越來越小,導致梯度消失。
He初始值使用標準差為 2 n \sqrt{\frac{2}{n}} n2?? 的高斯分布,n是輸入神經元的數量。與 Xavier 初始化相比, He 的方差更大,以補償ReLU造成的方差減半。
代碼:https://github.com/Benxiaogu/mnist
魚書全書代碼
https://github.com/qiaohaoforever/DeepLearningFromScratch
訓練結果:
函數im2col
的作用是將數據展開以適合濾波器(權重)。如下圖所示,對3維的輸入數據應用此函數后,數據轉換為2維矩陣(正確地講,是把包含批數量的4維數據轉換成了2維數據)。
im2col 是 "image to column"的縮寫,翻譯過來就是“從圖像到矩陣”的意思。
使用im2col
展開輸入數據之后,之后就只需將卷積層的濾波器(權重)縱向展開為1列,并計算2個矩陣的乘積即可,如下圖所示。這和全連接層的 Affine 層進行的處理基本相同。
如上圖所示,基于 im2col
方式的輸出結果是2為矩陣。因為 CNN 中數據會保存為4維數組,所以要將 2 維輸出數據轉換為合適的形狀。
函數col2im
是函數im2col
的逆過程
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):"""Parameters:input_data:由(數據量, 通道, 高, 長)的4維數組構成的輸入數據filter_h:濾波器的高filter_w:濾波器的長stride:步幅pad:填充Returnscol:2維數組"""N, C, H, W = input_data.shapeout_h = 1 + (H + 2*pad - filter_h) // strideout_w = 1 + (W + 2*pad - filter_w) // strideimg = np.pad(input_data, [(0,0), (0,0), (pad,pad), (pad,pad)], 'constant')col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wcol[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)return coldef col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):"""Parameters:col:2維數組input_data:由(數據量, 通道, 高, 長)的4維數組構成的輸入數據filter_h:濾波器的高filter_w:濾波器的長stride:步幅pad:填充Returns:img:由(數據量, 通道, 高, 長)的4維數組構成的輸出數據"""N, C, H, W = input_shapeout_h = (H + 2*pad - filter_h) // stride + 1out_w = (W + 2*pad - filter_w) // stride + 1col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2) # reshape形狀,transpose調整維度順序img = np.zeros((N, C, H + 2*pad + filter_h - 1, W + 2*pad + filter_w - 1))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wimg[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]return img[:, :, pad:H + pad, pad:W + pad] # 去除填充部分
函數np.flatten()
的作用是將多維數組扁平化為一維數組。np.flatten()
默認按行優先進行降維,也就是將多維數組的第一行所有元素放到一位數組的前面,然后是第二行,依次類推。