基于CNN的LeNet网络对CIFA-10数据集的模型训练及预测(多分类数据集)【图片分类和预测】【Pytorch】
前言
如果你还没有阅读过我的第一篇文章,请前去阅读一下,方便理解怎么搭建模型的。链接在下方!!!!
深度学习-图像分类(CNN)-CSDN博客
设计
-
使用LeNET方法进行模型训练
-
-
上图为LeNet方法搭建的训练模型
- 一个卷积层(Convolutiond)
- 一个下采样层(MaxPooling)
- 一个卷积层
- 一个下采样层
- 3个全连接层(注意与卷积层的区别,可以对照up视频上一讲的内容,全连接层所对应的神经元个数会比较多)
-
补充注意::全连接层的含义:(链接)
-
解释如何变为1 * 1* 120 的原因:如上图所示对于16 * 5 * 5 的输入,实际上就是用一个16 * 5 * 5 *120的卷积核去卷积激活函数的输出得到新的尺寸为(5-5+0)/1+1=1,故我们可以得到1 * 1 * 120 的全连接的层的输出
-
设计多层全连接函数的目的:大部分是两层以上呢这是为啥子呢,泰勒公式都知道吧,意思就是用多项式函数去拟合光滑函数,我们这里的全连接层中一层的一个神经元就可以看成一个多项式,我们用许多神经元去拟合数据分布,但是只用一层fully connected layer 有时候没法解决非线性问题而如果有两层或以上fully connected layer就可以很好地解决非线性问题了
使用的工具Pytorch
-
Pytorch的通道排序:
- batch:输入图片的个数,一组bantch
- channel:通道个数,比如RGB,3通道
- height:高
- weight:宽
-
model.py的设计:(详细见代码注释)
-
import torch.nn as nn import torch.nn.functional as F #卷积神经网络基于CCN的LeNET网络的模型训练 #模型结构分别为:一个全连接层,一个下采样层(maxpooling 2*2的下采样层) class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() #定义第一个卷积层 #Conv2d:[in_channel,out_channel(卷积核的个数),kernel_size(卷积核的大小,为5*5)] self.conv1 = nn.Conv2d(3, 16, 5) #MaxPool2d(池化层大小,步距) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(16, 32, 5) self.pool2 = nn.MaxPool2d(2, 2) #Liner为全连接层 self.fc1 = nn.Linear(32*5*5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) #正向传播的过程 def forward(self, x): x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28) #计算:(32-5+0)/1+1=28 ,输出的深度为卷积核的个数为16 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) #由于最后一个下采样层的输出为5*5*32,现在我们需要把他占成一个一维的向量然后通过全连接层实现有120个输出 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
下载CIFA-10数据集
-
核心代码:
transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # # 50000张训练图片 # 第一次使用时要将download设置为True才会自动去下载数据集 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) #train : 执行训练,download:是否被下载
-
使用torch自带的数据集进行下载,将download改为true,耐心等待即可
-
transfom函数:是一个图像预处理函数:
- toTensor()函数的处理为:源码注释如下,修改图片的展示,有H ,W , C更改为C,H,W变为torch的输入,同时修改 显示范围,从【0,255】变为 【0.0,1.0】
""" Convert a PIL Image or ndarray to tensor and scale the values accordingly. This transform does not support torchscript. Converts a PIL Image or numpy.ndarray (H x W x C) in the range [0, 255] to a torch.FloatTensor of shape (C x H x W) in the range [0.0, 1.0] if the PIL Image belongs to one of the modes (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1) or if the numpy.ndarray has dtype = np.uint8 """
-
Normalization()标准化处理:
"""Normalize a tensor image with mean and standard deviation. This transform does not support PIL Image. Given mean: ``(mean[1],...,mean[n])`` and std: ``(std[1],..,std[n])`` for ``n`` channels, this transform will normalize each channel of the input ``torch.*Tensor`` i.e., ``output[channel] = (input[channel] - mean[channel]) / std[channel]`` .. note:: This transform acts out of place, i.e., it does not mutate the input tensor. Args: mean (sequence): Sequence of means for each channel. std (sequence): Sequence of standard deviations for each channel. inplace(bool,optional): Bool to make this operation in-place. """
- 计算公式:输出 = (输入 - 均值) / 方差 == output[channel] = (input[channel] - mean[channel]) / std[channel]
-
模型训练:
预测模型对图片的读取正确与否
-
(先进性模型验证,观察是否能够读取出相应的图片)具体细节请仔细阅读代码及其代码注释
-
import torch import torchvision import torch.nn as nn from model import LeNet import torch.optim as optim import torchvision.transforms as transforms import matplotlib.pyplot as plt import numpy as np # # def main(): transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # # 50000张训练图片 # 第一次使用时要将download设置为True才会自动去下载数据集 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=transform) train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0) # 10000张验证图片 # 第一次使用时要将download设置为True才会自动去下载数据集 #从下载好的data内部拿数据,利用transform对图像进行预处理(可能会拿很多) val_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform) #下载:从val_set内部随机拿出batch_size张照片进行测验 val_loader = torch.utils.data.DataLoader(val_set, batch_size=3, shuffle=False, num_workers=0) #将测试的图片转换成可以迭代的迭代器 val_data_iter = iter(val_loader) val_image, val_label = next(val_data_iter) #val_image打印出来的是一组3 * 32 *32 的矩阵,val——label是图像在classes中的索引,后期可以注释掉,为了便于理解我没有注释,也可以自己进行debug print(val_image,val_label,"\n") classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') #参考pytorch官方给的代码示例观察读取出来的图片 def imshow(img): img = img/2+0.5 #进行非标准化处理,即前面我们进行了标准化处理,这里将其还原 npimg = img.numpy() plt.imshow(np.transpose(npimg,(1,2,0))) #由于我们读取进来的时候pytorch是按照C,H,W读取的,还原的时候需要进行还原成H,W,C的处理才能显示出图像使用transpose函数 plt.show() #打印出对应的标签 print(''.join('%5s'%classes[val_label[j]] for j in range (3))) #可视化一组图像 imshow(torchvision.utils.make_grid(val_image))
-
运行上述代码:得到下述图片信息:
- 我在这里预取了前3张照片,因为上述设计的batch_size为3,后续打印的标签的range范围也是3,否则会报错,注意细节!
- 同时注意观察对应图片的标签,第一个为猫(因为像素点过少的原因比较模糊),但后面的轮船ship可以清晰判别。
-
观察可知,读取正确接下来进行模型的训练、
模型训练
-
train.py(请看到这的读者慢慢看看注释,会有不一样的效果,加油!!!!)
import torch import torchvision import torch.nn as nn from model import LeNet import torch.optim as optim import torchvision.transforms as transforms import matplotlib.pyplot as plt import numpy as np # # def main(): transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # # 50000张训练图片 # 第一次使用时要将download设置为True才会自动去下载数据集,train为true表示的是导如的训练集合 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=transform) train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0) # 10000张验证图片 # 第一次使用时要将download设置为True才会自动去下载数据集 #从下载好的data内部拿数据,利用transform对图像进行预处理(可能会拿很多),train为false表示导入的是测试集合 val_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=transform) #下载:从val_set内部随机拿出batch_size张照片进行测验 val_loader = torch.utils.data.DataLoader(val_set, batch_size=10000, shuffle=False, num_workers=0) #将测试的图片转换成可以迭代的迭代器 val_data_iter = iter(val_loader) val_image, val_label = next(val_data_iter) #标签的分类 classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') net = LeNet() #设置交叉信息损失熵 loss_function = nn.CrossEntropyLoss() #选择Adam优化器,lr为学习率,可以自行经行调整学习率 optimizer = optim.Adam(net.parameters(), lr=0.001) for epoch in range(5): # loop over the dataset multiple times running_loss = 0.0 for step, data in enumerate(train_loader, start=0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients 历史损失梯度清零 optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs)#相当于调用了LeNET的forward函数 loss = loss_function(outputs, labels)#调用交叉信息损失熵,实际上内部有一个softmax函数,故后续在最后的连接层之后我们不在需要设计一个softmax函数了 loss.backward()#实现了误差权值的更新 optimizer.step()#实现了优化器的更新,一句Adam优化器的更新方式 # print statistics running_loss += loss.item() #每当训练完500次(0-499)就利用该模型验证一次,计算器训练损失,以及预测的精准度 if step % 500 == 499: # print every 500 mini-batches with torch.no_grad(): outputs = net(val_image) # 输出格式为[batch, 10] 输入训练网络LeNET predict_y = torch.max(outputs, dim=1)[1] #得出最大的索引值(得出结果第一维是batch,第二维才是index,可以见torch中的max函数的注释) #如果与原来标记val_label(测试集的标记)相一致的化,我们认为其验证正确,然后计算其在所有10000张图片的百分比为其accuracy accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0) #打印,epoch为轮次,step为第几个500,训练损失为累计损失除以训练图片的总数 print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' % (epoch + 1, step + 1, running_loss / 500, accuracy)) #将running_loss清零,进行下一个500验证 running_loss = 0.0 print('Finished Training') #保存训练网络的相关数据 save_path = './Lenet.pth' torch.save(net.state_dict(), save_path) if __name__ == '__main__': main()
- 运行上述代码可知下图的结果:
-
-
模型最后的正确率吧为0.673,其实已经很高了,一是因为该模型是在1998年提出的,其次我们可以修改优化器以及相应的学习率或者训练轮次可提高预测准确度
-
- 运行上述代码可知下图的结果:
模型预测
-
predict.py
import torch import torchvision.transforms as transforms from PIL import Image from model import LeNet def main(): transform = transforms.Compose( [transforms.Resize((32, 32)),#重新设置成为32*32*2 cifa-10的数据集是这么大 transforms.ToTensor(), #变成tensor数据类型 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')) for i in range(1,4,1): # print(i) im = Image.open(str(i)+'.jpg') im = transform(im) # [C, H, W] 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() #用在debug上面观察其返回的类型!!! # print(torch.max(outputs,dim=1)[1].numpy()) print(classes[int(predict)]) if __name__ == '__main__': main()
- 我在这里设置的1.jpg,2.jpg,3.jpg 分别为狗,飞机,轮船,同时得到预测结果为:(可以依据自己的不同设计照片进行更改)
- 结果:基本正确!
补充:工程项目目录:
结束语:
- 非常开心您可以看到这里!继续一起加油!!持续学习ing…