参考:https://blog.csdn.net/u013733326/article/details/80250818
欢迎来到本周的第二次作业!您将学习如何使用剩余网络(ResNets)构建非常深的卷积网络。理论上,深度很深的网络可以代表非常复杂的功能;但实际上,他们很难训练。剩余网络,由He等人介绍,允许你训练更深层次的网络比以前实际可行。
在这个作业中,你将:
实现resnet的基本构建块。
把这些构建块放在一起来实现和训练一个最先进的图像分类神经网络。
这项任务将在Keras中完成。
在开始这个问题之前,让我们运行下面的单元来加载所需的包。
import numpy as np from keras import layers from keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D from keras.models import Model, load_model from keras.preprocessing import image from keras.utils import layer_utils from keras.utils.data_utils import get_file from keras.applications.imagenet_utils import preprocess_input import pydot from IPython.display import SVG from keras.utils.vis_utils import model_to_dot from keras.utils import plot_model from resnets_utils import * from keras.initializers import glorot_uniform import scipy.misc from matplotlib.pyplot import imshow %matplotlib inline import os os.environ['KERAS_BACKEND'] = 'tensorflow' import keras.backend as K K.set_image_data_format('channels_last') K.set_learning_phase(1)
1 - The problem of very deep neural networks深层网络的麻烦
上周,我们构建了第一个卷积神经网络。最近几年,卷积神经网络变得越来越深,从只从几层(例如AlexNet)到超过一百层。
使用深层网络最大的好处就是它能够完成很复杂的功能,它能够从边缘(浅层)到非常复杂的特征(深层)中不同的抽象层次的特征中学习。然而,使用比较深的网络通常没有什么好处,一个特别大的麻烦就在于训练的时候会产生梯度消失,非常深的网络通常会有一个梯度信号,该信号会迅速的消退,从而使得梯度下降变得非常缓慢。更具体的说,在梯度下降的过程中,当你从最后一层回到第一层的时候,你在每个步骤上乘以权重矩阵,因此梯度值可以迅速的指数式地减少到0(在极少数的情况下会迅速增长,造成梯度爆炸)。
在训练的过程中,你可能会看到开始几层的梯度的大小(或范数)迅速下降到0,如下图:
**Figure 1** : **Vanishing gradient**梯度消失
在前几层中随着迭代次数的增加,学习的速度会下降的非常快。
为了解决这个问题,我们将构建残差网络。
2 - Building a Residual Network 构建一个残差网络
在残差网络中,一个“捷径(shortcut)”或者说“跳跃连接(skip connection)”允许梯度直接反向传播到更浅的层,如下图:
**Figure 2** : A ResNet block showing a **skip-connection**
图像左边是神经网络的主路,图像右边是添加了一条捷径的主路,通过这些残差块堆叠在一起,可以形成一个非常深的网络。
我们在视频中可以看到使用捷径的方式使得每一个残差块能够很容易学习到恒等式功能,这意味着我们可以添加很多的残差块而不会损害训练集的表现。
残差块有两种类型,主要取决于输入输出的维度是否相同,下面我们来看看吧~
2.1 - The identity block 恒等块
恒等块是残差网络使用的的标准块,对应于输入的激活值(比如????[????])与输出激活值(比如????[????+ 2])具有相同的维度。为了具象化残差块的不同步骤,我们来看看下面的图吧~,这里有一个替代的图表显示各个步骤:
**Figure 3** : **Identity block.** Skip connection "skips over" 2 layers.
上图中,上面的曲线路径是“捷径”,下面的直线路径是主路径。在上图中,我们依旧把CONV2D 与 ReLU包含到了每个步骤中,为了提升训练的速度,我们在每一步也把数据进行了归一化(BatchNorm),不要害怕这些东西,因为Keras框架已经实现了这些东西,调用BatchNorm只需要一行代码。 在实践中,我们要做一个更强大的版本:跳跃连接会跳过3个隐藏层而不是两个,就像下图:
**Figure 4** : **Identity block.** Skip connection "skips over" 3 layers.
下面是单独的步骤。
主路径第一部分:
1、第一CONV2D有????1个过滤器,大小为(1, 1),步长为(1, 1)。它的填充方式是“valid”,命名规则是 conv_name_base + '2a'。使用0作为随机初始化的种子。
2、第一个BatchNorm是对channels(通道)的轴进行规范化、归一化。其命名规则为 bn_name_base + '2a'。
3、然后应用ReLU激活函数。它没有命名和超参数。
主路径第二分部分:
1、第二个CONV2D有????2个过滤器,大小为(????,????)和(1,1)的步伐。它的填充方式是“same”,它的命名规则是 conv_name_base + '2b'。使用0作为随机初始化的种子。
2、第二个BatchNorm是通道的轴归一化。它的命名规则为 bn_name_base + '2b'。
3、然后应用ReLU激活函数。它没有名称和超参数。
主路径第三分量:
1、第三CONV2D有????3个过滤器,形状大小为(1,1)和(1,1)的步伐。它的填充方式是“valid”,它的命名方式是 conv_name_base + '2c'。使用0作为随机初始化的种子。
2、第三个BatchNorm是通道的轴归一化。它的名称应该是bn_name_base + '2c'
3、注意,该组件中没有ReLU激活函数。
最后一步:
1、将捷径与输入加在一起
2、然后应用ReLU激活函数。它没有命名和超参数。
接下来我们就要实现残差网络的恒等块了,请务必查看下面的中文手册:
1 # GRADED FUNCTION: identity_block 2 3 def identity_block(X, f, filters, stage, block): 4 """ 5 Implementation of the identity block as defined in Figure 3 实现图3的恒等块 6 7 Arguments: 8 X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev) 输入的tensor类型的数据,维度为( m, n_H_prev, n_W_prev, n_H_prev ) 9 f -- integer, specifying the shape of the middle CONV's window for the main path 整数,指定主路径中间的CONV窗口的维度 10 filters -- python list of integers, defining the number of filters in the CONV layers of the main path 11 整数列表,定义了主路径每层的卷积层的过滤器数量 12 13 stage -- integer, used to name the layers, depending on their position in the network 14 整数,根据每层的位置来命名每一层,与block参数一起使用。 15 block -- string/character, used to name the layers, depending on their position in the network 16 字符串,据每层的位置来命名每一层,与stage参数一起使用。 17 18 Returns: 19 X -- output of the identity block, tensor of shape (n_H, n_W, n_C) 恒等块的输出,tensor类型,维度为(n_H, n_W, n_C) 20 """ 21 22 # defining name basis 定义命名规则 23 conv_name_base = 'res' + str(stage) + block + '_branch' 24 bn_name_base = 'bn' + str(stage) + block + '_branch' 25 26 # Retrieve Filters 获取过滤器 27 F1, F2, F3 = filters 28 29 # Save the input value. You'll need this later to add back to the main path. 保存输入数据,将会用于为主路径添加捷径 30 X_shortcut = X 31 32 # First component of main path 主路径的第一部分 33 ##卷积层 34 X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2a', kernel_initializer = glorot_uniform(seed=0))(X) 35 #归一化 36 X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X) 37 #使用relu激活函数 38 X = Activation('relu')(X) 39 40 ### START CODE HERE ### 41 42 # Second component of main path (≈3 lines)主路径的第二部分 43 X = Conv2D(filters = F2, kernel_size = (f, f), strides = (1, 1), padding = 'same', name = conv_name_base + '2b', kernel_initializer = glorot_uniform(seed = 0))(X) 44 X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X) 45 X = Activation('relu')(X) 46 47 # Third component of main path (≈2 lines) 48 X = Conv2D(filters = F3, kernel_size = (1, 1), strides = (1, 1), padding = 'valid', name = conv_name_base + '2c', kernel_initializer = glorot_uniform(seed=0))(X) 49 50 X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X) 51 52 # Final step: Add shortcut value to main path, and pass it through a RELU activation (≈2 lines)最后一步,将捷径与输入加在一起 53 X = layers.add([X, X_shortcut]) 54 X = Activation('relu')(X) 55 56 ### END CODE HERE ### 57 58 return X