NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类

目录

5.5 实践:基于ResNet18网络完成图像分类任务

5.5.1 数据处理

5.5.1.1 数据集介绍

5.5.1.2 数据读取

5.5.1.3 构造Dataset类

5.5.2 模型构建

什么是“预训练模型”?什么是“迁移学习”?

比较“使用预训练模型”和“不使用预训练模型”的效果

5.5.3 模型训练

5.5.4 模型评价

5.5.5 模型预测

思考题

总结

参考

5.5 实践:基于ResNet18网络完成图像分类任务

在本实践中,我们实践一个更通用的图像分类任务。

图像分类(Image Classification)是计算机视觉中的一个基础任务,将图像的语义将不同图像划分到不同类别。很多任务也可以转换为图像分类任务。比如人脸检测就是判断一个区域内是否有人脸,可以看作一个二分类的图像分类任务。

这里,我们使用的计算机视觉领域的经典数据集:CIFAR-10数据集,网络为ResNet18模型,损失函数为交叉熵损失,优化器为Adam优化器,评价指标为准确率。

5.5.1 数据处理

5.5.1.1 数据集介绍

CIFAR-10数据集包含了10种不同的类别、共60,000张图像,其中每个类别的图像都是6000张,图像大小均为32×32像素。CIFAR-10数据集的示例如 图5.15 所示。

图5.15: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='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)

结果如下:

batch of imgs shape:  (10000, 3, 32, 32) batch of labels shape:  (10000,)

可视化观察其中的一张样本图像和对应的标签,代码如下所示:

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.show()

结果如下:

The label in the picture is 9

5.5.1.3 构造Dataset类

构造一个CIFAR10Dataset类,其将继承自torch.utils.data类,可以逐个数据进行处理。代码实现如下:

import torch
import torch.utils.data as io
from torchvision.transforms import Normalize


class CIFAR10Dataset(io.Dataset):
    def __init__(self, folder_path='/home/aistudio/cifar-10-batches-py', mode='train'):
        if mode == 'train':
            # 加载batch1-batch4作为训练集
            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':
            # 加载batch5作为验证集
            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 = Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2023, 0.1994, 0.2010])

    def __getitem__(self, idx):
        img, label = self.imgs[idx], self.labels[idx]
        img = self.transform(img)
        return img, label

    def __len__(self):
        return len(self.imgs)


torch.manual_seed(100)
train_dataset = CIFAR10Dataset(folder_path='/home/aistudio/datasets/cifar-10-batches-py', mode='train')
dev_dataset = CIFAR10Dataset(folder_path='/home/aistudio/datasets/cifar-10-batches-py', mode='dev')
test_dataset = CIFAR10Dataset(folder_path='/home/aistudio/datasets/cifar-10-batches-py', mode='test')

5.5.2 模型构建

使用torchvision中的Resnet18进行图像分类实验。

from torchvision.models import resnet18

resnet18_model = resnet18()

什么是“预训练模型”?什么是“迁移学习”?

在一个原始任务或数据集上预先训练一个初始的模型,然后在目标任务或数据集上使用该模型,针对目标任务的特性,对该初始模型进行精调,从而达到提高目标任务的目的,而预先训练这个初始模型的过程就叫预训练模型。

迁移学习的官方解释是,指的是一个预训练的模型被重新定义在另一个任务中;就好比如是把为任务A开发的模型作为初始点,重新使用在为任务B开发模型的过程中,通过从已学习的相关任务中转移知识来改进学习的新任务。举个例子来说,我们可能会发现学习识别苹果可能有助于识别梨,或者学习打篮球可能有助于学习打排球等等。

比较“使用预训练模型”和“不使用预训练模型”的效果

resnet = models.resnet18(pretrained=True)

resnet = models.resnet18(pretrained=False)

5.5.3 模型训练

复用RunnerV3类,实例化RunnerV3类,并传入训练配置。
使用训练集和验证集进行模型训练,共训练30个epoch。
在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:

# 指定运行设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
# 学习率大小
lr = 0.001
# 批次大小
batch_size = 64
# 加载数据
 
train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)
 
# 定义网络
model = resnet18_model
model = model.to(device)
# 定义优化器,这里使用Adam优化器以及l2正则化策略,相关内容在7.3.3.2和7.6.2中会进行详细介绍
optimizer = opt.Adam(lr=lr, params=model.parameters(), weight_decay=0.005)
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric =Accuracy(is_logist=True)
# 实例化RunnerV3
 
strat_time = time.time()
runner = RunnerV3(model, optimizer, loss_fn, metric)
 
# 启动训练
log_steps = 1000
eval_steps = 1000
runner.train(train_loader, dev_loader, num_epochs=30, log_steps=log_steps,
             eval_steps=eval_steps, save_path="best_model.pdparams")

运行结果如下:

  [Train] epoch: 0/30, step: 0/18750, loss: 12.96725
[Train] epoch: 1/30, step: 1000/18750, loss: 1.03638
C:\Users\LENOVO\PycharmProjects\pythonProject\深度学习\nndl.py:386: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
  batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).cpu().numpy()
[Evaluate]  dev score: 0.69020, dev loss: 0.94529
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.69020
[Train] epoch: 3/30, step: 2000/18750, loss: 0.63199
[Evaluate]  dev score: 0.67160, dev loss: 0.97103
[Train] epoch: 4/30, step: 3000/18750, loss: 0.99153
[Evaluate]  dev score: 0.71030, dev loss: 0.84957
[Evaluate] best accuracy performence has been updated: 0.69020 --> 0.71030
[Train] epoch: 6/30, step: 4000/18750, loss: 0.66442
[Evaluate]  dev score: 0.71620, dev loss: 0.85197
[Evaluate] best accuracy performence has been updated: 0.71030 --> 0.71620
[Train] epoch: 8/30, step: 5000/18750, loss: 0.60312
[Evaluate]  dev score: 0.72310, dev loss: 0.82674
[Evaluate] best accuracy performence has been updated: 0.71620 --> 0.72310
[Train] epoch: 9/30, step: 6000/18750, loss: 0.87040
[Evaluate]  dev score: 0.73230, dev loss: 0.79165
[Evaluate] best accuracy performence has been updated: 0.72310 --> 0.73230
[Train] epoch: 11/30, step: 7000/18750, loss: 0.39707
[Evaluate]  dev score: 0.73730, dev loss: 0.79308
[Evaluate] best accuracy performence has been updated: 0.73230 --> 0.73730
[Train] epoch: 12/30, step: 8000/18750, loss: 0.94639
[Evaluate]  dev score: 0.72790, dev loss: 0.79591
[Train] epoch: 14/30, step: 9000/18750, loss: 0.69579
[Evaluate]  dev score: 0.72820, dev loss: 0.82343
[Train] epoch: 16/30, step: 10000/18750, loss: 0.51241
[Evaluate]  dev score: 0.73740, dev loss: 0.79054
[Evaluate] best accuracy performence has been updated: 0.73730 --> 0.73740
[Train] epoch: 17/30, step: 11000/18750, loss: 0.52791
[Evaluate]  dev score: 0.73660, dev loss: 0.79049
[Train] epoch: 19/30, step: 12000/18750, loss: 0.63937
[Evaluate]  dev score: 0.74610, dev loss: 0.76617
[Evaluate] best accuracy performence has been updated: 0.73740 --> 0.74610
[Train] epoch: 20/30, step: 13000/18750, loss: 0.52228
[Evaluate]  dev score: 0.72630, dev loss: 0.83306
[Train] epoch: 22/30, step: 14000/18750, loss: 0.46715
[Evaluate]  dev score: 0.73620, dev loss: 0.80941
[Train] epoch: 24/30, step: 15000/18750, loss: 0.45405
[Evaluate]  dev score: 0.73640, dev loss: 0.80397
[Train] epoch: 25/30, step: 16000/18750, loss: 0.70669
[Evaluate]  dev score: 0.71390, dev loss: 0.87375
[Train] epoch: 27/30, step: 17000/18750, loss: 0.41202
[Evaluate]  dev score: 0.74100, dev loss: 0.79830
[Train] epoch: 28/30, step: 18000/18750, loss: 0.60628
[Evaluate]  dev score: 0.73470, dev loss: 0.80633
[Evaluate]  dev score: 0.73510, dev loss: 0.81483
[Train] Training done!

可视化观察训练集与验证集的准确率及损失变化情况:

plot(runner, fig_name='cnn-loss4.pdf')

运行结果为:

在本实验中,使用了第7章中介绍的Adam优化器进行网络优化,如果使用SGD优化器,会造成过拟合的现象,在验证集上无法得到很好的收敛效果。可以尝试使用第7章中其他优化策略调整训练配置,达到更高的模型精度。 

5.5.4 模型评价

使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下:

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

运行结果为:

 [Test] accuracy/loss: 0.7428/0.7691

5.5.5 模型预测

同样地,也可以使用保存好的模型,对测试集中的数据进行模型预测,观察模型效果,具体代码实现如下:

#获取测试集中的一个batch的数据
X, label = next(iter(test_loader))
logits = runner.predict(X)
#多分类,使用softmax计算预测概率
pred = F.softmax(logits)
#获取概率最大的类别
pred_class = torch.argmax(pred[2]).cpu().numpy()
print(pred_class)
print(label)
label = label[2].cpu().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='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

思考题

阅读《Deep Residual Learning for Image Rescognition》,了解5种深度的ResNet(18,34,50,101和152),并简单谈谈自己的看法。

首先给出五种深度ResNet的具体结构:

这5种深度的resnet,分别是18,34,50,101和152,首先看表最左侧,我们发现所有的网络都分成5部分,分别是:conv1,conv2_x,conv3_x,conv4_x,conv5_x

101-layer那列,我们先看看101-layer是不是真的是101层网络,首先有个输入7x7x64的卷积,然后经过3 + 4 + 23 + 3 = 33个building block,每个block为3层,所以有33 x 3 = 99层,最后有个fc层(用于分类),所以1 + 99 + 1 = 101层,确实有101层网络;

注:101层网络仅仅指卷积或者全连接层,而激活层或者Pooling层并没有计算在内; 这里我们关注50-layer和101-layer这两列,可以发现,它们唯一的不同在于conv4_x,ResNet50有6个block,而ResNet101有23个block,查了17个block,也就是17 x 3 = 51层。

在使用了ResNet的结构后,可以发现层数不断加深导致的训练集上误差增大的现象被消除了,ResNet 网络的训练误差会随着层数增大而逐渐减小,并且在测试机上的表现也会变好,在ResNet推出后不久,Google就借鉴了ResNet的精髓,提出了 Inception V4和 Inception-ResNet-V2,并通过融合这两个模型,在 ILSVRC数据集上取得了惊人的 3.08%的错误率。可见,ResNet及其思想对卷积神经网络研究的贡献确实非常显著,具有很强的推广性。

layer3和layer4结构和layer2相同,无非就是通道数变多,输出尺寸变小;

ResNet18、34、50、101和152都是基于Basicblock,结构非常相似,差别只在于每个layer的block数。

用自己的话简单评价:LetNet、AlexNet、VGG、GooleNet、ResNet

LetNet:第一个真正的卷积神经网络,主要指的是LeNet5或LeNet-5,它的主要特征是将卷积层和下采样层相结合作为网络的基本机构,如果不计输入层,该模型共7层,包括2个卷积层,2个下采样层,3个全连接层。

AlexNet:在AlexNet之前,神经网络一般都使用sigmoid或tanh作为激活函数,这类函数在自变量非常大或者非常小时,函数输出基本不变,称之为饱和函数,为了提高训练速度,AlexNet使用了修正线性函数ReLU,它是一种非饱和函数,与 sigmoid 和tanh 函数相比,ReLU分片的线性结构实现了非线性结构的表达能力,梯度消失现象相对较弱,有助于训练更深层的网络。

VGGNet:常用的有VGG16、VGG19两种类型。VGG16拥有13个卷积层(核大小均为3*3),5个最大池化层,3个全连接层。VGG19拥有16个卷积层(核大小均为3*3),5个最大池化层,3个全连接层。

ResNet:随着神经网络的深度不断的加深,梯度消失、梯度爆炸的问题会越来越严重,这也导致了神经网络的学习与训练变得越来越困难。有些网络在开始收敛时,可能出现退化问题,导致准确率很快达到饱和,出现层次越深、错误率反而越高的现象。让人惊讶的是,这不是过拟合的问题,仅仅是因为加深了网络。这便有了ResNet的设计。ReNet与普通残差网络不同之处在于,引入了跨层连接(shorcut connection),来构造出了残差模块。

总结

参考

NNDL 实验5(下) - HBU_DAVID - 博客园 (cnblogs.com)

NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类_HBU_David的博客-CSDN博客_cifar10预训练模型

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值