本文是Deep Learning Specialization系列课程的第4课《Convolutional Neural Networks》中Convolutional Neural Networks练习部分的学习笔记。
本文主要是通过NumPy来一步步实现卷积运算(Convolutional layer)和池化运算(Pooling layer)部分。具体包括:
填充有两个好处:
在填充的运算中,我们可以直接使用NumPy提供的 np.pad 函数。比如,要对形状为(5, 5, 5, 5, 5)的a
数组,将其第二维填充pad=1
,第四维填充pad=3
,其他维度不做填充(pad=0
),其表达式为:
a_pad = np.pad(a, ((0, 0), (1, 1), (0, 0), (3, 3), (0, 0)), mode = 'constant', constant_values = (0, 0))
在我们的例子中,输入数据X的维度为(m, n_h, n_w, n_c)
,我们只对图片的高、宽进行填充,最终填充的结果维度为(m, n_h + 2*pad, n_w + 2*pad, n_c)
。其具体实现为:
def zero_pad(X, pad):
X_pad = np.pad(X, ((0, 0), (pad, pad), (pad, pad), (0, 0)), mode='constant', constant_values = (0, 0))
return X_pad
在卷积运算中,我们是需要将滤波器与输入数据进行叠加运算,并根据步长来移动窗口,最后得到卷积运算后的结果,具体如下图所示:
我们这里先来实现一个窗口的卷积运算,即将滤波器叠加到输入图像,相乘后求和得到的结果,这里没有滤波器窗口的移动。这里a_slice_prev
与W
的尺寸相同,都为(f, f, n_c_prev)
def conv_single_step(a_slice_prev, W, b):
s = np.multiply(a_slice_prev, W)
Z = np.sum(s)
Z = Z + b
return Z
下面,就要将滤波器窗口移动起来了,这里就需要用到我们前面学到的关于第l
层各参数的知识了:
l-1
层): ( n h [ l − 1 ] , n w [ l − 1 ] , n c [ l − 1 ] ) (n_h^{[l-1]}, n_w^{[l-1]}, n_c^{[l-1]}) (nh[l−1],nw[l−1],nc[l−1])l
层维度的计算公式: n h [ l ] = ( n h [ l − 1 ] + 2 p [ l ] − f [ l ] ) / s [ l ] + 1 n_h^{[l]} = (n_h^{[l-1]} +2p^{[l]} -f^{[l]})/s^{[l]} + 1 nh[l]=(nh[l−1]+2p[l]−f[l])/s[l]+1于是有下面的维度信息:
在移动窗口计算中,需要定义移动窗口在输入层中的位置,这里需要用到for循环。下图是定义该窗口的一个很好的图示:
在实现代码中,需要注意的是权重的维度以及for循环中各个参数的维度:
def conv_forward(A_prev, W, b, h_parameters):
stride = h_parameters['stride']
pad = h_parameters['pad']
(m, n_h_prev, n_w_prev, n_c_prev) = A_prev.shape
(f, f, n_c_prev, n_c) = W.shape
n_h = int((n_h_prev + 2 * pad - f) / stride + 1)
n_w = int((n_w_prev + 2 * pad - f) / 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):
vert_start = h * stride
vert_end = vert_start + f
for w in range(n_w):
hori_start = w * stride
hori_end = hori_start + f
for c in range(n_c):
a_slice_prev = a_prev_pad[vert_start:vert_end, hori_start:hori_end, :]
weights = W[:, :, :, c]
bias = b[:, :, :, c]
Z[i, h, w, c] = conv_single_step(a_slice_prev, weights, bias)
assert(Z.shape == (m, n_h, n_w, n_c))
cache = (A_prev, W, b, h_parameters)
return Z, cache
池化层是为了减少输入层的尺寸,在保证特征不变的情况下来减少运算,一般是包含最大值和平均值两种方法。
在池化层的运算中,其大部分与卷积层运算类似,只是有以下不同点:
具体代码实现如下:
def pool_forward(A_prev, h_parameters, mode = 'max'):
f = h_parameters['f']
stride = h_parameters['stride']
(m, n_h_prev, n_w_prev, n_c_prev) = A_prev.shape
n_h = int((n_h_prev - f) / stride + 1)
n_w = int((n_w_prev - f) / stride + 1)
n_c = n_c_prev
A = np.zeros((m, n_h, n_w, n_c))
for i in range(m):
a_prev = A_prev[i]
for h in range(n_h):
vert_start = h * stride
vert_end = vert_start + f
for w in range(n_w):
hori_start = w * stride
hori_end = hori_start + f
for c in range(n_c):
a_slice_prev = a_prev[vert_start:vert_end, hori_start:hori_end, c]
if mode == 'max':
A[i, h, w, c] = np.max(a_slice_prev)
if mode == 'average':
A[i, h, w, c] = np.average(a_slice_prev)
cache = (A_prev, h_parameters)
assert(A.shape == (m, n_h, n_w, n_c))
return A, cache