声明:
- 翻译自Quick complete Tensorflow tutorial to understand and run Alexnet, VGG, Inceptionv3, Resnet and squeezeNet networks
- 虽然原文中有第3部分,使用预训练网络,但是文章中没有贴代码,所以本文没有翻译这一个部分。
TensorFlow卷积网络教程
本文包括2部分:
- 介绍一些成功的卷积网络,如AlexNet,Resnet等。
- 简要介绍一个轻量级高层TensorFlow库——TensorFlow-Slim(TF-Slim)。
1. Popular convolutional neural networks
数据集:Imagenet
1.1 Alexnet
![Alexnet](http://cv-tricks.com/wp-content/uploads/2017/03/500x154xalexnet_small.png.pagespeed.ic.e8ysG2A-k9.webp)
Alexnet在第一层使用的11*11卷积核后来被证明过大,以后的卷积网络的设计都是使用较小的卷积核。同时,Alexnet通过使用dropout层、ReLU激活函数和一系列数据增强的手段提升网络性能,这些方法被证明十分有效并沿用至今。
1.2 VGG
![VGG](http://cv-tricks.com/wp-content/uploads/2017/03/VGGNet.png.pagespeed.ce.NIN7l2u-nT.png)
图中每一列表示VGG的一个版本,其中vgg16和vgg19最常用。
VGG用3*3的卷积核代替Alexnet11*11的卷积核,网络更深,准确率更高。同时由于其简单的结构一度被广泛使用。它的缺点是参数量太大(主要是全连接层的参数)。一个预训练的VGG模型会占用500M以上的空间。
3. Inceptioin
越深的网络具有越强的capacity,这也是深度学习的由来。在深度学习的发展史上,Inception网络是第一个不仅仅在网络深度上进行改进的模型。
在第一层,对于输入图片,使用多个不同大小的卷积核(1*1, 3*3, 5*5),然后合并卷积结果以获得更robust的图片表示。
很多时候会在卷积层后面再加一个1*1的卷积层来调节通道的深度。
4. Resnet
2015年Imagenet的最佳结果来自于152层的残差网络Resnet。它通过残差的形式解决了深层网络难以训练的问题。
5. Squeezenet
squeezenet的优点在于大大减少了网络中参数的个数,一个预训练的squeezenet只有5M,更便于在实际中应用。
2. TensorFlow-slim tutorial
TensorFlow-slim是一个封装好的轻量级API,可以帮我们省去很多底层编码。下面通过构建InceptionV1来学习如何使用TF-Slim。
2.1 From Tensorflow version1.0 and above
TF-Slim在1.0或以上的版本中上线。在shell中运行以下命令检查TF-Slim是否存在。
python -c "import tensorflow.contrib.slim as slim; eval=slim.evaluation.evaluate_once"
2.2 TensorFlow-slim tutorial
import TF-Slim
import tf.contrib.slim as slim
2.2.1 Quick Slim variables
weights = slim.variable('weights', shape=[10, 10, 3, 3], initializer=tf.truncated_normal_initializer(stddev=0.1), regularizer=slim.l2_regularizer(0.05), device='/GPU:0')
一行代码,定义了:
- 变量名
- 变量shape
- 初始化方式
- 正则化方式
- 部署设备号
获取模型变量的方式:model_variables = slim.get_model_variables()
.
与之对应的TensorFlow底层方式:tf.get_global_variables()
.
获取全部变量的方式,包括步长变量(global_step):regular_variables_and_model_variables = slim.get_variables()
.
每次通过TF-Slim创建了变量,都会添加到tf.GraphKeys.MODEL_VARIABLES
集合中。
2.2.2 Higher level layers
创建二维卷积层,128个3*3的卷积核:
input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
跟Keras很像
要实现相同功能,底层TensorFlow:
input = ...
with tf.name_scope('conv1_1') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32, stddev=0.1), name='weights')
conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32), trainable=True, name='biases')
bias = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(bias, name=scope)
可以看出TF-Slim确实可以大大减少代码量,如果有多层卷积层:
net = ...
net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
或者更简单的,用一句话表示3个卷积层:
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
跟进一步,如果我们的网络由参数不同(比如卷积核个数不同)的全连接层层构成:
x = slim.fully_connected(x, 32, scope='fc/fc_1')
x = slim.fully_connected(x, 64, scope='fc/fc_2')
x = slim.fully_connected(x, 128, scope='fc/fc_3')
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
对于卷积层,也是同样的道理:
# 方案一:不使用stack
x = slim.conv2d(x, 32,[3, 3], scope='core/core_1')
x = slim.conv2d(x, 32,[1, 1], scope='core/core_2')
x = slim.conv2d(x, 64,[3, 3], scope='core/core_3')
x = slim.conv2d(x, 64,[1, 1], scope='core/core_4')
# 方案二:使用stack
slim.stack(x, slim.conv2d,[(32,[3, 3]),(32,[1, 1]),(64,[3, 3]),(64,[1, 1])], scope='core')
2.2.3 Arg scope and using it
还记得前面使用底层TensorFlow函数构建卷积层,里面使用了name_scope
。把源代码再次贴出来:
input = ...
with tf.name_scope('conv1_1') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32, stddev=0.1), name='weights')
conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32), trainable=True, name='biases')
bias = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(bias, name=scope)
name_scope
起到一个命名的前缀的作用,在上述代码中,kernel.name == 'conv1_1/weights:0
。
基于name_scope
的思想,TF-Slim有一个arg_scope
的功能。对于3个相似的卷积层:
net = slim.conv2d(inputs, 64,[11, 11], 4, padding='SAME',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv1')
net = slim.conv2d(net, 128,[11, 11], padding='VALID',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv2')
net = slim.conv2d(net, 256,[11, 11], padding='SAME',
weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
weights_regularizer=slim.l2_regularizer(0.0005), scope='conv3')
通过arg_scope
可以改为:
with slim.arg_scope([slim.conv2d], padding='SAME', weights_initializer=tf.truncated_normal_initializer(stddev=0.01), weights_regularizer=slim.l2_regularizer(0.0005)):
net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
net = slim.conv2d(net, 256, [11, 11], scope='conv3')