前言
本模型最终能得到约 83% 的分类测试精度,占用约1.7g显存大小。
使用的是经典CIFAR-10数据集,里面包含了10个种类的图片:
1. 导入Pytorch以及相关包
本文使用pytorch=1.5.1+cuda10.1
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
import time,os
torch.nn
和torch.nn.functional
中包含了很多高度集成的函数。
torchvision
中提供了主流的公开数据集,如MNIST、CIFAR-10、CIFAR-100等。MNIST(手写黑白图像数据集)比较小并且简单,并不能很好地体现现在模型的性能。使用CIFAR-10包含了共十类彩色图像数据集),可以较好地测试模型的能力。CIFAR-100更加困难一些。
torchvision.transforms
可以对图像进行数据增强,并使用transforms.ToTensr()
将数据转换成训练所需要的tensor。
torch.optim
中包含了pytorch中可使用的各种optimizer,如Adam、SGD等等。
2. 下载、加载数据集并进行增强
transform = transforms.Compose([
transforms.transforms.RandomRotation(0.5),
transforms.RandomGrayscale(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
train_dataset = torchvision.datasets.CIFAR10(root='./datasets', train=True,
download=False, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=50,
shuffle=True, num_workers=0)
test_dataset = torchvision.datasets.CIFAR10(root='./datasets', train=False,
download=False, transform=transform_test)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=25,
shuffle=False, num_workers=0)
使用Compose()
整合地对数据进行增强/预处理。其中:
RandomRotation(0.5)()
,对图像进行角度旋转,0.5代表在(-0.5, +0.5)范围内进行旋转,可以自行设置RandomGrayscale()
,随机对图像转换成灰度图,默认为50%ToTensor()
,将图像从numpy的array转化为pytorch训练需要的tensorNormalize()
,将tensor集合转换成指定的mean和std,这里都取0.5。其实,0.5是常用标准,要想提升模型能力,对于不同的数据集,应该使用不同具体的的mean和std,具体如何计算不同数据集的mean和std,可能以后会写。
一般,loader函数中的num_wokers
设置成2,具体作用可以自行搜索,这里设置成0是因为我的电脑GPU在多线程载入数据时会出现某些错误,当程序报错的时候会无法杀死旧的进程,设置成0就没事了。
数据增强/预处理,能够通过改变数据集的多样性,提升模型的鲁棒性和精度,几乎所有的训练都要对数据进行增强,是模型训练中必不可少的一部分。关于更多预处理的简单介绍请看。
使用torchvision.datasets.CIFAR10()
下载数据集,需要设置download=True
,root=
为下载位置,下载成功后只要不改变下载位置不会反复下载,所以建议都下载在统一文件夹中,便于管理并节省空间;在训练集中,需要设置train=True
,表示对训练集进行训练,测试集则不需要;transform
代表使用之前定义好的transform方法。
可以通过以下代码对数据集某个数据进行可视化:
import matplotlib.pyplot as plt
fig = plt.figure()
plt.imshow(train_dataset.data[0]) # 第一个数据
plt.show()
整个训练集为5W个32*32*3的彩色图,其中一个示例:
3. 模型设计
深度学习网络框架由AlexNet建立,包含了卷积层、激活函数、池化层和全连接层;之后的VGG通过将7*7卷积替换成两个3*3卷积,减少了参数,并且由于可以额外增加一个激活函数,增加了模型的非线性程度,性能得到较大提升;而ResNet通过residual block结构,将深度一词发挥的非常彻底,也极大地提升了模型的性能。
本文基于VGG设计网络结构,由于苦逼1050GPU只有2G大,所以为了提高性能,只能通过增加多个1x1卷积减少模型参数:
class Model(nn.Module):
def __init__(self,num_classes=10): #
super(Model,self).__init__()
layers = [] # 将层数添加到此列表中
in_dim = 3 # 输入3通道的彩色图片
out_dim = 64
for i in range(1, 6): # 共5个block;
layers += [nn.Conv2d(in_dim, out_dim, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(out_dim), nn.ReLU(inplace=True),
# 使用1x1卷积减少模型参数
nn.Conv2d(out_dim, out_dim, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(out_dim), nn.ReLU(inplace=True),
nn.Conv2d(out_dim, out_dim, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(out_dim),nn.ReLU(inplace=True),
]
# 使用步长为2的卷积代替maxpooling
if i==2 or i==3 or i==4:
layers += [nn.Conv2d(out_dim, out_dim,
kernel_size=3, stride=2, padding=1)]
# 也可以使用maxpooling
# layers+= [nn.Maxpool2d(2,2,padding=1)]
in_dim = out_dim # 交换
if i != 4: # 通道翻倍
out_dim *= 2
self.features = nn.Sequential(*layers)
# 布置全连接层进行分类
self.classifier = nn.Sequential(nn.Linear(8192, 1024),nn.Dropout2d(),
nn.Linear(1024,1024),nn.Dropout2d(),
nn.Linear(1024,num_classes))
# 前向传播
def forward(self,x):
x = self.features(x)
# 将BxCxWxH的数据类型=>Bx(C*W*H)后进入全连接层
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
继承nn.Module
类后开始初始化__init__()
,并前向传播forward()
- 初始化:使用
self.features
和self.classifier
保存网络结构,其中self.features
保存在layers
中,包含了网络中的卷积层、池化层、BN层、激活层等等 - 前向传播:输入x传进网络中,然后通过展平,将
self.features
的输出通道与self.classifier
的输入通道保持一致,计算并得到输出。现在还没进行反向传播
这里全连接层的参数8192
是如何来的呢?在工程上一般不需要直接设定好,直接搭建好模型,跑一遍。会报出BUG,BUG会显示无法进行矩阵相乘。这时只需要将报出的数据填入就好,具体我就不展示了,大家可以自己试一下。
通过:
model = Model()
model
打印出所构建的五层(除去全连接层)网络结构:
Model(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1)