初识深度学习——CNN
2026/9/14 3:36:54 网站建设 项目流程

一、引言:当图像遇到卷积

在上一篇博客中,我们用全连接神经网络实现了手写数字识别,达到了不错的准确率。但如果你仔细观察那个网络结构,会发现一个"隐患":我们将28×28的图像直接展平成784维向量,然后连接到全连接层

这种做法存在两个严重问题:

问题一:参数爆炸。假设第一层有1000个神经元,那么仅这一层就有784×1000 = 784,000个参数。如果输入是224×224的彩色图像(3通道),参数量会达到150,528×1000 = 1.5亿!这显然不可接受。

问题二:丢失空间信息。图像展平后,相邻像素之间的空间关系被彻底打乱。对于图像识别来说,"某个像素的上下左右是什么"恰恰是最重要的信息。

卷积神经网络(Convolutional Neural Network, CNN)正是为了解决这些问题而生的。它通过卷积操作提取图像的局部特征,通过权值共享大幅减少参数量,通过池化操作降低特征维度,是计算机视觉领域最重要的深度学习模型。

本篇博客将基于PyTorch框架,带领大家从零构建一个CNN来识别MNIST手写数字,深入理解卷积、池化、通道等核心概念。

二、CNN的核心概念

2.1 什么是卷积

卷积是CNN的核心操作。简单来说,它用一个小的窗口(称为卷积核滤波器)在图像上滑动,每次滑动时计算窗口内像素与卷积核的加权和,得到一个新的值。

输入图像(5×5) 卷积核(3×3) 输出特征图(3×3) 1 0 1 0 1 1 0 1 ? ? ? 0 1 0 1 0 0 1 0 ? ? ? 1 0 1 0 1 1 0 1 ? ? ? 0 1 0 1 0 1 0 1 0 1

卷积的意义

  • 卷积核可以看作一个"特征检测器"——不同的卷积核检测不同的特征(边缘、纹理、角点等)

  • 通过训练,网络会自动学习到最有用的卷积核

  • 权值共享:同一个卷积核在整个图像上滑动,参数只有9个,而不是784×1000个

2.2 关键参数

在PyTorch中,卷积层使用nn.Conv2d()创建,核心参数包括:

参数含义说明
in_channels输入通道数灰度图为1,RGB彩色图为3
out_channels输出通道数等于卷积核的个数
kernel_size卷积核大小常用3、5、7
stride步长卷积核每次滑动的距离,默认1
padding填充在图像边缘补零的圈数

2.3 输出尺寸计算公式

卷积后的特征图尺寸可以通过以下公式计算:

示例:输入28×28,kernel_size=5,stride=1,padding=2

这说明:当stride=1,padding=(kernel_size-1)/2时,输出尺寸与输入尺寸相同。这是一种常用的设计策略,使得网络可以加深而不改变空间维度。

2.4 池化操作

池化(Pooling)用于降低特征图的空间维度,减少参数量和计算量,同时增强特征的平移不变性。

最常用的池化是最大池化(Max Pooling):在窗口内取最大值。

特征图(4×4) 最大池化(2×2) 输出(2×2) 1 3 2 4 3 4 5 6 7 8 → 7 8 9 2 1 3 4 5 6 7

池化不改变通道数,只改变空间尺寸。

2.5 激活函数——ReLU

在CNN中,ReLU(Rectified Linear Unit)是最常用的激活函数:

ReLU(x)=max(0,x)ReLU(x)=max(0,x)

ReLU的优势

  • 计算简单(只需比较大小)

  • 缓解梯度消失问题

  • 稀疏激活(负值被置零),提高计算效率

相比上一篇博客中使用的Sigmoid函数,ReLU在深层网络中表现更好。

三、CNN网络架构设计

3.1 整体结构

本篇示例代码设计的CNN由3个卷积块和一个全连接层组成:

输入(1×28×28) ↓ Conv1: Conv2d(1→16) + ReLU + MaxPool2d(2) → (16×14×14) ↓ Conv2: Conv2d(16→32) + ReLU + Conv2d(32→32) + ReLU + MaxPool2d(2) → (32×7×7) ↓ Conv3: Conv2d(32→64) + ReLU → (64×7×7) ↓ Flatten: (64×7×7 = 3136) ↓ 全连接: Linear(3136→10) ↓ 输出(10个类别得分)

尺寸变化分析

  • 输入:1×28×28

  • Conv2d(1→16, k=5, s=1, p=2):16×28×28(尺寸不变)

  • ReLU:16×28×28(尺寸不变)

  • MaxPool2d(2):16×14×14(尺寸减半)

第二个卷积块包含两个卷积层:

self.conv2 = nn.Sequential( nn.Conv2d(16, 32, 5, 1, 2), # 32×14×14 nn.ReLU(), # 32×14×14 nn.Conv2d(32, 32, 5, 1, 2), # 32×14×14 nn.ReLU(), # 32×14×14 nn.MaxPool2d(2), # 32×7×7 )

第三个卷积块只有一个卷积层:

self.conv3 = nn.Sequential( nn.Conv2d(32, 64, 5, 1, 2), # 64×7×7 nn.ReLU(), # 64×7×7 )

3.3 全连接层与展平操作

self.out = nn.Linear(64 * 7 * 7, 10)

在进入全连接层之前,需要将三维特征图展平为一维向量:

x = x.view(x.size(0), -1) # (batch_size, 64*7*7)
  • x.size(0):batch_size(本例中为64)

  • -1:自动推断剩余维度的大小(64×7×7 = 3136)

  • 展平后形状:(64, 3136)

3.4 参数数量统计

让我们计算每一层的参数量:

计算参数量
conv116×(5×5×1+1)416
conv2-132×(5×5×16+1)12,832
conv2-232×(5×5×32+1)25,632
conv364×(5×5×32+1)51,264
out3136×10+1031,370
总计约12万

相比全连接网络的约16万参数,CNN参数量更少,且随着图像尺寸增大,CNN的优势会更加明显。

四、前向传播

def forward(self, x): x = self.conv1(x) # (64,1,28,28) → (64,16,14,14) x = self.conv2(x) # (64,16,14,14) → (64,32,7,7) x = self.conv3(x) # (64,32,7,7) → (64,64,7,7) x = x.view(x.size(0), -1) # 展平 → (64, 3136) output = self.out(x) # 全连接 → (64, 10) return output

数据流动过程

步骤操作输出形状
输入-(64, 1, 28, 28)
conv1卷积+ReLU+池化(64, 16, 14, 14)
conv2双层卷积+池化(64, 32, 7, 7)
conv3单层卷积(64, 64, 7, 7)
view展平(64, 3136)
out全连接(64, 10)

五、训练与测试

5.1 训练函数

训练函数与上一篇博客基本相同,核心五步:

def train(dataloader, model, loss_fn, optimizer): model.train() for x, y in dataloader: x, y = x.to(device), y.to(device) pred = model(x) loss = loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step()

5.2 测试函数

def test(dataloader, model, loss_fn): model.eval() size = len(dataloader.dataset) correct = 0 with torch.no_grad(): for x, y in dataloader: x, y = x.to(device), y.to(device) pred = model(x) correct += (pred.argmax(1) == y).type(torch.float).sum().item() print(f"Accuracy: {100*correct/size}%")

5.3 训练配置

loss_fn = nn.CrossEntropyLoss() # 交叉熵损失 optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # Adam优化器 epochs = 10

六、全连接网络 vs 卷积神经网络

对比维度全连接网络(NN)卷积神经网络(CNN)
输入处理直接展平保持二维结构
空间信息完全丢失保留局部关系
参数量较多较少(权值共享)
特征提取自动提取局部特征
平移不变性池化提供
适合任务结构化数据图像、视频

为什么CNN更适合图像?

  1. 局部连接:每个神经元只连接输入的局部区域,符合图像的局部相关性

  2. 权值共享:同一个卷积核在整张图上滑动,大大减少参数

  3. 平移不变性:无论物体出现在图像哪个位置,都能被同一卷积核检测到

  4. 层次化特征:浅层学习边缘、纹理,深层学习语义特征

七、CNN的核心设计思想

7.1 感受野

感受野是指输出特征图上一个像素对应输入图像上的区域大小。

  • 第一层卷积(5×5)的感受野为5×5

  • 经过两层卷积后,感受野会叠加扩大

  • 更深的网络,感受野更大,能捕捉更全局的特征

7.2 通道的变化规律

阶段通道数空间尺寸说明
输入128×28原始灰度图
conv1后1614×14提取16种低级特征
conv2后327×7提取32种中级特征
conv3后647×7提取64种高级特征

规律:随着网络加深,通道数增加,空间尺寸减小。这是一种常见的设计模式——用更多的通道来编码更丰富的语义信息。

7.3 为什么用多个卷积层堆叠?

在conv2中,我们使用了两个连续的3×3卷积层,而不是一个5×5卷积层。这样做有两个好处:

  1. 参数更少:两个3×3卷积的参数量为 2×3×3 = 18,而一个5×5卷积为 25

  2. 非线性更强:两个ReLU比一个ReLU引入更多非线性

这个思想来自VGG网络,是深度学习中的重要设计原则。

八、完整代码总结

import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvision.transforms import ToTensor # 1. 加载数据 training_data = datasets.MNIST(root="data", train=True, download=True, transform=ToTensor()) test_data = datasets.MNIST(root="data", train=False, download=True, transform=ToTensor()) # 2. 创建DataLoader train_dataloader = DataLoader(training_data, batch_size=64) test_dataloader = DataLoader(test_data, batch_size=64) # 3. 选择设备 device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" # 4. 构建CNN模型 class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Sequential( nn.Conv2d(1, 16, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(kernel_size=2), ) self.conv2 = nn.Sequential( nn.Conv2d(16, 32, 5, 1, 2), nn.ReLU(), nn.Conv2d(32, 32, 5, 1, 2), nn.ReLU(), nn.MaxPool2d(2), ) self.conv3 = nn.Sequential( nn.Conv2d(32, 64, 5, 1, 2), nn.ReLU(), ) self.out = nn.Linear(64 * 7 * 7, 10) def forward(self, x): x = self.conv1(x) # (64,1,28,28) → (64,16,14,14) x = self.conv2(x) # (64,16,14,14) → (64,32,7,7) x = self.conv3(x) # (64,32,7,7) → (64,64,7,7) x = x.view(x.size(0), -1) # 展平 output = self.out(x) return output model = CNN().to(device) # 5. 定义损失函数和优化器 loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 6. 训练函数 def train(dataloader, model, loss_fn, optimizer): model.train() for batch, (x, y) in enumerate(dataloader): x, y = x.to(device), y.to(device) pred = model(x) loss = loss_fn(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() # 7. 测试函数 def test(dataloader, model, loss_fn): model.eval() size = len(dataloader.dataset) correct = 0 with torch.no_grad(): for x, y in dataloader: x, y = x.to(device), y.to(device) pred = model(x) correct += (pred.argmax(1) == y).type(torch.float).sum().item() print(f"Accuracy: {100*correct/size}%") # 8. 执行训练和测试 epochs = 10 for t in range(epochs): print(f"Epoch {t+1}") train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model, loss_fn)

九、总结

本篇博客通过构建一个完整的CNN,系统学习了卷积神经网络的核心知识:

知识点核心内容
卷积层nn.Conv2d(),提取局部特征,权值共享
池化层nn.MaxPool2d(),降低空间维度
激活函数ReLU,缓解梯度消失
Sequential组合多个层为容器
展平操作view(),将三维特征图转为一维
全连接层nn.Linear(),输出分类结果
通道变化通道递增,空间递减

CNN的核心设计原则

  1. 局部连接:每个神经元只关注局部区域

  2. 权值共享:同一卷积核在整张图上复用

  3. 层次化特征:浅层低级→深层高级

  4. 空间下采样:通过池化逐步减小空间尺寸

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询