简介:Hinton的学生Alex Krizhevsky提出了深度卷积神经网络模型AlexNet,它可算是LeNet的一种更深更宽的版本。
包含的技术点:
1、使用了ReLU作为CNN的激活函数。解决了Sigmoid在网络较深时的梯度弥散问题。
2、使用Dropout随机忽略一部分神经元,以避免模型过拟合。在最后几个全连接层使用了Dropout。
3、使用重叠的最大池化,避免平均池化的模糊化效果。并让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。
4、提出了LRN层,对局部神经元的活动创建竞争机制,使响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强模型的泛化能力。
5、使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。
6、数据增强,随机地从256x256的原始图像中,截取224x224大小的区域。
网络小结:整个AlexNet有8个需要训练参数的层,前5层为卷积层,后3层为全连接层。最后一层有1000类输出的Softmax层用作分类。LRN层出现在第1个及第2个卷积层后,而最大池化层出现在两个LRN层及最后一个卷积层后,ReLU激活函数则应用在这8层每一层的后面。
AlexNet每层的超参数如下图所示。其中输入图片尺寸为224x224,第一个卷积层使用了较大的卷积核尺寸11x11,步长为4,有96个卷积核。紧接着一个LRN层,然后是一个3x3的最大池化层,步长为2。随后的卷积核都较小,都是5x5或3x3的大小,且步长为1。因此,卷积层通过较小的参数量提取有效的特征。
这里建立一个完整的AlexNet卷积神经网络,然后对它每个batch的前馈计算和反馈计算的速度进行测试。使用随机图片数据来计算每轮前馈、反馈的平均耗时。
from datetime import datetime
import math
import time
import tensorflow as tf
'''
AlexNet卷积神经网络进步:
(1)用Relu激活函数解决了原先Softmax的梯度弥散问题
(2)使用Dropout一定程度上避免了过拟合
(3)池化采用重叠最大池化
(4)使用LRN层对局部神经元建立竞争机制
(5)使用CUDA加速深度卷积网络的训练
(6)数据增强,更多元的数据输入防止CNN陷入过拟合
'''
batch_size = 32
num_batches = 100
# 打印数据状态函数,展示每个卷积层或池化层输出tensor的尺寸
# 接受一个tensor作为输入
def print_activations(t):
# 显示名称和尺寸
print(t.op.name, ' ', t.get_shape().as_list())
def inference(images):
# 用于存储卷积核数据和偏值数据
parameters = []
# 第一层卷积层
# 通过下面一行代码,可将scope内生成的Variable自动命名为conv1/xxx,便于区分不同卷积层之间的组件
with tf.name_scope('conv1') as scope:
# 卷积核大小为11*11,通道数为3,数量为64
kernel = tf.Variable(tf.truncated_normal([11, 11, 3, 64],
stddev=1e-1, dtype=tf.float32, name='weights'))
# 卷积步长为4,padding值为SAME
conv = tf.nn.conv2d(images, kernel, [1, 4, 4, 1], padding='SAME')
# 偏值64个
biases = tf.Variable(tf.constant(0.0, shape=[64], dtype=tf.float32,
name='biaes'))
# 卷积操作后,加上偏值
bias = tf.nn.bias_add(conv, biases)
# 用relu激活函数
conv1 = tf.nn.relu(bias, name=scope)
# 打印卷积数据状态
print_activations(conv1)
# 将卷积核和偏值数据存入参量集合
parameters += [kernel, biases]
# 使用LRN层对卷积数据进行处理
lrn1 = tf.nn.lrn(conv1, 4, bias=1.0, alpha=0.001 / 9, beta=0.75, name='lrn1')
# 采用3*3最大重叠池化,步长为2,小于池化窗口本身大小,所以会产生重叠
pool1 = tf.nn.max_pool(lrn1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='VALID', name='pool1')
# 打印池化数据状态
print_activations(pool1)
# 第二层卷积
with tf.name_scope('conv2') as scope:
# 卷积核大小为5*5,数量为192,上一层通道数为64
kernel = tf.Variable(tf.truncated_normal([5, 5, 64, 192],
stddev=1e-1, dtype=tf.float32, name='weights'))
# 卷积操作步长为1,padding值为SAME
conv = tf.nn.conv2d(pool1, kernel, [1, 1, 1, 1], padding='SAME')
# 偏值192个
biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[192], name='biaes'))
# 将卷积数据与偏值相加
bias = tf.nn.bias_add(conv, biases)
# 采用relu激活函数
conv2 = tf.nn.relu(bias, name=scope)
# 将本层卷积核和偏值数据存入参量集合
parameters += [kernel, biases]
# 打印当前卷积数据状态
print_activations(conv2)
# 使用LRN处理卷积数据
lrn2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9, beta=0.75, name='lrn2')
# 采用3*3最大重叠池化,步长为2,小于池化窗口本身大小,所以会产生重叠
pool2 = tf.nn.max_pool(lrn2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='VALID', name='pool2')
# 打印池化数据状态
print_activations(pool2)
# 第三层卷积
with tf.name_scope('conv3') as scope:
# 采用3*3卷积核,共384个,输入通道为192
kernel = tf.Variable(tf.truncated_normal([3, 3, 192, 384],
stddev=1e-1, dtype=tf.float32, name='weights'))
# 卷积操作步长为1,padding值为SAME
conv = tf.nn.conv2d(pool2, kernel, [1, 1, 1, 1], padding='SAME')
# 偏值384个
biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[384], name='biaes'))
# 偏值与卷积数据相加
bias = tf.nn.bias_add(conv, biases)
# 使用relu函数激活
conv3 = tf.nn.relu(bias, name=scope)
# 将卷积核和偏值数据存入参量集合
parameters += [kernel, biases]
# 打印当前数据状态
print_activations(conv3)
# 第四层卷积
with tf.name_scope('conv4') as scope:
# 卷积核大小为3*3,256个,输入通道数为384
kernel = tf.Variable(tf.truncated_normal([3, 3, 384, 256],
stddev=1e-1, dtype=tf.float32, name='weights'))
# 卷积步长为1,padding值为SAME
conv = tf.nn.conv2d(conv3, kernel, [1, 1, 1, 1], padding='SAME')
# 偏值256个
biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[256],
name='biaes'))
# 偏值与卷积数据相加
bias = tf.nn.bias_add(conv, biases)
# 激活函数relu
conv4 = tf.nn.relu(bias, name=scope)
# 将卷积核数据和偏值数据存入参量集合
parameters += [kernel, biases]
# 打印当前卷积数据状态
print_activations(conv4)
# 第五层卷积
with tf.name_scope('conv5') as scope:
# 卷积核大小为3*3,256个,通道数为256个
kernel = tf.Variable(tf.truncated_normal([3, 3, 256, 256],
stddev=1e-1, dtype=tf.float32, name='weights'))
# 卷积步长为1,padding值为SAME
conv = tf.nn.conv2d(conv4, kernel, [1, 1, 1, 1], padding='SAME')
# 偏值256个
biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[256],
name='biaes'))
# 偏值加上卷积数据
bias = tf.nn.bias_add(conv, biases)
# 用relu函数激活
conv5 = tf.nn.relu(bias, name=scope)
# 将卷积核数据和偏值数据存入参量集合
parameters += [kernel, biases]
# 打印当前卷积数据状态
print_activations(conv5)
# 采用3*3最大重叠池化,步长为2,小于池化窗口本身大小,所以会产生重叠
pool5 = tf.nn.max_pool(conv5, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='VALID', name='pool5')
# 打印池化数据状态
print_activations(pool5)
# 返回最后池化结果和参量记录
return pool5, parameters
# 每轮计算时间的评估函数[Session对象,需要评测的运算算子,测试名称]
def time_tensorflow_run(session, target, info_string):
# 预热轮数,给程序热身,头几轮迭代有显存加载、cache命中问题,可跳过
num_steps_burn_in = 10
# 记录总时间
total_duration = 0.0
# 记录总时间平方和用来计算方差
total_duration_squared = 0.0
for i in range(num_batches + num_steps_burn_in):
# 记录开始时间
start_time = time.time()
# 运行图
_ = session.run(target)
# 计算运算时间
duration = time.time() - start_time
# 如果完成10次预热
if i >= num_steps_burn_in:
# 每10次迭代打印一次当前时间,迭代次数,计算时间
if not i % 10:
print('%s:step %d,duration = %.3f' % (datetime.now(), i - num_steps_burn_in, duration))
# 记录总计算时间
total_duration += duration
# 累加子时间平方
total_duration_squared += duration * duration
# 计算每个批所用时间
mn = total_duration / num_batches
# 总用时平方和均值,减总平均用时平方
vr = total_duration_squared / num_batches - mn * mn
# 开平方
sd = math.sqrt(vr)
# 打印耗时评估数据
print('%s:%s across %d steps,%.3f +/- %.3f sec / batch' %
(datetime.now(), info_string, num_batches, mn, sd))
# 运行函数
def run_benchmark():
# 初始化图
with tf.Graph().as_default():
# 图片大小为224*224*3
image_size = 224
# 随机初始化图片,batch_size是每轮迭代的样本数
images = tf.Variable(tf.random_normal([batch_size,
image_size,
image_size, 3],
stddev=1e-1,
dtype=tf.float32))
# 获取卷积结果和参量记录
pool5, parameters = inference(images)
# 初始化全局变量
init = tf.global_variables_initializer()
# 初始化Session对象
sess = tf.Session()
# 运行图
sess.run(init)
# 评估pool5运算时间
time_tensorflow_run(sess, pool5, "Forword")
# L2正则化pool5数据
objective = tf.nn.l2_loss(pool5)
# 梯度下降数据
grad = tf.gradients(objective, parameters)
# 评估梯度下降运算时间
time_tensorflow_run(sess, grad, "Forword-backward")
run_benchmark()
程序显示的,AlexNet的网络结构:
显示的是,forward计算的时间。去除LRN层,运算时间会快3倍多。
显示的是backward运算的时间。去除LRN层,速度也会快3倍多。且无论是否有LRN层,backward运算的耗时大约是forward耗时的三倍。
CNN的训练通常需要过很多遍数据,进行大量的迭代。所以CNN的主要瓶颈还是在训练。目前TensorFlow已经支持在IOS、Android系统中运行,所以在手机上使用CPU进行人脸识别或图片分类很方便。
AlexNet为神经网络和深度学习正名,以绝对优势拿下了ILSVRC 2012冠军,在数据集上可达到16.4%的错误率。