赞
踩
大数据实验室 第N次 学习打卡
ResNet是在2015年由微软实验室提出,作者是何恺明(本科清华、博士香港中文大学出来的大神)、孙剑(现任西安交大人工智能学院首任院长)等人提出,其论文《Deep Residual Learning for Image Recognition》,斩获当年ImageNet竞赛中分类任务第一名,目标检测第一名。(不说多了,就是NB)
卷积网络发展史中,VGG最高达到了19层,再就是GoogleNet,达到了22层。
增加网络的宽度和深度可以很好的提高网络的性能,深的网络一般都比浅的网络效果好。比如说VGG,该网络就是在AlexNex的基础上通过增加网络的深度大幅度提高了网络性能。但是,简单地不断增加深度,又会导致以下问题:
但是问题4随着网络层数的增加,网络发生了退化现象:随着网络层数增加,训练集错误率逐渐下降,然后趋于饱和,再增加深度时,错误率反而会增大,所以这并不是过拟合(过拟合在训练中错误率应该是一直减小)。
综上,ResNet应运而生!
通过直接将输入信息绕道传到输出(shortcut / skip connection),保护信息的完整性,整个网络只需要学习输入、输出差别的那一部分,简化学习目标和难度。VGGNet和ResNet的对比如下图所示。
上表来自于原论文,展示了5种ResNet不同的结构,分别是18,34,50,101和152。
从第一列可以看出,所有的网络都由5部分组成:conv1_x, conv2_x, conv3_x, conv4_x, conv5_x。
以50层为例:首先有个输入7 x 7 x 64的卷积层,然后经过3 + 4 + 6 +3 = 16个bulding block,每个block为3层,所以由3 x 16 = 48层,最后有个fc层(全连接层),所以1 + 48 +1 = 50层。(我们一般说层数指的是卷积层或者全连接层,不包括激活层和池化层)
如果深层网络的后面那些层是恒等映射,那么模型就退化为一个浅层网络。那现在要解决的就是学习恒等映射函数了。 但是直接让一些层去拟合一个潜在的恒等映射函数H(x) = x,比较困难,这就是深层网络难以训练的原因。
但是,如果将这个问题转换为求解网络的残差映射函数,也就是F(x),其中F(x) = H(x)-x。
那么咱们要求解的问题变成了H(x) = F(x)+x。
假设当前网络的深度能够使得错误率最低,如果继续增加咱们的层数(普通卷积网络再增加就会出现网络退化问题了),为了保证下一层的网络状态仍然是最优状态,咱们只需要把令F(x)=0就好了,因为x是当前输出的最优解,为了让它成为下一层的最优解也就是希望咱们的输出H(x)=x的话,是不是只要让F(x)=0就行了,只用小小的更新F(x)部分的权重值,不用像一般的卷积层一样大动干戈。
从ResNet网络结构图中可以看出50层以下的和50层以上的ResNet有明显差别,差别就在于残差(residual)结构,50层以上采用BottleNeck(瓶颈,因为非常像瓶颈):
可以通过下面这个图更加直观的理解Bottleneck:
还是以34层的ResNet为例:
图中的有向曲线称为shotcut(捷径)或者skip connection,为啥会有虚实之分呢?
之所以有虚线是因为主分支(主分支的每一层都有预期的Size,如下图)与shortcut的输出特征矩阵shape必须相同,所以需要通过最大池化下采样对原输入进行改变。
图中我用黄色虚线分开了50层以下和50层以上的,是因为它们的网络结构又有所不同,这里的具体不同之处就是shortcut的虚实线:
总之,一句话,就是必须保证主分支和shortcut的输出矩阵的宽、高、维度必须一样,因为这样才能对两个矩阵进行相加!
from keras.layers import Conv2D, BatchNormalization, Dense, Flatten,\ MaxPooling2D, AveragePooling2D, ZeroPadding2D, Input, add from keras.models import Model from keras.utils import plot_model from keras.metrics import top_k_categorical_accuracy def conv_block(inputs, neuron_num, kernel_size, use_bias, padding= 'same', strides= (1, 1), with_conv_short_cut = False): conv1 = Conv2D( neuron_num, kernel_size = kernel_size, activation= 'relu', strides= strides, use_bias= use_bias, padding= padding )(inputs) conv1 = BatchNormalization(axis = 1)(conv1) conv2 = Conv2D( neuron_num, kernel_size= kernel_size, activation= 'relu', use_bias= use_bias, padding= padding)(conv1) conv2 = BatchNormalization(axis = 1)(conv2) if with_conv_short_cut: inputs = Conv2D( neuron_num, kernel_size= kernel_size, strides= strides, use_bias= use_bias, padding= padding )(inputs) return add([inputs, conv2]) else: return add([inputs, conv2]) inputs = Input(shape= [224, 224, 3]) x = ZeroPadding2D((3, 3))(inputs) # Define the converlutional block 1 x = Conv2D(64, kernel_size= (7, 7), strides= (2, 2), padding= 'valid')(x) x = BatchNormalization(axis= 1)(x) x = MaxPooling2D(pool_size= (3, 3), strides= (2, 2), padding= 'same')(x) # Define the converlutional block 2 x = conv_block(x, neuron_num= 64, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 64, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 64, kernel_size= (3, 3), use_bias= True) # Define the converlutional block 3 x = conv_block(x, neuron_num= 128, kernel_size= (3, 3), use_bias= True, strides= (2, 2), with_conv_short_cut= True) x = conv_block(x, neuron_num= 128, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 128, kernel_size= (3, 3), use_bias= True) # Define the converlutional block 4 x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True, strides= (2, 2), with_conv_short_cut= True) x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 256, kernel_size= (3, 3), use_bias= True) # Define the converltional block 5 x = conv_block(x, neuron_num= 512, kernel_size= (3, 3), use_bias= True, strides= (2, 2), with_conv_short_cut= True) x = conv_block(x, neuron_num= 512, kernel_size= (3, 3), use_bias= True) x = conv_block(x, neuron_num= 512, kernel_size= (3, 3), use_bias= True) x = AveragePooling2D(pool_size=(7, 7))(x) x = Flatten()(x) x = Dense(8, activation='softmax')(x) model = Model(inputs= inputs, outputs= x) # Print the detail of the model model.summary() # compile the model model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc',top_k_categorical_accuracy]) plot_model(model, to_file= 'C:/Users/12394/PycharmProjects/Keras/model_ResNet-34.png')
在深度残差网络的设计中通常都是一种“力求简洁”的设计方式,只是单纯加深网络,所有的卷积层几乎都采用3x3 的卷积核,而且绝不在隐藏层中设计任何的全连接层,也不会在训练的过程中考虑使用任何的 DropOut 机制。
到目前为止,在图像分类( image classification )、对象检测( object detection )、语义分割( semantic segmentation )等领域的应用中,深度残差网络都表现出了良好的效果。
《Deep Residual Learning for Image Recognition》
代码参考:Keras大法(9)——实现ResNet-34模型
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。