神经网络与深度学习(六)卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类
使用预训练resnet18实现CIFAR-10分类
目录
1.阅读《Deep Residual Learning for Image Recognition》,了解5种深度的ResNet(18,34,50,101和152),并简单谈谈自己的看法。
2.用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet
5.5 实践:基于ResNet18网络完成图像分类任务
在本实践中,我们实践一个更通用的图像分类任务。
图像分类(Image Classification)是计算机视觉中的一个基础任务,将图像的语义将不同图像划分到不同类别。很多任务也可以转换为图像分类任务。比如人脸检测就是判断一个区域内是否有人脸,可以看作一个二分类的图像分类任务。
- 数据集:CIFAR-10数据集,
- 网络:ResNet18模型,
- 损失函数:交叉熵损失,
- 优化器:Adam优化器,Adam优化器的介绍参考NNDL第7.2.4.3节。
- 评价指标:准确率。
5.5.1 数据处理
5.5.1.1 数据集介绍
CIFAR-10数据集包含了10种不同的类别、共60,000张图像,其中每个类别的图像都是6000张,图像大小均为32×3232×32像素。CIFAR-10数据集的示例如下图所示。
5.5.1.2 数据读取
在本实验中,将原始训练集拆分成了train_set、dev_set两个部分,分别包括40 000条和10 000条样本。将data_batch_1到data_batch_4作为训练集,data_batch_5作为验证集,test_batch作为测试集。
最终的数据集构成为:
- 训练集:40 000条样本。
- 验证集:10 000条样本。
- 测试集:10 000条样本。
读取一个batch数据的代码如下所示:
import os
import pickle
import numpy as np
def load_cifar10_batch(folder_path, batch_id=1, mode='train'):
if mode == 'test':
file_path = os.path.join(folder_path, 'test_batch')
else:
file_path = os.path.join(folder_path, 'data_batch_'+str(batch_id))
# 加载数据集文件
with open(file_path, 'rb') as batch_file:
batch = pickle.load(batch_file, encoding = 'latin1')
imgs = batch['data'].reshape((len(batch['data']),3,32,32)) / 255.
labels = batch['labels']
return np.array(imgs, dtype='float32'), np.array(labels)
imgs_batch, labels_batch = load_cifar10_batch(folder_path='C:\\Users\\PycharmProjects\\pythonProject\\cifar-10-batches-py', batch_id=1, mode='train')
查看数据的维度:
# 打印一下每个batch中X和y的维度
print("batch of imgs shape: ",imgs_batch.shape, "batch of labels shape: ", labels_batch.shape)
运行结果:
可视化观察其中的一张样本图像和对应的标签,代码如下所示:
import matplotlib.pyplot as plt
image, label = imgs_batch[1], labels_batch[1]
print("The label in the picture is {}".format(label))
plt.figure(figsize=(2, 2))
plt.imshow(image.transpose(1, 2, 0))
plt.savefig('cnn-car.pdf')
运行结果:
5.5.1.3 构造Dataset类
构造一个CIFAR10Dataset类,其将继承自torch.io.DataSet
类,可以逐个数据进行处理。代码实现如下:
import torch
from torch.utils.data import Dataset,DataLoader
from torchvision.transforms import transforms
class CIFAR10Dataset(Dataset):
def __init__(self, folder_path='C:/Users/大楠爱吃屁/Desktop/cifar-10-python.tar/cifar-10-python/cifar-10-batches-py', mode='train'):
if mode == 'train':
self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, batch_id=1, mode='train')
for i in range(2, 5):
imgs_batch, labels_batch = load_cifar10_batch(folder_path=folder_path, batch_id=i, mode='train')
self.imgs, self.labels = np.concatenate([self.imgs, imgs_batch]), np.concatenate(
[self.labels, labels_batch])
elif mode == 'dev':
self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, batch_id=5, mode='dev')
elif mode == 'test':
self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, mode='test')
self.transform = transforms.Compose(
[transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
def __getitem__(self, idx):
img, label = self.imgs[idx], self.labels[idx]
img = img.transpose(1, 2, 0)
img = self.transform(img)
return img, label
def __len__(self):
return len(self.imgs)
torch.manual_seed(100)
train_dataset = CIFAR10Dataset(folder_path='C:\\Users\\崔嘉诚\\PycharmProjects\\pythonProject\\cifar-10-batches-py', mode='train')
dev_dataset = CIFAR10Dataset(folder_path='C:\\Users\\崔嘉诚\\PycharmProjects\\pythonProject\\cifar-10-batches-py', mode='dev')
test_dataset = CIFAR10Dataset(folder_path='C:\\Users\\崔嘉诚\\PycharmProjects\\pythonProject\\cifar-10-batches-py', mode='test')
5.5.2 模型构建
使用torchvision.models
API中的resnet18进行图像分类实验。
from torchvision.models import resnet18
resnet18_model = resnet18()
1.什么是“预训练模型”?什么是“迁移学习”?
预训练模型
预训练模型是深度学习架构,已经过训练以执行大量数据上的特定任务(例如,识别图片中的分类问题)。这种训练不容易执行,并且通常需要大量资源,超出许多可用于深度学习模型的人可用的资源,我就没有大批次GPU。在谈论预训练模型时,通常指的是在Imagenet上训练的CNN(用于视觉相关任务的架构)。ImageNet数据集包含超过1400万个图像,其中120万个图像分为1000个类别(大约100万个图像含边界框和注释)。
迁移学习
迁移学习是一种机器学习技术,顾名思义就是指将知识从一个领域迁移到另一个领域的能力。
我们知道,神经网络需要用数据来训练,它从数据中获得信息,进而把它们转换成相应的权重。这些权重能够被提取出来,迁移到其他的神经网络中,我们"迁移"了这些学来的特征,就不需要从零开始训练一个神经网络了 。
2.比较“使用预训练模型”和“不使用预训练模型”的效果。
resnet = models.resnet18(pretrained=True)
resnet = models.resnet18(pretrained=False)
pytorch学习笔记之加载预训练模型_AI算法札记的博客-CSDN博客_pytorch加载预训练模型
【深度学习】使用预训练模型_DrCrypto的博客-CSDN博客_使用预训练模型的最后一层输出层进行下游任务,在训练时会更新所有层的参数吗?
5.5.3 模型训练
复用RunnerV3类,实例化RunnerV3类,并传入训练配置。
使用训练集和验证集进行模型训练,共训练30个epoch。
在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:
import torch.nn.functional as F
import torch.optim as opt
from nndl import RunnerV3, Accuracy
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
lr = 0.001
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = DataLoader(dev_dataset, batch_size=batch_size)
test_loader = DataLoader(test_dataset, batch_size=batch_size)
model = resnet18_model
model.to(device)
optimizer = opt.SGD(model.parameters(), lr=lr, momentum=0.9)
loss_fn = F.cross_entropy
metric = Accuracy()
runner = RunnerV3(model, optimizer, loss_fn, metric)
log_steps = 3000
eval_steps = 3000
runner.train(train_loader, dev_loader, num_epochs=30, log_steps=log_steps, eval_steps=eval_steps, save_path="best_model.pdparams")
运行结果:
cpu运行时间——非常非常久
cpu
[Train] epoch: 0/30, step: 0/18750, loss: 6.97701
[Train] epoch: 4/30, step: 3000/18750, loss: 0.85450
[Evaluate] dev score: 0.60330, dev loss: 1.16656
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.60330
[Train] epoch: 9/30, step: 6000/18750, loss: 0.29281
[Evaluate] dev score: 0.62940, dev loss: 1.44578
[Evaluate] best accuracy performence has been updated: 0.60330 --> 0.62940
[Train] epoch: 14/30, step: 9000/18750, loss: 0.17818
[Evaluate] dev score: 0.63910, dev loss: 1.70278
[Evaluate] best accuracy performence has been updated: 0.62940 --> 0.63910
[Train] epoch: 19/30, step: 12000/18750, loss: 0.02657
[Evaluate] dev score: 0.64540, dev loss: 1.80198
[Evaluate] best accuracy performence has been updated: 0.63910 --> 0.64540
[Train] epoch: 24/30, step: 15000/18750, loss: 0.04895
[Evaluate] dev score: 0.63760, dev loss: 2.00975
[Train] epoch: 28/30, step: 18000/18750, loss: 0.02457
[Evaluate] dev score: 0.64760, dev loss: 2.08300
[Evaluate] best accuracy performence has been updated: 0.64540 --> 0.64760
[Evaluate] dev score: 0.63870, dev loss: 2.08529
[Train] Training done!
可视化观察训练集与验证集的准确率及损失变化情况。
def plot(runner, fig_name):
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
train_items = runner.train_step_losses[::30]
train_steps = [x[0] for x in train_items]
train_losses = [x[1] for x in train_items]
plt.plot(train_steps, train_losses, color='#8E004D', label="Train loss")
if runner.dev_losses[0][0] != -1:
dev_steps = [x[0] for x in runner.dev_losses]
dev_losses = [x[1] for x in runner.dev_losses]
plt.plot(dev_steps, dev_losses, color='#E20079', linestyle='--', label="Dev loss")
# 绘制坐标轴和图例
plt.ylabel("loss", fontsize='x-large')
plt.xlabel("step", fontsize='x-large')
plt.legend(loc='upper right', fontsize='x-large')
plt.subplot(1, 2, 2)
# 绘制评价准确率变化曲线
if runner.dev_losses[0][0] != -1:
plt.plot(dev_steps, runner.dev_scores,
color='#E20079', linestyle="--", label="Dev accuracy")
else:
plt.plot(list(range(len(runner.dev_scores))), runner.dev_scores,
color='#E20079', linestyle="--", label="Dev accuracy")
# 绘制坐标轴和图例
plt.ylabel("score", fontsize='x-large')
plt.xlabel("step", fontsize='x-large')
plt.legend(loc='lower right', fontsize='x-large')
plt.savefig(fig_name)
plt.show()
plot(runner, fig_name='cnn-loss4.pdf')
运行结果:
在本实验中,使用了第7章中介绍的Adam优化器进行网络优化,如果使用SGD优化器,会造成过拟合的现象,在验证集上无法得到很好的收敛效果。可以尝试使用第7章中其他优化策略调整训练配置,达到更高的模型精度。
5.5.4 模型评价
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下:
# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(iter(test_loader))
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))
运行结果:
[Test] accuracy/loss: 0.7270/1.8212
5.5.5 模型预测
同样地,也可以使用保存好的模型,对测试集中的数据进行模型预测,观察模型效果,具体代码实现如下:
#获取测试集中的一个batch的数据
X, label = next(iter(test_loader))
X = X.cpu()
logits = runner.predict(X)
#多分类,使用softmax计算预测概率
pred = F.softmax(logits)
#获取概率最大的类别
pred_class = torch.argmax(pred[2]).numpy()
print(label[2].numpy())
label = label[2].numpy()
#输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label, pred_class))
#可视化图片
plt.figure(figsize=(2, 2))
imgs, labels = load_cifar10_batch(folder_path='C:\\Users\\崔嘉诚\\PycharmProjects\\pythonProject\\cifar-10-batches-py', mode='test')
plt.imshow(imgs[2].transpose(1,2,0))
plt.savefig('cnn-test-vis.pdf')
运行结果:
The true category is 8 and the predicted category is 8
思考题
1.阅读《Deep Residual Learning for Image Recognition》,了解5种深度的ResNet(18,34,50,101和152),并简单谈谈自己的看法。
本文章提出了残差学习网络ResNet,很好地解决了在线性的卷积神经网络中仅仅增加网络的深度,会出现的网络退化问题,使得网络的学习能力能够随着网络深度的增加而增加。其大幅度提高了深度卷积网络提取特征的能力,并使得计算机的在视觉方面的识别能力达到并超过了人类的水平。
本文通过引入残差学习(residual learning)来解决退化问题,即上方学习的为 ,这也正是要学习的残差,希望通过H(x)=F(x)⊕x 拟合x,这里引入了x,把恒等映射作为网络H(x)的一部分。而原本是H(x)拟合x。
深度学习经典论文分析(六)-Deep Residual Learning for Image Recognition - 知乎 (zhihu.com)
2.用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet
LeNet
手写体数字识别模型,是一个广为人知的商用的卷积神经网络, 当年美国大多数银行用它来识别支票上面的手写数字。Lenet-5 原始结构如下图所示,包括:卷积层,降采样,卷积层,降采样,卷积层(实现全连接),全连接层,高斯连接层(进行分类)。
LeNet特点:
(1)每个卷积层包含三个部分:卷积、池化和非线性激活函数
(2)使用卷积提取空间特征
(3)降采样(Subsample)的平均池化层(Average Pooling)
(4)双曲正切(Tanh)或S型(Sigmoid)的激活函数
MLP作为最后的分类器
(5)层与层之间的稀疏连接减少计算复杂度
AlexNet
AlexNet,它本质上就是扩展 LeNet 的深度,并应用一些 ReLU、Dropout 等技巧。AlexNet 有 5 个卷积层和 3 个最大池化层,它可分为上下两个完全相同的分支,这两个分支在第三个卷积层和全连接层上可以相互交换信息。与 Inception 同年提出的优秀网络还有 VGG-Net,它相比于 AlexNet 有更小的卷积核和更深的层级。
AlexNet相对于LeNet的新特点:
(1)成功使用ReLU作为CNN的激活函数,并验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题,此外,加快了训练速度,因为训练网络使用梯度下降法,非饱和的非线性函数训练速度快于饱和的非线性函数。。虽然ReLU激活函数在很久之前就被提出了,但是直到AlexNet的出现才将其发扬光大。
(2)训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合。Dropout虽有单独的论文论述,但是AlexNet将其实用化,通过实践证实了它的效果。在AlexNet中主要是最后几个全连接层使用了Dropout。
(3)在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。
(4)提出了LRN层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。
(5)使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。AlexNet使用了两块GTX?580?GPU进行训练,单个GTX?580只有3GB显存,这限制了可训练的网络的最大规模。因此作者将AlexNet分布在两个GPU上,在每个GPU的显存中储存一半的神经元的参数。
(6)数据增强,随机地从256*256的原始图像中截取224*224大小的区域(以及水平翻转的镜像),相当于增加了(256224)2*2=2048倍的数据量。如果没有数据增强,仅靠原始的数据量,参数众多的CNN会陷入过拟合中,使用了数据增强后可以大大减轻过拟合,提升泛化能力。进行预测时,则是取图片的四个角加中间共5个位置,并进行左右翻转,一共获得10张图片,对他们进行预测并对10次结果求均值。
VGG
VGG的泛化性能非常好,常用于图像特征的抽、目标检测候选框生成等。VGG 最大的问题就在于参数数量,VGG-19 基本上是参数量最多的卷积网络架构。这一问题也是第一次提出 Inception 结构的 GoogLeNet 所重点关注的,它没有如同 VGG-Net 那样大量使用全连接网络,因此参数量非常小。
GoogLeNet
GoogLeNet 最大的特点就是使用了 Inception 模块,它的目的是设计一种具有优良局部拓扑结构的网络,即对输入图像并行地执行多个卷积运算或池化操作,并将所有输出结果拼接为一个非常深的特征图。因为 1*1、3*3 或 5*5 等不同的卷积运算与池化操作可以获得输入图像的不同信息,并行处理这些运算并结合所有结果将获得更好的图像表征。
ResNet
ResNet依然是:没有最深,只有更深(152层)。听说目前层数已突破一千。
主要的创新在残差网络,其实这个网络的提出本质上还是要解决层次比较深的时候无法训练的问题。这种借鉴了Highway Network思想的网络相当于旁边专门开个通道使得输入可以直达输出,而优化的目标由原来的拟合输出H(x)变成输出和输入的差H(x)-x,其中H(X)是某一层原始的的期望映射输出,x是输入。
总结
参考资料
【论文阅读】《Deep Residual Learning for Image Recognition》 - 知乎 (zhihu.com)
一文读懂LeNet、AlexNet、VGG、GoogleNet、ResNet到底是什么? - 知乎 (zhihu.com)
更多推荐
所有评论(0)