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

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

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

【paddlepaddle】基础理论教程 - 卷积神经网络概论 - 创想鸟

一、 二维卷积

1.1. 二维卷积运算

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

基本概念

在图像数据处理中,我们通常使用二维卷积。假设我们有一个输入图像,它可以看作是一个二维矩阵,每个元素代表图像中对应位置的像素值。同时,我们有一个卷积核(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),这就是卷积结果左上角的值。然后卷积核向右滑动一个单位,继续上述计算过程,得到整个卷积结果。

数学原理

设输入图像为 (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=0h−1∑n=0w−1I(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) 用于遍历卷积核中的元素。这个公式本质上就是在每个位置对输入图像和卷积核进行对应元素相乘并求和的过程。

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

       代码解释:

输入张量的生成:

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

卷积核张量的生成:

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

二维卷积运算:

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

输出结果形状的打印:

通过 print 函数分别打印输入张量、卷积核和输出特征图的形状,方便观察卷积操作对数据形状的影响。在默认 stride 为 1,padding 为 0 的情况下,根据卷积运算的形状计算公式:输出特征图的高度 (Hout=⌊Hin−Hkernel+2×paddingstride⌋+1)(Hout=⌊strideHin−Hkernel+2×padding⌋+1)输出特征图的宽度 (Wout=⌊Win−Wkernel+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=⌊5−3+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]

       代码解释:

输入张量的生成:

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

卷积核张量的生成:

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

二维卷积运算:

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

输出结果的形状:

打印各部分的形状有助于理解卷积操作对数据的影响。详细解释了输出特征图形状的计算原理,通过代入具体的值,让清晰地看到如何根据输入和卷积核的参数计算出输出的形状。最终输出特征图的形状为 [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 实现不同卷积核大小的示例代码:

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=⌊Hin−Hkernel+2×paddings⌋+1][Hout=⌊sHin−Hkernel+2×padding⌋+1]

[Wout=⌊Win−Wkernel+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=0Cin−1∑m=0kh−1∑n=0kw−1xc,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 nnpaddle.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=⌊Hin−khs⌋+1][Hout=⌊sHin−kh⌋+1] [Wout=⌊Win−kws⌋+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 张测试图像,每张图像都是 28×28 像素的灰度图像,代表 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 finishedCache 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 downloadDownload 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 finishedCache 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 downloadDownload 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.11509466171264648Epoch 0, Batch 100, Loss: 0.03346274420619011Epoch 0, Batch 200, Loss: 0.22343681752681732Epoch 0, Batch 300, Loss: 0.05087178945541382Epoch 0, Batch 400, Loss: 0.13649000227451324Epoch 0, Batch 500, Loss: 0.013281416147947311Epoch 0, Batch 600, Loss: 0.05379164218902588Epoch 0, Batch 700, Loss: 0.04195011407136917Epoch 0, Batch 800, Loss: 0.12393365800380707Epoch 0, Batch 900, Loss: 0.1106325164437294Epoch 1, Batch 0, Loss: 0.09184302389621735Epoch 1, Batch 100, Loss: 0.19134733080863953Epoch 1, Batch 200, Loss: 0.05657428875565529Epoch 1, Batch 300, Loss: 0.11808892339468002Epoch 1, Batch 400, Loss: 0.10434942692518234Epoch 1, Batch 500, Loss: 0.09580446779727936Epoch 1, Batch 600, Loss: 0.07820305228233337Epoch 1, Batch 700, Loss: 0.0501871295273304Epoch 1, Batch 800, Loss: 0.05161799117922783Epoch 1, Batch 900, Loss: 0.053209058940410614Epoch 2, Batch 0, Loss: 0.10898318886756897Epoch 2, Batch 100, Loss: 0.020451167598366737Epoch 2, Batch 200, Loss: 0.026547012850642204Epoch 2, Batch 300, Loss: 0.12171617895364761Epoch 2, Batch 400, Loss: 0.01684199646115303Epoch 2, Batch 500, Loss: 0.14898955821990967Epoch 2, Batch 600, Loss: 0.08837781101465225Epoch 2, Batch 700, Loss: 0.03748306632041931Epoch 2, Batch 800, Loss: 0.04891512170433998Epoch 2, Batch 900, Loss: 0.06328010559082031Epoch 3, Batch 0, Loss: 0.05560357868671417Epoch 3, Batch 100, Loss: 0.052786339074373245Epoch 3, Batch 200, Loss: 0.06036677584052086Epoch 3, Batch 300, Loss: 0.020853416994214058Epoch 3, Batch 400, Loss: 0.014304134994745255Epoch 3, Batch 500, Loss: 0.030961915850639343Epoch 3, Batch 600, Loss: 0.11292454600334167Epoch 3, Batch 700, Loss: 0.05983085557818413Epoch 3, Batch 800, Loss: 0.021292399615049362Epoch 3, Batch 900, Loss: 0.00851159356534481Epoch 4, Batch 0, Loss: 0.03880467265844345Epoch 4, Batch 100, Loss: 0.038770634680986404Epoch 4, Batch 200, Loss: 0.02449839562177658Epoch 4, Batch 300, Loss: 0.09317301213741302Epoch 4, Batch 400, Loss: 0.20579950511455536Epoch 4, Batch 500, Loss: 0.0257789958268404Epoch 4, Batch 600, Loss: 0.04056982696056366Epoch 4, Batch 700, Loss: 0.04311925172805786Epoch 4, Batch 800, Loss: 0.024561088532209396Epoch 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 显示图像和预测结果。

以上就是【PaddlePaddle】基础理论教程 – 卷积神经网络概论的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/42030.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Mac如何连接鼠标
上一篇 2025年11月6日 17:20:21
英特尔CEO:已跌出芯片行业前十 在AI领域落后英伟达太多
下一篇 2025年11月6日 17:20:27

相关推荐

  • 开源免费PHP工具 PHP开发效率提升利器

    推荐开源免费PHP开发工具以提升效率:VS Code、Sublime Text轻量高效,PhpStorm专业强大;调试用Xdebug、Kint、Ray;依赖管理选Composer;代码质量工具包括PHPStan、Psalm、PHP_CodeSniffer;数据库管理可用%ignore_a_1%MyA…

    2026年5月10日
    000
  • Matplotlib 地图中多类型图例的创建与优化

    Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化Matplotlib 地图中多类型图例的创建与优化

    本教程旨在解决matplotlib地图可视化中,如何在一个图例中同时展示颜色块(如区域分类)和自定义标记(如特定兴趣点)的问题。文章详细介绍了当传统`patch`对象无法正确显示标记时,如何利用`matplotlib.lines.line2d`创建标记图例句柄,并将其与颜色块图例句柄合并,从而生成一…

    2026年5月10日 用户投稿
    100
  • Golang JSON序列化:控制敏感字段暴露的最佳实践

    本教程探讨golang中如何高效控制结构体字段在json序列化时的可见性。当需要将包含敏感信息的结构体数组转换为json响应时,通过利用`encoding/json`包提供的结构体标签,特别是`json:”-“`,可以轻松实现对特定字段的忽略,从而避免敏感数据泄露,确保api…

    2026年5月10日
    000
  • 利用海象运算符简化条件赋值:Python教程与最佳实践

    本文旨在探讨Python中海象运算符(:=)在条件赋值场景下的应用。通过对比传统if/else语句与海象运算符,以及条件表达式,分析海象运算符在简化代码、提高可读性方面的优势与局限性。并通过具体示例,展示如何在列表推导式等场景下合理使用海象运算符,同时强调其潜在的复杂性及替代方案,帮助开发者更好地掌…

    2026年5月10日
    000
  • 比特币新手教程 比特币交易平台有哪些

    比特币是一种去中心化的数字货币,基于区块链技术实现点对点交易,具有匿名性、有限发行和不可篡改等特点;新手可通过交易所购买,P2P交易获得比特币,常用平台包括Binance、OKX和Huobi;交易流程包括注册账户、实名认证、绑定支付方式、充值法币并下单购买,可选择市价单或限价单;比特币存储方式有交易…

    2026年5月10日
    000
  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • Go语言mgo查询构建:深入理解bson.M与日期范围查询的正确实践

    本文旨在解决go语言mgo库中构建复杂查询时,特别是涉及嵌套`bson.m`和日期范围筛选的常见错误。我们将深入剖析`bson.m`的类型特性,解释为何直接索引`interface{}`会导致“invalid operation”错误,并提供一种推荐的、结构清晰的代码重构方案,以确保查询条件能够正确…

    2026年5月10日
    100
  • RichHandler与Rich Progress集成:解决显示冲突的教程

    在使用rich库的`richhandler`进行日志输出并同时使用`progress`组件时,可能会遇到显示错乱或溢出问题。这通常是由于为`richhandler`和`progress`分别创建了独立的`console`实例导致的。解决方案是确保日志处理器和进度条组件共享同一个`console`实例…

    2026年5月10日
    000
  • Golang goroutine与channel调试技巧

    使用go run -race检测数据竞争,结合runtime.NumGoroutine监控协程数量,通过pprof分析阻塞调用栈,利用select超时避免永久阻塞,有效排查goroutine泄漏、死锁和数据竞争问题。 Go语言的goroutine和channel是并发编程的核心,但它们也带来了调试上…

    2026年5月10日
    000
  • 《魔兽世界》将于6月11日开启国服回归技术测试

    《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试《魔兽世界》将于6月11日开启国服回归技术测试

    《%ign%ignore_a_1%re_a_1%》官方宣布,将于6月11日开启国服回归技术测试,时间为7天,并称可以在6月内正式开服,玩家们可以访问官网下载战网客户端并预下载“巫妖王之怒”客户端,技术测试详情见下图。 WordAi WordAI是一个AI驱动的内容重写平台 53 查看详情 以上就是《…

    2026年5月10日 用户投稿
    200
  • 使用 Jupyter Notebook 进行探索性数据分析

    Jupyter Notebook通过单元格实现代码与Markdown结合,支持数据导入(pandas)、清洗(fillna)、探索(matplotlib/seaborn可视化)、统计分析(describe/corr)和特征工程,便于记录与分享分析过程。 Jupyter Notebook 是进行探索性…

    2026年5月10日
    000
  • 如何在HTML中插入表单元素_HTML表单控件与输入类型使用指南

    HTML表单通过标签构建,包含action和method属性定义数据提交目标与方式,常用input类型如text、password、email等适配不同输入需求,配合label、required、placeholder提升可用性,结合textarea、select、button等控件实现完整交互,是…

    2026年5月10日
    000
  • 创建指定大小并填充特定数据的Golang文件教程

    本文将介绍如何使用Golang创建一个指定大小的文件,并用特定数据填充它。我们将使用 `os` 包提供的函数来创建和截断文件,从而实现快速生成大文件的目的。示例代码展示了如何创建一个10MB的文件,并将其填充为全零数据。掌握这些方法,可以方便地在例如日志系统或磁盘队列等场景中,预先创建测试文件或初始…

    2026年5月10日
    000
  • Python命令怎样使用profile分析脚本性能 Python命令性能分析的基础教程

    使用Python的cProfile模块分析脚本性能最直接的方式是通过命令行执行python -m cProfile your_script.py,它会输出每个函数的调用次数、总耗时、累积耗时等关键指标,帮助定位性能瓶颈;为进一步分析,可将结果保存为文件python -m cProfile -o ou…

    2026年5月10日
    000
  • 使用 WebCodecs VideoDecoder 实现精确逐帧回退

    本文档旨在解决在使用 WebCodecs VideoDecoder 进行视频解码时,实现精确逐帧回退的问题。通过比较帧的时间戳与目标帧的时间戳,可以避免渲染中间帧,从而提高用户体验。本文将提供详细的解决方案和示例代码,帮助开发者实现精确的视频帧控制。 在使用 WebCodecs VideoDecod…

    2026年5月10日
    000
  • 如何插入查询结果数据_SQL插入Select查询结果方法

    如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法如何插入查询结果数据_SQL插入Select查询结果方法

    使用INSERT INTO…SELECT语句可高效插入数据,通过NOT EXISTS、LEFT JOIN、MERGE语句或唯一约束避免重复;表结构不一致时可通过别名、类型转换、默认值或计算字段处理;结合存储过程可提升可维护性,支持参数化与动态SQL。 将查询结果数据插入到另一个表中,可以…

    2026年5月10日 用户投稿
    000
  • Debian Copilot的社区活跃度如何

    debian copilot是codeberg社区维护的ai助手,旨在为debian用户提供服务。尽管搜索结果中没有直接提供关于debian copilot社区支持活跃度的具体数据,但我们可以通过debian社区的整体活跃度和特点来推断其活跃性。 Debian社区的一般情况: Debian拥有详尽的…

    2026年5月10日
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • Python递归函数追踪与性能考量:以序列打印为例

    本文深入探讨了Python中一种递归打印序列元素的方法,并着重演示了如何通过引入缩进参数来有效追踪递归函数的执行流程和参数变化。通过实际代码示例,文章揭示了递归调用可能带来的潜在性能开销,特别是对调用栈空间的需求,以及Python默认递归深度限制可能导致的错误,为读者提供了理解和优化递归算法的实用见…

    2026年5月10日
    000
  • python中zip函数详解 python多序列压缩zip函数应用场景

    zip函数的应用场景包括:1) 同时遍历多个序列,2) 合并多个列表的数据,3) 数据分析和科学计算中的元素运算,4) 处理csv文件,5) 性能优化。zip函数是一个强大的工具,能够简化代码并提高处理多个序列时的效率。 在Python中,zip函数是一个非常有用的工具,它能够将多个可迭代对象打包成…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信