ResNet原理分析和代码实现

大数据实验室 第N次 学习打卡

一、引入

ResNet是在2015年由微软实验室提出,作者是何恺明(本科清华、博士香港中文大学出来的大神)、孙剑(现任西安交大人工智能学院首任院长)等人提出,其论文《Deep Residual Learning for Image Recognition》,斩获当年ImageNet竞赛中分类任务第一名,目标检测第一名。(不说多了,就是NB)

二、提出ResNet的原因

卷积网络发展史中,VGG最高达到了19层,再就是GoogleNet,达到了22层。
增加网络的宽度和深度可以很好的提高网络的性能,深的网络一般都比浅的网络效果好。比如说VGG,该网络就是在AlexNex的基础上通过增加网络的深度大幅度提高了网络性能。但是,简单地不断增加深度,又会导致以下问题:

  1. 梯度消失或者梯度爆炸
  2. 模型过拟合
  3. 计算资源的消耗
  4. 退化问题
    问题1可以通过Batch Normalization可以避免。
    问题2可以通过增大数据量,配合Dropout来避免。
    问题3可以通过CPU集群来解决。

但是问题4随着网络层数的增加,网络发生了退化现象:随着网络层数增加,训练集错误率逐渐下降,然后趋于饱和,再增加深度时,错误率反而会增大,所以这并不是过拟合(过拟合在训练中错误率应该是一直减小)。
在这里插入图片描述
综上,ResNet应运而生!

通过直接将输入信息绕道传到输出(shortcut / skip connection),保护信息的完整性,整个网络只需要学习输入、输出差别的那一部分,简化学习目标和难度。VGGNet和ResNet的对比如下图所示。
在这里插入图片描述

三、ResNet网络

1.网络结构

在这里插入图片描述
上表来自于原论文,展示了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层。(我们一般说层数指的是卷积层或者全连接层,不包括激活层和池化层)

2.残差函数

如果深层网络的后面那些层是恒等映射,那么模型就退化为一个浅层网络。那现在要解决的就是学习恒等映射函数了。 但是直接让一些层去拟合一个潜在的恒等映射函数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(瓶颈,因为非常像瓶颈):
在这里插入图片描述

(有木有觉得我的图画得非常形象,hahahah)

可以通过下面这个图更加直观的理解Bottleneck:
在这里插入图片描述

3.最大池化下采样(Downsampling)

还是以34层的ResNet为例:
在这里插入图片描述
图中的有向曲线称为shotcut(捷径)或者skip connection,为啥会有虚实之分呢?
之所以有虚线是因为主分支(主分支的每一层都有预期的Size,如下图)与shortcut的输出特征矩阵shape必须相同,所以需要通过最大池化下采样对原输入进行改变。
在这里插入图片描述
图中我用黄色虚线分开了50层以下和50层以上的,是因为它们的网络结构又有所不同,这里的具体不同之处就是shortcut的虚实线:

  • 50层以下只分别在Conv3、Conv4、Con5的第一个shortcut进行了最大池化下采样,因为Conv2的shortcut的输入和主分支的输出矩阵shape是一样的。
  • 50层及以上的相比前者,在Conv2的第一个shortcut就进行了最大池化下采样,因为主分支的输出矩阵维度为256,而shortcut的输入矩阵维度为64.

总之,一句话,就是必须保证主分支和shortcut的输出矩阵的宽、高、维度必须一样,因为这样才能对两个矩阵进行相加!

四、基于keras实现ResNet-18代码

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模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shashank497

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值