【动手学深度学习PyTorch版】16 经典卷积神经网络 LeNet
创始人
2024-03-31 21:37:46
0

上一篇请移步【动手学深度学习PyTorch版】15 池化层_水w的博客-CSDN博客

目录

一、LeNet

1.1 手写数字识别

◼ 手写数字识别

◼ MNIST数据集

1.2 LeNet

1、INPUT层-输入层

2、C1层-卷积层

3、S2层-池化层(下采样层)

4、C3层-卷积层

5、S4层-池化层(下采样层)

6、C5层-卷积层

7、F6层-全连接层

 8、Output层-全连接层

1.3 总结

二、代码实现

2.1 LeNet网络(使用自定义)

2.2 LeNet在Fashion-MNIST数据集上的表现


一、LeNet

1.1 手写数字识别

◼ 手写数字识别

◼ MNIST数据集

当年的大数据:5万个训练数据集数据,1万个测试数据集数据,图像的大小为28x28,一共10类。

 

1.2 LeNet

全连接层的局限性:

  • 图像在同一列邻近的像素在这个向量中可能相距较远。它们构成的模式可能难以被模型识别。
  • 对于大尺寸的输入图像,使用全连接层容易导致模型过大。

卷积层的优势:

  • 卷积层保留输入形状。
  • 卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

LeNet分为卷积层块和全连接层块两个部分。

卷积神经网络就是含卷积层的网络。 LeNet交替使用卷积层和最大池化层后接全连接层来进行图像分类。

LeNet5 这个网络虽然很小,但是它包含了深度学习的基本模块:卷积层,池化层,全连接层。是其他深度学习模型的基础, 这里我们对LeNet5进行深入分析。同时,通过实例分析,加深对与卷积层和池化层的理解。

上图就是LeNet的网络结构,LeNet又被称为LeNet-5,其之所以称为这个名称是由于原始的LeNet是一个5层的卷积神经网络。LeNet-5共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个Feature Map是通过一种卷积滤波器提取输入的一种特征,然后每个Feature Map有多个神经元。

它主要包括两部分:

  • 卷积层
  • 全连接层

其中卷积层数为2,全连接层数为3。

1、INPUT层-输入层

首先是数据 INPUT 层,输入图像的尺寸统一归一化为32*32。

注意:本层不算LeNet-5的网络结构,传统上,不将输入层视为网络层次结构之一。

2、C1层-卷积层

输入图片:32*32

卷积核大小:5*5

卷积核种类:6

输出feature map大小:28*28 ,因为(32-5+1)=28

神经元数量:28*28*6

可训练参数:(5*5+1) * 6(每个滤波器5*5=25个单元参数(unit)和一个偏置参数(bias),一共6个滤波器)

连接数:(5*5+1)*6*28*28=122304

说明:对输入图像进行第一次卷积运算(使用 6 个大小为 5*5 的卷积核),得到6个C1特征图(6个大小为28*28的 feature maps, 32-5+1=28)。先来看看需要多少个参数,卷积核的大小为5*5,总共就有6*(5*5+1)=156个参数,其中+1是表示一个核有一个bias。对于卷积层C1,C1内的每个像素都与输入图像中的5*5个像素和1个bias有连接,所以总共有156*28*28=122304个连接(connection)。有122304个连接,但是我们只需要学习156个参数,主要是通过权值共享实现的--可以理解为把(5*5+1) * 6个训练参数共享了28*28次。

为什么是卷积?卷积运算一个重要的特点就是,通过卷积运算,可以使原信号特征增强,并且降低噪音),由6个特征图Feature Map构成。特征图中每个神经元与输入中5*5的邻域相连。特征图的大小为28*28,这样能防止输入的连接掉到边界之外(是为了BP反馈时的计算,不致梯度损失,个人见解)。C1有156个可训练参数(每个滤波器5*5=25个unit参数和一个bias参数,一共6个滤波器,共(5*5+1)*6=156个参数),共156*(28*28)=122,304个连接。

3、S2层-池化层(下采样层)

采样区域:2*2

采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

采样种类:6

输出featureMap大小:14*14,因为(28/2)

神经元数量:14*14*6

可训练参数:2*6(和的权+偏置这两个参数)

连接数:(2*2+1)*14*14*6

S2中每个特征图的大小是C1中特征图大小的1/4。

说明:第一次卷积之后就是池化运算,使用 2*2核 进行池化,得到了S2,6个14*14的 特征图(28/2=14)。S2这个pooling层是对C1中的2*2区域内的像素求和乘以一个权值系数再加上一个偏置,然后将这个结果再做一次映射。于是每个池化核有两个训练参数,所以共有2x6=12个训练参数,但是有5x14x14x6=5880个连接。

为什么是下采样?利用图像局部相关性的原理,对图像进行子抽样,可以减少数据处理量同时保留有用信息),有6个14*14的特征图。特征图中的每个单元与C1中相对应特征图的2*2邻域相连接。S2层每个单元的4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid函数计算。可训练系数和偏置控制着sigmoid函数的非线性程度。如果系数比较小,那么运算近似于线性运算,亚采样相当于模糊图像。如果系数比较大,根据偏置的大小亚采样可以被看成是有噪声的“或”运算或者有噪声的“与”运算。每个单元的2*2感受野并不重叠,因此S2中每个特征图的大小是C1中特征图大小的1/4(行和列各1/2)。有6个14*14的特征图。特征图中的每个单元与C1中相对应特征图的2*2邻域相连接。S2层每个单元的4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。每个单元的2*2感受野并不重叠,因此S2中每个特征图的大小是C1中特征图大小的1/4(行和列各1/2)。S2层有12(6*(1+1)=12)个可训练参数和5880(14*14*(2*2+1)*6=5880)个连接。

4、C3层-卷积层

输入:S2中所有6个或者几个特征map组合

卷积核大小:5*5

卷积核种类:16

输出featureMap大小:10*10  ,因为 (14-5+1)=10

可训练参数为:6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516

连接数:10*10*6*{(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)}=151600

C3特征图不是直接由S2和16个卷积核卷积运算直接得来的,而是采取特征图组合方式得出的,C3中前6个特征图来自于S2中任意连续的3个特征图作为输入与5* 5 *3大小的卷积核运算得来,S2中连续的3个特征图共有6种组合,所以得出C3中6个特征图则需要6个5* 5* 3的卷积核。C3中后6个特征图来自于S2中任意连续的4个特征图,类似的最终需要6个5 *5 *4大小的卷积核。C3中再后面的3个特征图来自于S2中两两不相邻的4个特征图,共3种组合所以需要3个5* 5 *4大小的卷积核。C3最后的一个特征图来自于S2中所有的特征图,因此需要的1个大小为5* 5* 6的卷积核。S4与S2的下采样方式类似,在C3上选择的单位处理区域大小为2*2,最后得到5* 5*16大小的特征图。

 说明:第一次池化之后是第二次卷积,第二次卷积的输出是C3,16个10x10的特征图,卷积核大小是 5*5. 我们知道S2 有6个 14*14 的特征图,怎么从6 个特征图得到 16个特征图了? 这里是通过对S2 的特征图特殊组合计算得到的16个特征图。具体如下:

C3的前6个feature map(对应上图第一个红框的6列)与S2层相连的3个feature map相连接(上图第一个框),后面6个feature map与S2层相连的4个feature map相连接(上图第二个框),后面3个feature map与S2层部分不相连的4个feature map相连接,最后一个与S2层的所有feature map相连。卷积核大小依然为5*5,所以总共有6*(3*5*5+1)+6*(4*5*5+1)+3*(4*5*5+1)+1*(6*5*5+1)=1516个参数。而图像大小为10*10,所以共有151600个连接。

C3与S2中前3个图相连的卷积结构如下图所示:

 

5、S4层-池化层(下采样层)

输入:10*10

采样区域:2*2

采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

采样种类:16

输出featureMap大小:5*5 ,因为(10/2)

神经元数量:5*5*16=400

可训练参数:2*16=32(和的权+偏置)

连接数:16*(2*2+1)*5*5=2000

S4中每个特征图的大小是C3中特征图大小的1/4

说明:S4是pooling层,窗口大小仍然是2*2,共计16个feature map,C3层的16个10x10的图分别进行以2x2为单位的池化得到16个5x5的特征图。

特征图中的每个单元与C3中相应特征图的2*2邻域相连接,跟C1和S2之间的连接一样。S4层有2x16共32个可训练参数(每个特征图1个因子和一个偏置)和5x5x5x16=2000个连接。

6、C5层-卷积层

输入:S4层的全部16个单元特征map(与s4全相连)

卷积核大小:5*5

卷积核种类:120

输出featureMap大小:1*1 ,因为(5-5+1)

可训练参数/连接:120*(16*5*5+1)=48120

说明:C5层是一个卷积层,有120个特征图,每个单元与S4层的全部16个单元的5*5邻域相连。

由于S4层的16个图的大小为5x5(同滤波器一样),与卷积核的大小相同,所以C5卷积后形成的特征图的大小为1x1,这构成了S4和C5之间的全连接。这里形成120个卷积结果。每个都与上一层的16个图相连。所以共有(5x5x16+1)x120 = 48120个参数,同样有48120个连接。

之所以仍将C5标示为卷积层而非全连接层,是因为如果LeNet-5的输入变大,而其他的保持不变,那么此时特征图的维数就会比1*1大。C5层有48120个可训练连接。


7、F6层-全连接层

输入:c5 120维向量

计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过Tanh函数输出。

F6层的激活函数是双曲正切函数tanh,将输出映射到(-1,1),对应到ascll编码

可训练参数:84*(120+1)=10164

说明:第6层是全连接层。F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。该层的训练参数和连接数是(120 + 1)x84=10164。ASCII编码图如下:

F6层有84个单元(之所以选这个数字的原因来自于输出层的设计),与C5层全相连。有10164个可训练参数。如同经典神经网络,F6层计算输入向量和权重向量之间的点积,再加上一个偏置。然后将其传递给sigmoid函数产生单元i的一个状态。

F6层的连接方式如下:

 8、Output层-全连接层

Output层也是全连接层,共有10个节点,分别代表数字0到9,且如果节点i的值为0,则网络识别的结果是数字i。
采用的是径向基函数(RBF)的网络连接方式。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:

上式w_ij 的值由i的比特图编码确定,i从0到9,j取值从0到7*12-1。RBF输出的值越接近于0,则越接近于i,即越接近于i的ASCII编码图,表示当前网络输入的识别结果是字符i。该层有84x10=840个参数和连接,没有偏置了。

上图是LeNet-5识别数字3的过程。

1.3 总结

LeNet-5是早期成功的神经网络,是一种用于手写体字符识别的非常高效的卷积神经网络。
卷积神经网络能够很好的利用图像的结构信息。
卷积层的参数相对较少,这也是由卷积层的主要特性是局部连接和共享权重所决定的

二、代码实现

2.1 LeNet网络(使用自定义)

LeNet(LeNet-5) 由两个部分组成:卷积编码器和全连接层密集块。

(1)自定义类:将X放成一个批量数不变,通道数变为1的1X28X28。

(2)先把1X28X28的图片放入卷积层里:输入通道1,输出通道6,核5x5,填充为2(因为图片元原始输入为32x32,28X28图片把两边各自的2行删掉了,所以此处额外添加)。

为了得到非线性性,在卷积后面加入nn.Sigmoid()激活函数。

(3)均值池化层:stride=2使得2X2的窗口不会被重叠在一起,

(4)卷积层:输入通道6,输出通道16,核5x5,为了得到非线性性,在卷积后面加入nn.Sigmoid()激活函数。

(5)均值池化层:stride=2使得2X2的窗口不会被重叠在一起,因为卷积层出来的是4D,所以把最后通道数,高和宽变成一维向量,输入到多层感知机。

(6)有两个隐藏层的多层感知机:

  • 线性:输入为16x5x5,输出为120,做Sigmoid激活;
  • 线性:输入为120,降到输出为84,做Sigmoid激活;
  • 线性:输入为84,降到输出为10(因为类别为10),做Sigmoid激活;
# LeNet(LeNet-5) 由两个部分组成:卷积编码器和全连接层密集块
import torch
from torch import nn
from d2l import torch as d2lclass Reshape(torch.nn.Module):"""自定义类:将X放成一个批量数不变,通道数变为1的1X28X28"""def forward(self,x):return x.view(-1,1,28,28) # 批量数自适应得到,通道数为1,图片为28X28net = torch.nn.Sequential(# 把1X28X28的图片放入卷积层里:输入通道1,输出通道6,核5x5,填充为2,为了得到非线性性,在卷积后面加入nn.Sigmoid()激活函数。Reshape(), nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),# 均值池化层:stride=2使得2X2的窗口不会被重叠在一起,nn.AvgPool2d(2,stride=2),# 卷积层:输入通道6,输出通道16,核5x5,为了得到非线性性,在卷积后面加入nn.Sigmoid()激活函数。nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),# 均值池化层:stride=2使得2X2的窗口不会被重叠在一起,因为卷积层出来的是4D,所以把最后通道数,高和宽变成一维向量,输入到多层感知机nn.AvgPool2d(kernel_size=2,stride=2),nn.Flatten(),## 有两个隐藏层的多层感知机:# 线性:输入为16x5x5,输出为120,做Sigmoid激活nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),# 线性:输入为120,降到输出为84,做Sigmoid激活nn.Linear(120, 84), nn.Sigmoid(),# 线性:输入为84,降到输出为10(因为类别为10),做Sigmoid激活nn.Linear(84,10))

定义好之后,随机给定一个输入,对里面的每一层做迭代,其中上一层的输出为这一层的输入。

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) # 上一层的输出为这一层的输入

 我们可以看到,每一层的输入和输出的具体变化情况。

(1)那么第一组模块:卷积+池化

Reshape output shape:	 torch.Size([1, 1, 28, 28])
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])

卷积层干的事情就是把通道是加6,高宽没变,激活函数也没变。

池化层:通道没变,高宽变了。

所以说,第一组模块:卷积+池化,等于是说把1X28X28的图片变成了6X14X14,即高宽减半,但是通道数增加了6倍。所以其实信息是变多了。

(2)那么第二组模块:卷积+池化

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])

输入到第二组模块的输入是6X14X14,整个第二组模块的输出是16X5X5,也就是说高宽仍然被减了大概3倍的样子,通道数从6变成了16,最后Flatten拉直了之后,变成了400。

(3)那么第三组模块:有两个隐藏层的多层感知机

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])

线性:输入为16x5x5,输出为120,做Sigmoid激活;
线性:输入为120,降到输出为84,做Sigmoid激活;
线性:输入为84,降到输出为10(因为类别为10),做Sigmoid激活。

总结就是说,不断的把空间信息压缩压缩,通道数不断增加,也就是说把抽出来的压缩的信息放到不同的通道里去,最后MLP把这些所有的模式拿出来,然后做全连接输出。

2.2 LeNet在Fashion-MNIST数据集上的表现

# LeNet在Fashion-MNIST数据集上的表现
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)    

对evaluate_accuracy函数进行轻微的修改,

# 对evaluate_accuracy函数进行轻微的修改
def evaluate_accuracy_gpu(net, data_iter, device=None):"""使用GPU计算模型在数据集上的精度"""if isinstance(net, torch.nn.Module):net.eval() # net.eval()开启验证模式,不用计算梯度和更新梯度if not device:device = next(iter(net.parameters())).device # 如果device没有给定,看net.parameters()中第一个元素的device为哪里metric = d2l.Accumulator(2) # 累加器for X, y in data_iter: # if isinstance(X,list):X = [x.to(device) for x in X] # 如果X是个List,则把每个元素都挪到device上else:X = X.to(device) # 如果X是一个Tensor,则只用移动一次,直接把X移动到device上y = y.to(device)  # 把y也挪过去# 把X放到network中得到输出,计算accuracy,然后用y.numel()计算y元素个数 metric.add(d2l.accuracy(net(X),y),y.numel())return metric[0]/metric[1]  # 分类正确的个数除以整个y的大小得到accuracy

为了使用GPU,还需要一点小改动,需要把把输入和输出,参数都挪到gpu上去。

# 为了使用GPU,还需要一点小改动
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):"""Train a model with a GPU"""def init_weights(m):"""初始化weight"""if type(m) == nn.Linear or type(m) == nn.Conv2d: # 如果是全连接层或者卷积层,使用xavier初始化nn.init.xavier_uniform_(m.weight) # 根据输入、输出大小,使得随即初始化后,输入和输出的的方差是差不多的              net.apply(init_weights) # 应用到整个net上print('training on',device) # 打印在哪个device上训练net.to(device) # 把整个参数挪到gpu的内存上optimizer = torch.optim.SGD(net.parameters(),lr=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): # 每次数据做迭代时,拿一个batch数据出来timer.start()optimizer.zero_grad() # 梯度设置为0X, y = X.to(device), y.to(device) # 把输入和输出挪到gpu上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 = evaluate_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)}')

训练和评估LeNet-5模型,

# 训练和评估LeNet-5模型
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

 

我们可以看到,测试精度和训练精度基本上重合的,没看到特别多的过拟合。

相关内容

热门资讯

Adobe 因使用 SlimP... AIPress.com.cn报道 12月29日消息,作为全球创意软件巨头,Adobe 正面临其首起重...
伟星新材:竞争优势明显 保持积... 12月28日,伟星新材(002372)发布公告,伟星新材(002372)于2025年12月25日召开...
健全数据制度 释放乘数效应——... 来源:经济日报 党的二十届四中全会审议通过的《中共中央关于制定国民经济和社会发展第十五个五年规划的建...
海关出口退税律师张严锋:套用其... 2018年3月2日,B稽查局对A公司涉税事项进行检查。经检查,B稽查局认为A公司涉嫌通过套用他人出口...
哈尔滨权威刑事律师服务推荐:谷... 在哈尔滨,当人们遭遇刑事法律问题时,往往会困惑于刑事律师服务哪家权威刑事律师推荐哪些刑事辩护律师哪个...
原创 《... 2025年12月26日,《晋中市平遥牛肉保护和发展条例》新闻发布会在晋中举行。该条例经山西省人大常委...
权威非法吸收公共存款律师推荐:... 在处理非法吸收公共存款这类复杂刑事案件时,选择一位经验丰富、靠谱且性价比高的律师至关重要。今天,我们...
政策领航聚英才 智农创新兴龙江 □刘凌灼 本报记者 姜斌 刘畅 在广袤的北大荒黑土地上,一片农业科技创新的“新沃土”正破土而出。这里...
上海海关商品归类律师张严锋:自... 自动衬环成型绕圆机如何归类 2023年01月01日至2024年05月30日期间,A公司以一般贸易方...
原创 男... 男子举报自己无证驾驶:进去待两天 朋友们,我最近看到一个很火的帖子,真的让我很感动!“男子自首承认无...