LeNet-5——CNN经典网络模型详解(pytorch实现)
一、LeNet-5
输出:分类结果,0~9之间的一个数因此我们可以知道,这是一个多分类问题,总共有十个类,因此神经网络的最后输出层必然是SoftMax问题,然后神经元的个数是10个。LeNet-5结构:
-
输入层:32*32的图片,也就是相当于1024个神经元 -
C1层:paper作者,选择6个特征卷积核,然后卷积核大小选择5 5,这样我们可以得到6个特征图,然后每个特征图的大小为32-5+1=28,也就是神经元的个数为628*28=784。
参考公式
-
S2层:这就是下采样层,也就是使用最大池化进行下采样,池化的size,选择(2,2),也就是相当于对C1层2828的图片,进行分块,每个块的大小为22,这样我们可以得到1414个块,然后我们统计每个块中,最大的值作为下采样的新像素,因此我们可以得到S1结果为:1414大小的图片,共有6个这样的图片。
-
C3层:卷积层,这一层我们选择卷积核的大小依旧为55,据此我们可以得到新的图片大小为14-5+1=10,然后我们希望可以得到16张特征图。那么问题来了?这一层是最难理解的,我们知道S2包含:6张14 * 14大小的图片,我们希望这一层得到的结果是:16张1010的图片。这16张图片的每一张,是通过S2的6张图片进行加权组合得到的,具体是怎么组合的呢?问题如下图所示:为了解释这个问题,我们先从简单的开始,我现在假设输入6特征图的大小是55的,分别用6个55的卷积核进行卷积,得到6个卷积结果图片大小为11,如下图所示: 为了简便起见,我这里先做一些标号的定义:我们假设输入第i个特征图的各个像素值为x1i,x2i……x25i,因为每个特征图有25个像素。因此第I个特征图经过55的图片卷积后,得到的卷积结果图片的像素值Pi可以表示成:这个是卷积公式因此对于上面的P1 ~ P6的计算方法,这个就是直接根据公式。然后我们把P1~P6相加起来,也就是:
P=P1+P2+……P6
把上面的Pi的计算公式,代入上式,那么我们可以得到:
P=WX
其中X就是输入的那6张55特征图片的各个像素点值,而W就是我们需要学习的参数,也就相当于6个55的卷积核,当然它包含着6*(5*5)个参数。因此我们的输出特征图就是:
Out=f(P+b)
这个就是从S2到C3的计算方法,其中b表示偏置项,f为激活函数。
我们回归到原来的问题:有6张输入1414的特征图片,我们希望用55的卷积核,然后最后我们希望得到一张10*10的输出特征图片?
根据上面的过程,也就是其实我们用55的卷积核去卷积每一张输入的特征图,当然每张特征图的卷积核参数是不一样的,也就是不共享,因此我们就相当于需要6(55)个参数。对每一张输入特征图进行卷积后,我们得到6张1010,新图片,这个时候,我们把这6张图片相加在一起,然后加一个偏置项b,然后用激活函数进行映射,就可以得到一张10*10的输出特征图了。
总之,C3层每个图片是通过S2图片进行卷积后,然后相加,并且加上偏置b,最后在进行激活函数映射得到的结果。
-
S4层:下采样层,比较简单,也是知己对C3的16张10 10的图片进行最大池化,池化块的大小为22。因此最后S4层为16张大小为5 5的图片。至此我们的神经元个数已经减少为:165*5=400。 -
C5层:我们继续用5*5的卷积核进行卷积,然后我们希望得到120个特征图。这样C5层图片的大小为5-5+1=1,也就是相当于1个神经元,120个特征图,因此最后只剩下120个神经元了。这个时候,神经元的个数已经够少的了,后面我们就可以直接利用全连接神经网络,进行这120个神经元的后续处理,
上面的结构,只是一种参考,在现实使用中,每一层特征图需要多少个,卷积核大小选择,还有池化的时候采样率要多少,等这些都是变化的,这就是所谓的CNN调参,我们需要学会灵活多变。
代码部分
采用pytorch官网的数据集
#model.py
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__()
self.covn1 = nn.Conv2d(3,16,5)
self.pool1 = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(16,32,5)
self.pool2 = nn.MaxPool2d(2,2)
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)
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(5*5*32)
x = F.relu(self.fc1(x)) #output(120)
x = F.relu(self.fc2(x)) #output(84)
x = self.fc3(x) #output(10)
return x
# #model调试
# import torch
# #定义shape
# input1 = torch.rand([32,3,32,32])
# model = LeNet()#实例化
# print(model)
# #输入网络中
# output = model(input1)
首先下载数据集
#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
#预处理函数
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 50000张训练图片
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform))
或者在网盘下载 链接:https://pan.baidu.com/s/1NBHp0SxEOJ5EIyYUsDHm_g 提取码:qp3k
先小部分测试一下
#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
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
#device : GPU or CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 50000张训练图片
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=False, num_workers=0)
# 10000张验证图片
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=4,
shuffle=False, num_workers=0)
val_data_iter = iter(val_loader)
val_image, val_label = val_data_iter.next()
print(val_image.size())
print(train_set.class_to_idx)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#显示图像,之前需把validate_loader中batch_size改为4
aaa = train_set.class_to_idx
cla_dict = dict((val, key) for key, val in aaa.items())
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
print(' '.join('%5s' % cla_dict[val_label[j].item()] for j in range(4)))
imshow(utils.make_grid(val_image))
正式测试
#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
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
#device : GPU or CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 50000张训练图片
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=False, num_workers=0)
# 10000张验证图片
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=5000,
shuffle=False, num_workers=0)
val_data_iter = iter(val_loader)
val_image, val_label = val_data_iter.next()
print(val_image.size())
# print(train_set.class_to_idx)
# classes = ('plane', 'car', 'bird', 'cat',
# 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
#
#
# #显示图像,之前需把validate_loader中batch_size改为4
# aaa = train_set.class_to_idx
# cla_dict = dict((val, key) for key, val in aaa.items())
# def imshow(img):
# img = img / 2 + 0.5 # unnormalize
# npimg = img.numpy()
# plt.imshow(np.transpose(npimg, (1, 2, 0)))
# plt.show()
#
# print(' '.join('%5s' % cla_dict[val_label[j].item()] for j in range(4)))
# imshow(utils.make_grid(val_image))
net = LeNet()
net.to(device)
loss_function = nn.CrossEntropyLoss()
#定义优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)
#训练过程
for epoch in range(10): # 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
#print(inputs.size(), labels.size())
# zero the parameter gradients
optimizer.zero_grad()#如果不清除历史梯度,就会对计算的历史梯度进行累加
# forward + backward + optimize
outputs = net(inputs.to(device))
loss = loss_function(outputs, labels.to(device))
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
if step % 500 == 499: # print every 500 mini-batches
with torch.no_grad():#上下文管理器
outputs = net(val_image.to(device)) # [batch, 10]
predict_y = torch.max(outputs, dim=1)[1]
accuracy = (predict_y == val_label.to(device)).sum().item() / val_label.size(0)
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
running_loss = 0.0
print('Finished Training')
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
进行预测
#predict.py
import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
import matplotlib.pyplot as plt
import numpy as np
#预处理
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'))
im = Image.open('飞机.jpg') #测试飞机
#im = Image.open('猫.jpg') #测试猫
#im = Image.open('轮船.jpg') #测试狗
#im = Image.open('狗。jpg’) #测试猫,可能出现错误
im1 = transform(im) # [C, H, W]
im = torch.unsqueeze(im1, dim=0) # [N, C, H, W]
#显示图像
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
imshow(im1)
with torch.no_grad():
outputs = net(im)
predict = torch.max(outputs, dim=1)[1].data.numpy()
predict1 = torch.softmax(outputs, dim=1)
print(predict1)
print(classes[int(predict)])