搭建卷积神经网络模型
在经历了前面多层神经网络的学习后,我们来到了卷积神经网络,我将吴恩达深度学习第四课第一周的课后作业分开两篇博客,本文首先是练习一下卷积神经网络底层的搭建。
在本文中,我们将实现以下功能:
- 卷积模块
- 使用0扩充边界(padding)
- 卷积窗口(filter)
- 正向卷积
- 池化模块
- 正向池化
当然卷积神经网络也与普通的网络一样有反向传播的部分,但由于卷积神经网络的反向传播较为复杂,吴恩达大佬的课上也没有讲,因此本文就不去实现了。得益于框架的强大,在实际开发神经网络模型中反向传播的实现不需要我们人工实现,框架就帮我们打理好一切了。
那么首先导入一些库:
import numpy as np
import h5py
import matplotlib.pyplot as plt
一、卷积模块
1.1 边界填充
边界填充就是在图像边界周围添加值为0的像素点:
由于在卷积后,图像的高度和宽度会变小,容易造成信息的丢失,因此填充边界后可以避免在卷积过程中导致高度和宽度的缩小,也避免了原图像边缘像素值被卷积掉而导致信息丢失
def zero_pad(X,pad):
"""
把数据集X的图像边界全部使用0来扩充pad个宽度和高度。
参数:
X - 图像数据集,维度为(样本数,图像高度,图像宽度,图像通道数)
pad - 整数,每个图像在垂直和水平维度上的填充量
返回:
X_paded - 扩充后的图像数据集,维度为(样本数,图像高度 + 2*pad,图像宽度 + 2*pad,图像通道数)
"""
X_paded = np.pad(X,(
(0,0),
(pad,pad),
(pad,pad),
(0,0)),
'constant',constant_values=0)
return X_paded
测试一下:
np.random.seed(1)
x = np.random.randn(4,3,3,2)
x_paded = zero_pad(x,2)
print ("x.shape =", x.shape)
print ("x_paded.shape =", x_paded.shape)
print ("x[1, 1] =", x[1, 1])
print ("x_paded[1, 1] =", x_paded[1, 1])
fig , axarr = plt.subplots(1,2) #一行两列
axarr[0].set_title('x')
axarr[0].imshow(x[0,:,:,0])
axarr[1].set_title('x_paded')
axarr[1].imshow(x_paded[0,:,:,0])
plt.show()
x.shape = (4, 3, 3, 2)
x_paded.shape = (4, 7, 7, 2)
x[1, 1] = [[ 0.90085595 -0.68372786]
[-0.12289023 -0.93576943]
[-0.26788808 0.53035547]]
x_paded[1, 1] = [[0. 0.]
[0. 0.]
[0. 0.]
[0. 0.]
[0. 0.]
[0. 0.]
[0. 0.]]
1.2单步卷积
卷积,其实就是用一个过滤器,将原图像每个区域的像素值过滤成一个新的像素值,最后组合成一个新的像素矩阵。这里我们先试试用一个3*3的过滤器过滤原图像的一个窗口。
def conv_single_step(a_slice_prev,W,b):
"""
在前一层的激活输出的一个片段上应用一个由参数W定义的过滤器。
这里切片大小和过滤器大小相同
参数:
a_slice_prev - 输入数据的一个片段,维度为(过滤器大小,过滤器大小,上一通道数)
W - 权重参数,包含在了一个矩阵中,维度为(过滤器大小,过滤器大小,上一通道数)
b - 偏置参数,包含在了一个矩阵中,维度为(1,1,1)
返回:
Z - 在输入数据的片X上卷积滑动窗口(w,b)的的结果
"""
s = np.multiply(a_slice_prev,W) + b
Z = np.sum(s)
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)
-23.16021220252078
这样算出来就是过滤后的一个像素值了。
1.3 正向传播
对于卷积神经网络的正向传播,我们需要对原图像的像素矩阵进行切片,然后对它们进行单步卷积:
输出维度的公式如下:
def conv_forward(A_prev,W,b,hparameters):
"""
参数:
A_prev - 上一层输出的矩阵,维度为(m,n_H_prev,n_W_prev,n_C_prev)
(样本数量,上一层图像的高度,上一层图像的宽度,上一层过滤器数量)
W - 权重矩阵,维度为维度为(f, f, n_C_prev, n_C)
(过滤器大小,过滤器大小,上一层的过滤器数量,这一层的过滤器数量)、
b - 偏置矩阵,维度为(1, 1, 1, n_C)
(1,1,1,这一层的过滤器数量)
hparameters - 包含stride(步长)和pad(填充值)的字典
返回:
Z - 卷积的输出,维度为(m, n_H, n_W, n_C)
(样本数,图像的高度,图像的宽度,过滤器数量)
cache - 缓存反向传播的数据
"""
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 + 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):
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[0,0,0,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": 1}
Z , cache_conv = conv_forward(A_prev,W,b,hparameters)
print("np.mean(Z) = ", np.mean(Z))
print("cache_conv[0][1][2][3] =", cache_conv[0][1][2][3])
np.mean(Z) = 0.15585932488906465
cache_conv[0][1][2][3] = [-0.20075807 0.18656139 0.41005165]
二、池化模块
池化层会减少输入的宽度和高度,这样它会较少计算量的同时也使特征检测器对其在输入中的位置更加稳定。池化层有两种类型:
- 最大值池化层:在输入矩阵中滑动一个大小为fxf的窗口,选取窗口里的值中的最大值,然后作为输出的一部分。
- 均值池化层:在输入矩阵中滑动一个大小为fxf的窗口,计算窗口里的值中的平均值,然后这个均值作为输出的一部分。
我们在一个函数中实现最大值池化和均值池化,公式如下:
def pool_forward(A_prev,hparameters,mode='max'):
"""
参数:
A_prev - 输入数据,维度为(m, n_H_prev, n_W_prev, n_C_prev)
hparameters - 包含了 "f" 和 "stride"的超参数字典
mode - 模式选择【"max" | "average"】
"""
m, n_H_prev, n_W_prev, n_C_prev = A_prev.shape
f = hparameters['f']
stride = hparameters['stride']
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):
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[i,vert_start:vert_end,horiz_start:horiz_end,c]
if mode == 'max':
A[i, h, w, c] = np.max(a_slice_prev)
elif mode == 'average':
A[i, h, w, c] = np.mean(a_slice_prev)
else:
print('mode参数出错!!!')
assert (A.shape == (m, n_H, n_W, n_C))
cache = (A_prev, hparameters)
return A, cache
测试一下:
np.random.seed(1)
A_prev = np.random.randn(2,4,4,3)
hparameters = {"f":4 , "stride":1}
A , cache = pool_forward(A_prev,hparameters,mode="max")
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)
mode = max
A = [[[[1.74481176 1.6924546 2.10025514]]]
[[[1.19891788 1.51981682 2.18557541]]]]
----------------------------
mode = average
A = [[[[-0.09498456 0.11180064 -0.14263511]]]
[[[-0.09525108 0.28325018 0.33035185]]]]
三、总结
本文就先到这里,前面已经说了卷积神经网络的反向传播比较复杂,我暂时还没弄懂,因此就不写在这里了,下一篇文章就介绍用tensorflow2 keras来完成这周的作业吧!