用Docker容器自带的tensorflow serving部署模型对外服务

相信很多人和我一样,在试图安装tensorflow serving的时候,翻遍了网上的博客和官网文档,安装都是以失败而告终,我也是一样,这个问题折磨了我两个星期之久,都快放弃了。幸运的是在同事的建议下,我采用了一种迂回的策略安装成功了。

 

我们采用的策略是:

        pull一个已经安装好了tensorflow serving的docker镜像,替换它自带的一些模型为我们自己的模型。

 

步骤:

1、拉取带tensorflow serving的docker镜像,这样我们服务器上就有了一个安装了ModelServer的docker容器, 这个容器就可以看做一台虚拟机,这个虚拟机上已经安装好了tensorflow serving,环境有了,就可以用它来部署我们的模型了。注意这个拉取下来后不是直接放在当前目录的,而是docker默认存储的路径,这个是个docker容器,和第2步clone下来的不是同一个东西

$docker pull tensorflow/serving

2、获取例子模型:(当然,也可以直接用上面容器中自带的例子),当然这里是直接拉取了tensorflow serving的源码,源码中有一些训练好的例子模型

  1. $cd /root/software/
  2. $git clone https://github.com/tensorflow/serving

3、用第一步拉取的docker容器运行例子模型

第2步中clone下来的serving源码中有这样一个训练好的例子模型,路径为:

/root/software/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_cpu

现在我们就要用第1步拉下来的docker容器来运行部署这个例子模型

  1. $docker run -p 8501:8501 \
  2. --mount type=bind,\
  3. source=/root/software/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_cpu,\
  4. target=/models/half_plus_two \
  5. -e MODEL_NAME=half_plus_two -t tensorflow/serving &

参数说明:

  1. --mount: 表示要进行挂载
  2. source: 指定要运行部署的模型地址, 也就是挂载的源,这个是在宿主机上的模型目录
  3. target: 这个是要挂载的目标位置,也就是挂载到docker容器中的哪个位置,这是docker容器中的目录
  4. -t: 指定的是挂载到哪个容器
  5. -p: 指定主机到docker容器的端口映射
  6. docker run: 启动这个容器并启动模型服务(这里是如何同时启动容器中的模型服务的还不太清楚)
  7. 综合解释:
  8. 将source目录中的例子模型,挂载到-t指定的docker容器中的target目录,并启动

这步注意,如果执行报错无法识别type=bind, 那应该是source的路径有问题

4、调用这个服务,这里用的http接口

  1. $curl -d '{"instances": [1.0, 2.0, 5.0]}' \
  2. -X POST http://localhost:8501/v1/models/half_plus_two:predict

得到的结果如下:

{ "predictions": [2.5, 3.0, 4.5] }

这就表明服务已经部署成功了,当然你也可以用requests来模型上述http请求

5、查看启动的这个模型的目录的结构

我们可以看到启动服务的命令有一个参数:

source=/root/software/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_cpu

这实际就是模型的位置, 我们进入到这个目录下(这个目录基于自己pull时所在的目录),可以看到里面是一个名为00000123的目录,这实际是模型的版本,再进入到这个目录下可以看到一个如下两个文件:

saved_model.pb, variables

variable目录下有如下两个文件:

variables.data-00000-of-00001, variables.index

6、用自己的模型替换上述half_plus_two模型

我在和saved_model_half_plus_two_cpu模型同级的目录下创建了一个文件夹,名为textcnnrnn, 这是我模型的名称,然后

  1. $cd textcnnrnn
  2. $mkdir 00000123
  3. $cd 00000123
  4. $mkdir variables
  5. $cd variables

我一开始是直接用的我之前训练好的模型放到了variables目录下,我训练好的模型包含如下几个文件:

best_validation.data-00000-of-00001  best_validation.index  best_validation.meta  checkpoint

相信大家都看出来了,这个是用这种方式保存的:

  1. saver = tf.train.Saver()
  2. saver.save(sess=session, save_path=save_path)

于是我激动的去重新启动我的模型,当然这里要修改模型的地址,我也把我的模型的名字改了下:

docker run -p 8501:8501 --mount source=/root/software/serving/tensorflow_serving/servables/tensorflow/testdata/textcnnrnn,type=bind,target=/models/find_lemma_category -e MODEL_NAME=find_lemma_category -t tensorflow/serving &

可是这个时候报错了,做法不对。下面是正确的做法。

其实仔细比较我的模型的几个文件和half_plus_two模型的下的文件的结构根本不一样,怎么办呢? 其实应该对模型的格式进行转换。 代码如下:

  1. # coding: utf-8
  2. from __future__ import print_function
  3. import pdb
  4. import time
  5. import os
  6. import tensorflow as tf
  7. import tensorflow.contrib.keras as kr
  8. from cnn_rnn_model import TCNNRNNConfig, TextCNNRNN
  9. save_path = 'model_saver/textcnnrnn/best_validation'
  10. try:
  11. bool(type(unicode))
  12. except NameError:
  13. unicode = str
  14. config = TCNNRNNConfig()
  15. def build_and_saved_wdl():
  16. model = TextCNNRNN(config) #我自己的模型结构是在这个类中定义的,基于自己的模型进行替换
  17. session = tf.Session()
  18. session.run(tf.global_variables_initializer())
  19. saver = tf.train.Saver()
  20. saver.restore(sess=session, save_path=save_path)
  21. # 将训练好的模型保存在model_name下,版本为2,当然你的版本可以随便写
  22. builder = tf.saved_model.builder.SavedModelBuilder("./model_name/2")
  23. inputs = {
  24. #注意,这里是你预测模型的时候需要传的参数,调用模型的时候,传参必须和这里一致
  25. #这里的model.input_x和model.keep_prob就是模型里面定义的输入placeholder
  26. "input_x": tf.saved_model.utils.build_tensor_info(model.input_x),
  27. "keep_prob": tf.saved_model.utils.build_tensor_info(model.keep_prob)
  28. }
  29. #model.y_pred_cls是模型的输出, 预测的时候就是计算这个表达式
  30. output = {"output": tf.saved_model.utils.build_tensor_info(model.y_pred_cls)}
  31. prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(
  32. inputs=inputs,
  33. outputs=output,
  34. method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
  35. )
  36. builder.add_meta_graph_and_variables(
  37. session,
  38. [tf.saved_model.tag_constants.SERVING],
  39. {tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: prediction_signature}
  40. )
  41. builder.save()
  42. if __name__ == '__main__':
  43. build_and_saved_wdl()

执行后,会在当前目录下生成一个名称为./model_name/2的文件夹, 这个文件夹下的文件格式和halt_plus_two中的文件格式是一致的了,这下肯定没错了。

将./model_name/2文件夹下的内容拷贝到textcnnrnn/00000123目录下即可。

重新启动模型,这次启动成功了,没有报错,说明我们的模型已经被识别成功。

7、调用模型

咋调啊?咋传参数啊?懵逼,先看看调用自带的模型怎么传参数的吧:

  1. curl -d '{"instances": [1.0, 2.0, 5.0]}' \
  2. -X POST http://localhost:8501/v1/models/half_plus_two:predict

看样子instances应该是参数的名字,于是我想看看tensorflow serving源码里面是怎么解析这个参数的,所以我在源码根目录下全局搜索了这个关键字,在根目录下搜索关键词instances:

$find . -name '*.*' | xargs grep -l instances

可以找到一个名为json_tensor.h的文件,这个文件详细介绍了不同的传参的方式:

instances是一个list,list中每个元素是一个待预测实例,每个实例里面是所有参数的值, 所以参数按照这种方式构造就可以了。

这里json.dumps的时候可能会遇到一个序列化的错误,原因是json.dumps对于含numpy.array类型的数据无法序列化, 可以构造一个编码器, 然后作为json.dumps参数:

  1. class NumpyEncoder(json.JSONEncoder):
  2. def default(self, obj):
  3. if isinstance(obj, np.ndarray):
  4. return obj.tolist()
  5. return json.JSONEncoder.default(self, obj)
  1. p_data = {"keep_prob": 1.0, "input_x": x_test[0]}
  2. param = {"instances": [p_data]}
  3. param = json.dumps(param, cls=NumpyEncoder)
  4. res = requests.post('http://localhost:8501/v1/models/find_lemma_category:predict', data=param)

这样就大功告成了!

这里还有一个地方需要注意:其实我的模型Input_x本身是直接可以接收多个实例的,也就是上面我的参数x_test是多个实例构造的参数,但是直接传入会出错,所以我只能传入一个实例x_test[0]。 如果想同时预测多个的话只能这样构造参数:

  1. data1 = {"keep_prob": 1.0, "input_x": x_test[0]}
  2. data2 = {"keep_prob": 1.0, "input_x": x_test[1]}
  3. data3 = {"keep_prob": 1.0, "input_x": x_test[2]}
  4. param = {"instances": [data1, data2, data3]}
  5. param = json.dumps(param, cls=NumpyEncoder)
  6. res = requests.post('http://localhost:8501/v1/models/find_lemma_category:predict', data=param)

8、参数要预处理怎么办?

假如我们需要在将参数输入模型之前做一些预处理怎么办?比如要对大段文本进行分词等等。

解决办法: 部署一个中转服务,我采用的策略是用tornado再部署一个服务,这个服务负责对业务方传输过来的参数进行预处理,处理成模型需要的格式后,再传输给模型, 所以我的结构是这样的:

业务方 ==>  tornado服务(参数预处理) ==> 模型(tensorflow serving服务)

这里面的两次远程调用都是http协议。

 

参考地址:

    https://www.tensorflow.org/serving/docker

    https://www.jianshu.com/p/2fffd0e332bc

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C资源

云风最近写了一篇博客《C语言的前世今生》。作为长期使用C语言开发网络游戏服务器的程序员,云风是有理由写这样一篇文字,不过还是感觉谈的不够深入,C语言在业界使用的现状没有怎么描写,有些意犹未尽。在这里想比较系统的谈谈个人对…

面试题汇总---深度学习(图像识别,NLP内容)

文章目录1.基本概念1.1 为什么神经网络中深度网络的表现比广度网络表现好?1.2 推导BP算法1.3 什么是梯度消失和梯度爆炸?1.4 常用的激活函数有哪些?1.5 常用的参数更新方法有哪些?1.6 解决过拟合的方法?数据层面模型层…

Python(28)-文件,os模块

文件1. 文件2. 文件的基本操作3. 读取文件open()3.1 文件指针: 标记从哪一个位置开始读取数据.3.2 文件的打开方式mode3.3 文件按行读取3.3.1 readline()3.3.2 readlines()4.文件输出f.write(),print()5.文件复制5.1 小文件复制(搬家)5.2 大文件复制&…

IOCP的程序

C代码 #include <winsock2.h> #include <mswsock.h> #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <assert.h> #include "vld.h" #pragma message("automatic link to ws2_32.lib and…

PaperNotes(3)-图像分割-RCNN-FCN-Boxsup

图像分割算法对比小结1.{基本概念}2.{R-CNN}2.1R-CNN 网络结构选择性搜索算法为什么选择SVM作分类器边框回归2.2{R-CNN 训练}2.3{R-CNN实验结果}2.4{R-CNN语义分割}2.5{补充材料}2.5.1{R-CNN建议区域放缩}2.5.2{IOU阈值设置不一样的原因}2.5.3{Bounding-box回归修正}2.6{R-CNN存…

Python模块(3)--PIL 简易使用教程

PIL模块-用与记1.图片导入Image.open()2.图像显示.show()4.查看图片属性.format,.size,.mode3.图像格式转换.convert()4.图像模式“L”&#xff0c;“RGB”,"CYMK"5. 图片旋转.rotate()旋转方式1&#xff1a;旋转不扩展旋转方式2&#xff1a;旋转扩展旋转方式3&#…

输入输出系统

I/O设备&#xff1a;输入输出和存储功能的设备 I/O设备的分类 按传输的速度&#xff1a; 低速设备&#xff08;如键盘、鼠标、语音输入输出设备&#xff09; 中速设备&#xff08;如行式打印机、激光打印机等&#xff09; 高速设备&#xff08;如磁带机、磁盘机、光盘机等&…

模拟银行家算法

介绍 data.h #ifndef _Data_h_ #define _Data_h_#include <stdio.h> #include <stdlib.h> #include <string.h>#define ElemType PCB #define Status int #define true 1 #define false 0 #define OK 1 #define ERROR 0 #define RESOURCE_NUM …

js知识点汇总

1.本门课的作用&#xff08;JavaScript的作用&#xff09;所有基于Web的程序开发基础 2.一种计算机客户端脚本语言&#xff0c;主要在Web浏览器解释执行。 3.浏览器中Javascript&#xff0c;用于与用户交互&#xff0c;以及实现页面中各种动态特效 4.在HTML文件中&#xff0…

关于Java中String的问题

String 对象的两种创建方式&#xff1a; String str1 "abcd";//先检查字符串常量池中有没有"abcd"&#xff0c;如果字符串常量池中没有&#xff0c;则创建一个&#xff0c;然后 str1 指向字符串常量池中的对象&#xff0c;如果有&#xff0c;则直接将 st…

如何判断对象已经死亡

引用计数 给对象中添加一个引用计数器&#xff0c;每当有一个地方引用它&#xff0c;计数器就加 1&#xff1b;当引用失效&#xff0c;计数器就减 1&#xff1b;任何时候计数器为 0 的对象就是不可能再被使用的。 这个方法实现简单&#xff0c;效率高&#xff0c;但是目前主流…

算法(2)-二叉树的遍历(递归/迭代)python实现

二叉树的遍历1.深度优先DFS1.1 DFS 递归解法1.1.1先序遍历1.1.2中序遍历1.1.3后序遍历1.2 DFS迭代解法1.2.1先序遍历1.2.2中序遍历1.2.3后序遍历2.广度优先BFS3.二叉树的最大深度3.1递归3.2迭代4.翻转二叉树4.1递归4.1迭代5.合并两棵二叉树5.1递归5.2迭代有两种通用的遍历树的策…

利用posix_fadvise清理系统中的文件缓存

利用posix_fadvise清理系统中的文件缓存leoncom c/c,unix2011-08-03当我们需要对某段读写文件并进行处理的程序进行性能测试时&#xff0c;文件会被系统cache住从而影响I/O的效率&#xff0c;必须清理cache中的对应文件的才能正确的进行性能测试。通常清理内存可以采用下面的这…

空间分配

目前主流的垃圾收集器都会采用分代回收算法&#xff0c;因此需要将堆内存分为新生代和老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 大多数情况下&#xff0c;对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时&#xff0c;虚拟…

创建与打开IPC通道的POSIX和SYSTEM V方法

先说&#xff30;&#xff2f;&#xff33;&#xff29;&#xff38;的吧&#xff1a; mq_open&#xff0c;sem_open&#xff0c;shm_open着三个函数用于创建或者打开一个IPC通道。 由此可见&#xff0c;消息队列的读写权限是任意的&#xff0c;然而信号灯就没有&#xff0c;…

软件测试基础知识

第一章 1.1 软件测试背景知识和发展史 互联网公司职位架构&#xff1a;产品 运营 技术 市场 行政软件测试&#xff1a;使用人工或自动化手段&#xff0c;来运行或测试某个系统的过程&#xff0c;其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别&#…

Jmeter-基础篇

常用压力测试工具对比 1、loadrunner 性能稳定&#xff0c;压测结果及细粒度大&#xff0c;可以自定义脚本进行压测&#xff0c;但是太过于重大&#xff0c;功能比较繁多 2、apache ab(单接口压测最方便) 模拟多线程并发请求,ab命令对发出负载的计算机…

shell一文入门通

简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。 W3Cschool 上的一篇文章是这样介绍 Shell的 hello world 学习任何一门编程语言第一件事就是输出HelloWord了&#xff01;下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。 (1)新建一个文件…

RPC编程

图 3 说明在客户机和服务器之间完成 RPC 涉及的步骤。 图 3. 在客户机和服务器之间完成 RPC 涉及的步骤服务器 RPC 应用程序初始化期间它会向 RPC 运行时库注册接口。需要注册接口是因为&#xff0c;客户机在向服务器发出远程过程调用时&#xff0c;要检查它是否与服务器兼容。…

synchronized使用和原理全解

synchronized是Java中的关键字&#xff0c;是一种同步锁。它修饰的对象有以下几种&#xff1a; 修饰一个方法 被修饰的方法称为同步方法&#xff0c;其作用的范围是整个方法&#xff0c;作用的对象是调用这个方法的对象&#xff1b; 修饰一个静态的方法 其作用的范围是整个…