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 模型预测

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

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

思考题

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

用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet(选做)

总结


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

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

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

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

5.5.1 数据处理

5.5.1.1 数据集介绍

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

 5.5.1.2 数据读取

cifar-10数据集直接从官网下载http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz

cifar-10 数据集由 60000 张分辨率为 32x32 彩色图像组成,共分为 10 类,每类包含 6000 张图像,cifar-10 数据集有 50000 个训练图像和 10000 个测试图像。
最终的数据集构成为:

  • 训练集:50 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)

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()
plt.savefig('cnn-car.pdf')

 得到以下结果:

batch of imgs shape:  (10000, 3, 32, 32) batch of labels shape:  (10000,)
The label in the picture is 9

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

 5.5.1.3 构造Dataset类

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

import torch
from torch.utils.data import Dataset
import torchvision.transforms as transforms

class CIFAR10Dataset(Dataset):
    def __init__(self, folder_path=r'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.transforms = transforms.Compose([transforms.Resize(32),transforms.ToTensor(), transforms.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=r'cifar-10-batches-py', mode='train')
dev_dataset = CIFAR10Dataset(folder_path=r'cifar-10-batches-py', mode='dev')
test_dataset = CIFAR10Dataset(folder_path=r'cifar-10-batches-py', mode='test')

5.5.2 模型构建

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

from torchvision.models import resnet18
 
resnet18_model = resnet18(pretrained=True)

我们总是可以在各种博客中看到用高层API中层API低层API构建网络,但是这不同层次的API是什么意思呢?又有什么区别呢?

API这个次相信大家不陌生,而且也很好理解。API是Application Programming Interface(应用程序接口)的缩写。是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

低阶API主要包括张量操作,计算图和自动微分。Pytorch的中阶API主要包括各种模型层,损失函数,优化器,数据管道等等。高层API用torchkeras举例torchkeras是仿照tf.keras.Model的功能对Pytorch的nn.Module进行了封装。torchkeras实现了 fit, validate,predict, summary 方法,相当于用户自定义高阶API。用起来可keras一样的爽!

越高级的API里封装的函数越多,用起来也就越方便。

API的好处也有很多:

外用

如果您的应用变得流行,其他技术可能会要求 API 与您的系统进行交互。因此,您基本上会收到免费宣传,并最终获得用户,因为它会将您的产品介绍给新的用户群。

安全

由于 API 充当系统之间的缓冲区,它们允许您选择允许和不允许的行为。用户可以根据自己的隐私偏好做出更个性化的选择,而不是全有或全无。

更快的生产

API 使程序员可以更轻松地快速构建应用程序,因为它们简化了他们必须考虑的设备数量,并允许他们导入外部 API 以添加某些关键功能。例如,您可以创建一个应用程序,让用户可以找到他们所在地区新晋艺术家的活动。

自动化

API 对于自动化测试也非常有用。以前,测试人员必须依靠自定义脚本、外部 Web 服务和直接集成来处理他们的测试需求。虽然这适用于小规模,但直接集成使测试环境在处理不同工具时变得不灵活,并且仅限于少量测试。API 提供的抽象允许测试人员在 XML 或 JSON 文件中创建通用测试套件,并在与其 API 集成的任何系统上使用它们。

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")

end_time = time.time()
print(strat_time-end_time)
C:\Users\DELL\.conda\envs\pytorch\python.exe C:/Users/DELL/PycharmProjects/pythonProject/CSDN/CSDN/实验5.5.py
[Train] epoch: 0/30, step: 0/18750, loss: 12.96724
[Evaluate]  dev score: 0.68550, dev loss: 0.92756
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.68550
[Train] epoch: 3/30, step: 2000/18750, loss: 0.59910
[Evaluate]  dev score: 0.66440, dev loss: 1.02158
[Train] epoch: 4/30, step: 3000/18750, loss: 1.01905
[Evaluate]  dev score: 0.72770, dev loss: 0.81920
[Evaluate] best accuracy performence has been updated: 0.68550 --> 0.72770
[Train] epoch: 6/30, step: 4000/18750, loss: 0.65524
[Evaluate]  dev score: 0.73560, dev loss: 0.78718
[Evaluate] best accuracy performence has been updated: 0.72770 --> 0.73560
[Train] epoch: 8/30, step: 5000/18750, loss: 0.62345
[Evaluate]  dev score: 0.72720, dev loss: 0.82402
[Train] epoch: 9/30, step: 6000/18750, loss: 0.87880
[Evaluate]  dev score: 0.71880, dev loss: 0.84249
[Train] epoch: 11/30, step: 7000/18750, loss: 0.43094
[Evaluate]  dev score: 0.74530, dev loss: 0.76993
[Evaluate] best accuracy performence has been updated: 0.73560 --> 0.74530
[Train] epoch: 12/30, step: 8000/18750, loss: 0.90284
[Evaluate]  dev score: 0.72090, dev loss: 0.84032
[Train] epoch: 14/30, step: 9000/18750, loss: 0.48723
[Evaluate]  dev score: 0.73010, dev loss: 0.81101
[Train] epoch: 16/30, step: 10000/18750, loss: 0.57254
[Evaluate]  dev score: 0.74340, dev loss: 0.76770
[Train] epoch: 17/30, step: 11000/18750, loss: 0.64364
[Evaluate]  dev score: 0.73890, dev loss: 0.78254
[Train] epoch: 19/30, step: 12000/18750, loss: 0.47278
[Evaluate]  dev score: 0.73730, dev loss: 0.79158
[Train] epoch: 20/30, step: 13000/18750, loss: 0.40847
[Evaluate]  dev score: 0.72970, dev loss: 0.80822
[Train] epoch: 22/30, step: 14000/18750, loss: 0.45966
[Evaluate]  dev score: 0.74420, dev loss: 0.77700
[Train] epoch: 24/30, step: 15000/18750, loss: 0.57626
[Evaluate]  dev score: 0.75220, dev loss: 0.75880
[Evaluate] best accuracy performence has been updated: 0.74530 --> 0.75220
[Train] epoch: 25/30, step: 16000/18750, loss: 0.88088
[Evaluate]  dev score: 0.72740, dev loss: 0.83202
[Train] epoch: 27/30, step: 17000/18750, loss: 0.49556
[Evaluate]  dev score: 0.73830, dev loss: 0.81733
[Train] epoch: 28/30, step: 18000/18750, loss: 0.51472
[Evaluate]  dev score: 0.74790, dev loss: 0.76180
[Evaluate]  dev score: 0.75390, dev loss: 0.75796
[Evaluate] best accuracy performence has been updated: 0.75220 --> 0.75390
[Train] Training done!
-23319.217297554016

进程已结束,退出代码为 0

 我用的是cpu训练,是i7 10750H的cpu训练了23319秒emm,差不多六个半小时吧。

 在本实验中,使用了第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.7448/0.7734

5.5.5 模型预测

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

#获取测试集中的一个batch的数据
for X, label in test_loader:
 
    logits = runner.predict(X)
    #多分类,使用softmax计算预测概率
    pred = F.softmax(logits)
    #获取概率最大的类别
    pred_class = torch.argmax(pred[2]).numpy()
    label = label[2].data.numpy()
    #输出真实类别与预测类别
    print("The true category is {} and the predicted category is {}".format(classes[label], classes[pred_class]))
    #可视化图片
    X=np.array(X)
    X=X[1]
    plt.imshow(X.transpose(1, 2, 0))
    plt.show()
    break
The true category is ship and the predicted category is ship

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

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

迁移学习,顾名思义,就是要进行迁移。放到人工智能和机器学习的学科里,迁移学习是一种学习的思想和模式。
首先机器学习是人工智能的一大类重要方法,也是目前发展最迅速、效果最显著的方法。机器学习解决的是让机器自主地从数据中获取知识,从而应用于新的问题中。迁移学习作为机器学习的一个重要分支,侧重于将已经学习过的知识迁移应用于新的问题中。
迁移学习的核心问题是,找到新问题和原问题之间的相似性,才可顺利地实现知识的迁移。

迁移学习的定义:
迁移学习,是指利用数据、任务、或模型之间的相似性,将在旧领域学习过的模型,应用于新领域的一种学习过程。

那为什么需要迁移学习?

1. 大数据与少标注之间的矛盾。

2. 大数据与弱计算之间的矛盾。

3. 普适化模型与个性化需求之间的矛盾。

4. 特定应用的需求。

预训练模型可以把迁移学习很好地用起来。这和小孩读书一样,一开始语文、数学、化学都学,读书、网上游戏等,在脑子里积攒了很多。当他学习计算机时,实际上把他以前学到的所有知识都带进去了。如果他以前没上过中学,没上过小学,突然学计算机就不懂这里有什么道理。这和我们预训练模型一样,预训练模型就意味着把人类的语言知识,先学了一个东西,然后再代入到某个具体任务,就顺手了,就是这么一个简单的道理。

为什么要做预训练模型

自然语言处理(NLP),目的是使得计算机具备人类的听、说、读、写、译、问、答、搜索、摘要、对话和聊天等能力,并可利用知识和常识进行推理和决策,并支持客服、诊断、法律、教学等场景。

预训练模型,则是使自然语言处理由原来的手工调参、依靠 ML 专家的阶段,进入到可以大规模、可复制的大工业施展的阶段。而且预训练模型从单语言、扩展到多语言、多模态任务。

为什么我们要做预训练模型?

  • 首先,预训练模型是一种迁移学习的应用,利用几乎无限的文本,学习输入句子的每一个成员的上下文相关的表示,它隐式地学习到了通用的语法语义知识。
  • 第二,它可以将从开放领域学到的知识迁移到下游任务,以改善低资源任务,对低资源语言处理也非常有利。
  • 第三,预训练模型在几乎所有NLP任务中都取得了目前最佳的成果。
  • 最后,这个预训练模型+微调机制具备很好的可扩展性,在支持一个新任务时,只需要利用该任务的标注数据进行微调即可,一般工程师就可以实现。

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

上面已经用了使用预训练模型过了,下面不使用预训练模

resnet18_model = resnet18(pretrained=False)

用这一行代码不使用预训练,得到结果 :

[Train] epoch: 25/30, step: 16000/18750, loss: 0.88088
[Evaluate]  dev score: 0.72740, dev loss: 0.83202
[Train] epoch: 28/30, step: 17000/18750, loss: 0.59556
[Evaluate]  dev score: 0.73830, dev loss: 0.81733
[Train] epoch: 30/30, step: 18000/18750, loss: 0.61472
[Evaluate]  dev score: 0.64790, dev loss: 0.86180
[Evaluate]  dev score: 0.65390, dev loss: 0.85796
[Evaluate] best accuracy performence has been updated: 0.64120 --> 0.66390
[Train] Training done!

可以发现不使用预训练效果会差一些。

所以预训练的优点有:

1、开源模型多,可以直接用于目标检测
2、可以快速地得到最终模型,需要的训练数据少

但是预训练也是有缺点的:

缺点:
1、预训练模型大、参数多、模型结构灵活性差、难以改变网络结构,计算量大,限制应用场景
2、分类和检测任务损失函数和类别分布不同,优化空间存在差异
3、尽管微调可以减少不同目标类别分布差异性,差异太大时,微调效果不明显

思考题

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

ResNet分为18层的,34层的,50层的,101层的,152层的。每种都是由两种block结构堆叠而成,一种是叫做BasicBlock,一种叫做BottleneckBlock。NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类_第3张图片

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

 ResNet 的前两层在输出通道数为 64、步幅为 2 的 7 × 7 7×77×7 卷积层后接步幅为 2 的 3 × 3 3×33×3 的最大池化层。不同之处在于 ResNet 每个卷积层后增加的批量归一化层ResNet 使用 4 个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。第一个模块的通道数同输入通道数一致。由于之前已经使用了步幅为 2 的最大池化层,所以无须减小高和宽。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。最后,加入全局平均池化层后接上全连接层输出

用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet(选做)

1 .lenet:始祖,瞎组合的一个结构,很多设计思想已经过时了,实践中切勿使用。

2.alexnet:近古老物,relu还能用,drop-out已经用的比较少了而且不太好用。lrn之类的已经不好用了,一般是论文里当被吊打的baseline用

3.vgg:老物,光芒被googlenet给掩盖了,真正的优点是 a.feature-map不变则conv-kenel-width这些不变 b.用一次pooling,width提升一倍

4 .GoogLeNet,核心亮点就是Inception,网络的最大特点是用全局平均池化取代全连接层,减少了模型计算量,使得模型训练速度更快并且减轻了过拟合

5.resnet:主流网络,在vgg基础上提升了长度,而且加入了res-block结构,还有resnext,也可以尝试下

总结

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

这次训练用了resnet18模型,我当时下载torch的时候下的是没有cuda版本的,这次本来想下载的,但是我看我的电脑cuda版本是12.0而torch刚出到11.7版本,再加上我已经开始训练了,后面想了想就没加,不过我问过别的同学,他们用gpu训练需要的时间短一点,大概几十分钟就好了。

至此卷积神经网络的学习结束了,下面开始的就是循环神经网络,加油! 

参考链接:

(25条消息) vgg和alexnet,lenet resnet等网络简要评价和使用体会_crazy-ye的博客-CSDN博客_lenet的优缺点

(25条消息) torch学习笔记之——高阶,中阶,低阶API,构建网络对比_卖香油的少掌柜的博客-CSDN博客

(25条消息) 什么是 API?_我是不会选择做一个普通人的的博客-CSDN博客_什么是api

你可能感兴趣的:(神经网络与深度学习,深度学习,神经网络,人工智能)