随笔 - 2  文章 - 0  评论 - 0  阅读 - 183

"卷积神经网络" 资料整理归纳

卷积神经网络(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)饱受争议,通常认为其对模型泛化能力提升很小,故而此处没有使用。

  1. 定义网络模型结构

     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
    
  2. 模型保存函数(保存训练好的模型)

     def saveModel(self):
     	torch.save(self.state_dict(), self._config['model_name'])
    
  3. 模型加载函数(加载保存后的模型)

     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)
    
  4. 数据预处理

     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
    
  5. 模型训练函数(这里将训练函数进行了封装)

     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()
    
  6. 加载之前定义的函数,实现模型的训练与测试

     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))
    

参考:

posted on   芥子须弥K  阅读(88)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示