1.任务
先一个个实现ResNets的网络模块,然后将这些模块拼到一起构建ResNets来实现图像分类 。
2.导入依赖包
1 | import numpy as np |
3.背景介绍
上篇文章建立第一个卷积神经网络。近年来,神经网络变得越来越深,最先进的网络从只有几层(例如,AlexNet)发展到超过一百层。
非常深网络的主要优点是它可以表示非常复杂的函数。它还可以在许多不同的抽象层次上学习特征,从较低层的边缘到较深层的非常复杂特征。然而,使用更深的网络并不总是有帮助。训练它们的一个巨大障碍是梯度消失:非常深的网络通常具有迅速趋近于零的梯度信号,从而使梯度下降变得非常缓慢。更具体地说,在梯度下降过程中,当你从最后一层反向传播回第一层时,你在每一步上都在乘以权重矩阵,因此梯度可以迅速指数级地减小到零(或者,在极少数情况下,指数级地增长并“爆炸”到非常大值)。
在训练过程中,你可能会看到随着训练的进行,较早层的梯度的大小(或范数)迅速减小到零:
4.创建一个ResNet(残差网络)
左侧的图像显示了网络的“主要路径”。右侧的图像在主要路径上添加了一个快捷方式。通过将这些ResNet块堆叠在一起,您可以形成一个非常深的网络。
我们还看到,具有快捷方式的ResNet块使得其中一个块学习恒等函数变得非常容易。这意味着您可以在几乎没有损害训练集性能的风险下堆叠额外的ResNet块。(还有一些证据表明,学习恒等函数的容易程度 - 甚至比跳过连接帮助解决梯度消失问题 - 解释了ResNet的卓越性能)
ResNet网络主要包含两个模块(The identity block、The convolutional block)
4.1The identity block
身份块是ResNet中使用的标准块,对应于输入激活(比如
主路径的第一部分:
- 第一个CONV2D有
$F_1$
个形状为 (1,1) 的滤波器,步长为 (1,1)。它的填充是“有效的”,其名称应该是conv_name_base + '2a'
。使用0作为随机初始化的种子。 - 第一个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2a'
。 - 然后应用ReLU激活函数。这没有名称和超参数。
主路径的第二部分:
- 第二个CONV2D有
$F_2$
个形状为$(f,f)$
的滤波器,步长为 (1,1)。它的填充是“相同的”,其名称应该是conv_name_base + '2b'
。使用0作为随机初始化的种子。 - 第二个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2b'
。 - 然后应用ReLU激活函数。这没有名称和超参数。
主路径的第三部分:
- 第三个CONV2D有
$F_3$
个形状为 (1,1) 的滤波器,步长为 (1,1)。它的填充是“有效的”,其名称应该是conv_name_base + '2c'
。使用0作为随机初始化的种子。 - 第三个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2c'
。注意,在这个组件中没有ReLU激活函数。
最后一步:
- 快捷方式和输入一起相加。
- 然后应用ReLU激活函数。这没有名称和超参数。
代码:
1 | # GRADED FUNCTION: identity_block |
部署:
1 | tf.reset_default_graph() |
输出:
1 | out = [0.19716813 0. 1.3561227 2.1713073 0. 1.3324987 ] |
4.2The convolutional block
快捷路径上的CONV2D层用于将输入
卷积块的细节如下。
主路径的第一部分:
- 第一个CONV2D有
个形状为 (1,1) 的滤波器,步长为 (s,s)。它的填充是“有效的”,其名称应该是 conv_name_base + '2a'
。 - 第一个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2a'
。 - 然后应用ReLU激活函数。这没有名称和超参数。
主路径的第二部分:
- 第二个CONV2D有
个形状为 (f,f) 的滤波器,步长为 (1,1)。它的填充是“相同的”,其名称应该是 conv_name_base + '2b'
。 - 第二个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2b'
。 - 然后应用ReLU激活函数。这没有名称和超参数。
主路径的第三部分:
- 第三个CONV2D有
个形状为 (1,1) 的滤波器,步长为 (1,1)。它的填充是“有效的”,其名称应该是 conv_name_base + '2c'
。 - 第三个BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '2c'
。注意,在这个组件中没有ReLU激活函数。
快捷路径:
- CONV2D有
个形状为 (1,1) 的滤波器,步长为 (s,s)。它的填充是“有效的”,其名称应该是 conv_name_base + '1'
。 - BatchNorm 正在规范化通道轴。它的名字应该是
bn_name_base + '1'
。
最后一步:
- 将快捷路径和主路径的值相加。
- 然后应用ReLU激活函数。这没有名称和超参数。
代码:
1 | # GRADED FUNCTION: convolutional_block |
部署:
1 | tf.reset_default_graph() |
输出:
1 | out = [0.09018463 1.2348977 0.46822017 0.0367176 0. 0.65516603] |
4.3创建ResNet
ResNet-50模型的具体细节如下:
- 零填充使用 (3,3) 的填充对输入进行填充
- 第1阶段:
- 2D卷积有64个形状为 (7,7) 的滤波器,使用 (2,2) 的步长。它的名称是“conv1”。
- BatchNorm 应用于输入的通道轴。
- MaxPooling 使用 (3,3) 的窗口和 (2,2) 的步长。
- 第2阶段:
- 卷积块使用三组大小为 [64,64,256] 的滤波器,“f”为3,“s”为1,块是“a”。
- 2个恒等块使用三组大小为 [64,64,256] 的滤波器,“f”为3,块是“b”和“c”。
- 第3阶段:
- 卷积块使用三组大小为 [128,128,512] 的滤波器,“f”为3,“s”为2,块是“a”。
- 3个恒等块使用三组大小为 [128,128,512] 的滤波器,“f”为3,块是“b”,“c”和“d”。
- 第4阶段:
- 卷积块使用三组大小为 [256, 256, 1024] 的滤波器,“f”为3,“s”为2,块是“a”。
- 5个恒等块使用三组大小为 [256, 256, 1024] 的滤波器,“f”为3,块是“b”,“c”,“d”,“e”和“f”。
- 第5阶段:
- 卷积块使用三组大小为 [512, 512, 2048] 的滤波器,“f”为3,“s”为2,块是“a”。
- 2个恒等块使用三组大小为 [512, 512, 2048] 的滤波器,“f”为3,块是“b”和“c”。
- 2D平均池化使用 (2,2) 形状的窗口,其名称是“avg_pool”。
- 扁平化没有任何超参数或名称。
- 全连接(密集)层使用softmax激活将其输入减少到类别数。它的名称应该是
'fc' + str(classes)
。
代码:
1 | # GRADED FUNCTION: ResNet50 |
部署:
1 | model = ResNet50(input_shape = (64,64,3),classes = 6) |
测试输出:
1 | number of training examples = 1080 |
训练:
1 | model.fit(X_train, Y_train, epochs = 2, batch_size = 32) |
训练输出:
1 | 120/120 [==============================] - 7s 61ms/step |
利用已有模型进行测试:
1 | model = load_model('ResNet50.h5') |
5.模型总结
ResNet50我们可以通过下述代码进行输出:
1 | model.summary() |
ResNet50的模型结构如下:
利用这段代码可以将模型结构进行图框输出:
1 | plot_model(model, to_file='model.png') |