AlexNet网络结构详解(含各层维度大小计算过程)与PyTorch实现


    在上文中详细介绍了第一个神经网络 LeNet,但是在其提出20年的时间里并没有引起学者和各研究机构的重视,随机机器学习的兴起,LeNet网络基本被遗忘了。而真正打破机器学习和传统特征提取方法限制的深度神经网络就是本文要讲述的AlexNet。

1、AlexNet之前的思考

    在AlexNet网络问世之前,大量的学者在进行图像分类、分割、识别等工作时,主要是通过对图像进行特征提取或是特征+机器学习的方法。但是,这种手工准确提取特征是非常难的事情,而且即便使用机器学习的方法,整个算法的鲁棒性依然存在较大的问题。因此,一直都有一种讨论:特征是不是也是可以进行学习的?如果是可以学习的,那么特征的表示是不是也存在层级问题(例如第一层为线或是点特征,第二层为线与点组成的初步特征,第三层为局部特征)?从这一思想出发,特征可学习且自动组合并给出结果,这是典型的“end-to-end”,而特征学习与自由组合就是深度学习的黑盒子部分。

    尽管以上思想在现在看来不能说是绝对正确的,但是至少是图像、语音等研究领域的最重要的研究方向之一。但是20多年来并没有得到充分发展,个人这主要有以下几个原因:

  1. 最重要的就是数据问题。从现在的情况看深度学习是需要大量数据进行支撑的,少量样本对传统算法和机器学习模型的优势并不大,甚至可以说效果相当不理想。少量的训练样本在处理实际项目中并不占优势。
  2. 硬件条件的限制。深度学习训练需要大量样本,但是大量样本需要大量的内存和计算开销,这种限制的打破也是在2001年以后GPU兴起逐步解决的。
  3. 专门研究初始化参数、优化算法等方面的学者相对较少,大量的学者还在研究手工提取特征,即深度学习理论基础并不坚实。

    上文中提到,AlexNet出现后打破了原来众多学者的认知,它首次证明了学习到的特征可以超越手工设计的特征,从而越来越多的人开始重新审视深度学习算法并加入到研究的浪潮中。

2、AlexNet网络结构

    AlexNet网络结构相对简单,使用了8层卷积神经网络,前5层是卷积层,剩下的3层是全连接层,具体如下所示。
在这里插入图片描述
    从上图看,在网络设计上其实并非如上图所示,上图包含了GPU通信的部分。这是由当时GPU内存的限制引起的,作者使用两块GPU进行计算,因此分为了上下两部分。但是,以目前GPU的处理能力,单GPU足够了,因此其结构图可以如下所示:

在这里插入图片描述

    值得注意的一点:原图输入224 × 224,实际上进行了随机裁剪,实际大小为227 × 227。

2.1 卷积层C1

    C1的基本结构为:卷积–>ReLU–>池化

  1. 卷积:输入227 × 227 × 3,96个11×11×3的卷积核,不扩充边缘padding = 0,步长stride = 4,因此其FeatureMap大小为(227-11+0×2+4)/4 = 55,即55×55×96;
  2. 激活函数:ReLU;
  3. 池化:池化核大小3 × 3,不扩充边缘padding = 0,步长stride = 2,因此其FeatureMap输出大小为(55-3+0×2+2)/2=27, 即C1输出为27×27×96(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为27×27×48);

2.2 卷积层C2

    C2的基本结构为:卷积–>ReLU–>池化

  1. 卷积:输入27×27×96,256个5×5×96的卷积核,扩充边缘padding = 2, 步长stride = 1,因此其FeatureMap大小为(27-5+2×2+1)/1 = 27,即27×27×256;
  2. 激活函数:ReLU;
  3. 池化:池化核大小3 × 3,不扩充边缘padding = 0,步长stride = 2,因此其FeatureMap输出大小为(27-3+0+2)/2=13, 即C2输出为13×13×256(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为13×13×128);

2.3 卷积层C3

    C3的基本结构为:卷积–>ReLU。注意一点:此层没有进行MaxPooling操作。

  1. 卷积:输入13×13×256,384个3×3×256的卷积核, 扩充边缘padding = 1,步长stride = 1,因此其FeatureMap大小为(13-3+1×2+1)/1 = 13,即13×13×384;
  2. 激活函数:ReLU,即C3输出为13×13×384(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为13×13×192);

2.4 卷积层C4

    C4的基本结构为:卷积–>ReLU。注意一点:此层也没有进行MaxPooling操作。

  1. 卷积:输入13×13×384,384个3×3×384的卷积核, 扩充边缘padding = 1,步长stride = 1,因此其FeatureMap大小为(13-3+1×2+1)/1 = 13,即13×13×384;
  2. 激活函数:ReLU,即C4输出为13×13×384(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为13×13×192);

2.5 卷积层C5

    C5的基本结构为:卷积–>ReLU–>池化

  1. 卷积:输入13×13×384,256个3×3×384的卷积核,扩充边缘padding = 1,步长stride = 1,因此其FeatureMap大小为(13-3+1×2+1)/1 = 13,即13×13×256;
  2. 激活函数:ReLU;
  3. 池化:池化核大小3 × 3, 扩充边缘padding = 0,步长stride = 2,因此其FeatureMap输出大小为(13-3+0×2+2)/2=6, 即C5输出为6×6×256(此处未将输出分到两个GPU中,若按照论文将分成两组,每组为6×6×128);

2.6 全连接层FC6

    FC6的基本结构为:全连接–>>ReLU–>Dropout

  1. 全连接:此层的全连接实际上是通过卷积进行的,输入6×6×256,4096个6×6×256的卷积核,扩充边缘padding = 0, 步长stride = 1, 因此其FeatureMap大小为(6-6+0×2+1)/1 = 1,即1×1×4096;
  2. 激活函数:ReLU;
  3. Dropout:全连接层中去掉了一些神经节点,达到防止过拟合,FC6输出为1×1×4096;

2.7 全连接层FC7

    FC7的基本结构为:全连接–>>ReLU–>Dropout

  1. 全连接:此层的全连接,输入1×1×4096;
  2. 激活函数:ReLU;
  3. Dropout:全连接层中去掉了一些神经节点,达到防止过拟合,FC7输出为1×1×4096;

2.8 全连接层FC8

    FC8的基本结构为:全连接–>>softmax

  1. 全连接:此层的全连接,输入1×1×4096;
  2. softmax:softmax为1000,FC8输出为1×1×1000;

    在整个过程中,并没有将C1C2中的Local Response Normalization(局部响应归一化)操作添加在其中,此操作就是将ReLU得到的结果进行归一化,读者可以查看一下原论文。

3、AlexNet网络结构的主要贡献

3.1 ReLU激活函数的引入

    采用修正线性单元(ReLU)的深度卷积神经网络训练时间比等价的tanh单元要快几倍。而时间开销是进行模型训练过程中很重要的考量因素之一。同时,ReLU有效防止了过拟合现象的出现。由于ReLU激活函数的高效性与实用性,使得它在深度学习框架中占有重要地位。

3.2 层叠池化操作

    以往池化的大小PoolingSize与步长stride一般是相等的,例如:图像大小为256*256,PoolingSize=2×2,stride=2,这样可以使图像或是FeatureMap大小缩小一倍变为128,此时池化过程没有发生层叠。但是AlexNet采用了层叠池化操作,即PoolingSize > stride。这种操作非常像卷积操作,可以使相邻像素间产生信息交互和保留必要的联系。论文中也证明,此操作可以有效防止过拟合的发生。

3.3 Dropout操作

    Dropout操作会将概率小于0.5的每个隐层神经元的输出设为0,即去掉了一些神经节点,达到防止过拟合。那些“失活的”神经元不再进行前向传播并且不参与反向传播。这个技术减少了复杂的神经元之间的相互影响。在论文中,也验证了此方法的有效性。

3.4 网络层数的增加

    与原始的LeNet相比,AlexNet网络结构更深,LeNet为5层,AlexNet为8层。在随后的神经网络发展过程中,AlexNet逐渐让研究人员认识到网络深度对性能的巨大影响。当然,这种思考的重要节点出现在VGG网络(下文中将会讲到),但是很显然从AlexNet为起点就已经开始了这项工作。

4、PyTorch实现

此代码来自《动手学深度学习》,这是一本不错的教材,大家可以参考一下。

import time
import torch
from torch import nn, optim
import torchvision
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
            nn.ReLU(),
            nn.MaxPool2d(3, 2), # kernel_size, stride
            # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
            nn.Conv2d(96, 256, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            # 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
            # 前两个卷积层后不使用池化层来减小输入的高和宽
            nn.Conv2d(256, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(3, 2)
        )
         # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
        self.fc = nn.Sequential(
            nn.Linear(256*5*5, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            # 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
            nn.Linear(4096, 10),
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐