目录
一、卷积神经网络介绍
1、简介
经典CNN架构
2、与传统神经网络区别
3、卷积神经网络的结构
(1) 卷积层(Convolutional Layer)
(2) 激活函数(Activation Function)
(3) 池化层(Pooling Layer)
(4) 全连接层(Fully Connected Layer)
二、卷积神经网络的原理
1、卷积层(Convolutional Layer)
2、激活函数层(Activation Function Layer)
3、池化层(Pooling Layer)
4、全连接层(Fully Connected Layer)
5、反向传播(Backpropagation)
三、卷积神经网络的搭建过程
1、卷积层的构建方法
2、激活层的构建方法
3、池化层的构建方法
4、全连接层的构建方法
四、卷积神经网络代码实践
1、导入相应的库
2、数据集加载
3、选择训练工具(这里是GPU)
4、构建卷积神经网络
5、将模型传入GPU、打印检查是否有误
6、构建优化器和损失函数
7、定义训练和测试函数
8、调用训练和测试函数
9、完整代码展示
10、结果展示
一、卷积神经网络介绍
1、简介
卷积神经网络(CNN)是一种专门用于处理网格状数据(如图像、视频、音频)的深度学习模型。其核心思想是通过卷积操作自动提取数据的空间或时序特征,广泛应用于计算机视觉、自然语言处理等领域。
经典CNN架构
模型 | 提出时间 | 主要贡献 | 应用场景 |
---|---|---|---|
LeNet-5 | 1998 | 首个成功的手写数字识别CNN | MNIST 分类 |
AlexNet | 2012 | 引入ReLU、Dropout,赢得ImageNet竞赛 | 图像分类 |
VGGNet | 2014 | 深层的3x3卷积堆叠,结构简洁 | 通用视觉任务 |
ResNet | 2015 | 残差连接解决深层网络梯度消失问题 | 图像分类/检测 |
YOLO | 2016 | 单阶段目标检测,实时性高 | 目标检测 |
2、与传统神经网络区别
-
传统神经网络:传统神经网络对输入数据的处理是基于全连接的方式,神经元与输入数据的每个元素都有连接。这意味着输入数据中目标的位置发生变化时,网络需要重新学习该目标的特征。因为对于传统神经网络而言,不同位置的相同特征在输入层对应的神经元是不同的,所以不具备画面不变性。
-
卷积神经网络:卷积神经网络通过卷积层中的卷积核在输入图像上滑动进行卷积操作,提取特征。由于卷积核的参数共享机制,无论目标在图像中的哪个位置,只要其特征模式与卷积核匹配,就能够被检测到。因此,卷积神经网络天然具有画面不变性。
3、卷积神经网络的结构
(1) 卷积层(Convolutional Layer)
-
功能:通过滑动窗口(卷积核)在输入数据上提取局部特征。
-
关键参数:
-
卷积核(Filter):权重矩阵,用于捕捉特征(如边缘、纹理)。
-
步长(Stride):卷积核每次滑动的距离。
-
填充(Padding):在输入边缘补零,控制输出尺寸。
-
-
输出:生成特征图(Feature Map)。
(2) 激活函数(Activation Function)
-
作用:引入非线性,增强模型表达能力。
-
常用函数:ReLU(修正线性单元)、Sigmoid、Leaky ReLU。
(3) 池化层(Pooling Layer)
-
功能:降低特征图的空间维度,减少计算量并增强平移不变性。一种降采样,减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合。
-
类型:
-
最大池化(Max Pooling):取窗口内最大值。
-
平均池化(Average Pooling):取窗口内平均值。
-
(4) 全连接层(Fully Connected Layer)
-
功能:将提取的特征映射到最终输出(如分类结果)。
-
位置:通常位于网络末端。
二、卷积神经网络的原理
1、卷积层(Convolutional Layer)
-
卷积操作:这是 CNN 的核心操作。卷积层通过卷积核(也叫滤波器)在输入数据(如图像)上滑动,对每个位置进行局部的加权求和运算。例如,对于一个二维图像,卷积核是一个小的二维矩阵,在图像上按照一定的步长(stride)滑动,每次滑动时,卷积核与图像上对应的局部区域进行元素相乘并求和,得到一个输出值。
-
特征提取:不同的卷积核可以提取不同的特征。例如,一些卷积核可能提取图像中的边缘信息,另一些可能提取纹理信息。通过多个不同的卷积核,可以从输入数据中提取丰富多样的特征。
-
参数共享:卷积核在滑动过程中,其参数是固定不变的。这意味着无论卷积核在图像的哪个位置,它对局部区域的处理方式都是相同的。这种参数共享机制大大减少了网络的参数数量,降低了计算复杂度,同时也使得网络能够更有效地学习到图像的平移不变性特征。
2、激活函数层(Activation Function Layer)
-
常见的激活函数有 ReLU(Rectified Linear Unit,修正线性单元)、Sigmoid、Tanh 等。激活函数的作用是为神经网络引入非线性因素。因为如果没有激活函数,神经网络只是线性的组合,其表达能力有限,只能处理线性可分的问题。而引入激活函数后,神经网络可以学习和表示更复杂的非线性关系。例如,ReLU 函数在输入大于 0 时直接输出输入值,在输入小于 0 时输出 0,它能够有效地解决梯度消失问题,并且计算速度快,在 CNN 中被广泛使用。
3、池化层(Pooling Layer)
-
下采样操作:池化层主要用于对卷积层输出的特征图进行下采样,降低特征图的尺寸。常见的池化方法有最大池化(Max Pooling)和平均池化(Average Pooling)。最大池化是在每个池化窗口中取最大值作为输出,平均池化则是计算池化窗口内的平均值作为输出。
-
作用:通过池化操作,一方面可以减少网络的计算量和参数数量,提高计算效率;另一方面可以在一定程度上增强网络对输入数据的平移、旋转和尺度变化的鲁棒性,使网络学习到更具代表性的特征。
4、全连接层(Fully Connected Layer)
-
经过多层卷积和池化操作后,将提取到的特征图展平成一维向量,然后输入到全连接层。全连接层中每个神经元与上一层的所有神经元都有连接,其作用是对前面提取到的特征进行综合和分类。例如,在图像分类任务中,全连接层可以根据前面提取的特征判断输入图像属于哪个类别。
-
通常,全连接层的最后一层会使用合适的激活函数(如 Softmax 用于多分类问题)来输出分类结果,Softmax 函数可以将输出值转换为概率分布,每个值表示输入数据属于相应类别的概率。
5、反向传播(Backpropagation)
-
在训练 CNN 时,使用反向传播算法来更新网络的参数(卷积核的权重和偏置等)。首先,根据网络的预测结果和真实标签计算损失函数(如交叉熵损失函数),衡量预测结果与真实值之间的差异。然后,通过反向传播算法,从输出层开始,将损失函数关于每个参数的梯度逐层反向传播到网络的输入层。最后,根据计算得到的梯度,使用优化算法(如随机梯度下降、Adam 等)更新网络的参数,使得损失函数逐渐减小,从而提高网络的性能。
三、卷积神经网络的搭建过程
在搭建网络只之前要先设计和模型的形状结构,多少层网络、使用什么激活函数、在哪里添加池化层等。
1、卷积层的构建方法
nn.Conv2d(in_channels=1, # 输入图片的通道数
out_channels=32, # 卷积核的个数
kernel_size=5, # 卷积核的大小
stride=1, # 移动步长
padding=2) # 边缘填充层数
2、激活层的构建方法
nn.ReLU()
3、池化层的构建方法
nn.MaxPool2d(2)
4、全连接层的构建方法
nn.Linear(64*7*7,out_features=10)
四、卷积神经网络代码实践
举例描述:以手写数字数据集作为例子,用卷积神经网络进行训练。
1、导入相应的库
import torch
print(torch.__version__)
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
2、数据集加载
# 训练集
training_data = datasets.MNIST(root='data', # 路径train=True, # True为训练集False为测试集download=True, # 下载后就不重复下载transform=ToTensor() # 转换为张量
)# 测试集
test_data = datasets.MNIST(root='data',train=False,download=True,transform=ToTensor()
)
print(len(training_data))train_dataloader = DataLoader(training_data, batch_size=32)
test_dataloader = DataLoader(test_data, batch_size=32)
for X, y in test_dataloader:print(f'Shape of X [N,C,H,W]:{X.shape}')print(f'Shape of y:{y.shape},{y.dtype}')break
3、选择训练工具(这里是GPU)
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")
4、构建卷积神经网络
class CNN(nn.Module):def __init__(self):super(CNN,self).__init__()self.conv1 = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=32,kernel_size=5,stride=1,padding=2),nn.ReLU(),nn.Conv2d(32,16,5,1,2),nn.ReLU(),nn.MaxPool2d(2) # 14*14)self.conv2 = nn.Sequential(nn.Conv2d(16,32,5,1,2),nn.ReLU())self.conv3 = nn.Sequential(nn.Conv2d(32,16,5,1,2),nn.ReLU(),nn.Conv2d(16,28,5,1,2),nn.ReLU(),)self.conv4 = nn.Sequential(nn.Conv2d(28,64,5,1,2),nn.ReLU(),nn.MaxPool2d(2) # 7*7)self.out = nn.Linear(64*7*7,out_features=10)def forward(self,x):x = self.conv1(x)x = self.conv2(x)x = self.conv3(x)x = self.conv4(x)x = x.view(x.size(0),-1)output = self.out(x)return output
5、将模型传入GPU、打印检查是否有误
model = CNN().to(device)
print(model)
6、构建优化器和损失函数
loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 优化器,lr为步长学习率
7、定义训练和测试函数
# 训练
def train(dataloader, model, loss_fn, optimizer):model.train() # 开始训练,w可以改变,与测试中的model.eval()相对应batch_size_num = 1for X, y in dataloader:X, y = X.to(device), y.to(device) # 将数据传入Gpupred = model.forward(X) # 前向传输,model的数据来自模型的outloss = loss_fn(pred, y) # 通过交叉熵损失函数计算loss,pred为预测值,y为真实值optimizer.zero_grad() # 优化,梯度值清零loss.backward() # 反向传播计算每个参数的梯度值Woptimizer.step() # 根据梯度更新Wloss_value = loss.item() # 从tensor数据中提取数据出来,转换成对应的整数或者浮点数if batch_size_num % 200 == 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)num_batches = len(dataloader)model.eval() # 测试,w不可更新test_loss, correct = 0, 0 # 初始化损失值以及正确率with torch.no_grad(): # 一个上下文管理器,关闭梯度计算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() # pred.argmax(1)返回每一行中最大值对应的索引号a = (pred.argmax(1) == y) # 比较预测值与正确标签是否相同,返回True或者Falseb = (pred.argmax(1) == y).type(torch.float) # 将True-->1,False-->0,便于统计正确率test_loss /= num_batchescorrect /= sizeprint(f"Test result: \n Accuracy: {(100 * correct)}%,Avg loss:{test_loss}")
8、调用训练和测试函数
这里训练10轮测试一次
epoch = 10
for i in range(epoch):print('第{}轮训练'.format(i))train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
9、完整代码展示
import torch
print(torch.__version__)
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor# 训练集
training_data = datasets.MNIST(root='data', # 路径train=True, # True为训练集False为测试集download=True, # 下载后就不重复下载transform=ToTensor() # 转换为张量
)# 测试集
test_data = datasets.MNIST(root='data',train=False,download=True,transform=ToTensor()
)
print(len(training_data))train_dataloader = DataLoader(training_data, batch_size=32)
test_dataloader = DataLoader(test_data, batch_size=32)
for X, y in test_dataloader:print(f'Shape of X [N,C,H,W]:{X.shape}')print(f'Shape of y:{y.shape},{y.dtype}')break
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):super(CNN,self).__init__()self.conv1 = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=32,kernel_size=5,stride=1,padding=2),nn.ReLU(),nn.Conv2d(32,16,5,1,2),nn.ReLU(),nn.MaxPool2d(2) # 14*14)self.conv2 = nn.Sequential(nn.Conv2d(16,32,5,1,2),nn.ReLU())self.conv3 = nn.Sequential(nn.Conv2d(32,16,5,1,2),nn.ReLU(),nn.Conv2d(16,28,5,1,2),nn.ReLU(),)self.conv4 = nn.Sequential(nn.Conv2d(28,64,5,1,2),nn.ReLU(),nn.MaxPool2d(2) # 7*7)self.out = nn.Linear(64*7*7,out_features=10)def forward(self,x):x = self.conv1(x)x = self.conv2(x)x = self.conv3(x)x = self.conv4(x)x = x.view(x.size(0),-1)output = self.out(x)return outputmodel = CNN().to(device)
print(model)loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 优化器,lr为步长学习率# 训练
def train(dataloader, model, loss_fn, optimizer):model.train() # 开始训练,w可以改变,与测试中的model.eval()相对应batch_size_num = 1for X, y in dataloader:X, y = X.to(device), y.to(device) # 将数据传入Gpupred = model.forward(X) # 前向传输,model的数据来自模型的outloss = loss_fn(pred, y) # 通过交叉熵损失函数计算loss,pred为预测值,y为真实值optimizer.zero_grad() # 优化,梯度值清零loss.backward() # 反向传播计算每个参数的梯度值Woptimizer.step() # 根据梯度更新Wloss_value = loss.item() # 从tensor数据中提取数据出来,转换成对应的整数或者浮点数if batch_size_num % 200 == 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)num_batches = len(dataloader)model.eval() # 测试,w不可更新test_loss, correct = 0, 0 # 初始化损失值以及正确率with torch.no_grad(): # 一个上下文管理器,关闭梯度计算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() # pred.argmax(1)返回每一行中最大值对应的索引号a = (pred.argmax(1) == y) # 比较预测值与正确标签是否相同,返回True或者Falseb = (pred.argmax(1) == y).type(torch.float) # 将True-->1,False-->0,便于统计正确率test_loss /= num_batchescorrect /= sizeprint(f"Test result: \n Accuracy: {(100 * correct)}%,Avg loss:{test_loss}")epoch = 10
for i in range(epoch):print('第{}轮训练'.format(i))train(train_dataloader, model, loss_fn, optimizer)
test(test_dataloader, model, loss_fn)
10、结果展示
正确率达到了99.21%,还是很可观的。