【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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月6日 17:20:16
下一篇 2025年11月6日 17:20:50

相关推荐

  • 在Laravel框架中如何解决“Too many open files”错误?

    在laravel框架中解决“too many open files”错误的方法 在使用php7.3和laravel框架执行定时任务时,你可能会遇到一个错误提示,指出“打开文件太多”,错误信息大致如下: [2023-03-15 00:14:13] local.ERROR: include(/www/v…

    好文分享 2025年12月11日
    100
  • php中的卷曲:如何在REST API中使用PHP卷曲扩展

    php客户端url(curl)扩展是开发人员的强大工具,可以与远程服务器和rest api无缝交互。通过利用libcurl(备受尊敬的多协议文件传输库),php curl有助于有效执行各种网络协议,包括http,https和ftp。该扩展名提供了对http请求的颗粒状控制,支持多个并发操作,并提供内…

    2025年12月11日
    000
  • Git服务器重装后,git pull一直提示输入密码怎么办?

    Git服务器重装后,持续提示输入密码的解决方案 重装Git服务器后,git pull 命令反复要求输入密码?本文提供详细的解决方法,助您快速恢复正常代码拉取流程。 问题背景: 您搭建的Git服务器并非基于GitHub或HTTPS协议,重装系统后,即使目录结构保留,git pull 仍然需要密码验证。…

    2025年12月11日
    100
  • 高并发秒杀下,如何保证Redis和数据库库存一致性?

    高并发秒杀:PHP+Redis与数据库库存一致性解决方案 高并发秒杀系统中,如何确保Redis缓存库存与数据库库存数据一致性是核心挑战。本文分析基于Redis原子自减操作和数据库操作的秒杀流程,探讨可能出现的问题及解决方案。 常见的秒杀流程:下单 -> Redis扣减库存 -> 创建订单…

    2025年12月11日
    000
  • 如何用PHP和CURL高效采集新闻列表及详情?

    本文将阐述如何利用PHP和cURL高效抓取目标网站的新闻列表和新闻详情,并展示最终结果。 关键在于高效运用cURL获取数据,处理相对路径并提取所需信息。 首先,解决第一个挑战:从列表页(例如,页面1)提取新闻标题和完整URL。 代码示例如下: <?php$url = 'http://…

    2025年12月11日
    000
  • HTML表单onsubmit事件失效,如何排查表单验证问题?

    HTML表单提交验证失效:排查与解决 在使用HTML表单进行数据提交时,onsubmit事件常用于客户端验证,确保数据符合要求后再提交至服务器。然而,onsubmit事件有时失效,导致表单直接提交,本文将分析一个案例,解决onsubmit=”return check()”失效的问题。 问题描述: 用…

    2025年12月11日
    000
  • 苹果M1芯片Mac上编译安装Redis失败怎么办?

    苹果m1芯片mac编译安装redis失败的排查与解决 在苹果M1芯片的Mac电脑上编译安装Redis,常常会遇到各种问题,例如编译失败等。本文将指导您如何有效地排查和解决这些问题。 很多用户反馈编译错误,但仅提供截图不足以诊断问题。 为了高效解决,务必提供完整的错误日志文本。 以下几个关键点需要关注…

    2025年12月11日
    000
  • ReactPHP非阻塞特性详解:如何理解“默认非阻塞,阻塞I/O用workers”?

    深入探究ReactPHP的非阻塞机制 ReactPHP官方文档中的一句话引发了诸多讨论:“ReactPHP默认是非阻塞的。对于阻塞I/O操作,请使用workers。” 让我们深入剖析这句话的含义。 ReactPHP的核心优势在于其默认的非阻塞特性。不同于传统PHP的阻塞式I/O模型,ReactPHP…

    2025年12月11日
    000
  • PHP字符串高效分割与对比:如何快速高亮显示长字符串中重复的部分?

    PHP文本处理中,字符串分割和对比是常见操作。本文详解如何高效分割长字符串,并与目标字符串对比,高亮显示重复部分。 示例任务:将长字符串$str分割成15字符长度的子串,并与字符串$aa对比,高亮显示$aa中与$str子串重复的部分。 传统方法使用循环和mb_substr逐个分割对比,效率低下。改进…

    2025年12月11日
    000
  • Beego项目中如何访问main函数定义的全局变量?

    在Beego项目中,如何正确访问main函数中定义的全局变量?本文将详细讲解如何在Go语言的Beego框架中,从非main.go文件(例如controllers目录下的文件)访问在main.go文件中定义的全局变量。对于Go语言新手来说,这个问题常常令人困惑。 问题背景:假设您需要在一个Beego项…

    2025年12月11日
    000
  • 微擎项目源码版本控制:如何高效配置.gitignore文件?

    微擎项目源码版本控制及.gitignore文件优化配置 高效管理微擎或人人商城等二次开发项目的源码版本,是避免版本混乱的关键。 Git版本控制系统能有效帮助我们,但需要巧妙地配置.gitignore文件,排除不必要的文件夹和文件,避免臃肿的版本库。本文提供一个.gitignore文件配置方案,帮助您…

    2025年12月11日
    000
  • 微擎项目Git版本控制:如何高效配置.gitignore文件忽略不必要文件?

    高效管理微擎项目源码:.gitignore文件配置指南 在使用Git管理基于微擎/人人商城二次开发的项目时,庞大的源码体积常常带来挑战。 本文提供一个.gitignore文件配置示例,帮助开发者高效管理微擎项目,避免将不必要的文件纳入版本控制,从而减小仓库体积并减少冲突。 问题:如何配置.gitig…

    2025年12月11日
    000
  • PHP二维数组如何排序并添加排名?

    PHP二维数组排序及排名:高效解决方案 本文将详细阐述如何对PHP二维数组进行排序,并为每个子数组添加排名信息。假设我们的二维数组包含多个子数组,每个子数组包含“xuhao”(序号)和“piaoshu”(票数)两个字段。目标是根据“piaoshu”字段降序排序,票数相同时则按“xuhao”字段升序排…

    2025年12月11日
    000
  • HTML表单onsubmit事件无效,表单仍提交:问题出在哪里?

    HTML表单onsubmit事件失效:排查与解决 在使用HTML表单时,onsubmit事件通常用于表单提交前的验证。然而,有时即使添加了onsubmit=”return check();”,表单仍会直接提交。本文分析此问题,并提供解决方案。 问题描述: 用户在HTML表单中添加onsubmit=”…

    2025年12月11日
    000
  • 如何在LAMP架构中整合Node.js或Python服务并处理网络请求?

    在LAMP架构中集成Node.js或Python服务 许多网站基于传统的LAMP架构(Linux, Apache, MySQL, PHP)构建,但随着项目扩展,可能需要添加Node.js或Python开发的新功能。由于Apache通常将80端口请求默认分配给PHP处理,因此在LAMP环境下启动并集成…

    2025年12月11日
    000
  • IntelliJ IDEA中如何高效地直接比较本地代码与远程服务器代码?

    高效利用IntelliJ IDEA和VS Code等IDE进行Git代码对比 习惯了SVN直接比较功能的开发者,在使用Git时,常常会问:能否像SVN一样直接比较本地代码与远程服务器代码,而无需先pull代码再处理冲突?答案是肯定的!主流IDE都提供了便捷的远程代码比较功能。 本文以IntelliJ…

    2025年12月11日
    000
  • ThinkPHP5框架下如何不修改模型实现Archives表与B表的多表关联查询?

    ThinkPHP5框架多表关联查询:无需修改模型 本文介绍如何在ThinkPHP5框架中,不修改现有模型的情况下,实现Archives表与自定义表B的多表关联查询,并以Archives表数据为主返回结果。 此方法适用于已有的TP5 CMS系统,需要在原有Archives模型查询基础上关联其他表的情况…

    2025年12月11日
    000
  • 高效的异步操作:Guzzle Promises 的实践与应用

    最近在开发一个需要同时访问多个外部 API 的应用时,遇到了严重的性能问题。 传统的同步请求方式导致应用响应时间过长,用户体验极差。 每个 API 请求都需要等待完成才能发出下一个请求,这在处理大量请求时效率极低,严重影响了系统的吞吐量。 为了解决这个问题,我开始寻找异步处理的方案,最终选择了 Gu…

    2025年12月11日
    000
  • PHP记录:PHP日志分析的最佳实践

    php日志记录对于监视和调试web应用程序以及捕获关键事件,错误和运行时行为至关重要。它为系统性能提供了宝贵的见解,有助于识别问题,并支持更快的故障排除和决策 – 但仅当它有效地实施时。 在此博客中,我概述了PHP记录以及它在Web应用程序中的使用方式。然后,我概述了一些关键的最佳实践,…

    2025年12月11日
    000
  • 告别依赖注入的困扰:使用 PSR-11 容器接口简化代码

    我最近参与了一个大型PHP项目的重构工作。项目中充斥着大量的new操作,各个类之间紧密耦合,代码难以测试和维护。修改一个类往往需要修改多个地方,这使得开发效率极低,而且容易引入新的bug。 我意识到,我们需要引入依赖注入来改善这种情况。然而,仅仅引入依赖注入的概念还不够,我们需要一个高效的机制来管理…

    2025年12月11日
    000

发表回复

登录后才能评论
关注微信