tensorflow ResNet

ResNet是何恺明团队提出的深度残差网络,通过残差结构避免了梯度消失问题,使得训练数百层的网络成为可能。网络不再直接拟合底层映射,而是拟合残差映射,简化了优化过程。ResNet与InceptionNet的相加方式有别,它是在特征图层面进行元素相加。文章还介绍了ResNet在网络深度增加时如何保持性能,以及在CIFAR10数据集上的应用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        借鉴点:层间残差跳连,引入前方信息,减少梯度消失,使神经网络层数变身成为可能。

        ResNet 即深度残差网络,由何恺明及其团队提出,是深度学习领域又一具有开创性的 工作,通过对残差结构的运用,ResNet 使得训练数百层的网络成为了可能,从而具有非常 强大的表征能力,其网络结构如图 下所示。

        图 上 ResNet18 网络结构图 ResNet 的核心是残差结构,如图下所示。在残差结构中,ResNet 不再让下一层直接 拟合我们想得到的底层映射,而是令其对一种残差映射进行拟合。若期望得到的底层映射为 H(x),我们令堆叠的非线性层拟合另一个映射 F(x) := H(x) – x,则原有映射变为 F(x) + x。 对这种新的残差映射进行优化时,要比优化原有的非相关映射更为容易。不妨考虑极限情况, 如果一个恒等映射是最优的,那么将残差向零逼近显然会比利用大量非线性层直接进行拟合 更容易。

        值得一提的是,这里的相加与 InceptionNet 中的相加是有本质区别的,Inception 中的相 加是沿深度方向叠加,像“千层蛋糕”一样,对层数进行叠加;ResNet 中的相加则是特征图 对应元素的数值相加,类似于 python 语法中基本的矩阵相加。

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

         但如果只是简单地堆叠更多层数,就会导致梯度消失(爆炸)问题,它从根源上导致了 函数无法收敛。然而,通过标准初始化(normalized initialization)以及中间标准化层 (intermediate normalization layer),已经可以较好地解决这个问题了,这使得深度为数十层 的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。

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

                                cifar10 数据集训练集错误率(左)及测试集错误率(右)

        ResNet 解决的正是这个问题,其核心思路为:对一个准确率达到饱和的浅层网络,在 它后面加几个恒等映射层(即 y = x,输出等于输入),增加网络深度的同时不增加误差。 这使得神经网络的层数可以超越之前的约束,提高准确率。图下展示了 ResNet 中残差结 构的具体用法。         上图中的实线和虚线均表示恒等映射,实线表示通道相同,计算方式为 H(x) = F(x) + x; 虚线表示通道不同,计算方式为 H(x) = F(x) + Wx,其中 W 为卷积操作,目的是调整 x 的维 度(通道数)。

代码实现:

import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model

np.set_printoptions(threshold=np.inf)

cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0


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=1, 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, inputs):
        residual = inputs  # 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(inputs)
            residual = self.down_b1(residual)

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


class ResNet18(Model):

    def __init__(self, block_list, initial_filters=64):  # block_list表示每个block有几个卷积层
        super(ResNet18, self).__init__()
        self.num_blocks = len(block_list)  # 共有几个block
        self.block_list = block_list
        self.out_filters = initial_filters
        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, inputs):
        x = self.c1(inputs)
        x = self.b1(x)
        x = self.a1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y


model = ResNet18([2, 2, 2, 2])

optimizer = tf.keras.optimizers.Adam()
model.compile(optimizer=optimizer ,
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])


history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1)
model.summary()

         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XMM-struggle

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

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

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

打赏作者

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

抵扣说明:

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

余额充值