【深度学习】ResNet解读及代码实现

版权声明:本文为博主原创文章,请尊重原创,转载请注明原文地址和作者信息! https://blog.csdn.net/zzc15806/article/details/83540661

简介

ResNet是何凯明大神在2015年提出的一种网络结构,获得了ILSVRC-2015分类任务的第一名,同时在ImageNet detection,ImageNet localization,COCO detection和COCO segmentation等任务中均获得了第一名,在当时可谓是轰动一时。

ResNet又名残差神经网络,指的是在传统卷积神经网络中加入残差学习(residual learning)的思想,解决了深层网络中梯度弥散和精度下降(训练集)的问题,使网络能够越来越深,既保证了精度,又控制了速度。


出发点

随着网络的加深,梯度弥散问题会越来越严重,导致网络很难收敛甚至无法收敛。梯度弥散问题目前有很多的解决办法,包括网络初始标准化,数据标准化以及中间层的标准化(Batch Normalization)等。但是网络加深还会带来另外一个问题:随着网络加深,出现训练集准确率下降的现象,如下图,

很多同学第一反应肯定是“这不是过拟合了吗”。其实,这不是由于过拟合引起的。过拟合通常指模型在训练集表现很好,在测试集很差。凯明大神针对这个问题提出了残差学习的思想。

残差学习指的是什么?

残差学习的思想就是上面这张图,可以把它理解为一个block,定义如下:

y=F(x,\{W_i\})+x

残差学习的block一共包含两个分支或者两种映射(mapping):

1. identity mapping,指的是上图右边那条弯的曲线。顾名思义,identity mapping指的就是本身的映射,也就是x自身;

2. residual mapping,指的是另一条分支,也就是F(x)部分,这部分称为残差映射,也就是y-x

为什么残差学习可以解决“网络加深准确率下降”的问题?

对于一个神经网络模型,如果该模型是最优的,那么训练就很容易将residual mapping优化到0,此时只剩下identity mapping,那么无论怎么增加深度,理论上网络会一直处于最优状态。因为相当于后面所有增加的网络都会沿着identity mapping(自身)进行信息传输,可以理解为最优网络后面的层数都是废掉的(不具备特征提取的能力),实际上没起什么作用。这样,网络的性能也就不会随着深度的增加而降低了。


网络结构

文中提到了一个名词叫“Shortcut Connection”,实际上它指的就是identity mapping,这里先解释一下,免的大家后面会confuse。针对不同深度的ResNet,作者提出了两种Residual Block:

 对上图做如下说明:

1. 左图为基本的residual block,residual mapping为两个64通道的3x3卷积,输入输出均为64通道,可直接相加。该block主要使用在相对浅层网络,比如ResNet-34;

2. 右图为针对深层网络提出的block,称为“bottleneck” block,主要目的就是为了降维。首先通过一个1x1卷积将256维通道(channel)降到64通道,最后通过一个256通道的1x1卷积恢复。

通过上面的介绍我们知道,residual mapping和identity mapping是沿通道维度相加的,那么如果通道维度不相同怎么办?

作者提出在identity mapping部分使用1x1卷积进行处理,表示如下:

y=F(x,\{W_i\})+W_sx

其中,W_s指的是1x1卷积操作。

下图为VGG-19,Plain-34(没有使用residual结构)和ResNet-34网络结构对比:

对上图进行如下说明:

1. 相比于VGG-19,ResNet没有使用全连接层,而使用了全局平均池化层,可以减少大量参数。VGG-19大量参数集中在全连接层;

2. ResNet-34中跳跃连接“实线”为identity mapping和residual mapping通道数相同,“虚线”部分指的是两者通道数不同,需要使用1x1卷积调整通道维度,使其可以相加。 

论文一共提出5种ResNet网络,网络参数统计表如下:


代码实现

 本节使用keras实现ResNet-18。

from keras.layers import Input
from keras.layers import Conv2D, MaxPool2D, Dense, BatchNormalization, Activation, add, GlobalAvgPool2D
from keras.models import Model
from keras import regularizers
from keras.utils import plot_model
from keras import backend as K


def conv2d_bn(x, nb_filter, kernel_size, strides=(1, 1), padding='same'):
    """
    conv2d -> batch normalization -> relu activation
    """
    x = Conv2D(nb_filter, kernel_size=kernel_size,
                          strides=strides,
                          padding=padding,
                          kernel_regularizer=regularizers.l2(0.0001))(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    return x


def shortcut(input, residual):
    """
    shortcut连接,也就是identity mapping部分。
    """

    input_shape = K.int_shape(input)
    residual_shape = K.int_shape(residual)
    stride_height = int(round(input_shape[1] / residual_shape[1]))
    stride_width = int(round(input_shape[2] / residual_shape[2]))
    equal_channels = input_shape[3] == residual_shape[3]

    identity = input
    # 如果维度不同,则使用1x1卷积进行调整
    if stride_width > 1 or stride_height > 1 or not equal_channels:
        identity = Conv2D(filters=residual_shape[3],
                           kernel_size=(1, 1),
                           strides=(stride_width, stride_height),
                           padding="valid",
                           kernel_regularizer=regularizers.l2(0.0001))(input)

    return add([identity, residual])


def basic_block(nb_filter, strides=(1, 1)):
    """
    基本的ResNet building block,适用于ResNet-18和ResNet-34.
    """
    def f(input):

        conv1 = conv2d_bn(input, nb_filter, kernel_size=(3, 3), strides=strides)
        residual = conv2d_bn(conv1, nb_filter, kernel_size=(3, 3))

        return shortcut(input, residual)

    return f


def residual_block(nb_filter, repetitions, is_first_layer=False):
    """
    构建每层的residual模块,对应论文参数统计表中的conv2_x -> conv5_x
    """
    def f(input):
        for i in range(repetitions):
            strides = (1, 1)
            if i == 0 and not is_first_layer:
                strides = (2, 2)
            input = basic_block(nb_filter, strides)(input)
        return input

    return f


def resnet_18(input_shape=(224,224,3), nclass=1000):
    """
    build resnet-18 model using keras with TensorFlow backend.
    :param input_shape: input shape of network, default as (224,224,3)
    :param nclass: numbers of class(output shape of network), default as 1000
    :return: resnet-18 model
    """
    input_ = Input(shape=input_shape)

    conv1 = conv2d_bn(input_, 64, kernel_size=(7, 7), strides=(2, 2))
    pool1 = MaxPool2D(pool_size=(3, 3), strides=(2, 2), padding='same')(conv1)

    conv2 = residual_block(64, 2, is_first_layer=True)(pool1)
    conv3 = residual_block(128, 2, is_first_layer=True)(conv2)
    conv4 = residual_block(256, 2, is_first_layer=True)(conv3)
    conv5 = residual_block(512, 2, is_first_layer=True)(conv4)

    pool2 = GlobalAvgPool2D()(conv5)
    output_ = Dense(nclass, activation='softmax')(pool2)

    model = Model(inputs=input_, outputs=output_)
    model.summary()

    return model

if __name__ == '__main__':
    model = resnet_18()
    plot_model(model, 'ResNet-18.png')  # 保存模型图

猜你喜欢

转载自blog.csdn.net/zzc15806/article/details/83540661