1.代码复现参考链接
阿里云开发者社区:https://developer.aliyun.com/article/1196254?spm=a2c6h.13148508.setting.14.21a54f0eQRsFIB
微信公众号:https://mp.weixin.qq.com/s?__biz=MzIwOTc2MTUyMg==&mid=2247530266&idx=1&sn=7da4a8f0334d3028bbe6b8c020684dd9&chksm=976cd287a01b5b912a20e03c58b09199a8cb4869c09a40320301fc22a494de86fc673475527e&token=431225665&lang=zh_CN#rd
GitHub代码、数据:https://github.com/RedstoneWill/ObjectDetectionLearner/tree/main/LeNet-5
三者皆为同一人所写。
2.代码复现中的笔记
(1)import torch.optim as optim的作用
torch.optim是PyTorch中用于实现各种优化算法的模块,在训练神经网络时非常有用。
它提供了各种类型的优化器,如随机梯度下降、Adam、Adagrad等,并允许我们根据需要自定义优化器。
我们可以使用各种类型的优化器来更新神经网络模型的参数。
(2)from torchvision import datasets,transforms的作用
tochvision主要处理图像数据,包含一些常用的数据集、模型、转换函数等。
torchvision主要包含以下四部分:
torchvision.models: 提供深度学习中各种经典的网络结构、预训练好的模型,如:Alex-Net、VGG、ResNet、Inception等。
torchvision.datasets:提供常用的数据集,设计上继承 torch.utils.data.Dataset,主要包括:MNIST、CIFAR10/100、ImageNet、COCO等。
torchvision.transforms:提供常用的数据预处理操作,主要包括对Tensor及PIL Image对象的操作。
torchvision.utils:工具类,如保存张量作为图像到磁盘,给一个小批量创建一个图像网格。
原文链接:https://blog.youkuaiyun.com/qq_45772756/article/details/128651447
torchvision 的 transforms 集成了随机翻转、旋转、增强对比度、
转化为tensor、转化为图像等功能,用于数据增强,非常便捷
原文链接:https://blog.youkuaiyun.com/iteapoy/article/details/106121752
(3)在正则化(当模型出现过拟合的情况时,用来降低模型的复杂度) transforms.Normalize((0.1307,),(0.3081,))中
标准化系数就是计算要用到的均值和标准差,在本例中是((0.1307,), (0.3081,)),均值是 0.1307,标准差是 0.3081,这些系数都是数据集提供方计算好的数据。不同数据集就有不同的标准化系数,另外还需要保持train_set、val_set和test_set标准化系数的一致性。
(4)关于x=x.view(-1,1655)
其实在torch里面,view函数就相当于numpy的reshape,执行的操作就是对tensor进行维度转换。这边的这里-1表示一个不确定的数,就是你如果不确定你想要reshape成几行
(5)在执行test.py文件时,出现AttributeError: Can’t get attribute ‘LeNet’ on <module ‘main’ from ‘C:\Code reproduction\LeNet-5\test.py’>报错
解决方法:因为是从model.py文件中调用的LeNet,所以应该在test.py文件中导入model.py文件中对应的LeNet类即可。即from model import LeNet
(6)结果
运行且识别成功。以下面的图片为例:
识别结果:
(7)完整代码
文件结构:(data文件运行代码时会自动生成,images文件是放测试识别图片的文件夹,models是存放训练的模型的权重的文件夹)
model.py的代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
"""torch.optim是PyTorch中用于实现各种优化算法的模块,在训练神经网络时非常有用。
它提供了各种类型的优化器,如随机梯度下降、Adam、Adagrad等,并允许我们根据需要自定义优化器。
我们可以使用各种类型的优化器来更新神经网络模型的参数。"""
from torchvision import datasets,transforms
"""tochvision主要处理图像数据,包含一些常用的数据集、模型、转换函数等。
torchvision主要包含以下四部分:
torchvision.models: 提供深度学习中各种经典的网络结构、预训练好的模型,如:Alex-Net、VGG、ResNet、Inception等。
torchvision.datasets:提供常用的数据集,设计上继承 torch.utils.data.Dataset,主要包括:MNIST、CIFAR10/100、ImageNet、COCO等。
torchvision.transforms:提供常用的数据预处理操作,主要包括对Tensor及PIL Image对象的操作。
torchvision.utils:工具类,如保存张量作为图像到磁盘,给一个小批量创建一个图像网格。
原文链接:https://blog.youkuaiyun.com/qq_45772756/article/details/128651447
torchvision 的 transforms 集成了随机翻转、旋转、增强对比度、
转化为tensor、转化为图像等功能,用于数据增强,非常便捷
原文链接:https://blog.youkuaiyun.com/iteapoy/article/details/106121752"""
import time
from matplotlib import pyplot as plt
pipline_train=transforms.Compose([
#随机旋转图片
transforms.RandomHorizontalFlip(),
#将图片尺寸resize到32*32
transforms.Resize((32,32)),
#将图片转化为Tensor格式
transforms.ToTensor(),
#正则化(当模型出现过拟合的情况时,用来降低模型的复杂度)
transforms.Normalize((0.1307,),(0.3081,))
])
"""标准化系数就是计算要用到的均值和标准差,在本例中是((0.1307,), (0.3081,)),
均值是 0.1307,标准差是 0.3081,这些系数都是数据集提供方计算好的数据。
不同数据集就有不同的标准化系数,另外还需要保持train_set、val_set和test_set标准化系数的一致性"""
pipline_test=transforms.Compose([
# 将图片尺寸resize到32*32
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,))
])
#下载数据集
train_set=datasets.MNIST(root="./data",train=True,download=True,transform=pipline_train)
test_set=datasets.MNIST(root="./data",train=False,download=True,transform=pipline_test)
#加载数据集
trainloader=torch.utils.data.DataLoader(train_set,batch_size=64,shuffle=True)
testloader=torch.utils.data.DataLoader(test_set,batch_size=32,shuffle=False)
#搭建LeNet-5网络
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
#定义激活函数,方便下面的使用
self.relu=nn.ReLU()
# 创建卷积和池化层
# 创建第一个卷积层
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5) # kernel_size=5代表5*5的卷积核
self.maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 在池化层中步长和池化的窗口大小一致
# 尺寸的逻辑:池化层未改变通道数;当前通道数为6
# 创建第二个卷积层
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
self.maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2)
#创建三个全连接层
#第一个全连接层的输出神经元个数为120, 第三个全连接层输出神经元个数为分类标签的类别数
self.fc1 = nn.Linear(in_features=16*5*5, out_features=120)
self.fc2 = nn.Linear(in_features=120, out_features=84)
self.fc3 = nn.Linear(in_features=84, out_features=10)
def forward(self, x):
x=self.conv1(x)
x=self.relu(x)
x=self.maxpool1(x)
x=self.conv2(x)
x=self.relu(x)
x=self.maxpool2(x)
x=x.view(-1,16*5*5) # 使用view函数展平成一维向量
"""其实在torch里面,view函数就相当于numpy的reshape,
执行的操作就是对tensor进行维度转换。这边的这里-1代表自动调整这个维度上的元素个数,以保证元素的总数不变"""
x=F.relu(self.fc1(x))
x=F.relu(self.fc2(x))
x=self.fc3(x)
output=F.log_softmax(x,dim=1)
"""对softmax的结果进行log,log_softmax解决函数overflow和underflow,加快运算速度,提高数据稳性定。防止溢出.
dim=0:对每一列的所有元素进行softmax运算,并使得每一列所有元素和为1。
dim=1:对每一行的所有元素进行softmax运算,并使得每一行所有元素和为1。"""
return output
#创建模型,部署gpu
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
model=LeNet().to(device)
#定义优化器
optimizer=optim.Adam(model.parameters(),lr=0.001)
#定义训练过程
def train_runner(model,device,trainloader,optimizer,epoch):
#训练模型,启用BatchNormalization和Dropout,将BatchNormalization和Dropout置为True
model.train()
total=0
correct=0.0
#enumerate迭代已加载的数据集,同时获取数据和数据下标
for i,data in enumerate(trainloader,0): #train_loader是一个数据加载器,0是一个可选参数,用于指定枚举对象的起始索引。
"""enumerate()用于可迭代、可遍历的数据对象组合为一个索引序列,同时列出数据和数据下标,
上面代码的0表示从索引从0开始,假如为1的话,那索引就从1开始.
data里面包含图像数据(inputs)(tensor类型的)和标签(labels)(tensor类型)。"""
inputs,labels=data
#把模型部署到device上
inputs,labels=inputs.to(device),labels.to(device)
#初始化梯度,将优化器的梯度清零,以便在下一次迭代中计算新的梯度。
optimizer.zero_grad()
#保存训练结果
outputs=model(inputs)
#计算损失和
#多分类情况通常使用cross_entropy(交叉熵损失函数), 而对于二分类问题, 通常使用sigmoid
loss=F.cross_entropy(outputs,labels)
#获取最大概率的预测结果
#dim=1表示返回每一行的最大值对应的下标
predict=outputs.argmax(dim=1)
#累加总样本数和正确预测的样本数。
total+=labels.size(0) #size()主要是用来统计矩阵元素个数,或矩阵某一维上的元素个数的函数。
correct+=(predict==labels).sum().item()
#反向传播
loss.backward()
#使用优化器更新模型参数
optimizer.step()
if i%1000==0:
# loss.item()表示当前loss的数值
print("Train Epoch{} \t Loss:{:.6f},accuracy:{:.6f}%".format(epoch,loss.item(),100*(correct/total)))
Loss.append(loss.item())
Accuracy.append(correct/total)
return loss.item(),correct/total
#定义测试过程
def test_runner(model,device,testloader):
# 模型验证, 必须要写, 否则只要有输入数据, 即使不训练, 它也会改变权值
# 因为调用eval()将不启用 BatchNormalization 和 Dropout, BatchNormalization和Dropout置为False
model.eval()
#统计模型正确率,设置初始值
correct=0.0
test_loss=0.0
total=0
#torch.no_grad将不会计算梯度,也不会进行反向传播
with torch.no_grad():
for data,label in testloader:
data,label=data.to(device),label.to(device)
output=model(data)
test_loss+=F.cross_entropy(output,label).item()
predict=output.argmax(dim=1)
#计算正确数量
total+=label.size(0)
correct+=(predict==label).sum().item()
#计算损失值
print("test_avarage_loss:{:.6f},accuracy: {:.6f}%".format(test_loss/total, 100*(correct/total)))
#开始运行
epoch=5
Loss=[]
Accuracy=[]
for epoch in range(1,epoch+1):
print("start_time",time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
loss,acc=train_runner(model,device,trainloader,optimizer,epoch)
Loss.append(loss)
Accuracy.append(acc)
test_runner(model,device,testloader)
print("end_time: ",time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())),'\n')
print('Finished Training')
plt.subplot(2,1,1)
"""plt.subplot(2, 1, 1)是matplotlib.pyplot库中的一个函数,用于在一张图中创建多个子图。这个函数的参数表示子图的布局和当前激活的子图。
在这个例子中,`2` 表示子图的行数,`1` 表示子图的列数,`1` 表示当前激活的子图索引。因此,这行代码将创建一个包含两行一列子图的图,并激活第一个子图"""
plt.plot(Loss)
plt.title('Loss')
plt.show()
plt.subplot(2,1,2)
plt.plot(Accuracy)
plt.title('Accuracy')
plt.show()
#保存模型
print(model)
torch.save(model,'./models/models-mnist.pth')
test.py的代码如下:
import cv2
import matplotlib.pyplot as plt
import torch
import torch.nn.functional as F
from torchvision import datasets,transforms
from model import LeNet
if __name__=='__main__':
device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model=torch.load('./models/models-mnist.pth')
model=model.to(device)
model.eval() #把模型转为test模式
#读取要预测的图片
img=cv2.imread("./images/5_5.jpg")
img=cv2.resize(img,dsize=(32,32),interpolation=cv2.INTER_NEAREST)
plt.axis('off')#不显示坐标
plt.show()
#导入图片,图片扩展后为[1,1,32,32]
trans=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#图片转为灰度图,因为mnist数据集都是灰度图
img=trans(img)
img=img.to(device)
img=img.unsqueeze(0) #图片扩展多一维,因为输入到保存的模型中是4维的[batch_size,通道,长,宽],而普通图片只有三维,[通道,长,宽]
#预测
output=model(img)
prob=F.softmax(output,dim=1)#prob是10个分类的概率
print("概率:",prob)
value,predicted=torch.max(output.data,1)
predict=output.argmax(dim=1)
print("预测类别:",predict.item())