tf的模型文件有三种:
第一种情况:
checkpoint: *.ckpt-xx.data-xx-of-xx + *.ckpt-xx.meta 文件 + *.ckpt-xx.index 文件
在训练 TensorFlow 模型时,每迭代若干轮需要保存一次权值到磁盘,称为“checkpoint”。这种格式文件是由 tf.train.Saver() 对象调用 saver.save() 生成的,ckpt只包含若干 Variables 对象序列化后的数据,不包含图结构,所以只给 checkpoint 模型不提供代码是无法重新构建计算图的。所以在这种情况下有两种方法构建计图:
[1] 手动构建模型
先定义新图:
graph = tf.Graph()
with graph.as_default():
[输入节点可用placeholder构建]
[graph body,一般可以通过调用构建训练图的函数来构建]
[可以对网络进行微调,增加softmax层或argmax层等不会影响下面数据的加载]
构建会话:
saver = tf.train.Saver()
with tf.Session() as sess:
[载入*.ckpt-xx.data-xx-of-xx变量文件:]
saver.restore(sess, checkpointspath + '/model.ckpt-xx')
[文件中保存的权重数据v将被加载到上面构建的图中]
[runtime]
sess.run(xx,feed_dict=xx)
[2] 直接加载图结构而不必重新定义 (import + restore)
with tf.Graph().as_default():
saver = tf.train.import_meta_graph(model_path + '/xxx.meta')# 加载图结构
# 得到当前图中所有变量的名称
tensor_name_list = [tensor.name for tensor in gragh.as_graph_def().node]
# 获取输入变量(占位符,由于保存时未定义名称,tf自动赋名称“Placeholder”)
x = gragh.get_tensor_by_name('xxx:0')
y = gragh.get_tensor_by_name('xxx:0')# 获取输出变量
pred = gragh.get_tensor_by_name('xxx/xxx:0')# 获取网络输出值
可以加入评估变量的定义:
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = y,\
logits = pred))
correct = tf.equal(tf.argmax(y,1), tf.argmax(pred,1))
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
构建会话:
with tf.Session() as sess:
saver.restore(sess, tf.train.latest_checkpoint(\
model_path+'/xxx.ckpt-xx'))# 加载变量值
print('finish loading model!')
[runtime]
sess.run(xx,feed_dict=xx) 或 continue to train
[3] 注意在恢复图的基础上构建新的网络进行训练(fine tune)方法:
(1)首先构建网络结构构建模型,根据自己的需要选择定义哪些网络层
[Model Body with fine tune layers]
(2)选择性地从模型文件里恢复权重变量值:https://github.com/tensorflow/models/tree/master/research/slim#Tuning
v1= tf.Variable(tf.zeros([100]), name="v1")
saver1 = tf.train.Saver([v1])
with tf.Session() as sess:
saver1.restore(sess, "checkpoint/model_test-1")
(3)固定已经恢复的训练好的权重(不更新),着重训练新加入的参数。网络的前几层得到的特征是比较基础local的特征, 基本适用于全部任务(边缘,角点特征),而网络层数越高的层,和数据库全局信息联系越紧密,(网络越靠后,信息越global,越靠前,越local,local的信息是多数情况共享的,而global的信息和原图紧密相关)
e.g.
train_params =network.all_params
new_train_params=[]
new_train_params=train_params[3:]
optimizer = tf.train.AdamOptimizer(learning_rate=model_config['learning_rate']).minimize(cost,var_list=new_train_params)
(4)以下借用slim里的函数说明操作步骤(https://blog.youkuaiyun.com/liyuan123zhouhui/article/details/69569493):
#tensorflow 中从ckpt文件中恢复指定的层或将指定的层不进行恢复:
#tensorflow 中不同的layer指定不同的学习率
with tf.Graph().as_default():
#存放的是需要恢复的层参数
variables_to_restore = []
#存放的是需要训练的层参数名,这里是没恢复的需要进行重新训练,实际上恢复了的参数也
可以训练
variables_to_train = []
for var in slim.get_model_variables():
excluded = False
for exclusion in fine_tune_layers:
#比如fine tune layer中包含logits,bottleneck
if var.op.name.startswith(exclusion):
excluded = True
break
if not excluded:
variables_to_restore.append(var)
#print('var to restore :',var)
else:
variables_to_train.append(var)
#print('var to train: ',var)
#这里省略掉一些步骤,进入训练步骤(我猜想是 model body 的构建)
#将variables_to_train,需要训练的参数给optimizer 的compute_gradients函数
grads = opt.compute_gradients(total_loss, variables_to_train)
#这个函数将只计算variables_to_train中的梯度
#然后将梯度进行应用:
apply_gradient_op = opt.apply_gradients(grads, \
global_step=global_step)
#也可以直接调用opt.minimize(total_loss,variables_to_train)
#minimize只是将compute_gradients与apply_gradients封装成了一个函数,实际上
#还是调用的这两个函数
#如果在梯度里面不同的参数需要不同的学习率,那么可以:
capped_grads_and_vars = []
#[(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars]
#update_gradient_vars是需要更新的参数,使用的是全局学习率
#对于不是update_gradient_vars的参数,将其梯度更新乘以0.0001,使用基本上不动
for grad in grads:
for update_vars in update_gradient_vars:
if grad[1]==update_vars:
capped_grads_and_vars.append((grad[0],grad[1]))
else:
capped_grads_and_vars.append((0.0001*grad[0],grad[1]))
apply_gradient_op = opt.apply_gradients(capped_grads_and_vars, \
global_step=global_step)
#在恢复模型时:
with sess.as_default():
if pretrained_model:
print('Restoring pretrained model: %s' % pretrained_model)
init_fn = slim.assign_from_checkpoint_fn(
pretrained_model,
variables_to_restore)
init_fn(sess)
#这样就将指定的层参数没有恢复
第二种情况:*.pd文件
[1] 二次生成pd文件:
with tf.Graph().as_default():
[define the graph body]
with tf.Session() as sess:
saver.restore(sess, tf.train.latest_checkpoint(\
model_path+'/xxx.ckpt-xx'))
# freeze graph
frozen_graph = tf.graph_util.convert_variables_to_constants(
tf_sess,
tf_sess.graph_def,
output_node_names=['logits', 'classes'])
with tf.gfile.GFile(prebuilt_graph_path, "wb") as f:
f.write(frozen_graph.SerializeToString())
[2] 使用pd文件(parse + import)
with tf.Session() as sess:
model_f = tf.gfile.FastGFile("./Test/model.pb", mode='rb')
graph_def = tf.GraphDef()
graph_def.ParseFromString(model_f.read())
c = tf.import_graph_def(graph_def, return_elements=["add2:0"])
c2 = tf.import_graph_def(graph_def, return_elements=["add3:0"])
x, x2, c3 = tf.import_graph_def(graph_def, \
return_elements=["x:0", "x2:0", "add:0"])
print(sess.run(c))
print(sess.run(c2))
print(sess.run(c3, feed_dict={x: 23, x2: 2}))
第三种情况:SavedModel文件(略)
接下来描述使用tensorrt的python接口压缩模型的流程:
[1] 生成frozen_graph(见上述第二种情况中[1])
[2] 利用tensorflow.contrib.tensorrt接口将生成的frozen_graph进一步处理:
frozen_graph = trt.create_inference_graph(
input_graph_def=frozen_graph,
outputs=['logits', 'classes'],
max_batch_size=batch_size,
max_workspace_size_bytes=max_workspace_size,
precision_mode=precision.upper(),
minimum_segment_size=minimum_segment_size,
is_dynamic_op=use_dynamic_op
)
函数原型:
def create_inference_graph(input_graph_def,
outputs,
max_batch_size=1,
max_workspace_size_bytes=2 << 20,
precision_mode="fp32",
minimum_segment_size=3,
is_dynamic_op=False,
maximum_cached_engines=1,
cached_engine_batch_sizes=None
use_calibration=True,
rewriter_config=None,
input_saved_model_dir=None,
input_saved_model_tags=None,
output_saved_model_dir=None,
session_config=None):
英伟达关于使用tensorrt的tensorflow接口tf-trt的文档地址:
https://docs.nvidia.com/deeplearning/frameworks/tf-trt-user-guide/index.html
[3] 将压缩后的 frozen_graph 保存为pd文件:
with tf.gfile.GFile(prebuilt_graph_path, "wb") as f:
f.write(frozen_graph.SerializeToString())
[4] 加载使用pd的过程见上述第二种情况中[2]
滑动平均:
有关于滑动指数平均,在加载原来的模型文件时需要注意,具体可以参考这篇博文:https://blog.youkuaiyun.com/qq_14845119/article/details/78767544,很详细
关于滑动平均值文件的读取,参考 https://blog.youkuaiyun.com/sinat_29957455/article/details/78508793
v = tf.Variable(1.,name="v")
#滑动模型的参数的大小并不会影响v的值
ema = tf.train.ExponentialMovingAverage(0.99)
print(ema.variables_to_restore())
#{'v/ExponentialMovingAverage': <tf.Variable 'v:0' shape=() dtype=float32_ref>}
sess = tf.Session()
saver = tf.train.Saver(ema.variables_to_restore())
saver.restore(sess,"./model.ckpt")
print(sess.run(v))
#0.0999999
通过使用variables_to_restore函数,可以使在加载模型的时候将影子变量直接映射到变量的本身,所以我们在获取变量的滑动平均值的时候只需要获取到变量的本身值而不需要去获取影子变量。