深度學習之四:卷積神經網路基礎
計算機視覺在深度學習的幫助下取得了令人驚歎的進展,其中發揮重要作用的是卷積神經網路。本節總結了卷積神經的原理與實現方法。
1 卷積神經網路
1.1 計算機視覺與深度學習
計算機視覺要解決的問題是如何讓機器理解現實世界的現象。目前主要處理的問題如影象分類,目標檢測,風格變換等。
對於小型圖片,如MNIST的圖片處理,影象大小為64*64,使用全連線神經網路還可以處理。如果影象較大,如1000*1000*3時,單個輸入層就需要3M的節點,全連線情況下,單層的權重就可能多達數十億的待訓練引數。這將導致神經網路無法進行有效訓練。因此業界提出卷積神經網路解決引數過多的問題。
先看一個卷積神經網路的結構,在卷積神經網路中包括三種類型的層:卷積層,池化層和全連線層。下面幾節介紹卷積神經網路的基本概念。
1.2 過濾器
在卷積神經網路中,過濾器也稱為卷積核,通常是3*3或5*5的小型矩陣(先忽略每個點可能有多個通道)。過濾器的作用是和輸入影象的各個區域逐次進行卷積。
所謂卷積運算是計算兩個矩陣的對應元素(Element-wise)乘積的和。卷積本質是兩個向量的內積,體現了兩個向量的距離。
下圖展示了過濾器在輸入影象上的卷積計算的過程:
因此過濾器可以捕獲影象中與之類似模式的影象區域。例如下圖展示了一個過濾器的數值矩陣與圖形。
當使用這個過濾器在影象上移動並計算卷積時,不同的區域會有不同的卷積結果。
在類似於的模式下過濾器與影象區域的卷積會產生較大的值。
不相似的區域則產生較小的值。
此外在一層上捕獲的模式可以組合為更復雜的模式並輸入到下一層,從而使得下一層能識別更復雜的影象模式。
訓練CNN在一定意義上是在訓練每個卷積層的過濾器,讓其組合對特定的模式有高的啟用,以達到CNN網路的分類/檢測等目的。
1.3 Padding
對於一個shape為的影象,若過濾器的 shape為,在卷積時每次移動1步,則生成的矩陣shape為。這樣如果經過多層的卷積後,輸出會越來越小。另一方面影象邊界的點的特徵不能被過濾器充分捕獲。
通過對原始影象四個邊界進行padding,解決上面提到的兩個問題。術語Same填充,會保證輸出矩陣與輸入矩陣大小相同,因此其需要對輸入影象邊界填充
通過下圖體會Padding對輸出的影響
下面的程式碼展示了使用0填充,向X邊界填充pad個畫素的方法
def zero_pad(X, pad):
X_pad = np.pad(X, ((0, 0), (pad, pad), (pad, pad), (0, 0)), 'constant', constant_values=0)
return X_pad
1.4 卷積步長
另一個影響輸出大小的因素是卷積步長(Stride)。
對於一個shape為的影象,若過濾器的 shape為,卷積步長為s,邊界填充p個象素,則生成的矩陣shape為。
1.5 卷積層記法說明
對於第層的卷積層,為該層的卷積核數,其輸入矩陣的維度為
輸出矩陣的維度以channel-last展示為,各維度值如下:
下面的程式碼展示了影象卷積計算的方法
def conv_single_step(a_slice_prev, W, b):
"""
Apply one filter defined by parameters W on a single slice (a_slice_prev) of the output activation
of the previous layer.
Arguments:
a_slice_prev -- slice of input data of shape (f, f, n_C_prev)
W -- Weight parameters contained in a window - matrix of shape (f, f, n_C_prev)
b -- Bias parameters contained in a window - matrix of shape (1, 1, 1)
Returns:
Z -- a scalar value, result of convolving the sliding window (W, b) on a slice x of the input data
"""
s = np.multiply(a_slice_prev, W) + b
Z = np.sum(s)
return Z
def conv_forward(A_prev, W, b, hparameters):
"""
Implements the forward propagation for a convolution function
Arguments:
A_prev -- output activations of the previous layer, numpy array of shape (m, n_H_prev, n_W_prev, n_C_prev)
W -- Weights, numpy array of shape (f, f, n_C_prev, n_C)
b -- Biases, numpy array of shape (1, 1, 1, n_C)
hparameters -- python dictionary containing "stride" and "pad"
Returns:
Z -- conv output, numpy array of shape (m, n_H, n_W, n_C)
cache -- cache of values needed for the conv_backward() function
"""
(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
# Initialize the output volume Z with zeros.
Z = np.zeros((m, n_H, n_W, n_C))
# Create A_prev_pad by padding A_prev
A_prev_pad = zero_pad(A_prev, pad)
for i in range(m): # loop over the batch of training examples
a_prev_pad = A_prev_pad[i] # Select ith training example's padded activation
for h in range(n_H):
for w in range(n_W):
for c in range(n_C): # loop over channels (= #filters) of the output volume
# Find the corners of the current "slice"
vert_start = h * stride
vert_end = vert_start + f
horiz_start = w * stride
horiz_end = horiz_start + f
# Use the corners to define the (3D) slice of a_prev_pad (See Hint above the cell).
a_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
# Convolve the (3D) slice with the correct filter W and bias b, to get back one output neuron.
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
1.6 池化層
有兩種型別的池化層,一種是最大值池化,一種是均值池化。
下圖體現了最大池化的作用,其過濾器大小為2,步幅為2:
均值池化與最大池化不同的是計算相應區域的均值,而不是取最大值。
對池化層各維度值如下: