LeNet是于1989年提出的一种网络结构,目前在各大深度学习框架中使用的是简化版的LeNet-5,与原始的结构区别在于把激活函数换成了ReLU;
LeNet奠定了卷积层+池化层+全连接层的结构,初用于手写数字的识别问题,输入为单通道的灰度图;
LeNet网络结构图:
第一层:输入层
输入层是32 * 32大小的图像(Mnist数据库为28 * 28大小),目的是希望潜在的明显特征出现在最高层特征监视子感受野的中心;
Mnist数据集包括70,000个样本,其中训练集包括60,000个样本,测试集包括10,000个样本,训练集与测试集中书写体与手写体的数字(0~9)各占一半;
C1层是卷积层
6个特征图谱(feature map),5 * 5大小的卷积核;
每个feature map有(32 - 5 + 1)*(32 - 5 + 1)个神经元;
每个神经元与输入层的5 * 5大小的区域相连,所以C1层每个神经元有(5 * 5 + 1)*6个训练参数(5 * 5个连接参数核=和一个偏置参数);
通过卷积运算使原信号特征增强,并降低噪音;
不同卷积核能提取到图像不同特征;
S2层是下采样层
6个14 * 14特征图;
每个feature map中的每个神经元与C1层对应的feature map中的2 * 2区域相连,S2中每个神经元由这四个输入相加,乘以一个训练参数,再加上这个feature map的偏置参数,结果通过sigmoid函数计算而得;
S2的每个feature map由14 * 14个神经元;
参数个数为(1 + 1)* 6 = 12个;
连接数为(4 + 1)*(14 * 14)*6 = 5880个;
池化层的目的是为了降低网络训练参数及模型的过拟合程度;
池化方式有最大池化、平均池化;
C3层是一个卷积层
16个feature map,5 * 5大小的卷积核;
每个feature map有(14- 5 + 1)*(14 - 5 + 1)个神经元;
每个feature map由上一层的各feature map之间的不同组合;
S4层是下采样层
由16个5 * 5大小的feature map组成;
每个神经元与C3层对应的feature map中的2 * 2区域相连;
参数个数为(1 + 1)* 16 = 32个;
连接数为(4 + 1)*(5 * 5)*6 = 2000个(连接数大大降低了);
C5是一个卷积层
120个feature map,5 * 5大小的卷积核;
每个feature map有(5- 5 + 1)*(5 - 5 + 1)个神经元;
每个单元都与S4层的全部16个feature map的5 * 5区域相连;
F6层是全连接层
共有84个feature map;
每个feature map只有一个神经元与C5层全连接;
有(1 * 1 *120 + 1)*84 = 10164个参数和连接;
计算输入向量和权重向量之间的点积和偏置,传递给sigmoid函数计算神经元;
输出层
输出层也是全连接层;
共有10个节点,分别代表数字0 ~ 9(如果节点i的值为0,则该网络的识别结果为数字i);
输出的计算方式为(径向基函数RBF):
所以输出越接近0,则越接近i的比特图编码,即表示当前网络输入的识别结果为i。
LeNet模型代码(Pytorch):
import torch
import torchvision
import torch.utils.data as Data
from torch import nn
from torch.autograd import Variable
BATCH_SIZE = 50
LOAD_DOWN = False
LR = 0.001
EPOCH = 1
# 搭建LeNet网络
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1, 6, 5, 1, 2, bias=True),
nn.MaxPool2d(2)
)
self.conv2 = nn.Sequential(
nn.Conv2d(6, 16, 5, 1, 2, bias=True),
nn.MaxPool2d(2)
)
self.conv3 = nn.Sequential(
nn.Conv2d(16, 120, 5, 1, 2, bias=True)
)
self.fc = nn.Linear(120*7*7, 84)
self.predict = nn.Linear(84, 10)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
x = torch.sigmoid(x)
output = self.predict(x)
return output
# 训练集
train_data = torchvision.datasets.MNIST(
root='./mnist',
train=True,
transform=torchvision.transforms.ToTensor(),
download=LOAD_DOWN
)
train_loader = Data.DataLoader(
dataset=train_data,
batch_size=BATCH_SIZE,
shuffle=True
)
# 测试集
test_data = torchvision.datasets.MNIST(
root='./mnist',
train=False,
)
x_test = torch.unsqueeze(test_data.test_data, dim=1)
x_test = Variable(x_test).type(torch.FloatTensor)[:1000]/255
y_test = test_data.test_labels.data.numpy()[:1000]
# 实例化
lenet = LeNet()
optimizer = torch.optim.Adam(lenet.parameters(), lr=LR)
loss_func = nn.CrossEntropyLoss()
# 训练
for epoch in range(EPOCH):
for step, (x, y) in enumerate(train_loader):
b_x = Variable(x)
b_y = Variable(y)
prediction = lenet(b_x)
loss = loss_func(prediction, b_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 测试
if step % 50 == 0:
pred_out = lenet(x_test)
pred_out = torch.max(pred_out, 1)[1].data.numpy().squeeze() # 只返回每行最大值元素所在行的索引
accuracy = sum(pred_out == y_test) / y_test.size
print('loss:', loss.data.numpy(), '| accuracy:', accuracy)