CNN卷积神经网络实例讲解(简单易懂)

CNN(Convolutional Neural Network,卷积神经网络)是一种深度学习模型,广泛应用于图像识别、视频分析、自然语言处理等领域。它通过模拟人类视觉系统的工作方式,能够自动提取图像或其他数据中的特征,从而实现高效的分类、检测和生成任务。以下是CNN的详细介绍:

一、CNN的起源与发展

起源

  • CNN的灵感来源于生物视觉系统的研究。20世纪60年代,Hubel和Wiesel通过研究猫的大脑皮层,发现视觉信息在大脑中是分层处理的。这一发现为CNN的设计提供了理论基础。

  • 1998年,Yann LeCun等人提出了LeNet-5,这是最早的CNN之一,用于手写数字识别(如MNIST数据集)。LeNet-5的成功展示了CNN在图像识别任务中的巨大潜力。

发展

  • 2012年,AlexNet在ImageNet竞赛中取得了突破性成绩,大幅提高了图像分类的准确率。AlexNet的出现标志着深度学习时代的到来,CNN开始在学术界和工业界受到广泛关注。

随后,VGGNet、GoogLeNet(Inception系列)、ResNet等更先进的CNN架构相继出现,进一步推动了CNN的发展。这些架构在模型深度、参数数量、计算效率等方面不断优化,使得CNN在各种视觉任务中的性能不断提升。

二、CNN的基本结构

CNN通常由以下几部分组成:

输入层(Input Layer)

  • 输入层接收原始数据,如图像的像素值。对于彩色图像,输入数据通常是三维的,包括高度、宽度和通道数(例如RGB图像有3个通道)。

卷积层(Convolutional Layer)

  • 卷积层是CNN的核心部分,负责提取输入数据的局部特征。它通过卷积核(或滤波器)在输入数据上滑动,进行卷积运算。

  • 卷积核的大小通常小于输入数据的大小,例如3×3或5×5。卷积核的参数(权重)在训练过程中会不断更新,以学习到更有用的特征。

  • 卷积运算的公式为:输出=\sum_{i=1}^{n}\sum_{j=1}^{m}​输入\left ( i,j \right )××卷积核\left ( i,j \right )×+偏置

  • 卷积层可以有多个卷积核,每个卷积核提取不同的特征,从而生成多个特征图(Feature Map)。这些特征图组合在一起,形成下一层的输入。

激活层(Activation Layer)

  • 激活层的作用是引入非线性因素,使CNN能够学习更复杂的特征。常用的激活函数包括ReLU(Rectified Linear Unit,修正线性单元)、Sigmoid、Tanh等。

  • ReLU是最常用的激活函数,其公式为:ReLU(X)=max\left ( 0,x \right )。ReLU的优点是计算简单,且能够有效缓解梯度消失问题。

池化层(Pooling Layer)

  • 池化层的作用是降低特征图的空间维度,减少计算量和参数数量,同时保留重要特征。常见的池化操作包括最大池化(Max Pooling)和平均池化(Average Pooling)。

  • 最大池化是取池化窗口内的最大值,而平均池化是取池化窗口内的平均值。例如,对于一个2×2的池化窗口,最大池化会输出窗口内的最大值,平均池化会输出窗口内的平均值。

全连接层(Fully Connected Layer)

  • 全连接层的输出通常经过Softmax函数(用于分类任务)或线性函数(用于回归任务)进行最终的预测。

  • 全连接层的作用是将卷积层和池化层提取的特征进行整合,输出最终的分类结果或回归值。在全连接层中,每个神经元都与前一层的所有神经元相连。

输出层(Output Layer)

  • 输出层根据任务类型输出最终结果。对于分类任务,输出层的神经元数量通常与类别数量相同,每个神经元的输出值表示该类别对应的概率。对于回归任务,输出层通常只有一个神经元,输出值为预测的回归值。

三、CNN的工作原理

CNN的工作原理可以分为前向传播和反向传播两个阶段:

前向传播(Forward Propagation)

  • 输入数据(如图像)首先经过输入层进入CNN。

  • 在卷积层中,卷积核在输入数据上滑动,进行卷积运算,生成特征图。每个卷积核提取不同的特征,多个卷积核组合生成多个特征图。

  • 特征图经过激活层进行非线性变换,提取更复杂的特征。

  • 池化层对特征图进行降采样,保留重要特征,同时减少计算量和参数数量。

  • 经过多个卷积层、激活层和池化层后,特征图被展平为一维向量,输入到全连接层。

  • 全连接层对特征进行整合,输出最终的分类结果或回归值。

反向传播(Backward Propagation)

  • 在训练过程中,CNN通过反向传播算法更新网络参数,以最小化损失函数。

  • 首先,计算输出层的损失值(如交叉熵损失函数用于分类任务,均方误差损失函数用于回归任务)。

  • 然后,通过链式法则,将损失值逐层反向传播到卷积层、激活层和池化层,计算每一层的梯度。

  • 最后,根据梯度更新卷积核的权重和偏置,以及全连接层的参数。常用的优化算法包括随机梯度下降(SGD)、Adam等。

  • 反向传播的目标是使网络在训练数据上具有更好的拟合能力,同时避免过拟合。

图1,CNN卷积举例

四、CNN实例代码(简单的CNN用于 MNIST 手写数字识别任务)

1. 导入必要的包和设置

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib
matplotlib.use('TkAgg')  # 或者使用 'Agg'
import matplotlib.pyplot as plt
  • torch:PyTorch 的核心库,用于张量操作。

  • torch.nn:包含神经网络构建的各种模块和层。

  • torch.utils.data:提供数据加载工具(DataLoader等),便于批量读取数据。

  • torchvision:主要用于计算机视觉任务,内置多个公开数据集和常用的图像预处理工具。

  • matplotlib:用于数据可视化,本例中可以用来显示 MNIST 图像。matplotlib.use('TkAgg') 是设置后端显示方式(也可以使用 'Agg' 用于无图形界面环境)。

2. 参数设置

EPOCH = 2             # 训练轮数
BATCH_SIZE = 50       # 每批次样本数量
LR = 0.001            # 学习率
DOWNLOAD_MNIST = True # 是否下载MNIST数据集
  • EPOCH:定义数据将完整通过模型的次数。本例设置为 2 次;

  • BATCH_SIZE:每个训练批次的样本数,较小的批量可以提供更稳定的梯度更新;

  • LR(Learning Rate):学习率,用于控制每一步参数更新的步长;

  • DOWNLOAD_MNIST:标识是否需要下载 MNIST 数据集,第一次运行时需要下载数据。

3. 数据加载与预处理

3.1 加载训练数据

train_data = torchvision.datasets.MNIST(
    root='./mnist',
    train=True,
    download=DOWNLOAD_MNIST,
    transform=torchvision.transforms.ToTensor()
)
print("训练集数据形状:", train_data.data.size())
print("训练集标签形状:", train_data.targets.size())
  • torchvision.datasets.MNIST:从本地或者通过网络下载 MNIST 数据集。

    • root='./mnist' 指定数据存放的目录。

    • train=True 加载训练集。

    • download=DOWNLOAD_MNIST 根据参数设置是否下载数据。

    • transform=torchvision.transforms.ToTensor():预处理步骤,将 PIL 图片转换成浮点型 Tensor,并将像素值归一化到 [0,1] 范围。

  • 打印训练数据和标签的尺寸,通常训练数据的尺寸为 [60000, 28, 28](60000 张 28×28 的图像),标签尺寸为 [60000]

3.2 可选:显示一张图像

plt.imshow(train_data.data[0].numpy(), cmap='gray')
plt.title('%i' % train_data.targets[0])
plt.show()
  • 用于可视化数据,通过 imshow 显示第一张图片,并将对应标签作为标题显示。

3.3 构造训练数据加载器

train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
  • DataLoader:自动将数据按批次加载,并且 shuffle=True 表示每个 epoch 内会打乱数据,提高模型泛化性能。

3.4 加载测试数据和预处理

test_data = torchvision.datasets.MNIST(
    root='./mnist',
    train=False,
    transform=torchvision.transforms.ToTensor()
)
# 这里只使用前3000个测试样本进行评估
test_x = test_data.data.unsqueeze(1).float()[:3000] / 255  # shape: [3000, 1, 28, 28]
test_y = test_data.targets[:3000]
  • 加载测试集时,train=False 表示加载测试数据;

  • transform 同样把图片转换为 Tensor;

  • 注意这里获取测试数据并没有使用 DataLoader,而是直接处理 Tensor。

    • test_data.data 的原始尺寸通常为 [10000, 28, 28]。但卷积层期望输入有一个 channel 维度,因此调用 unsqueeze(1) 在维度1上扩展,使其变成 [N, 1, 28, 28]

    • test_data.data 转换为 float 并除以 255 归一化到 [0,1]。

    • 最后只取前3000个样本进行评估,避免计算量过大。

4. 定义卷积神经网络模型

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=1,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(
                in_channels=16,
                out_channels=32,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)
        )
        self.out = nn.Linear(32 * 7 * 7, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0), -1)
        output = self.out(x)
        return output

4.1 初始化模块(__init__ 部分)

  • 第一层卷积 (self.conv1)
    • nn.Conv2d:接受输入图像 1 个通道,输出16个通道。

    • kernel_size=5 指定卷积核尺寸为 5×5。

    • stride=1 指定步长为 1。

    • padding=2 保持卷积之后图像尺寸不变(公式:(kernel_size - 1) / 2)。

    • 后接 ReLU 激活函数(添加非线性)和 nn.MaxPool2d(kernel_size=2),将特征图尺寸缩减一半,即从 28×28 变成 14×14,同时通道数为 16。

  • 第二层卷积 (self.conv2)
    • 输入通道为第一层输出 16,输出 32 个通道;

    • 同样使用 5×5 卷积核,padding=2 保持尺寸;

    • 后接 ReLU 激活函数和 2×2 最大池化,此时输出尺寸从 14×14 变为 7×7,通道数为 32。

  • 全连接层 (self.out)
    • 将卷积层输出的特征先展平,尺寸变为 32×7×7,共计 1568 个特征。

    • 通过全连接层映射到 10 个类别,分别对应 0~9 十个数字。

4.2 定义前向传播(forward 函数)

def forward(self, x):
    x = self.conv1(x)
    x = self.conv2(x)
    x = x.view(x.size(0), -1)
    output = self.out(x)
    return output
  • 输入 x 首先通过 conv1 层,再通过 conv2 层;

  • x.view(x.size(0), -1) 将每个样本的 3D 特征图展平成一维向量,方便输入全连接层;

  • 最后经过全连接层输出各类别的得分。

5. 模型实例化以及优化器和损失函数的定义

cnn = CNN()
print("网络结构:")
print(cnn)
  • 实例化 CNN 模型,并打印网络结构,方便验证层次和参数设置是否正确。
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
loss_fn = nn.CrossEntropyLoss()
  • Adam 优化器:自动调整每个参数的学习率,适合大多数任务;

  • CrossEntropyLoss:用于分类任务的损失函数,内部包括 softmax 操作。

6. 训练过程

for epoch in range(EPOCH):
    for step, (b_x, b_y) in enumerate(train_loader):
        output = cnn(b_x)               # 前向传播,得到预测输出
        loss = loss_fn(output, b_y)     # 计算输出与真实标签之间的损失
        optimizer.zero_grad()           # 清空当前 batch 所有参数的梯度
        loss.backward()                 # 反向传播,计算梯度
        optimizer.step()                # 根据梯度更新网络参数

        if step % 50 == 0:
            cnn.eval()  # 切换模型到评估模式(例如 Dropout、BatchNorm 等层在测试时的行为不同)
            with torch.no_grad():  # 在验证时关闭梯度计算,提高效率
                test_output = cnn(test_x)
                y_pred = test_output.argmax(dim=1)
                accuracy = (y_pred == test_y).float().mean().item()
            print(f'Epoch: {epoch} | Step: {step} | Loss: {loss.item():.4f} | Accuracy: {accuracy:.4f}')
            cnn.train()  # 恢复训练模式
  • 外层循环:遍历所有的 epoch,整个数据集会被完整训练多次。

  • 内层循环:遍历每个 mini-batch(由 DataLoader 提供),进行如下操作:

    • 前向传播:将批次图像 b_x 输入模型,得到预测结果 output

    • 计算损失:使用交叉熵损失函数计算预测 output 与真实标签 b_y 之间的误差。

    • 梯度清零:在每次梯度反向传播前,调用 optimizer.zero_grad() 清除上次迭代累积的梯度。

    • 反向传播:通过 loss.backward() 计算每个参数的梯度。

    • 参数更新:调用 optimizer.step() 根据计算得到的梯度更新模型参数。

  • 每 50 步:对当前模型在测试集上的表现进行评估:

    • 调用 cnn.eval() 切换到评估模式,确保如 Dropout 等层在测试时不再随机失活。

    • 使用 with torch.no_grad() 包裹预测过程以关闭梯度计算,从而节省内存和计算资源。

    • 计算测试集输出后,通过 argmax(dim=1) 选择概率最大的类别,并计算准确率。

    • 打印当前的 epoch、步数、损失值和准确率,并使用 cnn.train() 切换回训练模式。

7. 测试结果展示

with torch.no_grad():
    test_output = cnn(test_x[:10])
    y_pred = test_output.argmax(dim=1)
    print("预测结果:", y_pred.tolist())
    print("真实标签:", test_y[:10].tolist())
  • 目的:从测试集中选取前 10 个样本进行测试,并展示模型的预测结果与实际标签对比。

  • 使用 with torch.no_grad() 关闭梯度计算。

  • 调用 argmax(dim=1) 得到预测类别,转换为 Python 的列表打印输出。

总体代码

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib
matplotlib.use('TkAgg')  # 或者使用 'Agg'
import matplotlib.pyplot as plt

# ------------------------
# 参数设定
# ------------------------
EPOCH = 2             # 训练轮数
BATCH_SIZE = 50       # 每批次样本数量
LR = 0.001            # 学习率
DOWNLOAD_MNIST = True # 是否下载MNIST数据集

# ------------------------
# 数据加载与预处理
# ------------------------
# 加载训练集,这里利用 transform 将数据转为 [0,1] 的 Tensor,同时自动添加 channel 维度
train_data = torchvision.datasets.MNIST(
    root='./mnist',
    train=True,
    download=DOWNLOAD_MNIST,
    transform=torchvision.transforms.ToTensor()
)
print("训练集数据形状:", train_data.data.size())
print("训练集标签形状:", train_data.targets.size())

# 可选:显示第一张训练图像
# plt.imshow(train_data.data[0].numpy(), cmap='gray')
# plt.title('%i' % train_data.targets[0])
# plt.show()

# 构造数据加载器,自动 shuffle
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)

# 加载测试数据
test_data = torchvision.datasets.MNIST(
    root='./mnist',
    train=False,
    transform=torchvision.transforms.ToTensor()
)
# 这里只使用前3000个测试样本进行评估
test_x = test_data.data.unsqueeze(1).float()[:3000] / 255  # shape: [3000, 1, 28, 28]
test_y = test_data.targets[:3000]

# ------------------------
# 定义卷积神经网络
# ------------------------
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第1个卷积层:输入1通道,输出16通道;卷积核 5x5;padding=2 保证尺寸不变;之后 ReLU 激活和2x2最大池化
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=1,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)  # 输出尺寸: (16, 14, 14)
        )
        # 第2个卷积层:输入16通道,输出32通道;卷积核5x5;padding=2;再经过 ReLU 激活和2x2最大池化
        self.conv2 = nn.Sequential(
            nn.Conv2d(
                in_channels=16,
                out_channels=32,
                kernel_size=5,
                stride=1,
                padding=2
            ),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)  # 输出尺寸: (32, 7, 7)
        )
        # 全连接层:将32*7*7的特征扁平化为一维,映射到10个类别
        self.out = nn.Linear(32 * 7 * 7, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        # 展平特征,准备输入全连接层(batch_size, 32*7*7)
        x = x.view(x.size(0), -1)
        output = self.out(x)
        return output

cnn = CNN()
print("网络结构:")
print(cnn)

# ------------------------
# 定义优化器与损失函数
# ------------------------
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
loss_fn = nn.CrossEntropyLoss()

# ------------------------
# 训练过程
# ------------------------
for epoch in range(EPOCH):
    for step, (b_x, b_y) in enumerate(train_loader):
        # 前向传播
        output = cnn(b_x)
        loss = loss_fn(output, b_y)
        # 清除梯度,反向传播和参数更新
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 每50步进行一次评估
        if step % 50 == 0:
            cnn.eval()  # 转换为评估模式(如有Dropout、BatchNorm则更明显)
            with torch.no_grad():
                test_output = cnn(test_x)
                y_pred = test_output.argmax(dim=1)
                accuracy = (y_pred == test_y).float().mean().item()
            print(f'Epoch: {epoch} | Step: {step} | Loss: {loss.item():.4f} | Accuracy: {accuracy:.4f}')
            cnn.train()  # 恢复训练模式

# ------------------------
# 测试集预测结果展示
# ------------------------
with torch.no_grad():
    test_output = cnn(test_x[:15])
    y_pred = test_output.argmax(dim=1)
    print("预测结果:", y_pred.tolist())
    print("真实标签:", test_y[:15].tolist())

总结

整个代码主要分为以下几个阶段:

  1. 导入库和预设参数:初始化工作,为数据加载、网络构造和训练提供基础配置。

  2. 数据加载与预处理:从 MNIST 数据集中加载训练与测试数据,利用预处理(如归一化、扩展维度)使数据适配网络输入要求。

  3. 网络模型构造:使用 PyTorch 定义一个简单的 CNN,通过两层卷积和池化获得特征,最后经全连接层输出分类结果。

  4. 训练过程:利用训练数据迭代更新模型参数,同时每隔一定步数在测试集上评估模型准确率,监控训练状态。

  5. 测试和结果展示:利用训练好的模型对少量测试数据进行预测,并将预测结果与真实标签进行对比查看效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玖釉-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值