CNN经典网络模型(一):用Pytorch复现LeNet并进行代码解读

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

要想对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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值