深度学习入门:卷积神经网络(CNN)原理与 PyTorch 实战
卷积神经网络CNN:从图像原理到PyTorch实战
1. 引言
卷积神经网络(Convolutional Neural Networks,CNN)是深度学习图像处理领域最核心的模型之一。本文结合课程讲义内容,从图像在计算机中的表示出发,系统梳理CNN的卷积层、池化层、全连接层、经典网络结构,并通过MNIST手写数字识别项目,演示如何使用PyTorch搭建、训练和优化一个完整的卷积神经网络。
2. 图像在计算机中的表示
图像在计算机中是一堆按顺序排列的数字,数值范围通常为0到255。0表示最暗,255表示最亮。对于只有黑白颜色的灰度图,可以表示为一个二维矩阵;而更普遍的彩色图片使用RGB颜色模型,即红、绿、蓝三原色按不同比例相加,产生多种颜色。
在RGB颜色模型中,单个矩阵会扩展成三个有序排列的矩阵,也可以用三维张量表示。其中每一个矩阵称为图片的一个通道(channel)。因此,一张图片可以由宽、高、深(通道数)三个维度来描述。例如,一张32×32的彩色图片可以表示为32×32×3的张量,其中3表示RGB三个通道。
3. 为什么需要CNN
3.1 传统神经网络的局限
传统全连接神经网络直接处理图像时,会把每个像素都作为一个输入节点。这样做存在明显问题:当物体在图片中的位置、角度、大小发生变化时,网络需要大量位于不同位置的训练数据才能学会识别。为了扩大网络学习这些变体的能力,传统方法需要增加数据量,同时增加隐藏层个数,这会导致参数量急剧增加,训练成本很高。
3.2 画面不变性
CNN要解决的核心问题之一是画面不变性:一个物体不管在画面左侧还是右侧,都应该被识别为同一物体。我们希望建立的网络能够尽可能满足这种平移、旋转、缩放等不变性特点。CNN通过局部连接、权值共享和池化等机制,天然适合处理图像数据。
4. 卷积层:CNN的核心
4.1 什么是卷积
对图像的不同窗口数据和卷积核做内积(逐个元素相乘再求和)的操作,就是所谓的“卷积”操作,这也是卷积神经网络名字的来源。卷积核是一组固定的权重,因为每个神经元的多个权重固定,所以又可以看作一个恒定的滤波器filter。
卷积操作可以帮助计算机提取图像中的局部特征,例如边缘、纹理和轮廓。与人眼观看事物相似,卷积神经网络会先关注图片的轮廓和低级特征,再逐层组合成高级语义特征。
4.2 步长、填充和卷积核个数
- 步长stride:每次滑动窗口移动的位置长度。例如stride=2表示数据窗口每次移动两个像素。
- 卷积核个数:决定输出结果的depth(厚度),同时代表卷积核的数量。例如depth=2表示有两组神经元,即两个滤波器。
- 填充值zero-padding:在外围边缘补充若干圈0,方便从初始位置以步长为单位刚好滑动到末尾位置,通俗地讲就是让总长度能被步长整除。
4.3 输出尺寸计算
假设输入数据为32×32×3的图像,使用10个5×5×3的卷积核进行操作,步长为1,边界填充为2,则输出尺寸为:
(32 - 5 + 2 × 2) / 1 + 1 = 32
最终输出规模为32×32×10的特征图。这里10由卷积核个数决定。
4.4 感受野与参数数量
感受野是指输出特征图上的一个像素对应输入图像上的区域大小。计算卷积核参数量时,需要考虑通道数。例如,对于3通道图片:
- 使用3个
7×7的卷积核,每个卷积核覆盖3个通道,参数量为:3 × (3 × 7 × 7) = 441。 - 使用3层
3×3卷积核堆叠,参数量为:3 × (3 × 3 × 3) + 3 × (3 × 3 × 3) + 3 × (3 × 3 × 3) = 243。
可以看到,使用更小的卷积核堆叠不仅可以减少参数量,还能增加非线性表达能力,这也是VGGNet等网络采用3×3卷积的原因。
5. 池化层:降采样与防过拟合
池化层(Pooling)是一种降采样操作,可以减小数据的空间大小,因此参数数量和计算量也会下降,这在一定程度上控制了过拟合。通常来说,CNN的卷积层之间都会周期性地插入池化层。
常见的池化方式包括:
- 最大池化(max pooling):选择图像区域的最大值作为该区域池化后的值,是最常见的方式。
- 平均池化(average pooling):计算图像区域的平均值作为该区域池化后的值。
- 全局平均池化(global average pooling)和全局最大池化(global max pooling):针对整个特征图进行池化,常用于网络末端的替代全连接层。
池化层与卷积层类似,使用固定形状的窗口在输入区域上滑动。但与卷积层不同,池化层不包含可学习参数。
最大池化为什么有效?一条重要解释是:卷积后的特征图中存在大量对识别物体不必要的冗余信息,最大池化在压缩空间尺寸的同时,能够保留最显著的特征值,因此不会明显损坏识别结果。
6. 全连接层:从特征到分类
当卷积和池化操作抓取到足以识别图片的特征后,接下来的任务就是分类。全连接层(也叫前馈层)可以将最后的输出映射到线性可分的空间。通常卷积网络的最后会将末端得到的长方体特征图平摊(flatten)成一个长长的向量,并送入全连接层配合输出层进行分类。
例如,在MNIST手写数字识别中,经过卷积和池化后得到64×7×7的特征图,先展平为一维向量,再经过128、256等全连接层,最终输出10个类别的得分。
7. 经典CNN模型
以下是几个比较有名的卷积神经网络结构:
- LeNet:第一个成功的卷积神经网络应用,主要用于手写数字识别。
- AlexNet:类似LeNet,但更深更大。使用了层叠的卷积层来抓取特征,通常是一个卷积层后紧接一个max pooling层。
- ZF Net:增加了中间卷积层的尺寸,让第一层的stride和filter size更小,从而保留更多信息。
- GoogLeNet:减少参数数量,最后一层用max pooling层代替了全连接层,并引入Inception模块。
- VGGNet:只使用
3×3卷积层和2×2池化层从头到尾堆叠,结构简洁。 - ResNet:引入跨层连接和batch normalization,有效缓解深层网络训练困难。
- DenseNet:将跨层连接从头进行到尾,加强特征复用。
8. 搭建卷积神经网络:MNIST图像识别实战
下面使用PyTorch实现一个卷积神经网络,并在MNIST手写数字数据集上完成训练和测试。网络结构包含卷积层、最大池化层、激活函数和全连接层。
8.1 数据加载与预处理
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
training_data = datasets.MNIST(
root="data",
train=True,
download=True,
transform=transform
)
test_data = datasets.MNIST(
root="data",
train=False,
download=True,
transform=transform
)
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=False)
8.2 定义CNN模型
import torch.nn as nn
import torch.nn.functional as F
class CNN(nn.Module):
def init(self):
super(CNN, self).init()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 256)
self.fc3 = nn.Linear(256, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = self.flatten(x)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
8.3 训练与测试函数
def train(dataloader, model, loss_fn, optimizer, device):
model.train()
size = len(dataloader.dataset)
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()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test(dataloader, model, loss_fn, device):
model.eval()
test_loss, correct = 0, 0
size = len(dataloader.dataset)
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= len(dataloader)
accuracy = correct / size
print(f"Accuracy: {(100 * accuracy):>0.1f}%, Avg loss: {test_loss:>8f}")
return accuracy
8.4 数据增强
数据增强(Data Augmentation)用于缓解深度学习中数据不足的情况。在图像领域,数据增强首先得到广泛应用,随后延伸到NLP领域。其主要方向是增加训练数据的多样性,从而提高模型泛化能力。
常见的图像增强方式包括:颜色变换、垂直翻转、随机裁剪和随机旋转。在PyTorch中,可以通过torchvision.transforms方便地实现:
from torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomRotation(10),
transforms.RandomCrop(28, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
8.5 保存最优模型
训练过程中可以根据验证集准确率保存最优模型,避免后期过拟合导致性能下降。
import torch
best_accuracy = 0.0
for epoch in range(epochs):
train(train_dataloader, model, loss_fn, optimizer, device)
accuracy = test(test_dataloader, model, loss_fn, device)
if accuracy > best_accuracy:
best_accuracy = accuracy
torch.save(model.state_dict(), "best_model.pth")
print("Saved best model")
9. 调整学习率
常用的学习率有0.1、0.01、0.001等,学习率越大则权重更新越快。一般来说,我们希望在训练初期学习率大一些,使网络快速收敛;在训练后期学习率小一些,使网络更好地收敛到最优解。
PyTorch的学习率调整策略通过torch.optim.lr_scheduler接口实现,主要提供三类方法:
- 有序调整:等间隔调整(StepLR)、多间隔调整(MultiStepLR)、指数衰减(ExponentialLR)、余弦退火(CosineAnnealingLR)。
- 自适应调整:依训练状况伺机而变,通过监测loss或accuracy等指标的变化情况调整学习率,例如ReduceLROnPlateau。
- 自定义调整:通过自定义关于epoch的lambda函数调整学习率,例如LambdaLR。
9.1 有序调整示例
StepLR等间隔调整:每训练step_size个epoch,学习率调整为lr = lr × gamma。
torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1)
参数说明:optimizer为训练使用的优化器,step_size为学习率下降间隔的epoch数,gamma为学习率调整倍数,默认为0.1。
MultiStepLR多间隔调整:在指定的epoch列表处调整学习率。
torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 30, 80], gamma=0.1)
ExponentialLR指数衰减:学习率按指数规律衰减。
torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma)
其中gamma为学习率调整倍数的底数,指数为epoch。
CosineAnnealingLR余弦退火:按余弦函数调整学习率。
torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min=0)
当epoch=T_max时,学习率下降到余弦函数最小值;当epoch>T_max时,学习率会增大。也即学习率按余弦周期变化。
9.2 自适应调整与自定义调整
ReduceLROnPlateau:当某个指标不再改善时降低学习率。
torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode="min",
factor=0.1,
patience=10,
verbose=False,
threshold=0.0001,
threshold_mode="rel",
cooldown=0,
min_lr=0,
eps=1e-08
)
LambdaLR:通过自定义lambda函数计算学习率调整倍数。
torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
lr_lambda可以是函数或函数列表,通常是epoch的函数;当有多个参数组时,设置为list。
10. 迁移学习与ResNet
10.1 什么是迁移学习
迁移学习是指利用已经训练好的模型,在新的任务上进行微调。迁移学习可以加快模型训练速度,提高模型性能,并且在数据稀缺的情况下也能很好地工作。
迁移学习的一般步骤:
- 选择预训练模型和适当的层。通常选择在大规模图像数据集(如ImageNet)上预训练的模型,如VGG、ResNet等,再根据新数据集特点选择需要微调的层。
- 冻结预训练模型的参数,保持权重不变,只训练新增层或微调部分层,避免过拟合。
- 在新数据集上训练新增层,使模型适应新任务。
- 解冻部分预训练层进行微调,进一步提升性能。
- 在测试集上评估模型,必要时调整超参数或微调范围。
10.2 ResNet网络
ResNet由微软实验室的何凯明等人于2015年提出,斩获当年ImageNet竞赛分类任务第一名、目标检测第一名,并在COCO数据集中获得目标检测和图像分割第一名。
传统卷积神经网络通过卷积层和池化层叠加组成。随着网络层数加深,学习效果并不会一直变好,反而会出现两个问题:
- 梯度消失和梯度爆炸:若每一层误差梯度小于1,反向传播时网络越深梯度越趋近于0;若每一层误差梯度大于1,梯度会越来越大。
- 退化问题:深层网络即使能训练,训练误差也可能比浅层网络更高。
为了解决梯度问题,论文提出通过数据预处理以及在网络中使用Batch Normalization(BN)层来解决;为了解决退化问题,ResNet引入残差连接,人为地让神经网络某些层跳过下一层神经元的连接,隔层相连,弱化每层之间的强联系。
残差结构使用一种shortcut连接,也可理解为捷径。让特征矩阵隔层相加,注意F(X)和X形状要相同,所谓相加是特征矩阵相同位置上的数字进行相加。
10.3 在PyTorch中使用ResNet
下面示例演示如何基于预训练的ResNet18增加一个新的全连接层,以适应新的分类任务:
import torch.nn as nn
from torchvision import models
class NewNet(nn.Module):
def init(self, num_classes=20):
super(NewNet, self).init()
self.resnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
self.fc = nn.Linear(1000, num_classes)
def forward(self, x):
x = self.resnet(x)
x = self.fc(x)
return x
11. 总结
本文从图像表示出发,系统介绍了卷积神经网络的基本组成:卷积层负责提取局部特征,池化层负责降采样并降低计算量,全连接层负责将特征映射到分类空间。随后通过MNIST数据集完整演示了PyTorch搭建CNN、数据加载、数据增强、训练测试、保存最优模型以及学习率调整的完整流程。最后介绍了迁移学习和ResNet残差网络的核心思想与使用方法。
掌握这些内容后,你可以进一步尝试CIFAR-10、ImageNet等更复杂的图像分类任务,也可以将CNN作为特征提取器,迁移到目标检测、图像分割等更丰富的视觉任务中。
更多推荐

所有评论(0)