ResNet网络搭建(tensorflow,keras)

该文详细解析了ResNet网络结构,通过引入残差学习,解决了深层神经网络的退化问题。ResNet块分为实线和虚线两种情况,分别对应特征直接相加和维度调整后再相加。文章还提供了ResNet18模型的TensorFlow实现,并展示了模型的训练和验证损失及准确率曲线。
摘要由CSDN通过智能技术生成

ResNet网络搭建

网络结构及分析:

在这里插入图片描述
上述四种CNN,通过加深网络层数,结果会越来越好,但是增大到一定程度,再增加层数,会使神经网络模型退化,因为后面的特征会丢失前面特征的原本模样
在这里插入图片描述
因此,将前面的特征x直接跳过两个卷积层,与两个卷积层的输出F(x)相加得到H(x)。这种操作可以缓解神经网络模型层数堆叠导致的模型退化,使我们能够增加更多的层数
(注:ResNet中的+是特征图对应元素相加,矩阵值的每个对应元素相加,
InceptionNet中的+是沿深度方向叠加,增加特征图的层数)

ResNet块有两种情况:
一种是下图的实线所示,两层卷积之后没有改变特征图的维度,可以直接H(x)=F(x)+x
另一种是虚线所示,两层卷积之后改变了特征图的维度,需要借助1*1的卷积来调整x的维度,使W(x)与F(x)维度一致
在这里插入图片描述
下图即是ResNet的结构:
在这里插入图片描述
RetNet由一个卷积层+8个RetNet块+所有通道进行平均池化的池化层+Dense层组成
一共18层网络
每个ResNet块有两种情况:虚线和实线
在这里插入图片描述
如上图结构,可将ResNet块封装到类ResnetBlock中:

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语句,如果是虚线,则执行以下if语句,将W(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语句,如果是虚线,则执行以下if语句,将W(x)核F(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

进而可设计ResNet网络结构:

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
        # 第一个卷积层:CBA
        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网络结构
        # 外层循环层数由参数列表的循环个数决定,如model = ResNet18([2, 2, 2, 2]),则循环4次,有4个橙色块,即8个ResNet块
        for block_id in range(len(block_list)):  # 第几个橙色块
            for layer_id in range(block_list[block_id]):  # 第几个ResNet块

                if block_id != 0 and layer_id == 0:  # 除第一个橙色块,以下三个橙色块的第一个ResNet块外,都是虚线,定义residual_path=True
                    block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
                else:  # 第一个橙色块,以下三个橙色块的第一个ResNet块是实线,定义residual_path=False
                    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()
        # Dense层
        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])

整体代码:

以下以cifar10数据集为例进行演示
(cifar10数据集有5万张32*32像素点的彩色图片,用于训练
有1万张32*32像素点的彩色图片,用于测试)

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语句,如果是虚线,则执行以下if语句,将W(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语句,如果是虚线,则执行以下if语句,将W(x)核F(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
        # 第一个卷积层:CBA
        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网络结构
        # 外层循环层数由参数列表的循环个数决定,如model = ResNet18([2, 2, 2, 2]),则循环4次,有4个橙色块,即8个ResNet块
        for block_id in range(len(block_list)):  # 第几个橙色块
            for layer_id in range(block_list[block_id]):  # 第几个ResNet块

                if block_id != 0 and layer_id == 0:  # 除第一个橙色块,以下三个橙色块的第一个ResNet块外,都是虚线,定义residual_path=True
                    block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
                else:  # 第一个橙色块,以下三个橙色块的第一个ResNet块是实线,定义residual_path=False
                    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()
        # Dense层
        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])

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


###############################################    show   ###############################################

# 显示训练集和验证集的acc和loss曲线
plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()

注:本文来自于中国大学mooc中北京大学的人工智能实践:Tensorflow笔记,在此感谢北大的曹健老师

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值