提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
要想对yolo这样的目标检测算法,或者各种切割算法进行改进就必须了解深度学习,在基本了解CNN的基础知识(如果还不了解请看CNN介绍)后我们可以通过对CNN经典网络模型进行代码解读逐步了解检测算法的结构。从浅入深我会从时间线来逐行代码的讲解各个CNN经典网络模型。
本文我们就来复现最早发布的卷积神经网络之一——LeNet。
一、LeNet是什么?
Lenet 是一系列网络的合称,包括 Lenet1 - Lenet5。LeNet是最早发布的卷积神经网络之一,他是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并且以其命名),目的是识别图像中手写数字.当时,Yann LeCun发表了第一篇通过反向传播成功训练卷积神经网络的研究论文,这项工作代表了十多年来神经网络研究开发的成果.
当时,LeNet取得了与支持向量机(support vector machines)性能相媲美的成果,成为监督学习的主流方法。 LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。 时至今日,一些自动取款机仍在运行Yann LeCun和他的同事Leon Bottou在上世纪90年代写的代码呢!
二、网络结构
Lenet是一个 7 层的神经网络,包含 3 个卷积层,2 个池化层,1 个全连接层,1个输出层。其中所有卷积层的卷积核都为 5x5,步长=1,池化方法都为平均池化,激活函数为 Sigmoid(目前使用的Lenet已改为ReLu),网络结构如下:
上面的名词如果你不懂可以去看我之前的文章有详细讲解CNN介绍
输入:
灰度图像,通道为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。
第五层:全连接层
本层的输入矩阵大小为16×5x5。将此矩阵中的节点拉成一个向量,那么这就和全连接层的输入一样了,本层的输出节点个数为120。
第六层:全连接层
本层的输入节点个数为120个,输出节点个数为84个。
第七层:全连接层
本层的输入节点为84个,输出节点个数为10个。
三、所用数据集——CIFAR-10
在本文中,我们将使用CIFAR10数据集。 它有类:’ airplane ', ’ automobile ', ’ bird ', ’ cat ', ’ deer ',‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’.十个类别,每个类别有60000个图像(50000个训练图像和10000个测试图像)。CIFAR-10中的图像为 尺寸为3x32x32,即32x32像素的三通道彩色图像。
数据集不需要我们单独去下载,在下面的程序讲解中会有下载的代码。
四、代码讲解
1.引入库
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
2.定义 LeNet 类(初始化并定义前向传播方法):
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
定义 LeNet 类:
class LeNet(nn.Module):继承自 PyTorch 的nn.Module类,这是定义神经网络的常见方式。通过继承nn.Module,可以方便地使用 PyTorch 提供的各种功能,如自动求导、参数管理等。
初始化方法(init):
super(LeNet, self).init():调用父类的初始化方法,确保正确初始化nn.Module。
定义卷积层和全连接层:
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, stride=1):定义第一个卷积层,输入图像深度为 3(对应 RGB 三通道),输出图像深度为 16,卷积核大小为 5x5,步长为 1。
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2):定义第一个最大池化层,池化窗口大小为 2x2,步长为 2。
类似地定义conv2、pool2、fc1、fc2和fc3等层,分别对应第二个卷积层、第二个池化层和三个全连接层。全连接层的输入和输出大小根据网络结构进行设置。
前向传播方法(forward):
定义了神经网络的前向传播逻辑。
对输入数据依次进行卷积、激活函数(F.relu)、池化等操作,最后将数据展平(x = x.view(-1, 32 * 5 * 5))并通过全连接层得到最终输出。
例如,x = F.relu(self.conv1(x))对输入进行第一个卷积操作后,再通过激活函数ReLU增加非线性。然后经过池化层self.pool1(x)降低特征图的尺寸。重复这个过程直到通过全连接层得到最终的输出分类结果。
总的来说,这一块代码完整的定义了LeNet。
3.下载数据集并查看部分数据:
# 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=True, 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')
# 显示图像
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()
# 随机获取部分训练数据
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)))
数据预处理:
transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]):将两个数据转换操作组合起来。
transforms.ToTensor():将 PIL 图像转换为 PyTorch 张量。
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)):对数据进行标准化,将数据的每个通道的值减去均值(这里是 0.5),然后除以标准差(这里也是 0.5)。这有助于加速模型的训练和提高模型的泛化能力。
下载训练集和测试集:
trainset = torchvision.datasets.CIFAR10(root=‘./data’, train=True, download=True, transform=transform):下载 CIFAR-10 训练集。如果数据已经下载过,可将download参数设置为False。root参数指定数据存储的路径,train=True表示下载训练集。
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=0):使用数据加载器将训练集数据封装起来,方便在训练过程中进行批处理。batch_size=4表示每次加载 4 个样本,shuffle=True表示在每个 epoch 开始时随机打乱数据顺序,num_workers=0表示不使用多进程加载数据。
类似地,下载测试集并创建测试集的数据加载器testloader。
定义类别名称和显示图像的函数:
classes = (‘plane’, ‘car’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’):定义 CIFAR-10 数据集中 10 个类别的名称。
def imshow(img):定义一个函数用于显示图像。首先对图像进行反标准化操作(img = img / 2 + 0.5),将其转换回原始范围。然后将 PyTorch 张量转换为 NumPy 数组,并调整数组的轴顺序以便正确显示图像。最后使用plt.imshow和plt.show显示图像。
随机获取部分训练数据并显示:
dataiter = iter(trainloader):创建一个迭代器,用于从训练数据加载器中获取数据。
images, labels = dataiter.next():获取一批数据,包括图像和对应的标签。
imshow(torchvision.utils.make_grid(images)):使用imshow函数显示这批图像的拼接图。torchvision.utils.make_grid函数将多个图像拼接成一个网格图像,方便显示。
print(’ ‘.join(’%5s’ % classes[labels[j]] for j in range(4))):打印这批图像的标签,使用classes列表根据标签索引获取类别名称,并格式化为字符串进行打印。
结果显示
下图是从数据集中随意去除的四张图片
下面是这四张图片在数据集中的分类(注意!这里只是取出图片并标出相应类别,这个结果并不是我们预测的是读取数据集里的结果)
4.训练模型:
# 有GPU就用GPU跑,没有就用CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net = LeNet()
net=net.to(device)
# print("该网络共有 {} 个参数".format(sum(x.numel() for x in net.parameters())))
# # 该网络共有 121182 个参数
# 对于多分类问题,应该使用Softmax函数,这里CIFAR10数据集属于多分类,却使用了交叉熵损失函数,
# 是因为进入CrossEntropyLoss()函数内部就会发现其中包含了Softmax函数
loss_function = nn.CrossEntropyLoss() # 使用交叉熵损失函数
# 优化器选择Adam,学习率设为0.001
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 打印查看神经网络的结构
# print(net)
# # LeNet(
# # (conv1): Conv2d(3, 16, kernel_size=(5, 5), stride=(1, 1))
# # (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# # (conv2): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1))
# # (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# # (fc1): Linear(in_features=800, out_features=120, bias=True)
# # (fc2): Linear(in_features=120, out_features=84, bias=True)
# # (fc3): Linear(in_features=84, out_features=10, bias=True)
# # )
for epoch in range(10): # 整个迭代10轮
running_loss = 0.0 # 初始化损失函数值loss=0
for i, data in enumerate(trainloader, start=0):
# 获取训练数据
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()
optimizer.step()
# 显示损失值
running_loss += loss.item()
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')
选择设备:
device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”):检查是否有可用的 GPU,如果有则选择 GPU(cuda:0表示第一个 GPU),否则使用 CPU。
创建模型实例并转移到设备:
net = LeNet():创建 LeNet 模型的实例。
net = net.to(device):将模型转移到选择的设备上,以便在 GPU 上进行加速计算(如果可用)。
定义损失函数和优化器:
loss_function = nn.CrossEntropyLoss():选择交叉熵损失函数作为模型的损失函数。对于多分类问题,交叉熵损失函数是常用的选择。
optimizer = optim.Adam(net.parameters(), lr=0.001):选择 Adam 优化器,并设置学习率为 0.001。Adam 优化器是一种常用的优化算法,它结合了动量和自适应学习率的方法,在很多深度学习任务中表现良好。
结果显示
[1, 2000] loss: 1.848
[1, 4000] loss: 1.552
[1, 6000] loss: 1.456
[1, 8000] loss: 1.401
[1, 10000] loss: 1.366
[1, 12000] loss: 1.310
[2, 2000] loss: 1.231
[2, 4000] loss: 1.222
[2, 6000] loss: 1.199
[2, 8000] loss: 1.177
[2, 10000] loss: 1.159
[2, 12000] loss: 1.129
[3, 2000] loss: 1.074
[3, 4000] loss: 1.080
[3, 6000] loss: 1.071
[3, 8000] loss: 1.057
[3, 10000] loss: 1.028
[3, 12000] loss: 1.024
[4, 2000] loss: 0.965
[4, 4000] loss: 0.966
[4, 6000] loss: 0.977
[4, 8000] loss: 0.978
[4, 10000] loss: 0.978
[4, 12000] loss: 0.995
[5, 2000] loss: 0.886
[5, 4000] loss: 0.946
[5, 6000] loss: 0.925
[5, 8000] loss: 0.929
[5, 10000] loss: 0.921
[5, 12000] loss: 0.939
[6, 2000] loss: 0.853
[6, 4000] loss: 0.876
[6, 6000] loss: 0.882
[6, 8000] loss: 0.904
[6, 10000] loss: 0.895
[6, 12000] loss: 0.887
[7, 2000] loss: 0.815
[7, 4000] loss: 0.843
[7, 6000] loss: 0.846
[7, 8000] loss: 0.872
[7, 10000] loss: 0.849
[7, 12000] loss: 0.850
[8, 2000] loss: 0.790
[8, 4000] loss: 0.810
[8, 6000] loss: 0.830
[8, 8000] loss: 0.820
[8, 10000] loss: 0.820
[8, 12000] loss: 0.831
[9, 2000] loss: 0.746
[9, 4000] loss: 0.774
[9, 6000] loss: 0.790
[9, 8000] loss: 0.803
[9, 10000] loss: 0.810
[9, 12000] loss: 0.815
[10, 2000] loss: 0.728
[10, 4000] loss: 0.769
[10, 6000] loss: 0.770
[10, 8000] loss: 0.758
[10, 10000] loss: 0.769
[10, 12000] loss: 0.785
Finished Training
5.在测试集中随机预测四张图看看效果:
dataiter = iter(testloader)
images, labels = dataiter.__next__()
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
images, labels = images.to(device), labels.to(device)
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]for j in range(4)))
获取测试数据并显示:
dataiter = iter(testloader):创建一个迭代器,用于从测试数据加载器中获取数据。
images, labels = dataiter.next():获取一批测试数据和对应的标签。
imshow(torchvision.utils.make_grid(images)):显示这批测试图像的拼接图。
print('GroundTruth: ‘, ’ ‘.join(’%5s’ % classes[labels[j]] for j in range(4))):打印这批图像的真实标签。
进行预测并显示:
images, labels = images.to(device), labels.to(device):将测试数据转移到选择的设备上。
outputs = net(images):将测试图像通过模型进行前向传播,得到模型的输出。
_, predicted = torch.max(outputs, 1):找到模型输出中每个样本的最大概率对应的类别索引,作为预测结果。
print('Predicted: ‘, ’ ‘.join(’%5s’ % classes[predicted[j]] for j in range(4))):打印这批图像的预测标签。
结果显示
下面是我们从数据集中随机选择的四张图
下面则是LeNet预测得出的图像的种类。
6.测试模型在测试集上的准确率及 10 个类别的准确率:
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]))
计算整体准确率:
correct = 0和total = 0:用于统计正确预测的样本数和总样本数。
在一个循环中遍历测试数据加载器:
images, labels = data:获取一批测试数据和对应的标签。
images, labels = images.to(device), labels.to(device):将数据转移到选择的设备上。
outputs = net(images):将测试图像通过模型进行前向传播,得到模型的输出。
_, 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)):打印模型在整个测试集上的准确率。
计算每个类别的准确率:
class_correct = list(0. for i in range(10))和class_total = list(0. for i in range(10)):分别用于统计每个类别的正确预测样本数和总样本数。
在一个循环中遍历测试数据加载器:
与计算整体准确率的过程类似,获取测试数据、进行前向传播和得到预测结果。
c = (predicted == labels).squeeze():将预测结果与真实标签进行比较,得到一个布尔张量,然后去除多余的维度。
对于每个样本:
label = labels[i]:获取当前样本的真实标签。
class_correct[label] += c[i].item():根据真实标签,将当前样本的正确预测结果累加到对应类别的正确预测样本数中。
class_total[label] += 1:将对应类别的总样本数增加 1。
对于每个类别:
print(‘Accuracy of %5s : %2d %%’ % (classes[i], 100 * class_correct[i] / class_total[i])):打印每个类别的准确率。
结果显示
7.存储训练好的权重文件:
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
保存模型参数:
save_path = ‘./Lenet.pth’:指定保存模型参数的文件路径。
torch.save(net.state_dict(), save_path):将模型的参数保存到指定的文件中。net.state_dict()返回模型的参数字典,torch.save函数用于将这个字典保存到文件中,以便在以后的使用中可以加载这些参数来恢复模型。
8.重新创建一个model文件里面放入之前写好的LeNet模块:
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 # 优化器
'''1.构建神经网络'''
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
因为之前在第二步中讲过了,这里就不讲了。
9.创建一个text文件对我们自己的图片进行预测:
import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
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')
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth', weights_only=True))
im = Image.open('E:/Vision/LeNet5/dog2.jpg')
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中的类别
if len(predict.shape) == 1:
predict_value = predict.item()
else:
# 根据实际情况处理多维数组,提取出单个元素的值
predict_value = ...
print(classes[int(predict_value)])
# 直接打印张量的预测结果
with torch.no_grad():
outputs = net(im)
predict = torch.softmax(outputs,dim=1) # [batch,channel,height,width],这里因为对batch不需要处理
print(predict)
加载模型和处理图像
net = LeNet():创建一个LeNet模型的实例。
net.load_state_dict(torch.load(‘Lenet.pth’, weights_only=True)):加载预先训练好的模型参数。这里使用torch.load函数加载模型参数文件Lenet.pth,并设置weights_only=True以确保只加载模型的权重,避免潜在的安全问题。
im = Image.open(‘E:/Vision/LeNet5/dog2.jpg’):使用PIL的Image.open函数打开指定路径的图像文件。(这里图片的路径要换成你们自己的)
im = transform(im):对打开的图像进行预处理,依次应用之前定义的图像大小调整、转换为张量和标准化操作。
im = torch.unsqueeze(im, dim=0):为了满足 PyTorch 网络对输入格式的要求,在图像张量的维度上增加一个维度,将其从形状为[C, H, W](通道数、高度、宽度)转换为[N, C, H, W](批量大小、通道数、高度、宽度),这里批量大小为 1。
预测类别和打印结果
with torch.no_grad()::在这个上下文管理器中,PyTorch 不会计算梯度,因为在预测阶段不需要进行反向传播。这可以节省内存和计算资源。
outputs = net(im):将预处理后的图像输入到神经网络模型中,得到模型的输出。输出通常是一个形状为[batch_size, num_classes]的张量,表示每个输入图像对应各个类别的概率分布。
predict = torch.max(outputs, dim=1)[1].data.numpy():找到模型输出中每个图像对应概率最大的类别索引。torch.max(outputs, dim=1)返回两个张量,第一个是每个图像的最大概率值,第二个是对应的类别索引。这里只取类别索引,并将其转换为 NumPy 数组。
if len(predict.shape) == 1::检查预测结果的形状是否为一维。如果是一维,表示只有一个图像的预测结果;如果不是一维,可能是处理了多个图像的批量预测结果,需要根据实际情况进行处理。这里假设只有一个图像的情况。
predict_value = predict.item():从一维的预测结果中提取出单个元素的值。
print(classes[int(predict_value)]):将预测结果的索引转换为对应的类别名称,并打印出来。
with torch.no_grad()::再次使用上下文管理器进行无梯度计算的操作。
outputs = net(im):再次将图像输入到模型中,得到模型的输出。
predict = torch.softmax(outputs,dim=1):对模型的输出应用 softmax 函数,将输出转换为概率分布。softmax 函数将每个输出值转换为 0 到 1 之间的概率,且所有概率之和为 1。这里只对输出的第二个维度(对应不同的类别)进行 softmax 操作,因为批量维度不需要处理。
print(predict):打印经过 softmax 处理后的概率分布张量。
五、结果展示
下面展示我用自己训练的权重预测从网上下载的图片
预测结果:
其中dog是最终我们预测出的目标的种类。而tensor是一个形状为 [1, 10] 的张量,表示经过模型预测和 softmax 处理后的概率分布。这个张量表示对于一张输入图像,模型预测它属于各个类别的概率。这里有 10 个类别(两个图种类和概率对应),对应输出张量中的 10 个元素。
例如,第一个元素 2.3559e-04 表示模型预测该图像属于类别 “plane” 的概率非常小。而元素 7.0723e-01 表示模型预测该图像属于类别 “dog” 的概率相对较高。
六、完整程序
main
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 # 优化器
'''1.构建神经网络'''
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
'''2.下载数据集并查看部分数据'''
# 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=True, 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')
# 显示图像
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()
# 随机获取部分训练数据
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)))
'''3.训练模型'''
# 有GPU就用GPU跑,没有就用CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net = LeNet()
net=net.to(device)
# print("该网络共有 {} 个参数".format(sum(x.numel() for x in net.parameters())))
# # 该网络共有 121182 个参数
# 对于多分类问题,应该使用Softmax函数,这里CIFAR10数据集属于多分类,却使用了交叉熵损失函数,
# 是因为进入CrossEntropyLoss()函数内部就会发现其中包含了Softmax函数
loss_function = nn.CrossEntropyLoss() # 使用交叉熵损失函数
# 优化器选择Adam,学习率设为0.001
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 打印查看神经网络的结构
# print(net)
# # LeNet(
# # (conv1): Conv2d(3, 16, kernel_size=(5, 5), stride=(1, 1))
# # (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# # (conv2): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1))
# # (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# # (fc1): Linear(in_features=800, out_features=120, bias=True)
# # (fc2): Linear(in_features=120, out_features=84, bias=True)
# # (fc3): Linear(in_features=84, out_features=10, bias=True)
# # )
for epoch in range(10): # 整个迭代10轮
running_loss = 0.0 # 初始化损失函数值loss=0
for i, data in enumerate(trainloader, start=0):
# 获取训练数据
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()
optimizer.step()
# 显示损失值
running_loss += loss.item()
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')
'''4.在测试集中随机预测四张图看看效果'''
dataiter = iter(testloader)
images, labels = dataiter.__next__()
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
images, labels = images.to(device), labels.to(device)
outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]for j in range(4)))
'''5.测试模型在测试集上的准确率及10个类别的准确率'''
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]))
'''6.存储训练好的权重文件'''
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
model
import torch.nn as nn
import torch.nn.functional as F
'''1.构建神经网络'''
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
text
import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
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')
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth', weights_only=True))
im = Image.open('E:/Vision/LeNet5/dog2.jpg')
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中的类别
if len(predict.shape) == 1:
predict_value = predict.item()
else:
# 根据实际情况处理多维数组,提取出单个元素的值
predict_value = ...
print(classes[int(predict_value)])
# 直接打印张量的预测结果
with torch.no_grad():
outputs = net(im)
predict = torch.softmax(outputs,dim=1) # [batch,channel,height,width],这里因为对batch不需要处理
print(predict)