• ResNet架构解析


    参考论文:Deep Residual Learning for Image Recognition

    这篇文章我以前也翻译过,但当时感觉就大概了解了一下,由于后面好多东西都需要用到残差,就再回来看看。

    1、残差块

    image-20220805172609745

      上图中的weight layer是3*3的卷积层;

      F(x)表示经过连个卷积层计算后得到的结果;

      identity表示“恒等映射”,也成为“shortcut connection”,说白了就是把x的值不做任何处理直接传过去。

      最后计算F(x)+x,这里的F(x)跟x是种类相同的信号,所以其对应位置进行相加。

    这里残差F(x)与输入x的维度一致才能实现相加,如果不一致,可以使用shortcur connection执行一个线性投影以匹配shape

    2、残差网络优势

    • 易于优化、收敛
    • 解决网络推化问题
    • 网络的层数可以很深,准确率大大提升

    3、带残差与不带残差的结构对比

    image-20220805173648560

    4、残差分支的下采样

    当残差分支出出现下采样时,对于short connection有两种方案:

    • A方案:对多出来的通道使用padding补零填充
    • B方案:用1*1卷积升维

    不管采取哪种方案,shortcut分支第一个卷积层步长都为2

    5、ResNet各层参数配置

    image-20220805174133771

    注意,这里18-layer和34-layer的搭建方式一致,50-layer、101-layer、152-layer的搭建方式一致。

    image-20220805174347498

    上图左边为ResNet18和ResNet34的残差快,右边为ResNet50/101/152对应的残差快。

    5.1 ResNet18/34

    图片来源:6.1 ResNet网络结构,BN以及迁移学习详解

    image-20220805174544232

    实线表示输入和输出的shape相同,虚线表示输入和输出的shape不一致,需要使用1*1卷积保持维度相等才能执行Add操作。

    5.2 ResNet50/101/152

    image-20220805174749186

    其实也不是非要使用1*1卷积进行维度变换,原论文作者给出了三种解决方案:

    • A 所有shortcut物额外参数,升维时用padding补零

    • 平常的shrotcut用identity mapping ,升维时用1*1卷积

    • 所有的shortcut都使用1*1卷积。

      image-20220805175132788

    6、模型复现

    6.1 ResNet18

    image-20220515212714040

    实线表示维度相同,虚线表示维度不同,需要使用1*1卷积调整维度。

    结合下表看

    image-20220805175515210

      对于ResNet18和ResNet34,我们发现除过conv2_x都是实现连接之外,conv3_x、copnv4_x、conv5_x的第一个卷积都是虚线连接,即输入和输出的shape不一致,需要使用1*1卷积保持维度一致之后才能相加。

      ResNet18代码复现:

    import tensorflow as tf
    from tensorflow.keras.layers import *
    from tensorflow.keras.models import Model
    from plot_model import plot_model
    
    image_size=224
    num_classes=1000
    
    # 定义残差单元
    # 定义残差单元
    # residual_path决定是否需要1*1卷积操作,保证x能和F(x)维度相同
    def block(x, filters, strides=1,residual_path=False):
        # projection shortcut
        if residual_path:
            shortcut = Conv2D(filters, kernel_size=1, strides=strides, padding='same',use_bias=False)(x)
            # epsilon为BN公式中防止分母为零的值
            shortcut = BatchNormalization(epsilon=1.001e-5)(shortcut)
        else:
            # identity_shortcut
            shortcut = x
        # 2个卷积层
        x = Conv2D(filters=filters, kernel_size=3, strides=strides, padding='same',use_bias=False)(x)
        x = BatchNormalization(epsilon=1.001e-5)(x)
        x = Activation('relu')(x)
    
    
        x = Conv2D(filters=filters, kernel_size=3, strides=1, padding='same',use_bias=False)(x)
        x = BatchNormalization(epsilon=1.001e-5)(x)
    
        x = Add()([x, shortcut])
        x = Activation('relu')(x)
        return x
    # 堆叠残差单元
    # 从第二个block开始,每次的第一个输入和输出维度不相同,需要1*1卷积(虚线连接)
    def stack(x, filters, blocks):
        x = block(x, filters, strides=2,residual_path=True)
        for i in range(blocks - 1):
            x = block(x, filters,residual_path=False)
        return x
    # 第一个block块的输入和输出维度相同(实线连接)
    def stack_conv2_x(x,filters,blocks):
        for i in range(blocks):
            x=block(x,filters,residual_path=False)
        return x
    # 定义ResNet18
    inputs = Input(shape=(image_size, image_size, 3))
    # 填充3圈0,填充后图像从224×224变成230×230
    x = ZeroPadding2D((3, 3))(inputs)
    x = Conv2D(filters=64, kernel_size=7, strides=2, padding='valid')(x)
    x = BatchNormalization(epsilon=1.001e-5)(x)
    x = Activation('relu')(x)
    # 填充1圈0
    x = ZeroPadding2D((1, 1))(x)
    x = MaxPool2D(pool_size=3, strides=2, padding='valid')(x)
    # 堆叠残差结构
    # blocks表示堆叠数量, 每个blocks中有三个卷积层
    # filters是每个卷积块第一层的filters,第二层与第一层相等
    # 第三层的filters为第一层的四倍
    x = stack_conv2_x(x, filters=64, blocks=2)
    x = stack(x, filters=128, blocks=2)
    x = stack(x, filters=256, blocks=2)
    x = stack(x, filters=512, blocks=2)
    # 根据特征图大小进行平均池化,池化后得到2维数据
    x = GlobalAvgPool2D()(x)
    x = Dense(num_classes, activation='softmax')(x)
    # 定义模型
    model = Model(inputs=inputs, outputs=x)
    model.summary()
    plot_model(model,to_file='img/ResNet18.png',show_shapes=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    还有一种更优雅的搭建方式,看这里:经典卷积网络–ResNet残差网络

    网络结构如下:

    image-20220805175939218

    6.2 ResNet50

    image-20220805180038572

    image-20220805180112606

      上图右边为ResNet50的部分结构图,通过上边的参数和上图的结构可以发现,每个conv块中的第一个块都是使用虚线连接(即输入和输出维度不相同,需要使用1*1卷积进行维度变换)。

      我们以conv3_x为例,输入为[56,56,256],输出的shape为[28,28,512]。可以还想,此时输入和输出的唯独已经不一致了。图像的高宽和通道数都不一致,所以使用512个1*1大小的卷积核,步长为2来保持shape一致可以执行相加操作。如下图所示。

    image-20220805180544191

    ResNet50代码复现如下:

    import tensorflow as tf
    from tensorflow.keras.layers import *
    from tensorflow.keras.models import Model
    from plot_model import plot_model
    
    image_size=224
    num_classes=1000
    
    
    # 定义残差单元
    def block(x, filters, strides=1, conv_shortcut=True):
        # projection shortcut
        if conv_shortcut:
            shortcut = Conv2D(filters * 4, kernel_size=1, strides=strides, padding='valid')(x)
            # epsilon为BN公式中防止分母为零的值
            shortcut = BatchNormalization(epsilon=1.001e-5)(shortcut)
        else:
            # identity_shortcut
            shortcut = x
        # 3个卷积层
        x = Conv2D(filters=filters, kernel_size=1, strides=strides, padding='valid')(x)
        x = BatchNormalization(epsilon=1.001e-5)(x)
        x = Activation('relu')(x)
    
        x = Conv2D(filters=filters, kernel_size=3, strides=1, padding='same')(x)
        x = BatchNormalization(epsilon=1.001e-5)(x)
        x = Activation('relu')(x)
    
        x = Conv2D(filters=filters * 4, kernel_size=1, strides=1, padding='valid')(x)
        x = BatchNormalization(epsilon=1.001e-5)(x)
    
        x = Add()([x, shortcut])
        x = Activation('relu')(x)
        return x
    
    
    # 堆叠残差单元
    def stack(x, filters, blocks, strides):
        # 观察ResNet50结构可知,只有每个block中的第一块的第一层输入和输出的维度不一样
        # 需要使用1*1卷积保持维度一致再Add,其他的输入和输出的shape是一致的,可以直接Add
        x = block(x, filters, strides=strides)
        for i in range(blocks - 1):
            x = block(x, filters, conv_shortcut=False)
        return x
    
    
    # 定义ResNet50
    inputs = Input(shape=(image_size, image_size, 3))
    # 填充3圈0,填充后图像从224×224变成230×230
    x = ZeroPadding2D((3, 3))(inputs)
    x = Conv2D(filters=64, kernel_size=7, strides=2, padding='valid')(x)
    x = BatchNormalization(epsilon=1.001e-5)(x)
    x = Activation('relu')(x)
    # 填充1圈0
    x = ZeroPadding2D((1, 1))(x)
    x = MaxPool2D(pool_size=3, strides=2, padding='valid')(x)
    # 堆叠残差结构
    # blocks表示堆叠数量, 每个blocks中有三个卷积层
    # filters是每个卷积块第一层的filters,第二层与第一层相等
    # 第三层的filters为第一层的四倍
    x = stack(x, filters=64, blocks=3, strides=1)
    x = stack(x, filters=128, blocks=4, strides=2)
    x = stack(x, filters=256, blocks=6, strides=2)
    x = stack(x, filters=512, blocks=3, strides=2)
    # 根据特征图大小进行平均池化,池化后得到2维数据
    x = GlobalAvgPool2D()(x)
    x = Dense(num_classes, activation='softmax')(x)
    # 定义模型
    model = Model(inputs=inputs, outputs=x)
    model.summary()
    plot_model(model,to_file='img/ResNet50Test.png',show_shapes=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    image-20220805180626151

    注意观察每个conv块的第一个块的输入和输出维度。

    ResNet18与ResNet34的搭建方式是一样的,ResNet50与ResNet101、ResNet152搭建方式是一样的。只不过每层的block个数不一致,这里不再赘述。

    Reference

    经典卷积网络–ResNet残差网络

    Deep Residual Learning for Image Recognition

    6.1 ResNet网络结构,BN以及迁移学习详解

    【精读AI论文】ResNet深度残差网络

    (10) Resnet18、34 残差网络复现,附python完整代码

    resnet18 50网络结构以及pytorch实现代码

    神经网络学习小记录20——ResNet50模型的复现详解

  • 相关阅读:
    I2C总线协议
    CS创世 SD NAND与SPI NAND的对比
    【字符串函数和内存函数】——深入剖析
    【翻译】Seastar 教程(二)
    [附源码]计算机毕业设计JAVA小型银行贷款管理系统
    UE学习记录06----根据Actor大小自适应相机位置
    数据库基本概念与安装MySQL数据库
    I/O处理器与DMA控制器与I/O通道
    时间模块之datatime模块、os模块、sys模块、json模块、json模块实操
    网关、微服务、Nginx、OpenResty和Kong
  • 原文地址:https://blog.csdn.net/qq_43753724/article/details/126183292