通俗学AI(2):深入浅出AlexNet,深度卷积网络的探索

发布于:2023-01-20 ⋅ 阅读:(327) ⋅ 点赞:(0)

下一篇:通俗学AI(3):VGG套路得人心

目录

前言

为何卷积

容量

过拟合

论文中的技术

激活函数不一样。

多GPU训练。

重叠池化层。

局部响应归一化

减少过拟合

数据增广:

Dropout       

AlexNet

Pytorch实现

总结


前言

        今天我们要来讨论的论文是《ImageNet Classification with Deep Convolutional Neural Networks》,其作者提出的AlexNet网络是ISLVRC 2012竞赛的冠军网络。这篇论文里有很多关于减少过拟合的做法,可以说是对深度卷积网络应用于图像分类任务的重大探索。


为何卷积

        为何用卷积神经网络?

        作者在文中是这样说的:“要从数百万张图像中了解数千个对象,我们需要一个具有强大学习能力的模型。然而,对象识别任务的巨大复杂性意味着,即使是像ImageNet这样大的数据集也无法指定此问题,因此我们的模型还应该具有大量先验知识,以补偿我们没有的所有数据。卷积神经网络(CNN)就是这样一类模型。它们的容量可以通过改变深度和广度来控制,它们还对图像的性质(即统计数据的平稳性和像素相关性的局部性)做出了强有力且基本正确的假设。CNN的连接和参数要少得多,因此更容易训练,而理论上它们的最佳性能可能只是稍微差一点。”

        总结一下,卷积网络拥有强大的学习能力、可以通过改变深度和广度来获得极大的容量,而且卷积网络要训练的参数更少,更容易训练,而最终的效果却基本没差。换句话说,卷积神经网络的特点更适用于这个任务(从数百万张图像中了解数千个对象)。


容量

        这里补充一下关于容量的说明。大家普遍认为,一个网络的容量越大,就能容纳更多更丰富的语义信息,出来的效果肯定更好。而大家提高容量的方法一般都是加深网络!说人话就是叠层数,往死里叠。但显然这样做是有问题的,比如说网络太深极其容易过拟合,或者说网络深度深过一定程度后,就会出现网络退化的现象。网络退化的问题我们后面讲残差网络的时候会详细讲讲,今天主要讲一下过拟合的问题。


过拟合

 图片来自网络

        过拟合,通俗来说就是网络学习到了数据集里一些比较奇怪的特征,导致它在训练集上猛如虎,在测试集上菜成狗。就像你在驾校学倒车入库,发现每次倒车后面栏杆的一端靠近后视镜三分之一时打方向盘时效果最好,然后就利用这点拼命练习。到考场一看,发现后面没有栏杆。这就是过拟合。

        一般来说,数据太少或者模型过于复杂(网络深、参数多)就会出现过拟合。我个人认为这是相对的概念,模型越复杂,为了避免过拟合,就要更多的数据集。很多时候模型的复杂度是与性能挂钩的,但随着模型越来越复杂,我们不可能让数据集也盲目地跟着多,因为标数据集的成本可不便宜,所以我们就需要一些其他的手段来减少过拟合。


论文中的技术

        回到这篇论文,我们先把AlexNet的网络结构摆上来吧。

  图片来自论文

        和LeNet网络一样,都是前面卷积和后面全连接的模式,忘记的小伙伴可以点这里回顾一下。但其中有些操作不太一样,我们先来了解一下。


激活函数不一样。

 图片来自网络

  图片来自网络

        (上边是tanh,下边是ReLU)

        我们之前说过,LeNet是使用tanh函数作为激活函数的,这是一个饱和非线性函数,用其作为激活函数训练,训练的时间会比较就久。而因为本次的训练集比较大,图片多、分辨率高,再使用这个激活函数得训练到天荒地老,因此我们的作者使用了ReLU函数作为激活函数,下面这张图来自论文里,我们可以很直观地看出两者的区别。

 图片来自论文

        实线使用的是ReLU函数,而虚线使用的是tanh函数。可以看出来,使用ReLU函数很快就能拟合到0.25的训练误差,而tanh收敛的时间几乎是ReLU的7倍。原文说AlexNet网络训练一次的时间是6天左右,如果换成tanh函数,可能要42天才训练完毕,这种效率是我们不能忍的。


多GPU训练。

        为了训练这个网络,论文作者拿出了两张GPU进行训练,以此来提高速度与精度。我们可以看到刚才的结构图是有两条路的,这两条路分别在两块GPU上训练,同时GPU之间数据的通讯只在某些特定的卷积层进行。论文作者也提到了,可以使用一个GPU,训练半个网络(也就是只走结构图上的一条路)。但作者发现这样做的训练速度与精度都不如在两块GPU上训练。两块GPU并行训练略快于一块,同时top-5和top-1的错误率下降了1.2%和1.7%(也就是最容易分错类的前五类和第一类的错误率下降)。


重叠池化层。

我们之前说过,池化层的扫描框一般是不会重叠的,而这篇论文里的池化层就是会重叠的,它采取了核为3,步长为2的最大池化层。作者发现这样做可以略微提高泛化性能,同时减少过拟合的发生。


局部响应归一化

        局部响应归一化(Local Response Normalization,简称LRN),它的算法如下图。

        这个公式据说是来源于生物学概念中的侧抑制,生物学上的侧抑制概念是指被激活的神经元会抑制附近的神经元,这个公式也很好地体现了抑制周围的思想。

 图片来自论文

        首先其中的a是指输入,而b是指输出。输入和输出都有一种格式:一批照片为输入,其中这批照片可以看成是好几张长宽一样的图片叠在一起的,也就是我们说的多通道(听不懂可以看我以前写的这篇)。而我们取其中一张照片来看,其中一个点在这个像素坐标系上的坐标就是x和y,而这一批照片有多少张就是有多少个channel(通道),而channel的总数就是N了。假设我们取其中第i张(第i个通道的)照片,在其中找到一个x,y的点,我们将它看成激活的神经元,那它要抑制的周围的神经元就是上几张照片(通道小于i的照片)的同一个点和下几张照片(通道大于i的照片)的同一个点。那我们要抑制多少个神经元呢?这就要我们规定n了,这个通常是上下各抑制一半,所以公式中有n/2,另外在一些写深度学习的库中,通常用depth_radius来指n/2,因为是radius(半径),所以是n/2,而不是指n。另外k是指bias(偏置,防止发生除于0的情况),它和alpha与beta都是要自己设定的。这篇论文最后搞出来是k=2,n=5,α=10−4,β=0.75。

        为什么要做归一化呢?经过查找资料,我认为比较合理的解释是:深度学习实际是在学习分布,虽然这其中可能存在相同的分布,但这个分布又不相同。就像y=kx和y=kx+1一样,虽然都是线性的,斜率也一样,但有可能一个过原点一个不过。归一化就像把这些偏移或者缩放都调整为一样的,不会因为分布变化的累积而影响训练效果和拟合速度。这也许是CNN得以不断增加深度的原因吧。

        当然,后面有论文认为这个方法并没有什么鬼用,收效甚微,如果看不懂可以略过。


减少过拟合

数据增广:

        刚才我们说了,数据集要和网络容量匹配,但如果我们的数据集始终没办法增加,那该如何?一个比较简单的方法就是,无中生有!比如说这篇论文,它其中一个增广方式就是如此,在讲这个增广方法前,我们先说说这个作者是如何处理他的数据集的。

 图片改自论文

        他把竞赛给的数据集全部缩成256*256。你可能会说,这样放缩不就导致内容变形了吗?确实如此,因此作者是把图片的短边缩短成256,然后长边再等比例缩小,最后再从这个缩小后的图片中间,裁剪出256*256。并且这图上每个像素点都减去像素均值。

        随后,作者拿着这堆256*256的图片,在其中随意地裁剪出224*224的区域(这就是为什么网络的输入是224而不是256),并且将这个图片进行水平翻转,这样就将原来的数据集增加成(256-224)(256-224)*2=2048倍,可以说是玩无中生有的好手。

        文章里还提到了另外一种增广方式,涉及到PCA,大家有兴趣的可以自己了解一下,这里不做过多讲解。

        论文作者还提到,当前一批数据在GPU上训练时,后面一批数据就可以在CPU上做数据增广处理了。等前面训练完了,后面就可以直接拿去训练,所以基本不耗时


Dropout       

        数据增广,是作者减少过拟合的一种手段,还有另外一个减少过拟合的杀器,那就是Dropout这个其实就是随机将部分神经元暂时失活,只更新其他部分的神经元的参数,不断重复这个过程。这样做可以减少神经元之间的相互依赖,从而提高神经元的鲁棒性,防止过拟合。就像班级里,如果座位固定来考试的话,同桌之间很熟悉,就可能相互依赖,这样同学就可能不复习而依赖同桌,实力就没那么好。如果考试是打乱座位,相当于减少了同桌之间的依赖,倒逼同学去自我学习,从而不管座位怎么变都能考高分。

        但用这个方法会成倍增加训练时间,论文里使用的神经元失活概率是0.5,对应的,训练所花费的时间是原来的两倍。


AlexNet

        在了解完所有的技术后,我们回到AlexNet的结构,这里重新把结构图摆上来。

 图片来自论文

        其实这个图是不完整的,因为LRN和激活函数还没有标上去,我们根据文章里说的,将这些标上去,并给出一个新的图。

 图片改自论文

        下面我们计算一下每层的尺寸。下面这张图展示了计算的过程。层旁边标的是这一层尺寸的变化以及输出的结果:

        注意,第一个卷积中的+1和+2是指左边和右边其中一边补1列,另外一边补2列,上边和下边其中一边补1行,另外一边补2行。


Pytorch实现

下面我们用Pytorch来实现一下AlexNet网络。为了方便,我们只写使用一块GPU进行训练的网络。作者曾在论文中提到,为了使全连接层头一层的参数变化不要太大,最后的卷积层的核数可以不用减半。除此之外,为了方便,我们也不实现LRN,毕竟它的效果收效甚微。

# 各种引入
import torch.nn as nn
import torch

# 继承nn.Module
class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__() # 调用父类方法
        # 用Sequential可以把下面的层都排好顺序
        self.features = nn.Sequential(
            # 下面尺寸会算出来是55.25,当这个尺寸不是整数,它会自动舍去小数,变为55
            nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2),  # input[3, 224, 224]  output[48, 55, 55]
            # 使用inplace类似地址传递,不用复制一份浪费时间
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[48, 27, 27]
            nn.Conv2d(48, 128, kernel_size=5, padding=2),           # output[128, 27, 27]
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 13, 13]
            nn.Conv2d(128, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 192, kernel_size=3, padding=1),          # output[192, 13, 13]
            nn.ReLU(inplace=True),
            nn.Conv2d(192, 128, kernel_size=3, padding=1),          # output[128, 13, 13]
            # nn.Conv2d(192, 256, kernel_size=3, padding=1),          # output[256, 13, 13]
            # 最后的卷积层的核数可以不用减半
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),                  # output[128, 6, 6]
        )
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(128 * 6 * 6, 2048),
            # nn.Linear(256 * 6 * 6, 2048),如果上面改动了
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(2048, 2048),
            nn.ReLU(inplace=True),
            nn.Linear(2048, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        # faltten就是把卷积层最后的输出压缩成一行,start_dim不写0是因为第一维是batch_size
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x

总结

        我们可以看到,作者在论文里谈论了很多防止过拟合的手段:Dropout、LRN、数据增广、覆盖池化。由此我们能知道,想要效果好,就要网络深,但深不是简单的深,我们要面临多种问题,首当其冲的便是过拟合的问题

        再说个比较有意思的事情:论文作者在文中提到“如果移除单个卷积层,我们的网络性能会下降。例如,删除任何中间层都会导致网络的顶级性能损失约2%。因此,深度对于实现我们的成果非常重要。”这其实印证了我们之前的想法,即卷积其实是一个特征提取的过程,去掉任何一个卷积层,我们提取到的特征就没有那么高维,那么好

        同时它也引出了一个问题,即特征提取器的性能其实是与网络深度有关的。所以后面大家都开始研究如何让网络变得更深,从而来获取更好的性能。


作者:公|众|号【荣仙翁】

内容同步在其中,想看更多内容可以关注我哦。

本文含有隐藏内容,请 开通VIP 后查看