相关介绍
VGGNet是牛津大学计算机视觉组(Visual Geometry Group)和Google DeepMind公司的研究员一起研发的的深度卷积神经网络。
他们研究了卷积网络深度在大规模的图像识别环境下对准确性的影响。他们的主要贡献是使用非常小的(3×3)卷积滤波器架构对网络深度的增加进行了全面评估,这表明通过将深度推到16-19加权层可以实现对现有技术配置的显著改进。这些发现是他们的ImageNet Challenge 2014提交的基础,他们的团队在定位和分类过程中分别获得了第一名和第二名。VGG对于其他数据集泛化的很好,在其它数据集上取得了最好的结果。
1.VGGNet 探索的是神经网络的深度(depth)与其性能之间的关系。
VGGNet论文中全部使用了3´3的卷积核和2´2的池化核,通过不断加深网络结构来进行性能对比,最终得出较好的深层网络结构。牛津大学计算机视觉几何组对11-19层的网络都进行了相近的性能测试,根据网络深度的不同以及是否使用LRN,VGGNet可以分为A-E6个级别.各级网络结构如下图所示:
尽管A-E对网络逐步加深了,但是网络的参数量并没有显著增加,因为最后的3个全连层占据了大量的参数。在6个级别的VVGNet中,全连层都是相同的。
卷积层的参数共享和局部连接对降低参数数量做出了巨大贡献,但是由于卷积操作和池化操作的运算过程比较复杂,所以训练中比较耗时的部分是卷积层。
2.VGG结构全部都采用较小的卷积核(3×3,部分1×1)
在VGG出现之前的深度网络,比如ZFNet或Overfeat普遍都采用了7×7和11×11的卷积核。
如何选择卷积核的大小?越大越好还是越小越好?
答案是小而深,单独较小的卷积核也是不好的,只有堆叠很多小的卷积核,模型的性能才会提升。
为什么选择小而深的卷积核(即堆叠使用3×3的卷积核)
VGGNet相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,5x5)。
原因:1.两个3´3的卷积层串联相当于1个5´5的卷积层,即串联后一个像素感受野大小为5´5。而3个3´3的卷积层串联的效果则相当于1个7´7的卷积层。
2.除此之外,2个串联的3´3的卷积层,拥有比1个5´5的卷积层更少的参数量.
3.最重要的是,2个3´3的卷积层拥有比1个5´5的卷积层更多的非线性变换(前者可以使用2次ReLU激活函数,而后者只有1次),使得CNN对特征的学习能力更强。
如下图
3. 在训练与测试时进行数据增强处理(Multi-scale:多尺寸)
Multi-scale方法(多尺寸图片数据)
大致过程:先将原始图片缩放到不同尺寸,然后再将得到的图片进行224* 224的随机裁剪
初始对原始图片进行裁剪时,原始图片的最小边不宜过小,这样的话,裁剪到224x224的时候,就相当于几乎覆盖了整个图片,这样对原始图片进行不同的随机 裁剪得到的图片就基本上没差别,就失去了增加数据集的意义,但同时也不宜过大,这样的话,裁剪到的图片只含有目标的一小部分,也不是很好。 针对上述裁剪的问题,提出的两种解决办法:
(1) 固定最小边的尺寸为256
(2) 最小边随机从[256,512]的确定范围内进行抽样,这样原始图片尺寸不一,有利于训练,这个方法叫做尺度抖动scal jittering,有利于训练集增强。 训练时运用大量的裁剪图片有利于提升识别精确率。
单尺度评估
测试图像大小设置为单一固定的最小边,训练图像使用两种裁剪方式进行对比(是否进行尺寸抖动)
首先,我们注意到,使用局部响应归一化(A-LRN网络)对模型A没有改善。因此,我们在较深的架构(B-E)中不采用归一化。
第二,我们观察到分类误差随着ConvNet深度的增加而减小:从A中的11层到E中的19层。值得注意的是,尽管深度相同,配置C(包含三个1×1卷积层)比在整个网络层中使用3×3卷积的配置D更差。这表明,虽然额外的非线性确实有帮助(C优于B),但也可以通过使用具有非平凡感受野(D比C好)的卷积滤波器来捕获空间上下文。当深度达到19层时,我们架构的错误率饱和,但更深的模型可能有益于较大的数据集。 三,我们还将网络B与具有5×5卷积层的浅层网络进行了比较,浅层网络可以通过用单个5×5卷积层替换B中每对3×3卷积层得到。测量的浅层网络top-1错误率比网络B的top-1错误率高7%,这证实了具有小滤波器的深层网络优于具有较大滤波器的浅层网络。
最后,训练时的尺度抖动得到了与固定最小边的图像训练相比更好的结果,即使在测试时使用单尺度。这证实了通过尺度抖动进行的训练集增强确实有助于捕获多尺度图像统计。
多尺度评估
在单尺度上评估ConvNet模型后,我们现在评估测试时尺度抖动的影响。考虑到训练和测试尺度之间的巨大差异会导致性能下降,用固定S训练的模型在三个测试图像尺度上进行了评估。同时,训练时的尺度抖动允许网络在测试时应用于更广的尺度范围,所以用变量训练的模型在更大的尺寸范围上进行评估。表4中给出的结果表明,测试时的尺度抖动导致了更好的性能
可以看到结果稍好于前者测试图片采用单一尺寸的效果。
最后将多个模型进行合并进一步得到了更好的效果,并在ILSVRC比赛上拿到了第二的成绩。
新技术点:
①LRN层作用不大,还耗时,抛弃。
②网络越深,效果越好。
③卷积核用较小的卷积核,比如3*3。
import tensorflow as tf
from datetime import datetime
import math
import time
batch_size = 12
num_batches = 100
# 定义卷积操作
def conv_op(input, name, kernel_h, kernel_w, num_out, step_h, step_w, para):
# num_in是输入的深度,这个参数被用来确定过滤器的输入通道数
num_in = input.get_shape()[-1].value
with tf.name_scope(name) as scope:
kernel = tf.get_variable(scope + "w", shape=[kernel_h, kernel_w, num_in, num_out],
dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer_conv2d())
conv = tf.nn.conv2d(input, kernel, (1, step_h, step_w, 1), padding="SAME")
biases = tf.Variable(tf.constant(0.0, shape=[num_out], dtype=tf.float32),
trainable=True, name="b")
# 计算relu后的激活值
activation = tf.nn.relu(tf.nn.bias_add(conv, biases), name=scope)
para += [kernel, biases]
return activation
# 定义全连操作
def fc_op(input, name, num_out, para):
# num_in为输入单元的数量
num_in = input.get_shape()[-1].value
with tf.name_scope(name) as scope:
weights = tf.get_variable(scope + "w", shape=[num_in, num_out], dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer())
biases = tf.Variable(tf.constant(0.1, shape=[num_out], dtype=tf.float32), name="b")
# tf.nn.relu_layer()函数会同时完成矩阵乘法以加和偏置项并计算relu激活值
# 这是分步编程的良好替代
activation = tf.nn.relu_layer(input, weights, biases)
para += [weights, biases]
return activation
# 定义前向传播的计算过程,input参数的大小为224x224x3,也就是输入的模拟图片数据
def inference_op(input, keep_prob):
parameters = []
# 第一段卷积,输出大小为112x112x64(省略了第一个batch_size参数)
conv1_1 = conv_op(input, name="conv1_1", kernel_h=3, kernel_w=3, num_out=64,
step_h=1, step_w=1, para=parameters)
conv1_2 = conv_op(conv1_1, name="conv1_2", kernel_h=3, kernel_w=3, num_out=64,
step_h=1, step_w=1, para=parameters)
pool1 = tf.nn.max_pool(conv1_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pool1")
print(pool1.op.name, ' ', pool1.get_shape().as_list())
# 第二段卷积,输出大小为56x56x128(省略了第一个batch_size参数)
conv2_1 = conv_op(pool1, name="conv2_1", kernel_h=3, kernel_w=3, num_out=128,
step_h=1, step_w=1, para=parameters)
conv2_2 = conv_op(conv2_1, name="conv2_2", kernel_h=3, kernel_w=3, num_out=128,
step_h=1, step_w=1, para=parameters)
pool2 = tf.nn.max_pool(conv2_2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pool2")
print(pool2.op.name, ' ', pool2.get_shape().as_list())
# 第三段卷积,输出大小为28x28x256(省略了第一个batch_size参数)
conv3_1 = conv_op(pool2, name="conv3_1", kernel_h=3, kernel_w=3, num_out=256,
step_h=1, step_w=1, para=parameters)
conv3_2 = conv_op(conv3_1, name="conv3_2", kernel_h=3, kernel_w=3, num_out=256,
step_h=1, step_w=1, para=parameters)
conv3_3 = conv_op(conv3_2, name="conv3_3", kernel_h=3, kernel_w=3, num_out=256,
step_h=1, step_w=1, para=parameters)
pool3 = tf.nn.max_pool(conv3_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pool3")
print(pool2.op.name, ' ', pool2.get_shape().as_list())
# 第四段卷积,输出大小为14x14x512(省略了第一个batch_size参数)
conv4_1 = conv_op(pool3, name="conv4_1", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
conv4_2 = conv_op(conv4_1, name="conv4_2", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
conv4_3 = conv_op(conv4_2, name="conv4_3", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
pool4 = tf.nn.max_pool(conv4_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pool4")
print(pool4.op.name, ' ', pool4.get_shape().as_list())
# 第五段卷积,输出大小为7x7x512(省略了第一个batch_size参数)
conv5_1 = conv_op(pool4, name="conv5_1", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
conv5_2 = conv_op(conv5_1, name="conv5_2", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
conv5_3 = conv_op(conv5_2, name="conv5_3", kernel_h=3, kernel_w=3, num_out=512,
step_h=1, step_w=1, para=parameters)
pool5 = tf.nn.max_pool(conv5_3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pool5")
print(pool5.op.name, ' ', pool5.get_shape().as_list())
# pool5的结果汇总为一个向量的形式
pool_shape = pool5.get_shape().as_list()
flattened_shape = pool_shape[1] * pool_shape[2] * pool_shape[3]
reshped = tf.reshape(pool5, [-1, flattened_shape], name="reshped")
# 第一个全连层
fc_6 = fc_op(reshped, name="fc6", num_out=4096, para=parameters)
fc_6_drop = tf.nn.dropout(fc_6, keep_prob, name="fc6_drop")
# 第二个全连层
fc_7 = fc_op(fc_6_drop, name="fc7", num_out=4096, para=parameters)
fc_7_drop = tf.nn.dropout(fc_7, keep_prob, name="fc7_drop")
# 第三个全连层及softmax层
fc_8 = fc_op(fc_7_drop, name="fc8", num_out=1000, para=parameters)
softmax = tf.nn.softmax(fc_8)
# predictions模拟了通过argmax得到预测结果
predictions = tf.argmax(softmax, 1)
return predictions, softmax, fc_8, parameters
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=1e-1))
# Dropout的keep_prob会根据前向传播或者反向传播而有所不同,在前向传播时,
# keep_prob=1.0,在反向传播时keep_prob=0.5
keep_prob = tf.placeholder(tf.float32)
# 为当前计算图添加前向传播过程
predictions, softmax, fc_8, parameters = inference_op(images, keep_prob)
init_op = tf.global_variables_initializer()
# 使用BFC算法确定GPU内存最佳分配策略
config = tf.ConfigProto()
config.gpu_options.allocator_type = "BFC"
with tf.Session(config=config) as sess:
sess.run(init_op)
num_steps_burn_in = 10
total_dura = 0.0
total_dura_squared = 0.0
back_total_dura = 0.0
back_total_dura_squared = 0.0
# 运行前向传播的测试过程
for i in range(num_batches + num_steps_burn_in):
start_time = time.time()
_ = sess.run(predictions, feed_dict={keep_prob: 1.0})
duration = time.time() - start_time
if i >= num_steps_burn_in:
if i % 10 == 0:
print("%s: step %d, duration = %.3f" %
(datetime.now(), i - num_steps_burn_in, duration))
total_dura += duration
total_dura_squared += duration * duration
average_time = total_dura / num_batches
# 打印前向传播的运算时间信息
print("%s: Forward across %d steps, %.3f +/- %.3f sec / batch" %
(datetime.now(), num_batches, average_time,
math.sqrt(total_dura_squared / num_batches - average_time * average_time)))
# 定义求解梯度的操作
grad = tf.gradients(tf.nn.l2_loss(fc_8), parameters)
# 运行反向传播测试过程
for i in range(num_batches + num_steps_burn_in):
start_time = time.time()
_ = sess.run(grad, feed_dict={keep_prob: 0.5})
duration = time.time() - start_time
if i >= num_steps_burn_in:
if i % 10 == 0:
print("%s: step %d, duration = %.3f" %
(datetime.now(), i - num_steps_burn_in, duration))
back_total_dura += duration
back_total_dura_squared += duration * duration
back_avg_t = back_total_dura / num_batches
# 打印反向传播的运算时间信息
print("%s: Forward-backward across %d steps, %.3f +/- %.3f sec / batch" %
(datetime.now(), num_batches, back_avg_t,
math.sqrt(back_total_dura_squared / num_batches - back_avg_t * back_avg_t)))