从11月正式开始学习DL这块,由于项目这块目前都是用tensorflow写工程,所以学习DL同时也在学习tf,感觉在tf基础上出现了很多封装好的库,比如slim,主要也是同事们在用,所以自己也学习一下。
详细说明介绍可以参考tensorflow官方提供的说明文档,本文基本上是对说明的翻译
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/slim/README.md
导入slim模块
import tensorflow.contrib.slim as slim
Why TF-Slim?
TF-Slim是一个可以让你轻松构建、训练、评价网络的库
用户可以自定义模型,方便的使用argument scoping ,layers和variable来简洁的定义网络。
提供常用的regularizers让开发模型更简单
多种常用机器视觉模型(如VGG,AlexNet)都在slim中已经实现并且可以提供给拥护使用。这些可以作为黑盒或者使用多种方式进行扩展,例如在不同内层中添加“multiple heads”
Slim 使得扩展复杂模型更加简单,并且可以通过加载预训练模型的checkpoints热启动训练算法。
主要由以下几个部分组成:
data:包括TF-slim的数据集定义dataset,数据提供器(data providers),并行读入器(parallel_reader)和解码器(decoding)等。
evaluation: 包含评估模型程序
layers:包含使用tensorflow构建模型的high level layers
learning:包含训练模型程序
losses:包含常用的loss函数
metrics:包含常用评估矩阵
queues:提供一个上下文管理器来方便安全的启动或关闭QueueRunners
regularizers:包含权值正则项
variables:提供便捷封装器来创建和操作变量
Variables
在原生tensorflow中需要预定义值或者定义一个初始化机制(如 Gaussian随机采样)。并且,如果一个变量需要在特定的设备上生成,如一个GPU,则必须使用made explicit 进行声明。为了减少生成变量所需的代码两,TF-Slim提供了一套简单封装的函数variables.py 以使调用者可以简单定义变量。
例,要产生一个weight
变量,使用truncated normal distribution
来初始化,使用l2_loss
来正则化,并将其放到CPU
,可参考以下代码:
weights = slim.variable('weights',
shape=[10, 10, 3 , 3],
initializer=tf.truncated_normal_initializer(stddev=0.1),
regularizer=slim.l2_regularizer(0.05),
device='/CPU:0')
注意,在原生Tensorflow中,有两种变量类型:规则变量(regulra variables)和局部(transient)变量。大部分变量都是规则变量:一旦创建,变可以使用saver保存在磁盘上,局部变量则只能在一个session期间存在,不能保存在磁盘上。
TF-Slim通过定义 model variables进一步区分变量,该变量代表模型的参数。模型变量在学习阶段被训练或微调,在评估或预测阶段可以从checkpoint中加载。例如使用slim.fully_connected
或者 slim.conv2d
创建变量。非模型变量都是其他变量,在学习或评估过程中使用,在预测阶段并不会起作用。例如global_step
是一个在学习和预测阶段使用的变量,但是并不能真正算是模型的一部分。类似的,移动平均变量可以镜像模型变量,但是移动平均变量自身也并不能算是是模型变量。
模型变量和规则变量都可以通过TF-Slim轻松创建:
# Model Variables
weights = slim.model_variable('weights',
shape=[10, 10, 3 , 3],
initializer=tf.truncated_normal_initializer(stddev=0.1),
regularizer=slim.l2_regularizer(0.05),
device='/CPU:0')
model_variables = slim.get_model_variables()
# Regular variables
my_var = slim.variable('my_var',
shape=[20, 1],
initializer=tf.zeros_initializer())
regular_variables_and_model_variables = slim.get_variables()
这是如何运行的呢?当你通过TF-Slim的layer或者直接通过slim.model_variable
函数创建了一个模型变量,TF-Slim将变量添加到tf.GraphKeys.MODEL_VARIABLES
这个集合中。如果你有你自己定义的网络或这变量生成程序,而你有希望TF-Slim来管理你的模型变量要怎么办呢?TF-Slim提供了一个方便的函数用来添加模型变量到该集合:
my_model_variable = CreateViaCustomCode()
# Letting TF-Slim know about the additional variable.
slim.add_model_variable(my_model_variable)
Tensorflow的操作集非常广泛,开发者通常想到使用一些更高层次的概念如“layers”,“loss”,“metrics”和“networks”。一个层,比如一个卷积层,一个全连接层或者一个BatchNorm层要比单独的一个TensorFlow操作要抽象的多,而且往往要涉及到多个操作。并且不想那些基础操作,一个网络层经常(不是总是)有很多与之相关的变量。例如,一个网络中的卷积层经常由多个低层操作组成:
1. 创建权值(weight)和偏置(bias)变量
2. 将来自上一层的数据和权值进行卷积
3. 将偏置加到卷积结果上
4. 应用激活函数
如果只用普通的TensorFlow代码,将会变得非常费劲:
input = ...
with tf.name_scope('conv1_1') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32,
stddev=1e-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在更抽象的层面上提供了一系列边界操作定义神经网络。例如,对应于上述代码的TF-Slim代码如下:
input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
TF-Slimg提供了标准接口用来组建神经网络,这些包含:
Layer | TF-Slim |
---|---|
BiasAdd | slim.bias_add |
BatchNorm | slim.batch_norm |
Conv2d | slim.conv2d |
Conv2dInPlane | slim.conv2d_in_plane |
Conv2dTranspose (Deconv) | slim.conv2d_transpose |
FullyConnected | slim.fully_connected |
AvgPool2D | slim.avg_pool2d |
Dropout | slim.dropout |
Flatten | slim.flatten |
MaxPool2D | slim.max_pool2d |
OneHotEncoding | slim.one_hot_encoding |
SeparableConv2 | slim.separable_conv2d |
UnitNorm | slim.unit_norm |
TF-Slim还提供了两个元操作符repeat
and stack
,供给用户使用重复或相同操作。如VGG 网络中的一段,网络在两个池化层只之间有多个卷积:
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')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
一种减少这种代码重复的方法是使用for循环:
net = ...
for i in range(3):
net = slim.conv2d(net, 256, [3, 3], scope='conv3_%d' % (i+1))
net = slim.max_pool2d(net, [2, 2], scope='pool2')
使用TF-Slim的repeat
可以使得代码更加简洁:
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
slim.repeat
不但可以在一行中使用相同的参数,而且还能智能地展开scopes,即每个后续的slim.conv2d
调用所对应的scope都会追加下划线及迭代数字。更具体地讲,上面代码的scope分别为’conv3/conv3_1’, ‘conv3/conv3_2’ 和 ‘conv3/conv3_3’.
除此以外,TF-Slim的slim.stack
操作允许调用者用不同的参数重复使用相同的操作符是创建一个stack或网络层塔。 slim.stack
同样也为每个操作创建了一个新的tf.variable_scope
。例如,一个创建多层感知机(MLP)的简单方法:
# Verbose way:
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')
# Equivalent, TF-Slim way using slim.stack:
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
在这个例子中,slim.stack
调用 slim.fully_connected
三次,将上一层的输出作为下一层的输入。然而,每次调用中隐含单元的数量变化从32到64再到128。类似的,也可以使用stack来简化多层卷积塔:
# Verbose way:
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')
# Using stack:
slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')