前言
很多时候仅仅是线下跑一个模型,对特定一批数据进行预测并不够,需要随时来一个或几个样本都能输出结果。这时候就需要起一个服务,然后随时一个包含数据的请求过来,就返回相应的结果。架起这个服务的过程就称作“部署”。本文主要介绍通过tf.Serving Docker来部署tensorflow模型的过程。
1. 导出saved_model
只有saved_model形式的模型可以用来部署。我在《Tensorflow笔记:模型保存、加载和Fine-tune》中介绍了如何将模型保存为saved_model,又在《Tensorflow笔记:高级封装——tf.Estimator》中介绍了tf.Estimator模型如何保存为saved_model。这里就不多赘述,想了解的同学可以传送过去看一下。
假设我们的网络结构采用《Tensorflow笔记:模型保存、加载和Fine-tune》中的线性回归的结构,保存好的saved_model结构需要长这个样子:
代码语言:javascript复制|--saved_model_dir
| |--version
| |--saved_model.pb
| |--variables
| |--variables.data-00000-of-00001
| |--variables.index
其中version是版本(数字),在模型保存的时候需要将路径精确到version,在进行部署的时候,指定模型路径只需要精确到saved_model_dir,tf.serving会自动选择版本最大的模型进行服务。
然后就可以通过tf.serving进行部署了。但是如果直接通过tf.serving进行部署,一个是需要配置tf.serving环境比较麻烦,再一个是如果部署多个模型容易环境混乱。所以在工业界通常采用Docker进行部署,只需要下载一个镜像即带有了完整的tf.serving环境,而且不会造成镜像之外的环境混乱。所以本文重点在于通过Docker tf.serving进行部署。
2. Docker环境搭建
Docker是做什么的呢?说白了就是把一个特定的环境封装起来,如果我们要部署不同的模型,他们有的是需要python2有的需要python3,有的用tf1.x有的用tf2.x,那就需要好几套环境来进行部署,全都装在一个机器里太乱了。所以干脆搞一个集装箱式的东西,每一个集装箱里面装了一套环境(比如py2 tf1.x,或者py3 tf2.x),集装箱里面的环境与外界是独立的,然后把模型部署到这个集装箱里,整个机器的环境就不乱了。
那虚拟机也可以起到同样的作用,为什么不用虚拟机呢?因为相比之下Docker能灵活的分配资源。一个虚拟机占的资源(CPU、内存等)在他被创造出来时候就设定好的,哪怕这个虚拟机啥也不干,这些资源也不能被其他程序利用,这就造成了资源浪费。而Docker可以根据实际消耗来分配不同镜像所占的资源。
1.1 安装Docker
如何安装Docker不是本篇重点,网上已有的教程比我写的好,我就负责贴转送门。MAC可以直接在这里点击下载Docker并安装。Linux可以这样安装。最后安装完成后,查看一下是否安装成功
代码语言:javascript复制$ docker --version
Docker version 19.03.8, build afacb8b
1.2 拉取 tf.Serving镜像
前面提到Docker就是把不同的环境封装成一个集装箱,而一个集装箱里有什么,我们需要一张类似图纸的东西。有了这个图纸,就可以按照这个图纸成产出一个又一个的集装箱。这个图纸学名叫“镜像”,而一个具体的集装箱学名叫“容器”(就好比编程中类和实例的概念)。安装好Docker之后我们来看一下现在有哪些镜像
代码语言:javascript复制# 查看现有镜像命令
docker images
# 输出
REPOSITORY TAG IMAGE ID CREATED SIZE
我们还没有任何镜像,不过Google官方已经做好了很多镜像,我们可以直接下载合适的版本。官方所有的Tensorflow Docker都在这个Docker Hub代码库中。每个镜像都有自己的“标记”和“变体”,下载时需要根据这个标记和变体来确定我们要下载哪一个版本的镜像。标记有四种
- latest:TensorFlow CPU 二进制映像的最新版本。(默认版本)
- nightly:TensorFlow 映像的每夜版。(不稳定)
- version:指定 TensorFlow 二进制映像的版本,例如:2.1.0
- devel:TensorFlow master开发环境的每夜版。包含 TensorFlow 源代码。
在上面四个标记(tag)之外还有三个变体:
- tag-gpu:支持 GPU 的指定标记版本。
- tag-py3:支持 Python 3 的指定标记版本。
- tag-jupyter:带有 Jupyter 的指定标记版本(包含 TensorFlow 教程笔记本)
比如下面的下载命令都是合法的
代码语言:javascript复制docker pull tensorflow/tensorflow # latest stable release
docker pull tensorflow/tensorflow:devel-gpu # nightly dev release w/ GPU support
docker pull tensorflow/tensorflow:latest-gpu-jupyter # latest release w/ GPU support and Jupyter
这里我们选择tensorflow 1.15.0版本的镜像
代码语言:javascript复制docker pull tensorflow/serving:1.15.0
在看看我们有什么镜像
代码语言:javascript复制# 查看现有镜像命令
docker images
# 输出
REPOSITORY TAG IMAGE ID CREATED SIZE
tensorflow/serving 1.15.0 13090fcf0f63 5 months ago 218MB
现在我们已经成功下载了一个tf.serving的Docker镜像。另外补充一下,如果下载错了的话,可以这样删除镜像:
代码语言:javascript复制docker rmi tensorflow/serving:1.15.0
docker rmi后面跟的是镜像的名字 TAG。
1.3 容器操作
前面提到镜像相当于集装箱的图纸,但是我们需要一个集装箱的实例才能在里面进行部署。所以接下来我们要做的事根据刚刚下载的镜像,启动一个容器。这里只是为了介绍如何启动、停止、删除一个容器。首先来启动一个容器:
代码语言:javascript复制# 启动一个容器
docker run tensorflow/serving:1.15.0
启动容器后,控制台不断输出报错:
代码语言:javascript复制E tensorflow_serving/sources/storage_path/file_system_storage_path_source.cc:362] FileSystemStoragePathSource encountered a filesystem access error: Could not find base path /models/model for servable model
报错内容就是,没找到模型。启动之后他会不断的监控是否有模型,是否有模型更新,以及是否有请求等等,这里他监控的时候发现没有模型自然会报错。不过我们只是为了介绍去和启动、停止和删除。具体如何部署在下一章介绍。
首先来看一下一共有哪些容器:
代码语言:javascript复制# 列出所有容器
docker ps
# 输出
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eac49702b304 tensorflow/serving:1.15.0 "/usr/bin/tf_serving…" 2 seconds ago Up 1 second 8500-8501/tcp thirsty_keller
然后我们来停止容器:
代码语言:javascript复制# 停止容器
docker stop 要停止容器的 CONTAINER ID
当然这只是让容器停止运行,还可以重新运行起来:
代码语言:javascript复制# 重启容器
docker restart 要重启容器的 CONTAINER ID
如果不想要了的话,需要将容器删除:
代码语言:javascript复制# 删除单个容器
docker rm 要删除容器的 CONTAINER ID
3. 部署
只要将模型按照《Tensorflow笔记:模型保存、加载和Fine-tune》中的方法保存好了,配置好了合适的signature、signature_def_map以及tags。就可以通过Docker进行部署了。用白话就是,根据镜像(图纸)创造一个容器(集装箱),并且把可服务模型,放到容器(集装箱)里。这里给出两种简单的进行模型部署的命令:
第一种:
代码语言:javascript复制docker run -p 8500:8500
--mount type=bind,source=/Users/coreyzhong/workspace/tensorflow/saved_model/,target=/models/test-model
-t tensorflow/serving:1.15.0
-e MODEL_NAME=test-model --model_base_path=/models/test-model/ &
其中-p 8501:8501表示服务端口;-t表示指定哪一个镜像(图纸);--mount后面跟的这一大长串中间不能有空格,其中type=bind是选择挂在模式,source指的是模型在机器种储存的路径(必须是绝对路径),target指的是模型在容器中储存的路径(放在集装箱的哪里);per_process_gpu_memory_fraction为程序运行的时,所需的GPU显存资源最大不允许超过的比率的设定值;model_name就只是起个名字。
第二种:
代码语言:javascript复制docker run -t --rm -p 8501:8501 -v /Users/coreyzhong/workspace/tensorflow/saved_model/:/models/test-model
-e MODEL_NAME=test-model tensorflow/serving:1.15.0 &
其中-p 8501:8501表示服务端口;-v path1:path2中path1指的是模型在机器种储存的路径(必须是绝对路径),path2指的是模型在容器中储存的路径(放在集装箱的哪里);MODEL_NAME指的是给模型起个名,最后tensorflow/serving:1.15.0是指用哪一个镜像来生成容器。
4. 请求
请求的数据格式应该是json格式,具体形式应该和保存模型时signature的inputs相匹配。
代码语言:javascript复制# 假设这是一个请求的样本,包含两个样本:[1,2,3,4,5,6,7,8,9,10]和[1,1,1,1,1,1,1,1,1,1]
data='{"inputs":[[1,2,3,4,5,6,7,8,9,10],[1,1,1,1,1,1,1,1,1,1]]}'
在本地新开一个控制台窗口,并通过curl发送请求
代码语言:javascript复制# 通过 curl 发送请求
curl http://localhost:8501/v1/models/test-model:predict -X POST -d "${data}"
# 输出
{
"outputs": [
[
-13.8265877
],
[
-3.47753382
]
]
}
这样一个模型就部署好了!