kubeflow系列(三):模型即服务,关于tensorflow serving的使用

kubeflow 中采用了 tensorflow serving 作为官方的tensorflow模型接口, TensorFlow Serving是GOOGLE开源的一个服务系统,适用于部署机器学习模型,灵活、性能高、可用于生产环境。 TensorFlow Serving可以轻松部署新算法和实验,同时保持相同的服务器架构和API。

Tensorflow Serving 直接加载模型即可生成接口,不过 serving 支持的模型只有 SaveModel,因此这里主要介绍 SaveModel。

SaveModel

SaveModel 是一种专门用于tf模型 拓扑结构(topology)权重(weights) ,基于 SaveModel 不需要运行原始的模型构建代码,这样非常利于共享或部署模型,因此一般模型部署都用 SaveModel

  • 拓扑结构(Topology): 这是一个描述模型结构的文件(例如它使用的了哪些操作)。它包含对存储在外部的模型权重的引用。
  • 权重(Weights): 这些是以有效格式存储给定模型权重的二进制文件。它们通常存储在与拓扑结构相同的文件夹中。

SaveModel文件目录:

assets  saved_model.pb  variables

查看MetaGraphDefsSignatureDefs:

saved_model_cli show --dir <SaveModel路径> --all

生成模型需要模型的MetaGraphDefsSignatureDefsMetaGraphDefs就是我们常见的meta graph,其中包含了四种主要的信息:

  • MetaInfoDef: 存放了一些元信息,例如版本和其他用户信息;
  • GraphDef: 描述的Graph中序列化得到的图,由Protocol Buffer组成;
  • SaverDef: 图的Saver信息,例如最多同时保存的checkpoint数量,需要保存的Tensor名字等,不保存Tensor中的实际内容;
  • CollectionDef: 任何需要特殊注意的python对象,需要特殊的标注以方便import_meta_graph后取回,如”prediction”。

SignatureDefs则是模型的签名定义,定义了 输入 和 输出函数`。

SignatureDefs

SignatureDef 定义了 TensorFlow graph 计算的签名,定义了 输入 和 输出函数,SignatureDef 结构 :

inputs as a map of string to TensorInfo.
outputs as a map of string to TensorInfo.
method_name (which corresponds to a supported method name in the loading tool/system).

Classification SignatureDef例子

必须要一个输入 Tensors inputs 和两个输出Tensors: classesscores

signature_def: {
key : "my_classification_signature"
value: {
inputs: {
key : "inputs"
value: {
name: "tf_example:0"
dtype: DT_STRING
tensor_shape: ...
}
}
outputs: {
key : "classes"
value: {
name: "index_to_string:0"
dtype: DT_STRING
tensor_shape: ...
}
}
outputs: {
key : "scores"
value: {
name: "TopKV2:0"
dtype: DT_FLOAT
tensor_shape: ...
}
}
method_name: "tensorflow/serving/classify"
}
}

Predict SignatureDef例子

signature_def: {
key : "my_prediction_signature"
value: {
inputs: {
key : "images"
value: {
name: "x:0"
dtype: ...
tensor_shape: ...
}
}
outputs: {
key : "scores"
value: {
name: "y:0"
dtype: ...
tensor_shape: ...
}
}
method_name: "tensorflow/serving/predict"
}
}

Regression SignatureDef例子

signature_def: {
key : "my_regression_signature"
value: {
inputs: {
key : "inputs"
value: {
name: "x_input_examples_tensor_0"
dtype: ...
tensor_shape: ...
}
}
outputs: {
key : "outputs"
value: {
name: "y_outputs_0"
dtype: DT_FLOAT
tensor_shape: ...
}
}
method_name: "tensorflow/serving/regress"
}
}

生成 SaveModel 文件

生成 SaveModel文件的方式:

  • (1)tf.saved_model # 最直接简单
  • (2)Estimator的export_savedmodel # 高级API Estimator模型导出

    classifier = classifier = tf.estimator.Estimator(
    model_fn=conv_model, model_dir=args.tf_model_dir,
    config=training_config, params=model_params)
    classifier.export_savedmodel(args.tf_export_dir, serving_input_receiver_fn=serving_fn)
  • (3)keras.Model.save(output_path)

将 checkpoint 模型文件 改为 SaveModel 文件

import sys, os, io
import tensorflow as tf

model_version = "1"
model_name = "object"

def restore_and_save(input_checkpoint, export_path_base):
checkpoint_file = tf.train.latest_checkpoint(input_checkpoint)
graph = tf.Graph()

with graph.as_default():
session_conf = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
sess = tf.Session(config=session_conf)

with sess.as_default():
# 载入保存好的meta graph,恢复图中变量,通过SavedModelBuilder保存可部署的模型
saver = tf.train.import_meta_graph("{}.meta".format(checkpoint_file))
saver.restore(sess, checkpoint_file)
print ("name scope: ",graph.get_name_scope())

export_path_base = export_path_base
export_path = os.path.join(
tf.compat.as_bytes(export_path_base),
tf.compat.as_bytes(model_name+"/"+model_version))
print('Exporting trained model to', export_path)

builder = tf.saved_model.builder.SavedModelBuilder(export_path)
# 模型的各种operator 可以通过 graph.get_operations() 获得
# input 为输入层operator
inputs = tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("Placeholder").outputs[0])
print(inputs)
# output 为输出层operator, 这里的输出层 type 为 Softmax
outputs = tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("final_result").outputs[0])
print(outputs)
"""
signature_constants:SavedModel保存和恢复操作的签名常量。
在序列标注的任务中,这里的method_name是"tensorflow/serving/predict"
"""
# 定义模型的输入输出,建立调用接口与tensor签名之间的映射
labeling_signature = (
tf.saved_model.signature_def_utils.build_signature_def(
inputs={
"Placeholder":
inputs,
},
outputs={
"final_result":
outputs,
},
method_name="tensorflow/serving/predict"))

"""
tf.group : 创建一个将多个操作分组的操作,返回一个可以执行所有输入的操作
"""
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')

"""
add_meta_graph_and_variables:建立一个Saver来保存session中的变量,
输出对应的原图的定义,这个函数假设保存的变量已经被初始化;
对于一个SavedModelBuilder,这个API必须被调用一次来保存meta graph;
对于后面添加的图结构,可以使用函数 add_meta_graph()来进行添加
"""
# 建立模型名称与模型签名之间的映射
builder.add_meta_graph_and_variables(
sess, [tf.saved_model.tag_constants.SERVING],
signature_def_map={
tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
labeling_signature},
legacy_init_op=legacy_init_op
)
builder.save()

print("Build Done")

Run server

生成好 SaveModel 模型文件,就可以直接运行 serving 来实现模型服务:

(1)用DOCKER运行:

docker run --rm -it -p 8500:8500 \
--mount type=bind,source=/root/inception/models,target=/models \
-e MODEL_NAME=1 tensorflow/serving

挂载的默认目录为两级目录:./<模型名称>/<版本号>/save_model.pb, 版本号必须为数字。

(2)或者可以用k8s运行deployment(kubeflow):

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: inception
name: inception-service-local
namespace: kubeflow
spec:
template:
metadata:
labels:
app: inception
version: v1
spec:
containers:
- args:
- --port=9000
- --rest_api_port=8500
- --model_name=1
- --model_base_path=/mnt/export
command:
- /usr/bin/tensorflow_model_server
env:
- name: modelBasePath
value: /mnt/export
image: tensorflow/serving:1.11.1
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
tcpSocket:
port: 9000
name: mnist
ports:
- containerPort: 9000
- containerPort: 8500
volumeMounts:
- mountPath: /mnt
name: local-storage

构建请求测试

测试模型接口

import requests
from PIL import Image
import numpy as np

filename = "./CES/astra_mini/1576210854440.png" # 图片
img=Image.open(filename)
img_arr=np.array(img,dtype=np.uint8)
print(img_arr.shape) # (299, 299, 3)

data = json.dumps({"instances": [img_arr.tolist()]})
headers = {"content-type": "application/json"}
json_response = requests.post('http://127.0.0.1:8501/v1/models/object:predict', data=data, headers=headers)
print(json_response.text)

参考文献

https://www.tensorflow.org/tfx/tutorials/serving/rest_simple

shikanon wechat
欢迎您扫一扫,订阅我滴↑↑↑的微信公众号!