网络结构图
LeNet-5模型是Yan Lecun在1998年提出的一种卷积神经网络模型,最初设计用于手写数字的识别,是早期卷积神经网络中最有代表性的实验系统之一
LeNet的网络结构如下图所示
LeNet模型
LeNet分为卷积层块和全连接层块两个部分。
上图为LeNet-5识别手写体数字的过程,其包含输入层在内共有八层。
LeNet5由7层CNN(不包含输入层)组成,上图中输入的原始图像大小是32×32像素,卷积层用Ci表示,采样层(pooling,池化)用Si表示,全连接层用Fi表示。
输入层
数据输入层,输入图像的尺寸为32X32
1、C1层(卷积层):6@28×28
该层使用了6个卷积核,每个卷积核的大小为5×5,这样就得到了6个feature map(特征图)。
每个卷积核(5×5)与原始的输入图像(32×32)进行卷积,这样得到的feature map(特征图)大小为(32-5+1)×(32-5+1)= 28×28
2、S2层(下采样层,也称池化层):6@14×14
这一层主要是做池化或者特征映射(特征降维),池化单元为2×2,因此,6个特征图的大小经池化后即变为14×14。
3、C3层(卷积层):16@10×10
C3层有16个卷积核,卷积核大小为5×5。
4、S4(下采样层,也称池化层):16@5×5
与S2的分析类似,池化单元大小为2×2,因此,该层与C3一样共有16个特征图,每个特征图的大小为5×5。
5、C5层(卷积层):120
该层有120个卷积核,每个卷积核的大小仍为5×5,特征图大小为(5-5+1)×(5-5+1)= 1×1
6、F6层(全连接层):84
F6层有84个单元,之所以选这个数字的原因是来自于输出层的设计,对应于一个7×12的比特图,如下图所示,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码
7、OUTPUT层(输出层):10
Output层也是全连接层,共有10个节点,分别代表数字0到9
代码实现
import torch
import torchvision
from torch import nn, optim
# 加载数据函数
def load_data_mnist(batch_size, resize=None, root='~/Datasets/'):
"""Download the fashion mnist dataset and then load into memory."""
trans = []
if resize:
trans.append(torchvision.transforms.Resize(size=resize))
trans.append(torchvision.transforms.ToTensor())
transform = torchvision.transforms.Compose(trans)
mnist_train = torchvision.datasets.MNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.MNIST(root=root, train=False, download=True, transform=transform)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=0)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=0)
return train_iter, test_iter
# 定义网络
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5,padding=2,padding=2), # in_channels, out_channels, kernel_size
nn.ReLU(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(16*5*5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
net = LeNet()
batch_size = 256
# 加载数据
train_iter, test_iter = load_data_mnist(batch_size=batch_size,root=root)
# 评估
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
with torch.no_grad():
for X, y in data_iter:
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
net.train() # 改回训练模式
n += y.shape[0]
return acc_sum / n
# 训练
def train_ch5(net, train_iter, test_iter, batch_size, optimizer, num_epochs):
loss = torch.nn.CrossEntropyLoss()
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, batch_count = 0.0, 0.0, 0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
batch_count += 1
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc))
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer, num_epochs)