0

0

【PaddlePaddle】基础理论教程 - 卷积神经网络概论

P粉084495128

P粉084495128

发布时间:2025-07-31 13:28:21

|

1055人浏览过

|

来源于php中文网

原创

本文围绕二维卷积及卷积神经网络展开,先讲解二维卷积运算的概念、原理及Paddle框架实现,介绍卷积算子的定义、参数。还阐述卷积神经网络的卷积层、汇聚层算子,最后以LeNet为例,说明其在手写体数字识别任务中的数据集构建、模型构建、训练、评价与预测过程。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

【paddlepaddle】基础理论教程 - 卷积神经网络概论 - php中文网

一、 二维卷积

1.1. 二维卷积运算

在深度学习领域,卷积(Convolution)是卷积神经网络(Convolutional Neural Network,CNN)的核心操作。它通过对输入数据进行特定的数学运算,能够提取数据中的重要特征,广泛应用于图像、音频等多种数据处理任务。

  1. 基本概念

    • 在图像数据处理中,我们通常使用二维卷积。假设我们有一个输入图像,它可以看作是一个二维矩阵,每个元素代表图像中对应位置的像素值。同时,我们有一个卷积核(Convolution Kernel),也叫滤波器(Filter),同样是一个二维矩阵。二维卷积运算就是将卷积核在输入图像上滑动,在每个位置,将卷积核与对应位置的图像区域进行逐元素相乘,然后将这些乘积相加,得到一个输出值,这个输出值就是卷积结果在该位置的值。

    • 例如,假设有一个 (3×3)(3×3) 的输入图像矩阵 (I)(I) 和一个 (2×2)(2×2) 的卷积核 (K)(K):

    [I=[123456789]][I=⎣⎢⎡147258369⎦⎥⎤]

    [K=[1001]][K=[1001]]

    当卷积核 (K)(K) 从输入图像 (I)(I) 的左上角开始滑动时,首先计算:

    ((1×1+2×0)+(4×0+5×1)=1+5=6)((1×1+2×0)+(4×0+5×1)=1+5=6),这就是卷积结果左上角的值。然后卷积核向右滑动一个单位,继续上述计算过程,得到整个卷积结果。

  2. 数学原理

    • 设输入图像为 (I)(I),其大小为 (H×W)(H×W)(高度 (H)(H) 和宽度 (W)(W)),卷积核为 (K)(K),大小为 (h×w)(h×w)。输出特征图(Feature Map)为 (O)(O)。对于输出特征图 (O)(O) 中的每个元素 (O(i,j))(O(i,j)),其计算公式为:

    [O(i,j)=m=0h1n=0w1I(i+m,j+n)K(m,n)][O(i,j)=m=0∑h−1n=0∑w−1I(i+m,j+n)K(m,n)]

    其中 (i)(i) 和 (j)(j) 表示输出特征图中元素的位置,(m)(m) 和 (n)(n) 用于遍历卷积核中的元素。这个公式本质上就是在每个位置对输入图像和卷积核进行对应元素相乘并求和的过程。

  3. 在 Paddle 框架中的实现

    • 在 PaddlePaddle 中,可以使用 paddle.nn.functional.conv2d 函数来实现二维卷积运算。下面是一个简单的代码示例:
In [2]
import paddleimport paddle.nn.functional as F# 1. 生成输入张量# 这里我们生成一个随机的输入张量,其形状为 [batch_size, in_channels, height, width]# batch_size 表示一次处理的数据样本数量,这里设为 1 意味着我们一次只处理一个样本# in_channels 表示输入数据的通道数,对于彩色图像,通常有 3 个通道(红、绿、蓝),这里我们也设为 3# height 和 width 分别表示输入图像的高度和宽度,这里都设为 5input_tensor = paddle.randn([1, 3, 5, 5])# 2. 生成卷积核张量# 卷积核张量的形状为 [out_channels, in_channels, kernel_height, kernel_width]# out_channels 表示卷积运算后输出特征图的通道数,这里设为 1,即输出一个特征图# in_channels 要和输入张量的通道数一致,因为卷积操作需要在每个通道上进行# kernel_height 和 kernel_width 分别表示卷积核的高度和宽度,这里都设为 3kernel = paddle.randn([1, 3, 3, 3])# 3. 进行二维卷积运算# 使用 paddle.nn.functional.conv2d 函数进行二维卷积运算# 该函数的第一个参数是输入张量,第二个参数是卷积核张量# 这里没有设置 stride(步幅)和 padding(填充),默认 stride 为 1,padding 为 0output = F.conv2d(input_tensor, kernel)# 4. 输出结果的形状# 打印输出结果的形状,观察卷积操作后数据的变化print("输入张量的形状:", input_tensor.shape)print("卷积核的形状:", kernel.shape)print("输出特征图的形状:", output.shape)
       
输入张量的形状: [1, 3, 5, 5]
卷积核的形状: [1, 3, 3, 3]
输出特征图的形状: [1, 1, 3, 3]
       
  • 代码解释:
  1. 输入张量的生成:

    • input_tensor = paddle.randn([1, 3, 5, 5]):使用 paddle.randn 函数生成一个形状为 [1, 3, 5, 5] 的随机张量。其中,1 代表 batch_size,表示一次处理一个样本;3 代表 in_channels,模拟彩色图像的三个通道;5 和 5 分别代表输入图像的高度和宽度。
  2. 卷积核张量的生成:

    • kernel = paddle.randn([1, 3, 3, 3]):同样使用 paddle.randn 函数生成一个形状为 [1, 3, 3, 3] 的随机张量作为卷积核。1 代表 out_channels,即卷积后输出一个特征图;3 与输入张量的通道数一致,保证卷积操作在每个通道上进行;3 和 3 分别代表卷积核的高度和宽度。
  3. 二维卷积运算:

    • output = F.conv2d(input_tensor, kernel):调用 paddle.nn.functional.conv2d 函数进行二维卷积运算。该函数会将卷积核在输入张量上按照卷积运算规则进行滑动,对每个位置进行逐元素相乘并求和,最终得到输出特征图。由于没有显式设置 stride 和 padding,函数默认 stride 为 1(卷积核每次移动一个像素),padding 为 0(不进行填充)。
  4. 输出结果形状的打印:

    • 通过 print 函数分别打印输入张量、卷积核和输出特征图的形状,方便观察卷积操作对数据形状的影响。在默认 stride 为 1,padding 为 0 的情况下,根据卷积运算的形状计算公式:
    • 输出特征图的高度 (Hout=HinHkernel+2×paddingstride+1)(Hout=⌊strideHin−Hkernel+2×padding⌋+1)
    • 输出特征图的宽度 (Wout=WinWkernel+2×paddingstride+1)(Wout=⌊strideWin−Wkernel+2×padding⌋+1) 这里 (Hin=Win=5)(Hin=Win=5),(Hkernel=Wkernel=3)(Hkernel=Wkernel=3),(padding=0)(padding=0),(stride=1)(stride=1),代入可得 (Hout=Wout=53+2×01+1=3)(Hout=Wout=⌊15−3+2×0⌋+1=3),再结合 batch_size 和 out_channels,输出特征图的形状为 [1, 1, 3, 3]。
In [1]
import paddleimport paddle.nn.functional as F# 1. 生成输入张量# batch_size 表示一次处理的数据样本数量。在深度学习训练中,通常不会一次只处理一个样本,# 而是将多个样本组合成一个批次(batch)进行处理,这样可以提高计算效率。# 这里我们将 batch_size 设置为 2,意味着一次处理 2 个样本。# in_channels 表示输入数据的通道数。以图像数据为例,彩色图像一般有 3 个通道(红、绿、蓝),# 这里我们将 in_channels 设置为 3,模拟彩色图像的情况。# height 和 width 分别表示输入图像的高度和宽度,这里都设置为 5,代表输入图像是 5x5 大小的。batch_size = 2in_channels = 3height = 5width = 5input_tensor = paddle.randn([batch_size, in_channels, height, width])# 2. 生成卷积核张量# out_channels 表示卷积运算后输出特征图的通道数。不同的 out_channels 可以让卷积核学习到不同类型的特征。# 这里我们将 out_channels 设置为 4,意味着经过卷积操作后会得到 4 个不同的特征图。# 卷积核的 in_channels 必须和输入张量的 in_channels 一致,因为卷积操作需要在每个输入通道上进行。# kernel_height 和 kernel_width 分别表示卷积核的高度和宽度,这里都设置为 3,即使用 3x3 的卷积核。out_channels = 4kernel_height = 3kernel_width = 3kernel = paddle.randn([out_channels, in_channels, kernel_height, kernel_width])# 3. 进行二维卷积运算# 使用 paddle.nn.functional.conv2d 函数进行二维卷积运算。# 该函数的第一个参数是输入张量,第二个参数是卷积核张量。# 这里没有设置 stride(步幅)和 padding(填充),默认 stride 为 1,padding 为 0。# 步幅控制卷积核在输入张量上滑动的步长,填充是在输入张量的边界添加额外的值(通常是 0),# 以控制输出特征图的大小。output = F.conv2d(input_tensor, kernel)# 4. 输出结果的形状# 打印输入张量、卷积核和输出特征图的形状,方便观察卷积操作对数据形状的影响。print("输入张量的形状:", input_tensor.shape)print("卷积核的形状:", kernel.shape)print("输出特征图的形状:", output.shape)print(output.shape)
       
/opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages/paddle/utils/cpp_extension/extension_utils.py:686: UserWarning: No ccache found. Please be aware that recompiling all source files may be required. You can download and install ccache from: https://github.com/ccache/ccache/blob/master/doc/INSTALL.md
  warnings.warn(warning_message)
       
输入张量的形状: [2, 3, 5, 5]
卷积核的形状: [4, 3, 3, 3]
输出特征图的形状: [2, 4, 3, 3]
[2, 4, 3, 3]
       
  • 代码解释:
  1. 输入张量的生成:

    • batch_size 体现了深度学习中批量处理的思想,通过一次性处理多个样本,可以充分利用计算资源(如 GPU 的并行计算能力),加快训练速度。
    • in_channels 对于图像数据非常关键,不同的通道数代表了不同类型的图像信息。彩色图像的 3 个通道分别存储红、绿、蓝三种颜色的强度值。
    • height 和 width 确定了输入图像的尺寸大小,这是图像数据的重要属性。
    • paddle.randn([batch_size, in_channels, height, width]) 生成一个具有指定形状的随机张量,模拟输入的图像数据。
  2. 卷积核张量的生成:

    • out_channels 决定了卷积操作后能提取到的不同特征的数量。不同的卷积核可以学习到输入图像的不同特征,如边缘、纹理等。
    • 卷积核的 in_channels 必须与输入张量的 in_channels 匹配,这样才能在每个输入通道上进行卷积操作。
    • kernel_height 和 kernel_width 定义了卷积核的大小,不同大小的卷积核可以捕捉不同尺度的特征。
    • paddle.randn([out_channels, in_channels, kernel_height, kernel_width]) 生成一个随机的卷积核张量。
  3. 二维卷积运算:

    • F.conv2d(input_tensor, kernel) 执行二维卷积操作。默认的 stride = 1 表示卷积核每次在输入张量上滑动一个像素;padding = 0 表示不进行填充,即直接在输入张量上进行卷积。
  4. 输出结果的形状:

    • 打印各部分的形状有助于理解卷积操作对数据的影响。
    • 详细解释了输出特征图形状的计算原理,通过代入具体的值,让清晰地看到如何根据输入和卷积核的参数计算出输出的形状。最终输出特征图的形状为 [batch_size, out_channels, H_out, W_out],在这个例子中就是 [2, 4, 3, 3]。

1.2 二维卷积算子

1.2.1 卷积算子定义

  • 作用
    • 二维卷积算子在深度学习尤其是卷积神经网络(CNN)中扮演着核心角色。其主要作用是对输入数据进行特征提取。在图像领域,原始图像数据包含大量像素信息,但这些信息是杂乱无章的,直接用于分类、识别等任务效率极低。卷积算子通过卷积运算,能够从这些原始像素中提取出有意义的特征,如物体的边缘、纹理、形状等。这些特征是后续进行图像分类、目标检测等任务的基础。
    • 从模型训练的角度来看,卷积算子还具有参数共享的特性,这大大减少了模型的参数数量,降低了计算复杂度和过拟合的风险。例如,在处理一张大尺寸图像时,如果不使用卷积算子,每个像素都需要单独的参数来处理,参数数量会极其庞大;而使用卷积算子,相同的卷积核在不同位置进行卷积运算,参数可以共享,大大节省了计算资源。
  • 原理
    • 从数学原理上,二维卷积算子将卷积核(一个二维矩阵)与输入数据(同样是二维矩阵,如灰度图像)进行卷积运算。具体来说,卷积核在输入数据上按照一定的规则滑动,在每个位置,将卷积核的元素与对应位置的输入数据元素逐元素相乘,然后将这些乘积相加,得到输出特征图上的一个值。这个过程不断重复,直到卷积核遍历完整个输入数据,最终生成完整的输出特征图。
    • 从信号处理的角度理解,卷积运算可以看作是一种滤波操作。卷积核就像是一个滤波器,不同的卷积核可以提取输入信号中的不同频率成分。在图像处理中,高频成分通常对应图像的边缘、细节等信息,低频成分对应图像的整体轮廓、大致形状等信息。通过设计不同的卷积核,可以提取出输入图像中不同类型的特征。
  • 实际使用示例
    • 在人脸识别系统中,卷积算子用于提取人脸图像的特征。首先,输入的人脸图像经过一系列卷积层处理,每个卷积层使用不同的卷积核。例如,第一个卷积层可能使用一些简单的卷积核来提取人脸的边缘特征,如眉毛、眼睛的轮廓等;后续的卷积层可能使用更复杂的卷积核来提取人脸的纹理特征,如皮肤的纹理等。这些提取到的特征会被输入到后续的全连接层进行分类,从而判断输入的人脸图像属于哪个人。

1.2.2 卷积算子参数

卷积核大小(Kernel Size)
  • 作用
    • 卷积核大小决定了卷积算子在输入数据上的感受野范围。感受野是指卷积层输出特征图上的一个像素点对应输入数据的区域大小。较小的卷积核(如 1×11×1、2×22×2、3×33×3)感受野小,能够聚焦于输入数据的局部细节,提取出局部的纹理、边缘等特征。例如,在处理手写数字图像时,小卷积核可以捕捉到数字笔画的断点、拐角等细微特征。
    • 较大的卷积核(如 5×55×5、7×77×7)感受野大,可以捕捉到更全局的特征,如物体的整体形状、大致轮廓等。在图像分类任务中,大卷积核有助于把握图像中物体的整体结构信息,对于判断物体的类别非常重要。
  • 原理
    • 当卷积核在输入数据上滑动时,其大小直接决定了每次参与卷积运算的输入数据区域大小。较小的卷积核每次只覆盖输入数据的一小部分,因此对局部信息更加敏感;而较大的卷积核覆盖的范围更广,能够综合更多的局部信息,从而提取出更宏观的特征。
    • 从计算复杂度的角度来看,较大的卷积核通常需要更多的计算量,因为每次卷积运算涉及的元素更多。而小卷积核计算量相对较小,在保证一定特征提取能力的前提下,可以提高计算效率。
  • 实际使用示例
    • 在经典的 VGG 网络中,使用了多个 3×33×3 的卷积核堆叠。3×33×3 卷积核虽然感受野小,但通过堆叠多个卷积层,可以在增加网络深度的同时,有效地提取图像的特征。例如,两个 3×33×3 的卷积核堆叠的效果相当于一个 5×55×5 的卷积核,但参数数量更少。

用 PaddlePaddle 实现不同卷积核大小的示例代码:

Shakespeare
Shakespeare

一款人工智能文案软件,能够创建几乎任何类型的文案。

下载
In [4]
import paddleimport paddle.nn.functional as F# 生成一个随机的输入张量,形状为 [batch_size, in_channels, height, width]input_tensor = paddle.randn([1, 3, 32, 32])# 定义 3x3 卷积核kernel_3x3 = paddle.randn([1, 3, 3, 3])
output_3x3 = F.conv2d(input_tensor, kernel_3x3)# 定义 5x5 卷积核kernel_5x5 = paddle.randn([1, 3, 5, 5])
output_5x5 = F.conv2d(input_tensor, kernel_5x5)print("3x3 卷积核输出形状:", output_3x3.shape)print("5x5 卷积核输出形状:", output_5x5.shape)
       
3x3 卷积核输出形状: [1, 1, 30, 30]
5x5 卷积核输出形状: [1, 1, 28, 28]
       
步幅(Stride)
  • 作用
    • 步幅控制了卷积核在输入数据上滑动的速度。较大的步幅可以减少卷积运算的次数,从而降低计算量,提高计算效率。同时,步幅的增大也会使输出特征图的尺寸变小,相当于对输入数据进行了下采样。在一些情况下,我们希望减少数据的维度,去除一些冗余信息,这时可以使用较大的步幅。
    • 较小的步幅(如步幅为 1)会使卷积核在输入数据上逐个像素地滑动,覆盖输入数据的每一个可能位置,输出特征图能够保留更多的输入数据信息,尺寸相对较大。
  • 原理
    • 当步幅为 ss 时,卷积核每次在输入数据上水平和垂直方向都移动 ss 个像素。根据输出特征图尺寸的计算公式:

[Hout=HinHkernel+2×paddings+1][Hout=⌊sHin−Hkernel+2×padding⌋+1]

[Wout=WinWkernel+2×paddings+1][Wout=⌊sWin−Wkernel+2×padding⌋+1]

其中 HinHin 和 WinWin 是输入数据的高度和宽度,HkernelHkernel 和 WkernelWkernel 是卷积核的高度和宽度,paddingpadding 是填充的数量。可以看出,步幅 ss 越大,输出特征图的高度 HoutHout 和宽度 WoutWout 就越小。

  • 实际使用示例
    • 在图像分类任务中,当输入图像尺寸较大时,为了减少计算量,可以在某些卷积层使用较大的步幅。例如,在 ResNet 网络中,一些卷积层使用步幅为 2 的卷积操作来进行下采样,减少特征图的尺寸.

使用不同步幅的 PaddlePaddle 示例代码:

In [5]
import paddleimport paddle.nn.functional as F# 生成一个随机的输入张量,形状为 [batch_size, in_channels, height, width]input_tensor = paddle.randn([1, 3, 32, 32])
kernel = paddle.randn([1, 3, 3, 3])# 步幅为 1output_stride_1 = F.conv2d(input_tensor, kernel, stride=1)# 步幅为 2output_stride_2 = F.conv2d(input_tensor, kernel, stride=2)print("步幅为 1 时输出形状:", output_stride_1.shape)print("步幅为 2 时输出形状:", output_stride_2.shape)
       
步幅为 1 时输出形状: [1, 1, 30, 30]
步幅为 2 时输出形状: [1, 1, 15, 15]
       
填充(Padding)
  • 作用
    • 填充的主要作用是控制卷积运算后输出特征图的尺寸。在不使用填充的情况下,卷积核在输入数据的边界处可能无法完全覆盖输入数据,导致输出特征图的尺寸小于输入数据。通过在输入数据的边界周围添加额外的像素值(通常是 0),可以使卷积核在边界处也能正常进行卷积运算,从而保持输出特征图的尺寸与输入数据相近,或者满足特定的尺寸要求。
    • 填充还可以使卷积核更好地处理输入数据的边界部分,避免边界信息的丢失。在一些情况下,边界信息对于图像的特征提取也非常重要,如物体的边缘可能位于图像的边界处。
  • 原理
    • 在进行卷积运算之前,在输入数据的上下左右边界添加一定数量的像素值。填充的数量由 padding 参数决定,例如 padding = 1 表示在输入数据的边界周围添加一层 0。这样,在卷积核滑动到输入数据的边界时,有足够的元素参与运算,从而使输出特征图的尺寸能够根据我们的需求进行调整。根据前面提到的输出特征图尺寸计算公式,合适的填充值可以使输出特征图的高度和宽度与输入数据相同或满足特定的比例关系。
  • 实际使用示例
    • 在一些需要保持特征图尺寸不变的卷积层中,通常会使用填充。例如,在 LeNet 网络中,为了使卷积层输出的特征图尺寸与输入图像尺寸相同,使用了适当的填充。

使用填充的 PaddlePaddle 示例代码:

In [6]
import paddleimport paddle.nn.functional as F# 生成一个随机的输入张量,形状为 [batch_size, in_channels, height, width]input_tensor = paddle.randn([1, 3, 32, 32])
kernel = paddle.randn([1, 3, 3, 3])# 不使用填充output_no_padding = F.conv2d(input_tensor, kernel)# 使用填充,padding = 1output_with_padding = F.conv2d(input_tensor, kernel, padding=1)print("不使用填充时输出形状:", output_no_padding.shape)print("使用填充时输出形状:", output_with_padding.shape)## 通过合理调整卷积核大小、步幅和填充这些参数,可以灵活地控制卷积算子的行为,以适应不同的任务需求,提高模型的性能和效率。
       
不使用填充时输出形状: [1, 1, 30, 30]
使用填充时输出形状: [1, 1, 32, 32]
       

二 卷积神经网络的基础算子

2.1 卷积层算子

2.1.1 卷积层算子概述

在卷积神经网络(Convolutional Neural Network, CNN)中,卷积层算子是核心组成部分之一。它的主要功能是从输入数据中提取特征,这些特征对于后续的分类、识别等任务至关重要。卷积层通过卷积运算将卷积核应用到输入数据上,产生一系列的特征图(Feature Map),每个特征图对应一种特定的特征。

2.1.2 卷积层的工作原理

  • 多通道卷积:在实际应用中,输入数据往往具有多个通道,例如彩色图像有红、绿、蓝三个通道。对于多通道输入,卷积核也需要具有相同的通道数。每个卷积核在所有输入通道上进行卷积运算,然后将各通道的卷积结果相加,得到一个单通道的特征图。如果有多个卷积核,就会产生多个特征图,这些特征图共同构成了卷积层的输出。
    • 设输入数据的形状为 [Cin,Hin,Win][Cin,Hin,Win],其中 CinCin 是输入通道数,HinHin 和 WinWin 分别是输入的高度和宽度。卷积核的形状为 [Cout,Cin,kh,kw][Cout,Cin,kh,kw],其中 CoutCout 是输出通道数,khkh 和 kwkw 分别是卷积核的高度和宽度。对于第 jj 个输出通道的特征图上的 (i1,i2)(i1,i2) 位置的元素 yj,i1,i2yj,i1,i2,其计算公式为:

[yj,i1,i2=c=0Cin1m=0kh1n=0kw1xc,i1+m,i2+nkj,c,m,n+bj][yj,i1,i2=c=0∑Cin−1m=0∑kh−1n=0∑kw−1xc,i1+m,i2+nkj,c,m,n+bj]

其中 xx 是输入数据,kk 是卷积核,bjbj 是第 jj 个输出通道的偏置项。

  • 偏置项(Bias):为了增加模型的表达能力,每个输出通道通常会有一个偏置项。偏置项是一个标量,在卷积运算得到的结果上加上偏置项,相当于对卷积结果进行了平移操作。

2.1.3 卷积层在 Paddle 框架中的实现

In [13]
import paddleimport paddle.nn as nn

paddle.disable_static()

x_var = paddle.uniform((2, 4, 8, 8), dtype='float32', min=-1., max=1.)

conv = nn.Conv2D(4, 6, (3, 3))
y_var = conv(x_var)print(y_var.shape)
       
[2, 6, 6, 6]
       
In [14]
import paddleimport paddle.nn as nn# 定义输入数据,形状为 [batch_size, in_channels, height, width]batch_size = 1in_channels = 3height = 32width = 32input_tensor = paddle.randn([batch_size, in_channels, height, width])# 定义卷积层out_channels = 16kernel_size = 3stride = 1padding = 1# 使用 nn.Conv2D 创建卷积层对象conv_layer = nn.Conv2D(in_channels, out_channels, kernel_size, stride=stride, padding=padding)# 进行卷积运算output = conv_layer(input_tensor)print("输入张量的形状:", input_tensor.shape)print("卷积层输出的形状:", output.shape)
       
输入张量的形状: [1, 3, 32, 32]
卷积层输出的形状: [1, 16, 32, 32]
       

在上述代码中,使用 nn.Conv2D 类定义了一个卷积层。in_channels 表示输入通道数,out_channels 表示输出通道数,kernel_size 是卷积核的大小,stride 是步幅,padding 是填充。通过调用卷积层对象 conv_layer 并传入输入张量 input_tensor,就可以得到卷积层的输出。

2.2 汇聚层算子

1. 汇聚层算子概述

汇聚层(Pooling Layer)也是卷积神经网络中的重要组成部分。它的主要作用是对输入的特征图进行下采样,减少特征图的尺寸,从而降低模型的计算复杂度和参数数量,同时还能增强模型的鲁棒性。汇聚层通过在特征图上进行局部区域的统计操作,得到一个更小尺寸的特征图。

2. 常见的汇聚方式

  • 最大汇聚(Max Pooling):最大汇聚是最常用的汇聚方式之一。它在输入特征图的每个局部区域(通常是一个矩形窗口)中选取最大值作为该区域的输出。最大汇聚可以保留特征图中的主要特征信息,同时对局部的小变化具有一定的鲁棒性。例如,在图像识别中,物体的边缘、角点等重要特征通常具有较大的值,最大汇聚可以有效地保留这些特征。
  • 平均汇聚(Average Pooling):平均汇聚则是在每个局部区域中计算所有元素的平均值作为该区域的输出。平均汇聚可以平滑特征图,减少噪声的影响,但可能会丢失一些重要的细节信息。

3. 汇聚层的工作原理

以最大汇聚为例,假设输入特征图的形状为 [C,Hin,Win][C,Hin,Win],汇聚窗口的大小为 kh×kwkh×kw,步幅为 ss。在每个通道上,汇聚窗口在特征图上滑动,对于每个窗口位置,选取窗口内的最大值作为输出特征图上对应位置的值。输出特征图的高度 HoutHout 和宽度 WoutWout 的计算公式为:

[Hout=Hinkhs+1][Hout=⌊sHin−kh⌋+1] [Wout=Winkws+1][Wout=⌊sWin−kw⌋+1]

4. 汇聚层在 Paddle 框架中的实现

In [16]
import paddleimport paddle.nn as nn# 定义输入数据,形状为 [batch_size, in_channels, height, width]batch_size = 1in_channels = 16height = 32width = 32input_tensor = paddle.randn([batch_size, in_channels, height, width])# 定义最大汇聚层kernel_size = 2stride = 2max_pool_layer = nn.MaxPool2D(kernel_size, stride=stride)# 进行最大汇聚运算output_max_pool = max_pool_layer(input_tensor)# 定义平均汇聚层avg_pool_layer = nn.AvgPool2D(kernel_size, stride=stride)# 进行平均汇聚运算output_avg_pool = avg_pool_layer(input_tensor)print("输入张量的形状:", input_tensor.shape)print("最大汇聚层输出的形状:", output_max_pool.shape)
       
输入张量的形状: [1, 16, 32, 32]
最大汇聚层输出的形状: [1, 16, 16, 16]
       

在上述代码中,使用 nn.MaxPool2D 类定义了一个最大汇聚层,使用 nn.AvgPool2D 类定义了一个平均汇聚层。通过调用相应的汇聚层对象并传入输入张量,就可以得到汇聚层的输出。可以看到,汇聚层输出的特征图尺寸比输入特征图尺寸变小了,实现了下采样的目的。

三、基于 LeNet 实现手写体数字识别任务

3.1 数据集构建

3.1.1 数据集介绍

手写体数字识别任务常用的数据集是 MNIST 数据集,它包含 60,000 张训练图像和 10,000 张测试图像,每张图像都是 28x28 像素的灰度图像,代表 0 - 9 之间的一个数字。

3.1 2. 数据集加载与预处理

在 PaddlePaddle 中,可以使用 paddle.vision.datasets.MNIST 来加载 MNIST 数据集,并使用 paddle.vision.transforms 对数据进行预处理。

In [18]
import paddlefrom paddle.vision.datasets import MNISTfrom paddle.vision.transforms import ToTensor, Normalize# 定义数据预处理transform = paddle.vision.transforms.Compose([
    ToTensor(),  # 将图像转换为 Tensor
    Normalize(mean=[0.1307], std=[0.3081])  # 归一化处理])# 加载训练集train_dataset = MNIST(mode='train', transform=transform)# 加载测试集test_dataset = MNIST(mode='test', transform=transform)# 创建数据加载器train_loader = paddle.io.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = paddle.io.DataLoader(test_dataset, batch_size=64, shuffle=False)
       
item  256/2421 [==>...........................] - ETA: 1s - 465us/item
       
Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-images-idx3-ubyte.gz 
Begin to download
       
item 8/8 [============================>.] - ETA: 0s - 890us/item
       
Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-labels-idx1-ubyte.gz 
Begin to download

Download finished
       
item 232/403 [================>.............] - ETA: 0s - 517us/item
       
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-images-idx3-ubyte.gz 
Begin to download
       
item 2/2 [===========================>..] - ETA: 0s - 679us/item
       
Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-labels-idx1-ubyte.gz 
Begin to download

Download finished
       
  • 代码解释
    • ToTensor():将图像数据转换为 Paddle 的 Tensor 格式,方便后续的计算。
    • Normalize(mean=[0.1307], std=[0.3081]):对图像数据进行归一化处理,使数据的均值为 0.1307,标准差为 0.3081,有助于模型的收敛。
    • paddle.io.DataLoader:创建数据加载器,用于批量加载数据,batch_size 表示每个批次加载的样本数量,shuffle=True 表示在每个 epoch 开始时打乱数据顺序。

3.2 模型构建

1. LeNet 模型结构

LeNet 是最早的卷积神经网络之一,由 Yann LeCun 等人在 1998 年提出,主要用于手写体数字识别。它包含两个卷积层、两个池化层和三个全连接层。

2. 在 Paddle 中实现 LeNet

In [22]
import paddle.nn as nnclass LeNet(nn.Layer):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5)
        self.pool1 = nn.MaxPool2D(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5)
        self.pool2 = nn.MaxPool2D(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(in_features=16 * 4 * 4, out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)    def forward(self, x):
        x = self.pool1(paddle.nn.functional.relu(self.conv1(x)))
        x = self.pool2(paddle.nn.functional.relu(self.conv2(x)))
        x = paddle.flatten(x, start_axis=1)
        x = paddle.nn.functional.relu(self.fc1(x))
        x = paddle.nn.functional.relu(self.fc2(x))
        x = self.fc3(x)        return x# 创建 LeNet 模型实例model = LeNet()
   

代码解释

  • nn.Conv2D:定义卷积层,in_channels 表示输入通道数,out_channels 表示输出通道数,kernel_size 表示卷积核大小。
  • nn.MaxPool2D:定义最大池化层,kernel_size 表示池化窗口大小,stride 表示步幅。
  • nn.Linear:定义全连接层,in_features 表示输入特征数,out_features 表示输出特征数。
  • forward 方法:定义模型的前向传播过程,依次经过卷积层、池化层、全连接层,并使用 ReLU 激活函数。

3.3 模型训练

1. 定义损失函数和优化器

在手写体数字识别任务中,通常使用交叉熵损失函数(Cross Entropy Loss)和随机梯度下降(SGD)优化器。

In [23]
import paddle.optimizer as opt# 定义损失函数criterion = nn.CrossEntropyLoss()# 定义优化器optimizer = opt.SGD(parameters=model.parameters(), learning_rate=0.01)
   

2. 训练模型

In [27]
# 设置训练轮数epochs = 5for epoch in range(epochs):
    model.train()    for batch_id, (data, label) in enumerate(train_loader):        # 前向传播
        logits = model(data)        # 计算损失
        loss = criterion(logits, label)        # 反向传播
        loss.backward()        # 更新参数
        optimizer.step()        # 清空梯度
        optimizer.clear_grad()        if batch_id % 100 == 0:            print(f'Epoch {epoch}, Batch {batch_id}, Loss: {loss.numpy()}')
       
Epoch 0, Batch 0, Loss: 0.11509466171264648
Epoch 0, Batch 100, Loss: 0.03346274420619011
Epoch 0, Batch 200, Loss: 0.22343681752681732
Epoch 0, Batch 300, Loss: 0.05087178945541382
Epoch 0, Batch 400, Loss: 0.13649000227451324
Epoch 0, Batch 500, Loss: 0.013281416147947311
Epoch 0, Batch 600, Loss: 0.05379164218902588
Epoch 0, Batch 700, Loss: 0.04195011407136917
Epoch 0, Batch 800, Loss: 0.12393365800380707
Epoch 0, Batch 900, Loss: 0.1106325164437294
Epoch 1, Batch 0, Loss: 0.09184302389621735
Epoch 1, Batch 100, Loss: 0.19134733080863953
Epoch 1, Batch 200, Loss: 0.05657428875565529
Epoch 1, Batch 300, Loss: 0.11808892339468002
Epoch 1, Batch 400, Loss: 0.10434942692518234
Epoch 1, Batch 500, Loss: 0.09580446779727936
Epoch 1, Batch 600, Loss: 0.07820305228233337
Epoch 1, Batch 700, Loss: 0.0501871295273304
Epoch 1, Batch 800, Loss: 0.05161799117922783
Epoch 1, Batch 900, Loss: 0.053209058940410614
Epoch 2, Batch 0, Loss: 0.10898318886756897
Epoch 2, Batch 100, Loss: 0.020451167598366737
Epoch 2, Batch 200, Loss: 0.026547012850642204
Epoch 2, Batch 300, Loss: 0.12171617895364761
Epoch 2, Batch 400, Loss: 0.01684199646115303
Epoch 2, Batch 500, Loss: 0.14898955821990967
Epoch 2, Batch 600, Loss: 0.08837781101465225
Epoch 2, Batch 700, Loss: 0.03748306632041931
Epoch 2, Batch 800, Loss: 0.04891512170433998
Epoch 2, Batch 900, Loss: 0.06328010559082031
Epoch 3, Batch 0, Loss: 0.05560357868671417
Epoch 3, Batch 100, Loss: 0.052786339074373245
Epoch 3, Batch 200, Loss: 0.06036677584052086
Epoch 3, Batch 300, Loss: 0.020853416994214058
Epoch 3, Batch 400, Loss: 0.014304134994745255
Epoch 3, Batch 500, Loss: 0.030961915850639343
Epoch 3, Batch 600, Loss: 0.11292454600334167
Epoch 3, Batch 700, Loss: 0.05983085557818413
Epoch 3, Batch 800, Loss: 0.021292399615049362
Epoch 3, Batch 900, Loss: 0.00851159356534481
Epoch 4, Batch 0, Loss: 0.03880467265844345
Epoch 4, Batch 100, Loss: 0.038770634680986404
Epoch 4, Batch 200, Loss: 0.02449839562177658
Epoch 4, Batch 300, Loss: 0.09317301213741302
Epoch 4, Batch 400, Loss: 0.20579950511455536
Epoch 4, Batch 500, Loss: 0.0257789958268404
Epoch 4, Batch 600, Loss: 0.04056982696056366
Epoch 4, Batch 700, Loss: 0.04311925172805786
Epoch 4, Batch 800, Loss: 0.024561088532209396
Epoch 4, Batch 900, Loss: 0.10791753977537155
       
  • 代码解释

    • nn.CrossEntropyLoss():定义交叉熵损失函数,用于衡量模型预测结果与真实标签之间的差异。
    • opt.SGD:定义随机梯度下降优化器,parameters=model.parameters() 表示优化模型的所有参数,learning_rate=0.01 表示学习率。
    • 在每个 epoch 中,遍历训练数据加载器,进行前向传播、计算损失、反向传播和参数更新。

3.4 模型评价

1. 评估模型性能

使用测试集评估模型的准确率。

In [28]
model.eval()
correct = 0total = 0with paddle.no_grad():    for data, label in test_loader:
        logits = model(data)
        pred = paddle.argmax(logits, axis=1)
        total += label.shape[0]
        correct += (pred == label).sum().numpy()

accuracy = correct / totalprint(f'Test Accuracy: {accuracy}')
       
Test Accuracy: 7.0603
       
  • 代码解释
    • model.eval():将模型设置为评估模式,关闭一些在训练时使用的特殊层(如 Dropout)。
    • paddle.no_grad():上下文管理器,用于在评估过程中不计算梯度,减少内存消耗。
    • paddle.argmax(logits, axis=1):获取模型预测的类别索引。
    • 计算预测正确的样本数和总样本数,最后计算准确率。

3.5 模型预测

1. 随机选择样本进行预测

In [29]
import numpy as npimport matplotlib.pyplot as plt# 随机选择一个样本index = np.random.randint(0, len(test_dataset))
image, true_label = test_dataset[index]
image = image.unsqueeze(0)  # 添加 batch 维度model.eval()with paddle.no_grad():
    logits = model(image)
    pred = paddle.argmax(logits, axis=1).numpy()[0]# 显示图像和预测结果plt.imshow(image.squeeze().numpy(), cmap='gray')
plt.title(f'True Label: {true_label}, Predicted Label: {pred}')
plt.show()
       
               
  • 代码解释
    • 随机选择一个测试样本,添加 batch 维度后输入到模型中进行预测。
    • 使用 paddle.argmax 获取预测的类别索引。
    • 使用 matplotlib 显示图像和预测结果。

相关专题

更多
python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

185

2023.09.27

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

392

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

572

2023.08.10

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

75

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

36

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

59

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

38

2025.11.27

css中的padding属性作用
css中的padding属性作用

在CSS中,padding属性用于设置元素的内边距。想了解更多padding的相关内容,可以阅读本专题下面的文章。

133

2023.12.07

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

23

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 5.5万人学习

Django 教程
Django 教程

共28课时 | 3.3万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号