文章目录
- 一、LeNet-5网络结构简介
- 二、LeNet-5每一层的实现原理
- 2.1. 第一层 (C1) :卷积层(Convolution Layer)
- 2.2. 第二层 (S2) :池化层(Pooling Layer)
- 2.3. 第三层(C3):卷积层(Convolution Layer)
- 2.4. 第四层(S4):池化层(Pooling Layer)
- 2.5. 第五层(C5):卷积层(全连接卷积层)
- 2.6. 第六层(F6):全连接层(Fully Connected Layer)
- 2.7. 第七层(Output):全连接输出层(Softmax分类层)
- 三、matlab实现LeNet-5
- 四、训练测试网络
LeNet-5是一种经典的卷积神经网络(CNN),由Yann LeCun等人提出,最初用于手写数字识别任务。它的结构简单清晰,为CNN的发展奠定了基础。
一、LeNet-5网络结构简介
LeNet-5的网络结构如下图所示
详细参数:
层名 | 类型 | 输入尺寸 | 尺寸 | 输出尺寸 | 激活函数 |
---|---|---|---|---|---|
输入 | 图片 | 28×28×1 | - | 32×32×1 | |
C1 | 卷积 | 28×28×1,padding为2 | 5×5, 6个卷积核,步长为1 | 28×28×6 | relu |
S2 | 池化 | 28×28×6 | 2×2 池化,步长为2 | 14×14×6 | |
C3 | 卷积 | 14×14×6,padding为0 | 5×5, 16个卷积核 | 10×10×16 | relu |
S4 | 池化 | 10×10×16 | 2×2 池化,步长为2 | 5×5×16 | |
C5 | 卷积(全连接卷积) | 5×5×16 | 5×5, 120个卷积核 | 120×1 | relu |
F6 | 全连接层 | 120 | 全连接 | 84×1 | relu |
输出 | 全连接层 | 84 | 全连接 | 10 (数字类别) | softmax |
二、LeNet-5每一层的实现原理
2.1. 第一层 (C1) :卷积层(Convolution Layer)
输入: 一张灰度图像,尺寸为 28 × 28 × 1 28\times28\times1 28×28×1。
参数:
- 卷积核尺寸为 5 × 5 5\times5 5×5,共 6个卷积核。
- 偏置项 (bias):每个卷积核对应一个独立的偏置值,因此共有6个偏置值。
输出: 经过卷积后的特征图,共6个,尺寸为 28 × 28 × 6 28\times28\times6 28×28×6。
(1)卷积计算原理:
卷积层本质上是将卷积核(滤波器)与输入图像进行卷积操作:
Output ( x , y ) = f ( ∑ i = 1 5 ∑ j = 1 5 Kernel ( i , j ) × Input ( x + i , y + j ) + b ) \text{Output}(x,y) = f\left(\sum_{i=1}^{5}\sum_{j=1}^{5}\text{Kernel}(i,j)\times\text{Input}(x+i,y+j)+b\right) Output(x,y)=f(i=1∑5j=1∑5Kernel(i,j)×Input(x+i,y+j)+b)
- 这里:
- Kernel ( i , j ) \text{Kernel}(i,j) Kernel(i,j):表示卷积核的第 i i i行、第 j j j列元素的数值。
- Input ( x + i , y + j ) \text{Input}(x+i,y+j) Input(x+i,y+j):表示输入图像中对应位置上的数值。
- b b b:表示该卷积核的偏置项(bias)。
- f ( ⋅ ) f(\cdot) f(⋅):激活函数(例如sigmoid或tanh函数或relu函数),推荐使用relu函数。
上面公式中求和部分的计算原理可以通过下面简单的例子理解
(2)输出特征图尺寸的计算:
卷积后输出尺寸计算公式为:
Output Size = ( Input Size − Kernel Size ) + 2 × Padding Size Stride + 1 \text{Output Size} = \frac{(\text{Input Size}-\text{Kernel Size})+2\times\text{Padding Size}}{\text{Stride}} + 1 Output Size=Stride(Input Size−Kernel Size)+2×Padding Size+1
- 输入:28×28,卷积核:5×5,Stride(步长)=1,padding=2时,计算结果为:
( 28 − 5 ) + 2 × 2 1 + 1 = 28 \frac{(28 - 5)+2\times2}{1} + 1 = 28 1(28−5)+2×2+1=28
- 因此,输出特征图为28×28,6个卷积核则输出为28×28×6。
对于padding的理解,可以通过下面简单的例子理解
当padding为0时
当padding为2时
2.2. 第二层 (S2) :池化层(Pooling Layer)
输入: C1层输出特征图,共6个,尺寸为 28 × 28 × 6 28\times28\times6 28×28×6。
参数:
- 池化(Pooling)窗口大小: 2 × 2 2\times2 2×2,步长(Stride)= 2。
- 无需额外参数,无权值,通常采用平均池化或最大池化,推荐使用最大池化。
输出: 池化后的特征图,共6个,尺寸为 14 × 14 × 6 14\times14\times6 14×14×6。
(1)池化计算原理:
池化层的作用是降低特征图的尺寸,同时提取特征的重要信息,增强网络的泛化能力。
常见池化方法:
-
平均池化(Average Pooling): 取池化窗口区域内所有像素值的平均值作为输出。
例如窗口区域:
[ 2 4 6 8 ] \begin{bmatrix} 2 & 4\\ 6 & 8 \end{bmatrix} [2648]
平均池化的结果为:
2 + 4 + 6 + 8 4 = 5 \frac{2+4+6+8}{4}=5 42+4+6+8=5 -
最大池化(Max Pooling): 取池化窗口区域内的最大像素值作为输出。
以上面的区域为例:
max { 2 , 4 , 6 , 8 } = 8 \max\{2,4,6,8\}=8 max{2,4,6,8}=8
LeNet-5原论文中使用的是平均池化。
一个图形化的例子如下所示
(2)输出特征图尺寸的计算:
池化后输出尺寸计算公式为:
Output Size = Input Size − Pooling Size Stride + 1 \text{Output Size} = \frac{\text{Input Size}-\text{Pooling Size}}{\text{Stride}} + 1 Output Size=StrideInput Size−Pooling Size+1
- 输入:28×28,池化窗口:2×2,Stride=2时,计算结果为:
28 − 2 2 + 1 = 14 \frac{28-2}{2}+1=14 228−2+1=14
- 所以S2层输出特征图为 14 × 14 × 6 14\times14\times6 14×14×6。
2.3. 第三层(C3):卷积层(Convolution Layer)
输入: 第二层(S2)输出,尺寸为 14 × 14 × 6 14 \times 14 \times 6 14×14×6。
参数:
- 卷积核大小为 5 × 5 5\times 5 5×5,共 16个卷积核。
- 偏置(bias):每个卷积核一个偏置,共16个偏置。
这里卷积的计算需要注意的是,输入是多个通道了,这种情况下的计算可以通过下面的图示理解
一个简单的计算示例如下,这里输入是 4 × 4 × 3 4\times 4\times3 4×4×3,卷积核大小为 3 × 3 3\times3 3×3,但是也要是三通道
2.4. 第四层(S4):池化层(Pooling Layer)
输入: 第三层(C3)输出,尺寸为 10 × 10 × 16 10\times10\times16 10×10×16。
参数:
- 池化窗口大小: 2 × 2 2\times2 2×2,步长: 2 2 2。
- LeNet-5使用平均池化(Average Pooling),输出特征图尺寸为: 5 × 5 × 16 5\times5\times16 5×5×16。
2.5. 第五层(C5):卷积层(全连接卷积层)
输入:第四层(S4)输出特征图,尺寸为 5 × 5 × 16 5\times5\times16 5×5×16。
参数:
- 卷积核尺寸: 5 × 5 5\times5 5×5
- 卷积核数量:120个
- 偏置数量:120个(每个卷积核对应1个偏置)
由于输入特征图大小为 5 × 5 × 16 5\times5\times 16 5×5×16,而卷积核也为 5 × 5 5\times5 5×5,因此每个卷积核与输入特征图卷积后会变成 1 × 1 1\times1 1×1 的输出,这相当于对整个特征图做了全连接卷积(即卷积核与整个输入特征图区域进行完全连接)。因此,这一层又称为 全连接卷积层。
2.6. 第六层(F6):全连接层(Fully Connected Layer)
输入:第五层(C5)输出,共120个神经元(1维向量)。
参数:
- 全连接层的神经元数量:84个
- 权重数量: 84 × 120 84\times120 84×120(120个输入与84个神经元全连接)
- 偏置数量:84个(每个神经元一个偏置)
全连接层的计算本质为矩阵乘法+偏置+激活函数:
- 计算公式:
Output = f ( W × Input ( 120 × 1 ) + b ) \text{Output} = f\left(W \times \text{Input}_{(120\times1)}+b\right) Output=f(W×Input(120×1)+b)
其中:
- W W W:是一个大小为 84 × 120 84\times120 84×120 的权重矩阵
- b b b:大小为 84 × 1 84\times1 84×1 偏置向量
- f f f:激活函数(如tanh或sigmoid)
- 输出为84维的向量,即大小为: 84 × 1 84\times1 84×1。
2.7. 第七层(Output):全连接输出层(Softmax分类层)
输入:第六层(F6)输出的84个神经元(向量)。
参数:
- 输出类别:10个类别(0-9手写数字)
- 权重数量: 10 × 84 10\times84 10×84(84个输入与10个输出神经元全连接)
- 偏置数量:10个(每个输出神经元对应1个偏置)
最后一层通常是全连接层,并使用softmax函数作为分类器:
- 计算公式:
Z j = ∑ i = 1 84 w j , i x i + b j , j = 1 , 2 , . . . , 10 Z_j = \sum_{i=1}^{84}w_{j,i}x_i + b_j,\quad j=1,2,...,10 Zj=i=1∑84wj,ixi+bj,j=1,2,...,10
其中:
- Z j Z_j Zj:第 j j j个类别的线性输出值
- w j , i w_{j,i} wj,i:第 j j j个输出神经元与第 i i i个输入神经元的权重
- b j b_j bj:第 j j j个输出神经元的偏置
计算Softmax概率:
y j = e Z j ∑ k = 1 10 e Z k y_j = \frac{e^{Z_j}}{\sum_{k=1}^{10}e^{Z_k}} yj=∑k=110eZkeZj
- y j y_j yj:为第 j j j 类别的概率值,所有 y j y_j yj 和为1。
- 最终预测类别即为概率最大的类别。
- 输出尺寸为: 10 × 1 10\times1 10×1,表示10个类别的概率分布。
分析完每一层的原理后,我们就可以计算一下这个网络的可学习参量了
第一层:卷积核尺寸为 5 × 5 5\times5 5×5,共 6个卷积核,考虑到一个卷积核对应一个偏置量,所以
5 × 5 × 6 + 6 = 156 5\times5\times6+6=156 5×5×6+6=156
第二层:池化层无需要学习参量
第三层:卷积核大小为 5 × 5 5\times 5 5×5,共 16个卷积核,因为这一层的输入是6个通道,并考虑到偏置量,所以
5 × 5 × 6 × 16 + 16 = 2416 5\times5\times6\times16+16=2416 5×5×6×16+16=2416
第四层:池化层无需要学习参量
第五层:卷积核大小为 5 × 5 5\times 5 5×5,共 120个卷积核,因为这一层的输入是16个通道,并考虑到偏置量,所以
5 × 5 × 16 × 120 + 120 = 48120 5\times5\times16\times120+120=48120 5×5×16×120+120=48120
第六层:学习参数为权重数量加偏置,所以
84 × 120 + 84 = 10164 84\times120+84=10164 84×120+84=10164
第七层:学习参数为权重数量加偏置,所以
10 × 84 + 10 = 850 10\times84+10=850 10×84+10=850
所以,这个网络的总学习参数为
156 + 2416 + 48120 + 10164 + 850 = 61706 156+2416+48120+10164+850=61706 156+2416+48120+10164+850=61706
三、matlab实现LeNet-5
这里,我们需要借用到matlab工具栏里APPS里的Deep Network Designer,如下图所示
在Deep Network Designer, 我们创建一个空白Designer画布
然后我们可以拖动相应的层到Designer里,并连接各个层,如下图所示
然后,我们需要设置各个层的参量,对于输入图像来说
第一层:
第二层:注意到第一层和二层之间有一个Batchnormalization和激活函数,Batchnormalization一般是为了让训练效果更好,所有的卷积层和全连接层后一般都跟激活函数
第三层:
第四层:
第五层:
第六层:
第七层:

设置完成后,我们就可以用Analyze这个按钮分析这个网络了,分析内容很详细,可以看到这个网络的可学习参量为61.7k,跟我们上面计算的结果相同。至此,我们就完成了LeNet-5的搭建。
然后用Export按钮输出到工作区,这个网络的名称会自动命名为net_1。
四、训练测试网络
加载图像数据集
dataFolder = "DigitsData"; % 指定图像数据所在的文件夹路径
imds = imageDatastore(dataFolder, ...IncludeSubfolders=true, ... % 包括子文件夹中的图像LabelSource="foldernames"); % 使用文件夹名称作为图像的标签
imageDatastore
用于批量加载图像,并自动将图像按文件夹名称分类(用于监督学习)。
可视化部分图像
figure
tiledlayout("flow"); % 设置自适应布局以显示多个图像
perm = randperm(10000, 20); % 从 10000 张图像中随机选取 20 张
for i = 1:20nexttile % 在下一图块中显示图像imshow(imds.Files{perm(i)}); % 显示图像文件路径对应的图像
end
随机选出 20 张图像用于展示,验证数据是否正确读取和标注。
查看类别和样本数量
classNames = categories(imds.Labels); % 提取所有类别名称
labelCount = countEachLabel(imds); % 统计每个类别的图像数量
有助于检查数据是否均衡,每类是否数量相近。
查看图像尺寸
matlab复制编辑img = readimage(imds,1); % 读取第 1 张图像
size(img) % 查看图像尺寸(应该为 28×28×1)
确保图像大小和通道数(灰度或彩色)与网络输入一致。
划分训练集、验证集、测试集
[imdsTrain, imdsValidation, imdsTest] = splitEachLabel(imds, 0.7, 0.15, 0.15, "randomized");
从每个类别中随机划分出 70% 用于训练,15% 用于验证,15% 用于测试。
设置训练选项
options = trainingOptions("sgdm", ...InitialLearnRate=0.01, ... % 初始学习率MaxEpochs=4, ... % 最大训练轮数Shuffle="every-epoch", ... % 每个 epoch 打乱训练数据ValidationData=imdsValidation, ... % 指定验证数据集ValidationFrequency=30, ... % 每训练 30 个 mini-batch 进行一次验证Plots="training-progress", ... % 实时显示训练过程图表Metrics="accuracy", ... % 关注准确率指标Verbose=false); % 不在命令行输出详细训练信息
控制训练过程的关键参数,适用于小型数据集的快速实验。
训练神经网络
net = trainnet(imdsTrain, net_1, "crossentropy", options);
使用自定义网络
net_1
和交叉熵损失函数对训练集进行训练,返回训练好的网络net
。
在测试集上评估准确率
accuracy = testnet(net, imdsTest, "accuracy");
在未见过的测试集上评估模型性能,输出预测准确率(值范围 0~1)。
进行预测并生成标签
scores = minibatchpredict(net, imdsTest); % 对测试集进行批量预测,输出每类得分
YTest = scores2label(scores, classNames); % 将得分转换为预测的标签
scores
是每张图像对每个类别的得分;scores2label
会选择得分最高的类别作为预测结果。
可视化预测结果
numTestObservations = numel(imdsTest.Files); % 测试集中图像的总数
idx = randi(numTestObservations, 9, 1); % 随机选取 9 个图像索引用于展示figure
tiledlayout("flow") % 设置图像自动排列布局
for i = 1:9nexttileimg = readimage(imdsTest, idx(i)); % 读取图像imshow(img) % 显示图像title("Predicted Class: " + string(YTest(idx(i)))) % 显示预测类别
end
随机从测试集中挑选 9 张图像,并在图像上标注预测的类别结果,可用于人工评估模型表现。
完整代码如下
%%
dataFolder = "DigitsData"; % 指定图像数据所在的文件夹路径
imds = imageDatastore(dataFolder, ...IncludeSubfolders=true, ... % 包括子文件夹中的图像LabelSource="foldernames"); % 使用文件夹名称作为图像的标签figure
tiledlayout("flow"); % 设置自适应布局以显示多个图像
perm = randperm(10000, 20); % 从 10000 张图像中随机选取 20 张
for i = 1:20nexttile % 在下一图块中显示图像imshow(imds.Files{perm(i)}); % 显示图像文件路径对应的图像
end
%%
classNames = categories(imds.Labels); % 提取所有类别名称
labelCount = countEachLabel(imds); % 统计每个类别的图像数量%%
img = readimage(imds,1); % 读取第 1 张图像
size(img); % 查看图像尺寸(应该为 28×28×1)%%
% 从每个类别中随机划分出 70% 用于训练,15% 用于验证,15% 用于测试。
[imdsTrain, imdsValidation, imdsTest] = splitEachLabel(imds, 0.7, 0.15, 0.15, "randomized");%%
% options = trainingOptions("sgdm", ...
% InitialLearnRate=0.01, ...
% MaxEpochs=4, ...
% Shuffle="every-epoch", ...
% ValidationData=imdsValidation, ...
% ValidationFrequency=30, ...
% Plots="training-progress", ...
% Metrics="accuracy", ...
% Verbose=false);
options = trainingOptions("sgdm", ...InitialLearnRate=0.01, ... % 初始学习率MaxEpochs=4, ... % 最大训练轮数Shuffle="every-epoch", ... % 每个 epoch 打乱训练数据ValidationData=imdsValidation, ... % 指定验证数据集ValidationFrequency=30, ... % 每训练 30 个 mini-batch 进行一次验证Plots="training-progress", ... % 实时显示训练过程图表Metrics="accuracy", ... % 关注准确率指标Verbose=false); % 不在命令行输出详细训练信息%%
% 使用自定义网络 net_1 和交叉熵损失函数对训练集进行训练,返回训练好的网络 net。
net = trainnet(imdsTrain,net_1,"crossentropy",options);% 在未见过的测试集上评估模型性能,输出预测准确率(值范围 0~1)。
accuracy = testnet(net, imdsTest, "accuracy");scores = minibatchpredict(net, imdsTest); % 对测试集进行批量预测,输出每类得分
YTest = scores2label(scores, classNames); % 将得分转换为预测的标签numTestObservations = numel(imdsTest.Files); % 测试集中图像的总数
idx = randi(numTestObservations, 9, 1); % 随机选取 9 个图像索引用于展示figure
tiledlayout("flow") % 设置图像自动排列布局
for i = 1:9nexttileimg = readimage(imdsTest, idx(i)); % 读取图像imshow(img) % 显示图像title("Predicted Class: " + string(YTest(idx(i)))) % 显示预测类别
end