文章目录
0 CIFAR-10数据集简介
官网链接:The CIFAR-10 dataset
- CIFAR-10和ClFAR-100是8000万个微型图像数据集的被标注过后的子集。它们由Alex Krizhevsky,Vinod Nair和Geoffrey Hinton收集。
- CIFAR-10数据集包含10个类别的60000个32x32彩色图像,每个类别有6000张图像。有50000张训练图像和10000张测试图像。
- 数据集分为五个训练批次和一个测试批次,每个批次具有10000张图像。测试集包含从每个类别中1000张随机选择的图像。剩余的图像按照随机顺序构成5个批次的训练集,每个批次中各类图像的数量不相同,但总训练集中每一类都正好有5000张图片
- 数据集中的class(类),以及每个class的10个随机图像:
与MNIST 数据集中目比, CIFAR-10 有以下不同点
- CIFAR-10 是3 通道的彩色RGB 图像,而MNIST 是单通道的灰度图像。
- CIFAR-10 的图片尺寸为32 × 32 , 而MNIST 的图片尺寸为28 × 28 ,比MNIST 稍大。
- 相比于手写字符, CIFAR-10 含有的是现实世界中真实的物体,不仅噪声很大,而且物体的比例、特征都不尽相同,这为识别带来很大困难。直接的线性模型如Softmax 在CIFAR-10 上表现得很差
1 下载CIFAR-10数据集
# download cifar-10 datasets
train_set = torchvision.datasets.CIFAR10('./data',train=True,transform=transform,download=True)
test_set = torchvision.datasets.CIFAR10('./data',train=False,transform=transform,download=True)
参数说明:
- root(string):MNIST数据存放的位置’./data/’
- train(bool,optional):如果为True则作为训练集,否则作为测试集
- download(bool,optional):如果为True,从网上把这个数据集下载下来并放到root指定的位置,如果下过了就不会重复下载
- transform(callable,oprtional):接受一个PIL图像并返回转换后的版本,对数据集中的图片进行相应的操作
# 没有进行ToTensor()的数据集
train_set
----------------------OUT---------------------
Dataset CIFAR10
Number of datapoints: 50000
Root location: ./data
Split: Train
train_set[1]
----------------------OUT---------------------
(<PIL.Image.Image image mode=RGB size=32x32 at 0x122A0F150>, 9)
需要注意的是,我们下载下来的CIFAR-10数据集的格式是PIL文件,而送入CNN中进行训练的都是Tensor格式的数据,所以先要对数据集中的图片进行transform成Tensor类型的数据
train_set
----------------------OUT---------------------
Dataset CIFAR10
Number of datapoints: 50000
Root location: ./data
Split: Train
StandardTransform
Transform: Compose(
ToTensor()
)
train_set[1]
----------------------OUT---------------------
(tensor([[[0.6039, 0.4941, 0.4118, ..., 0.3569, 0.3412, 0.3098],
[0.5490, 0.5686, 0.4902, ..., 0.3765, 0.3020, 0.2784],
[0.5490, 0.5451, 0.4510, ..., 0.3098, 0.2667, 0.2627],
...,
[[0.7333, 0.5333, 0.3725, ..., 0.2784, 0.2784, 0.2745],
[0.6627, 0.6039, 0.4627, ..., 0.3059, 0.2431, 0.2392],
[0.6431, 0.5843, 0.4392, ..., 0.2510, 0.2157, 0.2157],
...,
[0.6510, 0.6275, 0.6667, ..., 0.1412, 0.2235, 0.3569],
[0.5020, 0.5098, 0.5569, ..., 0.3765, 0.4706, 0.5137],
[0.4706, 0.4784, 0.5216, ..., 0.5451, 0.5569, 0.5647]]]),
9)
1.1 查看数据集的组成
import pickle
import os
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
file = os.getcwd()+'data/cifar-10-batches-py/data_batch_1'
data_batch_1_dict = unpickle(file)
每个batch文件包含一个字典,每个字典包含有:
- Data:一个10000*3072的numpy数组,数据类型是无符号整形uint8。这个数组的每一行存储了32*32大小的彩色图像(32*32*3通道=3072)。前1024个数是red通道,然后分别是green,blue。另外,图像是以行的顺序存储的,也就是说前32个数就是这幅图的像素矩阵的第一行
- labels:一个范围在0-9的含有10000个数的列表(一维的数组)第i个数就是第i个图像的类标
数据集除了6个batch之外,还有一个文件batches.meta。它包含一个python字典对象,内容有:
一个包含10个元素的列表,每一个描述了labels array中每个数字对应类标的名字。比如:label_names[0] == “airplane”, label_names[1] == “automobile”
2 装载数据集并可视化
2.1 装载cifar10数据集
torch.utils.data中的Dataloader(注意这个D是大写)模块可以很方便的将dataset装载起来,相当于一个数据读取器,使其可以iterable方便对数据集进行处理
- numworkers (int, optional) : 有多少个子进程用于数据加载。0表示将在主进程中加载数据。(Default:0) 只是节省了我们从磁盘中读取批处理数据的时间。
- pin_memory(bool,optional):设置pin_memory=True,则意味着生成的Tensor数据最开始是属于内存中的锁页内存,这样将内存的Tensor转义到GPU的显存就会更快一些。视内存大小情况设置(Default:False)
# 创建训练/测试加载器,
# trainset/testset -- 数据集
# batch_size -- 不解释
# shuffle -- 是否打乱顺序
# num_workers -- 子进程数
dataloaders = { x:torch.utils.data.DataLoader(datasets[x],batch_size=BATCH_SIZE,
shuffle=True,num_workers=NUM_WORKERS,pin_memory=True)
for x in ['train_set','test_set']
}
2.2 可视化数据集
# 显示单个图片
def imshow(inp,title=None):
inp = inp.numpy().transpose(1,2,0)
plt.imshow(inp)
if title is not None:
plt.title(title)
# plt.pause(0.1)
# 显示一个batch的图片
def visualize_batch():
(inputs,classes) = next(iter(dataloaders['train_set'])) # 读取一个batch的数据
for num in range(inputs.shape[0]):
plt.subplot(4,8,num+1)
imshow(inputs[num],class_names[classes[num].item()])
3 构建AlexNet网络
3.1 AlexNet简介
- 由于受到计算机性能的影响,虽然LeNet在图像分类中取得了较好的成绩,但是并没有引起很多的关注。 直到2012年,Alex Krizhevsky等人提出的AlexNet网络在ImageNet大赛上以远超第二名的成绩夺冠,卷积神经网络乃至深度学习重新引起了广泛的关注。
- AlexNet模型来源于论文-ImageNet Classification with Deep Convolutional Neural Networks
3.2AlexNet的主要贡献
- 使用ReLu函数来增加模型的非线性能力
- 使用Droput训练期间选择性的忽略一些神经元,来减小模型的过拟合
- 局部响应归一化层(LRN):提高精度
- 双GPU并行运行,提高训练速度
3.3 用torch.nn搭建AlexNet
-
Conv2d
H o u t = ⌊ H i n + 2 ∗ p a d d i n g [ 0 ] − d i l a t i o n [ 0 ] × ( k e r n e l s i z e [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1 ⌋ H_{out} =⌊\frac{H_{in}+2∗padding[0]−dilation[0]×(kernel_{size}[0]−1)−1}{ stride[0]}+1⌋ Hout=⌊stride[0]Hin+2∗padding[0]−dilation[0]×(kernelsize[0]−1)−1+1⌋ -
Maxpool2d
H o u t = ⌊ H i n + 2 ∗ p a d d i n g [ 0 ] − d i l a t i o n [ 0 ] × ( k e r n e l s i z e [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1 ⌋ H_{out} =⌊\frac{H_{in}+2∗padding[0]−dilation[0]×(kernel_{size}[0]−1)−1}{ stride[0]}+1⌋ Hout=⌊stride[0]Hin+2∗padding[0]−dilation[0]×(kernelsize[0]−1)−1+1⌋
import torch
from torch import nn
import torchvision
from torchvision import transforms
import torch.nn.functional as F
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.Conv = nn.Sequential(
# IN : 3*32*32
nn.Conv2d(in_channels=3,out_channels=96,kernel_size=5,stride=2,padding=2), # 论文中kernel_size = 11,stride = 4,padding = 2
nn.ReLU(),
# IN : 96*16*16
nn.MaxPool2d(kernel_size=2,stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 96*8*8
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1, padding=2),
nn.ReLU(),
# IN :256*8*8
nn.MaxPool2d(kernel_size=2,stride=2), # 论文中为kernel_size = 3,stride = 2
# IN : 256*4*4
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
# IN : 384*4*4
nn.MaxPool2d(kernel_size=2, stride=2), # 论文中为kernel_size = 3,stride = 2
# OUT : 384*2*2
)
self.linear = nn.Sequential(
nn.Linear(in_features=384 * 2 * 2, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(),
nn.Linear(in_features=4096, out_features=10),
)
def forward(self,x):
x = self.Conv(x)
x = x.view(-1, 384 * 2 * 2)
x = self.linear(x)
return x
3.4 初始化网络参数
# 显示网络参数量
def Init_net():
model = AlexNet()
data_input = Variable(torch.randn(8,3,32,32))
print(data_input.size())
model(data_input)
print(summary(model,(3,32,32)))
torch.Size([8, 3, 32, 32])
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 96, 16, 16] 7,296
ReLU-2 [-1, 96, 16, 16] 0
MaxPool2d-3 [-1, 96, 8, 8] 0
Conv2d-4 [-1, 256, 8, 8] 614,656
ReLU-5 [-1, 256, 8, 8] 0
MaxPool2d-6 [-1, 256, 4, 4] 0
Conv2d-7 [-1, 384, 4, 4] 885,120
ReLU-8 [-1, 384, 4, 4] 0
Conv2d-9 [-1, 384, 4, 4] 1,327,488
ReLU-10 [-1, 384, 4, 4] 0
Conv2d-11 [-1, 384, 4, 4] 1,327,488
ReLU-12 [-1, 384, 4, 4] 0
MaxPool2d-13 [-1, 384, 2, 2] 0
Linear-14 [-1, 4096] 6,295,552
ReLU-15 [-1, 4096] 0
Linear-16 [-1, 4096] 16,781,312
ReLU-17 [-1, 4096] 0
Linear-18 [-1, 10] 40,970
================================================================
Total params: 27,279,882
Trainable params: 27,279,882
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 1.12
Params size (MB): 104.06
Estimated Total Size (MB): 105.20
----------------------------------------------------------------
None
4 选择优化器
cost_fun = nn.CrossEntropyLoss() # 选择交叉熵作为损失函数
optimizer = torch.optim.SGD(net.parameters(),lr=1e-3) # 选择SGD作为优化器
5 开始训练+测试并保存模型
参数说明:
- batch_idx:第几组送入网络训练中的数据,计算方法:训练集总数据个数/batch_size,向上取整
- input:送入网络中训练的tensor
- labels:对应数据的label
def train_model(model,criterion,optimizer,scheduler,num_epochs=25):
since = time.time()
best_acc = 0.0
for epoch in range(num_epochs):
print('Epoc{}/{}'.format(epoch,num_epochs-1))
print('-'*10)
# epoch一次完成后切换到测试phase
for phase in ['train','test']:
if phase == 'train':
model.train() # 切换到train mode
else:
model.eval()
running_loss = 0.0
running_corrects = 0
# Iterate over data
for inputs,labels in tqdm(dataloaders[phase]):
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad() # 梯度清零
# forward
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_,preds = torch.max(outputs,1)
loss = criterion(outputs,labels)
# backward + optimize only if in trainning phase
if phase == 'train':
loss.backward()
optimizer.step()
# statistics
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds==labels.data)
if phase == 'train':
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase,epoch_loss,epoch_acc))
lr = next(iter(optimizer.param_groups))['lr']
print(lr)
writer.add_scalar('Train/Loss',epoch_loss,epoch)
writer.add_scalar('Train/Acc',epoch_acc,epoch)
else:
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase,epoch_loss,epoch_acc))
writer.add_scalar('Test/Loss',epoch_loss,epoch)
writer.add_scalar('Test/Acc',epoch_acc,epoch)
if epoch_acc > best_acc:
best_acc = epoch_acc
lr_list.append(optimizer.state_dict()['param_groups'][0]['lr'])
lr_scheduler.step() # 更新
print()
writer.close()
time_elapsed = time.time() - since
print('Trainning complete in {:.0f}m {:.0f}s'.format(
time_elapsed // 60 , time_elapsed % 60
))
print('Best test Acc: {:4f}'.format(best_acc))
return model
5.1保存模型
# 只保存网络中训练好的权重文件
def save_state():
print('===> Saving models...')
state = {
'state': net.state_dict(),
'epoch': epoch # 将epoch一并保存
}
if not os.path.isdir('checkpoint'): # 如果没有这个目录就创建一个
os.mkdir('./checkpoint')
torch.save(state, path_model + 'Epoch:' + str(epoch) + ' Loss:' +
str(train_loss[-1].item()) + '.pth')
6 使用Tensorboard绘制loss曲线
- writer = SummaryWriter(log_dir=‘logs’)在工程目录下生成logs文件
- writer.add_scalar(‘Train_loss’, loss, (epoch)) 每训练完一次epoch,记录一次loss的值
- writer.close() 训练结束后,关闭
- 在terminal内输入tensorboard --logdir=D:\Study\Collection\Tensorboard-pytorch\logs 再打开返回的网址即可
使用改进的alexnet+adam lr=1e-4+dropout0.5+weight_decay=5e-4 Best test Acc: 0.675800 test Loss: 2.2339
参考文章:
https://blog.csdn.net/qq_41185868/article/details/82793025
https://blog.csdn.net/qq_30129009/article/details/98772599?ops_request_misc=&request_id=&biz_id=102&utm_term=alexnet%20%20torch&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-98772599.pc_search_result_hbase_insert
实验记录:
使用lenet+adam lr=1e-4 结果最好的为54%, loss=1.3284
使用alexnet+adam lr=1e-4 结果最好是69.2%,test_loss=5.7270,train_loss=0,过拟合了
使用alexnet+adam lr=1e-4+dropout0.5 69.6% test_loss=4.0592
使用alexnet+adam lr=1e-4+dropout0.5+weight_decay=5e-4 Best test Acc: 0.675800 test Loss: 2.2339