还在用原生Tensorflow吗?试试TF-Slim吧

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014296502/article/details/80419017

TF-Slim是tensorflow中用来定义、训练与评估的轻量级库,tf-slim的组件与tf.contrib.learn相似能够将你从复杂的原生tensorflow解放出来。
你需要导入import tensorflow.contrib.slim as slim

为什么要用TF-Slim?

tf-slim能够将搭建、训练、评估神经网络变的简单:

  1. 通过使用参数域(argument scoping)与更多的高抽象层(layer)与变量,允许用户定义更加紧凑的模型,而不像原生tensorflow那样冗余。这个工具增加了代码的可读性与可维护性,减少像粘贴复制参数产生的错误,简化了参数的使用
  2. 通过提供常用的正则化方式使得开发模型变的简单
    3.内置了常用的视觉模型(VGG、Alextnet…),你可以把它当做一个黑盒子使用,并且也可以根据自己的需求进行修改
  3. Slim使得扩展复杂的模型变的简单,最令人兴奋的是你可以使用已经存在的checkpoints,去训练模型,做迁移学习
TF-Slim有哪些组件?
  1. arg_scope
  2. data
  3. evaluation
  4. layers
  5. learning
  6. losses
  7. metrics
  8. nets
  9. queues
  10. regularizers
  11. variables

Variables

如果使用原生tensorflow创建一个variables,你需要给它一个预定义值,或者初始化机制(如:在高斯模型中随机取值),而且如果一个变量你想要把它创建在GPU上 ,你需要更加相信的指定。为了解放这个过程,TF-Slim提供了一个包裹函数使得我们更加简单的定义变量
例如:下面这个例子,不仅指定了初始化方式、使用L2正则化方式并且还指定了,这个变量在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中有两类变量,局部变量与全局变量,全局变量一旦被创建,就可以通过saver保存到磁盘中,而局部变量只存在session生命周期中,一点session关闭则就会被清除,不能保存到磁盘中。
TF-Slim进一步区分变量类型,定义了模型变量,模型变量指的是模型中的参数。模型变量是学习过程中被训练的或者被微调的,在评估模型与推理模型的时候,能够通过checkpoint文件加载到计算图(Graph)中。例如:slim.fully_connected o(创建全连接函数)与slim.conv2d (卷积操作)创建的变量就是模型变量

# 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来管理您的模型变量,那该怎么办呢?
TF-Slim提供了一个简单便利的方式添加到模型变量集合中

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

Layers

TF-Slim另外一个高度抽象的方法就是Layers组件,如果使用原生tensorflow创建一个:

  1. 创建权重与偏置变量
  2. 将输入变量与权重做卷积操作
  3. 加上偏置
  4. 应用激活函数,输出
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)

上面是定义一个卷积层的基础步骤,如果创建多个呢?比如说vgg-16?为了减少重复代码,TF-Slim提供了更简单的方式去定义一个卷积。

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

为了搭建神经网络,TF-Slim提供了一些标准的实现方式

这里写图片描述这里写图片描述
TF-Slim也提供了两个元运算( meta-operations),repeat与stack,这两个函数将会更加的精简代码。比如在搭建vgg网络过程中,会有很多3*3相同的网络结构,你需要这样定义:

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

但是如果使用slim的话,你只需要这样定义:

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

在inception网络的Inception-Model中,经常碰到的是相同网络结构,但是参数不同,你可能会这样定义:

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

Scopes

除了name_scope与variable_scope之外,TF-Slim还新添加了一个新的scoping机制叫做arg_scope。允许用户指定一个或多个操作和一组参数,这些参数将传递给arg_scope中定义的每个操作。比如定义三个卷积层,每层都使用L2正则化方式,权重初始化方式都采用标准差为0.01,从高斯函数中随机取值

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

不仅如此,arg_scope还支持嵌套,如下设置全局的激活函数为relu,在全链接层设置局部激活函数为None

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

Losses

TF-Slim也内置了损失函数,

# Define the loss functions and get the total loss.
classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)
sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)
`

Training Loop

TF-Slim提供了一个简单但是强大的工具去训练模型,包括一个训练方法能够重复的计算损失、梯度并且保存模型到磁盘中还有几个可以手动操作梯度的简单方法。例如:一旦我们指定了模型、损失函数、优化策略,我们可以调用 slim.learning.create_train_op和slim.learning.train 去执行优化策略

g = tf.Graph()
# 创建模型指定损失函数...
total_loss = slim.losses.get_total_loss()
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
# create_train_op 可以确保每次去执行损失函数, the update_ops
#会被运行,并且也会执行梯度的计算.
train_op = slim.learning.create_train_op(total_loss, optimizer)
logdir = ... # checkpoints 文件位置
slim.learning.train(
    train_op,
    logdir,
    number_of_steps=1000,
    save_summaries_secs=300,
    save_interval_secs=600)

train_op提供给slim.learning.train将会自动的计算损失与梯度下降。logdir的指定用来存储checkpoints与event文件。我们可以限制梯度下降的步数为任何值。比如上面我们限制为1000步。最后save_summaries_secs=300,是指定tensorboard中,多少次存储一个summary,300秒就是5分钟。我们经常会碰到训练中断,为了避免重头训练,可以设置save_interval_secs=600,表示10分钟保存一次模型。

Fine-Tuing已经存在的模型

模型被训练完以后,可以使用tf.train.Saver保存模型,也可以利用它从checkpoints文件中加载模型
例如:把变量从checkpoint文件中加载到模型中

# 创建变量
v1 = tf.Variable(..., name="v1")
v2 = tf.Variable(..., name="v2")
restorer = tf.train.Saver()
restorer = tf.train.Saver([v1, v2])
with tf.Session() as sess:
  # 从磁盘中加载模型
  restorer.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")

checkpoint文件就是模型的元数据文件,里面记载了保存模型的历史与最新模型的名称,通过ckpt=tf.train.get_checkpoint_state('./checkpoints/')方法,可以得到最近一次ckpt文件的名称。saver.restore(sess, ckpt.model_checkpoint_path)把变量加载进来,这种方式需要重新运算模型,构造静态图,当然也有一种直接把静态图也加载进来,就不需要模型代码了saver = tf.train.import_meta_graph(ckpt.model_checkpoint_path+'.meta'),通过加载模型元数据可以直接恢复静态图。

恢复部分模型

有的时候模型很大比如说残差网络,动辄几百层,也许你只想观察某一层,而不想把所有的变量都加载进来,那么slim也提供了一个简单的方式

# Create some variables.
v1 = slim.variable(name="v1", ...)
v2 = slim.variable(name="nested/v2", ...)
...
# Get list of variables to restore (which contains only 'v2'). These are all
# equivalent methods:
variables_to_restore = slim.get_variables_by_name("v2")
# or
variables_to_restore = slim.get_variables_by_suffix("2")
# or
variables_to_restore = slim.get_variables(scope="nested")
# or
variables_to_restore = slim.get_variables_to_restore(include=["nested"])
# or
variables_to_restore = slim.get_variables_to_restore(exclude=["v1"])
# Create the saver which will be used to restore the variables.
restorer = tf.train.Saver(variables_to_restore)
with tf.Session() as sess:
  # Restore variables from disk.
  restorer.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # Do some work with the model
  ...

评估 Models

一旦我们训练了一个模型,我们想看看模型在实践中表现得如何。这是通过选择一组评价指标来完成的,这些评价指标将对模型的性能进行评分,而评估代码实际上是加载数据,执行模型,将结果与一组真实值进行比较,并记录评估得分。这个步骤可以进行一次或定期重复。网上大多都是粘贴复制,根本没有进行测试,甚至连代码能不能运行都不知道,也就是翻译一番而已,我的环境是tensoflow1.8,执行mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels)一直报错

images, labels = LoadTestData(...)
predictions = MyModel(images)
mae_value_op, mae_update_op = slim.metrics.streaming_mean_absolute_error(predictions, labels)
mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels)
mre_value_op, mre_update_op = slim.metrics.streaming_mean_squared_error(predictions, labels)
pl_value_op, pl_update_op = slim.metrics.percentage_less(mean_relative_errors, 0.3)

先以mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels)为例,我翻阅了源码具体操作如下absolute_errors = math_ops.abs(predictions - labels),其实就是取绝对值,那么这个可能在评价回归模型比较有用,分类模型就没啥用处了。同理squared_error = math_ops.square(labels - predictions)也就是取平方。

对于streaming_mean_relative_error,我测试一直报错TypeError: streaming_mean_relative_error() missing 1 required positional argument: 'normalizer',源码如下:一起分析一下

  relative_errors = array_ops.where(
      math_ops.equal(normalizer, 0.0), array_ops.zeros_like(labels),
      math_ops.div(math_ops.abs(labels - predictions), normalizer))
  return mean(relative_errors, weights, metrics_collections,
              updates_collections, name or 'mean_relative_error')

总结的来说,就是需要设置一个normalizer参数,这个参数要与labels shape相同,如果normalizer为0,则这个label就设置为0,否则就是lable与prediction绝对值与normalizer的商。
这个用法我没体会到哪里会用到。也许当你阅读sllim英文文档时候,会搞不明白他们的含义,经过我们源码讨论一波,你是不是稍微有点感觉了?
最常用的还是,准确率,精准率,召回率,这点slim还是比较良心的

names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
    'accuracy': slim.metrics.accuracy(predictions, labels),
    'precision': slim.metrics.precision(predictions, labels),
    'recall': slim.metrics.recall(mean_relative_errors, 0.3),
})

如果你的代码运行不了,提示AttributeError: module 'tensorflow.contrib.metrics' has no attribute 'precision',那是因为tensorflow 1.8 直接把方法放到tensorflow.metrics中了,你可以降低版本,或者重新安装一下slim。欢迎关注我的微信公共号,一起讨论学习吧

这里写图片描述

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页