AlexNet网络详解

 

目录

一、AlexNet特点

1.1ReLu作为激活函数

1.2数据增强

1.3层叠池化

1.4局部相应归一化(Local Response Normalization,LRN)

1.5 Dropout

二、Alex网络

1.网络结构

2.参数数量

3.代码实现

Keras实现

TensorFlow实现

pytorch实现:


由于受到计算机性能的影响,虽然LeNet在图像分类中取得了较好的成绩,但是并没有引起很多的关注。 直到2012年,Alex等人提出的AlexNet网络在ImageNet大赛上以远超第二名的成绩夺冠,卷积神经网络乃至深度学习重新引起了广泛的关注。

一、AlexNet特点

AlexNet是在LeNet的基础上加深了网络的结构,学习更丰富更高维的图像特征。AlexNet的特点:

  • 更深的网络结构

  • 使用层叠的卷积层,即卷积层+卷积层+池化层来提取图像的特征

  • 使用Dropout抑制过拟合

  • 使用数据增强Data Augmentation抑制过拟合

  • 使用Relu替换之前的sigmoid的作为激活函数

  • 多GPU训练

AlexNet将LeNet的思想发扬光大,把CNN的基本原理应用到了很深很宽的网络中。AlexNet主要使用到的新技术点如下:
(1)成功使用ReLU作为CNN的激活函数,并验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题。虽然ReLU激活函数在很久之前就被提出了,但是直到AlexNet的出现才将其发扬光大。
(2)训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合。Dropout虽有单独的论文论述,但是AlexNet将其实用化,通过实践证实了它的效果。在AlexNet中主要是最后几个全连接层使用了Dropout。
(3)在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,AlexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。
(4)提出了LRN层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。
(5)使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。AlexNet使用了两块GTX 580 GPU进行训练,单个GTX 580只有3GB显存,这限制了可训练的网络的最大规模。因此作者将AlexNet分布在两个GPU上,在每个GPU的显存中储存一半的神经元的参数。因为GPU之间通信方便,可以互相访问显存,而不需要通过主机内存,所以同时使用多块GPU也是非常高效的。同时,AlexNet的设计让GPU之间的通信只在网络的某些层进行,控制了通信的性能损耗。
(6)数据增强,随机地从256*256的原始图像中截取224*224大小的区域(以及水平翻转的镜像),相当于增加了2*(256-224)^2=2048倍的数据量。如果没有数据增强,仅靠原始的数据量,参数众多的CNN会陷入过拟合中,使用了数据增强后可以大大减轻过拟合,提升泛化能力。进行预测时,则是取图片的四个角加中间共5个位置,并进行左右翻转,一共获得10张图片,对他们进行预测并对10次结果求均值。同时,AlexNet论文中提到了会对图像的RGB数据进行PCA处理,并对主成分做一个标准差为0.1的高斯扰动,增加一些噪声,这个Trick可以让错误率再下降1%。
 

1.1ReLu作为激活函数

在最初的感知机模型中,输入和输出的关系如下:

y=∑iwixi+b

只是单纯的线性关系,这样的网络结构有很大的局限性:即使用很多这样结构的网络层叠加,其输出和输入仍然是线性关系,无法处理有非线性关系的输入输出。因此,对每个神经元的输出做个非线性的转换也就是,将上面就加权求和∑iwixi+b的结果输入到一个非线性函数,也就是激活函数中。 这样,由于激活函数的引入,多个网络层的叠加就不再是单纯的线性变换,而是具有更强的表现能力。

AlexNet网络详解_第1张图片

在最初,sigmod和tanh 函数最常用的激活函数。

  1. sigmod

    AlexNet网络详解_第2张图片

在网络层数较少时,sigmod函数的特性能够很好的满足激活函数的作用:它把一个实数压缩至0到1之间,当输入的数字非常大的时候,结果会接近1;当输入非常大的负数时,则会得到接近0的结果。这种特性,能够很好的模拟神经元在受刺激后,是否被激活向后传递信息(输出为0,几乎不被激活;输出为1,完全被激活)。

sigmod一个很大的问题就是梯度饱和。 观察函数的曲线,当输入的数字较大(或较小)时,其函数值趋于不变,其导数变的非常的小。这样,在层数很多的的网络结构中,进行反向传播时,由于很多个很小的sigmod导数累成,导致其结果趋于0,权

        2.ReLu

AlexNet网络详解_第3张图片

针对sigmod梯度饱和导致训练收敛慢的问题,在AlexNet中引入了ReLU。ReLU是一个分段线性函数,小于等于0则输出为0;大于0的则恒等输出。相比于sigmod,ReLU有以下有点:

  • 计算开销下。

    sigmod的正向传播有指数运算,倒数运算,而ReLu是线性输出;反向传播中,sigmod有指数运算,而ReLU有输出的部分,导数始终为1.

  • 梯度饱和问题

  • 稀疏性。Relu会使一部分神经元的输出为0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。

这里有个问题,前面提到,激活函数要用非线性的,是为了使网络结构有更强的表达的能力。那这里使用ReLU本质上却是个线性的分段函数,是怎么进行非线性变换的。

这里把神经网络看着一个巨大的变换矩阵M,其输入为所有训练样本组成的矩阵A,输出为矩阵B。

B=M⋅A

这里的M是一个线性变换的话,则所有的训练样本A进行了线性变换输出为B。

简单来说,不同训练样本中的同样的特征,在经过神经网络学习时,流经的神经元是不一样的(激活函数值为0的神经元不会被激活)。这样,最终的输出实际上是输入样本的非线性变换。

单个训练样本是线性变换,但是每个训练样本的线性变换是不一样的,这样整个训练样本集来说,就是非线性的变换。

1.2数据增强

神经网络由于训练的参数多,表能能力强,所以需要比较多的数据量,不然很容易过拟合。当训练数据有限时,可以通过一些变换从已有的训练数据集中生成一些新的数据,以快速地扩充训练数据。对于图像数据集来说,可以对图像进行一些形变操作:

  • 翻转

  • 随机裁剪

  • 平移,颜色光照的变换

  • ...

AlexNet中对数据做了以下操作:

  1. 随机裁剪,对256*256的图片进行随机裁剪到227*227,然后进行水平翻转。

  2. 测试的时候,对左上、右上、左下、右下、中间分别做了5次裁剪,然后翻转,共10个裁剪,之后对结果求平均。

  3. 对RGB空间做PCA(主成分分析),然后对主成分做一个(0, 0.1)的高斯扰动,也就是对颜色、光照作变换,结果使错误率又下降了1%。

1.3层叠池化

在LeNet中池化是不重叠的,即池化的窗口的大小和步长是相等的,如下

AlexNet网络详解_第4张图片

在AlexNet中使用的池化(Pooling)却是可重叠的,也就是说,在池化的时候,每次移动的步长小于池化的窗口长度。AlexNet池化的大小为3×3的正方形,每次池化移动步长为2,这样就会出现重叠。重叠池化可以避免过拟合,这个策略贡献了0.3%的Top-5错误率。与非重叠方案s=2,z=2相比,输出的维度是相等的,并且能在一定程度上抑制过拟合。

1.4局部相应归一化(Local Response Normalization,LRN)

AlexNet网络详解_第5张图片

但似乎,在后来的设计中,这一层已经被其它种的Regularization技术,如drop out, batch normalization取代了。知道了这些,似乎也可以不那么纠结这个LRN了。

 

1.5 Dropout

这个是比较常用的抑制过拟合的方法了。

引入Dropout主要是为了防止过拟合。在神经网络中Dropout通过修改神经网络本身结构来实现,对于某一层的神经元,通过定义的概率将神经元置为0,这个神经元就不参与前向和后向传播,就如同在网络中被删除了一样,同时保持输入层与输出层神经元的个数不变,然后按照神经网络的学习方法进行参数更新。在下一次迭代中,又重新随机删除一些神经元(置为0),直至训练结束。

Dropout应该算是AlexNet中一个很大的创新,现在神经网络中的必备结构之一。Dropout也可以看成是一种模型组合,每次生成的网络结构都不一样,通过组合多个模型的方式能够有效地减少过拟合,Dropout只需要两倍的训练时间即可实现模型组合(类似取平均)的效果,非常高效。

如下图:

AlexNet网络详解_第6张图片

二、Alex网络

1.网络结构

AlexNet网络详解_第7张图片

上图中的输入是224*224,不过经过计算(224-11)/4=54.75,  不是论文中的 55*55,而使用227*227作为输入,则(227-11)/4=55

网络包含8个带权重的层;前5层是卷积层,剩下的3层是全连接层。最后一层全连接层的输出是1000维softmax的输入,softmax会产生1000类标签的分布网络包含8个带权重的层;前5层是卷积层,剩下的3层是全连接层。最后一层全连接层的输出是1000维softmax的输入,softmax会产生1000类标签的分布。

总的来看,AlexNet网络的结构是:

[227x227x3] INPUT
[55x55x96] CONV1 : 96 11x11 filters at stride 4, pad 0
[27x27x96] MAX POOL1 : 3x3 filters at stride 2
[27x27x96] NORM1: Normalization layer
[27x27x256] CONV2 : 256 5x5 filters at stride 1, pad 2
[13x13x256] MAX POOL2: 3x3 filters at stride 2
[13x13x256] NORM2: Normalization layer
[13x13x384] CONV3 : 384 3x3 filters at stride 1, pad 1
[13x13x384] CONV4 : 384 3x3 filters at stride 1, pad 1
[13x13x256] CONV5 : 256 3x3 filters at stride 1, pad 1
[6x6x256] MAX POOL3 : 3x3 filters at stride 2
[4096] FC6: 4096 neurons
[4096] FC7: 4096 neurons
[1000] FC8: 1000 neurons (class scores)

 

具体到每一层的结构是:

AlexNet网络详解_第8张图片

AlexNet网络详解_第9张图片

AlexNet网络详解_第10张图片

 

 

2.参数数量

AlexNet网络详解_第11张图片

3.代码实现

Keras实现

由于AlexNet是使用两块显卡进行并行训练的,其网络结构的实际是分组进行的(结构图中后面的部分分成上下两部分特征图)。并且,在C2,C4,C5上其卷积核只和上一层的同一个GPU上的卷积核相连。 对于单显卡来说,并不适用,本文基于Keras的实现,忽略其关于双显卡的的结构,并且将局部归一化操作换成了BN。其网络结构如下:

AlexNet网络详解_第12张图片

 

TensorFlow实现

方式一:这里实现了3次convolutional和二次全连接层


# 输入数据
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./mnist/", one_hot=True)
import tensorflow as tf

# 定义网络超参数
learning_rate = 0.001
training_iters = 200000
batch_size = 64
display_step = 20

# 定义网络参数
n_input = 784 # 输入的维度
n_classes = 10 # 标签的维度
dropout = 0.8 # Dropout 的概率

# 占位符输入
x = tf.placeholder(tf.types.float32, [None, n_input])
y = tf.placeholder(tf.types.float32, [None, n_classes])
keep_prob = tf.placeholder(tf.types.float32)

# 卷积操作
def conv2d(name, l_input, w, b):
    return tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(l_input, w, strides=[1, 1, 1, 1], padding='SAME'),b), name=name)

# 最大下采样操作
def max_pool(name, l_input, k):
    return tf.nn.max_pool(l_input, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME', name=name)

# 归一化操作
def norm(name, l_input, lsize=4):
    return tf.nn.lrn(l_input, lsize, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name=name)

# 定义整个网络
def alex_net(_X, _weights, _biases, _dropout):
    # 向量转为矩阵
    _X = tf.reshape(_X, shape=[-1, 28, 28, 1])

    # 卷积层
    conv1 = conv2d('conv1', _X, _weights['wc1'], _biases['bc1'])
    # 下采样层
    pool1 = max_pool('pool1', conv1, k=2)
    # 归一化层
    norm1 = norm('norm1', pool1, lsize=4)
    # Dropout
    norm1 = tf.nn.dropout(norm1, _dropout)

    # 卷积
    conv2 = conv2d('conv2', norm1, _weights['wc2'], _biases['bc2'])
    # 下采样
    pool2 = max_pool('pool2', conv2, k=2)
    # 归一化
    norm2 = norm('norm2', pool2, lsize=4)
    # Dropout
    norm2 = tf.nn.dropout(norm2, _dropout)

    # 卷积
    conv3 = conv2d('conv3', norm2, _weights['wc3'], _biases['bc3'])
    # 下采样
    pool3 = max_pool('pool3', conv3, k=2)
    # 归一化
    norm3 = norm('norm3', pool3, lsize=4)
    # Dropout
    norm3 = tf.nn.dropout(norm3, _dropout)


    # 全连接层,先把特征图转为向量
    dense1 = tf.reshape(norm3, [-1, _weights['wd1'].get_shape().as_list()[0]])
    dense1 = tf.nn.relu(tf.matmul(dense1, _weights['wd1']) + _biases['bd1'], name='fc1')
    # 全连接层
    dense2 = tf.nn.relu(tf.matmul(dense1, _weights['wd2']) + _biases['bd2'], name='fc2') # Relu activation

    # 网络输出层
    out = tf.matmul(dense2, _weights['out']) + _biases['out']
    return out

# 存储所有的网络参数
weights = {
    'wc1': tf.Variable(tf.random_normal([3, 3, 1, 64])),
    'wc2': tf.Variable(tf.random_normal([3, 3, 64, 128])),
    'wc3': tf.Variable(tf.random_normal([3, 3, 128, 256])),
    'wc4': tf.Variable(tf.random_normal([3, 3, 128, 256])),
    'wc5': tf.Variable(tf.random_normal([3, 3, 128, 256])),
    'wd1': tf.Variable(tf.random_normal([4*4*256, 1024])),    
    'wd2': tf.Variable(tf.random_normal([1024, 1024])),    
    'out': tf.Variable(tf.random_normal([1024, 10]))}


biases = {    'bc1': tf.Variable(tf.random_normal([64])),
	      'bc2': tf.Variable(tf.random_normal([128])),  
	      'bc3': tf.Variable(tf.random_normal([256])),    
	      'bd1': tf.Variable(tf.random_normal([1024])), 
	      'bd2': tf.Variable(tf.random_normal([1024])),    
              'out': tf.Variable(tf.random_normal([n_classes]))}


# 构建模型
pred = alex_net(x, weights, biases, keep_prob)

# 定义损失函数和学习步骤
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

# 测试网络
correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

# 初始化所有的共享变量
init = tf.initialize_all_variables()

# 开启一个训练
with tf.Session() as sess:
    sess.run(init)
    step = 1
    # Keep training until reach max iterations
    while step * batch_size < training_iters:
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # 获取批数据
        sess.run(optimizer, feed_dict={x: batch_xs, y: batch_ys, keep_prob: dropout})
        if step % display_step == 0:
            # 计算精度
            acc = sess.run(accuracy, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
            # 计算损失值
            loss = sess.run(cost, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
            print "Iter " + str(step*batch_size) + ", Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.5f}".format(acc)
        step += 1
    print "Optimization Finished!"
    # 计算测试精度
    print "Testing Accuracy:", sess.run(accuracy, feed_dict={x: mnist.test.images[:256], y: mnist.test.labels[:256], keep_prob: 1.})

方式二:

# coding=utf-8
import tensorflow as tf
from datatime import datatime
import math
import time

batch_size = 32
num_batches = 100


"""
定义一个用来显示网络每一层结构的函数print_actications,展示每一个卷积层或池化层输出tensor的尺寸。
这个函数接受一个tensor作为输入,并显示其名称(t.op.name)和tensor尺寸(t.get_shape.as_list)
"""
def print_activations(t):
    print(t.op.name, ' ', t.get_shape.as_list())

# 设计Alexnet的网络结构

"""
先定义函数inference,它接受images作为输入,返回最后一层pool5(第5个池化层)及parameters(Alexnet所有需要训练的模型参数)。
这个inference函数会很大,包含多个卷积层和池化层。
"""
def inference(images):
    parameters = []
    # 第一层卷积层
    with tf.name_scope('conv1') as scope:
        kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64],
                                                 dtype=tf.float32, stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32),
                             trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(bias, name=scope)
        print_activations(conv1)
        parameters += [kernel, biases]


    # 在第一层之后加上LRN层和最大池化层
    # depth_radius = 4
    lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001/9, beta=0.75, name='lrn1')
    # padding=VALID,取样时不能超过边框
    pool1 = tf.nn.max_pool(lrn1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                           padding='VALID', name='pool1')
    print_activations(pool1)


    # 第二层卷积层
    # 卷积的步长全为1,即扫描全图像素
    with tf.name_scope('conv2'):
        kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(o.o, shape=[192],
                                         dtype=tf.float32), trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, biases]
    print_activations(conv2)


    lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001/9, beta=0.75, name='lrn2')
    pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                           padding='VALID', name='pool2')
    print_activations(pool2)


    # 第三层卷积层,没有添加LRN和最大池化层
    with tf.name_scope('conv3'):
        kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv2, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, shape=[384], dtype=tf.float32),
                                         trainable=True, name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv3 = tf.nn.relu(bias, name=scope)
        print_activations(conv3)
        parameters += [kernel, biases]


    # 第四层卷积层
    with tf.name_scope('conv4') as scope:
        kernel = tf.Vaiable(tf.truncated_normal([3, 3, 384, 256], dtype=tf.float32,
                                                stddev=1e-1), name='weight')
        conv = tf.nn.conv2d(conv3, kernel)
        biases = tf.Variable(tf.constant(0.0, shape=[256],
                                         dtype=tf.float32), name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv4 = tf.nn.relu(bias, name=scope)
        print_activations(conv4)
        parameters += [kernel, biases]


    # 第五层卷积层,后面加一个池化层
    with tf.name_scope('conv5') as scope:
        kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256], dtype=tf.float32,
                                                 stddev=1e-1), name='weights')
        conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
        biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[256]), name='biases')
        bias = tf.nn.bias_add(conv, biases)
        conv5 = tf.nn.relu(bias, name=scope)
        parameters += [kernel, bias]
        print_activations(conv5)


    pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                           padding='VALID', name='pool5')
    print_activations(pool5)

    # 全连接层,先把特征图转为向量
    dense1 = tf.reshape(pool5, [-1, [ 6*6*256, 4096]].get_shape().as_list()[0]]) 

    #6th
    dense1 = tf.nn.relu(tf.matmul(dense1, [ 6*6*256, 4096]) + _biases['bd1'], name='fc1') 
    dense1=tf.nn.dropout(dense1, keep_prob)
    # 全连接层,7th
    dense2 = tf.nn.relu(tf.matmul(dense1, [4096, 4096]) + _biases['bd2'], name='fc2') 
    dense2=tf.nn.dropout(dense1, keep_prob)

    # Relu activation

    # 网络输出层 8th
    out = tf.matmul(dense2, [4096,1024]) + _biases['out']
    

    return out    

pytorch实现:

AlexNet网络详解_第13张图片

 

你可能感兴趣的:(深度学习,深度学习,神经网络)