Table of Contents
三 slim中的图像预处理文件: preprocessing_factory.py
四 slim.assign_from_checkpoint_fn
一 slim.separable_conv2d
函数原型如下:
slim.separable_conv2d(inputs,
num_outputs,
kernel_size,
depth_multiplier,
strides=1,
padding='SAME',
data_format='NHWC'
rate=1,
activation_fn=tf.nn.relu
pointwise_initializer=None,
weights_regularizer=None,
biases_initializer=None,
biases_regularizer=None,
reuse=None,
variables_collections=None,
outputs_collections=None,
trainable=True,
scope=None
)
下面仅仅介绍几个重要参数和调用方法。
- inputs 一个四维的tensor 其shape形如 [batch_size, height, width, channels]
- num_outputs 是pointwise conv2d的输出通道数目
- kernel_size 是一个列表,里面装着卷积核的高和宽,形如 [kernel_height, kernel_width]
- depth_multiplier 这个看过论文的人都该知道了,影响depthwise conv2d的输出结果,输出的通道数为该值乘上输入的 channels
调用方法如下,来自于slim的mobilenet实现。
net = slim.separable_conv2d(net, None,kernel,
depth_multiplier=1,
stride=layer_stride,
rate=layer_rate,
normalizer_fn=slim.batch_norm,
scope=end_point)
这个的num_output为None,请看官方解释。
If is None, then we skip the pointwise convolution stage.
意思就是说如果num_output为None,这个函数就相当于是执行了depthwise conv2d,而没有执行pointwise conv2d。
二 slim.arg_scope
slim由于高封装性,所以参数数目就很多了,如果每一次运用诸如conv2d,batch_norm等具有相同参数的函数,都要重复写这些参数的实参赋值,肯定是谷歌的工程师不愿意见到的,于是有了slim.arg_scope。
slim.arg_scope([funcs],**parameters)
这个原型并非摘自源码,funcs指的是一些函数名称,用逗号隔开;parameter是funcs中都有的形参。
我们先看一段实例。
with slim.arg_scope([slim.conv2d,slim.separable_conv2d],
weights_initializer = weight_init,
activation_fn= tf.nn.relu6, normalizer_fn=slim.batch_norm):
slim.conv2d和slim.separable_conv2d中都有如上所示的形参,所以通过sllim.arg_scope可以将这些形参设置到函数中,只要在with所属的作用域内部,我们使用conv2d都不需要设置上面那些形参了。
另外,如果你想在某些操作设置形参的方式和arg_scope中不一致,只需要显示的(explictly),直接的(directly)设置就行了。而且这个函数支持嵌套。见如下代码:
with slim.arg_scope([slim.conv2d,slim.separable_conv2d],
weights_initializer = weight_init,
activation_fn= tf.nn.relu6, normalizer_fn=slim.batch_norm):
with slim.arg_scope([slim.batch_norm],**batch_norm_parames):
with slim.arg_scope([slim.conv2d],weights_regularizer=regularizer):
with slim.arg_scope([slim.separable_conv2d],
weights_regularizer=depthwise_regularizer) as sc:
观察发现,最外层的arg_scope中设置了多个函数中共有的形参,里面层仅仅对单个函数的某些形参单独设置。
三 slim中的图像预处理文件: preprocessing_factory.py
这个文件中有个核心函数
get_preprocessing(model_name,is_training=is_training)
slim提供了几个主流网络的预处理方式,当然这些文件也适用于其他网络,可能需要小修小改。
比如源码中这样使用:
image_preprocessing_fn = preprocessing_factory.get_preprocessing(
'mobilenet_v1', is_training=is_training)
这里就获得了'mobilenet_V1'的预处理方式。接着调用:
image = image_preprocessing_fn(image, FLAGS.image_size, FLAGS.image_size)
- image是一个三维的tensor,其实就是图像的那三维信息
image_size是图像的宽和高的数值,是一个数字,函数里面
使用了两次,就是说处理之后的图像高和宽都是指定的image_size
之后就可以用 tf.train.batch函数变成能用于训练的tensor了。
四 slim.assign_from_checkpoint_fn
我们平常使用tf.Saver类也是挺方便的,但是Saver在restore的时候有个问题,如果ckpt中记录的图结构或者一小部分tensor 的name和我们程序里面构建的图不一样,那么就无法导入权重值。这时slim提供的这个函数就可以很好解决这个问题。
该函数的用法有两个步骤,第一步:
slim_init_fn = slim.assign_from_checkpoint_fn(
FLAGS.fine_tune_checkpoint,
variables_to_restore,
ignore_missing_vars=True)
第一个参数是ckpt文件的地址+文件名;variable_to_restore是需要加载权值的tensor,最后一个参数设置为TRUE,则如果variable_to_restore中记录的tensor在ckpt中没有,那么就不对该tensor赋值了,直接忽视。
第二步:得到的这slim_init_fn其实是个函数,还需要如下方式调用。
slim_init_fn(sess)
也就是说这个函数只能在一个session中使用。
2019.1.8
五 slim.batch_norm
在TensorFlow中想运用BN其实是很麻烦的事情。我们先看下源码 tf.layers.batch_normalization 自带的一段注释。
Note: when training, the moving_mean and moving_variance need to be updated.
By default the update ops are placed in `tf.GraphKeys.UPDATE_OPS`, so they
need to be added as a dependency to the `train_op`. Also, be sure to add
any batch_normalization ops before getting the update_ops collection.
Otherwise, update_ops will be empty, and training/inference will not work
properly. For example:
```python
x_norm = tf.layers.batch_normalization(x, training=training)
# ...
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss)
不想看英文或者看不明白就看我这一段。我们试想一下BN的操作:计算每一个mini batch中所有图片的均值和方差,然后所有的图片都用这两个值做normalization。我们在训练的时候确实执行的是上述操作,但是如果在测试的时候,我们仍这么做,最终结果就带上了batch自身的影响。比如说a这张图片被shuffle到了一个batch-1中,得到了一个结果;然后a放在另一个不同的batch中,可能会得到不同的解果,原因就是bn计算了一个batch的mean和var, 但是这两个参数是随着batch的改变而改变的。为了消除这种影响,我们在测试的时候,BN使用的mean值和var值是固定的,来自于在训练的时候计算的滑动平均值。但是有个问题,滑动平均值不是训练参数,它不会在训练过程中像权重一样更新,所以需要人为的告诉程序,要在反向传播之前计算更新滑动平均值。
具体做法就是:
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss)
先获得在Graph中update_ops集合中的更新滑动平均值得操作。之后我们只要强制的通过tf.control_dependencies把这个操作加入到图中,告诉程序,执行train_op操作之前,要先执行update_ops。
如果你不要上面的操作,你会发现你的训练出来的模型,在测试的时候精度会很低,并且is_training参数必须是True才能让结果变成正常的,但实际上这样做还是让模型计算每个batch的mean和var,实际结果是受到batch 自身干扰的。
接下来就来看看slim中的batch_norm的用法吧。
import tensorflow as tf
from tensorflow.contrib import slim
import numpy as np
def BN_arg_scope(is_training=True,
weight_decay=0.00004,
stddev=0.09,
batch_norm_decay=0.9997,
batch_norm_epslion=0.001):
batch_norm_parames={
'center': True,
'scale' : True,
'decay' : batch_norm_decay,
'epslion' : batch_norm_epslion
}
if is_training:
batch_norm_parames['is_training']=is_training
with slim.arg_scope([slim.fully_connected],normalizer_fn=slim.batch_norm):
with slim.arg_scope([slim.batch_norm],**batch_norm_parames) as sc:
return sc
def inference(inputs):
net=slim.fully_connected(inputs,32)
net=slim.fully_connected(net,64)
net=slim.fully_connected(net,2,activation_fn=None)
net=slim.softmax(net)
return net
def main():
data=np.random.rand(100)
label=np.ones([100,],np.int32)
with slim.arg_scope(BN_arg_scope()):
data=tf.convert_to_tensor(data)
label=tf.convert_to_tensor(label)
label_one_hot=slim.one_hot_encoding(label,num_classes=2)
logits=inference(data)
loss=tf.reduce_mean(tf.square(logits-label),axis=[0,1])
opt=tf.train.GradientDescentOptimizer(0.01)
update_ops=tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op=opt.minimize(loss)
with tf.Session() as sess:
'''
#自己的训练代码
'''