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)))
运行结果:
输入:
灰度图像,通道为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)
# 接下来进行模型训练
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')
运行结果:
四、测试训练结果并打印训练的准确率
'''测试模型'''
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]))
运行结果:
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