TF-Slim是tensorflow中用来定义、训练与评估的轻量级库,tf-slim的组件与tf.contrib.learn相似能够将你从复杂的原生tensorflow解放出来。
你需要导入import tensorflow.contrib.slim as slim
tf-slim能够将搭建、训练、评估神经网络变的简单:
如果使用原生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)
TF-Slim另外一个高度抽象的方法就是Layers组件,如果使用原生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)
上面是定义一个卷积层的基础步骤,如果创建多个呢?比如说vgg-16?为了减少重复代码,TF-Slim提供了更简单的方式去定义一个卷积。
input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
为了搭建神经网络,TF-Slim提供了一些标准的实现方式
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')
除了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')
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)
`
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分钟保存一次模型。
模型被训练完以后,可以使用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
...
一旦我们训练了一个模型,我们想看看模型在实践中表现得如何。这是通过选择一组评价指标来完成的,这些评价指标将对模型的性能进行评分,而评估代码实际上是加载数据,执行模型,将结果与一组真实值进行比较,并记录评估得分。这个步骤可以进行一次或定期重复。网上大多都是粘贴复制,根本没有进行测试,甚至连代码能不能运行都不知道,也就是翻译一番而已,我的环境是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。欢迎关注我的微信公共号,一起讨论学习吧