如何查看tensorflow SavedModel的签名
如何加载tensorflow SavedModel
如何修改现有的TensorFlow模型,增加输入层
如果你想要了解更多关于本项目,可以参考这个系列的前三篇文章:
当微信小程序遇上TensorFlow:Server端实现
当微信小程序遇上TensorFlow:Server端实现补充
当微信小程序遇上TensorFlow:小程序实现
关于Tensorflow SavedModel格式模型的处理,可以参考前面的文章:
Tensorflow SavedModel模型的保存与加载
如何查看tensorflow SavedModel格式模型的信息
如何合并两个TensorFlow模型
问题截至到目前为止,我们实现了一个简单的微信小程序,使用开源的Simple TensorFlow Serving部署了服务端。但这种实现方案还存在一个重大问题:小程序和服务端通信传递的图像数据是(299, 299, 3)二进制数组的JSON化表示,这种二进制数据JSON化的最大缺点是数据量太大,一个简单的299 x 299的图像,这样表示大约有3 ~ 4 M。其实HTTP传输二进制数据常用的方案是对二进制数据进行base64编码,经过base64编码,虽然数据量比二进制也会大一些,但相比JSON化的表示,还是小很多。
所以现在的问题是,如何让服务器端接收base64编码的图像数据?
查看模型的签名为了解决这一问题,我们还是先看看模型的输入输出,看看其签名是怎样的?这里的签名,并非是为了保证模型不被修改的那种电子签名。我的理解是类似于编程语言中模块的输入输出信息,比如函数名,输入参数类型,输出参数类型等等。借助于Tensorflow提供的saved_model_cli.py工具,我们可以清楚的查看模型的签名:
python ./tensorflow/python/tools/saved_model_cli.py show --dir /data/ai/workspace/aiexamples/AIDog/serving/models/inception_v3/ --all MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs: signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['image'] tensor_info: dtype: DT_FLOAT shape: (-1, 299, 299, 3) name: Placeholder:0 The given SavedModel SignatureDef contains the following output(s): outputs['prediction'] tensor_info: dtype: DT_FLOAT shape: (-1, 120) name: final_result:0 Method name is: tensorflow/serving/predict从中我们可以看出模型的输入参数名为image,其shape为(-1, 299, 299, 3),这里-1代表可以批量输入,通常我们只输入一张图像,所以这个维度通常是1。输出参数名为prediction,其shape为(-1, 120),-1和输入是对应的,120代表120组狗类别的概率。
现在的问题是,我们能否在模型的输入前面增加一层,进行base64及解码处理呢?
也许你认为可以在服务器端编写一段代码,进行base64字符串解码,然后再转交给Simple Tensorflow Serving进行处理,或者修改Simple TensorFlow Serving的处理逻辑,但这种修改方案增加了服务器端的工作量,使得服务器部署方案不再通用,放弃!
修改模型,增加输入层其实在上一篇文章《 如何合并两个TensorFlow模型 》中我们已经讲到了如何连接两个模型,这里再稍微重复一下,首先是编写一个base64解码、png解码、图像缩放的模型:
base64_str = tf.placeholder(tf.string, name='input_string') input_str = tf.decode_base64(base64_str) decoded_image = tf.image.decode_png(input_str, channels=input_depth) # Convert from full range of uint8 to range [0,1] of float32. decoded_image_as_float = tf.image.convert_image_dtype(decoded_image, tf.float32) decoded_image_4d = tf.expand_dims(decoded_image_as_float, 0) resize_shape = tf.stack([input_height, input_width]) resize_shape_as_int = tf.cast(resize_shape, dtype=tf.int32) resized_image = tf.image.resize_bilinear(decoded_image_4d, resize_shape_as_int) tf.identity(resized_image, name="DecodePNGOutput")接下来加载retrain模型:
with tf.Graph().as_default() as g2: with tf.Session(graph=g2) as sess: input_graph_def = saved_model_utils.get_meta_graph_def( FLAGS.origin_model_dir, tag_constants.SERVING).graph_def tf.saved_model.loader.load(sess, [tag_constants.SERVING], FLAGS.origin_model_dir) g2def = graph_util.convert_variables_to_constants( sess, input_graph_def, ["final_result"], variable_names_whitelist=None, variable_names_blacklist=None)这里调用了graph_util.convert_variables_to_constants将模型中的变量转化为常量,也就是所谓的冻结图(freeze graph)操作。
利用tf.import_graph_def方法,我们可以导入图到现有图中,注意第二个import_graph_def,其input是第一个graph_def的输出,通过这样的操作,就将两个计算图连接起来,最后保存起来。代码如下:
with tf.Graph().as_default() as g_combined: with tf.Session(graph=g_combined) as sess: x = tf.placeholder(tf.string, name="base64_string") y, = tf.import_graph_def(g1def, input_map={"input_string:0": x}, return_elements=["DecodePNGOutput:0"]) z, = tf.import_graph_def(g2def, input_map={"Placeholder:0": y}, return_elements=["final_result:0"]) tf.identity(z, "myOutput") tf.saved_model.simple_save(sess, FLAGS.model_dir, inputs={"image": x}, outputs={"prediction": z})