经典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)的方式开始收敛。 但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是, 这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升。

#mermaid-svg-nQciERliqblr7KPm {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nQciERliqblr7KPm .error-icon{fill:#552222;}#mermaid-svg-nQciERliqblr7KPm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nQciERliqblr7KPm .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-nQciERliqblr7KPm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nQciERliqblr7KPm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nQciERliqblr7KPm .marker.cross{stroke:#333333;}#mermaid-svg-nQciERliqblr7KPm svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nQciERliqblr7KPm .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster-label text{fill:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster-label span{color:#333;}#mermaid-svg-nQciERliqblr7KPm .label text,#mermaid-svg-nQciERliqblr7KPm span{fill:#333;color:#333;}#mermaid-svg-nQciERliqblr7KPm .node rect,#mermaid-svg-nQciERliqblr7KPm .node circle,#mermaid-svg-nQciERliqblr7KPm .node ellipse,#mermaid-svg-nQciERliqblr7KPm .node polygon,#mermaid-svg-nQciERliqblr7KPm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nQciERliqblr7KPm .node .label{text-align:center;}#mermaid-svg-nQciERliqblr7KPm .node.clickable{cursor:pointer;}#mermaid-svg-nQciERliqblr7KPm .arrowheadPath{fill:#333333;}#mermaid-svg-nQciERliqblr7KPm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nQciERliqblr7KPm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nQciERliqblr7KPm .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-nQciERliqblr7KPm .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-nQciERliqblr7KPm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nQciERliqblr7KPm .cluster text{fill:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster span{color:#333;}#mermaid-svg-nQciERliqblr7KPm p.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nQciERliqblr7KPm :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;} 增加网络层数 梯度消失或梯度爆炸 引入批标准化等解决 继续增加网络层数 网络退化

例如下面的例子:

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

经典CNN的实现 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])

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2022年3月15日
下一篇 2022年3月15日

相关推荐