目录
1. 前言
Alexnet是Alex 在2012年提出的深度卷积神经网络,是LeNet一种更深更宽的版本,是CNN在图像分类上的经典模型。
2. 数据集
ImageNet数据集包含大概22000类150多万带标签的高分辨率图像。本文模型要求固定输入的输入维度,因此对图像采样获得256x256的图像集。对于每张图片,先把短边调整为256,然后取中间的256x256区域。预处理——训练集和测试集,都减去训练集的均值图像。
数据归一化和标准化
3. 结构
3.1 ReLU非线性函数(ReLU Nonlinearity)
一般神经元的**函数会选择sigmoid函数或者tanh函数,然而Alex发现在训练时间的梯度衰减方面,这些非线性饱和函数比非线性非饱和函数慢很多。在AlexNet中用的非线性非饱和函数f(x)=max(0, x),ReLU。实验表明,ReLU比tanh快6倍。
sigmoid函数缺点和ReLU函数优点
3.2 Training on Multiple GPUs
GTX 580 GPU 3G memory,每个GPU放置一半的神经元,GPU之间只在特定的层交流。这种模式允许我们精确调整连接数,使计算量可控。
3.3 LRN(局部响应归一化)
侧抑制,增加泛化能力。ReLU本来不需要对输入进行标准化,本文发现LRN能提高性能。
其中a代表在feature map中第i个卷积核(x,y)坐标经过了ReLU**函数的输出,n表示相邻的几个卷积核。N表示这一层总的卷积核数量。k, n, α和β是hyper-parameters,他们的值是在验证集上实验得到的,本文k = 2,n = 5,α = 0.0001,β = 0.75。
3.4 Overlapping Pooling:
使用重叠的最大池化,以前在卷积神经网络中大部分都采用平均池化,在AlexNet中都是使用最大池化,最大池化可以避免平均池化的模糊化效果。重叠的最大池化是指卷积核的尺寸要大于步长,这样池化层的输出之间会有重叠和覆盖,提升特征的丰富性。在AlexNet中使用的卷积核大小为3×3,横向和纵向的步长都为2。
3.5 整体结构
3.5.1 Alexnet结构
整个Alexnet具有8个需要训练参数的层(不包括有max pool以及LRN层),前面5个是卷积层,后面的3个是全链接层。如上图。最后的一层是1000类的输出的softmax层,是作为最后分类输出的。LRN出现在第一和第二个卷积层之后,max pool出现在两个LRN层以及最后一个卷积层之后。而ReLU均出现在这8层每一层的后面。Alexnet在训练时候分到两个GPU加以训练,两个GPU除了在第3层卷积层进行数据通信外,其他的卷积操作(提取特征)都是独立进行。
3.5.2 各层训练参数的计算
3.5.3 参数计算公式
4. 减少过度拟合
4.1 数据增益
降低图像数据过拟合的最简单常见的方法就是利用标签转换人为地增大数据集。本文采取两种不同的数据增强方式,这两种方式只需要少量的计算就可以从原图中产生转换图像,因此转换图像不需要存入磁盘。本文中利用GPU训练先前一批图像的同时,使用CPU运行Python代码生成转换图像。因此这些数据增强方法实际上是不用消耗计算资源的。
- 第一种数据增强的形式包括生成平移图像和水平翻转图像。做法就是从256x256的图像中提取随机的224x224大小的块(以及它们的水平翻转),然后基于这些提取的块训练网络。这个让我们的训练集增大了2048倍((256-224)2*2=2048),尽管产生的这些训练样本显然是高度相互依赖的。如果不使用这个方法,本文的网络会有大量的过拟合,这将会迫使我们使用更小的网络。在测试时,网络通过提取5个224x224块(四个边角块和一个中心块)以及它们的水平翻转(因此共十个块)做预测,然后网络的softmax层对这十个块做出的预测取均值。
- 第二种数据增强的形式包括改变训练图像的RGB通道的强度。特别的,本文对整个ImageNet训练集的RGB像素值进行了PCA。对每一幅训练图像,本文加上多倍的主成分,倍数的值为相应的特征值乘以一个均值为0标准差为0.1的高斯函数产生的随机变量。因此对每一个RGB图像像素Ixy=[IRxy,IGxy,IBxy]T加上如下的量:
[P1, P2, P3][α1λ1,α2λ2,α3λ3]T。这里Pi,λi分别是RGB像素值的3x3协方差矩阵的第i个特征向量和特征值,αi是上述的随机变量。每一个αi的值对一幅特定的训练图像的所有像素是不变的,直到这幅图像再次用于训练,此时才又赋予αi新的值。这个方案得到了自然图像的一个重要的性质,也就是,改变光照的颜色和强度,目标的特性是不变的。这个方案将top-1错误率降低了1%。
4.2 Dropout
结合多个模型的预测值是减少错误的有效方法,但是对于训练时间用好几天的大型神经网络太耗费时间。Dropout是有效的模型集成学习方法,具有0.5的概率讲隐藏神经元设置输出为0。运用了这种机制的神经元不会干扰前向传递也不影响后续操作。因此当有输入的时候,神经网络采样不用的结构,但是这些结构都共享一个权重。这就减少了神经元适应的复杂性。测试时,用0.5的概率随机失活神经元。dropout减少了过拟合,也使收敛迭代次数增加一倍。
5. Alexnet实现
from datetime import datetime
import math, time
import tensorflow as tf
batch_size = 32
num_bathes = 100
"获取tensor信息"
def print_tensor_info(tensor):
print("tensor name:", tensor.op.name, "-tensor shape:", tensor.get_shape().as_list())
'''
计算每次迭代消耗时间
session:tensorflow的session
target:需要评测的运算算子
info_string:测试的名称
'''
def time_tensorflow_run(session, target, info_string):
# 前10次迭代不计入时间消耗
num_step_burn_in = 10
total_duration = 0.0
total_duration_squared = 0.0
for i in range(num_bathes + num_step_burn_in):
start_time = time.time()
_ = session.run(target)
duration = time.time() - start_time
if i >= num_step_burn_in:
if not i % 10:
print("%s:step %d,duration=%.3f" % (datetime.now(), i - num_step_burn_in, duration))
total_duration += duration
total_duration_squared += duration * duration
# 计算消耗时间的平均差
mn = total_duration / num_bathes
# 计算消耗时间的标准差
vr = total_duration_squared / num_bathes - mn * mn
std = math.sqrt(vr)
print("%s:%s across %d steps,%.3f +/- %.3f sec / batch" % (datetime.now(), info_string, num_bathes, mn, std))
# 主函数
def run_benchmark():
with tf.Graph().as_default():
image_size = 224
# 以高斯分布产生一些图片
images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype=tf.float32, stddev=0.1))
output, parameters = inference(images)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
time_tensorflow_run(sess, output, "Forward")
objective = tf.nn.l2_loss(output)
grad = tf.gradients(objective, parameters)
time_tensorflow_run(sess, grad, "Forward-backward")
def inference(images):
# 定义参数
parameters = []
# 第一层卷积层
with tf.name_scope("conv1") as scope:
"第一层卷积一共有64个11*11*3大小的卷积核,横向和纵向的步长都为4,初始化权重使用截断的正态分布进行初始化,经过ReLU" \
"函数之后,再通过LRN层,再使用重叠的最大池化"
# 设置卷积核11*11,3个通道,64个卷积核
kernel1 = tf.Variable(tf.truncated_normal([11, 11, 3, 64], mean=0, stddev=0.1, dtype=tf.float32),
name="weights")
# 卷积 卷积的横向和竖向步长都为4
"input是一个张量[batch, in_height, in_width, in_channels]和一个过滤器/内核张量[filter_heigth, filter_width, in_channels, out_channels]后" \
"执行以下操作:展平filter为一个形状为[filter_height*filter_width*in_channels, ouput_channels]的二维矩阵,从input中按照filter大小提取图片子集" \
"形成一个大小为[batch, out_height, out_width, filter_height*filter_width*in_channels]的虚拟张量。循环每个图片子集,右乘filter矩阵。"
conv = tf.nn.conv2d(images, kernel1, [1, 4, 4, 1], padding='SAME')
# 初始化偏置
biases = tf.Variable(tf.constant(0, shape=[64], dtype=tf.float32), trainable=True, name="biases")
bias = tf.nn.bias_add(conv, biases)
# ReLU**函数
conv1 = tf.nn.relu(bias, name=scope)
# 输出该层的信息
print_tensor_info(conv1)
# 统计参数
parameters += [kernel1, biases]
# lrn处理
lrn1 = tf.nn.lrn(conv1, 4, bias=1, alpha=1e-3 / 9, beta=0.75, name="lrn1")
# 最大池化
"输入:需要池化的feature map, [batch, height, width, channels];ksize:池化窗口的大小,batch和channels上一般不做池化,取1;" \
"strides:步长; padding; name"
pool1 = tf.nn.max_pool(lrn1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding="VALID", name="pool1")
# 输出该层信息
print_tensor_info(pool1)
# 第二层卷积层
"第二层卷积层的结构和第一层大致相似,卷积层的大小由11*11变成了5*5,有192个卷积核"
with tf.name_scope("conv2") as scope:
# 初始化权重
kernel2 = tf.Variable(tf.truncated_normal([5, 5, 64, 192], dtype=tf.float32, stddev=0.1), name="weights")
conv = tf.nn.conv2d(pool1, kernel2, [1, 1, 1, 1], padding="SAME")
# 初始化偏置
biases = tf.Variable(tf.constant(0, dtype=tf.float32, shape=[192]), trainable=True, name="biases")
bias = tf.nn.bias_add(conv, biases)
# ReLU**
conv2 = tf.nn.relu(bias, name=scope)
print_tensor_info(conv2)
parameters += [kernel2, biases]
# LRN
lrn2 = tf.nn.lrn(conv2, 4, 1.0, alpha=1e-3 / 9, beta=0.75, name="lrn2")
# 最大池化
pool2 = tf.nn.max_pool(lrn2, [1, 3, 3, 1], [1, 2, 2, 1], padding="VALID", name="pool2")
print_tensor_info(pool2)
# 第三层卷积层
"第三层卷积层卷积核的个数是第二层的一倍,第三层卷积层没有使用LRN层和最大池化"
with tf.name_scope("conv3") as scope:
# 初始化权重
kernel3 = tf.Variable(tf.truncated_normal([3, 3, 192, 384], dtype=tf.float32, stddev=0.1), name="weights")
conv = tf.nn.conv2d(pool2, kernel3, strides=[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)
# ReLU
conv3 = tf.nn.relu(bias, name=scope)
parameters += [kernel3, biases]
print_tensor_info(conv3)
# 第四层卷积层
with tf.name_scope("conv4") as scope:
# 初始化权重
kernel4 = tf.Variable(tf.truncated_normal([3, 3, 384, 256], stddev=0.1, dtype=tf.float32), name="weights")
# 卷积
conv = tf.nn.conv2d(conv3, kernel4, strides=[1, 1, 1, 1], padding="SAME")
biases = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[256]), trainable=True, name="biases")
bias = tf.nn.bias_add(conv, biases)
# RELU**
conv4 = tf.nn.relu(bias, name=scope)
parameters += [kernel4, biases]
print_tensor_info(conv4)
# 第五层卷积层
with tf.name_scope("conv5") as scope:
# 初始化权重
kernel5 = tf.Variable(tf.truncated_normal([3, 3, 256, 256], stddev=0.1, dtype=tf.float32), name="weights")
conv = tf.nn.conv2d(conv4, kernel5, strides=[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)
# ReLU
conv5 = tf.nn.relu(bias)
parameters += [kernel5, biases]
# 最大池化
pool5 = tf.nn.max_pool(conv5, [1, 3, 3, 1], [1, 2, 2, 1], padding="VALID", name="pool5")
print_tensor_info(pool5)
# 第六层全连接层
pool5 = tf.reshape(pool5, (-1, 6 * 6 * 256))
weight6 = tf.Variable(tf.truncated_normal([6 * 6 * 256, 4096], stddev=0.1, dtype=tf.float32), name="weight6")
ful_bias1 = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[4096]), name="ful_bias1")
ful_con1 = tf.nn.relu(tf.add(tf.matmul(pool5, weight6), ful_bias1))
# 第七层全连接层
weight7 = tf.Variable(tf.truncated_normal([4096, 4096], stddev=0.1, dtype=tf.float32), name="weight7")
ful_bias2 = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[4096]), name="ful_bias2")
ful_con2 = tf.nn.relu(tf.matmul(ful_con1, weight7)+ful_bias2)
# 第八层全连接层
weight8 = tf.Variable(tf.truncated_normal([4096, 1000], stddev=0.1, dtype=tf.float32), name="weight8")
ful_bias3 = tf.Variable(tf.constant(0.0, dtype=tf.float32, shape=[1000]), name="ful_bias3")
ful_con3 = tf.nn.relu(tf.add(tf.matmul(ful_con2, weight8), ful_bias3))
# softmax层
weight9 = tf.Variable(tf.truncated_normal([1000, 10], stddev=0.1), dtype=tf.float32, name="weight9")
bias9 = tf.Variable(tf.constant(0.0, shape=[10], dtype=tf.float32, name="bias9"))
output_softmax = tf.nn.softmax(tf.matmul(ful_con3, weight9) + bias9)
return output_softmax, parameters
if __name__ == '__main__':
run_benchmark()
参考链接
[1] zyc博客:https://yuchaozheng.github.io/2018/10/23/Alexnet/#21-alexnet结构
[2] 参数计算详细:https://blog.csdn.net/u012679707/article/details/80793916
[3] 论文翻译:https://blog.csdn.net/qianqing13579/article/details/71381016
[4] Alexnet详细解读:https://blog.csdn.net/qq_24695385/article/details/80368618
[5] Alexnet各层实现:https://blog.csdn.net/sinat_29957455/article/details/80657517
[6]结构详细:https://blog.csdn.net/taoyanqi8932/article/details/71081390