当前位置: 首页 > article >正文

神经网络-AlexNet

AlexNet是在2012年的ImageNet竞赛后,整理发表的文章,也是对CNN网络的衍生。

网络结构

AlexNet网络结构如下图所示,网络分为了上下两部分,对应两个不同的GPU训练,可以更好的利用GPU算力。只有在特殊的网络层后,两个网络层才进行交互,上下网络之间网络结构差异不是很大。网络总共有8层,分别是5层卷积、3层全连接。

论文中虽然显示图线是224*224*3,但按照后面的数据推算,应该输入的227*227*3的图像,可能是当时写错了?下面以227为例进行说明。

网络亮点

  • 数据增强(data augmentation)

为了防止在有限的数据集与较深的网络结构下,网络仍有一个较好的结果,避免过拟合。论文中采用了两种数据增强的方式,

第一种:对图片镜像反射并裁剪。将原本256*256的图像镜像反射,这样训练集翻倍;针对原始图像和镜像图像,在左上、右上、左下、右下、中间分别做了5次裁剪,裁剪大小为224*224;这样一张图片经过了1变2,2变10的增加,完成的数据量级的增加。

第二种方式:对图像中RGB数据做PCA处理,对主成分做标准差为0.1的高斯扰动,增加数据噪声。通过PCA的色彩增强方法,使得图片的明亮程度会发生变化,但并没有改变图片的结构。具体过程如下:

  1. 一张图片为224*224*3,我们对其做一个变换,将其变成224*224行,3列的大矩阵。

  2. 对上面的矩阵进行主成分分析,获取排名top3的特征向量p和对应的特征值λ。

  3. 创建一个随机变量α,使其满足均值为0,方差1的高斯分布。

  4. 通过下面公式,对图片中的像素点进行重新赋值。

该方法在每训练一次之后,就会重新进行一次计算,产生的图片也像在强光或弱光下的照片,最终使得在top1错误率减少了1%以上。

  • 激活函数

在神经网络中,常用的激活函数有tanh()和sigmoid()函数,这些饱和的非线性函数相对于非饱和的非线性函数max()等函数要慢很多,因此最终使用了ReLU作为激活函数,同时一定程度上解决了sigmoid函数带来的梯度弥散问题。

在使用ReLU的四层卷积网络,在CIFAR-10训练集中error rate降到25%的时候,比tanh快了六倍。

什么是饱和性?

简单来说,饱和性(saturating)是指,对于输入,函数的输出可以将其限定在一个范围内,即其输出有最大值和最小值,例如我们常见的sigmoid()->[0,1],输出在0~1之间。

非饱和性,即输入的数值,在通过函数之后没有被限定在一个范围,即我们常见的ReLU()函数。从论文中的实验结果看,也验证了非饱和性的速度更快一些。

  • Local Response Normalization(局部响应归一化)

局部归一化,简称LRN,可以带来泛化性能的提升,公式如下:

如下图是一组feature map,其中黄色像素点的位置为ax,y,计算相邻feature map相同位置的像素值,取平方,然后乘以α,加上k,做β次运算。论文中采用了k=2,n=5,α=0.0001,β=0.75。

具体来说,我们将k称作偏移量,α称作缩放比例系数,β称作超参数(影响归一化)。论文中在使用局部归一化后,top1的错误率降低了1.4%,top5的错误率降低了1.2%。

  • overlapping pooling(覆盖化的池化)

传统的CNN网络中池化核之间并不会重叠,本网络中使用池化层大小为3*3,stride为2,这样池化核之间就会有重叠,在top1和top5的实验中,错误率分别降低了0.4%和0.3%,在训练过程中也更加不易过拟合。

传统CNN中池化层一般会采用平均池化,AlexNet使用了最大池化,避免了平均池化带来的模糊化效果,覆盖化的池化,一定程度上提升了特征的丰富性。

  • Dropout

在全连接层中,使用了Dropout关闭一些网络中的神经节点,起到防止过拟合的作用。这些被关闭的神经元不再参与前向传播和反向传播。

因此在每次数据输入时,网络的结构(神经元)都会有所不同,但学习的权重一直都是在共享的状态。这种方式,是的神经元不是依赖单个或某些神经元完成训练,而是能有更大范围的鲁棒性,因此很大程度上避免了过拟合。

  • GPU计算

网络使用了两个GPU进行训练,减少了原有网络的训练时间,网络一共8层,其中绿色部分是上下两个GPU发生信息交互的位置。

  • 权重动态调整

对于权重的动态调整,采用了如下公式,使得模型的训练误差得到了降低。

应用

  • 定义模型结构 

import torch.nn as nn
import torch

class AlexNet(nn.Module):
    def __init__(self, num_classes=1000, init_weights=False):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            # 卷积层1
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  
            nn.ReLU(inplace=True),  # inplace=True 增加计算量,降低了内存消耗
            nn.MaxPool2d(kernel_size=3, stride=2),  
            
            # 卷积层2
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),  
            
            # 卷积层3
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          
            nn.ReLU(inplace=True),
            
            # 卷积层4
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          
            nn.ReLU(inplace=True),
            
            # 卷积层5
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2)
        )
        
        self.classifier = nn.Sequential(
            # 全连接层6
            nn.Dropout(p=0.5),
            nn.Linear(128 * 6 * 6, 2048),
            nn.ReLU(inplace=True),
            
            # 全连接层7
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            
            # 全连接层8
            nn.Linear(2048, num_classes),
        )
        
        if init_weights:
            self._initialize_weights()
            
    def forward(self, x):
        x = self.features(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        # 遍历所有网络层结构
        for m in self.modules():
            # 如果属于卷积层,使用如下方法初始化
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            # 如果是全连接层,使用如下方法初始化
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)
        


http://www.kler.cn/a/458707.html

相关文章:

  • 机器学习算法基础知识1:决策树
  • c++表达范围勿用数学符号
  • JVM对象内存分配
  • 【密码学】基于 fastcoll 实现 MD5 碰撞快速生成(MD5碰撞)
  • LinuxC高级day4
  • MAC环境安装(卸载)软件
  • Android笔试面试题AI答之非技术问题(1)
  • Asp.NET Core - 尝试一下在NET9中使用Yarp作为Api Proxy
  • C语言基础
  • Spring Boot实战:构建一个简单的RESTful API
  • vue2 升级为 vite 打包
  • Unity-Editor扩展显示文件夹大小修复版 FileCapacity.cs
  • HarmonyOS Next“说书人”项目 单机版 实践案例
  • AI与云计算:天作之合
  • 如何高效学习PHP框架源码
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验四----城市用地适宜性评价(超超超详细!!!)
  • unity使用代码在动画片段中添加event
  • 汽车网络安全基线安全研究报告
  • vue.js普通组件的注册-局部注册
  • C11.【C++ Cont】遍历字符串的两种方式和strstr函数
  • 华为OD E卷(100分)37-考勤信息
  • 基于 Paimon x Spark 采集分析半结构化 JSON 的优化实践
  • Spring Retry + Redis Watch实现高并发乐观锁
  • UI页面布局分析(5)- 评分弹窗的实现
  • 【PCIe 总线及设备入门学习专栏 5.1 -- PCIe 引脚 PRSNT 与热插拔】
  • Edge Scdn是用来干什么的?