Pytorch CIFAR-10分类(LeNet5)

CSDN只作为查看网络结构,具体代码和结果展示请移步GitHub

1.数据读取

CIFAR-10 是由 Hinton 的学生 Alex Krizhevsky 和 Ilya Sutskever 整理的一个用于识别普适物体的小型数据集。一共包含 10 个类别的 RGB 彩色图 片:飞机( arplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。图片的尺寸为 32×32 ,数据集中一共有 50000 张训练圄片和 10000 张测试图片。

与 MNIST 数据集中目比, CIFAR-10 具有以下不同点:

• CIFAR-10 是 3 通道的彩色 RGB 图像,而 MNIST 是灰度图像。
• CIFAR-10 的图片尺寸为 32×32, 而 MNIST 的图片尺寸为 28×28,比 MNIST 稍大。
• 相比于手写字符, CIFAR-10 含有的是现实世界中真实的物体,不仅噪声很大,而且物体的比例、 特征都不尽相同,这为识别带来很大困难。

首先使用torchvision加载和归一化我们的训练数据和测试数据。

  1. torchvision这个东西,实现了常用的一些深度学习的相关的图像数据的加载功能,比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets模块中。
  2. 同时,也封装了一些处理数据的方法。保存在torchvision.transforms模块中
  3. 还封装了一些模型和工具封装在相应模型中,比如torchvision.models当中就包含了AlexNet,VGG,ResNet,SqueezeNet等模型。

由于torchvision的datasets的输出是[0,1]的PILImage,所以我们先先归一化为[-1,1]的Tensor

首先定义了一个变换transform,利用的是上面提到的transforms模块中的Compose( )把多个变换组合在一起,可以看到这里面组合了ToTensor和Normalize这两个变换

transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))前面的(0.5,0.5,0.5) 是 R G B 三个通道上的均值, 后面(0.5, 0.5, 0.5)是三个通道的标准差,注意通道顺序是 R G B ,用过opencv的同学应该知道openCV读出来的图像是 BRG顺序。这两个tuple数据是用来对RGB 图像做归一化的,如其名称 Normalize 所示这里都取0.5只是一个近似的操作,实际上其均值和方差并不是这么多,但是就这个示例而言 影响可不计。精确值是通过分别计算R,G,B三个通道的数据算出来的。

import torch
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms

transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 

# datasets.CIFAR10( )也是封装好了的,就在我前面提到的torchvision.datasets块中
trainset = datasets.CIFAR10(root='D:/CIFAR-10', train=True,download=True, transform=transform)

trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,shuffle=True, num_workers=2)

# 对于测试集的操作和训练集一样,我就不赘述了
testset = torchvision.datasets.CIFAR10(root='D:/CIFAR-10', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=128,shuffle=False, num_workers=2)
    # 类别信息也是需要我们给定的
classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2.定义网络(LeNet5)

手写字体识别模型LeNet5诞生于1994年,是最早的卷积神经网络之一。LeNet5通过巧妙的设计,利用卷积、参数共享、池化等操作提取特征,避免了大量的计算成本,最后再使用全连接神经网络进行分类识别,这个网络也是最近大量神经网络架构的起点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBSpCaRR-1587021133464)(attachment:image.png)]

LeNet-5 一些性质:

  1. 如果输入层不算神经网络的层数,那么 LeNet-5 是一个 7 层的网络。(有些地方也可能把 卷积和池化 当作一个 layer)(LeNet-5 名字中的“5”也可以理解为整个网络中含可训练参数的层数为 5。)
  2. LeNet-5 大约有 60,000 个参数。
  3. 随着网络越来越深,图像的高度和宽度在缩小,与此同时,图像的 channel 数量一直在增加。
  4. 现在常用的 LeNet-5 结构和 Yann LeCun 教授在 1998 年论文中提出的结构在某些地方有区别,比如激活函数的使用,现在一般使用 ReLU 作为激活函数,输出层一般选择 softmax。
import torch
import torch.nn as nn

#若能使用cuda,则使用cuda
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#定义网络
class LeNet5(nn.Module):# nn.Module是所有神经网络的基类,我们自己定义任何神经网络,都要继承nn.Module
    def __init__(self):
        super(LeNet5,self).__init__()
        self.conv1 = nn.Sequential(
            # 卷积层1,3通道输入,6个卷积核,核大小5*5
            # 经过该层图像大小变为32-5+1,28*28
            nn.Conv2d(in_channels=3,out_channels=6,kernel_size=5,stride=1, padding=0),
            #激活函数
            nn.ReLU(),
            # 经2*2最大池化,图像变为14*14
            nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
        )
        self.conv2 = nn.Sequential(
            # 卷积层2,6输入通道,16个卷积核,核大小5*5
            # 经过该层图像变为14-5+1,10*10
            nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,stride=1, padding=0),
            nn.ReLU(),
            # 经2*2最大池化,图像变为5*5
            nn.MaxPool2d(kernel_size=2,stride=2,padding=0),
        )
        self.fc = nn.Sequential(
            # 接着三个全连接层
            nn.Linear(16*5*5,120),
            nn.ReLU(),
            nn.Linear(120,84),
            nn.ReLU(),
            nn.Linear(84,10),
        )
        
        # 定义前向传播过程,输入为
    def forward(self,x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
            
        x = x.view(x.size()[0],-1)
        x = self.fc(x)
        return x
            
net = LeNet5().cuda()
print("LeNet5 out: ", net)

3. 定义损失函数和优化器

pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier

损失函数是封装在神经网络工具箱nn中的,包含很多损失函数

import torch.optim as optim
#用到了神经网络工具箱 nn 中的交叉熵损失函数
criterion = nn.CrossEntropyLoss() 
# 使用SGD(随机梯度下降)优化,学习率为0.001,动量为0.9
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)  

4.训练

from torch.autograd import Variable

plotloss = []
plotauc = []
for epoch in range(50):  # 指定训练一共要循环几个epoch
    net.train()
    sum_loss = 0.0
    correct = 0.0
    total = 0.0
    
    # 这里我们遇到了第一步中出现的trailoader,代码传入数据,enumerate是python的内置函数,既获得索引也获得数据
    for i, (images,labels) in enumerate(trainloader):
        # data是从enumerate返回的data,包含数据和标签信息,分别赋值给inputs和labels
        # data的结构是:[4x3x32x32的张量,长度4的张量],4是batch_size的数值
 
        # 把input数据从tensor转为variable,variable才拥有梯度grad,输入模型训练都要转成Variable        
        if torch.cuda.is_available():
            images=Variable(images).cuda()
            labels=Variable(labels).cuda()
        else:
            images=Variable(images) 
            labels=Variable(labels)

        # 将参数的grad值初始化为
        optimizer.zero_grad()                
 
        # forward + backward + optimize      
        outputs = net(images)
        # 将output和labels使用叉熵计算损失
        loss = criterion(outputs, labels)
        # 反向传播
        loss.backward()
        # 用SGD更新参数
        optimizer.step()
        
        # loss.item()转换为numpy
        # loss本身为Variable类型,所以要使用loss.data[0]获取其Tensor,因为其为标量,所以取0
        sum_loss += loss.item()
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)          # 更新测试图片的数量
        correct += (predicted == labels).sum() # 更新正确分类的图片的数量
#         if i % 200 == 199:
        print('[epoch:%d, iter:%d] Loss: %.03f | Acc: %.3f%% ' 
              % (epoch + 1, (i + 1 + epoch * len(trainloader)), sum_loss / (i + 1), 100. * correct / total))
        plotloss.append(sum_loss / (i + 1))
        plotauc.append(100. * correct / total)
        
print('Finished Training')
plt.subplot(2,1,1)
plt.plot(plotloss)
plt.subplot(2,1,2)
plt.plot(plotauc)

5.测试

 # 定义2个存储每类中测试正确的个数的 列表,初始化为0
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))  
for data in testloader:
    images, labels = data
    images=Variable(images).cuda()
    labels=Variable(labels).cuda()
    outputs = net(images)

    _, predicted = torch.max(outputs.data, 1)
    #4组(batch_size)数据中,输出于label相同的,标记为1,否则为0
    c = (predicted == labels).squeeze()
    for i in range(16):     
        label = labels[i]   # 对各个类的进行各自累加
        class_correct[label] += c[i]
        class_total[label] += 1
 
 
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))

6.保存模型

pytorch保存模型的方式有两种:

(1)将整个网络都都保存下来

torch.save(model_object, 'model.pkl')
    
model = torch.load('model.pkl')

这种方式再重新加载的时候不需要自定义网络结构,保存时已经把网络结构保存了下来,但是,再读取模型的时候还需要将以前的网络结构复制进来且比较死板(pycharm中不用),另外不能调整网络结构。

(2)仅保存和加载模型参数(推荐使用这样的方法)

GPU上保存,CPU上加载:

torch.save(model.state_dict(), PATH)
device = torch.device('cpu')
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location=device))

GPU上保存,GPU上加载:

torch.save(model.state_dict(), PATH)
         
device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)

往模型中输入数据的时候不要忘记在任意tensor上调用input = input.to(device)

这种方式再重新加载的时候需要自己定义网络,并且其中的参数名称与结构要与保存的模型中的一致(可以是部分网络,比如只使用VGG的前几层),相对灵活,便于对网络进行修改。

torch.save(net, 'D:/CIFAR-10/model/LeNet5-128.pth')

7.预测

import torch
from PIL import Image
from torch.autograd import Variable
import torch.nn.functional as F
from torchvision import datasets, transforms
import numpy as np
 
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = torch.load('D:/CIFAR-10/model/LeNet5-128.pth')  # 加载模型
model = model.to(device)
model.eval()  # 把模型转为test模式

# 读取要预测的图片
img = Image.open("D:/CIFAR-10/bird1.png").convert('RGB') # 读取图像
trans = transforms.Compose([transforms.Scale((32,32)),
                            transforms.ToTensor(),
                            transforms.Normalize(mean=(0.5, 0.5, 0.5), 
                                                 std=(0.5, 0.5, 0.5)),
                           ])
 
img = trans(img)
img = img.to(device)
# 图片扩展多一维,因为输入到保存的模型中是4维的[batch_size,通道,长,宽],而普通图片只有三维,[通道,长,宽]
img = img.unsqueeze(0)  
    # 扩展后,为[1,1,28,28]
output = model(img)
prob = F.softmax(output,dim=1) #prob是10个分类的概率
print("概率",prob)
value, predicted = torch.max(output.data, 1)
print("类别",predicted.item())
print(value)
pred_class = classes[predicted.item()]
print("分类",pred_class)
发布了23 篇原创文章 · 获赞 61 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_40195360/article/details/105558440