《深度学习》——CNN卷积神经网络模型及项目实例
文章目录
- CNN卷积神经网络
- 基本组成成分
- 工作原理
- 常见CNN模型
- CNN优势
- 应用领域
- 局限性
- CNN项目实例
- 项目实现
- 导入所需库
- 下载训练集和测试集数据
- 对训练和测试样本进行分批次
- 判断pytorch是否支持GPU
- 定义CNN卷积神经网络模型
- 定义训练函数
- 定义测试函数
- 创建交叉熵损失函数和优化器
- 通过多轮训练降低损失值得到最终结果
CNN卷积神经网络
卷积神经网络(Convolutional Neural Network,简称 CNN)是一种专门为处理具有网格结构数据(如图像、音频)而设计的深度学习模型,在计算机视觉、语音识别等领域有着广泛的应用。
基本组成成分
- 卷积层:是 CNN 的核心层,通过卷积核在输入数据上滑动并进行卷积运算,提取数据的局部特征。不同的卷积核可以捕捉到不同类型的特征,例如边缘、纹理等。随着网络层数的增加,卷积层可以学习到越来越复杂的特征。
- 池化层:通常紧随卷积层之后,主要作用是对数据进行下采样,降低数据的维度,减少计算量,同时还能在一定程度上防止过拟合。常见的池化操作有最大池化(Max Pooling)和平均池化(Average Pooling),最大池化是取池化窗口内的最大值,平均池化则是计算池化窗口内的平均值。
- 全连接层:在经过多个卷积层和池化层的特征提取后,全连接层将前面提取到的特征进行整合,并映射到输出空间。全连接层的每一个神经元都与上一层的所有神经元相连,用于对提取到的特征进行分类或回归等任务。
- 激活函数:为神经网络引入非线性因素,使得网络能够学习和表示复杂的非线性关系。常见的激活函数有 ReLU(Rectified Linear Unit)、Sigmoid、Tanh 等。以 ReLU 为例,其函数表达式为 f (x) = max (0, x),当输入大于 0 时,输出等于输入;当输入小于 0 时,输出为 0。
进行卷积处理:
工作原理
- 输入数据(如图像)首先进入卷积层,卷积核与输入数据进行卷积运算,生成特征图。特征图中每个元素的值是卷积核与输入数据对应局部区域的加权和。
- 经过卷积层后,特征图进入池化层进行下采样,池化操作在不改变特征图深度的情况下,减小其空间尺寸。
- 经过多个卷积层和池化层的交替处理后,得到的特征图被展平成一维向量,输入到全连接层。全连接层对特征进行进一步的处理和变换,最终输出预测结果。
常见CNN模型
-
LeNet-5(1998):首个成功应用于手写数字识别的CNN,包含卷积、池化和全连接层。
-
AlexNet(2012):在ImageNet竞赛中突破性表现,引入ReLU和Dropout。
-
VGGNet(2014):通过堆叠3×3小卷积核构建深层网络。
-
ResNet(2015):提出残差连接(Residual Block),解决深层网络梯度消失问题。
-
Transformer-based模型(如ViT):结合注意力机制,挑战传统CNN地位。
CNN优势
-
自动特征提取:无需人工设计特征,直接从数据中学习。
-
参数效率:局部连接和参数共享大幅减少参数量。
-
平移不变性:池化操作使模型对目标位置变化更鲁棒。
-
端到端学习:从原始输入到最终输出一体化训练。
应用领域
-
图像分类(如ResNet、EfficientNet)
-
目标检测(如YOLO、Faster R-CNN)
-
图像分割(如U-Net、Mask R-CNN)
-
人脸识别、医学影像分析、自动驾驶等。
局限性
-
计算资源需求:深层CNN训练需要大量GPU资源。
-
空间信息依赖:对输入尺寸敏感,需固定或动态调整。
-
序列数据处理弱:长距离依赖建模不如RNN或Transformer灵活。
CNN项目实例
- 项目需求:对手写数字进行识别。
- 数据集:此项目数据集来自MNIST 数据集由美国国家标准与技术研究所(NIST)整理而成,包含手写数字的图像,主要用于数字识别的训练和测试。该数据集被分为两部分:训练集和测试集。训练集包含 60,000 张图像,用于模型的学习和训练;测试集包含 10,000 张图像,用于评估训练好的模型在未见过的数据上的性能。
- 图像格式:数据集中的图像是灰度图像,即每个像素只有一个值表示其亮度,取值范围通常为 0(黑色)到 255(白色)。
- 图像尺寸:每张图像的尺寸为 28x28 像素,总共有 784 个像素点。
- 标签信息:每个图像都有一个对应的标签,标签是 0 到 9 之间的整数,表示图像中手写数字的值
项目实现
导入所需库
import torch
from torch import nn # 导入神经网络模块
from torch.utils.data import DataLoader # 数据包管理工具,打包数据
from torchvision import datasets # 封装了很对与图像相关的模型,数据集
from torchvision.transforms import ToTensor # 数据转换,张量,将其他类型的数据转换成tensor张量
下载训练集和测试集数据
'''下载训练数据集(包含训练集图片+标签)'''
training_data = datasets.MNIST( # 跳转到函数的内部源代码,pycharm 按下ctrl+鼠标点击
root='data', # 表示下载的手写数字 到哪个路径。60000
train=True, # 读取下载后的数据中的数据集
download=True, # 如果你之前已经下载过了,就不用再下载了
transform=ToTensor(), # 张量,图片是不能直接传入神经网络模型
# 对于pytorch库能够识别的数据一般是tensor张量
)
'''下载测试数据集(包含训练图片+标签)'''
test_data = datasets.MNIST(
root='data',
train=False,
download=True,
transform=ToTensor(), # Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如pytorch,TensorFlow)
) # numpy数组只能在cpu上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(training_data))
print(len(test_data))
打印训练集和测试集的数量:
对训练和测试样本进行分批次
# 创建训练数据的 DataLoader 对象
# DataLoader 是 PyTorch 中用于批量加载数据的实用工具类,它可以帮助我们更高效地处理大规模数据集。
train_dataloader = DataLoader(training_data, batch_size=64) # 建议用2的指数当作一个包的数量
test_dataloader = DataLoader(test_data, batch_size=64)
判断pytorch是否支持GPU
'''判断是否支持GPU'''
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(f'Using {device} device')
定义CNN卷积神经网络模型
# 定义一个名为 CNN 的卷积神经网络类,继承自 nn.Module
class CNN(nn.Module):
def __init__(self):
# 调用父类 nn.Module 的构造函数
super(CNN, self).__init__()
# 定义第一个卷积层块
# nn.Conv2d(1, 64, 5, 1, 2):输入通道数为 1(因为是灰度图),输出通道数为 64,卷积核大小为 5x5,步长为 1,填充为 2
# nn.ReLU():使用 ReLU 激活函数增加模型的非线性
# nn.MaxPool2d(kernel_size=2):使用 2x2 的最大池化层进行下采样
self.conv1 = nn.Sequential(
nn.Conv2d(1, 64, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2),
)
# 定义第二个卷积层块
# nn.Conv2d(64, 128, 5, 1, 2):输入通道数为 64,输出通道数为 128,卷积核大小为 5x5,步长为 1,填充为 2
# nn.ReLU():使用 ReLU 激活函数增加模型的非线性
# nn.MaxPool2d(2):使用 2x2 的最大池化层进行下采样
self.conv2 = nn.Sequential(
nn.Conv2d(64, 128, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(2),
)
# 定义第三个卷积层块
# 包含多个卷积层和 ReLU 激活函数
# nn.Conv2d(128, 64, 5, 1, 2):输入通道数为 128,输出通道数为 64,卷积核大小为 5x5,步长为 1,填充为 2
# nn.Conv2d(64, 32, 5, 1, 2):输入通道数为 64,输出通道数为 32,卷积核大小为 5x5,步长为 1,填充为 2
# nn.Conv2d(32, 16, 5, 2, 4):输入通道数为 32,输出通道数为 16,卷积核大小为 5x5,步长为 2,填充为 4
# nn.ReLU():使用 ReLU 激活函数增加模型的非线性
# nn.MaxPool2d(2):使用 2x2 的最大池化层进行下采样
self.conv3 = nn.Sequential(
nn.Conv2d(128, 64, 5, 1, 2),
nn.ReLU(),
nn.Conv2d(64, 32, 5, 1, 2),
nn.ReLU(),
nn.Conv2d(32, 16, 5, 2, 4),
nn.ReLU(),
nn.MaxPool2d(2),
)
# 定义第四个卷积层块
# nn.Conv2d(16, 8, 3, 2, 5):输入通道数为 16,输出通道数为 8,卷积核大小为 3x3,步长为 2,填充为 5
# nn.ReLU():使用 ReLU 激活函数增加模型的非线性
# nn.MaxPool2d(2):使用 2x2 的最大池化层进行下采样
self.conv4 = nn.Sequential(
nn.Conv2d(16, 8, 3, 2, 5),
nn.ReLU(),
nn.MaxPool2d(2)
)
# 定义全连接层
# 输入特征数为 8 * 3 * 3,输出特征数为 10(假设用于 10 分类任务)
self.out = nn.Linear(8 * 3 * 3, 10)
def forward(self, x):
# 前向传播过程
# 将输入 x 通过第一个卷积层块
x = self.conv1(x)
# 将第一个卷积层块的输出通过第二个卷积层块
x = self.conv2(x)
# 将第二个卷积层块的输出通过第三个卷积层块
x = self.conv3(x)
# 将第三个卷积层块的输出通过第四个卷积层块
x = self.conv4(x)
# 将卷积层的输出展平为一维向量,以便输入到全连接层
# x.size(0) 表示批量大小,-1 表示自动计算其余维度的大小
x = x.view(x.size(0), -1)
# 将展平后的向量通过全连接层得到最终输出
output = self.out(x)
return output
# 创建 CNN 模型的实例,并将模型移动到指定设备(GPU 或 CPU)上
model = CNN().to(device)
定义训练函数
def train(dataloader,model,loss_fn,optimizer):
model.train() # 告诉模型,要开始训练,模型中w进行随机化操作,已经更新w,在训练过程中w会被修改
# pytorch提供两种方式来切换训练和测试的模式,分别是:model.train()和model.eval()
# 一般用法:在训练之前写model.train(),在测试时写model.eval()
batch_size_num = 1
for x,y in dataloader: # 其中batch为每一个数据的编号
x,y=x.to(device),y.to(device) # 将训练数据和标签传入gpu
pred = model.forward(x) # .forward可以被省略,父类中已经对次功能进行了设置。自动初始化w权值
loss = loss_fn(pred,y) # 通过交叉熵损失函数计算损失值loss
# Backpropagation 进来个batch的数据,计算一次梯度,更新一次网络
optimizer.zero_grad() #梯度值清零
loss.backward() # 反向传播计算每一个参数的梯度值w
optimizer.step() # 根据梯度更新网络w参数
loss_value = loss.item() # 从tensor数据中提取数据出来,tensor获取损失值
if batch_size_num % 100 == 0:
print(f'loss:{loss_value:7f} [number:{batch_size_num}]' )
batch_size_num += 1
定义测试函数
def test(dataloader,model,loss_fn):
size = len(dataloader.dataset) # 10000
num_batches = len(dataloader) # 打包的数据
model.eval() # 测试,w就不能再更新
test_loss,correct = 0,0
with torch.no_grad(): # 一个上下文管理器,关闭梯度计算。当你确定不会调用Tensor.backward()的时候。这可以减少计算内存
for x,y in dataloader:
x,y = x.to(device),y.to(device)
pred = model.forward(x)
test_loss += loss_fn(pred,y).item()#test_loss是会自动累加每一个批次的损失值
correct +=(pred.argmax(1) == y).type(torch.float).sum().item()
a = (pred.argmax(1) == y)#dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
b = (pred.argmax(1) == y).type(torch.float)
test_loss /=num_batches#能来衡量模型测试的好坏。
correct /= size#平均的正确率
print(f'Test result: \n Accuracy:{(100*correct)}%,Avg loss:{test_loss}')
创建交叉熵损失函数和优化器
loss_fn = nn.CrossEntropyLoss()#创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
#一会改成adam优化器
optimizer = torch.optim.Adam(model.parameters(),lr=0.001)#创建一个优化器,SGD为随机梯度下降算法
#params:要训练的参数,一般我们传入的都是model.parameters()
# #lr:learning_rate学习率,也就是步长。
通过多轮训练降低损失值得到最终结果
epochs = 20
for t in range(epochs):
print(f'epoch{t+1}\n--------------------')
train(train_dataloader,model, loss_fn, optimizer)
print('Done!')
test(test_dataloader,model, loss_fn)
结果:
可以看到在训练20轮过后的准确率为99.35%,损失值为0.0363。
与经典神经网络BP神经网络对数据的训练:https://blog.csdn.net/lou0720/article/details/145557286?spm=1001.2014.3001.5501
的结果:
相比明显CNN的准确性更高,模型对数据的训练更加优秀。