pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练

LeNet5这个经典的卷积神经网络,它有3个全连接层,输出维度分别是120,84,10。

一、下载CIFAR-10数据集

可以通过pytorch的数据集加载工具进行CIFAR-10数据集下载

代码中各个参数的含义在下面的代码段中标识,请读者按需自取;

import torch
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim # 优化器

'''下载数据集'''
# transforms.Compose()函数将两个函数拼接起来。
# (ToTensor():把一个PIL.Image转换成Tensor,Normalize():标准化,即减均值,除以标准差)
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 训练集:下载CIFAR10数据集,如果没有事先下载该数据集,则将download参数改为True
trainset = torchvision.datasets.CIFAR10(root='../data', train=True,download=False, transform=transform)
# 用DataLoader得到生成器,其中shuffle:是否将数据打乱;
# num_workers表示使用多进程加载的进程数,0代表不使用多进程
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,shuffle=True, num_workers=0)

# 测试集数据下载
testset = torchvision.datasets.CIFAR10(root='../data', train=False,download=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,shuffle=False, num_workers=0)

classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')


上面的代码中我们已经在目录的最顶层下的data文件中下载好了数据集,接下来可以对数据集中已经下载好的数据进行显示:

# 显示图像
def imshow(img):
    # 因为标准化normalize是:output = (input-0.5)/0.5
    # 则反标准化unnormalize是:input = output*0.5 + 0.5
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    # transpose()会更改多维数组的轴的顺序
    # Pytorch中是[channel,height,width],这里改为图像的[height,width,channel]
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# 随机获取部分训练数据
# next() 返回迭代器的下一个项目。
# next() 函数要和生成迭代器的 iter() 函数一起使用。
dataiter = iter(trainloader)
images, labels = dataiter.next()
# 显示图像
# torchvision.utils.make_grid()将多张图片拼接在一张图中
imshow(torchvision.utils.make_grid(images))
# 打印标签
# str.join(sequence):用于将序列中的元素以指定的字符str连接成一个新的字符串。这里的str是' ',空格
# %5s:表示输出字符串至少5个字符,不够5个的话,左侧用空格补
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

运行结果:

pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第1张图片

 

二、搭建网络结构

LeNet模型

输入:
灰度图像,通道为1,尺寸为1×32×32
第一层:卷积层
LeNet-5模型接受的输入层大小是1×32x32。卷积层的过滤器的尺寸是5x5,深度(卷积核个数)为6,不使用全0填充,步长为1。则这一层的输出的尺寸为32-5+1=28,深度为6。本层的输出矩阵大小为6×28×28。
第二层:池化层
这一层的输入是第一层的输出,是一个6×28x28=4704的节点矩阵。本层采用的过滤器为2x2的大小,长和宽的步长均为2,所以本层的输出矩阵大小为6×14x14。
第三层:卷积层
本层的输入矩阵大小为6×14x14,使用的过滤器大小为5x5,深度为16。本层不使用全0填充,步长为1。本层的输出矩阵大小为16×10x10。
第四层:池化层
本层的输入矩阵大小是16×10x10,采用的过滤器大小是2x2,步长为2,本层的输出矩阵大小为16×5x5。
第五层:全连接层(LeNet5有3个全连接层,输出维度分别是120,84,10)
本层的输入矩阵大小为16×5x5。将此矩阵中的节点拉成一个向量,那么这就和全连接层的输入一样了,本层的输出节点个数为120
第六层:全连接层
本层的输入节点个数为120个,输出节点个数为84个。
第七层:全连接层
本层的输入节点为84个,输出节点个数为10个。

激活函数的引入是为了增加神经网络模型的非线性,没有激活函数每层就相当于矩阵相乘。每一层输出都是上层的输入的线性函数,无论神经网络多少层,输出都是输入的线性组合,就是最原始的感知机

加入激活函数,给神经元引入非线性因素,神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中,本文中激活函数使用的是relu函数,它克服梯度消失的问题。

import torch

import torch.nn as nn
import torch.nn.functional as F


# 有GPU就用GPU跑,没有就用CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


class LeNet(nn.Module):
    def __init__(self):
        super(LeNet,self).__init__()
        # 卷积层1:输入图像深度=3,输出图像深度=16,卷积核大小=5*5,卷积步长=1;16表示输出维度,也表示卷积核个数
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=16,kernel_size=5,stride=1)
        # 池化层1:采用最大池化,区域集大小=2*2.池化步长=2
        self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)
        # 卷积层2
        self.conv2 = nn.Conv2d(in_channels=16,out_channels=32,kernel_size=5,stride=1)
        # 池化层2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 全连接层1:输入大小=32*5*5,输出大小=120
        self.fc1 = nn.Linear(32*5*5,120)
        # 全连接层2
        self.fc2 = nn.Linear(120,84)
        # 全连接层3
        self.fc3 = nn.Linear(84,10)

    def forward(self,x):
        x = F.relu(self.conv1(x))  # input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)  # output(16, 14, 14)
        x = F.relu(self.conv2(x))  # output(32, 10, 10)
        x = self.pool2(x)  # output(32, 5, 5)
        x = x.view(-1, 32 * 5 * 5)  # output(32*5*5)
        x = F.relu(self.fc1(x))  # output(120)
        x = F.relu(self.fc2(x))  # output(84)
        x = self.fc3(x)  # output(10)
        return x

net=LeNet()
net=net.to(device)
# 查看网络结构
print("查看网络结构")
print(net)

 运行结果:pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第2张图片

三、进行模型训练

# 接下来进行模型训练
net = LeNet()
net = net.to(device)
loss_function = nn.CrossEntropyLoss()  # 使用交叉熵损失函数
# 优化器选择Adam,学习率设为0.001
optimizer = optim.Adam(net.parameters(), lr=0.001)
for epoch in range(10):  # 整个迭代10轮
    running_loss = 0.0  # 初始化损失函数值loss=0
    for i, data in enumerate(trainloader, start=0):#对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值
        #enumerate多用于在for循环中得到计数,enumerate 是python自带的一个函数,start为索引起始值
            # 获取训练数据
            inputs, labels = data
            inputs, labels = inputs.to(device), labels.to(device)  # 将数据及标签传入GPU/CPU

            # 权重参数梯度清零
            optimizer.zero_grad()

            # 正向及反向传播
            outputs = net(inputs)#调用上面的神经网络,正向传播
            loss = loss_function(outputs, labels)#损失函数计算经过卷积神经网络后的值与原来的值的差距
            loss.backward()#调用pytorch的自动反向传播函数,自动生成梯度
            optimizer.step()#执行优化器,把梯度传播回每个网络

            # 显示损失值
            running_loss += loss.item()#item()方法把字典中每对key和value组成一个元组,并把这些元组放在列表中返回。python自带的字典遍历函数
            if i % 2000 == 1999:  # print every 2000 mini-batches
                print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
                running_loss = 0.0

    print('Finished Training')

运行结果:

pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第3张图片pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第4张图片pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第5张图片

 四、测试训练结果并打印训练的准确率

'''测试模型'''
correct = 0
total = 0
# with是一个上下文管理器
# with torch.no_grad()表示其包括的内容不需要计算梯度,也不会进行反向传播,节省内存
with torch.no_grad():
    for data in testloader:
        images, labels = data
        images, labels = images.to(device), labels.to(device)
        outputs = net(images)
        # torch.max(outputs.data, 1)返回outputs每一行中最大值的那个元素,且返回其索引
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

# 打印10个分类的准确率
class_correct = list(0. for i in range(10)) #class_correct=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
class_total = list(0. for i in range(10)) #class_total=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
with torch.no_grad():
    for data in testloader:
        images, labels = data
        images, labels = images.to(device), labels.to(device)
        outputs = net(images) # outputs的维度是:4*10
        # torch.max(outputs.data, 1)返回outputs每一行中最大值的那个元素,且返回其索引
        # 此时predicted的维度是:4*1
        _, predicted = torch.max(outputs, 1)
        # 此时c的维度:4将预测值与实际标签进行比较,且进行降维
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] += 1
for i in range(10):
    print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))

运行结果:

pytorch学习——使用LeNet-5模型对CIFAR-10数据集进行训练_第6张图片

五、保存训练结果

 Python在遍历已知的库文件目录过程中,如果见到一个._pth 文件,就会将文件中所记录的路径加入到 sys.path 设置中,于是 .pth 文件说指明的库也就可以被 Python 运行环境找到了

#保存权重和参数
save_path = 'Lenets.pth'#与当前.py文件同级
torch.save(net.state_dict(), save_path)

六、利用刚刚保存的测试结果进行一张随便照片的预测

新建一个python文件,进行自己的图片预测1.png

这里应当注意的是,原来的网络结构Lenet要在该文件中复制粘贴一遍,或者单独创建一个python文件,文件里单独写一下这个网络结构,然后通过from import LeNet引入

import torch
import torchvision.transforms as transforms
from PIL import Image
from torch import nn
import torch.nn.functional as F


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

classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet,self).__init__()
        # 卷积层1:输入图像深度=3,输出图像深度=16,卷积核大小=5*5,卷积步长=1;16表示输出维度,也表示卷积核个数
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=16,kernel_size=5,stride=1)
        # 池化层1:采用最大池化,区域集大小=2*2.池化步长=2
        self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)
        # 卷积层2
        self.conv2 = nn.Conv2d(in_channels=16,out_channels=32,kernel_size=5,stride=1)
        # 池化层2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 全连接层1:输入大小=32*5*5,输出大小=120
        self.fc1 = nn.Linear(32*5*5,120)
        # 全连接层2
        self.fc2 = nn.Linear(120,84)
        # 全连接层3
        self.fc3 = nn.Linear(84,10)

    def forward(self,x):#forward函数的任务需要把输入层、网络层、输出层链接起来实现数据的前向传导
        x = F.relu(self.conv1(x))  # input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)  # output(16, 14, 14)
        x = F.relu(self.conv2(x))  # output(32, 10, 10)
        x = self.pool2(x)  # output(32, 5, 5)
        x = x.view(-1, 32 * 5 * 5)  # output(32*5*5)
        x = F.relu(self.fc1(x))  # output(120)
        x = F.relu(self.fc2(x))  # output(84)
        x = self.fc3(x)  # output(10)
        return x

net = LeNet()
net.load_state_dict(torch.load('Lenets.pth'))

im = Image.open('1.png')
im = transform(im)  # [C, H, W]
# 输入pytorch网络中要求的格式是[batch,channel,height,width],所以这里增加一个维度
im = torch.unsqueeze(im, dim=0)  # [N, C, H, W]

with torch.no_grad():
    outputs = net(im)
    predict = torch.max(outputs, dim=1)[1].data.numpy() # 索引即classed中的类别
print(classes[int(predict)])

# 直接打印张量的预测结果
with torch.no_grad():
    outputs = net(im)
    predict = torch.softmax(outputs,dim=1) # [batch,channel,height,width],这里因为对batch不需要处理
print(predict)

 运行结果:

由于0.4469准确率最高,所以返回dog

你可能感兴趣的:(pytorch,pytorch,深度学习,计算机视觉)