ImageNet Classification with Deep Convolutional Neural Networks【论文笔记】

1 简介

      本论文出自2012年,在当时带标签数据集是很少的(e.g. NoRB, Caltech-101/256, CIFAR10/100),这些数据集仅有几万张图片组成,在数据增广基础上,利用这些数据集可以很好地处理简单的物体识别任务,但是现实中物体展现出很强的可变性,所以需要更大的训集来训练模型。在当时仅有的小规模数据集的缺点已经被广泛意识到,知道后来收集到上百万张的带标签的数据集(LabelMe, ImageNet),其中ImageNet包含了22000个类别的共15,000,000张带标签数据集。

      相比于简单的前向传播神经网络,卷积神经网络有更少的连接和参数,并且可以通过改变卷积层的深度和内核数来改变模型的能力,故卷积神经网络可以很容易训练并且有很强的学习能力。

      可优化执行2维卷积运算的GPU能够强有力的训练更大的CNNs。

      论文通过ImageNet的一个子集训练了一个卷积网络,在网络用在ImageNet Large-Scale Visual Recognition Challenge(ILSVRC)-2010 和ILSVRC-2012的比赛上均取得了突破性的成绩。

      模型包含 五个卷积层和三个全连接层,这个深度是很关键的,作者发展移除任何一层都会导致模型表现下降。

      该网络在两个GTX 580 3GB GPU上训练了5-6天,作者认为该结果仍然可以在更快的GPU和更大的数据集上得以优化。

2 数据集

      在ILSVRC-2010比赛中,测试集标签是可用的,所以该个版本是模型表现最好的一个版本;而在ILSVRC-2012中,测试集标签是不可用的。共有1,200,000训练数据,50,000,验证数据集和150,000测试数据集。

      在ImageNet中,用来记录错误率的最通用的两个指标是top-1top-5。top-1就是预测的label取最后概率向量里面最大的那一个作为预测结果,即你的预测结果中概率最大的那个类必须是正确类别才算预测正确;而top-5指最后的预测概率向量最大的前五名中出现了正确概率即为预测正确。

      对于数据集的预处理,由于图像分辨率不全都一样,故作者将训练集先Resize成256×256的形状,然后在CenteredCrop出输入图像;作者还将每张图像的像素值减去训练集图像像素平均值,除此之外没有对图像进行其他任何预处理。

3 网络结构

在这里插入图片描述

3.1 ReLU非线性激活函数

      由于tanh()softmax()随着训练时间的推移逐渐趋于饱和,将传统网络的激活函数tanh()softmax()改为不饱和非线性激活函数Relu(),实验表明Relu()比tanh()或softmax()在训练时间上快很多倍。对于在大数据集上训练的大规模模型,更快速的学习在模型表现上有很大的影响

3.2 在多GPUs上训练

      单个GPU的内存是有限的,故利用两个GPU进行并行训练;

      GPU只在某些层间进行通信。这意味着,例如,第3层的内核从第2层的所有内核映射(kernel maps)中获取输入。然而,第4层中的内核又仅从位于同一GPU上的第3层中的那些内核映射获取输入。选择连接模式对于交叉验证是一个不小的问题,但这使得我们能够精确调整通信量,直到它的计算量达到可接受的程度。

3.3 局部响应归一化

      尽管RELU激活函数的使用,可以在加快训练速度的同时,取得比饱和非线性映射更好的效果,但是作者在采用了局部归一化技术后,泛化性得到了提高。在特定的层RELU()激活函数后使用局部响应归一化。
在这里插入图片描述
      公式中的a表示卷积层(卷积操作和池化操作)后的输出,输出结果是一个四维数组[batch, height, width, channel];aix,y表示在这个输出结构中的一个位置[a,b,c,d],可以理解成在某一张图中的某一个通道下的某个高度和某个宽度位置的点,即第a张图的第d个通道下的高度为b宽度为c的点;
N为某一层的卷积内核数,n为该层通道数,特别注意一下叠加的方向是沿着通道方向的,即每个点值的平方和是沿着a中的第3维channel方向的,也就是一个点同方向的前面n/2个通道(最小为第0个通道)和后n/2个通道(最大为第n-1个通道)的点的平方和(共n+1个点)。
      可以从下图看出,求和所选择的点的范围是以n/2为半径的,这个半径的坐标轴是channel。
在这里插入图片描述
      LRN在生物学上的解释是相近的神经元彼此之间发生的抑制作用,即在某个神经元受到刺激而产生兴奋时,再刺激相近的神经元,则后者所发生的兴奋对前者会产生抑制作用。

参考博客:
https://blog.csdn.net/weixin_44633882/article/details/86777433
https://blog.csdn.net/yangdashi888/article/details/77918311

3.4 重叠池化

      CNNs中的池化层汇集了相同内核映射中的近邻神经元输出。
      令池化层单元大小为3×3,步长为2,则可实现重叠池化,该方案同样减少了错误率,并且作者发现在训练有重叠池化的模型时更不易发生过拟合。

3.5 整体框架

      网络包含5个卷积层和3个全连接层,输出为一个1000通道的softmax()用来表示1000个种类的标签。

      第二,第四和第五个卷积层的内核仅与上一层存放在同一GPU上的内核映射相连;第三个卷积层的内核连接到第二层中的所有内核映射;全连接层中的神经元连接到前一层中的所有神经元。响应归一化层紧接着第一个和第二个卷积层。 最大池化层跟在响应归一化层以及第五个卷积层之后。将ReLU()应用于每个卷积层和全连接层的输出。
模型整体框架表述如下:

输入图像尺寸:224×224×3
第一卷积层:(96个)kernel_size = 11×11×3 stride = 4
相响应归一化层
最大池化层
第二卷积层:(256个)kernel_size = 5×5×48
相响应归一化层
最大池化层
第三卷积层:(384个) kernel_size = 3×3×256
第四卷积层:(384个) kernel_size = 3×3×192
第三卷积层:(256个) kernel_size = 3×3×192
第一全连接层:input = 4096 output = 4096
Dropout
第二全连接层:input = 4096 output = 4096
Dropout
第三全连接层:input = 4096 output = 1000

4 减少过拟合

      网络模型有60,000,000个参数,用了两个方法来解决过拟合问题。

4.1 图像增广

      最简单和最常见的减少过拟合的方法是使用标签保留转换(label-preserving transformations),人工增大数据集。
      图像增广的第一种形式包括平移图像和水平镜像。
      具体的,从256×256的图像种随机抽取了224×224的图像patch用于训练,这将训练集的大小增大了2048倍,尽管由此产生的训练示例当然是高度相互依赖的。在测试阶段,取每一个测试样本四个角以及中间区域,一共5个patch然后再镜像后得到10个样本输入到网络中,最后将10个softmax输出平均后作为最后的输出(测试阶段的处理有意思)。

      第二种形式的图像增广包括改变训练图像中RGB通道的灰度。
      使用PCA对于训练数据进行增强:对于每一个RGB图像进行一个PCA的变换,完成去噪功能,同时为了保证图像的多样性,在特征值上加了一个随机的尺度因子α,每一轮重新生成一个尺度因子,这样保证了同一副图像中在显著特征上有一定范围的变换,降低了过拟合的概率,作者指出这种方法近似的捕获了自然图像的主要属性,即对象标识不受光照强度和颜色变化的影响;

4.2 Dropout

      将每个隐藏层的神经元以50%的概率进行随机置零;这些被随机置零的神经元并不在前向传播中产生作用,也不参与反向传播。使得每次的输入,神经网络都会对不同的体系结构进行采样,但是这些结构是分享权重的;减小了神经元之间复杂的协同适应能力。

      所以dropout强迫网络学习与其他神经元的许多不同的子集一起使用的更加健壮的特征。作者在前两个全连接层使用了dropout , 不过dropout花费了两倍的迭代时间才收敛。

5 训练中的超参数设置

      优化器采用SGD batch_size = 128 momentum = 0.9 weight_decay = 0.0005
      每一层的权重初始化为均值为0、方差为0.01的高斯分布;初始化第2、4、5层卷积层和全连接层的bias为常数1,剩余层的bias全为0。这种初始化权重的方法通过向ReLU提供了正的输入,来加速前期的训练。权重更新公式为:
在这里插入图片描述

      对所有的层使用相同的学习率,在训练过程中手动调整学习率,以经验知:以当前的学习速率训练,当验证集上的错误率停止降低时,则将学习速率除以10。学习率初始时设为0.01,并且在终止前减少3次。num_epochs 大致为90次。

6 pyTorch代码实现

      在pyTorch中未实现LRN(Local Response Normalizaton)这个功能,实际上自从后续的VGGResNet等提出后,发现LRN本质上也是一种正则化方法,效果并不明显,因此现在很少使用了。
      下面是实现LRN的部分代码:

class LRN(nn.modules):
    def __init__(self, local_size=1, k=2, alpha=1.0, beta=0.75, ACROSS_CHANNELS=False):
        super(LRN, self).__init__()
        self.ACROSS_CHANNELS = ACROSS_CHANNELS
        if self.ACROSS_CHANNELS:
            self.average = nn.AvgPool3d(kernel_size=(local_size, 1, 1), stride=1, padding=(int((local_size-1.0)/2), 0, 0))
        else:
            self.average = nn.AvgPool2d(kernel_size=local_size, stride=1, padding=(int(local_size -1.0)/2))
        self.alpha = alpha
        self.beta = beta
        self.k = k

    def forward(self, x):
        if self.ACROSS_CHANNELS:
            div = x.pow(2).unsqueeze(1)
            div = self.average(div).squeeze(1)
            div = div.mul(self.alpha).add(self.k).pow(self.beta)
        else:
            div = x.pow(2)
            div = self.average(div)
            div = div.mul(self.alpha).add(self.k).pow(self.beta)
        x = x.div(div)
        return x

      接下来根据AlexNet模型框架,创建模型:

class AlexNet(nn.Module):
    def __init__(self, num_classes=1000):  # imagenet数量
        super().__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            LRN(local_size=5, k=2, alpha=1e-4, beta=0.75, ACROSS_CHANNELS=True)
        )

        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, groups=2, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            LRN(local_size=5, k=2, alpha=1e-4, beta=0.75, ACROSS_CHANNELS=True)
        )

        self.layer3 = nn.Sequential(
            nn.Conv2d(in_channels=256, out_channels=384, padding=1, kernel_size=3),
            nn.ReLU(inplace=True)
        )
        self.layer4 = nn.Sequential(
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )

        self.layer5 = nn.Sequential(
            nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2)
        )

        # 需要针对上一层改变view
        self.layer6 = nn.Sequential(
            nn.Linear(in_features=6 * 6 * 256, out_features=4096),
            nn.ReLU(inplace=True),
            nn.Dropout()
        )
        self.layer7 = nn.Sequential(
            nn.Linear(in_features=4096, out_features=4096),
            nn.ReLU(inplace=True),
            nn.Dropout()
        )

        self.layer8 = nn.Linear(in_features=4096, out_features=num_classes)

    def forward(self, x):
        x = self.layer5(self.layer4(self.layer3(self.layer2(self.layer1(x)))))
        x = x.view(x.shape[0], -1)
        x = self.layer8(self.layer7(self.layer6(x)))

        return x

代码参考知乎
欢迎关注【OAOA

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值