本文围绕二维卷积及卷积神经网络展开,先讲解二维卷积运算的概念、原理及Paddle框架实现,介绍卷积算子的定义、参数。还阐述卷积神经网络的卷积层、汇聚层算子,最后以LeNet为例,说明其在手写体数字识别任务中的数据集构建、模型构建、训练、评价与预测过程。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

在深度学习领域,卷积(Convolution)是卷积神经网络(Convolutional Neural Network,CNN)的核心操作。它通过对输入数据进行特定的数学运算,能够提取数据中的重要特征,广泛应用于图像、音频等多种数据处理任务。
基本概念
在图像数据处理中,我们通常使用二维卷积。假设我们有一个输入图像,它可以看作是一个二维矩阵,每个元素代表图像中对应位置的像素值。同时,我们有一个卷积核(Convolution Kernel),也叫滤波器(Filter),同样是一个二维矩阵。二维卷积运算就是将卷积核在输入图像上滑动,在每个位置,将卷积核与对应位置的图像区域进行逐元素相乘,然后将这些乘积相加,得到一个输出值,这个输出值就是卷积结果在该位置的值。
例如,假设有一个 (3×3) 的输入图像矩阵 (I) 和一个 (2×2) 的卷积核 (K):
[I=⎣⎢⎡147258369⎦⎥⎤]
[K=[1001]]
当卷积核 (K) 从输入图像 (I) 的左上角开始滑动时,首先计算:
((1×1+2×0)+(4×0+5×1)=1+5=6),这就是卷积结果左上角的值。然后卷积核向右滑动一个单位,继续上述计算过程,得到整个卷积结果。
数学原理
[O(i,j)=m=0∑h−1n=0∑w−1I(i+m,j+n)K(m,n)]
其中 (i) 和 (j) 表示输出特征图中元素的位置,(m) 和 (n) 用于遍历卷积核中的元素。这个公式本质上就是在每个位置对输入图像和卷积核进行对应元素相乘并求和的过程。
在 Paddle 框架中的实现
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]
输入张量的生成:
卷积核张量的生成:
二维卷积运算:
输出结果形状的打印:
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]
输入张量的生成:
卷积核张量的生成:
二维卷积运算:
输出结果的形状:
用 PaddlePaddle 实现不同卷积核大小的示例代码:
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]
[Hout=⌊sHin−Hkernel+2×padding⌋+1]
[Wout=⌊sWin−Wkernel+2×padding⌋+1]
其中 Hin 和 Win 是输入数据的高度和宽度,Hkernel 和 Wkernel 是卷积核的高度和宽度,padding 是填充的数量。可以看出,步幅 s 越大,输出特征图的高度 Hout 和宽度 Wout 就越小。
使用不同步幅的 PaddlePaddle 示例代码:
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]
使用填充的 PaddlePaddle 示例代码:
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]
在卷积神经网络(Convolutional Neural Network, CNN)中,卷积层算子是核心组成部分之一。它的主要功能是从输入数据中提取特征,这些特征对于后续的分类、识别等任务至关重要。卷积层通过卷积运算将卷积核应用到输入数据上,产生一系列的特征图(Feature Map),每个特征图对应一种特定的特征。
[yj,i1,i2=c=0∑Cin−1m=0∑kh−1n=0∑kw−1xc,i1+m,i2+nkj,c,m,n+bj]
其中 x 是输入数据,k 是卷积核,bj 是第 j 个输出通道的偏置项。
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]
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,就可以得到卷积层的输出。
汇聚层(Pooling Layer)也是卷积神经网络中的重要组成部分。它的主要作用是对输入的特征图进行下采样,减少特征图的尺寸,从而降低模型的计算复杂度和参数数量,同时还能增强模型的鲁棒性。汇聚层通过在特征图上进行局部区域的统计操作,得到一个更小尺寸的特征图。
以最大汇聚为例,假设输入特征图的形状为 [C,Hin,Win],汇聚窗口的大小为 kh×kw,步幅为 s。在每个通道上,汇聚窗口在特征图上滑动,对于每个窗口位置,选取窗口内的最大值作为输出特征图上对应位置的值。输出特征图的高度 Hout 和宽度 Wout 的计算公式为:
[Hout=⌊sHin−kh⌋+1] [Wout=⌊sWin−kw⌋+1]
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 类定义了一个平均汇聚层。通过调用相应的汇聚层对象并传入输入张量,就可以得到汇聚层的输出。可以看到,汇聚层输出的特征图尺寸比输入特征图尺寸变小了,实现了下采样的目的。
手写体数字识别任务常用的数据集是 MNIST 数据集,它包含 60,000 张训练图像和 10,000 张测试图像,每张图像都是 28x28 像素的灰度图像,代表 0 - 9 之间的一个数字。
在 PaddlePaddle 中,可以使用 paddle.vision.datasets.MNIST 来加载 MNIST 数据集,并使用 paddle.vision.transforms 对数据进行预处理。
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
LeNet 是最早的卷积神经网络之一,由 Yann LeCun 等人在 1998 年提出,主要用于手写体数字识别。它包含两个卷积层、两个池化层和三个全连接层。
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()在手写体数字识别任务中,通常使用交叉熵损失函数(Cross Entropy Loss)和随机梯度下降(SGD)优化器。
import paddle.optimizer as opt# 定义损失函数criterion = nn.CrossEntropyLoss()# 定义优化器optimizer = opt.SGD(parameters=model.parameters(), learning_rate=0.01)
# 设置训练轮数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
代码解释
使用测试集评估模型的准确率。
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
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()<Figure size 640x480 with 1 Axes>
以上就是【PaddlePaddle】基础理论教程 - 卷积神经网络概论的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号