经典CNN的实现 ResNet

经典CNN的实现 ResNet

ResNet诞生于2015年,当年ImageNet竞赛冠军,Top5错误率为3.57%

论文出处:Kaiming He, Xiangyu Zhang, Shaoqing Ren. Deep Residual Learning for Image Recognition. In CPVR, 2016.

引入残差结构

从之前介绍的 4 种 CNN 经典网络结构我们也可以看出,网络层数的发展趋势是不断加深的。 这是由于深度网络本身集成了低层/中层/高层特征和分类器,以多层首尾相连的方式存在,所以可以通过增加堆叠的层数(深度)来丰富特征的层次,以取得更好的效果。

image-20220309111100400

然而,单纯地增加网络的层数会导致**梯度消失(爆炸)**问题,它从根源上导致了函数无法收敛。通过标准初始化(normalized initialization)以及中间标准化层 (intermediate normalization layer),已经可以较好地解决这个问题了,这使得深度为数十层 的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。 但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是, 这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升

增加网络层数
梯度消失或梯度爆炸
引入批标准化等解决
继续增加网络层数
网络退化

例如下面这个例子:

image-20220309111731096

为了应对这个问题,ResNet 引入残差结构,通过对残差结构的运用,ResNet 使得训练数百层的网络成为了可能,从而具有非常 强大的表征能力。

image-20220309111851139

需要注意的是,ResNet块中的“+”是特征图对应元素值相加(矩阵值相加),而Inception块中的“+”是沿深度方向叠加(千层蛋糕层数叠加)

也就是说,每个ResNet块的输入经过两条路径:

  1. 一条经过两次卷积运算
  2. 一条经过恒等映射
  3. 在输出时将二者对应元素值相加作为输出。

image-20220309112150249

上图中的实线和虚线均表示恒等映射

  • 实线表示通道相同,计算方式为 H(x) = F(x) + x
  • 虚线表示通道不同,计算方式为 H(x) = F(x) + Wx,其中 W 为卷积操作,目的是调整 x 的维 度(通道数)

注意:1*1卷积操作可通过步长改变特征图尺寸,通过卷积核个数改特征图深度。

这里说明一下1 * 1的卷积运算是如何降低特征深度:下面以5 * 5的卷积运算为例说明这个问题。

假设网络上一层的输出为 128 * 100 * 100 (C * H * W )

  • (1)通过 32 * 5 * 5(32 个大小 为 5 * 5 的卷积核)的卷积层(步长为 1、全零填充)后,输出为 32 * 100 * 100 ,卷积层的 参数量为 32 * 5 * 5 * 128 = 102400;
  • (2)如果先通过 32 * 1 * 1 的卷积层(输出为 32 * 100 * 100), 再通过 32 * 5 * 5 的卷积层,输出仍为 32 * 100 * 100,但卷积层的参数量变为 32 * 1 * 1 * 128 + 32 * 5 * 5 * 32 = 29696,仅为原参数量的 30 %左右,这就是小卷积核的降维作用。

image-20220309115023983

实现残差结构

为了实现这种残差结构,我们定义一个新的 ResnetBlock 类

image-20220309112459631

class ResnetBlock(Model):
    def __init__(self,filters,strides=1,residual_path=False):
        super(ResnetBlock, self).__init__()
        self.filters=filters
        self.strides=strides
        self.residual_path=residual_path

        self.c1=Conv2D(filters,(3,3),strides=strides,padding='same',use_bias=False)
        self.b1=BatchNormalization()
        self.a1=Activation('relu')

        self.c2=Conv2D(filters,(3,3),strides=strides,padding='same',use_bias=False)
        self.b2 = BatchNormalization()

        # residual_path为True时,对输入进行下采样,即用1x1的卷积核做卷积操作,保证x能和F(x)维度相同,顺利相加
        if residual_path:
            self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same', use_bias=False)
            self.down_b1 = BatchNormalization()

        self.a2 = Activation('relu')

    def call(self,x):
        residual=x # residual等于输入值本身,即residual=x

        # 将输入通过卷积、BN层、激活层,计算F(x)
        x = self.c1(inputs)
        x = self.b1(x)
        x = self.a1(x)

        x = self.c2(x)
        y = self.b2(x)

        if self.residual_path:
            residual = self.down_c1(x)
            residual = self.down_b1(residual)

        out = self.a2(y + residual)  # 最后输出的是两部分的和,即F(x)+x或F(x)+Wx,再过激活函数
        return out

构建出 ResNet 模型

class MyResnet18(Model):

    def __init__(self, block_list, initial_filters=64):  # block_list表示每个block有几个卷积层
        super(MyResnet18, self).__init__()
        self.num_blocks = len(block_list)  # 共有几个block
        self.block_list = block_list
        self.out_filters = initial_filters
        # 先是一个3*3的卷积
        self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')

        self.blocks = tf.keras.models.Sequential()
        # 构建ResNet网络结构
        for block_id in range(len(block_list)):  # 第几个resnet block
            for layer_id in range(block_list[block_id]):  # 第几个卷积层

                if block_id != 0 and layer_id == 0:  # 对除第一个block以外的每个block的输入进行下采样
                    block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
                else:
                    block = ResnetBlock(self.out_filters, residual_path=False)
                self.blocks.add(block)  # 将构建好的block加入resnet
            self.out_filters *= 2  # 下一个block的卷积核数是上一个block的2倍
        self.p1 = tf.keras.layers.GlobalAveragePooling2D()
        self.f1 = tf.keras.layers.Dense(10, activation='softmax', 					            kernel_regularizer=tf.keras.regularizers.l2())

    def call(self, x):
        x = self.c1(x)
        x = self.b1(x)
        x = self.a1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y


model = MyResnet18([2, 2, 2, 2])
  x = self.a1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y


model = MyResnet18([2, 2, 2, 2])
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值