![](https://i-blog.csdnimg.cn/blog_migrate/1e794f4e5ff0a018c5bb899819b2cf35.png)
LeNet的结构如图所示,其网状结构比较简单,如果不包括输入,它一共是7层,输入层图像为32*32
-
C1 卷积层1
![](https://i-blog.csdnimg.cn/blog_migrate/2f921e3b2633f6c4d177cc5d6b124273.png)
卷积核(过滤器):6个5*5
特征图:6个28*28
训练参数:1 *(5 * 5) * 6+6=156
原图经过卷积核后生成的特征图大小计算:
(输入-卷积核+加边像素)/步长+1 →(32-5+0)/1+1
训练参数的计算方法:原图数量 * (卷积核大小) * 卷积核个数 +偏置项,其中池化层不需要训练参数,一个卷积核对应一个偏置项
此外,我们发现,即便步长为1,经过卷积后特征图片的尺寸变小了。但是由于过滤器在移动到边缘的时候就结束了,中间的像素点比边缘的像素点参与计算的次数要多。因此越是边缘的点,对输出的影响就越小,就有可能丢失边缘的信息。Padding,在图片外围补充一些像素点,这些像素点的值初始化为0
-
S2 池化层1
![](https://i-blog.csdnimg.cn/blog_migrate/30049940eb4bfcaf937f080026b395d3.png)
池化窗口:6个2*2
特征图:6个14*14
池化的目的是降低数据的维度,过程很简单就是向下采样,一般有两种生成池化特征的方式,分别为最大值池化,平均值池化
-
C3 卷积层2
![](https://i-blog.csdnimg.cn/blog_migrate/908703e8e64a1d5e477f1e8215ca1225.png)
卷积核:16个5*5
特征图:16个10*10
训练参数:6 * (5 * 5) * 16+16 =2416
如图所示,C3与S2的连接组合方式并不是固定的,C3的每一张特征图可以连接S2层中的全部或者部分特征图
-
S4 池化层2
![](https://i-blog.csdnimg.cn/blog_migrate/983239b1531d734b2a5224e09f8af8a8.png)
池化窗口:16个2*2
特征图:16个5*5
-
C5 全连接层1
![](https://i-blog.csdnimg.cn/blog_migrate/0cb67d20af43f5b7b2eeac84706f4651.png)
神经元结点:120个
训练参数:16 * (5 * 5)*120 +120=48120
每个神经元结点与S4层的16张特征图进行全连接
-
F6 全连接层2
![](https://i-blog.csdnimg.cn/blog_migrate/ab0dfb08faaf51621c2c94e13e14afb1.png)
神经元结点:84个
训练参数:120 * 84+84=10164
每个神经元与F5的120神经元全连接
-
OUTPUT 输出层
![](https://i-blog.csdnimg.cn/blog_migrate/f42289f3cdfdacbbf7c254e69d65cc1a.png)
神经元结点:10个
训练参数:84 * 10+10=850
把输出神经元节点数设为10,代表0~9的分值
每个神经元与F6的84个神经元全连接
-
1.构建LeNet
import torch
from torch import nn,optim
from torchvision import datasets,transforms
import torch.nn.functional as F
#datasets是加载图像数据的方法,transforms是图像数据预处理的方法
#定义数据预处理的方法
transform=transforms.Compose([
transforms.ToTensor(),
#将数据转化为Tensor对象
transforms.Normalize((0.1307,),(0.3081,))
#将数据进行归一化处理
])
#使用datasets.MNIST()函数分别下载训练数据集和测试数据集
trainset=datasets.MNIST('data',train=True,download=True,transform=transform)
testset=datasets.MNIST('data',train=False,download=True,transform=transform)
#第一个参数表示数据集下载并存储的目标文件夹,第二个参数True表示加载训练数据集,False表示加载测试数据集
#第三个参数表示自动下载,第四个参数表示使用刚才定义的数据预处理的方法
class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__()
#c1,c3定义卷积层,nn.Conv2d()函数为我们简化了卷积层的搭建
#第一个参数表示输入一张灰度图,第二个参数表示输出六张特征图
#第三个参数(5,5)可以简化成5,表示大小为5*5的卷积核过滤器
self.c1=nn.Conv2d(1,6,(5,5))
self.c3=nn.Conv2d(6,16,5)
#fc1,fc2,fc3定义连接层
#输入特征为16*4*4,输出为120
self.fc1=nn.Linear(16*4*4,120)
self.fc2=nn.Linear(120,84)
self.fc3=nn.Linear(84,10)
def forward(self,x):
x=F.max_pool2d(F.relu(self.c1(x)),2)
#对c1特征图进行池化,池化核大小2*2,可以简化参数为2
#c1卷积之后,使用relu()函数增加网络的非线性拟合能力
x=F.max_pool2d(F.relu(self.c3(x)),2)
x=x.view(-1,self.num_flat_features(x))
#view函数将x的形状转化成1维的向量
#在函数的参数中经常可以看到 - 1
#例如x.view(-1, 4)这里 - 1,表示一个不确定的数,就是你如果不确定你想要reshape成几行,但是你很肯定要reshape成4列,那不确定的地方就可以写成 - 1例如一个长度的16向量x,
#x.view(-1, 4)等价于x.view(4, 4)
x=F.relu(self.fc1(x))
#增加全连接后的非线性拟合能力
x=F.relu(self.fc2(x))
##输出结果一般不使用relu激活
x=self.fc3(x)
return x
# 自定义num_flat_features()用于计算特征点的总数
#假设批量输入4张图片,信息为[4,16,4,4],表示经过view函数之前的是x是4维的,转化成特征图16张,每张4*4
#因此计算特征点总数只需要后面的三维度size=x.size()[1:],即[16,4,4],16*4*4=256
def num_flat_features(self, x):
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
CUDA=torch.cuda.is_available()
if CUDA:
lenet=LeNet().cuda()
else:
lenet=LeNet()
criterion=nn.CrossEntropyLoss()
#momentum=0.9,动量0.9抱歉,目前不晓得干啥用的
optimizer=optim.SGD(lenet.parameters(),lr=0.001,momentum=0.9)
#随后使用PyTorch的数据加载工具DataLoad来加载训练数据
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=0)
#其中batch_size表示一次性加载的数据量,shuffle=True遍历不同批次的数据时打乱顺序,num_workers=0表示使用0个子进程加载数据(2为报错点)
#下面开始训练LeNet,完全遍历训练数据2次
def train(model,criterion,optimizer,epochs=1):
for epoch in range(epochs):
running_loss=0.0
for i,data in enumerate(trainloader,0):
#enumerate表示从第0项开始枚举对trainloader中的数据进行枚举,返回i是序号,data是我们需要的数据
inputs,labels=data
if CUDA:
inputs,labels=inputs.cuda(),labels.cuda()
optimizer.zero_grad()
outputs=model(inputs)
loss=criterion(outputs,labels)
loss.backward()
optimizer.step()
running_loss+=loss.item()
#对输入每一个训练数据后的loss值进行累加,每训练1000次打印一次loss的均值
if i%1000==999:
print('[Epoch:%d,Batch:%5d] loss:%.3f' % (epoch+1,i+1,running_loss/1000))
running_loss=0.0
print('Finished Training')
train(lenet,criterion,optimizer,epochs=2)
-
2.训练
[Epoch:1,Batch: 1000] loss:1.301
[Epoch:1,Batch: 2000] loss:0.340
[Epoch:1,Batch: 3000] loss:0.214
[Epoch:1,Batch: 4000] loss:0.166
[Epoch:1,Batch: 5000] loss:0.151
[Epoch:1,Batch: 6000] loss:0.132
[Epoch:1,Batch: 7000] loss:0.129
[Epoch:1,Batch: 8000] loss:0.108
[Epoch:1,Batch: 9000] loss:0.112
[Epoch:1,Batch:10000] loss:0.111
[Epoch:1,Batch:11000] loss:0.084
[Epoch:1,Batch:12000] loss:0.086
[Epoch:1,Batch:13000] loss:0.085
[Epoch:1,Batch:14000] loss:0.085
[Epoch:1,Batch:15000] loss:0.075
[Epoch:2,Batch: 1000] loss:0.061
[Epoch:2,Batch: 2000] loss:0.049
[Epoch:2,Batch: 3000] loss:0.072
[Epoch:2,Batch: 4000] loss:0.066
[Epoch:2,Batch: 5000] loss:0.070
[Epoch:2,Batch: 6000] loss:0.066
[Epoch:2,Batch: 7000] loss:0.072
[Epoch:2,Batch: 8000] loss:0.057
[Epoch:2,Batch: 9000] loss:0.061
[Epoch:2,Batch:10000] loss:0.050
[Epoch:2,Batch:11000] loss:0.059
[Epoch:2,Batch:12000] loss:0.059
[Epoch:2,Batch:13000] loss:0.044
[Epoch:2,Batch:14000] loss:0.059
[Epoch:2,Batch:15000] loss:0.056
Finished Training
从结果可以看出,我们的训练是有效的,损失值loss的平均值从1.301逐渐优化下降到0.056
-
3.存储与加载
训练完神经网络之后,需要存储训练好的参数,以方便以后使用。
存储方式有两种,第一种是:存储与加载模型。第二种是:存储和加载模型参数。
方法一:
//存储
torch.save(lenet,'model.pkl')
//加载
lenet=torch.load('model.pkl')
方法二:(更推荐)
//存储
torch.save(lenet.state_dict(),'model.pkl')
//加载
lenet.load_state_dict(torch.load('model.pkl'))
利用os包的exists()方法检查是否存在模型参数文件。构造load_param()和save_param()方便随时调用
def load_param(model,path):
if os.path.exists(path):
model.load_state_dict(torch.load(path))
def save_param(model,path):
torch.save(model.state_dict(),path)
-
4.测试
神经网络模型经过长时间的训练之后,能在训练集的数据上表现得很好,但是不一定在测试集上表现得很好,为此,设置train=False准备测试集,并设置testloader对测试集进行加载
testset=datasets.MNIST('data',train=False,download=True,transform=transform)
下面对训练完成的神经网络添加测试模块
def test(testloader,model):
corret=0
total=0
for data in testloader:
images,labels=data
if CUDA:
images=images.cuda()
labels=labels.cuda()
outputs=model(images)
#找出每一组10个输出值中最大的那个值,0是每列的最大值,1是每行的最大值,并将该值所在的序号保存在predicted中
_,predicted=torch.max(outputs.data,1)
#total用于累计labels的总数
total+=labels.size(0)
#corret用于累计正确的结果
corret+=(predicted==labels).sum()
print('Accuracy on the test set: %d %%' %(100*corret/total))
-
5.测试结果
Finished Training
Accuracy on the test set: 98 %
-
6.完整程序
import torch
import os
from torch import nn,optim
from torchvision import datasets,transforms
import torch.nn.functional as F
#datasets是加载图像数据的方法,transforms是图像数据预处理的方法
#定义数据预处理的方法
transform=transforms.Compose([
transforms.ToTensor(),
#将数据转化为Tensor对象
transforms.Normalize((0.1307,),(0.3081,))
#将数据进行归一化处理
])
#使用datasets.MNIST()函数分别下载训练数据集和测试数据集
trainset=datasets.MNIST('data',train=True,download=True,transform=transform)
testset=datasets.MNIST('data',train=False,download=True,transform=transform)
#第一个参数表示数据集下载并存储的目标文件夹,第二个参数True表示加载训练数据集,False表示加载测试数据集
#第三个参数表示自动下载,第四个参数表示使用刚才定义的数据预处理的方法
class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__()
#c1,c3定义卷积层,nn.Conv2d()函数为我们简化了卷积层的搭建
#第一个参数表示输入一张灰度图,第二个参数表示输出六张特征图
#第三个参数(5,5)可以简化成5,表示大小为5*5的卷积核过滤器
self.c1=nn.Conv2d(1,6,(5,5))
self.c3=nn.Conv2d(6,16,5)
#fc1,fc2,fc3定义连接层
#输入特征为16*4*4,输出为120
self.fc1=nn.Linear(16*4*4,120)
self.fc2=nn.Linear(120,84)
self.fc3=nn.Linear(84,10)
def forward(self,x):
x=F.max_pool2d(F.relu(self.c1(x)),2)
#对c1特征图进行池化,池化核大小2*2,可以简化参数为2
#c1卷积之后,使用relu()函数增加网络的非线性拟合能力
x=F.max_pool2d(F.relu(self.c3(x)),2)
x=x.view(-1,self.num_flat_features(x))
#view函数将x的形状转化成1维的向量
#在函数的参数中经常可以看到 - 1
#例如x.view(-1, 4)这里 - 1,表示一个不确定的数,就是你如果不确定你想要reshape成几行,但是你很肯定要reshape成4列,那不确定的地方就可以写成 - 1例如一个长度的16向量x,
#x.view(-1, 4)等价于x.view(4, 4)
x=F.relu(self.fc1(x))
#增加全连接后的非线性拟合能力
x=F.relu(self.fc2(x))
##输出结果一般不使用relu激活
x=self.fc3(x)
return x
# 自定义num_flat_features()用于计算特征点的总数
#假设批量输入4张图片,信息为[4,16,4,4],表示经过view函数之前的是x是4维的,转化成特征图16张,每张4*4
#因此计算特征点总数只需要后面的三维度size=x.size()[1:],即[16,4,4],16*4*4=256
def num_flat_features(self, x):
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
CUDA=torch.cuda.is_available()
if CUDA:
lenet=LeNet().cuda()
else:
lenet=LeNet()
criterion=nn.CrossEntropyLoss()
#momentum=0.9,动量0.9抱歉,目前不晓得干啥用的
optimizer=optim.SGD(lenet.parameters(),lr=0.001,momentum=0.9)
#随后使用PyTorch的数据加载工具DataLoad来加载训练数据
trainloader=torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=0)
#其中batch_size表示一次性加载的数据量,shuffle=True遍历不同批次的数据时打乱顺序,num_workers=0表示使用0个子进程加载数据(2为报错点)
testloader=torch.utils.data.DataLoader(testset,batch_size=4,shuffle=False)
#下面开始训练LeNet,完全遍历训练数据2次
def train(model,criterion,optimizer,epochs=1):
for epoch in range(epochs):
running_loss=0.0
for i,data in enumerate(trainloader,0):
#enumerate表示从第0项开始枚举对trainloader中的数据进行枚举,返回i是序号,data是我们需要的数据
inputs,labels=data
if CUDA:
inputs,labels=inputs.cuda(),labels.cuda()
optimizer.zero_grad()
outputs=model(inputs)
loss=criterion(outputs,labels)
loss.backward()
optimizer.step()
running_loss+=loss.item()
#对输入每一个训练数据后的loss值进行累加,每训练1000次打印一次loss的均值
if i%1000==999:
print('[Epoch:%d,Batch:%5d] loss:%.3f' % (epoch+1,i+1,running_loss/1000))
running_loss=0.0
print('Finished Training')
#存储与加载
def load_param(model,path):
if os.path.exists(path):
model.load_state_dict(torch.load(path))
def save_param(model,path):
torch.save(model.state_dict(),path)
#下面对训练完成的神经网络添加测试模块
def test(testloader,model):
corret=0
total=0
for data in testloader:
images,labels=data
if CUDA:
images=images.cuda()
labels=labels.cuda()
outputs=model(images)
#找出每一组10个输出值中最大的那个值,0是每列的最大值,1是每行的最大值,并将该值所在的序号保存在predicted中
_,predicted=torch.max(outputs.data,1)
#total用于累计labels的总数
total+=labels.size(0)
#corret用于累计正确的结果
corret+=(predicted==labels).sum()
print('Accuracy on the test set: %d %%' %(100*corret/total))
load_param(lenet,'model.pkl')
train(lenet,criterion,optimizer,epochs=2)
save_param(lenet,'model.pkl')
test(testloader,lenet)