前言

初步学习tensorflow serving的手写数字识别模型部署。包括简单的模型训练、保存、部署上线。因为对docker和网络不太熟悉,可能会有部分错误,但是看完博客,能跑通整个流程。此博客将详细介绍流程,但是不详细介绍每个流程的每步的含义,因为这些步骤不会随着任务的不同而发生太大改变。在后续博客中可能会精细介绍每一步的含义。

国际惯例,参考博客:

tensorflow官方文档:低阶API保存和恢复

tensorflow官方文档:tensorflow serving

tensorflow github案例:mnist和resnet

Tensorflow SavedModel模型的保存与加载

如何用TF Serving部署TensorFlow模型

Tensorflow Serving | Tensorflow Serving

Tensorflow使用SavedModel格式模型

我们给你推荐一种TensorFlow模型格式

使用 TensorFlow Serving 和 Docker 快速部署机器学习服务

如何将TensorFlow Serving的性能提高超过70%?

模型构建

跟之前的博客一样,简单搭建一个卷积网络,输入数据是mnist,还有损失函数和评估函数:

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_datasteps = 1000
batch_size = 100
mnist = input_data.read_data_sets('./mnist_dataset',one_hot=True)def conv_network(x):x = tf.reshape(x,[-1,28,28,1])# 第一层卷积conv1 = tf.layers.conv2d(inputs=x,filters=32,kernel_size=[5,5],activation=tf.nn.relu)conv1 = tf.layers.max_pooling2d(conv1,pool_size=[2,2],strides=[2,2])#第二层卷积conv2 = tf.layers.conv2d(inputs=conv1,filters=64,kernel_size=[3,3],activation=tf.nn.relu)conv2 = tf.layers.max_pooling2d(conv2,pool_size=[2,2],strides=[2,2])#第三层卷积conv3 = tf.layers.conv2d(inputs=conv2,filters=32,kernel_size=[3,3],activation=tf.nn.relu)conv3 = tf.layers.max_pooling2d(inputs=conv3,pool_size=[2,2],strides=[2,2])#全连接fc1 = tf.layers.flatten(conv3)fc1 = tf.layers.dense(fc1,500,activation=tf.nn.relu)#输出分类fc2 = tf.layers.dense(fc1,10)return fc2#输入输出容器
input_x = tf.placeholder(dtype=tf.float32,shape=[None,28*28],name='X')
input_y = tf.placeholder(dtype=tf.int32,shape=[None,10])#损失函数
model = conv_network(input_x)
logit_loss = tf.nn.softmax_cross_entropy_with_logits_v2(labels=input_y,logits=model)
optimize = tf.train.AdamOptimizer(0.001).minimize(logit_loss)
#评估
pred_equal = tf.equal(tf.arg_max(model,1),tf.arg_max(input_y,1))
accuracy = tf.reduce_mean(tf.cast(pred_equal,tf.float32))

模型保存

传统方法checkpoint

这部分就不细说了,我们之前训练模型基本都是这个方法:

init = tf.global_variables_initializer()
saver = tf. train.Saver(max_to_keep=1)
tf.add_to_collection('pred',model)with tf.Session() as sess:sess.run(init)for step in range(steps):data_x,data_y = mnist.train.next_batch(batch_size)test_x,test_y = mnist.test.next_batch(1000)        train_acc = sess.run(optimize,feed_dict={input_x:data_x,input_y:data_y})        if(step % 100==0 or step==1):accuracy_val = sess.run(accuracy,feed_dict={input_x:data_x,input_y:data_y})print('steps:{0},val_loss:{1}'.format(step,accuracy_val))#保存模型print('train finished!')saver.save(sess,'./model/cnn')

主要就是利用tf.train.Saver保存训练好的模型

tesorflow serving准备的模型保存方法

第一步:准备好模型需要保存的位置以及版本控制:

model_version = 1 #版本控制
export_path_base = '/tmp/cnn_mnist'
export_path =  os.path.join(tf.compat.as_bytes(export_path_base),tf.compat.as_bytes(str(model_version)))
print('Exporting trained model to',export_path)
builder = tf.saved_model.builder.SavedModelBuilder(export_path)tensor_info_x = tf.saved_model.utils.build_tensor_info(input_x)
tensor_info_y = tf.saved_model.utils.build_tensor_info(model)
prediction_signature = (tf.saved_model.signature_def_utils.build_signature_def(inputs={'images':tensor_info_x},outputs={'scores':tensor_info_y},method_name = tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

此处注意,如果你的export_path_base/model_version目录存在,将会报错,因为tensorflow serving有一个有点就是在更新模型的时候,无需停止服务,服务是根据版本来控制的,所以每次训练都是一个新版本。而且这个模型最好是绝对路径,因为后续部署服务的时候,模型不能是相对路径。

错误提示:

AssertionError: Export directory already exists. Please specify a different export directory: b'/tmp/cnn_mnist/1'

第二步:将输入输出打包起来,方便从客户端接收参数

prediction_signature = (tf.saved_model.signature_def_utils.build_signature_def(inputs={'images':tensor_info_x},outputs={'scores':tensor_info_y},method_name = tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

注意这里有个method_name, 这个需要用tf.saved_model.signature_constants.里面的一系列NAME,因为后续客户端传递给服务端的请求是json格式,而predcitregressclassify任务的json格式有区别,具体格式看这里,当然后面也会讲到

第三步:就是在Session中保存模型了

#训练与保存
with tf.Session() as sess:sess.run(tf.global_variables_initializer())for step in range(steps):data_x,data_y = mnist.train.next_batch(batch_size)test_x,test_y = mnist.test.next_batch(1000)        train_acc = sess.run(optimize,feed_dict={input_x:data_x,input_y:data_y})        if(step % 100==0 or step==1):accuracy_val = sess.run(accuracy,feed_dict={input_x:data_x,input_y:data_y})print('steps:{0},val_loss:{1}'.format(step,accuracy_val))#保存模型    builder.add_meta_graph_and_variables(sess,[tf.saved_model.tag_constants.SERVING],signature_def_map = {'predict_images':prediction_signature},main_op = tf.tables_initializer(),strip_default_attrs = True)builder.save()print('Done exporting')

这一步,官方文档有详细介绍,具体参数的使用没仔细看,目前只需要前面三个必须传入sesstagsignature_def_map,重点是将上面定义的包含输入输出与任务种类的prediction_signature传进来。给个名字predict_images是为了后续调用服务的时候,说明我们要调用哪个服务,所以这个signature_def_map理论上应该可以包含多个任务接口,而官方例子也有相关操作:

  builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING],signature_def_map={'predict_images':prediction_signature,tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:classification_signature,},main_op=tf.tables_initializer(),strip_default_attrs=True)

至此,为tensorflow serving提供的模型文件是如何训练和保存的已经介绍完毕,在下一篇博客应该会探索如何将训练好的checkpoint转换为tensorflow serving可使用的模型文件。

通过docker部署模型

安装docker的方法在这里能找到,或者docker官方文档我当时好像就一句话搞定:

sudo apt install docker.io

因为我以前没装过docker,服务器上用过一丢丢。

tensorflow serving镜像

首先拉取tensorflow的镜像:

docker pull tensorflow/serving

有时候由于环境限制,可以从别人pull好的镜像中恢复,镜像的导出和导入可参考此处,主要用到了:

有镜像的电脑导出镜像:

docker save 582a4 > tensorflow_serving.tar

其中582a4是用docker images查看的tensorflow/serving的ID。

无镜像的电脑导入镜像:

docker load < tensorflow_serving.tar

通常导入以后,REPOSITORYTAGnone,最好给个名区分:

docker tag 91abe tensorflow/serving:latest

这里我备用了一份tensorflow/serving镜像:

链接: https://pan.baidu.com/s/1l_ZGVkRKcP4HgSKxGgekRA 提取码: ewqv

启动在线服务

方法一:

docker run -p 9500:8500 -p:9501:8501 \
--mount type=bind,source=/tmp/cnn_mnist,target=/models/cnn_mnist \
-e MODEL_NAME=cnn_mnist -t tensorflow/serving

这句话的意思就是:

  • 启动docker容器container

  • 实现gRPCREST端口到主机端口的映射,注意,port1:port2,前者是主机端口,后者是tensorflow serving dockergRPCREST端口。主机端口port1可以随便改,只要没被占用,但是tensorflow serving docker的两个端口固定,不能动。

终端通过sudo netstat -nap可以看到tcp6中开启了两个端口,分别就是95009501

运行容器后的最后一部分输出是

2019-09-03 11:21:48.489776: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:103] No warmup data file found at /models/cnn_mnist/1/assets.extra/tf_serving_warmup_requests
2019-09-03 11:21:48.489938: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: cnn_mnist version: 1}
2019-09-03 11:21:48.504477: I tensorflow_serving/model_servers/server.cc:324] Running gRPC ModelServer at 0.0.0.0:8500 ...
[warn] getaddrinfo: address family for nodename not supported
2019-09-03 11:21:48.519991: I tensorflow_serving/model_servers/server.cc:344] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 239] RAW: Entering the event loop ...

可以发现,服务端自动查找新模型,同事给出了gRPCREST的端口,但是这连个端口貌似用不了,难道是因为我们做映射了?后面所有的访问,无论是用dockerip还是用hostip,一律通过ip:9500/9501接收请求。

方法二

docker run -t --rm -p 9500:8500 -p:9501:8501 \-v "/tmp/cnn_mnist:/models/cnn_mnist" \-e MODEL_NAME=cnn_mnist \tensorflow/serving

其实和上面一样,只不过对docker的用法不同而已。

如果对docker比较熟悉,可以两种方法都记住,不熟悉的话,熟记一种方法就行了。

测试服务是否开通

下面的dockeriphostip分别为ifconfig -a查出来的dockerhostip

  • 测试1:
    输入:curl http://localhost:8501/v1/models/cnn_mnist
    输出:curl: (7) Failed to connect to localhost port 8501: 拒绝连接

  • 测试2:
    输入:curl http://localhost:8500/v1/models/cnn_mnist
    输出:curl: (7) Failed to connect to localhost port 8500: 拒绝连接

  • 测试3:
    输入:curl http://dockerip:9500/v1/models/cnn_mnist
    输出:

    Warning: Binary output can mess up your terminal. Use "--output -" to tell
    Warning: curl to output it to your terminal anyway, or consider "--output
    Warning: <FILE>" to save to a file.
    

    说明没有拒绝连接

  • 测试4:
    输入:curl http://hostip:9500/v1/models/cnn_mnist
    输出:

    Warning: Binary output can mess up your terminal. Use "--output -" to tell
    Warning: curl to output it to your terminal anyway, or consider "--output
    Warning: <FILE>" to save to a file.
    

    说明没有拒绝连接

  • 测试5:
    输入:curl http://dockerip:9501/v1/models/cnn_mnist
    输出:

    {"model_version_status": [{"version": "1","state": "AVAILABLE","status": {"error_code": "OK","error_message": ""}}]
    }
    

    没拒绝连接

  • 测试6:
    输入:curl http://hostip:9501/v1/models/cnn_mnist
    输出:

    {"model_version_status": [{"version": "1","state": "AVAILABLE","status": {"error_code": "OK","error_message": ""}}]
    }
    

暂时测试这几种情况,其余组合自己可以测试看看,如果拒绝连接,那就说明ip和对应接口组合不通,无法调用服务。

调用模型

上面说过了,tensorflow serving有两类端口:gRPCREST API,关于这两个区别,可以查看这里,下面分别讲解tensorflow serving中分别怎么请求和解析返回数据。

注意手写数字识别模型接受的是$ (None,784) $的向量

使用gRPC

引入必要包:

import argparse
import tensorflow as tf
from tensorflow_serving.apis  import predict_pb2,prediction_service_pb2_grpc
import grpc
import numpy as np
import cv2

定义入口接收参数:

parser = argparse.ArgumentParser(description='mnist recognization client')
parser.add_argument('--host',default='0.0.0.0',help='serve host')
parser.add_argument('--port',default='9000',help='serve port')
parser.add_argument('--image',default='',help='image path')
FLAGS = parser.parse_args()

所以,用户需要输入的都有:ip、端口、输入图像

读取图像:

img = cv2.imread(FLAGS.image,cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img,(28,28))
_,img = cv2.threshold(img,250,255,cv2.THRESH_BINARY)
img = np.array(img,dtype='float32')
img = img.reshape((28*28))
print(img.shape)  #(784,)

连接服务

server = FLAGS.host + ':' + FLAGS.port
channel = grpc.insecure_channel(server)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)

请求服务

request = predict_pb2.PredictRequest()
request.model_spec.name = 'cnn_mnist'
request.model_spec.signature_name = 'predict_images'
request.inputs['images'].CopyFrom(tf.contrib.util.make_tensor_proto(img,shape=[1,28*28]))
result = stub.Predict(request,10.0)

【注】

  • 这里有prediction_service_pb2_grpcpredict_pb2,那么是否有classifyregress对应库呢?后面学习的时候再看。
  • 还有就是因为模型接收的是tensor,所以得用tf.contrib.util.make_tensor_proto转换

解析请求

scores=result.outputs['scores'].float_val
pred_label = np.argmax(scores)
print('pred_label',pred_label)

【注】C++的解析方法戳这里,python的解析方法戳这里

运行测试

在终端中执行:

python serving_test_grpc.py --host '127.0.0.1' --port '9500' --image './test_image/6.png'

这里面的host换成docker或者主机的ipport换成你上面开启的端口。

使用REST

gRPC区别很大,需要用json作为请求的输入格式,具体格式查阅这里,我们使用predict API中的格式:

{// (Optional) Serving signature to use.// If unspecifed default serving signature is used."signature_name": <string>,// Input Tensors in row ("instances") or columnar ("inputs") format.// A request can have either of them but NOT both."instances": <value>|<(nested)list>|<list-of-objects>"inputs": <value>|<(nested)list>|<object>
}

引入相关包:

import requests
import numpy as np
import cv2

读取数据:注意最后转换为(1,784)(1,784)(1,784)的list

image_path='./test_image/2.png'
img = cv2.imread(image_path,cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img,(28,28))
_,img = cv2.threshold(img,250,255,cv2.THRESH_BINARY)
img = np.array(img,dtype='float32')
img = img.reshape((28*28))
img = img[np.newaxis,:]
img = img.tolist()

json格式化请求:此处一定要严格按照下面的语句书写,不然请求很容易失败

predict_request='{"signature_name": "predict_images", "instances":[{"images":%s}] }' %img

请求失败时,提示

requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http://127.0.0.1:9501/v1/models/cnn_mnist:predict

如果提示这个bad request,不要问为什么,问就是你写错json请求了。

发送请求与接收回复以及解析

response = requests.post(SERVER_URL, data=predict_request)
response.raise_for_status()
prediction = response.json()['predictions'][0]
print('label:',np.argmax(prediction))

response.elapsed.total_seconds()可以返回时间,用于测试效率.

使用tensorflow_model_server启动服务

安装

按照官方文档走:

  • 第一步

    echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list && \
    curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
    
  • 第二步

    apt-get update && apt-get install tensorflow-model-server
    
  • 第三步

    apt-get upgrade tensorflow-model-server
    

这个是在线方法,还有一个离线方法,我就不写了,戳这里就行,听说离线编译方法的成功率有点低。以后有机会再试试。

启动服务

一条命令搞定

tensorflow_model_server --port=9500 --rest_api_port=9501 \--model_name=cnn_mnist --model_base_path=/tmp/cnn_mnist

就是直接开启gRPC端口为9500以及开启REST端口为9501,剩下的请求服务与上面的docker教程一模一样。

有些小问题总结

  • 端口占用

    有时候提示端口被占用

    如果使用docker的方法启动服务,可以使用docker ps看启动的服务占用的端口,如果有,就用docker kill CONTAINER_ID

    如果使用tensorflow_model_server启动服务,使用netstat -nap查找端口被谁占用,然后kill -9 PID

  • 重启docker容器
    当你killdocker里面的容器时,并非移除了该容器,可以通过docker ps -a查看所有容器,包括关闭容器,当你再次启动服务的时候,没必要去执行docker run .....的那个脚本,直接docker start CONTAINER_ID即可。

  • 官方有个比较好的例子,是调用resnet作为分类服务的
    模型下载方法:

    #https://storage.googleapis.com/download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz
    mkdir /tmp/resnet
    curl -s https://storage.googleapis.com/download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz | tar --strip-components=2 -C /tmp/resnet -xvz
    

网盘下载:
链接: https://pan.baidu.com/s/1Kyh8sGggdKld4u1wuQSAbA 提取码: 4k3z
服务启动方法:

docker run -p 8500:8500 -p 8501:8501 \
--mount type=bind,source=/tmp/resnet,target=/models/resnet \
-e MODEL_NAME=resnet -t tensorflow/serving
  • 检查模型的输入输出

    saved_model_cli show --dir /tmp/cnn_mnist/1/ --all
    

    输出:

    signature_def['predict_images']:The given SavedModel SignatureDef contains the following input(s):inputs['images'] tensor_info:dtype: DT_FLOATshape: (-1, 784)name: X:0The given SavedModel SignatureDef contains the following output(s):outputs['scores'] tensor_info:dtype: DT_FLOATshape: (-1, 10)name: dense_1/BiasAdd:0Method name is: tensorflow/serving/predict
    
  • 线上调用
    如果用其他ip或者电脑调用模型,请求的ip必须是host,而非localhost

后记

这里只是一个初步入门,后续会更进一步了解其他功能。

本文所有代码打包下载:

链接: https://pan.baidu.com/s/1MOUnU-sUAxfOjAHSPDKvkA 提取码: sa88

里面包含我调试的脚本,懒得剔除了,有兴趣慢慢看

【TensorFlow-serving】初步学习模型部署相关推荐

  1. nvidia-docker2完成tensorflow/serving深度学习模型在线部署

    深度学习技术已经广泛应用在各个行业领域.实际应用,通过大量数据往往可以训练一个泛化能力好的模型,但如何将模型进行快捷.方便的远程部署,已成为好多企业考虑的问题.现阶段,常用的深度学习模型远程部署工具有 ...

  2. 深度学习模型部署技术方案

    深度学习模型部署技术方案 训练好的深度学习模型如何进行部署的相关技术方案 1 什么是模型部署? 2 数据科学项目整个开发流程 3 使用flask 将 Keras深度学习模型部署为Web应用程序 4 T ...

  3. 基于web端和C++的两种深度学习模型部署方式

    深度学习Author:louwillMachine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基... 深度学习 Author:l ...

  4. 深度学习模型部署简要介绍

    一.模型部署简介 近几年来,随着算力的不断提升和数据的不断增长,深度学习算法有了长足的发展.深度学习算法也越来越多的应用在各个领域中,比如图像处理在安防领域和自动驾驶领域的应用,再比如语音处理和自然语 ...

  5. 【深度学习】基于web端和C++的两种深度学习模型部署方式

    深度学习 Author:louwill Machine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署 ...

  6. pytorch基于web端和C++的两种深度学习模型部署方式

    本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署. 基于web服务端的模型部署,主要是通过REST API的形式来提供接口方便调用 ...

  7. 为什么将表格的method改为post后就无法工作_用Python将Keras深度学习模型部署为Web应用程序...

    构建一个很棒的机器学习项目是一回事,但归根结底,你希望其他人能够看到你的辛勤工作.当然,你可以将整个项目放在GitHub上,但是怎么让你的祖父母也看到呢?我们想要的是将深度学习模型部署为世界上任何人都 ...

  8. 面向生产环境!深度学习模型部署资源全辑

    点上方计算机视觉联盟获取更多干货 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:机器学习实验室   Author:louwill Machine Learning Lab AI博士笔记系列推 ...

  9. 深度学习模型部署学习一

    深度学习模型部署 学习链接:模型部署入门教程(一):模型部署简介 写在前面: 本文档为学习上述链接的相关记录,基本内容一致,仅用于学习用途,若侵权请联系我删除 目   录 深度学习模型部署 1 为什么 ...

最新文章

  1. Java设计模式(十四):MVC设计模式
  2. Mysql HA实现MYSQL的高可用(上)
  3. c语言 linux sd_Linux内核组成部分(二)
  4. 让vue文件直接在浏览器中运行
  5. ubuntu里面的任务管理器
  6. mfc 二进制转换成图像_图像相似度---感知哈希算法(phash)---用matlab实现
  7. Ext.Net动态创建按钮-动态添加事件
  8. 思翼FM30高频头蓝牙数传连接地面站远距离通信功能
  9. 苹果支付Java后台总结
  10. uni-app 快速集成 IM 即时通信的方法——TUIKit 来啦
  11. 百度地图 web 开发 BMap.point格式
  12. 机器学习之二:回归分析
  13. 一个Callable接口能有多少知识点?在裁员的大背景下,我仍然吊打了大厂面试官
  14. 梦江湖获取服务器信息,《一梦江湖》6月5日更新公告
  15. NOIP 2001 一元三次方程求解(二分||盛金公式)
  16. .NET序列化工具Jil、Json.NET和Protobuf的简单测评
  17. Xv6 Operating System Organization
  18. 软考高级-系统架构师-第四章系统开发基础知识
  19. icg不支持mysql_MongoDB与MySQL关于写确认的异同
  20. 统计图之小提琴图解析

热门文章

  1. android遥控器按键传输流程,android 海思平台遥控器按键映射流程
  2. 成绩查询系统c语言,学生成绩查询系统C语言代码(分服务器端和客户端).doc
  3. 875. 爱吃香蕉的珂珂(二分)
  4. php图片显示框,【转】php让图片弹出下载框的方法
  5. 有趣java_有趣的Java
  6. powershell 开发入门_详谈Ubuntu PowerShell(小白入门必看教程)
  7. android blcr 编译,BLCR 基本环境搭建【zz~】
  8. python新闻聚合_使用python对数据进行高效处理,包你玩转分组聚合
  9. 抖音云控_抖音云控/快手云控:引领5G电商短视频新潮流
  10. 表格布局页面_对于表格布局管理器的回顾以及接下来的目标