使用ResNet18网络实现对Cifar-100数据集分类
使用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数据集分类相关推荐
- ML之K-means:基于K-means算法利用电影数据集实现对top 100 电影进行文档分类
ML之K-means:基于K-means算法利用电影数据集实现对top 100 电影进行文档分类 目录 输出结果 实现代码 输出结果 先看文档分类后的结果,一共得到五类电影: 实现代码 # -*- c ...
- ML之H-Clusters:基于H-Clusters算法利用电影数据集实现对top 100电影进行文档分类
ML之H-Clusters:基于H-Clusters算法利用电影数据集实现对top 100电影进行文档分类 目录 输出结果 实现代码 输出结果 先看输出结果 实现代码 # -*- coding: ut ...
- 利用胶囊网络实现对CIFAR10分类
利用胶囊网络实现对CIFAR10分类 数据集:CIFAR-10数据集由10个类中的60000个32x32彩色图像组成,每个类有6000个图像.有50000个训练图像和10000个测试图像. 实验:搭建 ...
- ML之DT:利用DT(DTC)实现对iris(鸢尾花)数据集进行分类并可视化DT结构
ML之DT:利用DT(DTC)实现对iris(鸢尾花)数据集进行分类并可视化DT结构 目录 输出结果 实现代码 输出结果 实现代码 #1. iris = load_iris() dir(iris)ir ...
- 第五章:Tensorflow 2.0 利用十三层卷积神经网络实现cifar 100训练(理论+实战)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/LQ_qing/article/deta ...
- ML之nyoka:基于nyoka库利用LGBMClassifier模型实现对iris数据集训练、保存为pmml模型并重新载入pmml模型进而实现推理
ML之nyoka:基于nyoka库利用LGBMClassifier模型实现对iris数据集训练.保存为pmml模型并重新载入pmml模型进而实现推理 目录 基于nyoka库利用LGBMClassifi ...
- CV之FDFA:利用MTCNN的脚本实现对LFW数据集进行FD人脸检测和FA人脸校准
CV之FD&FA:利用MTCNN的脚本实现对LFW数据集进行FD人脸检测和FA人脸校准 目录 运行结果 运行过程 运行(部分)代码 在裁剪好的LFW数据集进行验证 运行结果 运行过程 time ...
- ML之RFDT:利用RF(RFR)、DT(DTR)两种算法实现对boston(波士顿房价)数据集进行训练并预测
ML之RF&DT:利用RF(RFR).DT(DTR)两种算法实现对boston(波士顿房价)数据集进行训练并预测 目录 输出结果 实现代码 输出结果 1.两种算法的预测结果 2.回归树的可视化 ...
- (pytorch-深度学习系列)使用softmax回归实现对Fashion-MNIST数据集进行分类-学习笔记
使用softmax回归实现对Fashion-MNIST数据集进行分类 import torch from torch import nn from torch.nn import init impor ...
最新文章
- C#学习系列之泛型类
- Linux学习第十篇之用户管理命令useradd、passwd、who、w
- 解决 吃货阶段02 0928
- 大数据之-Hadoop环境搭建_安装hadoop---大数据之hadoop工作笔记0018
- mysql sql执行慢_Mysql执行查询语句慢的解决方式
- 正则表达式匹配第一个单个字符
- 加上华为mate30系列,9月还有5场新机发布会,你更期待哪场
- 色彩空间(一):色彩空间基础
- 【Android开发经验】Android相关问题的好文章整理——温故而知新,可以为师矣
- 中国文学通史之各个阶段介绍
- 【学术】英文写作中值得参考的语法、句式(二)
- 金色传说:SAP-ABAP-销售订单增强:记录销售订单修改信息和修改原因
- 全球6G产业及政策进展研究
- python 支持erp自动化操作
- 十二月,我们一起在云台山风景区赏雪
- win10定时关机c语言,win10定时关机在哪?win10设置定时关机的三种方法
- Java 内部类之匿名内部类
- 前端开发面试问题及答案
- 罚函数(内点罚函数和外点罚函数)
- Docker部署Oracle11g
热门文章
- android开发项目app实例!我在华为做Android外包的真实经历!深度好文
- 想写一本书,而这是序言
- 微型投影仪第一篇——万物伊始
- 2020年小红书校招数据分析笔试题详解
- 360浏览器启动时不打开主页的原因
- gitlab markdown 修改 字体颜色
- 逻辑斯谛回归(logistics regression,LR)
- tgp饥荒服务器文件夹,《饥荒》TGP版服务器搭建及设置图文教程 TGP版怎么建服务器...
- AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
- 学习Spring必学的Java基础知识----反射