用PyTorch训练CIFAR-10数据集训练集精度达100%/AlexNet

文章目录

  • 0 CIFAR-10数据集简介
  • 1 下载CIFAR-10数据集
    • 1.1 查看数据集的组成
  • 2 装载数据集并可视化
    • 2.1 装载cifar10数据集
    • 2.2 可视化数据集
  • 3 构建AlexNet网络
    • 3.1 AlexNet简介
    • 3.2AlexNet的主要贡献
    • 3.3 用torch.nn搭建AlexNet
    • 3.4 初始化网络参数
  • 4 选择优化器
  • 5 开始训练+测试并保存模型
    • 5.1保存模型
  • 6 使用Tensorboard绘制loss曲线

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个随机图像:
    用PyTorch训练CIFAR-10数据集训练集精度达100%/AlexNet_第1张图片
    与MNIST 数据集中目比, CIFAR-10 有以下不同点
  1. CIFAR-10 是3 通道的彩色RGB 图像,而MNIST 是单通道的灰度图像。
  2. CIFAR-10 的图片尺寸为32 × 32 , 而MNIST 的图片尺寸为28 × 28 ,比MNIST 稍大。
  3. 相比于手写字符, 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()])

用PyTorch训练CIFAR-10数据集训练集精度达100%/AlexNet_第2张图片

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+2padding[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+2padding[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曲线

  1. writer = SummaryWriter(log_dir=‘logs’)在工程目录下生成logs文件
  2. writer.add_scalar(‘Train_loss’, loss, (epoch)) 每训练完一次epoch,记录一次loss的值
  3. writer.close() 训练结束后,关闭
  4. 在terminal内输入tensorboard --logdir=D:\Study\Collection\Tensorboard-pytorch\logs 再打开返回的网址即可
    用PyTorch训练CIFAR-10数据集训练集精度达100%/AlexNet_第3张图片
    用PyTorch训练CIFAR-10数据集训练集精度达100%/AlexNet_第4张图片
    使用改进的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

你可能感兴趣的:(深度学习,pytorch,神经网络,深度学习)