TensorFlow-Slim介绍

TF-Slim是Tensorflow中一个轻量级的库,用于定义、训练和评估复杂的模型。TF-Slim中的组件可以与Tensorflow中原生的函数一起使用,与其他的框架,比如与tf.contrib.learn也可以一起使用,用法如下:

import tensorflow.contrib.slim as slim

TF-Slim的意义:
TF-Slim可以使建立、训练和评估神经网络更加简单。

  1. 允许用户通过减少模板代码使得模型更加简洁。这个可以通过使用argument scoping和大量的高层layers、variables来实现;
  2. 通过使用常用的正则化( regularizers)使得建立模型更加简单;
  3. 一些广泛使用的计算机视觉相关的模型(比如VGG,AlexNet)已经在slim中定义好了,用户可以很方便的使用;这些既可以当成黑盒使用,也可以被扩展使用,比如添加一些“multiple heads”到不同的内部的层;
  4. Slim使得扩展复杂模型变得容易,可以使用已经存在的模型的checkpoints来开始训练算法。

TF-Slim的组成:

1.arg_scope:提供一个新的作用域(scope),称为arg_scope,在该作用域(scope)中,用户可以定义一些默认的参数,用于特定的操作;

2.data:包含TF-Slim的dataset定义,data providers,parallel_reader,和 decoding utilities;

3.evaluation:包含用于模型评估的常规函数;

4.layers:包含用于建立模型的高级layers;

5.learning:包含一些用于训练模型的常规函数;

6.losses:包含一些用于loss function的函数;

7.metrics:包含一些热门的评价标准;

8.nets:包含一些热门的网络定义,如VGG,AlexNet等模型;

9.queues:提供一个内容管理者,使得可以很容易、很安全地启动和关闭QueueRunners;

10.regularizers:包含权重正则化;

11.variables:提供一个方便的封装,用于变量创建和使用。

使用TF-Slim,结合variables, layers 和 scopes,模型可以很简洁地被定义。

Variables
在原生的Tensorflow中,创建Variable需要一个预定义的值或者一种初始化机制(比如从一个高斯分布中随机采样)。此外,如果一个变量需要在一个特定的设备上(如GPU)创建,那么必须被明确说明。为了减少变量创建所需的代码,TF-Slim提供了一些封装函数(定义在variables.py中),可以使得用户定义变量变得简单。

例如,定义一个权重(weight)变量,使用一个截断的正态分布来初始化,使用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中,有两种类型的variables:regular variables 和 local (transient) variables。绝大部分变量是regular variables,一旦被创建,可以使用saver来将这些变量保存到磁盘中;Local variables是那些仅仅存在于一个session内,并不会被保存到磁盘中。

TF-Slim通过定义model variables来进一步区别变量,这些是表示一个模型参数的变量。Model variables在学习期间被训练或者微调(fine-tuned),在评估或者推断期间可以从一个checkpoint中加载。模型变量包括使用slim.fully_connected 或者 slim.conv2d创建的变量等。非模型变量(Non-model variables)指的是那些在学习或者评估阶段使用但是在实际的inference中不需要用到的变量。比如说,global_step在学习和评估阶段会用到的变量,但是实际上并不是模型的一部分。类似的,moving average variables也是非模型变量。

model variables和regular variables在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集合中。如果你想拥有自己定制化的layers或者variables创建机制,但是仍然想利用TF-Slim来管理你的变量,此时,TF-Slim提供一个方便的函数,用于添加模型的变量到集合中:

my_model_variable = CreateViaCustomCode()
# Letting TF-Slim know about the additional variable.
slim.add_model_variable(my_model_variable)

Layers
在原生的Tensorflow中,要定义一些层(比如说卷积层,全连接层,BatchNorm层等)是比较麻烦的。例如,神经网络中的卷积层由以下几个步骤组成:

1.创建权重和偏置变量
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提供了一些方便高级别更抽象的神经网络层。比如说,卷积层实现如下:

input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')

TF-Slim也两个操作符,称为repeat 和 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命名为’conv3/conv3_1’, ‘conv3/conv3_2’ 和 ‘conv3/conv3_3’。

另外,TF-Slim的 slim.stack操作允许用户用不同的参数重复调用同一种操作。slim.stack也为每一个被创建的操作创建一个新的tf.variable_scope。比如说,下面是一种简单的方式来创建多层感知器(Multi-Layer Perceptron (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三次。类似的,我们可以使用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')

Scopes
除了Tensorflow中作用域(scope)之外(name_scope, variable_scope),TF-Slim增加了新的作用域机制,称为arg_scope。这个新的作用域允许使用者明确一个或者多个操作和一些参数,这些定义好的操作或者参数会传递给arg_scope内部的每一个操作。下面举例说明。先看如下代码片段:

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')

从上面的代码中可以清楚的看出来,有3层卷积层,其中很多超参数都是一样的。两个卷积层有相同的padding,所有三个卷积层有相同的weights_initializer和weight_regularizer。上面的代码包含了大量重复的值,其中一种解决方法是使用变量来说明一些默认的值:

padding = 'SAME'
initializer = tf.truncated_normal_initializer(stddev=0.01)
regularizer = slim.l2_regularizer(0.0005)
net = slim.conv2d(inputs, 64, [11, 11], 4,
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv1')
net = slim.conv2d(net, 128, [11, 11],
                  padding='VALID',
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv2')
net = slim.conv2d(net, 256, [11, 11],
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  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')

上面的例子表明,使用arg_scope可以使得代码变得更整洁、更干净并且更加容易维护。注意到,在arg_scope中规定的参数值,它们可以被局部覆盖。比如说,上面的padding参数被设置成‘SAME’,但是在第二个卷积层中用‘VALID’覆盖了这个参数。

我们也可以嵌套使用arg_scope,在相同的作用域内使用多个操作。举例如下:

with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                      weights_regularizer=slim.l2_regularizer(0.0005)):
  with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'):
    net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1')
    net = slim.conv2d(net, 256, [5, 5],
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.03),
                      scope='conv2')
    net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')

在上面的例子中,在第一个arg_scope中,卷积层和全连接层被应用于相同的权重初始化和权重正则化;在第二个arg_scope中,额外的参数仅仅对卷积层conv2d起作用。

Working Example: Specifying the VGG16 Layers
通过结合TF-Slim的Variables, Operations 和 scopes,我们可以使用比较少的代码来实现一个比较复杂的网络。比如说,整个VGG网络定义如下:

def vgg16(inputs):
  with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,
                      weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),
                      weights_regularizer=slim.l2_regularizer(0.0005)):
    net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
    net = slim.max_pool2d(net, [2, 2], scope='pool1')
    net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
    net = slim.max_pool2d(net, [2, 2], scope='pool2')
    net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
    net = slim.max_pool2d(net, [2, 2], scope='pool3')
    net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
    net = slim.max_pool2d(net, [2, 2], scope='pool4')
    net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
    net = slim.max_pool2d(net, [2, 2], scope='pool5')
    net = slim.fully_connected(net, 4096, scope='fc6')
    net = slim.dropout(net, 0.5, scope='dropout6')
    net = slim.fully_connected(net, 4096, scope='fc7')
    net = slim.dropout(net, 0.5, scope='dropout7')
    net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8')
  return net
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值