残差网络的搭建
利用上周学的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 keras.backend as K
tf.compat.v1.disable_eager_execution()
K.set_image_data_format('channels_last')
K.set_learning_phase(1)
深层网络的好与坏
好处就是它能够完成很复杂的功能,它能够从边缘(浅层)到非常复杂的特征(深层)中不同的抽象层次的特征中学习。
坏处就在于训练的时候会产生梯度消失,非常深的网络通常会有一个梯度信号,该信号会迅速的消退,从而使得梯度下降变得非常缓慢。
如这张图所示:
构建一个残差网络
在残差网络中,一个“捷径(shortcut)”或者说“跳跃连接(skip connection)”允许梯度直接反向传播到更浅的层,如下图:
使用捷径的方式使得每一个残差块能够很容易学习到恒等式功能,这意味着我们可以添加很多的残差块而不会损害训练集的表现。
残差块有两种类型,主要取决于输入输出的维度是否相同。
恒等块
恒等块是残差网络使用的的标准块,输入的激活值和输出的激活值维度相同。
曲线路径是“捷径”,直线路径是主路径。把CONV2D 与 ReLU包含到了每个步骤中,为了提升训练的速度,我们在每一步也把数据进行了归一化(BatchNorm),Keras框架已经实现了这些东西,调用BatchNorm只需要一行代码。
实践中我们跳跃连接会跳过3个隐藏层而不是两个:
实现残差网络的恒等块有4步:
- 主路径的第一部分:
第一个CONV2D有F1个过滤器,其大小为(1,1),步长为(1,1),使用填充方 式为“valid”,命名规则为conv_name_base + ‘2a’,使用0作为随机种子为其初始化。
第一个BatchNorm是通道的轴归一化,其命名规则为bn_name_base + ‘2a’。
接着使用ReLU激活函数,它没有命名也没有超参数。 - 主路径的第二部分:
第二个CONV2D有F2个过滤器,其大小为(f,f),步长为(1,1),使用填充方 式为“same”,命名规则为conv_name_base + ‘2b’,使用0作为随机种子为其初始化。
第一个BatchNorm是通道的轴归一化,其命名规则为bn_name_base + ‘2b’。
接着使用ReLU激活函数,它没有命名也没有超参数。 - 主路径的第三部分:
第三个CONV2D有F3个过滤器,其大小为(1,1),步长为(1,1),使用填充方 式为“valid”,命名规则为conv_name_base + ‘2c’,使用0作为随机种子为其初始化。
第三个BatchNorm是通道的轴归一化,其命名规则为bn_name_base + ‘2c’。 - 将捷径与输入加在一起使用ReLU激活函数
def identity_block(X, f, filters, stage, block):
conv_name_base = 'res' + str(stage) + block + '_branch'
bn_name_base = 'bn' + str(stage) + block + '_branch'
F1, F2, F3 = filters
X_shortcut = X
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)
X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X)
X = Activation('relu')(X)
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)
X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X)
X = Activation('relu')(X)
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)
X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X)
X = Add()([X,X_shortcut])
X = Activation('relu')(X