什么是AlexNet?

       对于CNN(卷积神经网络)最早可以追溯到1986年BP算法的提出,然后1989年LeCun将其用到多层神经网络中,直到1998年LeCun提出LeNet-5模型,神经网络的雏形完成。第一个典型的CNN就是LeNet5网络结构,但是今天我们要讲的主角是AlexNet也就是文章《ImageNet Classification with Deep Convolutional Neural Networks》所介绍的网络结构。Alex等人在2012年提出的AlexNet网络结构模型在ILSVRC-2012上以巨大的优势获得第一名,引爆了神经网络的应用热潮,使得卷积神经网络CNN成为在图像分类上的核心算法模型。这篇论文阐述了一个多层卷积网络,目标是将120万高分辨率的图像分成1000类。

Net Structure
  • AlexNet首先用一张227×227×3的图片作为输入,实际上原文中使用的图像是224×224×3,但是如果你尝试去推导一下,你会发现227×227这个尺寸更好一些。

  • 第一层我们使用96个11×11的过滤器,步幅为4,由于步幅是4,因此尺寸缩小到55×55,缩小了4倍左右。然后用一个3×3的过滤器构建最大池化层,f=3,步幅s为2,卷积层尺寸缩小为27×27×96。接着再执行一个5×5的卷积,padding之后,输出是27×27×256。然后再次进行最大池化,尺寸缩小到13×13。再执行一次same卷积,相同的padding,得到的结果是13×13×384,用384个过滤器再做一次same卷积,再做一次同样的操作,最后再进行一次最大池化,尺寸缩小到6×6×256。6×6×256等于9216,将其展开为9216个单元,

  • 然后是三层全连接层,最后使用softmax函数输出识别的结果,看它究竟是1000个可能的对象中的哪一个。

  • 第2,4,5卷积层的神经元只与位于同一GPU上的前一层的核映射相连接。第3卷积层的核与第2层的所有核映射相连。全连接层的神经元与前一层的所有神经元相连。第1,2卷积层之后是响应归一化层。最大池化层加在了响应归一化层和第5卷积层之后。ReLU非线性应用在每个卷积层和全连接层的输出上。

  • 最终整个网络包含5个卷积层和3个全连接层,深度似乎是非常重要的:移除任何卷积层(每个卷积层包含的参数不超过模型参数的1%)都会导致更差的性能。

什么是AlexNet?

New and Unusual Features
  • ReLU

    • 激活函数采用ReLU,而不是tanh和sigmoid函数,与饱和神经元相比,有ReLU的网络学习速度要快好几倍

  • Training on Multiple GPUs

  • 在写这篇论文的时候,GPU的处理速度还比较慢,所以AlexNet采用了非常复杂的方法在两个GPU上进行训练。大致原理是,这些层分别拆分到两个不同的GPU上,同时还专门有一个方法用于两个GPU进行交流。

  • Local Response Normalization

    • “局部响应归一化层”(Local Response Normalization),即LRN层。局部响应归一层的基本思路是,假如这是网络的一块,比如是13×13×256,LRN要做的就是选取一个位置,从这个位置穿过整个通道,能得到256个数字,并进行归一化。进行局部响应归一化的动机是,对于这张13×13的图像中的每个位置来说,我们可能并不需要太多的高激活神经元。但是后来,很多研究者发现LRN起不到太大作用,因此我们现在并不用LRN来训练网络。

  • Overlapping Pooling

    • 一般的池化层因为没有重叠,所以poolsize 和 stride一般是相等的,例如8×8的一个图像,如果池化层的尺寸是2×2 ,那么经过池化后的操作得到的图像是 4×4 大小,这种设置叫做不覆盖的池化操作,如果 stride < poolsize, 那么就会产生覆盖的池化操作,这种有点类似于convolutional化的操作,这样可以得到更准确的结果。论文指出,在训练模型过程中,覆盖的池化层更不容易过拟合。

Reducing Overfitting
  •  

    Data Augmentation 数据扩充是防止过拟合的一种简单方法,只需要对原始的数据进行合适的变换,就会得到更多有差异的数据集,防止过拟合。本文中主要采用了两种方法来进行数据扩充,这两种方式都是从原始图像通过非常少的计算量产生变换的图像,因此变换图像不需要存储在硬盘上。

     

    • 第一种数据增强方式包括产生图像平移和水平翻转。我们从256× 256图像上通过随机提取224 × 224的图像块(以及这些图像块的水平翻转)实现了这种方式,然后在这些提取的图像块上进行训练。在测试时,网络会提取5个224 × 224的图像块(四个角上的图像块和中心的图像块)和它们的水平翻转(因此总共10个图像块)进行预测,然后对网络在10个图像块上的softmax层的预测结果进行平均。

    • 第二种数据增强方式是改变训练图像的RGB通道的强度。在整个ImageNet训练集上对RGB像素值集合执行主成分分析(PCA)。对于每幅训练图像,我们加上多倍找到的主成分,大小成正比的对应特征值乘以一个随机变量,这个随机变量通过均值为0,标准差为0.1的高斯分布得到。这个方案近似抓住了自然图像的一个重要特性,即光照的强度和颜色发生变化时,物体本身没有发生变化。

  •  

    Dropout

     

    • 以0.5的概率对每个隐层神经元的输出设为0。那些用这种方式“丢弃”的神经元不再进行前向传播并且不参与反向传播。因此每次输入时,神经网络会采样一个不同的架构,但所有架构共享权重。这个技术减少了复杂的神经元互适应,因为一个神经元不能依赖特定的其它神经元的存在。

    • 在前两层全连接层使用Dropout。

    • 该技术大致上使要求收敛的迭代次数翻了一倍。

  •  

    Details of Learning

     

     

     

    • 使用随机梯度下降来训练模型,样本的batch size为128,动量为0.9,权重衰减率为0.0005。少量的权重衰减对于模型的学习是重要的。换句话说,权重衰减不仅仅是一个正则项:而且它减少了模型的训练误差。权重的更新规则: 什么是AlexNet?

    • i是迭代索引,v是动量变量,ϵ是学习率, 是目标函数对w,在wi上的第i批微分Di的平均。

    • 使用均值为0,标准差为0.01的高斯分布对每一层的权重进行初始化。在第2,4,5卷积层和全连接隐层将神经元偏置初始化为常量1。这个初始化通过为ReLU提供正输入加速了早期阶段的学习,对剩下的层的神经元偏置初始化为0。

    • 当验证误差在当前的学习率下停止改善时,遵循启发式的方法将学习率除以10。

Code with Tensorflow
  1. class AlexNet(object):
    
    def __init__(self, x, keep_prob, num_classes, skip_layer,
    
    weights_path='DEFAULT'):
    
    """
    
    Create the graph of the AlexNet model.
    
    Args:
    
    x: Placeholder for the input tensor.
    
    keep_prob: Dropout probability.
    
    num_classes: Number of classes in the dataset.
    
    skip_layer: List of names of the layer, that get trained from
    
    scratch
    
    weights_path: Complete path to the pretrained weight file, if it
    
    isn't in the same folder as this code
    
    """
    
    # Parse input arguments into class variables
    
    self.X = x
    
    self.NUM_CLASSES = num_classes
    
    self.KEEP_PROB = keep_prob
    
    self.SKIP_LAYER = skip_layer
    
    if weights_path == 'DEFAULT':
    
    self.WEIGHTS_PATH = 'bvlc_alexnet.npy'
    
    else:
    
    self.WEIGHTS_PATH = weights_path
    
    # Call the create function to build the computational graph of AlexNet
    
    self.create()
    
    def create(self):
    
    # 1st Layer: Conv (w ReLu) -> Lrn -> Pool
    
    conv1 = conv(self.X, 11, 11, 96, 4, 4, padding='VALID', name='conv1')
    
    norm1 = lrn(conv1, 2, 1e-04, 0.75, name='norm1')
    
    pool1 = max_pool(norm1, 3, 3, 2, 2, padding='VALID', name='pool1')
    
    # 2nd Layer: Conv (w ReLu) -> Lrn -> Pool with 2 groups
    
    conv2 = conv(pool1, 5, 5, 256, 1, 1, groups=2, name='conv2')
    
    norm2 = lrn(conv2, 2, 1e-04, 0.75, name='norm2')
    
    pool2 = max_pool(norm2, 3, 3, 2, 2, padding='VALID', name='pool2')
    
    # 3rd Layer: Conv (w ReLu)
    
    conv3 = conv(pool2, 3, 3, 384, 1, 1, name='conv3')
    
    # 4th Layer: Conv (w ReLu) splitted into two groups
    
    conv4 = conv(conv3, 3, 3, 384, 1, 1, groups=2, name='conv4')
    
    # 5th Layer: Conv (w ReLu) -> Pool splitted into two groups
    
    conv5 = conv(conv4, 3, 3, 256, 1, 1, groups=2, name='conv5')
    
    pool5 = max_pool(conv5, 3, 3, 2, 2, padding='VALID', name='pool5')
    
    # 6th Layer: Flatten -> FC (w ReLu) -> Dropout
    
    flattened = tf.reshape(pool5, [-1, 6*6*256])
    
    fc6 = fc(flattened, 6*6*256, 4096, name='fc6')
    
    dropout6 = dropout(fc6, self.KEEP_PROB)
    
    # 7th Layer: FC (w ReLu) -> Dropout
    
    fc7 = fc(dropout6, 4096, 4096, name='fc7')
    
    dropout7 = dropout(fc7, self.KEEP_PROB)
    
    # 8th Layer: FC and return unscaled activations
    
    self.fc8 = fc(dropout7, 4096, self.NUM_CLASSES, relu=False, name='fc8')
    
    def load_initial_weights(self, session):
    
    # Load the weights into memory
    
    weights_dict = np.load(self.WEIGHTS_PATH, encoding='bytes').item()
    
    # Loop over all layer names stored in the weights dict
    
    for op_name in weights_dict:
    
    # Check if layer should be trained from scratch
    
    if op_name not in self.SKIP_LAYER:
    
    with tf.variable_scope(op_name, reuse=True):
    
    # Assign weights/biases to their corresponding tf variable
    
    for data in weights_dict[op_name]:
    
    # Biases
    
    if len(data.shape) == 1:
    
    var = tf.get_variable('biases', trainable=False)
    
    session.run(var.assign(data))
    
    # Weights
    
    else:
    
    var = tf.get_variable('weights', trainable=False)
    
    session.run(var.assign(data))

 

上一篇:AlexNet-pytorch实现


下一篇:深度学习之经典网络架构ZFNet(三)