此文之目的,不在其他,而在加深个人对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 ),具体计算公式如下:
分母中的第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个邻居。经过局部规范化后就得到了的新值。举个例子:
TensorFlow代码实现
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))
本文如有理解偏差,请纠正。