摘要:本文介绍了经典的卷积神经网络LeNet及其实现。LeNet由Yann LeCun于1989年提出,是最早的CNN之一,主要用于手写数字识别。其结构包含两个卷积块(每个含卷积层、Sigmoid激活和平均池化)和三个全连接层。文中详细展示了PyTorch实现代码,并演示了28x28输入图像在各层的维度变化。同时提供了模型训练方法,包括数据加载、评估函数和GPU训练过程。最后介绍了一个3D可视化工具,可交互式观察LeNet-5各层处理过程,包括输入填充、卷积、池化及全连接层的具体参数变化。该可视化直观展示了CNN。
1.简单的CNN-LeNet
1.1结构解析
LeNet是最早发布的卷积神经网络之一,因其在计算机视觉任务中的高性能表现收到关注。LeNet是AT&T贝尔实验室的Yann LeCun在1989年发布的,目的是识别图像中的手写数字。LeNet被广泛应用在ATM机上。
LeNet有以下两个部分组成:
卷积编码器:由两个卷积层。
全连接层稠密块:由3个全连接层组成。
结构如下图所示(来自网络)。

每个卷积块都包含了一个卷积层,一个激活函数和一个池化层。第一个卷积层由6个通道,第二个卷积层由16个通道。池化层具有2x2的窗口和2的步距,通过空间采样将维数减少4倍。稠密块有3个全连接层,分别是120、84、10。
1.2构建
实现程序:
python
#模型
net=nn.Sequential(
#卷积层1,输入1转6通道,核函数5x5,填充2,激活函数Sigmoid
nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
#池化层1,平均池化,窗口2x2,步幅2
nn.AvgPool2d(kernel_size=2,stride=2),
#卷积层2,6通道变16通道,核函数5x5,激活函数Sigmoid
nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
#池化层2,平均池化,窗口2x2,步幅2
nn.AvgPool2d(kernel_size=2,stride=2),
nn.Flatten(),
#全连接层1,输出120,激活函数Sigmoid
nn.Linear(16*5*5,120),nn.Sigmoid(),
#全连接层2,输出84,激活函数Sigmoid
nn.Linear(120,84),nn.Sigmoid(),
#全连接层3,输出10
nn.Linear(84,10))
输入时28x28像素的单通道图像通过LeNet做前向计算。我们查看下每一层的数据。
python
X=torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
X=layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
结果:
Conv2d output shape: torch.Size([1, 6, 28, 28])
Sigmoid output shape: torch.Size([1, 6, 28, 28])
AvgPool2d output shape: torch.Size([1, 6, 14, 14])
Conv2d output shape: torch.Size([1, 16, 10, 10])
Sigmoid output shape: torch.Size([1, 16, 10, 10])
AvgPool2d output shape: torch.Size([1, 16, 5, 5])
Flatten output shape: torch.Size([1, 400])
Linear output shape: torch.Size([1, 120])
Sigmoid output shape: torch.Size([1, 120])
Linear output shape: torch.Size([1, 84])
Sigmoid output shape: torch.Size([1, 84])
Linear output shape: torch.Size([1, 10])
2. 模型训练
加载数据集
python
#模型训练
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size=batch_size)
评估准确率函数
python
def evalute_accuracy_gpu(net,data_iter,device=None):#@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net,nn.Module):
net.eval() #设置为评估模式
if not device:
device=next(iter(net.parameters())).device
#正确预测的数量,总预测的数量
metric=d2l.Accumulator(2)
with torch.no_grad():
for X,y in data_iter:
if isinstance(X,list):
#BERT微调所需的
X=[x.to(device) for x in X]
else:
X=X.to(device)
y=y.to(device)
metric.add(d2l.accuracy(net(X),y),y.numel())
return metric[0]/metric[1]
训练函数
python
def train_ch6(net,train_iter,test_iter,num_epochs,lr,device):
"""用GPU训练模型"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on',device)
net.to(device)
#优化函数选择了SGD,损失函数选择交叉熵
optimizer=torch.optim.SGD(net.parameters(),lr=lr)
loss=nn.CrossEntropyLoss()
animator=d2l.Animator(xlabel='epoch',xlim=[1,num_epochs],legend=['train loss','train acc','test acc'])
timer,num_batches=d2l.Timer(),len(train_iter)
for epoch in range(num_epochs):
"""训练损失之和,训练准确率之和,样本数"""
metric=d2l.Accumulator(3)
net.train()
for i,(X,y) in enumerate(train_iter):
timer.start() #计时器开始
optimizer.zero_grad() #优化函数初始化
X,y=X.to(device),y.to(device)
y_hat=net(X)
l=loss(y_hat,y) #计算损失
l.backward() #计算反向梯度
optimizer.step() #优化函数计算
with torch.no_grad():
metric.add(l*X.shape[0],d2l.accuracy(y_hat,y),X.shape[0])
timer.stop() #计时器停止
train_l=metric[0]/metric[2]
train_acc=metric[1]/metric[2]
if (i+1)%(num_batches//5)==0 or i==num_batches-1:
animator.add(epoch+(i+1)/num_batches,(train_l,train_acc,None))
test_acc=evalute_accuracy_gpu(net,test_iter)
animator.add(epoch+1,(None,None,test_acc))
print(f'loss {train_l:.3f},train acc {train_acc:.3f},' f'test acc {test_acc:.3f}')
print(f'{metric[2]*num_epochs/timer.sum():.1f} examples/sec' f'on{str(device)}')
训练模型
python
lr,num_epochs=0.9,10
train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())

3.CNN可视化
在资料查找中发现一个网址:3D Visualization of a Convolutional Neural Network

该网站提供了一个训练好的CNN网络,和基本的LeNet-5的结果类似。左上角可以用鼠标拖动输入数字,右侧控制每一层的的显示与否,中部是每一层每个像素的具体参数。该页面是在三维中开发,可通过不同视觉观察。CNN的具体配置如下:
输入:28x28,但是四周填充了2列,共32x32=1024个单元;
卷积层1:6通道,28x28=784个单元,tanh激活函数;
池化层1:6通道,14x14=196单元,最大池化;
卷积层2:16通道,10x10=100单元,tanh激活函数;
池化层2:16通道,5x5=25单元,最大池化;
稠密层1:120单元,tanh激活函数;
稠密层2:100单元,tanh激活函数;
稠密层3:10单元,tanh激活函数;
最终输出第一结果和第二结果。