【TensorFlow-serving】初步学习模型部署
前言
初步学习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
格式,而predcit
、regress
、classify
任务的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')
这一步,官方文档有详细介绍,具体参数的使用没仔细看,目前只需要前面三个必须传入sess
、tag
、signature_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
通常导入以后,REPOSITORY
和TAG
是none
,最好给个名区分:
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
实现
gRPC
和REST
端口到主机端口的映射,注意,port1:port2
,前者是主机端口,后者是tensorflow serving docker
的gRPC
和REST
端口。主机端口port1
可以随便改,只要没被占用,但是tensorflow serving docker
的两个端口固定,不能动。
终端通过sudo netstat -nap
可以看到tcp6
中开启了两个端口,分别就是9500
和9501
。
运行容器后的最后一部分输出是
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 ...
可以发现,服务端自动查找新模型,同事给出了gRPC
和REST
的端口,但是这连个端口貌似用不了,难道是因为我们做映射了?后面所有的访问,无论是用docker
的ip
还是用host
的ip
,一律通过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
比较熟悉,可以两种方法都记住,不熟悉的话,熟记一种方法就行了。
测试服务是否开通
下面的dockerip
与hostip
分别为ifconfig -a
查出来的docker
和host
的ip
测试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
有两类端口:gRPC
和REST 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_grpc
和predict_pb2
,那么是否有classify
和regress
对应库呢?后面学习的时候再看。 - 还有就是因为模型接收的是
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
或者主机的ip
,port
换成你上面开启的端口。
使用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
容器
当你kill
掉docker
里面的容器时,并非移除了该容器,可以通过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】初步学习模型部署相关推荐
- nvidia-docker2完成tensorflow/serving深度学习模型在线部署
深度学习技术已经广泛应用在各个行业领域.实际应用,通过大量数据往往可以训练一个泛化能力好的模型,但如何将模型进行快捷.方便的远程部署,已成为好多企业考虑的问题.现阶段,常用的深度学习模型远程部署工具有 ...
- 深度学习模型部署技术方案
深度学习模型部署技术方案 训练好的深度学习模型如何进行部署的相关技术方案 1 什么是模型部署? 2 数据科学项目整个开发流程 3 使用flask 将 Keras深度学习模型部署为Web应用程序 4 T ...
- 基于web端和C++的两种深度学习模型部署方式
深度学习Author:louwillMachine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基... 深度学习 Author:l ...
- 深度学习模型部署简要介绍
一.模型部署简介 近几年来,随着算力的不断提升和数据的不断增长,深度学习算法有了长足的发展.深度学习算法也越来越多的应用在各个领域中,比如图像处理在安防领域和自动驾驶领域的应用,再比如语音处理和自然语 ...
- 【深度学习】基于web端和C++的两种深度学习模型部署方式
深度学习 Author:louwill Machine Learning Lab 本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署 ...
- pytorch基于web端和C++的两种深度学习模型部署方式
本文对深度学习两种模型部署方式进行总结和梳理.一种是基于web服务端的模型部署,一种是基于C++软件集成的方式进行部署. 基于web服务端的模型部署,主要是通过REST API的形式来提供接口方便调用 ...
- 为什么将表格的method改为post后就无法工作_用Python将Keras深度学习模型部署为Web应用程序...
构建一个很棒的机器学习项目是一回事,但归根结底,你希望其他人能够看到你的辛勤工作.当然,你可以将整个项目放在GitHub上,但是怎么让你的祖父母也看到呢?我们想要的是将深度学习模型部署为世界上任何人都 ...
- 面向生产环境!深度学习模型部署资源全辑
点上方计算机视觉联盟获取更多干货 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:机器学习实验室 Author:louwill Machine Learning Lab AI博士笔记系列推 ...
- 深度学习模型部署学习一
深度学习模型部署 学习链接:模型部署入门教程(一):模型部署简介 写在前面: 本文档为学习上述链接的相关记录,基本内容一致,仅用于学习用途,若侵权请联系我删除 目 录 深度学习模型部署 1 为什么 ...
最新文章
- Java设计模式(十四):MVC设计模式
- Mysql HA实现MYSQL的高可用(上)
- c语言 linux sd_Linux内核组成部分(二)
- 让vue文件直接在浏览器中运行
- ubuntu里面的任务管理器
- mfc 二进制转换成图像_图像相似度---感知哈希算法(phash)---用matlab实现
- Ext.Net动态创建按钮-动态添加事件
- 思翼FM30高频头蓝牙数传连接地面站远距离通信功能
- 苹果支付Java后台总结
- uni-app 快速集成 IM 即时通信的方法——TUIKit 来啦
- 百度地图 web 开发 BMap.point格式
- 机器学习之二:回归分析
- 一个Callable接口能有多少知识点?在裁员的大背景下,我仍然吊打了大厂面试官
- 梦江湖获取服务器信息,《一梦江湖》6月5日更新公告
- NOIP 2001 一元三次方程求解(二分||盛金公式)
- .NET序列化工具Jil、Json.NET和Protobuf的简单测评
- Xv6 Operating System Organization
- 软考高级-系统架构师-第四章系统开发基础知识
- icg不支持mysql_MongoDB与MySQL关于写确认的异同
- 统计图之小提琴图解析
热门文章
- android遥控器按键传输流程,android 海思平台遥控器按键映射流程
- 成绩查询系统c语言,学生成绩查询系统C语言代码(分服务器端和客户端).doc
- 875. 爱吃香蕉的珂珂(二分)
- php图片显示框,【转】php让图片弹出下载框的方法
- 有趣java_有趣的Java
- powershell 开发入门_详谈Ubuntu PowerShell(小白入门必看教程)
- android blcr 编译,BLCR 基本环境搭建【zz~】
- python新闻聚合_使用python对数据进行高效处理,包你玩转分组聚合
- 抖音云控_抖音云控/快手云控:引领5G电商短视频新潮流
- 表格布局页面_对于表格布局管理器的回顾以及接下来的目标