目录
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),并简单谈谈自己的看法。(选做)
2.用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet(选做)
思维导图
图像分类(Image Classification)
计算机视觉中的一个基础任务,将图像的语义将不同图像划分到不同类别。
很多任务可以转换为图像分类任务。
比如人脸检测就是判断一个区域内是否有人脸,可以看作一个二分类的图像分类任务。
CIFAR-10数据集包含了10种不同的类别、共60,000张图像,其中每个类别的图像都是6000张,图像大小均为32×3232×32像素。CIFAR-10数据集的示例如 图5.15 所示。
在本实验中,将原始训练集拆分成了train_set、dev_set两个部分,分别包括40 000条和10 000条样本。将data_batch_1到data_batch_4作为训练集,data_batch_5作为验证集,test_batch作为测试集。
最终的数据集构成为:
读取一个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='cifar10/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[2], labels_batch[1]
print("The label in the picture is {}".format(label))
plt.figure(figsize=(3, 3))
plt.imshow(image.transpose(1, 2, 0))
plt.savefig('cnn-car.pdf')
plt.show()
构造一个CIFAR10Dataset类,其将继承自paddle.io.DataSet
类,可以逐个数据进行处理。代码实现如下:
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
import PIL.Image as Image
class CIFAR10Dataset(Dataset):
def __init__(self, folder_path='cifar10/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 = transforms.Compose([
transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), 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 = Image.fromarray(np.uint8(img))
img = self.transform(img)
return img, label
def __len__(self):
return len(self.imgs)
train_dataset = CIFAR10Dataset(folder_path='cifar10/cifar-10-batches-py', mode='train')
dev_dataset = CIFAR10Dataset(folder_path='cifar10/cifar-10-batches-py', mode='dev')
test_dataset = CIFAR10Dataset(folder_path='cifar10/cifar-10-batches-py', mode='test')
使用torchvision.models
API中的Resnet18进行图像分类实验。
from torchvision.models import resnet18
resnet18_model = resnet18()
什么是“
预训练模型”?
预训练模型是深度学习架构,已经过训练以执行大量数据上的特定任务(例如,识别图片中的分类问题)。这种训练不容易执行,并且通常需要大量资源,超出许多可用于深度学习模型的人可用的资源,我就没有大批次GPU。在谈论预训练模型时,通常指的是在Imagenet上训练的CNN(用于视觉相关任务的架构)。ImageNet数据集包含超过1400万个图像,其中120万个图像分为1000个类别(大约100万个图像含边界框和注释)。
从字面上看,预训练模型(pre-training model)是先通过一批语料进行训练模型,然后在这个初步训练好的模型基础上,再继续训练或者另作他用。这样的理解基本上是对的,预训练模型的训练和使用分别对应两个阶段:预训练阶段(pre-training)和 微调(fune-tuning)阶段。
预训练阶段一般会在超大规模的语料上,采用无监督(unsupervised)或者弱监督(weak-supervised)的方式训练模型,期望模型能够获得语言相关的知识,比如句法,语法知识等等。经过超大规模语料的"洗礼",预训练模型往往会是一个Super模型,一方面体现在它具备足够多的语言知识,一方面是因为它的参数规模很大。
微调阶段是利用预训练好的模型,去定制化地训练某些任务,使得预训练模型"更懂"这个任务。例如,利用预训练好的模型继续训练文本分类任务,将会获得比较好的一个分类结果,直观地想,预训练模型已经懂得了语言的知识,在这些知识基础上去学习文本分类任务将会事半功倍。利用预训练模型去微调的一些任务(例如前述文本分类)被称为下游任务(down-stream)。
以BERT为例,BERT是在海量数据中进行训练的,预训练阶段包含两个任务:MLM(Masked Language Model)和NSP (Next Sentence Prediction)。前者类似"完形填空",在一句中扣出一个单词,然后利用这句话的其他单词去预测被扣出的这个单词;后者是给定两句话,判断这两句话在原文中是否是相邻的关系。
BERT预训练完成之后,后边可以接入多种类型的下游任务,例如文本分类,序列标注,阅读理解等等,通过在这些任务上进行微调,可以获得比较好的实验结果。
什么是“迁移学习”?(必做)
迁移学习是一种深度学习策略,它通过将解决一个问题所获得的知识应用于另一个不同但相关的问题来重用这些知识。例如,有3种类型的花:玫瑰、向日葵和郁金香。可以使用标准的预训练模型,如VGG16/19、ResNet50或Inception v3模型(在ImageNet上预训练了1000个输出类)对花卉图像进行分类,但是由于模型没有学习这些花卉类别,因此这样的模型无法正确识别它们。换句话说,它们是模型不知道的类。
迁移学习(Transfer Learning)通俗来讲就是学会举一反三的能力,通过运用已有的知识来学习新的知识,其核心是找到已有知识和新知识之间的相似性,通过这种相似性的迁移达到迁移学习的目的。世间万事万物皆有共性,如何合理地找寻它们之间的相似性,进而利用这个桥梁来帮助学习新知识,是迁移学习的核心问题。
比较“使用
预训练模型”和“不使用
预训练模型”的效果。(必做)
【深度学习】使用预训练模型_DrCrypto的博客-CSDN博客_深度学习预训练模型操作
pytorch学习笔记之加载预训练模型_AI算法札记的博客-CSDN博客_pytorch加载预训练模型
复用RunnerV3类,实例化RunnerV3类,并传入训练配置。
使用训练集和验证集进行模型训练,共训练30个epoch。
在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:
import torch.nn.functional as F
import torch.optim as opt
# 指定运行设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
# 学习率大小
lr = 0.01
# 批次大小
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)
# 定义优化器,这里使用Adam优化器以及l2正则化策略,相关内容在7.3.3.2和7.6.2中会进行详细介绍
optimizer = opt.SGD(model.parameters(), lr=lr, momentum=0.9)
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric = Accuracy()
# 实例化RunnerV3
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
[Train] epoch: 0/30, step: 0/18750, loss: 7.48426
[Train] epoch: 4/30, step: 3000/18750, loss: 3.82750
[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.65240
[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!
在本实验中,使用了第7章中介绍的Adam优化器进行网络优化,如果使用SGD优化器,会造成过拟合的现象,在验证集上无法得到很好的收敛效果。可以尝试使用第7章中其他优化策略调整训练配置,达到更高的模型精度。
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下:
# 加载最优模型
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.8257/0.9169
同样地,也可以使用保存好的模型,对测试集中的数据进行模型预测,观察模型效果,具体代码实现如下:
#获取测试集中的一个batch的数据
X, label = next(iter(test_loader))
X=X.cuda()
logits = runner.predict(X)
#多分类,使用softmax计算预测概率
pred = F.softmax(logits)
#获取概率最大的类别
pred_class = torch.argmax(pred[2]).cpu().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='cifar10/cifar-10-batches-py', mode='test')
plt.imshow(imgs[2].transpose(1,2,0))
plt.show()
8
The true category is 8 and the predicted category is 8
18-layer和34-layer的 residual nets(ResNets)
每两层3*3卷积层会添加一条shortcut connnection。在第一个比较(Table 2 和 Fig.4)中,所有的shortcut connection都是恒等映射,增加维度的地方补0以匹配维度。所以该网络相较于plain nets并没有增加额外的参数,这样便于公平地比较。通过观察能够发现:
1)结果与plain nets相反,34-layer ResNet的性能优于18-layer ResNet。更重要的是,34-layer ResNet的训练错误率更低,在验证集上的泛化能力更强。这些现象说明 degradation problem已经被很好地解决了,并且从增加的深度中获得了精度的提升。
2)与对应的plain版本比较,34-layer ResNet的top-1 error降低了。这验证了残差学习在极深网络中的有效性。
18-layer plain/residual nets的精度相当(Table 2),但是18-layer ResNet收敛得更快(Fig.4 right vs. left)。精度相当是因为在18-layer时,当前的求解器(solver)SGD仍能够为plain net找到很好的解。此时,ResNet能在训练早期提供更快的收敛速度来加速优化。
总之,不论网络的深浅,使用残差学习总是有好处的:浅时能够加快收敛,深时可以解决退化问题,使求解器找到较好的解。
50-layer、101-layer和152-layer ResNets。
在网络很深时,模型的训练时间会难以让人接受。考虑到这个问题,作者就将building block修改成了如Fig.5所示的bottleneck design。具体修改是:将stacked layer由两层换成三层,三层分别为1*1、3*3和1*1卷积层。其中1*1卷积层先负责降维,再负责升维(恢复维度),3*3卷积层是输入输出维度更小的bottleneck。
最终,Building block和bottleneck design有相近的时间复杂度(因为二者参数量相近)。
需要指出的是,无参的恒等映射对bottleneck architectures来说相当重要。如果projection shortcuts取代恒等映射的话,模型的尺寸和参数量都会加倍,因为它两端连接的都是高维特征。因此,恒等映射使模型更加高效。
1)50-layer ResNet:通过将34-layer net中的每个2-layer block替换成Fig.5中的bottleneck design获得,结果为一个50-layer ResNet。增加维度时使用上述中的方案B(option B);该模型的计算量为3.8 billion个FLOPs
2)101-layer and 152-layer ResNets:作者通过使用更多3-layer blocks构建了 101-layer 和 152-layer ResNets,如Table 1所示。虽然152-layer ResNet(11.3 billion FLOPS)已经非常深,但是计算量仍然比VGG-16/19 networks(15.3/19.6 billion FLOPS)低
50/101/152-layer ResNets都比34-layer ResNets精度高很多(Table 3 and 4)。所以,随着网络层数的增加,我们并没有看到退化问题,反而在性能上获得了重大提升。这证明残差学习在一定程度上解决了退化问题,使得网络能够继续从增加的深度中获得性能提升。
VGG-Net 的泛化性能非常好,常用于图像特征的抽、目标检测候选框生成等。VGG 最大的问题就在于参数数量,VGG-19 基本上是参数量最多的卷积网络架构。这一问题也是第一次提出 Inception 结构的 GoogLeNet 所重点关注的,它没有如同 VGG-Net 那样大量使用全连接网络,因此参数量非常小。
AlexNet,它本质上就是扩展 LeNet 的深度,并应用一些 ReLU、Dropout 等技巧。AlexNet 有 5 个卷积层和 3 个最大池化层,它可分为上下两个完全相同的分支,这两个分支在第三个卷积层和全连接层上可以相互交换信息。与 Inception 同年提出的优秀网络还有 VGG-Net,它相比于 AlexNet 有更小的卷积核和更深的层级。
GoogLeNet 最大的特点就是使用了 Inception 模块,它的目的是设计一种具有优良局部拓扑结构的网络,即对输入图像并行地执行多个卷积运算或池化操作,并将所有输出结果拼接为一个非常深的特征图。因为 1*1、3*3 或 5*5 等不同的卷积运算与池化操作可以获得输入图像的不同信息,并行处理这些运算并结合所有结果将获得更好的图像表征。
Inception网络即是Inception模块的重复拼接,其中插有额外的有池化层来改变模型的宽度和高度。 所有卷积和池化操作均使用Padding=”SAME”卷积/池化方式 。
结构就是Inception,结构里的卷积stride都是1,另外为了保持特征响应图大小一致,都用了零填充。最后每个卷积层后面都立刻接了个ReLU层。在输出前有个叫concatenate的层,直译的意思是“并置”,即把4组不同类型但大小相同的特征响应图一张张并排叠起来,形成新的特征响应图。
LeNet是卷积神经网络的祖师爷LeCun在1998年提出,LeNet-5(-5表示具有5个层)是一种用于手写体字符识别的非常高效的卷积神经网络。其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用softmax分类作为输出层,LeNet-5包含七层。LeNet-5跟现有的conv->pool->ReLU的套路不同,它使用的方式是conv1->pool->conv2->pool2再接全连接层,但是不变的是,卷积层后紧接池化层的模式依旧不变。
ResNet是在Inception v3和 Inception v4 之间产生的。
ResNet主要解决的问题,就是在深度网络中的退化的问题。在深度学习的领域中,常规网络的堆叠并不会是越深效果则越好,在超过一定深度以后,准确度开始下降,并且由于训练集的准确度也在降低,证明了不是由于过拟合的原因。
在ResNet中增加一个identity mapping(恒等映射),将原始所需要学的函数H(x)转换成F(x)+x,而作者认为这两种表达的效果相同,但是优化的难度却并不相同,作者假设F(x)的优化 会比H(x)简单的多。这一想法也是源于图像处理中的残差向量编码,通过一个reformulation,将一个问题分解成多个尺度直接的残差问题,能够很好的起到优化训练的效果。
ref:
https://blog.51cto.com/u_14013325/3239223
https://blog.csdn.net/epubit17/article/details/110390339
【论文阅读】《Deep Residual Learning for Image Recognition》 - 知乎 (zhihu.com)
https://blog.csdn.net/u010986753/article/details/99191760
一文读懂LeNet、AlexNet、VGG、GoogleNet、ResNet到底是什么? - 知乎 (zhihu.com)
个人总结:
本次实验训练的时间很长,晚上写着半断电了第二天有起来继续运行,让我印象深刻;选做的问题我在知乎上找到了一篇有关论文的解读,对我有很大帮助,在实验开进程中出现了下面的错误,第一次看到这种报错,于是记录了一下然后上网查找解决办法
可以通过在程序前添加来解决
import os
os.environ[“KMP_DUPLICATE_LIB_OK”]=“TRUE”
究其原因其实是,anaconda的环境下存在其他libiomp5md.dll文件。所以直接去虚拟环境的路径下搜索这个文件,剪切到其他路径即可