深度学习分类任务(5):TF2.0基于fashion数据集的ResNet神经网络搭建

TF2.0基于fashion数据集的ResNet神经网络搭建

1、ResNet简介

随着网络的加深,出现了训练集准确率下降的现象,我们可以确定这不是由于Overfit过拟合造成的(过拟合的情况训练集应该准确率很高)。传统的卷积网络或者全连接网络在信息传递的时候或多或少会存在信息丢失,损耗等问题,同时还有导致梯度消失或者梯度爆炸,阻碍网络收敛,导致很深的网络无法训练。

ResNet(Residual Neural Network)由微软研究院的Kaiming He等四名华人提出,通过使用ResNet Unit成功训练出了152层的神经网络,并在ILSVRC2015比赛中取得冠军,在top5上的错误率为3.57%,同时参数量比VGGNet低,效果非常突出。ResNet的结构可以极快的加速神经网络的训练,模型的准确率也有比较大的提升。同时ResNet的推广性非常好,甚至可以直接用到InceptionNet网络中。ResNet的主要思想是在网络中增加了直连通道,即Highway Network的思想。此前的网络结构是性能输入做一个非线性变换,而Highway Network则允许保留之前网络层的一定比例的输出。ResNet的思想和Highway Network的思想也非常类似,允许原始输入信息直接传到后面的层中,如下图所示。
  在这里插入图片描述
这样设计后这一层的神经网络可以不用学习整个的输出,而是学习上一个网络输出的残差,因此ResNet又叫做残差网络。
残差学习的结构如图有点类似与电路中的“短路”,所以是一种短路连接(shortcut connection)。增加一个identity mapping(恒等映射),将原始所需要学的函数H(x)转换成F(x)+x,F(x)的优化 会比H(x)简单的多。

Residual block通过shortcut connection实现,通过shortcut将这个block的输入和输出进行一个element-wise的加叠,这个简单的加法并不会给网络增加额外的参数和计算量,同时却可以大大增加模型的训练速度、提高训练效果,并且当模型的层数加深时,这个简单的结构能够很好的解决退化问题。
在这里插入图片描述

上面公式中:

h 表示 shortcut 使用什么形式的变换(Identity map)
F 是 residual function。F= y-h(x)
f 为Residual Units输出处使用的函数(relu)

1.1、ResNet网络结构

输入部分、输出部分和中间卷积部分(四个stage),网络之间的不同主要在于中间卷积部分的block参数和个数存在差异。
在这里插入图片描述
输入部分 Input stem主要包括conv1, bn1, relu, maxpool四个部分。

网络中间卷积部分对比图:
在这里插入图片描述
Resnet18 、 50、ResNet34及ResNet101分别具有两种不同的基本“shortcut connection”结构。
ResNet34使用BasicBlock,
ResNet101使用 Bottleneck作为“shortcut connection”。

“shortcut connection” 只是元素级相加操作

下图左边表示BasicBlock,右图表示Bottleneck。
在这里插入图片描述

downsample 主要用来处理H(x)=F(x)+x中F(x)和xchannel维度不匹配问题,由bootlenect和直线shortcut组成。整个bootlenect+shortcut称为Residual uint。几个Residual uint的叠加称为Residual block。Resnet结构都是由4个Residual block组成的。
在这里插入图片描述

特点

  • 卷积层主要是3×3卷积
  • 不使用dropout,全部使用BN
  • 对于相同的输出特征图大小的层,即同一stage,具有相同数量的3x3滤波器;
  • 当feature map大小降低一半时,feature map的数量增加一倍,这保持了网络层的复杂度。
  • 每个stage通过步长为2的卷积层执行下采样(downsample),而这个下采样只会在每一个stage的第一个卷积完成,有且仅有一次。
  • 网络以全局平均池化层和softmax的1000路全连接层结束。

2、使用tensorflow2.0实现ResNet网络并训练Fashion-mnist数据集

关于Fashion-mnist数据集的简介请参考前一篇文章《深度学习分类任务(1):TF2.0基于fashion数据集的LeNet神经网络搭建

2.1 准备数据集

这部分其实和之前的一样,没有改变过。

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)

fashion = tf.keras.datasets.fashion_mnist
(x_train, y_train), (x_test, y_test) = fashion.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
print("x_train.shape", x_train.shape)
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)  # 给数据增加一个维度,使数据和网络结构匹配
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)
print("x_train.shape", x_train.shape)

2.2 搭建resnet网络

同样,为了渣渣笔记本能正常运行,这里用tf2.0搭建resnet18网络。
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 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

再用残差块组建resnet18网络

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


为了模型简单,block_list取值为4,表示使用四个block,每个block有两层卷积,因此模型为

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

2.3装配网络

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

2.4 设置权重文件保存路径和callback


checkpoint_save_path = "./checkpoint/ResNet18.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)

2.5 开始训练

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

同样为了节省时间,只跑一个epoch,十多分钟过去之后,得到了结果如下图,准确率达到了89%。
在这里插入图片描述

2.6 显示训练集和验证集的acc和loss曲线


acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_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()

只有一个epoch,这里就没法绘制曲线了。

参考文献:
https://www.cnblogs.com/gujiangtaoFuture/articles/12168774.html

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值