搭建一个经典的LeNet5神经网络
- 软件开发
- 2025-09-08 13:21:01

第一章:计算机视觉中图像的基础认知 第二章:计算机视觉:卷积神经网络(CNN)基本概念(一) 第三章:计算机视觉:卷积神经网络(CNN)基本概念(二) 第四章:搭建一个经典的LeNet5神经网络
一、LeNet-5背景LeNet-5是一种经典的卷积神经网络(CNN)架构,由Yann LeCun(杨立昆)等人于1998年提出,最初用于手写字符识别任务。
传统的模式识别方法依赖于人工设计的特征提取方法,这些方法存在许多局限性,如特征选择困难、对数据分布敏感等。
而LeNet-5通过引入卷积层和池化层,利用图像的空间相关性来自动提取特征。
LeNet-5最初用于MNIST数据集的手写数字识别任务,取得了高达99.2%的准确率。这一成就标志着深度学习在图像识别领域的突破,并为后续的神经网络研究奠定了基础。
LeNet-5的结构相对简单,但它仍然是理解CNN基本原理的重要起点。
二、LeNet-5架构LeNet-5的网络结构包括以下几层:
输入层:INPUT,接收32x32像素的灰度图像。卷积层:Convolutions,包含多个卷积核,用于提取图像特征。LeNet-5有两组卷积层(C1、C3),Subsampling,每个卷积层后接一个平均池化层(S2和S4)。全连接层:Full connection,将卷积层提取的特征图展平,并通过全连接层进行分类。输出层:OUTPUT,使用softmax函数输出分类结果。具体来说:
C1层:使用5x5的卷积核,输出6个特征图。S2层:使用2x2的平均池化核,将特征图尺寸减半。C3层:使用5x5的卷积核,输出16个特征图。S4层:再次使用2x2的平均池化核,将特征图尺寸减半。C5层:使用5x5的卷积核,输出120个特征图。F6层:一个全连接层,输出84个特征。输出层:使用softmax函数输出10个类别的概率。LeNet-5 的命名中,“5”表示网络包含 5层可训练参数层(2卷积层 + 3全连接层)。以下为典型结构:
层级参数配置输出尺寸说明输入层-32×32×1灰度图像输入Conv15×5卷积核,6通道28×28×6首次提取边缘特征Pool12×2池化,步长214×14×6下采样减少计算量Conv25×5卷积核,16通道10×10×16提取高阶组合特征Pool22×2池化,步长25×5×16进一步压缩空间维度Flatten展平操作400全连接层输入准备FC1120神经元120非线性特征映射FC284神经元84进一步抽象特征Output10神经元(对应0-9数字)10输出分类概率Conv1层的卷积核为什么是5×5,通道为什么是 6?不知道为什么,可能是基于经验、实验得出的。
三、实现LeNet5神经网络模型通过下面这段代码定义一个用于图像分类的卷积神经网络模型,并展示如何使用这个模型对输入数据进行预测。
3.1 定义模型第一种实现方式
import torch from torch import nn # 继承自 nn.Module,这是所有PyTorch模型的基础类。 class Model_1(nn.Module): """ 自定义一个神经网络 """ # 调用父类的构造函数 super(Model_1, self).__init__() 来初始化父类。 def __init__(self, in_channels=1, n_classes=10): """ 初始化 """ super(Model_1, self).__init__() # conv1 和 conv2 是两个卷积层,分别输出6个和16个特征图(通道数)。 # 每个卷积层使用5x5的卷积核,步长为1,不使用填充。 self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1, padding=0) # mp1 和 mp2 是最大池化层,用于减小特征图的空间尺寸。 # 这两个层使用2x2的窗口大小,步长也为2,同样不使用填充。 self.mp1 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0) self.mp2 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) # 展平为400维向量 # flatten 层将前面得到的多维特征图展平成一维向量,以便输入到全连接层中。 # 这里假设输入图像大小为32x32,在经过两次卷积和池化后,最终得到的是16通道的5x5特征图, # 因此展平后的向量长度为 16×5×5=400。 self.flatten = nn.Flatten(start_dim=1, end_dim=-1) # fc1, fc2, fc3 分别是三个全连接层。 # 第一个全连接层有120个神经元,第二个有84个,最后一个根据类别数量 n_classes 输出分类结果。 # 全连接层1 self.fc1 = nn.Linear(in_features=400, out_features=120) # 全连接层2 self.fc2 = nn.Linear(in_features=120, out_features=84) # 输出层 self.fc3 = nn.Linear(in_features=84, out_features=n_classes) def forward(self, x): """ 前向传播 在 forward 方法中定义了数据通过网络时的计算流程: 1.输入张量 x 首先通过第一个卷积层 conv1,然后是第一个最大池化层 mp1,接着是第二个卷积层 conv2 和第二个最大池化层 mp2。 2.然后,将特征图展平并通过三个全连接层 fc1, fc2, fc3 进行分类处理。 3.最终返回分类结果。 """ x = self.conv1(x) x = self.mp1(x) x = self.conv2(x) x = self.mp2(x) x = self.flatten(x) x = self.fc1(x) x = self.fc2(x) x = self.fc3(x) return x第一种实现方式直接定义了每个层(如卷积层、池化层和全连接层)作为类的属性,并在forward方法中顺序调用这些层。
第二种实现方式
class Model_2(nn.Module): """ 自定义一个神经网络 """ def __init__(self, in_channels=1, n_classes=10): """ 初始化 """ super(Model_2, self).__init__() # 1. 特征抽取 self.feature_extractor = nn.Sequential( # 卷积层1 nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1, padding=0), # 池化层1 nn.MaxPool2d(kernel_size=2, stride=2, padding=0), # 卷积层2 nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0), # 池化层2 nn.MaxPool2d(kernel_size=2, stride=2, padding=0) ) # 2. 分类输出 self.classifier = nn.Sequential( nn.Flatten(start_dim=1, end_dim=-1), nn.Linear(in_features=400, out_features=120), nn.Linear(in_features=120, out_features=84), nn.Linear(in_features=84, out_features=n_classes) ) def forward(self, x): """ 前向传播 """ # 1. 先做特征抽取 x = self.feature_extractor(x) # 2. 再做分类回归 x = self.classifier(x) return x第二种实现方式将特征抽取部分和分类输出部分分别封装到两个Sequential对象中,使代码更简洁易读。这种组织方式有助于分离关注点,使得网络结构更清晰。
上面有两个类定义,但实际上它们是重复的,只是第二种实现方式更加模块化。
3.2 层的解释 nn.Conv2d: 卷积层,用于提取图像的局部特征。第一个卷积层有6个5x5的滤波器,第二个卷积层有16个5x5的滤波器。nn.MaxPool2d: 最大池化层,用于降低特征图的空间维度。这里使用的是2x2的窗口大小。nn.Flatten: 将多维的输入一维化,常用在从卷积层过渡到全连接层时。nn.Linear: 全连接层(线性层),用于执行从输入特征到输出类别得分的映射。这里有三个连续的全连接层,最后的输出大小为n_classes,即类别数。 3.3 前向传播过程在forward函数中,输入数据首先通过一系列卷积和池化操作进行特征提取,然后通过Flatten层展平成一维张量,最后通过几个全连接层完成分类任务。
计算过程(输入为32x32图像):
Conv1:(32-5)/1 + 1 = 28 → 输出 6通道的28x28特征图MaxPool1:28/2 = 14 → 输出6通道的14x14特征图Conv2:(14-5)/1 + 1 = 10 → 输出16通道的10x10特征图MaxPool2:10/2 = 5 → 最终得到16通道的5x5特征图 3.4 模型测试 # 创建了一个`Model`实例,指定输入通道数为1(例如灰度图像) model = Model_2(in_channels=1) # 使用`torch.randn`生成形状为`(2, 1, 32, 32)`的随机输入数据,表示2个样本,每个样本是一个1通道32x32像素的图像 X = torch.randn(2, 1, 32, 32) # 调用模型`model(X)`进行前向传播,得到预测结果`y_pred` y_pred = model(X) # 打印`y_pred`的形状,预期输出形状应该是`(2, n_classes)`, # 其中`n_classes`是在初始化模型时指定的类别数量,默认为10 print(y_pred.shape) print(model) # 查看模型的结构输出:
torch.Size([2, 10]) Model_2( (feature_extractor): Sequential( (0): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1)) (1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (classifier): Sequential( (0): Flatten(start_dim=1, end_dim=-1) (1): Linear(in_features=400, out_features=120, bias=True) (2): Linear(in_features=120, out_features=84, bias=True) (3): Linear(in_features=84, out_features=10, bias=True) ) ) 四、参数规模卷积层参数量:(5x5x1x6 + 6) + (5x5x6x16 + 16) = 156 + 2416 = 2572
全连接层参数量:(400x120+ 120) + (120x84+84) + (84x10+10) = 48,120 + 10,164 + 850 = 59,134
维度变化:32x32 → 28x28 → 14x14 → 10x10 → 5x5 → 400 → 120 → 84 → 10
卷积层参数量的计算公式 对于 Conv2d(in_channels=C_in, out_channels=C_out, kernel_size=K):
权重参数:K × K × C_in × C_out偏置参数:C_out(每个输出通道一个偏置)总参数量 = 权重参数 + 偏置参数 = K²×C_in×C_out + C_out1. 第一层卷积 Conv1
输入通道:C_in=1输出通道:C_out=6卷积核:5×5权重参数:5×5×1×6 = 150偏置参数:6总参数量:150 + 6 = 1562. 第二层卷积 Conv2
输入通道:C_in=6输出通道:C_out=16卷积核:5×5权重参数:5×5×6×16 = 2400偏置参数:16总参数量:2400 + 16 = 24163. 卷积层总参数量
总计:156 (Conv1) + 2416 (Conv2) = 2572 全连接层参数计算(以 fc1 为例) Linear(in_features=400, out_features=120)权重参数:400×120 = 48,000偏置参数:120总参数量:48,000 + 120 = 48,120全网络总参数量
层类型参数量计算式参数量Conv15×5×1×6 + 6156Conv25×5×6×16 + 162416FC1400×120 + 12048,120FC2120×84 + 8410,164FC384×10 + 10850总计61,706使用下面代码,可以看到具体参数量:
# 遍历模型的所有子模块 for name, param in model.named_parameters(): if param.requires_grad: print(f"Layer: {name}") if 'weight' in name: print(f"Weights:{param.data.shape}") if 'bias' in name: print(f"Bias:{param.data.shape}\n")输出:
Layer: feature_extractor.0.weight Weights:torch.Size([6, 1, 5, 5]) Layer: feature_extractor.0.bias Bias:torch.Size([6]) Layer: feature_extractor.2.weight Weights:torch.Size([16, 6, 5, 5]) Layer: feature_extractor.2.bias Bias:torch.Size([16]) Layer: classifier.1.weight Weights:torch.Size([120, 400]) Layer: classifier.1.bias Bias:torch.Size([120]) Layer: classifier.2.weight Weights:torch.Size([84, 120]) Layer: classifier.2.bias Bias:torch.Size([84]) Layer: classifier.3.weight Weights:torch.Size([10, 84]) Layer: classifier.3.bias Bias:torch.Size([10])这整个过程演示了如何定义一个简单的卷积神经网络模型,并使用该模型对一批输入数据进行分类预测。
五、总结LeNet-5 是深度学习史上的里程碑,其设计哲学至今仍深刻影响着计算机视觉领域。尽管现代模型在深度和复杂度上远超LeNet-5,但其核心思想——通过卷积和池化逐步提取层级特征——仍然是所有CNN模型的基石。理解LeNet-5不仅能掌握CNN的基本原理,更能体会深度学习从理论到实践的关键突破。
搭建一个经典的LeNet5神经网络由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“搭建一个经典的LeNet5神经网络”