6.7 经典卷积神经网络LeNet

🏷️sec0607_Convolutional_LeNet

通过前面的学习,我们已经基本了解了构建一个完整的卷积神经网络所需要的组件。回想我们在Softmax回归多层感知机中所提到的,我们不再需要将输入样本拉伸成一个784维的固定长度的一维向量了,而是保持图片的原始形态直接使用28×28的矩阵进行输入。这种方法可以有效地保留图像的空间结构信息,通过卷积层的使用,我们实现了对图像空间局部信息的提取,并且大大减少了模型所需要的参数,提高了设备的运算效率。

在本小节中,我们将介绍大名鼎鼎的LeNet5模型,它是最早发布的卷积神经网络,因其在计算机视觉任务中的高效性而被广泛关注。这个模型最早由AT&T贝尔实验室的研究员Yann LeCun在1989年提出,经过多次迭代后于1998年正式在论文(LeCun, 1998)中发布。LeCun认为,可训练参数的卷积层是一种用少量参数在图像上的多个位置提取相似特征的有效方法,这和直接把每个像素作为神经网络输入的多层感知机不同,这种模型能够更好地提取局部特征,并获得平移不变模式。与此同时,LeNet5通过反向传播算法成功地实现了卷积神经网络的训练,这种技术代表了十多年来神经网络研究开发的成果,并且沿用至今。

LeNet5是当时唯一能和支持向量机(support vector machines)性能相媲美的模型,由于它在MNIST手写数字数据集上能够达到99.2%的正确率,因此被广泛应用于美国自动取款机(ATM)的支票数字识别系统中。时至今日,一些自动取款机依然在运行着Yann LeCun和他的同事Leno Bottou在上个世纪90年代编写的代码。起源于LeNet5的卷积神经网络也依然是今天最主流的监督学习方法。

6.7.1 LeNet简介

总的来说,经典的LeNet5由输入层、2个卷积层、2个汇聚层和3个全连接层组成。其中卷积层和汇聚层部分一般称为特征编码器,而后面的全连接层部分称为特征解码器。在LeNet时代,每个完整的卷积模块都是由三个部分构成,包括是卷积层、Sigmoid激活函数层和平均汇聚层。值得注意的是,虽然我们知道ReLU激活函数和最大汇聚在卷积神经网络中效果更好,但是在上个世纪的八、九十年代它们都还没有出现。因此,当时的卷积层都是由一个5×5的卷积核和一个Sigmoid激活函数来实现特征提取的。LeNet架构的具体结构如 图6-30 所示。

图6-30 LeNet5 体系结构图

图6-30 LeNet5 体系结构图

如上图所示,LeNet5是以一个输入为28×28的图像作为起点的,在它之后是两个卷积模块,这一部分我们一般称之为特征编码器。因此,我们可以理解卷积层的功能就是实现对输入样本的特征编码,并从中获取有用的信息。这些编码器层能够将输入映射成多个不同的二维特征输出,每种二维特征输出都代表一种特定类型的特征。卷积核就是用来提取这些特定类型特征的工具。一般来说,经过卷积之类的操作后所生成的矩阵,就称为卷积特征图或者特征图(feature map),它们都是用来表征样本的高维矩阵。与传统的机器学习不同,我们事先并不知道每个卷积核提取的是哪一种特定特征。因为,卷积核所有的权重都是在训练过程中,通过拟合训练数据自动生成的。卷积核的数量代表了该卷积层想要生成的特征种类,它也对应到了该卷积层的输出通道数量。在LeNet5中,第一个卷积层有6个输出通道,第二个卷积层有16个输出通道。在每个卷积层后面都紧跟了一个汇聚层,它通过一个2×2的均值池化窗口,将输入的特征图降采样为原来的一半。卷积和汇聚的输出形状由输入特征图的高度、宽度和卷积核的个数决定。例如,卷积层C3接收从上一层来的6个14×14的特征图。它通过16个5×5的卷积核将原始的输入映射成16个10×10的特征图。此时,特征图的尺度变小了,但是通道数增加了。在卷积层之后,是一个2×2的汇聚窗口。它的功能是将刚刚由卷积层生成的16个10×10的特征图压缩成同等数量的5×5的特征图。不难发现,降采样操作并不改变通道的数量。总的来说,“卷积+汇聚”可以实现特征图尺度的缩小和通道数量的增加,这种基本的设计思想被一直沿用到今天的卷积神经网络的设计中。

为了将卷积模块的输出能够传递给后面的全连接层,我们必须要将每个样本的输出都展开为一个向量。换句话说,原来由特征编码器所生成的四维张量[N,C,H,W]将被转换为全连接层所期望的二维向量[N,D]。这里的四维张量的四个元素分别代表的是小批量中的样本数量以及最后一个卷积特征图的通道数、高度和宽度。二维向量中的第一个维度N表示的也是小批量中的样本数量,它与四维张量中的N的值是相同的,保持不变;第二个维度D表示的是每个样本展开成平面向量的维度。LeNet5的全连接层总共有三个,分别包含有120、84和10个输出。其中120和84是人为设定的超参数,最后的10对应于分类任务最终将要输出的结果的类别数量。需要知道的是,在最初的LeNet5模型中,最终的输出使用的是一个称为Gassian connections的层,它使用径向基函数(RBF)来计算向量和参数向量之间的欧式距离。目前,该功能层已经被Softmax归一化层所替代。

这就是最古老的卷积神经网络LeNet5。

下面,我们使用Paddle代码来实现LeNet5模型。

程序清单6-29 定义LeNet5模型类

#@save models.LeNet5
class LeNet5(paddle.nn.Layer):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2),
            nn.Sigmoid(),
            nn.AvgPool2D(kernel_size=2, stride=2),
            nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1),
            nn.Sigmoid(),
            nn.AvgPool2D(kernel_size=2, stride=2)
        )
        self.fc = nn.Sequential(
            nn.Linear(in_features=16*5*5, out_features=120),
            nn.Sigmoid(),
            nn.Linear(in_features=120, out_features=84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, inputs):
        x = self.features(inputs)
        x = paddle.flatten(x, 1)
        y = self.fc(x)
        return y

在上面的代码中,我们使用Paddle的动态图模式进行模型的创建,首先创建了一个名为 LeNet5 的类,该类继承自paddle.nn.Layer。在模型构建的时候,实例化了两个Sequential块来分别组建编码器部分和解码器部分。当然,如果需要你也可以将两个部分合成一个Sequential块。此外,我们去掉了原始模型最后一层的高斯函数。在训练部分,我们会使用一个Softmax激活层来替代它。除此之外,该网络与最初的LeNet5完全相同。

下面,我们将一个大小为 28×28 的灰度图送入到LeNet5模型中。通过打印每一层的输出形状,我们可以检查模型的设置是否与我们所期望 图6.7.1 的一致。

程序清单6-30 实例化LeNet5模型并输出模型结构

# codes06025_lenet_print
model = LeNet5()
paddle.summary(model, (1,1,28,28)) 

W1211 14:57:04.302058 23528 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.5, Driver API Version: 10.2, Runtime API Version: 10.2
W1211 14:57:04.566298 23528 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.

---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
   Conv2D-1       [[1, 1, 28, 28]]      [1, 6, 28, 28]          156      
   Sigmoid-1      [[1, 6, 28, 28]]      [1, 6, 28, 28]           0       
  AvgPool2D-1     [[1, 6, 28, 28]]      [1, 6, 14, 14]           0       
   Conv2D-2       [[1, 6, 14, 14]]     [1, 16, 10, 10]         2,416     
   Sigmoid-2     [[1, 16, 10, 10]]     [1, 16, 10, 10]           0       
  AvgPool2D-2    [[1, 16, 10, 10]]      [1, 16, 5, 5]            0       
   Linear-1          [[1, 400]]            [1, 120]           48,120     
   Sigmoid-3         [[1, 120]]            [1, 120]              0       
   Linear-2          [[1, 120]]            [1, 84]            10,164     
   Sigmoid-4         [[1, 84]]             [1, 84]               0       
   Linear-3          [[1, 84]]             [1, 10]              850      
===========================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.11
Params size (MB): 0.24
Estimated Total Size (MB): 0.35
---------------------------------------------------------------------------

从以上的输出结果来看,每个卷积块的输出特征图的尺度都缩小了。不过,在第一个卷积层中,由于使用了像素为2的填充来补偿5×5卷积核导致的特征图尺度减小,所以它的输出保持28×28不变。在之后的第二个卷积层,由于没有填充的补偿,因此它的高度和宽度都减少了4个像素。对于每个均值汇聚层来说,也因为使用了步长为2的2×2的池化窗口,因此每个汇聚层的输出特征图的尺度也都变为了原来的一半。从通道数来看,随着深度的增加,特征图的通道数量从输入时的1个,增加到了第一个卷积层之后的6个,再到第二个卷积层之后的16个。这说明,随着模型深度的增加,它所提取的特征的种类数量也增加了。最后,在全连接层中,特征的维度逐渐减少,最终输出一个维数和分类类别数相匹配的输出。

6.7.2 LeNet模型的训练

现在我们已经实现了LeNet5模型,接下来让我们一起来看看LeNet5在Fashion-MNIST数据集上的表现。此处,为了简便,我们直接调用本课程的高阶API来获取该Fashion-MNIST数据集(函数描述可参考 4.4节)。注意以下数据载入代码如果是首次运行,将会有一个下载数据集的过程。

程序清单6-31 载入Fashion Mnist数据集

# codes06026_load_dataset_fashion_mnist
import sys
sys.path.append(r'D:\WorkSpace\DeepLearning\WebsiteV2')   # 定义课程自定义模块保存位置
from utils import pdlPaddle as pdl

train_reader, test_reader = pdl.load_dataset_fashion_mnist(batch_size=256)

接下来,我们就可以使用LeNet5模型进行训练了。如下所示,训练函数train1()的定义与 4.5节 的定义是相同。与多层感知机相同,我们使用交叉熵损失函数和小批量随机梯度下降算法进行训练,并输出Top1和Top5精度。在训练过程中,我们同样调用 pdl.evaluate() 函数在测试集上对模型进行评估。

程序清单6-32 在Fashion Mnist数据集上训练LeNet5模型

# codes06027_train_fashion_mnist
# 1. 设置输入样本的维度、数据类型及名称
input_spec = paddle.static.InputSpec(shape=[None, 1, 28, 28], dtype='float32', name='image')
label_spec = paddle.static.InputSpec(shape=[None, 1], dtype='int64', name='label')

# 2. 载入预先定义好的网络,并实例化model变量
network = LeNet5()   
model = paddle.Model(network, input_spec, label_spec) 

# 3. 设置学习率、优化器、损失函数和评价指标
optimizer = paddle.optimizer.SGD(learning_rate=0.9, parameters=model.parameters()) 
model.prepare(optimizer,
            paddle.nn.CrossEntropyLoss(),
            paddle.metric.Accuracy(topk=(1,5)))

# 4. 启动训练过程
total_epoch = 10
pdl.train1(model, train_reader, test_reader, total_epoch)

train_loss:0.369, train_acc_top1:0.852, train_acc_top5:0.996, val_acc_top1:0.860, val_acc_top5:0.998
chapter06042Results_train_fashion_mnist_log_figure_SGD

6.7.3 小结

6.7.4 练习

  1. 对于一个卷积神经网络模型,规定它的输入为32×32的RGB,输入张量形状为[16,3,32,32],这里16表示()。
    A. batch_size
    B. channel
    C. height
    D. epoch

  2. MNIST是由NIST收集的来自于250个不同人的手写数字数据集,该数据集包含()个训练集图像。
    A. 250
    B. 10000
    C. 50000
    D. 60000

  3. 在经典的LeNet-5模型中,总共包含()个卷积层。
    A. 2
    B. 3
    C. 5
    D. 7

  4. 在LeNet5模型中,以下哪个层用于实现图像维度的降低?
    A. 卷积层
    B. 汇聚层(降采样层)
    C. 全连接层
    D. 高斯层

  5. 尝试构建一个比LeNet更复杂的网络,以提高MNIST-Fasion数据集的精度。例如,调整卷积核的尺度、输出通道数、激活函数、卷积层的数量、全连接层的数量以及学习率、优化方法等训练细节。

  6. 在MNIST数据上尝试第5题中改进的网络。

6.7 经典卷积神经网络LeNet