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 模型构建

什么是“预训练模型”?什么是“迁移学习”?(必做) 

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

1、不使用预训练模型

5.5.3 模型训练 

5.5.4 模型评价

 5.5.5 模型预测

二、思考题

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

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

遇到的问题 

总结


前言

       这次我还是写很细,写了很长时间,我这次改GPU改了好长时间,一开始以为是电脑出问题,因为训练的很慢,后来才发现是没有全转到GPU上去,建议训练之前重启电脑清一下缓存,要不也会影响速度。

       最后,写的不太好,请老师和各位大佬多教教我。

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


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

图像分类(Image Classification)

计算机视觉中的一个基础任务,将图像的语义将不同图像划分到不同类别。

很多任务可以转换为图像分类任务。

比如人脸检测就是判断一个区域内是否有人脸,可以看作一个二分类的图像分类任务。

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

 

  • 数据集: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.15 所示。

将数据集文件进行解压:

# 解压数据集
# 初次运行时将注释取消,以便解压文件
# 如果已经解压过,不需要运行此段代码,否则由于文件已经存在,解压时会报错
!tar -xvf /home/aistudio/data/data9154/cifar-10-python.tar.gz -C /home/aistudio/datasets/

        大家注意这段代码,如果你喜欢用的是Jupyter的话,是要加上这段代码的,这是解压的代码,但是,如果大家用的是pycharm的话,用这段代码是会出现问题的,如果使用pycharm的话,大家只需要把CIFAR-10数据集下载下来,然后直接解压在当前目录下就行(因为老师给的是自定义dataset类,但是如果是用pytorch的dataset类就不用这么麻烦了)。

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

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数据的代码如下所示:

# coding=gbk
# 解压数据集
# 初次运行时将注释取消,以便解压文件
# 如果已经解压过,不需要运行此段代码,否则由于文件已经存在,解压时会报错
import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.utils.data as data
import torchvision.transforms
from torchvision.transforms import Compose, Resize, Normalize,ToTensor
from torchvision.models import resnet18
import torch.nn.functional as F
import torch.optim as opt
from nndl import RunnerV3, Accuracy
from nndl import plot

        这个是下边将要用到的所有的库,但注意解码方式,同时我说一下之前就想说的两个库os和pickle库,这两个库可能大家比较陌生。

        os.path.join()函数用于路径拼接文件路径,可以传入多个路径

        python的pickle模块提供了一个简答的持久化功能,可以将对象以文件的形式存放在磁盘上。
        pickle模块实现了基本的数据序列化和反序列化
        通过pickle模块的序列化操作pickle.dump(obj, file, [,protocol]),我们能够将程序中运行的对象信息保存到文件中去,永久存储。
         通过pickle模块的反序列化操作pickle.load(file),我们能从文件中创建上一次程序保存的对象

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

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

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

 运行结果为:

The label in the picture is 9

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

        这个其实和上两个实验是一样的,大家有兴趣的可以看看看我上两个实验写的这,我写的很详细,大家有兴趣的可以看一看。

5.5.1.3 构造Dataset类 

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

class CIFAR10Dataset(data.Dataset):
    def __init__(self, folder_path='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 = Compose([ToTensor(),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=img.transpose(1, 2, 0)
        img = self.transforms(img).cuda()
        label=torch.tensor(label).cuda()
        return img, label

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

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

5.5.2 模型构建

使用pytorch高层API中的Resnet18进行图像分类实验。

resnet18_model = resnet18(pretrained=True)

什么是“预训练模型”?什么是“迁移学习”?(必做) 

说一下这个,这个就是老师上课讲的,就是我的理解。

我的理解是:

       迁移学习就是就是类似一种站在巨人的肩膀上的感觉,我的理解是这个不是专门的指提训练模型的参数,更加泛指你使用的模型,甚至解决问题的思想。

       预训练模型有点像是迁移学习的一种应用,就是如果两个数据集有某方面的共性的话,可以提前在一个相似的数据集上提前训练好,这样再训练的话就不是从零开始了,再训练的话这样效果比较好,哪怕数据集较少也会有较好的结果,但是代价会比较高。

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

        说一下从网上找到的,比较官方的解释

4、迁移学习和预训练模型_不知道叫啥的喵的博客-CSDN博客_迁移学习 预训练模型

注:迁移学习内容来自于王晋东老师的《迁移学习简明手册》

1、什么是迁移学习

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

2、预训练模型

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

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

1、不使用预训练模型

resnet18_model = resnet18(pretrained=False)

运行结果为:

[Train] epoch: 0/30, step: 0/18750, loss: 6.94789
[Train] epoch: 1/30, step: 1000/18750, loss: 1.08444
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.56800, dev loss: 1.25183
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.56800
[Train] epoch: 3/30, step: 2000/18750, loss: 0.93303
[Evaluate]  dev score: 0.56870, dev loss: 1.24630
[Evaluate] best accuracy performence has been updated: 0.56800 --> 0.56870
[Train] epoch: 4/30, step: 3000/18750, loss: 1.10861
[Evaluate]  dev score: 0.66820, dev loss: 0.95978
[Evaluate] best accuracy performence has been updated: 0.56870 --> 0.66820
[Train] epoch: 6/30, step: 4000/18750, loss: 0.82029
[Evaluate]  dev score: 0.67690, dev loss: 0.95927
[Evaluate] best accuracy performence has been updated: 0.66820 --> 0.67690
[Train] epoch: 8/30, step: 5000/18750, loss: 0.69778
[Evaluate]  dev score: 0.71650, dev loss: 0.84400
[Evaluate] best accuracy performence has been updated: 0.67690 --> 0.71650
[Train] epoch: 9/30, step: 6000/18750, loss: 0.82190
[Evaluate]  dev score: 0.68230, dev loss: 0.95333
[Train] epoch: 11/30, step: 7000/18750, loss: 0.56318
[Evaluate]  dev score: 0.71430, dev loss: 0.84213
[Train] epoch: 12/30, step: 8000/18750, loss: 0.78213
[Evaluate]  dev score: 0.71240, dev loss: 0.85661
[Train] epoch: 14/30, step: 9000/18750, loss: 0.56063
[Evaluate]  dev score: 0.69230, dev loss: 0.93725
[Train] epoch: 16/30, step: 10000/18750, loss: 0.57713
[Evaluate]  dev score: 0.72350, dev loss: 0.82346
[Evaluate] best accuracy performence has been updated: 0.71650 --> 0.72350
[Train] epoch: 17/30, step: 11000/18750, loss: 0.50005
[Evaluate]  dev score: 0.70620, dev loss: 0.87751
[Train] epoch: 19/30, step: 12000/18750, loss: 0.55691
[Evaluate]  dev score: 0.71680, dev loss: 0.86619
[Train] epoch: 20/30, step: 13000/18750, loss: 0.77892
[Evaluate]  dev score: 0.71880, dev loss: 0.86092
[Train] epoch: 22/30, step: 14000/18750, loss: 0.48677
[Evaluate]  dev score: 0.74160, dev loss: 0.79807
[Evaluate] best accuracy performence has been updated: 0.72350 --> 0.74160
[Train] epoch: 24/30, step: 15000/18750, loss: 0.50528
[Evaluate]  dev score: 0.72300, dev loss: 0.86205
[Train] epoch: 25/30, step: 16000/18750, loss: 0.59597
[Evaluate]  dev score: 0.70250, dev loss: 0.91597
[Train] epoch: 27/30, step: 17000/18750, loss: 0.45800
[Evaluate]  dev score: 0.72890, dev loss: 0.84346
[Train] epoch: 28/30, step: 18000/18750, loss: 0.49239
[Evaluate]  dev score: 0.71990, dev loss: 0.83751
[Evaluate]  dev score: 0.72280, dev loss: 0.85737
[Train] Training done!

 [Test] accuracy/loss: 0.7262/0.8333

2、使用预训练模型

resnet18_model = resnet18(pretrained=True)

 [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!

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

 [Test] accuracy/loss: 0.7428/0.7691

          说一下加载预训练模型的好处,这在上边两张图的对比中完美的体现了出来

1.加快梯度下降的收敛速度О

2.更有可能获得一个低模型误差,或者低泛化误差的模型

3.降低因未初始化或初始化不当导致的梯度消失或者梯度爆炸问题。此情况会导致模型训练速度变慢,崩溃,直至失败。

4.其中随机初始化,可以打破对称性,从而保证不同的隐藏单元可以学习到不同的东西

          总结一下就是使用预训练模型作为初始化,好处是:1)加速训练,可以使用更少的训练epoch;2)好的预训练模型可以避免陷入局部最优点或鞍点。 

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

 运行结果为:

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

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

这里说一下会用到的函数

class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标

        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []

        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []

        # 记录全局最优指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()

        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_steps = kwargs.get("log_steps", 100)
        # 评价频率
        eval_steps = kwargs.get("eval_steps", 0)

        # 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"
        save_path = kwargs.get("save_path", "best_model.pdparams")

        custom_print_log = kwargs.get("custom_print_log", None)

        # 训练总的步数
        num_training_steps = num_epochs * len(train_loader)

        if eval_steps:
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None!')
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')

        # 运行的step数目
        global_step = 0

        # 进行num_epochs轮训练
        for epoch in range(num_epochs):
            # 用于统计训练集的损失
            total_loss = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y.long())  # 默认求mean
                total_loss += loss

                # 训练过程中,每个step的loss进行保存
                self.train_step_losses.append((global_step, loss.item()))

                if log_steps and global_step % log_steps == 0:
                    print(
                        f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")

                # 梯度反向传播,计算每个参数的梯度值
                loss.backward()

                if custom_print_log:
                    custom_print_log(self)

                # 小批量梯度下降进行参数更新
                self.optimizer.step()
                # 梯度归零
                self.optimizer.zero_grad()

                # 判断是否需要评价
                if eval_steps > 0 and global_step > 0 and \
                        (global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):

                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")

                    # 将模型切换为训练模式
                    self.model.train()

                    # 如果当前指标为最优指标,保存该模型
                    if dev_score > self.best_score:
                        self.save_model(save_path)
                        print(
                            f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")
                        self.best_score = dev_score

                global_step += 1

            # 当前epoch 训练loss累计值
            trn_loss = (total_loss / len(train_loader)).item()
            # epoch粒度的训练loss保存
            self.train_epoch_losses.append(trn_loss)

        print("[Train] Training done!")

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None

        # 将模型设置为评估模式
        self.model.eval()

        global_step = kwargs.get("global_step", -1)

        # 用于统计训练集的损失
        total_loss = 0

        # 重置评价
        self.metric.reset()

        # 遍历验证集每个批次
        for batch_id, data in enumerate(dev_loader):
            X, y = data

            # 计算模型输出
            logits = self.model(X)

            # 计算损失函数
            loss = self.loss_fn(logits, y.long()).item()
            # 累积损失
            total_loss += loss

            # 累积评价
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()

        # 记录验证集loss
        if global_step != -1:
            self.dev_losses.append((global_step, dev_loss))
            self.dev_scores.append(dev_score)

        return dev_score, dev_loss

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def predict(self, x, **kwargs):
        # 将模型设置为评估模式
        self.model.eval()
        # 运行模型前向计算,得到预测值
        logits = self.model(x)
        return logits

    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    def load_model(self, model_path):
        state_dict = torch.load(model_path)
        self.model.load_state_dict(state_dict)
class Accuracy():
    def __init__(self, is_logist=True):
        """
        输入:
           - is_logist: outputs是logist还是激活后的值
        """

        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0

        self.is_logist = is_logist

    def update(self, outputs, labels):
        """
        输入:
           - outputs: 预测值, shape=[N,class_num]
           - labels: 标签值, shape=[N,1]
        """

        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1:  # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs >= 0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = torch.tensor((outputs >= 0.5), dtype=torch.float32)
        else:
            # 多分类时,使用'torch.argmax'计算最大元素索引作为类别
            preds = torch.argmax(outputs, dim=1)

        # 获取本批数据中预测正确的样本个数
        labels = torch.squeeze(labels, dim=-1)
        batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).cpu().numpy()
        batch_count = len(labels)

        # 更新num_correct 和 num_count
        self.num_correct += batch_correct
        self.num_count += batch_count

    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count

    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0

    def name(self):
        return "Accuracy"
# 可视化
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()

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

二、思考题

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

我看了看英文版的,但是我太菜了,好多词汇看不懂,所以我看了一个大神的解说。

Deep Residual Learning for Image Recognition(译)_小时候贼聪明的博客-CSDN博客

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

 首先提出了一个问题,随着层数合理的加深为什么精度下去了呢?然后引出自己的残差网络。

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

这里解释一下残差单元的根据,然后由此构建残差网络

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

 在后边就是对,网络层数的探究了。

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

 这张图非常的关键,这是相当于告诉你是resnet的优越性,以及层数的重要对比。

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

 下边就是最重要的深层数的对比了。NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类_第13张图片

 下边这张表相当于是验证性试验的结果,验证结论对不对。

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

      总结一下就是,按上边的结论,前两个看表,记住表就记住特性了 ,主要说说后边。

      50层 ResNet:我们将34层网络中2层的模块替换成3层的瓶颈模块,整个模型也就变成了50层的ResNet (Table 1)。对于增加的维度我们使用选项B来处理。整个模型含有38亿个FLOPs。

      101层和152层 ResNets:我们使用更多的3层模块来构建101层和152层的ResNets (Table 1)。值得注意的是,虽然层的深度明显增加了,但是152层ResNet的计算复杂度(113亿个FLOPs)仍然比VGG-16(153 亿个FLOPs)和VGG-19(196亿个FLOPs)的小很多。

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

     老师强调的了自己的haunt,我就结合学到简单说一说,有不对欢迎,各位大神批评指正。

     首先,lenet首个如此成功的卷积神经网络,老师说这个是应用级的,真的应用到了美国的很多家银行,拜一拜,具体过程在下边。

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

        AlexNet的意义不用我多说了,光用gpu一条我直接跪下,更别说还有首个深度卷积网络,拜一拜。NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类_第16张图片

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

       VGG一看这名,我直接竖大拇指,这个如此简单的结构,经过搭配之后效果这么好,并且现在咱们用的好多东西,发现,都是这个的首例。 

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

 

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

 

       GoogLeNet这个是致敬了LeNet,并且这个是在采取了加宽的思想,相当于是拓展了思维这个是很重要的,但是按老师的意思,这个不如VGG影响深远,这个重点是理解Inception模块 ,拜一拜。

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

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

        然后就是无敌的残差网络了,老师说,残差网络之后,大赛直接不办了,因为这个网络把问题解决了,然后这个网络时VGG的升级版,这个重点是要理解残差模块,拜一拜,太厉害了,我是fw。

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

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

遇到的问题 

如果只把模型GPU化的话,而不把数据导进去,会导致无法运行,并且一个一个导进去的话(改Runner的话会导致速度特别慢,这也是我要说一下的原因),也就是下边的错。

RuntimeError: Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same or input should be a MKLDNN tensor and weight is a dense tensor

改进的方法是

class CIFAR10Dataset(data.Dataset):
    def __init__(self, folder_path='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 = Compose([ToTensor(),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=img.transpose(1, 2, 0)
        img = self.transforms(img).cuda()
        label=torch.tensor(label).cuda()
        return img, label

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

我试了试这种改的方式和在runner那改结果是一样的,但是时间会快不少,省去了循环遍历的时间。 


总结

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

       首先,先发放上我画的思维导图,我一开始照着书画,后来发现和老师的一模一样,老师实在是厉害。

        其次,以前听说过迁移学习,但是根本不知道是啥东西,这次真的学了学,了解一下预训练模型与不预训练模型的区别。

        其次,这次终于体会到了GPU训练和CPU训练的区别,GPU真的会快不少。

        最后,当然是谢谢老师,谢谢老师在学习和生活上的关心(哈哈哈)。

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

你可能感兴趣的:(人工智能,深度学习,pytorch,cnn)