Tensorflow学习之TF-Slim的使用

机器学习 2017-09-03

  TF-Slim是一个轻量级库,用于定义,训练和评估TensorFlow中的复杂模型。tf-slim的组件可以与本地tensorflow以及其他框架(如tf.contrib.learn)自由组合。

  TF-Slim的优点在于:

  1. 通过消除模板代码,允许用户更紧凑地定义模型。这是通过使用参数范围和许多高级层和变量来实现的。这些工具增加了可读性和可维护性,减少了复制和粘贴超参数值造成错误的可能性,并简化了超参数调整。
  2. 通过提供常用的regularizers使开发模型变得简单。
  3. 已经开发出许多广泛使用的计算机视觉模型(例如,VGG,AlexNet),并且可用于用户。 这些可以用作黑盒子,或者可以以各种方式进行扩展。
  4. Slim可以轻松扩展复杂的模型,并通过使用先前存在的模型检查点来加快启动训练算法。

  通过组合其变量,层和范围,可以使用TF-Slim简洁地定义模型。这些元素中的每一个都定义如下。

1.变量

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中,有两种类型的变量:常规变量和局部变量。绝大多数变量是常规变量:一旦创建,它们可以使用程序保存到磁盘。局部变量是在会话持续时间内仅存在且不保存到磁盘。   TF-Slim通过定义模型变量进一步区分变量,模型变量是表示模型参数的变量 模型变量在学习期间进行训练或微调,并在评估或推理期间从检查点加载。示例包括由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的图层或直接通过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)

2.层

  虽然一组TensorFlow操作相当广泛,但神经网络的开发人员通常会根据诸如“层”,“损耗”,“指标”和“网络”等更高层次的概念来考虑模型。诸如卷积层,全连接层或BatchNorm层的层比单个TensorFlow操作更抽象,并且通常涉及若干操作。此外,通常(但不总是)的层具有与其相关联的变量(可调参数),与更原始的操作不同。为了减少代码,TF-Slim提供了便捷的定义抽象级别的神经网络层的一些操作。

普通方法代码如下:

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)

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

  可以使用循环代替:

net = ...
for i in range(3):
  net = slim.conv2d(net, 256, [3, 3], scope='conv3_' % (i+1))
net = slim.max_pool2d(net, [2, 2], scope='pool2')

  可以用repeat操作进一步改进:

net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')

  slim.repeat不仅应用相同的参数,它也足够智能地展开范围,以便给每个后续调用的slim.conv2d附加一个下划线和迭代号。具体地说,上述示例中将被命名为“conv3 / conv3_1”,“conv3 / conv3_2”和“conv3 / conv3_3”。

  此外,TF-Slim的slim.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.类似地,可以使用堆栈来简化多个卷积层:

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

3.域

  除了TensorFlow(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')

  应该清楚的是,这三个卷积层共享许多相同的超参数。两个具有相同的填充,所有三个具有相同的weight_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中指定时,可以在本地覆盖。具体来说,当填充参数设置为“SAME”时,第二个卷积用“VALID”值覆盖它。也可以嵌套arg_scopes并在同一范围内使用多个操作。例如:

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将相同的weight_initializer和weights_regularizer参数应用于其作用域中的conv2d和fully_connected图层。 在第二个arg_scope中,仅指定了conv2d的其他默认参数。

  通过组合TF-Slim变量,操作和范围,我们可以编写一个通常非常复杂的网络,代码行数很少。例如,可以使用以下代码片段定义整个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

  参考:TF-Slim文档


本文由 Tony 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!