在《TensorFlow Serving系列之导出自己的训练模型》中,我们将训练的目标检测模型导成了TFS所需的格式,本文要实现的是将该模型导入到服务中,并实现客户端调用。TFS支持REST和gRPC两种形式的接口调用,比较常用且效率较高的是gRPC方式,因此,下文中的客户端是基于gRPC接口实现
系列文章目录
(一)TensorFlow Serving系列之安装及调用方法
(二)TensorFlow Serving系列之导出自己的训练模型
(三)TensorFlow Serving系列之客户端gRPC调用
(四)TensorFlow Serving系列之gRPC基本知识
(五)TensorFlow Serving系列之源码安装服务
(六)TensorFlow Serving系列之多模型多版本控制
我们将之前生成的模型保存在detection/1文件夹下,生成如下目录结构,其中1表示版本信息,如果有新模型,依次往后叫2/3等,TFS会自动加载热更新编号最大的模型
├── detection
│ └── 1
│ ├── saved_model.pb
│ └── variables
│ ├── variables.data-00000-of-00001
│ └── variables.index
使用如下指令创建容器
# CPU版本
docker run -p 8500:8500 --mount type=bind,source=$(pwd)/detection,target=/models/detection -e MODEL_NAME=detection -t tensorflow/serving:1.12.3
# GPU版本
docker run --runtime=nvidia -p 8500:8500 --mount type=bind,source=$(pwd)/detection,target=/models/detection -e MODEL_NAME=detection -t tensorflow/serving:1.12.3-gpu
使用docker ps指令,可查看到容器是否成功创建并运行
为了避免环境干扰,我们创建一个conda环境,依次使用如下指令进行配置
# 创建conda环境并进入
conda create tensorflow python=3.6
source activate tensorflow
# 安装tensorflow
conda install tensorflow-gpu
# 修改pip源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 安装图像相关软件库
pip install matplotlib Pillow opencv-python
# 安装相关api
pip install -U grpcio grpcio-tools protobuf
pip install tensorflow-serving-api
在写客户端的python文件时,客户端调用必须依赖于tensorflow-serving-api,可直接通过pip安装
pip install tensorflow-serving-api
主要用到了tensorflow_serving.api中的predict_pb2和prediction_service_pb2两个接口,首先tensorflow_serving.api接口为我们创建一个grpc存根,存根允许我们调用远程服务器的方法,是远程连接的必要逻辑,包含了连接的IP地址和端口等信息,然后predict_pb2的PredictRequest用于建立调用请求,然后result = stub.Predict(request)是将这个请求发送并返回结果。
由于要用到object detection API中的可视化接口,即将服务返回的结果画在图片上,因此,我们在object_detection程序中创建myclient_grpc.py文件,先放上完整代码(代码结构详见《TensorFlow之目标检测API接口调试(超详细)》)
from __future__ import print_function
import grpc
import requests
import tensorflow as tf
import cv2
import time
import numpy as np
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
from utils import label_map_util
from utils import visualization_utils as vis_util
tf.app.flags.DEFINE_string('server', '****:8500',
'PredictionService host:port')
tf.app.flags.DEFINE_string('image', 'test.jpg', 'path to image in JPEG format')
FLAGS = tf.app.flags.FLAGS
def main(_):
# 设置grpc
options = [('grpc.max_send_message_length', 1000 * 1024 * 1024),
('grpc.max_receive_message_length', 1000 * 1024 * 1024)]
channel = grpc.insecure_channel(FLAGS.server, options = options)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
request = predict_pb2.PredictRequest()
request.model_spec.name = 'detection'
request.model_spec.signature_name = 'serving_default'
# 输入图片并进行请求
img = cv2.imread(FLAGS.image)
tensor = tf.contrib.util.make_tensor_proto(img, shape=[1]+list(img.shape))
request.inputs['inputs'].CopyFrom(tensor)
start = time.time()
# 法一,速度较慢
# result = stub.Predict(request, 10.0) # 10 secs timeout
# 法二,速度较快
result_future = stub.Predict.future(request, 10.0) # 10 secs timeout
result = result_future.result()
stop = time.time()
print('time is ', stop - start)
# 读取标签配置文件
NUM_CLASSES = 11
label_map = label_map_util.load_labelmap('./data/object-detection.pbtxt')
categories = label_map_util.convert_label_map_to_categories(
label_map, max_num_classes=NUM_CLASSES, use_display_name=True)
category_index = label_map_util.create_category_index(categories)
# 可视化检测结果
boxes = result.outputs['detection_boxes'].float_val
classes = result.outputs['detection_classes'].float_val
scores = result.outputs['detection_scores'].float_val
result = vis_util.visualize_boxes_and_labels_on_image_array(
img,
np.reshape(boxes,[300,4]),
np.squeeze(classes).astype(np.int32),
np.squeeze(scores),
category_index,
use_normalized_coordinates=True,
line_thickness=8)
# 保存结果图片
cv2.imwrite('result.jpg', result)
if __name__ == '__main__':
tf.app.run()
有几处需要注意:
1、设置grpc中,增加grpc.max_send_message_length和grpc.max_receive_message_length设置,如果不加该设置,会报错如下
grpc._channel._Rendezvous: <_Rendezvous of RPC that terminated with:
status = StatusCode.RESOURCE_EXHAUSTED
details = "Sent message larger than max (6220875 vs. 1048576)"
debug_error_string = "{"created":"@1569462805.550691433","description":"Sent message larger than max (6220875 vs. 1048576)","file":"src/core/ext/filters/message_size/message_size_filter.cc","file_line":202,"grpc_status":8}"
2、signature_name设置方法
signature_name要与模型中的定义保持一致,使用如下指令可查看模型情况,
saved_model_cli show --dir='./detection/1' --all
返回结果如下
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['inputs'] tensor_info:
dtype: DT_UINT8
shape: (-1, -1, -1, 3)
name: image_tensor:0
The given SavedModel SignatureDef contains the following output(s):
outputs['detection_boxes'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300, 4)
name: detection_boxes:0
outputs['detection_classes'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300)
name: detection_classes:0
outputs['detection_features'] tensor_info:
dtype: DT_FLOAT
shape: (-1, -1, -1, -1, -1)
name: detection_features:0
outputs['detection_multiclass_scores'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300, 13)
name: detection_multiclass_scores:0
outputs['detection_scores'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300)
name: detection_scores:0
outputs['num_detections'] tensor_info:
dtype: DT_FLOAT
shape: (-1)
name: num_detections:0
outputs['raw_detection_boxes'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300, 4)
name: raw_detection_boxes:0
outputs['raw_detection_scores'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 300, 13)
name: raw_detection_scores:0
Method name is: tensorflow/serving/predict
其中就包含signature_def,以及输入和输出的详细信息,输入输出的shape中-1表示大小不确定,实际使用时会变化
修改完毕后,运行客户端程序,即可生成并保存结果图片,至此,完成一次成功的客户端调用
我们从镜像仓库中拉下TFS的镜像后,做了一些自己的配置,比如导入自己的模型、配置安装其它软件,为了能快速使用和迁移,可以将修改完的容器打包成镜像,下一次启动时,直接基于自己的镜像来创建容器即可,免去重复的工作
首先使用docker ps指令来查看我们运行的容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
80d16401b050 tensorflow/serving:1.12.3-gpu "/usr/bin/tf_serving…" 19 hours ago Up 17 hours 0.0.0.0:8500->8500/tcp, 8501/tcp vigilant_shockley
然后使用docker commit指令来得到一个新的镜像,其命令格式为
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
主要参数选项是
docker commit --change "ENV MODEL_NAME detection" 80d16401b050 myimg
上面我们把有了模型目录的镜像commit得到新的名为myimg的镜像,同时用–change设置环境变量 MODEL_NAME为detection,这样启动的时候就不需要设置环境变量了,运行后返回镜像ID,然后就可以基于自己的镜像创建容器,例如
docker run --runtime=nvidia -p 8500:8500 --mount type=bind,source=$(pwd)/detection,target=/models/detection -t myimg
如果想要将镜像上传到镜像仓库,包括官方或自己的私有镜像仓库,则使用docker push指令,指令格式
docker push NAME[:TAG]
默认上传需要登录,更详细的内容可搜索docker使用教程