目录
5.5 实践:基于ResNet18网络完成图像分类任务
5.5.1 数据处理
5.5.1.1 数据集介绍
5.5.1.2 数据读取
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数据集,网络为ResNet18模型,损失函数为交叉熵损失,优化器为Adam优化器,评价指标为准确率。
CIFAR-10数据集包含了10种不同的类别、共60,000张图像,其中每个类别的图像都是6000张,图像大小均为32×3232×32像素。CIFAR-10数据集的示例如 图5.15 所示。
cifar-10 数据集由 60000 张分辨率为 32x32 彩色图像组成,共分为 10 类,每类包含 6000 张图像,cifar-10 数据集有 50000 个训练图像和 10000 个测试图像。
最终的数据集构成为:
读取一个batch数据的代码如下所示:
import torch
from torchvision.transforms import transforms
import torchvision
from torch.utils.data import DataLoader
transformer=transforms.Compose([transforms.ToTensor(),
transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2023, 0.1994, 0.2010])])
trainset = torchvision.datasets.CIFAR10(root='./cifar10', train=True, download=True, transform=transformer)
devset=torchvision.datasets.CIFAR10(root='./cifar10',train=False,download=True,transform=transformer)
testset=torchvision.datasets.CIFAR10(root='./cifar10',train=False,download=True,transform=transformer)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
可视化观察其中的一张样本图像和对应的标签,代码如下所示:
image,label=trainset[0]
print(image.size())
image, label = np.array(image), int(label)
plt.imshow(image.transpose(1,2,0))
plt.show()
print(classes[label])
torch.Size([3, 32, 32])
frog
使用torchvision API中的Resnet18进行图像分类实验。
from torchvision.models import resnet18
resnet18_model = resnet18(pretrained=True)
Pytorch 提供 torchvision.models
接口,里面包含了一些常用用的网络结构,并提供了预训练模型,可以通过简单调用来读取网络结构和预训练模型。
什么是“
预训练模型”?什么是“迁移学习”?
迁移学习,是指利用数据、任务、或模型之间的相似性,将在旧领域学习过的模型,应用于新领域的一种学习过程。
利用迁移学习,不是从零开始学习,而是从之前解决各种问题时学到的模式开始。这样,你就可以利用以前的学习成果(例如VGG、 Inception、MobileNet),避免从零开始。我们把它看作是站在巨人的肩膀上。
预训练模型,首先,在一个原始任务上预先训练一个初始模型,然后在目标任务上使用该模型,针对目标任务的特性,对该初始模型进行精调,从而达到提高目标任务的目的。在本质上,这是一种迁移学习的方法,在自己的目标任务上使用别人训练好的模型。
在计算机视觉领域中,迁移学习通常是通过使用预训练模型来表示的。预训练模型是在大型基准数据集上训练的模型,用于解决相似的问题。由于训练这种模型的计算成本较高,因此,导入已发布的成果并使用相应的模型是比较常见的做法。
复用RunnerV3类,实例化RunnerV3类,并传入训练配置。
使用训练集和验证集进行模型训练,共训练30个epoch。
在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:
import torch.nn.functional as F
import torch.optim as opt
from Runner import RunnerV3
from metric import Accuracy
#指定运行设备
torch.cuda.set_device('cuda:0')
# 学习率大小
lr = 0.001
# 批次大小
batch_size = 64
# 加载数据
train_loader = DataLoader(trainset, batch_size=batch_size, shuffle=True)
dev_loader = DataLoader(devset, batch_size=batch_size)
test_loader = DataLoader(testset, batch_size=batch_size)
# 定义网络
model = resnet18_model
# 定义优化器,这里使用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
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")
[Train] epoch: 0/30, step: 0/23460, loss: 7.09256
[Train] epoch: 3/30, step: 3000/23460, loss: 0.68586
[Evaluate] dev score: 0.65970, dev loss: 0.99221
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.65970
[Train] epoch: 7/30, step: 6000/23460, loss: 0.57793
[Evaluate] dev score: 0.68040, dev loss: 0.93663
[Evaluate] best accuracy performence has been updated: 0.65970 --> 0.68040
[Train] epoch: 11/30, step: 9000/23460, loss: 0.90398
[Evaluate] dev score: 0.72970, dev loss: 0.80892
[Evaluate] best accuracy performence has been updated: 0.68040 --> 0.72970
[Train] epoch: 15/30, step: 12000/23460, loss: 0.54963
[Evaluate] dev score: 0.70290, dev loss: 0.89479
[Train] epoch: 19/30, step: 15000/23460, loss: 0.73634
[Evaluate] dev score: 0.73600, dev loss: 0.80622
[Evaluate] best accuracy performence has been updated: 0.72970 --> 0.73600
[Train] epoch: 23/30, step: 18000/23460, loss: 0.47213
[Evaluate] dev score: 0.73230, dev loss: 0.80564
[Train] epoch: 26/30, step: 21000/23460, loss: 0.52694
[Evaluate] dev score: 0.72850, dev loss: 0.81326
[Evaluate] dev score: 0.73140, dev loss: 0.79042
[Train] Training done!
这个训练过程进行了几乎一天的时间 ,可能是因为模型太复杂,backward过程耗时太多。
在本实验中,使用了第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.7360/0.8062
同样地,也可以使用保存好的模型,对测试集中的数据进行模型预测,观察模型效果,具体代码实现如下:
#获取测试集中的一个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
比较“使用
预训练模型”和“不使用
预训练模型”的效果。
resnet18_model = resnet18(pretrained=False)
[Train] epoch: 24/30,step: 15000/18750,loss: 0.38762
[Evaluate] dev score: 0.70030,dev loss: 0.89444
[Evaluate] best accuracy performence has been updated: 0.69470 --> 0.70030[Train] epoch: 28/30,step: 18000/18750,loss: 0.47557
[Evaluate] dev score: 0.68210,dev loss: 0.99598
[Evaluate]dev score: 0.69590,dev loss: 0.92128[Train] Training done!
可以看出不使用预训练模型的准确率和误差都不如使用预训练模型的效果。
预训练模型优点:
1、开源模型多,可以直接用于目标检测
2、可以快速地得到最终模型,需要的训练数据少
缺点:
1、预训练模型大、参数多、模型结构灵活性差、难以改变网络结构,计算量大,限制应用场景
2、分类和检测任务损失函数和类别分布不同,优化空间存在差异
3、尽管微调可以减少不同目标类别分布差异性,差异太大时,微调效果不明显
看一下模型的参数量
# 加载最优模型
runner.load_model('best_model.pdparams')
import torchsummary
torchsummary.summary(runner.model.to('cuda'), (3, 32, 32))
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 16, 16] 9,408
BatchNorm2d-2 [-1, 64, 16, 16] 128
ReLU-3 [-1, 64, 16, 16] 0
MaxPool2d-4 [-1, 64, 8, 8] 0
Conv2d-5 [-1, 64, 8, 8] 36,864
BatchNorm2d-6 [-1, 64, 8, 8] 128
ReLU-7 [-1, 64, 8, 8] 0
Conv2d-8 [-1, 64, 8, 8] 36,864
BatchNorm2d-9 [-1, 64, 8, 8] 128
ReLU-10 [-1, 64, 8, 8] 0
BasicBlock-11 [-1, 64, 8, 8] 0
Conv2d-12 [-1, 64, 8, 8] 36,864
BatchNorm2d-13 [-1, 64, 8, 8] 128
ReLU-14 [-1, 64, 8, 8] 0
Conv2d-15 [-1, 64, 8, 8] 36,864
BatchNorm2d-16 [-1, 64, 8, 8] 128
ReLU-17 [-1, 64, 8, 8] 0
BasicBlock-18 [-1, 64, 8, 8] 0
Conv2d-19 [-1, 128, 4, 4] 73,728
BatchNorm2d-20 [-1, 128, 4, 4] 256
ReLU-21 [-1, 128, 4, 4] 0
Conv2d-22 [-1, 128, 4, 4] 147,456
BatchNorm2d-23 [-1, 128, 4, 4] 256
Conv2d-24 [-1, 128, 4, 4] 8,192
BatchNorm2d-25 [-1, 128, 4, 4] 256
ReLU-26 [-1, 128, 4, 4] 0
BasicBlock-27 [-1, 128, 4, 4] 0
Conv2d-28 [-1, 128, 4, 4] 147,456
BatchNorm2d-29 [-1, 128, 4, 4] 256
ReLU-30 [-1, 128, 4, 4] 0
Conv2d-31 [-1, 128, 4, 4] 147,456
BatchNorm2d-32 [-1, 128, 4, 4] 256
ReLU-33 [-1, 128, 4, 4] 0
BasicBlock-34 [-1, 128, 4, 4] 0
Conv2d-35 [-1, 256, 2, 2] 294,912
BatchNorm2d-36 [-1, 256, 2, 2] 512
ReLU-37 [-1, 256, 2, 2] 0
Conv2d-38 [-1, 256, 2, 2] 589,824
BatchNorm2d-39 [-1, 256, 2, 2] 512
Conv2d-40 [-1, 256, 2, 2] 32,768
BatchNorm2d-41 [-1, 256, 2, 2] 512
ReLU-42 [-1, 256, 2, 2] 0
BasicBlock-43 [-1, 256, 2, 2] 0
Conv2d-44 [-1, 256, 2, 2] 589,824
BatchNorm2d-45 [-1, 256, 2, 2] 512
ReLU-46 [-1, 256, 2, 2] 0
Conv2d-47 [-1, 256, 2, 2] 589,824
BatchNorm2d-48 [-1, 256, 2, 2] 512
ReLU-49 [-1, 256, 2, 2] 0
BasicBlock-50 [-1, 256, 2, 2] 0
Conv2d-51 [-1, 512, 1, 1] 1,179,648
BatchNorm2d-52 [-1, 512, 1, 1] 1,024
ReLU-53 [-1, 512, 1, 1] 0
Conv2d-54 [-1, 512, 1, 1] 2,359,296
BatchNorm2d-55 [-1, 512, 1, 1] 1,024
Conv2d-56 [-1, 512, 1, 1] 131,072
BatchNorm2d-57 [-1, 512, 1, 1] 1,024
ReLU-58 [-1, 512, 1, 1] 0
BasicBlock-59 [-1, 512, 1, 1] 0
Conv2d-60 [-1, 512, 1, 1] 2,359,296
BatchNorm2d-61 [-1, 512, 1, 1] 1,024
ReLU-62 [-1, 512, 1, 1] 0
Conv2d-63 [-1, 512, 1, 1] 2,359,296
BatchNorm2d-64 [-1, 512, 1, 1] 1,024
ReLU-65 [-1, 512, 1, 1] 0
BasicBlock-66 [-1, 512, 1, 1] 0
AdaptiveAvgPool2d-67 [-1, 512, 1, 1] 0
Linear-68 [-1, 1000] 513,000
================================================================
Total params: 11,689,512
Trainable params: 11,689,512
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 1.29
Params size (MB): 44.59
Estimated Total Size (MB): 45.90
----------------------------------------------------------------
再看一下计算量
from torchstat import stat
stat(runner.model, (3, 32, 32))
对比上个实验自定义的resnet18,
Total params: 11,175,434
-------------------------------------------------------------------------------------------------------------------------
Total memory: 0.47MB
Total MAdd: 71.04MMAdd
Total Flops: 35.56MFlops
Total MemR+W: 43.58MB
预训练模型的参数和计算量仅仅大了一点,所以我觉得训练慢不是模型的问题,那应该是cifar10数据集太大的问题。
表格中提到了五种深度的ResNet,分别是18,34,50,101,152。表格的最左侧是说明无论是深度为多少的ResNet都将网络分成了五部分。分别是:conv1,conv2_x,conv3_x,conv4_x,conv5_x。而仔细观察图,可以得出以下结论。
从50-layer之后,conv2——conv5都是采取三层块结构以减小计算量和参数数量
说明50-layer以后开始采用BottleBlock
从50-layer之后,层数的加深仅仅体现在conv4_x这一层中,也就是output size为14×14的图像
LeNet
Lenet也称Lenet-5,共5个隐藏层(不考虑磁化层),网络结构为:
Conv(5*5,6,1)+Conv(5*5,16)+FC(120)+FC(84)+FC(10)
AlexNet
提出背景:解决Lenet识别大尺寸图片进行的效果不尽人意的问题
与LeNet相比,AlexNet具有更深的网络结构,共8个隐藏层,包含5层卷积和3层全连接,网络结构为:
Conv(11*11,96,2)+Conv(5*5,256,1)+Conv(3*3,384,1)+Conv(3*3,384,1)+Conv(3*3,256,1)+FC(4096)+FC(4096)+FC(1000)
同时使用了如下方法改进模型的训练过程:
数据增广:深度学习中常用的一种处理方式,通过对训练随机加一些变化,比如平移、缩放、裁剪、旋转、翻转或者增减亮度等,产生一系列跟原始图片相似但又不完全相同的样本,从而扩大训练数据集。通过这种方式,可以随机改变训练样本,避免模型过度依赖于某些属性,能从一定程度上抑制过拟合。
使用Dropout抑制过拟合
使用ReLU激活函数减少梯度消失现象(梯度消失和梯度爆炸):在AlexNet之前,神经网络一般都使用sigmoid或tanh作为激活函数,这类函数在自变量非常大或者非常小时,函数输出基本不变,称之为饱和函数。为了提高训练速度,AlexNet使用了修正线性函数ReLU,它是一种非饱和函数,与 sigmoid 和tanh 函数相比,ReLU分片的线性结构实现了非线性结构的表达能力,梯度消失现象相对较弱,有助于训练更深层的网络。
使用GPU训练。与CPU不同的是,GPU转为执行复杂的数学和几何计算而设计,AlexNet使用了2个GPU来提升速度,分别放置一半卷积核。
局部响应归一化。AlexNet使用局部响应归一化技巧,将ImageNet上的top-1与top-5错误率分别减少了1.4%和1.2%。
重叠池化层。与不重叠池化层相比,重叠池化层有助于缓解过拟合,使得AlexNet的top-1和top-5错误率分别降低了0.4%和0.3%。
VggNet
提出背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。
在论文中有VGG-11,VGG-13,VGG-16,VGG-19的实验比较,VGG-16的效果最佳,这里给出网络结构。
VGG11:Conv(3*3,64,1)*1+Conv(3*3,128,1)*1+Conv(3*3,256,1)*2+Conv(3*3,512,1)*2+Conv(3*3,512,1)*2+FC(4096)+FC(4096)+FC(1000)
VGG13:Conv(3*3,64,1)*2+Conv(3*3,128,1)*2+Conv(3*3,256,1)*2+Conv(3*3,512,1)*2+Conv(3*3,512,1)*2+FC(4096)+FC(4096)+FC(1000)
VGG16:Conv(3*3,64,1)*2+Conv(3*3,128,1)*2+Conv(3*3,256,1)*3+Conv(3*3,512,1)*3+Conv(3*3,512,1)*3+FC(4096)+FC(4096)+FC(1000)
VGG19:Conv(3*3,64,1)*2+Conv(3*3,128,1)*2+Conv(3*3,256,1)*4+Conv(3*3,512,1)*4+Conv(3*3,512,1)*4+FC(4096)+FC(4096)+FC(1000)
vggnet严格使用3*3小尺寸卷积和池化层构造深度CNN,取得较好的效果。小卷积能减少参数,方便堆叠卷积层来增加深度(加深了网络,减少了卷积)。即vggnet=更深的Alex net+conv(3*3)
googlenet
背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。
googlenet设计了inception结构来降低通道数,减少计算复杂度,其中inception结构包括以下几种
googlenet:Alex net+inception=conv*1+inception*9+FC*1
Resnet
提出背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。
Resnet从避免梯度消失或爆炸的角度,使用残差连接结构使网络可以更深,共5个版本
使用思维导图全面总结CNN
这次实验使用torchvision.models
里的resnet模型训练了cifar10数据集,感觉这个预训练的模型参数量很大,导致训练时间耗费很多。至此卷积神经网络的学习就告一段落,感觉对于resnet等经典网络的理解还不够深,等到期末复习的时候再好好学一下。