吳恩達深度學習4-Week1課後作業1-卷積模型Step by Step
一、deeplearning-assignment
在本次任務中,我們將學習用numpy實現卷積(CONV)層和池化(POOL)層,由於大多數深度學習工程師不需要關注反向傳遞的細節,而且卷積網路的反向傳遞很複雜,所以在本次作業中只討論關於前向傳播的處理細節。
用 python 來實現每個函式,在下次任務用 TensorFlow 中等價的函式構造如下模型:
對於每個前向傳播,都有相應的反向傳播。因此,每一步前向傳播,都會儲存一些引數在快取中。這些引數用於計算反向傳播中的梯度。
卷積神經網路
儘管程式設計框架使得卷積操作易於實現,但它仍然是深度學習中最難理解的概念之一。卷積層將輸入轉換為不同尺寸的輸出:
在實現卷積神經網路之前,首先要實現兩個輔助函式:一個用於零填充,另一個用於卷積計算。
零填充
指的是在影象的邊緣填充一系列的零點。
上圖中,將一個影象(三個通道對應三個RGB值)的每一層RGB矩陣進行padding為2的零填充。
零填充的主要好處如下:
-
它允許你在不縮小寬度和高度的同時使用CONV層. 這對構建更深層次的網路非常重要, 否則網路越深,寬度/高度越小. 一個重要的特殊案例是 "same" convolution, 它在經歷一層卷積後寬度/高度維持不變.
-
它會在影象的邊界保留更多的資訊. 如果沒有填充,下一層的極少數值會受到邊緣畫素的影響。
卷積計算
將過濾器對應於Image中對應的位置,進行乘法運算後將累加和輸出到新矩陣對應的位置,然後通過步長平移相應的位置重複之前的步驟,直到輸出矩陣計算完畢。
卷積神經網路的前向傳播
在正向傳遞中,將對輸入採用多種過濾器進行卷積。每個“卷積”輸出一個二維矩陣。最後疊加二維矩陣獲得3D volume。
池化層
池化層會降低輸入的高度和寬度。它有助於減少計算量,主要有兩種型別的池化,如下圖所示:
池化層中相關引數的計算:
二、相關演算法程式碼
import numpy as np import h5py import matplotlib.pyplot as plt plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots plt.rcParams['image.interpolation'] = 'nearest' plt.rcParams['image.cmap'] = 'gray' np.random.seed(1) def zero_pad(X, pad): X_pad = np.pad(X, ((0, 0), (pad, pad), (pad, pad), (0, 0)), 'constant') return X_pad # np.random.seed(1) # x = np.random.randn(4, 3, 3, 2) # x_pad = zero_pad(x, 2) # print("x.shape =", x.shape) # print("x_pad.shape =", x_pad.shape) # print("x[1,1] =", x[1, 1]) # print("x_pad[1,1] =", x_pad[1, 1]) # fig, axarr = plt.subplots(1, 2) # axarr[0].set_title('x') # axarr[0].imshow(x[0, :, :, 0]) # axarr[1].set_title('x_pad') # axarr[1].imshow(x_pad[0, :, :, 0]) # plt.show() def conv_single_step(a_slice_prev, W, b): s = a_slice_prev * W Z = np.sum(s) Z = float(Z + b) return Z # np.random.seed(1) # a_slice_prev = np.random.randn(4, 4, 3) # W = np.random.randn(4, 4, 3) # b = np.random.randn(1, 1, 1) # Z = conv_single_step(a_slice_prev, W, b) # print("Z =", Z) def conv_forward(A_prev, W, b, hparameters): (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape (f, f, n_C_prev, n_C) = W.shape stride = hparameters['stride'] pad = hparameters['pad'] n_H = int((n_H_prev - f + 2 * pad) / stride + 1) n_W = int((n_W_prev - f + 2 * pad) / stride + 1) Z = np.zeros((m, n_H, n_W, n_C)) A_prev_pad = zero_pad(A_prev, pad) for i in range(m): a_prev_pad = A_prev_pad[i, :, :, :] for h in range(n_H): for w in range(n_W): for c in range(n_C): vert_start = h * stride vert_end = vert_start + f horiz_start = w * stride horiz_end = horiz_start + f a_slice_prev = a_prev_pad[vert_start: vert_end, horiz_start: horiz_end, :] Z[i, h, w, c] = conv_single_step(a_slice_prev, W[:, :, :, c], b[:, :, :, c]) assert (Z.shape == (m, n_H, n_W, n_C)) cache = (A_prev, W, b, hparameters) return Z, cache # np.random.seed(1) # A_prev = np.random.randn(10, 4, 4, 3) # W = np.random.randn(2, 2, 3, 8) # b = np.random.randn(1, 1, 1, 8) # hparameters = {"pad": 2, # "stride": 2} # Z, cache_conv = conv_forward(A_prev, W, b, hparameters) # print("Z.shape = ", Z.shape) # print("Z's mean = ", np.mean(Z)) # print("Z[3,2,1] = ", Z[3, 2, 1]) # print("cache_conv[0][1][2][3] =", cache_conv[0][1][2][3]) def pool_forward(A_prev, hparameters, mode='max'): (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape f = hparameters['f'] stride = hparameters['stride'] n_H = int(1 + (n_H_prev - f) / stride) n_W = int(1 + (n_W_prev - f) / stride) n_C = n_C_prev A = np.zeros((m, n_H, n_W, n_C)) for i in range(m): for h in range(n_H): for w in range(n_W): for c in range(n_C): vert_start = h * stride vert_end = vert_start + stride horiz_start = w * stride horiz_end = horiz_start + stride a_prev_slice = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] if mode == "max": A[i, h, w, c] = np.max(a_prev_slice) elif mode == "average": A[i, h, w, c] = np.mean(a_prev_slice) cache = (A_prev, hparameters) assert (A.shape == (m, n_H, n_W, n_C)) return A, cache np.random.seed(1) A_prev = np.random.randn(2, 4, 4, 3) hparameters = {"stride": 2, "f": 3} A, cache = pool_forward(A_prev, hparameters) print("mode = max") print("A =", A) print() A, cache = pool_forward(A_prev, hparameters, mode="average") print("mode = average") print("A =", A)
三、總結
在前面的學習中,我們知道了怎樣通過numpy建立輔助函式來理解卷積神經網路背後的機制,包括零填充、卷積計算以及池化層等相關的操作。不過今天大多數實際應用的深度學習都會使用程式設計框架,有很多內建函式可以簡單地呼叫,在下次的work中,我們將學習運用tensorflow框架來實現卷積神經網路。