"卷积神经网络" 资料整理归纳
卷积神经网络(Convolutional Neural Networks, CNN)是一种包含 卷积 计算且具有深度结构的 前馈神经网络(Feedforward Neural Networks)
基础理解:
- 卷积:滤波,特征提取
- 池化:即采样,对数据进行降维压缩,常见的有最大池化(每组数中用最大值作为代表)、平均池化(每组数中用平均值作为代表)
- 全连接层:将数据展平为一维向量,再进行全连接运算(即加权求和),然后映射到具体的类别或输出。
(全连接层可以多层叠加,通常最后一个全连接层会接一个 softmax 函数,输出每个类别的概率。) - 激活函数:防止梯度消失或爆炸,增强神经网络表达能力。常见的有Sigmoid函数、ReLU函数等。
- 神经网络:万能函数拟合器,也可以理解为一个输入x到输出y的映射函数,即f(x)=y。
(简单理解,y=wx+b) - 前向传播:根据初始w和b(一般为随机数),从输入层x开始,逐层计算每个神经元的输出,直到得到最终的预测结果f(x)
- 损失函数:度量模型的预测值f(x)与真实值Y的差异程度
- 反向传播:从最后一层逐步调整权重参数w
- 梯度下降:一种优化算法,可以用在神经网络的训练中,以更新网络的权重参数,使得损失函数的值最小化。梯度下降法需要计算损失函数对每个参数的梯度,即偏导数,表示参数变化时损失函数的变化率。
在机器学习中,我们知道输入的feature(或称为x)需要通过模型(model)预测出y,此过程称为前向传播(forward pass),而要将预测与真实值的差值减小需要更新模型中的参数,这个过程称为反向传播(backward pass)。其中损失函数(lossfunction)就基于这两种传播之间,在机器学习中,想让预测值无限接近于真实值,所以需要将差值降到最低(在这个过程中就需要引入损失函数)。而在此过程中损失函数的选择是十分关键的,在具体的项目中,有些损失函数计算的差值梯度下降的快,而有些下降的慢。
反向传播是用来快速求解目标函数关于各个参数的梯度;而梯度下降则是根据计算得到的梯度来更新各个权重参数。
一个非常不错的过程动画:
模型调参过程(b站视频)
AlexNet
一种经典的卷积神经网络模型,在ILSVRC比赛中,AlexNet所用的数据集是ImageNet,总共识别1000个类别。
有一点需要解释一下,鉴于当时的硬件资源限制,由于AlexNet结构复杂、参数很庞大,难以在单个GPU上进行训练。因此AlexNet采用两路GTX 580 3GB GPU并行训练。也就是说把原先的卷积层平分成两部分FeatureMap分别在两块GPU上进行训练(例如卷积层55x55x96分成两个FeatureMap:55x55x48)。
AlexNet的网络结构如下:
-
输入层:接收一个RGB三通道的224×224×3大小的图像(原论文中,AlexNet的输入图像尺寸是224x224x3。但是实际图像尺寸为227x227x3);
-
卷积层C1:【卷积 --> ReLU --> 局部响应归一化(LRN) --> 最大池化】
使用96个11×11×3的卷积核,步长为4,不扩充边缘,输出(55x55x48)x2的特征图,然后使用ReLU激活函数和局部响应归一化(LRN),最后进行3×3的最大池化,步长为2,输出(27×27×48)x2的特征图;注:这里(55x55x48)x2的特征图是指55x55x96特征图被分成两部分进行运算
-
卷积层C2:【卷积 --> ReLU --> 局部响应归一化(LRN) --> 最大池化】
使用256个5×5×48的卷积核,步长为1,扩充边缘为2,输出(27×27×128)×2的特征图,然后使用ReLU激活函数和LRN,最后进行3×3的最大池化,步长为2,输出(13×13×128)×2的特征图; -
卷积层C3:【卷积 --> ReLU】
使用384个3×3×256的卷积核,步长为1,扩充边缘为1,输出(13×13×192)x2的特征图,然后使用ReLU激活函数; -
卷积层C4:【卷积 --> ReLU】
使用384个3×3×192的卷积核,步长为1,扩充边缘为1,输出(13×13×192)x2的特征图,然后使用ReLU激活函数; -
卷积层C5:【卷积 --> ReLU --> 最大池化】
使用256个3×3×192的卷积核,步长为1,扩充边缘为1,输出(13×13×128)x2的特征图,然后使用ReLU激活函数,最后进行3×3的最大池化,步长为2,输出6×6×128x2的特征图; -
全连接层F6: 【全连接 --> ReLU --> Dropout】
将6×6×256的特征图展平为一维向量,然后使用4096个神经元进行全连接,输出4096维的向量,然后使用ReLU激活函数和Dropout;注:“将6×6×256的特征图展平为一维向量输入为6×6×256”是指:使用4096个6×6×256的卷积核进行卷积,由于卷积核尺寸与输入的尺寸完全相同,即卷积核中的每个系数只与输入尺寸的一个像素值相乘一一对应,根据公式:(input_size + 2 * padding - kernel_size) / stride + 1=(6+2*0-6)/1+1=1,得到输出是1x1x4096。
-
全连接层F7:【全连接 --> ReLU --> Dropout】
使用4096个神经元进行全连接,输出4096维的向量,然后使用ReLU激活函数和Dropout; -
全连接层F8:【全连接 --> Softmax】
使用1000个神经元进行全连接,输出1000维的向量,然后使用Softmax函数,得到每个类别的概率值。
代码实现
这里的代码是针对CIFAR-10数据集进行训练,故而部分参数有一些修改。
其次,由于局部响应归一化(LRN)饱受争议,通常认为其对模型泛化能力提升很小,故而此处没有使用。
-
定义网络模型结构
import torch import torch.nn as nn class AlexNet(nn.Module): def __init__(self, config): super(AlexNet, self).__init__() self._config = config # 定义卷积层和池化层 self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(64, 192, kernel_size=5, stride=1, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(192, 384, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), ) # 自适应层,将上一层的数据转换成6x6大小 self.avgpool = nn.AdaptiveAvgPool2d((6, 6)) # 全连接层 self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, 1024), nn.ReLU(inplace=True), nn.Linear(1024, self._config['num_classes']), ) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x
-
模型保存函数(保存训练好的模型)
def saveModel(self): torch.save(self.state_dict(), self._config['model_name'])
-
模型加载函数(加载保存后的模型)
def loadModel(self, map_location): state_dict = torch.load(self._config['model_name'], map_location=map_location) self.load_state_dict(state_dict, strict=False)
-
数据预处理
import torchvision from torch.utils.data import DataLoader import torchvision.transforms as transforms # 定义构造数据加载器的函数 def Construct_DataLoader(dataset, batchsize): return DataLoader(dataset=dataset, batch_size=batchsize, shuffle=True) # 图像预处理 transform = transforms.Compose([ transforms.Resize(96), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) # 加载CIFAR-10数据集函数 def LoadCIFAR10(download=False): # Load CIFAR-10 dataset train_dataset = torchvision.datasets.CIFAR10(root='../CIFAR10', train=True, transform=transform, download=download) test_dataset = torchvision.datasets.CIFAR10(root='../CIFAR10', train=False, transform=transform) return train_dataset, test_datase
-
模型训练函数(这里将训练函数进行了封装)
from torch.autograd import Variable from torch.utils.data import DataLoader import torch.nn as nn class Trainer(object): # 初始化模型、配置参数、优化器和损失函数 def __init__(self, model, config): self._model = model self._config = config self._optimizer = torch.optim.Adam(self._model.parameters(),\ lr=config['lr'], weight_decay=config['l2_regularization']) self.loss_func = nn.CrossEntropyLoss() # 对单个小批量数据进行训练,包括前向传播、计算损失、反向传播和更新模型参数 def _train_single_batch(self, images, labels): y_predict = self._model(images) loss = self.loss_func(y_predict, labels) # 先将梯度清零,如果不清零,那么这个梯度就和上一个mini-batch有关 self._optimizer.zero_grad() # 反向传播计算梯度 loss.backward() # 梯度下降等优化器 更新参数 self._optimizer.step() # 将loss的值提取成python的float类型 loss = loss.item() # 计算训练精确度 # 这里的y_predict是一个多个分类输出,将dim指定为1,即返回每一个分类输出最大的值以及下标 _, predicted = torch.max(y_predict.data, dim=1) return loss, predicted def _train_an_epoch(self, train_loader, epoch_id): """ 训练一个Epoch,即将训练集中的所有样本全部都过一遍 """ # 设置模型为训练模式,启用dropout以及batch normalization self._model.train() total = 0 correct = 0 # 从DataLoader中获取小批量的id以及数据 for batch_id, (images, labels) in enumerate(train_loader): images = Variable(images) labels = Variable(labels) if self._config['use_cuda'] is True: images, labels = images.cuda(), labels.cuda() loss, predicted = self._train_single_batch(images, labels) # 计算训练精确度 total += labels.size(0) correct += (predicted == labels.data).sum() # print('[Training Epoch: {}] Batch: {}, Loss: {}'.format(epoch_id, batch_id, loss)) print('Training Epoch: {}, accuracy rate: {}%%'.format(epoch_id, correct / total * 100.0)) def train(self, train_dataset): # 是否使用GPU加速 self.use_cuda() for epoch in range(self._config['num_epoch']): print('-' * 20 + ' Epoch {} starts '.format(epoch) + '-' * 20) # 构造DataLoader data_loader = DataLoader(dataset=train_dataset, batch_size=self._config['batch_size'], shuffle=True) # 训练一个轮次 self._train_an_epoch(data_loader, epoch_id=epoch) # 用于将模型和数据迁移到GPU上进行计算,如果CUDA不可用则会抛出异常 def use_cuda(self): if self._config['use_cuda'] is True: assert torch.cuda.is_available(), 'CUDA is not available' torch.cuda.set_device(self._config['device_id']) self._model.cuda() # 保存训练好的模型 def save(self): self._model.saveModel()
-
加载之前定义的函数,实现模型的训练与测试
from torch.autograd import Variable # 定义参数配置信息 alexnet_config = \ { 'num_epoch': 20, # 训练轮次数 'batch_size': 500, # 每个小批量训练的样本数量 'lr': 1e-3, # 学习率 'l2_regularization':1e-4, # L2正则化系数 'num_classes': 10, # 分类的类别数目 'device_id': 0, # 使用的GPU设备的ID号 'use_cuda': True, # 是否使用CUDA加速 'model_name': '../AlexNet.model' # 保存模型的文件名 } if __name__ == "__main__": # AlexNet 模型 train_dataset, test_dataset = LoadCIFAR10(True) # 定义 AlexNet model alexNet = AlexNet(alexnet_config) # 模型训练阶段 # # 实例化模型训练器 trainer = Trainer(model=alexNet, config=alexnet_config) # # 训练 trainer.train(train_dataset) # # 保存模型 trainer.save() # 模型测试阶段 alexNet.eval() alexNet.loadModel(map_location=torch.device('cpu')) if alexnet_config['use_cuda']: alexNet = alexNet.cuda() correct = 0 total = 0 # 对测试集中的每个样本进行预测,并计算出预测的精度 for images, labels in Construct_DataLoader(test_dataset, alexnet_config['batch_size']): images = Variable(images) labels = Variable(labels) if alexnet_config['use_cuda']: images = images.cuda() labels = labels.cuda() y_pred = alexNet(images) _, predicted = torch.max(y_pred.data, 1) total += labels.size(0) temp = (predicted == labels.data).sum() correct += temp print('Accuracy of the model on the test images: %.2f%%' % (100.0 * correct / total))
参考:
- CSDN博主「小林学编程」文章:损失函数(lossfunction)的全面介绍
- Github 源码:ClassicNetworks
- 知乎用户「红色石头」文章:手撕 CNN 经典网络之 AlexNet(理论篇)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现