使用ResNet18网络实现对Cifar-100数据集分类

简介

本次作业旨在利用ResNet18实现对于Cifar-100数据集进行图像识别按照精细类进行分类。
Cifar-100数据集由20个粗类和100个细类组成,每个粗类包含5个细类,每个细类有500张训练图片和100张测试图片。
残差神经网络(ResNet)是由微软研究院的何恺明、张祥雨、任少卿、孙剑等人提出的。ResNet 在2015 年的ILSVRC(ImageNet Large Scale Visual Recognition Challenge)中取得了冠军。

网络模型


ResNet18由1个卷积层,8个残差块和1个全连接层组成,其中在全连接层前做的是平均池化,而不是最大值池化。

导入:

import tensorflow.keras as keras
import tensorflow as tf
from tensorflow.keras import layers

下面是一个基本残差结构的代码:

class BasicBlock(layers.Layer):def __init__(self, filter_num, stride=1):super(BasicBlock, self).__init__()self.left = keras.Sequential([layers.Conv2D(filters=filter_num, kernel_size=(3, 3), strides=stride, padding='same'),layers.BatchNormalization(),layers.Activation('relu'),layers.Conv2D(filters=filter_num, kernel_size=(3, 3), strides=1, padding='same'),layers.BatchNormalization()])if stride != 1:self.downSample = keras.Sequential([layers.Conv2D(filters=filter_num, kernel_size=(1, 1), strides=stride),layers.BatchNormalization()])else:self.downSample = lambda x: xdef call(self, inputs, training=None):identity = self.downSample(inputs)out = self.left(inputs)output = layers.add([out, identity])output = tf.nn.relu(output)return output

残差边的作用在于可以通过一条残差边将这一部分直接“跳过”。实现这一目的很简单,将这些层的权重参数设置为0就行了。这样一来,不管网络中有多少层,效果好的层我们保留,效果不好的我们可以跳过。总之,添加的新网络层至少不会使效果比原来差,就可以较为稳定地通过加深层数来提高模型的效果了。

接下来使用make_res_block组合残差块:

def make_res_block(filter_num, blocks, stride=1):res_block = keras.Sequential()res_block.add(BasicBlock(filter_num, stride))for i in range(1, blocks):res_block.add(BasicBlock(filter_num, stride=1))return res_block

最后由多个残差块构成一个残差网络的模型,和传统的ResNet18不同的是,将第一层卷积层的size改为了3*3的结构以适应32*32*3的图片大小:

class ResNet(keras.Model):def __init__(self, layer_dims, num_class=100):super(ResNet, self).__init__()self.pre = keras.Sequential([layers.Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1)),layers.BatchNormalization(),layers.Activation('relu'),layers.MaxPooling2D(pool_size=(2, 2), strides=(1, 1), padding='same')])self.layer1 = make_res_block(64, layer_dims[0])self.layer2 = make_res_block(128, layer_dims[1], stride=2)self.layer3 = make_res_block(256, layer_dims[2], stride=2)self.layer4 = make_res_block(512, layer_dims[3], stride=2)self.average_pooling = layers.GlobalAveragePooling2D()self.fc = layers.Dense(num_class)def call(self, inputs, training=None):x = self.pre(inputs, training=training)x = self.layer1(x, training=training)x = self.layer2(x, training=training)x = self.layer3(x, training=training)x = self.layer4(x, training=training)x = self.average_pooling(x)x = self.fc(x)return x

数据处理

导入:

import tensorflow as tf

我们选择从keras库中导入Cifar-100数据集的数据并将图片的数据转化到0-1之间,从训练集中切分出10%作为验证集:

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.cifar100.load_data()
train_images, test_images = train_images / 255.0, test_images / 255.0
train_labels = train_labels.astype(np.float32)
test_labels = test_labels.astype(np.float32)test_dataset = tf.data.Dataset.from_tensor_slices((test_images, test_labels)).batch(50)validation_images = train_images[45000:]
train_images = train_images[0:45000]
validation_labels = train_labels[45000:]
train_labels = train_labels[0:45000]

然后使用ImageDataGenerator进行数据增广,数据增广是深度学习中常用的优化技巧之一,通过对训练数据进行一系列变换增加样本数量及样本多样性,使得模型具有更强的泛化能力。
代码如下:

datagen = ImageDataGenerator(featurewise_center=False,  # 布尔值。将输入数据的均值设置为 0,逐特征进行。samplewise_center=False,  # 布尔值。将每个样本的均值设置为 0。featurewise_std_normalization=False,  # 布尔值。将输入除以数据标准差,逐特征进行。samplewise_std_normalization=False,  # 布尔值。将每个输入除以其标准差。zca_whitening=False,  # 布尔值。是否应用 ZCA 白化。# zca_epsilon  ZCA 白化的 epsilon 值,默认为 1e-6。# rotation_range=30,  # 整数。随机旋转的度数范围 (degrees, 0 to 180)width_shift_range=0.2,  # randomly shift images horizontally (fraction of total width)height_shift_range=0.2,  # randomly shift images vertically (fraction of total height)horizontal_flip=True,  # 布尔值。随机水平翻转。vertical_flip=False,  # 布尔值。随机垂直翻转fill_mode='nearest'
)datagen.fit(train_images)

模型训练

构建优化器,Adam优化器实现简单,计算高效,对内存需求少:

optimizer=tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.004)

自定义损失函数,由一个keras的SparseCategoricalCrossentropy损失函数和损失函数l2正则化构成,l2正则化通过限制权重的大小,防止模型拟合随机噪音,用于限制过拟合问题:

def spareCE(y_true, y_pred):sce = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)return tf.reduce_mean(sce(y_true, y_pred))def l2_loss(my_model, weights=1e-4):variable_list = []for v in my_model.trainable_variables:if 'kernel' or 'bias' in v.name:variable_list.append(tf.nn.l2_loss(v))return tf.add_n(variable_list) * weightsdef myLoss(y_true, y_pred):sce = spareCE(y_true, y_pred)l2 = l2_loss(my_model=model)loss = sce + l2return loss

模型初始化:

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

model.compile()方法用于在配置训练方法时,告知训练时用的优化器、损失函数和准确率评测标准:

    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001, decay=0.004),loss=myLoss,metrics=['sparse_categorical_accuracy'])

model.fit()方法用于执行训练过程,训练数据由ImageDataGenerator生成,并记录日志。其中学习率由keras_tuner方法调试得出:

    history = model.fit(datagen.flow(train_images, train_labels, batch_size=64), steps_per_epoch=len(train_images) / 64,# x=train_images, y=train_labels, batch_size=64,epochs=150, verbose=2,validation_data=(validation_images, validation_labels))

保存模型:

model.save('saved_model/my_model')

模型测试

my_model = tf.keras.models.load_model('saved_model/my_model', custom_objects={'myLoss': myLoss})correct_num = 0
test_dataset = tf.data.Dataset.from_tensor_slices((test_images, test_labels)).batch(50)
for x, y in test_dataset:y_pred = my_model(x, training=False)y_pred = tf.cast(tf.argmax(y_pred, 1), dtype=tf.int32)y_true = tf.cast(tf.squeeze(y, -1), dtype=tf.int32)equality = tf.equal(y_pred, y_true)equality = tf.cast(equality, dtype=tf.float32)correct_num += tf.reduce_sum(equality)print(float(correct_num))
print('acc=', float(correct_num) / 10000.0)

得出测试集的正确率为62.21%:

acc= 0.6221

可视化

通过history日志调出训练时训练集和测试集的正确率和损失值并用matplotlib绘制图像:

from matplotlib import pyplot as pltfig1, ax_acc = plt.subplots()
plt.plot(history.history['sparse_categorical_accuracy'], 'r', label='acc')
plt.plot(history.history['val_sparse_categorical_accuracy'], 'b', label='val_acc')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Model - Accuracy')
plt.legend(loc='lower right')
plt.show()fig2, ax_loss = plt.subplots()
plt.plot(history.history['loss'], 'r', label='loss')
plt.plot(history.history['val_loss'], 'b', label='val_loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Model- Loss')
plt.legend(loc='upper right')
plt.show()

训练过程中训练集正确率与验证集正确率变化:

训练过程中训练集损失值与验证集损失值变化:

使用opencv绘制预测训练集时的预测结果可视化(图片很大,仅截取部分):

绘图代码如下:

# 精细类别的序号与名称 序号:名称
fineLabelNameDict = {}
# 精细类别对应的粗糙类别 精细序号:粗糙序号-粗糙名称
fineLableToCoraseLabelDict = {}def dealData(meta, train):for fineLabel, coarseLabel in zip(train[b'fine_labels'], train[b'coarse_labels']):if fineLabel not in fineLabelNameDict.keys():fineLabelNameDict[fineLabel] = meta[b'fine_label_names'][fineLabel].decode('utf-8')if fineLabel not in fineLableToCoraseLabelDict.keys():fineLableToCoraseLabelDict[fineLabel] = str(coarseLabel) + "-" + meta[b'coarse_label_names'][coarseLabel].decode('utf-8')meta = unpickle('cifar-100-python/meta')
train = unpickle('cifar-100-python/test')
dealData(meta, train)img = np.zeros([3300, 6400, 3], np.uint8)
font = cv.FONT_HERSHEY_SIMPLEX
for i in range(100):category = fineLabelNameDict[i]cv.putText(img, text=category, org=(10, i * 32 + 50), fontFace=font, fontScale=1, color=(255, 255, 255),lineType=cv.LINE_AA)position = np.zeros([100], dtype=int)
for x, y in test_dataset:y_pred = my_model(x, training=False)y_pred = tf.cast(tf.argmax(y_pred, 1), dtype=tf.int32)for image, img_category in zip(x, y_pred):print(img_category, '__', position[img_category])x0 = int(img_category) * 32 + 18y0 = position[img_category] * 32 + 250# image = image * 255.0image = image[:, :, ::-1]  # RGB TO BGRimg[x0:x0 + image.shape[0], y0:y0 + image.shape[1], :] = image * 255.0position[img_category] += 1# cv.imshow('img', img)
cv.imwrite('test_visualisation.png', img=img)

改进过程

5月29日

第一版ResNet18。仅做图像数除以255将图像数据转化到0-1的处理,设定学习率为0.1,无学习率decay,使用SparseCategoricalCrossentropy作为损失函数。

最终结果:在50个epoch内,训练集损失值稳定下降,正确率稳定上升至约0.8,损失值从约4.5下降至约0.5。但是验证集的损失值从约4.3下降至约3.8再重新上升至10.0,正确率仅仅达到约0.20且难以上升。

5月30日

第一版VGG16,尝试使用VGG16作为网络模型,数据处理方式和学习率和损失函数不变。

最终结果,在150个epoch内,训练集损失值稳定下降,正确率稳定上升至约0.7。但是验证集的表现很差,损失值从约4.9一路上升到约20,正确率卡在0.11难以上升。

5月30日

第二版ResNet18。在第一版ResNet18基础上使用ImageDataGenerator来进行数据增广。epoch改为200

最终结果:在200个epoch内,训练集正确率到达约0.45,损失值从约4.7降低至约1.88。验证集正确率达到约0.37

6月1日

第三版ResNet18。在第二版ResNet18的基础上调整了ImageDataGenerator参数并使用了根据,将学习率改为0.001。epoch改为100

最终结果:在100个epoch内,训练集正确率上升至约0.99,损失值降至0.0151,验证集的正确率达到约0.49但难以继续上升且持续震荡,验证集损失率先从约4.6降至约2.5再升至4.5并不断震荡。

6月1日

第四版ResNet18。在第三版ResNet18的基础上重新调整了ImageDataGenerator参数,学习率改为0.1。epoch改为50

最终结果:在50个epoch内,训练集正确率在10个epoch左右快速上升至约0.9,损失值降至0.3,在最后训练集正确率达到0.9999,损失值达到4.7983e-04。验证集的正确率达到约0.5震荡幅度小但卡住难以继续上升,验证集损失从约4.6降至约2.6再升回约3.6。

6月3日

第1版ResNet34。在第四版ResNet18基础上仅仅将ResNet18改为ResNet34结构,轻微重新调整ImageDataGenerator参数。进行100个epoch。

最终结果:由于ResNet34的结构较大,训练花费时间很长。在100个epoch内,训练集正确率上升至约0.98,损失值从约4.0下降至约0.05。验证集正确率上升至0.65但是在0.56和0.65之间剧烈震荡,震荡程度非常大,损失值从约4.1下降至最低约1.8再到最后在约2.5和约4.0之间剧烈震荡。

6月4日

第5版ResNet18,在第四版ResNet18的使用自定义损失函数,在原有SparseCategoricalCrossentropy损失函数的基础上,增加l2正则化来降低过拟合问题。调整了Adam优化器的decay参数。根据keras_tuner调参,根据调参结果将学习率改为0.01。修改了ImageDataGenerator参数。进行150个epoch。

最终结果:训练集正确率上升到约0.92,损失值从约4.1降至约0.74。验证集的正确率稳步上升至约0.62,损失值从约3.6下降至约2.0且从上方可视化那一节的图像上来看震荡非常小,几乎没有剧烈震荡。

使用ResNet18网络实现对Cifar-100数据集分类相关推荐

  1. ML之K-means:基于K-means算法利用电影数据集实现对top 100 电影进行文档分类

    ML之K-means:基于K-means算法利用电影数据集实现对top 100 电影进行文档分类 目录 输出结果 实现代码 输出结果 先看文档分类后的结果,一共得到五类电影: 实现代码 # -*- c ...

  2. ML之H-Clusters:基于H-Clusters算法利用电影数据集实现对top 100电影进行文档分类

    ML之H-Clusters:基于H-Clusters算法利用电影数据集实现对top 100电影进行文档分类 目录 输出结果 实现代码 输出结果 先看输出结果 实现代码 # -*- coding: ut ...

  3. 利用胶囊网络实现对CIFAR10分类

    利用胶囊网络实现对CIFAR10分类 数据集:CIFAR-10数据集由10个类中的60000个32x32彩色图像组成,每个类有6000个图像.有50000个训练图像和10000个测试图像. 实验:搭建 ...

  4. ML之DT:利用DT(DTC)实现对iris(鸢尾花)数据集进行分类并可视化DT结构

    ML之DT:利用DT(DTC)实现对iris(鸢尾花)数据集进行分类并可视化DT结构 目录 输出结果 实现代码 输出结果 实现代码 #1. iris = load_iris() dir(iris)ir ...

  5. 第五章:Tensorflow 2.0 利用十三层卷积神经网络实现cifar 100训练(理论+实战)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/LQ_qing/article/deta ...

  6. ML之nyoka:基于nyoka库利用LGBMClassifier模型实现对iris数据集训练、保存为pmml模型并重新载入pmml模型进而实现推理

    ML之nyoka:基于nyoka库利用LGBMClassifier模型实现对iris数据集训练.保存为pmml模型并重新载入pmml模型进而实现推理 目录 基于nyoka库利用LGBMClassifi ...

  7. CV之FDFA:利用MTCNN的脚本实现对LFW数据集进行FD人脸检测和FA人脸校准

    CV之FD&FA:利用MTCNN的脚本实现对LFW数据集进行FD人脸检测和FA人脸校准 目录 运行结果 运行过程 运行(部分)代码 在裁剪好的LFW数据集进行验证 运行结果 运行过程 time ...

  8. ML之RFDT:利用RF(RFR)、DT(DTR)两种算法实现对boston(波士顿房价)数据集进行训练并预测

    ML之RF&DT:利用RF(RFR).DT(DTR)两种算法实现对boston(波士顿房价)数据集进行训练并预测 目录 输出结果 实现代码 输出结果 1.两种算法的预测结果 2.回归树的可视化 ...

  9. (pytorch-深度学习系列)使用softmax回归实现对Fashion-MNIST数据集进行分类-学习笔记

    使用softmax回归实现对Fashion-MNIST数据集进行分类 import torch from torch import nn from torch.nn import init impor ...

最新文章

  1. C#学习系列之泛型类
  2. Linux学习第十篇之用户管理命令useradd、passwd、who、w
  3. 解决 吃货阶段02 0928
  4. 大数据之-Hadoop环境搭建_安装hadoop---大数据之hadoop工作笔记0018
  5. mysql sql执行慢_Mysql执行查询语句慢的解决方式
  6. 正则表达式匹配第一个单个字符
  7. 加上华为mate30系列,9月还有5场新机发布会,你更期待哪场
  8. 色彩空间(一):色彩空间基础
  9. 【Android开发经验】Android相关问题的好文章整理——温故而知新,可以为师矣
  10. 中国文学通史之各个阶段介绍
  11. 【学术】英文写作中值得参考的语法、句式(二)
  12. 金色传说:SAP-ABAP-销售订单增强:记录销售订单修改信息和修改原因
  13. 全球6G产业及政策进展研究
  14. python 支持erp自动化操作
  15. 十二月,我们一起在云台山风景区赏雪
  16. win10定时关机c语言,win10定时关机在哪?win10设置定时关机的三种方法
  17. Java 内部类之匿名内部类
  18. 前端开发面试问题及答案
  19. 罚函数(内点罚函数和外点罚函数)
  20. Docker部署Oracle11g

热门文章

  1. android开发项目app实例!我在华为做Android外包的真实经历!深度好文
  2. 想写一本书,而这是序言
  3. 微型投影仪第一篇——万物伊始
  4. 2020年小红书校招数据分析笔试题详解
  5. 360浏览器启动时不打开主页的原因
  6. gitlab markdown 修改 字体颜色
  7. 逻辑斯谛回归(logistics regression,LR)
  8. tgp饥荒服务器文件夹,《饥荒》TGP版服务器搭建及设置图文教程 TGP版怎么建服务器...
  9. AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
  10. 学习Spring必学的Java基础知识----反射