连接:https://blog.csdn.net/thriving_fcl/article/details/75213361git
saved_model模块主要用于TensorFlow Serving。TF Serving是一个将训练好的模型部署至生产环境的系统,主要的优势在于能够保持Server端与API不变的状况下,部署新的算法或进行试验,同时还有很高的性能。
保持Server端与API不变有什么好处呢?有不少好处,我只从我体会的一个方面举例子说明一下,好比咱们须要部署一个文本分类模型,那么输入和输出是能够肯定的,输入文本,输出各种别的几率或类别标签。为了获得较好的效果,咱们可能想尝试不少不一样的模型,CNN,RNN,RCNN等,这些模型训练好保存下来之后,在inference阶段须要从新载入这些模型,咱们但愿的是inference的代码有一份就好,也就是使用新模型的时候不须要针对新模型来修改inference的代码。这应该如何实现呢?
在TensorFlow 模型保存/载入的两种方法中总结过。
1. 仅用Saver来保存/载入变量。这个方法显然不行,仅保存变量就必须在inference的时候从新定义Graph(定义模型),这样不一样的模型代码确定要修改。即便同一种模型,参数变化了,也须要在代码中有所体现,至少须要一个配置文件来同步,这样就很繁琐了。
2. 使用tf.train.import_meta_graph导入graph信息并建立Saver, 再使用Saver restore变量。相比第一种,不须要从新定义模型,可是为了从graph中找到输入输出的tensor,仍是得用graph.get_tensor_by_name()来获取,也就是还须要知道在定义模型阶段所赋予这些tensor的名字。若是建立各模型的代码都是同一我的完成的,还相对好控制,强制这些输入输出的命名都一致便可。若是是不一样的开发者,要在建立模型阶段就强制tensor的命名一致就比较困难了。这样就不得再也不维护一个配置文件,将须要获取的tensor名称写入,而后从配置文件中读取该参数。
通过上面的分析发现,要实现inference的代码统一,使用原来的方法也是能够的,只不过TensorFlow官方提供了更好的方法,而且这个方法不只仅是解决这个问题,因此仍是得学习使用saved_model这个模块。
saved_model 保存/载入模型
先列出会用到的API
class tf.saved_model.builder.SavedModelBuilder
# 初始化方法
__init__(export_dir)
# 导入graph与变量信息
add_meta_graph_and_variables(
sess,
tags,
signature_def_map=None,
assets_collection=None,
legacy_init_op=None,
clear_devices=False,
main_op=None
)
# 载入保存好的模型
tf.saved_model.loader.load(
sess,
tags,
export_dir,
**saver_kwargs
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(1) 最简单的场景,只是保存/载入模型
保存
要保存一个已经训练好的模型,使用下面三行代码就能够了。
builder = tf.saved_model.builder.SavedModelBuilder(saved_model_dir)
builder.add_meta_graph_and_variables(sess, ['tag_string'])
builder.save()
1
2
3
首先构造SavedModelBuilder对象,初始化方法只须要传入用于保存模型的目录名,目录不用预先建立。
add_meta_graph_and_variables方法导入graph的信息以及变量,这个方法假设变量都已经初始化好了,对于每一个SavedModelBuilder这个方法必定要执行一次用于导入第一个meta graph。
第一个参数传入当前的session,包含了graph的结构与全部变量。
第二个参数是给当前须要保存的meta graph一个标签,标签名能够自定义,在以后载入模型的时候,须要根据这个标签名去查找对应的MetaGraphDef,找不到就会报如RuntimeError: MetaGraphDef associated with tags 'foo' could not be found in SavedModel这样的错。标签也能够选用系统定义好的参数,如tf.saved_model.tag_constants.SERVING与tf.saved_model.tag_constants.TRAINING。
save方法就是将模型序列化到指定目录底下。
保存好之后到saved_model_dir目录下,会有一个saved_model.pb文件以及variables文件夹。顾名思义,variables保存全部变量,saved_model.pb用于保存模型结构等信息。
载入
使用tf.saved_model.loader.load方法就能够载入模型。如
meta_graph_def = tf.saved_model.loader.load(sess, ['tag_string'], saved_model_dir)
1
第一个参数就是当前的session,第二个参数是在保存的时候定义的meta graph的标签,标签一致才能找到对应的meta graph。第三个参数就是模型保存的目录。
load完之后,也是从sess对应的graph中获取须要的tensor来inference。如
x = sess.graph.get_tensor_by_name('input_x:0')
y = sess.graph.get_tensor_by_name('predict_y:0')
# 实际的待inference的样本
_x = ...
sess.run(y, feed_dict={x: _x})
1
2
3
4
5
6
这样和以前的第二种方法同样,也是要知道tensor的name。那么如何能够在不知道tensor name的状况下使用呢? 那就须要给add_meta_graph_and_variables方法传入第三个参数,signature_def_map。
(2) 使用SignatureDef
关于SignatureDef个人理解是,它定义了一些协议,对咱们所需的信息进行封装,咱们根据这套协议来获取信息,从而实现建立与使用模型的解耦。SignatureDef的结构以及相关详细的文档在:https://github.com/tensorflow/serving/blob/master/tensorflow_serving/g3doc/signature_defs.md
相关API
# 构建signature
tf.saved_model.signature_def_utils.build_signature_def(
inputs=None,
outputs=None,
method_name=None
)
# 构建tensor info
tf.saved_model.utils.build_tensor_info(tensor)
1
2
3
4
5
6
7
8
9
SignatureDef,将输入输出tensor的信息都进行了封装,而且给他们一个自定义的别名,因此在构建模型的阶段,能够随便给tensor命名,只要在保存训练好的模型的时候,在SignatureDef中给出统一的别名便可。
TensorFlow的关于这部分的例子中用到了很多signature_constants,这些constants的用处主要是提供了一个方便统一的命名。在咱们本身理解SignatureDef的做用的时候,能够先不用管这些,遇到须要命名的时候,想怎么写怎么写。
保存
假设定义模型输入的别名为“input_x”,输出的别名为“output” ,使用SignatureDef的代码以下
builder = tf.saved_model.builder.SavedModelBuilder(saved_model_dir)
# x 为输入tensor, keep_prob为dropout的prob tensor
inputs = {'input_x': tf.saved_model.utils.build_tensor_info(x),
'keep_prob': tf.saved_model.utils.build_tensor_info(keep_prob)}
# y 为最终须要的输出结果tensor
outputs = {'output' : tf.saved_model.utils.build_tensor_info(y)}
signature = tf.saved_model.signature_def_utils.build_signature_def(inputs, outputs, 'test_sig_name')
builder.add_meta_graph_and_variables(sess, ['test_saved_model'], {'test_signature':signature})
builder.save()
1
2
3
4
5
6
7
8
9
10
11
12
上述inputs增长一个keep_prob是为了说明inputs能够有多个, build_tensor_info方法将tensor相关的信息序列化为TensorInfo protocol buffer。
inputs,outputs都是dict,key是咱们约定的输入输出别名,value就是对具体tensor包装获得的TensorInfo。
而后使用build_signature_def方法构建SignatureDef,第三个参数method_name暂时先随便给一个。
建立好的SignatureDef是用在add_meta_graph_and_variables的第三个参数signature_def_map中,但不是直接传入SignatureDef对象。事实上signature_def_map接收的是一个dict,key是咱们本身命名的signature名称,value是SignatureDef对象。
载入
载入与使用的代码以下
## 略去构建sess的代码
signature_key = 'test_signature'
input_key = 'input_x'
output_key = 'output'
meta_graph_def = tf.saved_model.loader.load(sess, ['test_saved_model'], saved_model_dir)
# 从meta_graph_def中取出SignatureDef对象
signature = meta_graph_def.signature_def
# 从signature中找出具体输入输出的tensor name
x_tensor_name = signature[signature_key].inputs[input_key].name
y_tensor_name = signature[signature_key].outputs[output_key].name
# 获取tensor 并inference
x = sess.graph.get_tensor_by_name(x_tensor_name)
y = sess.graph.get_tensor_by_name(y_tensor_name)
# _x 实际输入待inference的data
sess.run(y, feed_dict={x:_x})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
从上面两段代码能够知道,咱们只须要约定好输入输出的别名,在保存模型的时候使用这些别名建立signature,输入输出tensor的具体名称已经彻底隐藏,这就实现建立模型与使用模型的解耦。
---------------------
做者:thriving_fcl
来源:CSDN
原文:https://blog.csdn.net/thriving_fcl/article/details/75213361?utm_source=copy
版权声明:本文为博主原创文章,转载请附上博文连接!github