AlexNet网络结构详解及TensorFlow代码实现

        此文之目的,不在其他,而在加深个人对AlexNet之印象。AlexNet来自论文《ImageNet Classification with Deep Convolutional Neural Networks》。论文作者有Alex Krizhevsky,Ilya Sutskever,Geoffrey E. Hinton。有兴趣可查阅原文。

AlexNet网络结构

AlexNet网络原图如下:

为了帮助理解,我们添加了注释(红色部分),如图: 

横向来说,网络总共分为8层(输入层不计算在内),第1层~第5层为卷积层,第6层~第8层为全连接层。纵向来说分为上下两层,对应两个GPU,分上下两层主要是为了通过GPU并行加快计算速度,但是它们又不是完全独立的,在第2层~第3层,两个GPU之间有数据通信,第6层~第8层也有数据通信。考虑到上下两层网络结构相同,且不影响我们对网络结构的理解,后面以下层GPU为主。下面我们逐层说明:

(1)第0层

        也就是输入层,我们输入的是120万张高分辨率的彩色图片(image),图片大小224*224,一张图片的每个像素点pixel是由三个0~255的数组成的,可记为p=(r,g,b),分别对应红(R)、绿(G)、蓝(B)。所以我们可以将一张彩色图片看成是由三个224*224矩阵构成的,图中输入层标注了数字3,代表此意。

(2)第1层

        这层使用了96个大小为11×11×3(长×宽×深)的核(kernel)对输入图片进行卷积运算,每个GPU各分配48个kernel,这正好对应该层的厚度48(如图中表示),kernel的深度为3对应每个输入图片的三个矩阵。实际这一层会得到48*3=144个feature map(一个矩阵,一个kernel生成一个feature map)。kernel的移动步长stride=4,padding=2,输入的224*224大小的图片经过kernel卷积运算后得到一个55*55大小的特征图片(feature map),这个大小由下面这个公式计算:

feature map大小=(原图大小+2*padding-kernel大小)/stride+1=(224+2*2-11)/4+1=55.25=55

右边为小数时,取整数部分。卷积过程如下:

图的左边表示一张照片对应的三个矩阵,中间是深度为3的kernel,假设stride=1,每次移动1步,右边是卷积后的结果。比如第一个元素如下计算:

1*2+0*4+1*7+(-1)*7=2。

这一层经过卷积运算得到55*55大小的特征图片并没有结束,还要经过激活函数处理,通常我们选用

这两类激活函数,但本文选择ReLU函数来处理,ReLU=max(0,x)。原因是训练速度快。如果以上面为例,上面卷积的最终结果同过ReLU计算后如下:

因为每个feature map的值都大于0,所以结果不变。在进入最大值池化层(max pooling)前,对ReLU输出结果做了局部规范化(Local Response Normalization,简记LRU ),具体计算公式如下:

其中 k,n,\alpha ,\beta是超参数,可以调整,本文取值如下: k = 2 , n = 5, α=10^{-4} β = 0. 75。分子中的 a^i_{x,y}是ReLu的输出结果,表示第i个kernel在位置(x,y)处的输出,举个例子,如图:

分母中的a^j_{x,y}第j个kernel在位置(x,y)处的输出,这里的j有所不同,这些j对应的kernel是第i个kernel的邻居。什么是第i个kernel的邻居呢?通俗的说是靠的最近的几个。举个例子,如图(图片从其他博客复制,感谢作者提供):

假设绿色箭头指向的是第i个kernel(大小5*5),n=5,蓝色箭头指向的kernel分别为第i-2,i-1,i+1,i+2个。它们就是第i个kernel的邻居,算上自己,总共5个邻居。经过局部规范化后就得到了a^j_{x,y}的新值b^i_{x,y}。举个例子:

上图只考虑了输入图片的一个矩阵,有5个kernel,计算第3个kernel位置(1,1)位置5的局部规范化的值约为2.958。局部规范化后的值送入该层的最大值池化层。假设上面的ReLU的计算结果经过局部规范化后如下:
我们使用一各大小为3*3的kernel,每次移动2步。最大值池化是指从被kernel覆盖的所有值中取最大值来对上面的结果池化,比如上面这个例子最后得到一个大小1*1的矩阵。也有用均值池化的,均值池化是指使用被kernel覆盖的所有值的均值。比如上面这个例子均值池化如下:
 
到这里第1层终于结果了,该层虽然被称为卷积层,其实做了三个事情,第一是卷积,第二是LRU,第三是池化。本文将池化层放在了卷积层中看作一层,准确来说,池化层应该单独算做一层。
 
(2)第2层
        第1层明白后,第2层就类似了。这里我们主要对图中标注的128做个解释。这一层使用 256个大小为5 × 5 × 48kernel。上下GPU各128个。所以图中标注的128,表示的是kernel当前层kernel个数。kernel的深度48正好对应上一层的kernel个数,后面各层类似。
 
(3)第3层~第5层
        这些层和前面类似,只是有些层没有池化这一步。具体看上面这个图。
 
(4)第6层~第8层
        这三层是全连接层,所谓全连接是指上一层的输出都作为当前层每个神经元的输入。不像卷积层那样,这里就没有kernel的概念了,其中第6层~第7层分别由4096个神经元构成,上下各2048个神经元。第8层有1000个神经元,因为最后是将这120万张图片分成1000个类,所以每个神经元对应一个分类。
        第6层~第7层与其他层还有一个不一样的地方。这两层使用了“dropout”技术。什么是dropout呢?所谓dropout是指我们在训练AlexNet网络模型时,这两层的神经元不必每次都计算在内,可以随机选择一部分参与训练,剩余的暂时剔除掉。比如这个选择比例设置为0.5,对于下面的这个GPU来说,每次从2048个神经元中随机选择1024个神经元参与训练。每个神经元被选中的概率是0.5。
 
 

TensorFlow代码实现

        代码实现前,我们先整理下AlexNet网络和我们本案例中各层使用kernel情况对比,如表格:
 
AlexNet网络层 kernel大小

单个GPU上kernel

或神经元数量

本案例

kernel大小

kernel

或神经元数量

第0层

227× 227×3

(图片大小)

28× 28× 1
第1层 11×11×3 48 11×11×1 96
第2层 5 × 5 × 48 128 5 × 5 × 96 256
第3层 3 × 3 ×128 192 3 × 3 ×256 384
第4层 3 × 3 × 192 192 3 × 3 × 384 384
第5层 3 × 3 × 192 128 3 × 3 × 384 256
第6层 2048 4096
第7层 2048 4096
第8层 1000 10

几点说明:

(1)第0层

AlexNet网络输入的一张彩色图片大小227× 227×3,本案例中输入为一张手写数字黑白图片,大小为28× 28,因为是黑白图片所以只对应一个矩阵,也就是大小为28× 28× 1。

(2)第1层~第7层

本案例中每层kernel大小或者神经元个数都是AlexNet单个GPU的双倍,原因是图标统计的是AlexNet的单个GPU,我们使用单个CPU来模拟两个GPU。

(3)第8层

AlexNet网络最后是将图片分为1000类,本案例是数字0~9,所以对应10个类。

数据集minist文件如下:

CPU/内存/磁盘使用情况,如图:

程序输出结果,如图:
 
上面输出用时十几分钟左右,具体代码如下:
import tensorflow as tf

# 输入数据
from tensorflow.examples.tutorials.mnist import input_data
import time

# 读取数据集
minist = input_data.read_data_sets("G:\minist", one_hot=True)

learning_rate = 0.001
training_iters = 200000
batch_size = 128
display_step = 10

n_input = 28 * 28
n_classes = 10
dropout = 0.5

x = tf.placeholder(tf.float32, [None, n_input])
y = tf.placeholder(tf.float32, [None, n_classes])
keep_prob = tf.placeholder(tf.float32)

weights = {
    'wc1': tf.Variable(tf.random_normal([11, 11, 1, 96])),
    'wc2': tf.Variable(tf.random_normal([5, 5, 96, 256])),
    'wc3': tf.Variable(tf.random_normal([5, 5, 256, 384])),
    'wc4': tf.Variable(tf.random_normal([5, 5, 384, 384])),
    'wc5': tf.Variable(tf.random_normal([3, 3, 384, 256])),
    'wd1': tf.Variable(tf.random_normal([4096, 4096])),
    'wd2': tf.Variable(tf.random_normal([4096, 4096])),
    'out': tf.Variable(tf.random_normal([4096, 10])),
}
biases = {
    'bc1': tf.Variable(tf.random_normal([96])),
    'bc2': tf.Variable(tf.random_normal([256])),
    'bc3': tf.Variable(tf.random_normal([384])),
    'bc4': tf.Variable(tf.random_normal([384])),
    'bc5': tf.Variable(tf.random_normal([256])),
    'bd1': tf.Variable(tf.random_normal([4096])),
    'bd2': tf.Variable(tf.random_normal([4096])),
    'out': tf.Variable(tf.random_normal([n_classes])),
}


def conv2d(level, x, W, b, strides=1):
    """
    定义卷积操作
    :param name:
    :param x:
    :param W:
    :param b:
    :param strides:
    :return:
    """
    x = tf.nn.conv2d(x, W, strides=[1, strides, strides, 1], padding='SAME')
    x = tf.nn.bias_add(x, b)
    x = tf.nn.relu(x, name='conv_' + level)

    return x


def max_pooling2d(level, x, k=2):
    """
    定义最大值池化层操作
    :param name:
    :param x:
    :param k: kernel的高和宽,这里默认为2,
                ksize=[1, k, k, 1],第1个1表示输入图片数量,最后一个1表示通道,比如才是对应3
    :return:
    """

    x = tf.nn.max_pool(x, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME', name='max_pool_' + level)

    return x


def lrn(level, x):
    """
    局部规范化(Local Response Normalization)
    :param name: 操作名称
    :param input: 输入 对应局部规范化公式中a^i_{x,y}
    :return:
    """
    k = 1.0
    n = 5
    alpha = 0.001 / 9.0
    beta = 0.75

    return tf.nn.lrn(x, depth_radius=n, bias=k, alpha=alpha, beta=beta, name='lrn_' + level)


def conv_level(level, x, w, b):
    conv_out = conv2d(level, x, w, b)
    lrn_out = lrn(level, conv_out)
    pool_out = max_pooling2d(level, lrn_out)

    return pool_out


def alex_net(x, weights, biases, dropout):
    x = tf.reshape(x, shape=[-1, 28, 28, 1])
    # 第1层
    level_1_out = conv_level('1', x, weights['wc1'], biases['bc1'])
    # 第2层
    level_2_out = conv_level('2', level_1_out, weights['wc2'], biases['bc2'])
    # 第3层
    conv_3_out = conv2d('3', level_2_out, weights['wc3'], biases['bc3'])
    level_3_out = lrn('3', conv_3_out)
    # 第4层
    conv_4_out = conv2d('4', level_3_out, weights['wc4'], biases['bc4'])
    level_4_out = lrn('4', conv_4_out)
    # 第5层
    level_5_out = conv_level('5', level_4_out, weights['wc5'], biases['bc5'])
    # 第6层,全连接层
    fc1 = tf.reshape(level_5_out, [-1, weights['wd1'].get_shape().as_list()[0]])
    fc1 = tf.add(tf.matmul(fc1, weights['wd1']), biases['bd1'])
    fc1 = tf.nn.relu(fc1)
    fc1 = tf.nn.dropout(fc1, dropout)
    # 第7层,全连接层
    fc2 = tf.reshape(fc1, [-1, weights['wd2'].get_shape().as_list()[0]])
    fc2 = tf.add(tf.matmul(fc2, weights['wd2']), biases['bd2'])
    fc2 = tf.nn.relu(fc2)
    fc2 = tf.nn.dropout(fc2, dropout)

    out = tf.add(tf.matmul(fc2, weights['out']), biases['out'])

    return out


pred = alex_net(x, weights, biases, keep_prob)

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
optimizer = tf.train.AdamOptimizer(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))


def run():
    init = tf.global_variables_initializer()
    with tf.Session() as session:
        session.run(init)
        step = 1
        while step * batch_size < training_iters:
            batch_x, batch_y = minist.train.next_batch(batch_size)
            session.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: dropout})
            if step % display_step == 0:
                loss, acc = session.run([cost, accuracy], feed_dict={x: batch_x,
                                                                     y: batch_y,
                                                                     keep_prob: 1.0})
                print("Iter " + str(step * batch_size) + ",Minibatch Loss= " + \
                      "{:.6f}".format(loss) + ",Training Accuracy= " + \
                      "{:.6f}".format(acc))

            step += 1
        print("Optimization Finished!")
        print('Testing Accuracy:', session.run(accuracy, feed_dict={x: minist.test.images[:256],
                                                                    y: minist.test.images[:256],
                                                                    keep_prob: 1}))


if __name__ == '__main__':
    start_time = time.time()
    run()
    end_time = time.time()
    print('total time: ' + str(end_time - start_time))

本文如有理解偏差,请纠正。

发布了89 篇原创文章 · 获赞 79 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/L_15156024189/article/details/105166638