【深度学习】VGGNet原理解析及实现
很小的卷积(3*3),增加网络深度可以有效提升模型的效果,而且VGGNet对其他数据集具有很好的泛化能力。到目前为止,VGGNet依然经常被用来提取图像特征。
反复堆叠3*3的小型卷积核和2*2的最大池化层,VGGNet成功的构筑了16-19层深的CNN。
一、VGGNet结构
(图6-7),原因为:参数量主要消耗在最后3个全连接层,而前面的卷积层虽然层数多,但消耗的参数量不大。不过,卷积层的训练比较耗时,因为其计算量大。
1*1的卷积层,1*1卷积的意义在于线性变换,而输入的通道数和输出的通道数不变,没有发生降维。
VGG的性能:
VGGNet网络特点:
1. VGGNet拥有5段卷积,每段卷积内有2-3个卷积层,同时每段尾部都会连接一个最大池化层(用来缩小图片)。
2. 每段内的卷积核数量一样,越后边的段内卷积核数量越多,依次为:64-128-256-512-512
3. 越深的网络效果越好。(图6-9)
4. LRN层作用不大(作者结论)
5. 1*1的卷积也是很有效的,但是没有3*3的卷积好,大一些的卷积核可以学习更大的空间特征。
为什么一个段内有多个3*3的卷积层堆叠?
这是个非常有用的设计。如下图所示,2个3*3的卷积层串联相当于1个5*5的卷积层,即一个像素会跟周围5*5的像素产生关联,可以说感受野大小为5*5。而3个3*3的卷积层相当于1个7*7的卷积层。并且,两个3*3的卷积层的参数比1个5*5的更少,前者为2*3*3=18,后者为1*5*5=25。
更重要的是,2个3*3的卷积层比1个5*5的卷积层有更多的非线性变换(前者可使用2次ReLu函数,后者只有两次),这使得CNN对特征的学习能力更强。
所以3*3的卷积层堆叠的优点为:
(1)参数量更小
(2)小的卷积层比大的有更多的非线性变换,使得CNN对特征的学习能力更强。
与其他网络对比:
二、VGGNet实现
1.卷积层操作
def conv_op(input,kh,kw,n_out,dh,dw,parameters,name): """ 定义卷积层的操作 :param input: 输入的tensor :param kh:卷积核的高 :param kw:卷积核的宽 :param n_out:输出通道数(即卷积核的数量) :param dh:步长的高 :param dw:步长的宽 :param parameters:参数列表 :param name:层的名字 :return:返回卷积层的结果 """ n_in = input.get_shape()[-1].value #通道数 with tf.name_scope(name) as scope: kernel =tf.get_variable(scope+'w', shape=[kh,kw,n_in,n_out],dtype=tf.float32, initializer=tf.contrib.layers.xavier_initializer_conv2d()) conv=tf.nn.conv2d(input,kernel,[1,dh,dw,1],padding='SAME') biases=tf.Variable(tf.constant(0.0,shape=[n_out],dtype=tf.float32), trainable=True,name='b') z=tf.nn.bias_add(conv,biases) # wx+b activation =tf.nn.relu(z,name=scope) parameters +=[kernel,biases] return activation
2.全连接层操作
def fc_op(input,n_out,parameters,name): """ 定义全连接层操作 注意:卷积层的结果要做扁平化才能和fc层相连接;此全连接操作带着RELU :param input: 输入的tensor :param n_out: 输出通道数(即神经元的数量) :param parameters: 参数列表 :param name: 层的名字 :return: 返回全连接层的结果 """ n_in=input.get_shape()[-1].value with tf.name_scope(name) as scope: kernel =tf.get_variable(scope+'w', shape=[n_in,n_out],dtype=tf.float32, initializer=tf.contrib.layers.xavier_initializer() ) biases = tf.Variable(tf.constant(0.1, shape=[n_out], dtype=tf.float32), trainable=True, name='b') activation=tf.nn.relu(tf.matmul(input,kernel)+biases, name=scope) #ReLU parameters +=[kernel,biases] return activation
3.最大池化操作
def maxPool_op(input,kh,kw,dh,dw,name): return tf.nn.max_pool(input,ksize=[1,kh,kw,1],strides=[1,dh,dw,1], padding='SAME',name=name)
4.VGGNet实现
def vggNet(input,keep_prob): parameters =[] #conv1段 conv1_1 =conv_op(input,kh=3,kw=3,n_out=64,dh=1,dw=1, parameters=parameters,name='conv1_1') conv1_2 =conv_op(conv1_1, kh=3, kw=3, n_out=64, dh=1, dw=1, parameters=parameters, name='conv1_2') pool1 =maxPool_op(conv1_2,kh=2,kw=2,dh=2,dw=2,name='pool1') # conv2段 conv2_1 = conv_op(pool1, kh=3, kw=3, n_out=128, dh=1, dw=1, parameters=parameters, name='conv2_1') conv2_2 = conv_op(conv2_1, kh=3, kw=3, n_out=128, dh=1, dw=1, parameters=parameters, name='conv2_2') pool2 = maxPool_op(conv2_2, kh=2, kw=2, dh=2, dw=2, name='pool2') # conv3段 conv3_1 = conv_op(pool2, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_1') conv3_2 = conv_op(conv3_1, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_2') conv3_3 = conv_op(conv3_2, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_3') pool3 = maxPool_op(conv3_3, kh=2, kw=2, dh=2, dw=2, name='pool3') # conv4段 conv4_1 = conv_op(pool3, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_1') conv4_2 = conv_op(conv4_1, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_2') conv4_3 = conv_op(conv4_2, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_3') pool4 = maxPool_op(conv4_3, kh=2, kw=2, dh=2, dw=2, name='pool4') # conv5段 conv5_1 = conv_op(pool4, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_1') conv5_2 = conv_op(conv5_1, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_2') conv5_3 = conv_op(conv5_2, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_3') pool5 = maxPool_op(conv5_3, kh=2, kw=2, dh=2, dw=2, name='pool5') #将最后一个卷积层的结果扁平化:每个样本占一行 conv_shape=pool5.get_shape() col=conv_shape[1].value *conv_shape[2].value * conv_shape[3].value flat=tf.reshape(pool5, [-1,col],name='flat') # fc6段 fc6 =fc_op(input=flat,n_out=4096,parameters=parameters,name='fc6') fc6_dropout =tf.nn.dropout(fc6,keep_prob,name='fc6_drop') # fc7段 fc7 = fc_op(input=fc6_dropout, n_out=4096, parameters=parameters, name='fc7') fc7_dropout = tf.nn.dropout(fc7, keep_prob, name='fc7_drop') # fc8段:最后一个全连接层,使用softmax进行处理得到分类输出概率 fc8=fc_op(input=fc7_dropout,n_out=1000,parameters=parameters,name='fc8') softmax =tf.nn.softmax(fc8) predictions =tf.arg_max(softmax,1) return predictions,softmax,fc8,parameters
5.测评:前向和反向用时测评
def time_compute(session, target, feed,info_string): num_batch = 100 #100 num_step_burn_in = 10 # 预热轮数,头几轮迭代有显存加载、cache命中等问题可以因此跳过 total_duration = 0.0 # 总时间 total_duration_squared = 0.0 for i in range(num_batch + num_step_burn_in): start_time = time.time() _ = session.run(target,feed_dict=feed ) duration = time.time() - start_time if i >= num_step_burn_in: if i % 10 == 0: # 每迭代10次显示一次duration print("%s: step %d,duration=%.5f " % (datetime.now(), i - num_step_burn_in, duration)) total_duration += duration total_duration_squared += duration * duration time_mean = total_duration / num_batch time_variance = total_duration_squared / num_batch - time_mean * time_mean time_stddev = math.sqrt(time_variance) # 迭代完成,输出 print("%s: %s across %d steps,%.3f +/- %.3f sec per batch " % (datetime.now(), info_string, num_batch, time_mean, time_stddev))
6.运行结果
为了节约时间,设置 batch_size = 2 ,运行结果如下:前向预测和反向学习
前向预测:
反向学习
【附录】整体代码
# -*- coding:utf-8 -*- """ @author:Lisa @file:VggNet.py.py @note:from<tensorflow实战> @time:2018/6/25 0025下午 7:29 """ import tensorflow as tf import math import time from datetime import datetime def conv_op(input,kh,kw,n_out,dh,dw,parameters,name): """ 定义卷积层的操作 :param input: 输入的tensor :param kh:卷积核的高 :param kw:卷积核的宽 :param n_out:输出通道数(即卷积核的数量) :param dh:步长的高 :param dw:步长的宽 :param parameters:参数列表 :param name:层的名字 :return:返回卷积层的结果 """ n_in = input.get_shape()[-1].value #通道数 with tf.name_scope(name) as scope: kernel =tf.get_variable(scope+'w', shape=[kh,kw,n_in,n_out],dtype=tf.float32, initializer=tf.contrib.layers.xavier_initializer_conv2d()) conv=tf.nn.conv2d(input,kernel,[1,dh,dw,1],padding='SAME') biases=tf.Variable(tf.constant(0.0,shape=[n_out],dtype=tf.float32), trainable=True,name='b') z=tf.nn.bias_add(conv,biases) # wx+b activation =tf.nn.relu(z,name=scope) parameters +=[kernel,biases] return activation def fc_op(input,n_out,parameters,name): """ 定义全连接层操作 注意:卷积层的结果要做扁平化才能和fc层相连接 :param input: 输入的tensor :param n_out: 输出通道数(即神经元的数量) :param parameters: 参数列表 :param name: 层的名字 :return: 返回全连接层的结果 """ n_in=input.get_shape()[-1].value with tf.name_scope(name) as scope: kernel =tf.get_variable(scope+'w', shape=[n_in,n_out],dtype=tf.float32, initializer=tf.contrib.layers.xavier_initializer() ) biases = tf.Variable(tf.constant(0.1, shape=[n_out], dtype=tf.float32), trainable=True, name='b') activation=tf.nn.relu(tf.matmul(input,kernel)+biases, name=scope) parameters +=[kernel,biases] return activation def maxPool_op(input,kh,kw,dh,dw,name): return tf.nn.max_pool(input,ksize=[1,kh,kw,1],strides=[1,dh,dw,1], padding='SAME',name=name) def vggNet(input,keep_prob): parameters =[] #conv1段 conv1_1 =conv_op(input,kh=3,kw=3,n_out=64,dh=1,dw=1, parameters=parameters,name='conv1_1') conv1_2 =conv_op(conv1_1, kh=3, kw=3, n_out=64, dh=1, dw=1, parameters=parameters, name='conv1_2') pool1 =maxPool_op(conv1_2,kh=2,kw=2,dh=2,dw=2,name='pool1') # conv2段 conv2_1 = conv_op(pool1, kh=3, kw=3, n_out=128, dh=1, dw=1, parameters=parameters, name='conv2_1') conv2_2 = conv_op(conv2_1, kh=3, kw=3, n_out=128, dh=1, dw=1, parameters=parameters, name='conv2_2') pool2 = maxPool_op(conv2_2, kh=2, kw=2, dh=2, dw=2, name='pool2') # conv3段 conv3_1 = conv_op(pool2, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_1') conv3_2 = conv_op(conv3_1, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_2') conv3_3 = conv_op(conv3_2, kh=3, kw=3, n_out=256, dh=1, dw=1, parameters=parameters, name='conv3_3') pool3 = maxPool_op(conv3_3, kh=2, kw=2, dh=2, dw=2, name='pool3') # conv4段 conv4_1 = conv_op(pool3, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_1') conv4_2 = conv_op(conv4_1, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_2') conv4_3 = conv_op(conv4_2, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv4_3') pool4 = maxPool_op(conv4_3, kh=2, kw=2, dh=2, dw=2, name='pool4') # conv5段 conv5_1 = conv_op(pool4, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_1') conv5_2 = conv_op(conv5_1, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_2') conv5_3 = conv_op(conv5_2, kh=3, kw=3, n_out=512, dh=1, dw=1, parameters=parameters, name='conv5_3') pool5 = maxPool_op(conv5_3, kh=2, kw=2, dh=2, dw=2, name='pool5') #将最后一个卷积层的结果扁平化:每个样本占一行 conv_shape=pool5.get_shape() col=conv_shape[1].value *conv_shape[2].value * conv_shape[3].value flat=tf.reshape(pool5, [-1,col],name='flat') # fc6段 fc6 =fc_op(input=flat,n_out=4096,parameters=parameters,name='fc6') fc6_dropout =tf.nn.dropout(fc6,keep_prob,name='fc6_drop') # fc7段 fc7 = fc_op(input=fc6_dropout, n_out=4096, parameters=parameters, name='fc7') fc7_dropout = tf.nn.dropout(fc7, keep_prob, name='fc7_drop') # fc8段:最后一个全连接层,使用softmax进行处理得到分类输出概率 fc8=fc_op(input=fc7_dropout,n_out=1000,parameters=parameters,name='fc8') softmax =tf.nn.softmax(fc8) predictions =tf.arg_max(softmax,1) return predictions,softmax,fc8,parameters def time_compute(session, target, feed,info_string): num_batch = 100 #100 num_step_burn_in = 10 # 预热轮数,头几轮迭代有显存加载、cache命中等问题可以因此跳过 total_duration = 0.0 # 总时间 total_duration_squared = 0.0 for i in range(num_batch + num_step_burn_in): start_time = time.time() _ = session.run(target,feed_dict=feed ) duration = time.time() - start_time if i >= num_step_burn_in: if i % 10 == 0: # 每迭代10次显示一次duration print("%s: step %d,duration=%.5f " % (datetime.now(), i - num_step_burn_in, duration)) total_duration += duration total_duration_squared += duration * duration time_mean = total_duration / num_batch time_variance = total_duration_squared / num_batch - time_mean * time_mean time_stddev = math.sqrt(time_variance) # 迭代完成,输出 print("%s: %s across %d steps,%.3f +/- %.3f sec per batch " % (datetime.now(), info_string, num_batch, time_mean, time_stddev)) def main(): with tf.Graph().as_default(): """仅使用随机图片数据 测试前馈和反馈计算的耗时""" image_size = 224 batch_size = 2 #32 images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype=tf.float32, stddev=0.1)) keep_prob=tf.placeholder(tf.float32) predictions,softmax,fc8, parameters = vggNet(images,keep_prob) init = tf.global_variables_initializer() sess = tf.Session() sess.run(init) """ AlexNet forward 计算的测评 传入的target:fc8(即最后一层的输出) 优化目标:loss 使用tf.gradients求相对于loss的所有模型参数的梯度 AlexNet Backward 计算的测评 target:grad """ time_compute(sess, target=fc8, feed={keep_prob:1.0},info_string="Forward") obj = tf.nn.l2_loss(fc8) grad = tf.gradients(obj, parameters) time_compute(sess, grad, feed={keep_prob:0.5},info_string="Forward-backward") if __name__ == "__main__": main()
参考:
《tensorflow实战》黄文坚(本文内容及代码大多源于此书,感谢!)大牛论文《VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION》Karen Simonyan