引言
卷积神经网络(Convolutional Neural Networks, CNN)是深度学习领域最具影响力的架构之一,尤其在计算机视觉任务中表现出色。自2012年AlexNet在ImageNet竞赛中一战成名以来,CNN不断演进,推动着图像识别、医疗影像分析、自动驾驶等领域的快速发展。本文将系统介绍CNN的核心原理、关键组件和实际应用,并通过PyTorch代码示例展示如何构建一个CNN模型。
一、CNN的基本原理
1.1 传统神经网络的问题
在处理图像数据时,传统全连接神经网络(FCN)存在明显缺陷:
- 参数爆炸:一张28×28的灰度图像展开后就有784个输入节点,若第一隐藏层有1000个神经元, 仅这一层就需要784,000个参数!
- 忽略局部相关性:图像中相邻像素间存在强相关性,但全连接网络无法有效利用这种空间信息
- 缺乏平移不变性:物体在图像中的位置变化会导致网络需要重新学习
1.2 CNN的三大核心思想
CNN通过以下设计巧妙解决了上述问题:
1. 局部感受野(Local Receptive Fields)
每个神经元只连接输入区域的局部区域,而非全部输入
2. 权值共享(Shared Weights)
同一特征图使用相同的卷积核,大幅减少参数数量
3. 空间下采样(Spatial Subsampling)
通过池化层逐步降低空间分辨率,增加高层特征的感受野
> 参数数量对比:
> 对于28×28图像,全连接层(隐藏层1000神经元)需要784,000参数
> 而5×5卷积核(32个通道)仅需5×5×32=800参数
二、CNN的核心组件详解
2.1 卷积层(Convolutional Layer)
import torch.nn as nn# 定义一个卷积层
conv_layer = nn.Conv2d(in_channels=3, # 输入通道数(RGB图像为3)out_channels=16, # 输出通道数/卷积核数量kernel_size=3, # 卷积核尺寸stride=1, # 步长padding=1 # 边缘填充
)
2.2 池化层(Pooling Layer)
# 最大池化示例
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)# 平均池化示例
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)
作用:
- 降低空间维度,减少计算量
- 增强平移不变性
- 扩大感受野
2.3 激活函数
# ReLU激活函数
activation = nn.ReLU()# LeakyReLU示例
leaky_relu = nn.LeakyReLU(negative_slope=0.01)
三、经典CNN架构演进
class CNN(nn.Module):def __init__(self): #python基础关于类,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(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)x=self.conv2(x)x=self.conv3(x)x=x.view(x.size(0),-1)output=self.out(x)return output
四、PyTorch实现CNN实战
4.1手写数字识别:
import torchprint(torch.__version__)'''
MNIST包含70,000张手写数字图像:60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
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(root='data', #表示下载的手写数字 到哪个路径。60000train=True, #读取下载后的数据 中的 训练集download=True, #如果你之前已经下载过了,就不用再下载transform=ToTensor(), #张量,图片是不能直接传入神经网络模型
) #对于pytorch库能够识别的数据一般是tensor张量。'''下载测试数据集(包含训练图片+标签)'''
test_data=datasets.MNIST(root='data', #表示下载的手写数字 到哪个路径。60000train=False, #读取下载后的数据 中的 训练集download=True, #如果你之前已经下载过了,就不用再下载transform=ToTensor(), #Tensor是在深度学习中提出并广泛应用的数据类型
) #NumPy数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度
print(len(training_data))'''展示手写字图片,把训练数据集中的前59000张图片展示一下'''
from matplotlib import pyplot as plt
figure = plt.figure()
for i in range(9):img,label=training_data[i+59000]#提取第59000张图片figure.add_subplot(3,3,i+1)#图像窗口中创建多个小窗口,小窗口用于显示图片plt.title(label)plt.axis("off") # plt.show(I)#是示矢量,plt.imshow(img.squeeze(),cmap="gray") #plt.imshow()将Numpy数组data中的数据显示为图像,并在图形窗口中显贡a = img.squeeze()# img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。#cmap="gray
plt.show()# 创建数据DataLoader(数据加载器)
# batch_size:将数据集分成多份,每一份为batch_size个数据
# 优点:可以减少内存的使用,提高训练速度。train_dataloader=DataLoader(training_data,batch_size=64)
test_dataloader=DataLoader(test_data,batch_size=64)'''断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device") #字符串的格式化'''定义神经网络 类的继承'''
class CNN(nn.Module):def __init__(self): #python基础关于类,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(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)x=self.conv2(x)x=self.conv3(x)x=x.view(x.size(0),-1)output=self.out(x)return outputmodel = CNN().to(device)
print(model)def train(dataloader,model,loss_fn,optimizer):model.train() #告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train()和 model.eval()。
#一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval()batch_size_num=1for X,y in dataloader: #其中batch为每一个数据的编号X,y=X.to(device),y.to(device) #把训练数据集和标签传入cpu或GPUpred=model.forward(X) #.forward可以被省略,父类中已经对次功能进行了设置。自动初始化loss=loss_fn(pred,y) #通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad() #梯度值清零loss.backward() #反向传播计算得到每个参数的梯度值woptimizer.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+=1def test(dataloader,model,loss_fn):size=len(dataloader.dataset)num_batches=len(dataloader) #打包的数量model.eval() #测试,w就不能再更新。test_loss,correct=0,0with 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_batchescorrect /=sizeprint(f'Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}')loss_fn=nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
optimizer=torch.optim.Adam(model.parameters(),lr=0.01) #创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()#
# lr:learning_rate学习率,也就是步长#loss表示模型训练后的输出结果与,样本标签的差距。如果差距越小,就表示模型训练越好,越逼近干真实的模型。# train(train_dataloader,model,loss_fn,optimizer)
# test(test_dataloader,model,loss_fn)epoch=9
for i in range(epoch):print(i+1)train(train_dataloader, model, loss_fn, optimizer)test(test_dataloader,model,loss_fn)
运行结果:
结语
CNN作为深度学习的基石架构,不仅在计算机视觉领域大放异彩,其核心思想也被广泛应用于语音识别、自然语言处理等领域。通过本文的系统介绍,希望读者能够建立起对CNN的全面认识,并能够动手实现自己的CNN模型。记住,理解原理只是第一步,持续的实践和创新才是掌握CNN的关键!