TensorFlow Serving 安装 和 部署模型

由于是tensorflow 1.9.0,tensorflow 2.0的那套简便的方法就用不了

大体思路:

  1. 首先export model,准备计算图的代码,将保存的参数restore进入计算图;
  2. 然后调用export model的方法,导出模型
  3. 模型导出之后就直接拉取tensorflow serving的镜像然后创建,这里注意如果是tensorflow-serving-1.x.0的镜像,docker run之后就直接可以访问容器服务,tensorflow-serving-1.x.0-devel的镜像需要docker run之后,再进入容器运行tensorflow_model_server指定容器映射的端口,model_name等等,如果计算图没有其他依赖,直接拉取tensorflow-serving-1.x.0就行
  4. 创建tensorflow serving镜像时需要指定端口映射,然后你可以通过这个端口访问serving服务,还需要将宿主机自身模型的path挂载到容器的/models
  5. 如何使用运行好的容器服务?可以通过RESTful API访问,也可以通过gRPC API访问
  6. 最后模型部分已经全部交给了tensorflow-server,再去写一个web服务包装一下就可以快速部署了

 

首先要安装docker,参见其他教程

安装一些包

pip install -U grpcio
pip install -U grpcio-tools

pip install -U protobuf

tensorflow-serving要和TensorFlow版本配合使用,否则后期调用会有一些问题

pip install tensorflow-serving-api==1.14.0

 

检查tensorflow serving是否安装成功,注意路径

Serve a Tensorflow model in 60 seconds

# Download the TensorFlow Serving Docker image and repo
docker pull tensorflow/serving

git clone https://github.com/tensorflow/serving
# Location of demo models
TESTDATA="$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata"

# Start TensorFlow Serving container and open the REST API port
docker run -t --rm -p 8501:8501 \
    -v "$TESTDATA/saved_model_half_plus_two_cpu:/models/half_plus_two" \
    -e MODEL_NAME=half_plus_two \
    tensorflow/serving &

# Query the model using the predict API
curl -d '{"instances": [1.0, 2.0, 5.0]}' \
    -X POST http://localhost:8501/v1/models/half_plus_two:predict

# Returns => { "predictions": [2.5, 3.0, 4.5] }

-p 8501:8501 将端口映射到8501,也可以改端口,例 -p 8000:8501  -p 9000:8501 ,后面的端口不能变(应该是TensorFlow serving封装好的,目前不是很懂)

-v "$TESTDATA/saved_model_half_plus_two_cpu:/models/half_plus_two" \

这句是将本地 $TESTDATA/saved_model_half_plus_two_cpu 挂载到docker 的这个位置  /models/half_plus_two

 

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

这句是传入测试实例   {"instances": [1.0, 2.0, 5.0]} ,请求docker上面部署好的模型 http://localhost:8501/v1/models/half_plus_two:predict  ,注意端口要统一,上面是9000,则 curl -d '{"instances": [1.0, 2.0, 5.0]}' \  -X POST http://localhost:9000/v1/models/half_plus_two:predict

curl -d ' \  -X POST

 

有两种写法

第一种

docker run -p 8500:8500 \
    --mount type=bind,source=/Absolute/path/to/local/saved_model/,target=/models/model_name \
    -t tensorflow/serving \
    -e MODEL_NAME=model_name --model_base_path=/models/model_name/ &

 

-p 8501:8501表示服务端口;
-t表示指定哪一个镜像(图纸);
--mount后面跟的这一大长串中间不能有空格,其中type=bind是选择挂在模式
source指的是模型在机器种储存的路径(必须是绝对路径),target指的是模型在容器中储存的路径(放在集装箱的哪里);
per_process_gpu_memory_fraction为程序运行的时,所需的GPU显存资源最大不允许超过的比率的设定值;
model_name就只是起个名字

 

第二种

docker run -t --rm -p 8501:8501 -v /Absolute/path/to/local/saved_model/:/models/model_name \

  -e MODEL_NAME=test-model tensorflow/serving &

-p 8501:8501表示服务端口;
-v path1:path2中path1指的是模型在机器种储存的路径(必须是绝对路径),path2指的是模型在容器中储存的路径(放在集装箱的哪里);
MODEL_NAME指的是给模型起个名;
tensorflow/serving 是指用哪一个镜像来生成容器。

 

注意

docker 挂载主机目录 -v 和 --mount区别

使用-v  时,如果宿主机上没有这个文件,也会自动创建,

但是如果使用--mount时,宿主机中没有这个文件会报错找不到这个文件,并创建失败

 

docker images   查看镜像

docker ps   查看运行的镜像

docker ps -a   查看所有的容器

docker stop <容器 ID>    停止<容器 ID> 进程

docker rm <容器 ID>       删除 <容器 ID> 进程

docker restart <容器 ID>   重启停止容器

docker exec -it <容器 ID> /bin/bash  进入容器(推荐大家使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止)

 

# 启动/停止容器
docker start/stop $container_id或$container_name
# 查看运行容器
docker ps
# 查看全部容器
docker ps -a
# 删除指定容器
docker rm $container_id或$container_name
# 查看运行容器的日志
docker logs -f $container_id或$container_name
# docker 删除镜像
docker rmi image:tag # 例如docker rmi tensorflow/serving:latest
--mount:   表示要进行挂载
source:    指定要运行部署的模型地址, 也就是挂载的源,这个是在宿主机上的servable模型目录(pb格式模型而不是checkpoint模型)
target:     这个是要挂载的目标位置,也就是挂载到docker容器中的哪个位置,这是docker容器中的目录,模型默认挂在/models/目录下,如果改变路径会出现找不到model的错误
-t:         指定的是挂载到哪个容器
-d:         后台运行
-p:         指定主机到docker容器的端口映射
-e:         环境变量
-v:         docker数据卷
--name:     指定容器name,后续使用比用container_id更方便

命令整理

容器操作:

docker create # 创建一个容器但是不启动它
docker run # 创建并启动一个容器
docker stop # 停止容器运行,发送信号SIGTERM
docker start # 启动一个停止状态的容器
docker restart # 重启一个容器
docker rm # 删除一个容器
docker kill # 发送信号给容器,默认SIGKILL
docker attach # 连接(进入)到一个正在运行的容器
docker wait # 阻塞一个容器,直到容器停止运行
docker exec -it 775c7c9ee1e1 /bin/bash #容器阻塞 用此方法


获取容器信息:

docker ps # 显示状态为运行(Up)的容器
docker ps -a # 显示所有容器,包括运行中(Up)的和退出的(Exited)
docker inspect # 深入容器内部获取容器所有信息
docker logs # 查看容器的日志(stdout/stderr)
docker events # 得到docker服务器的实时的事件
docker port # 显示容器的端口映射
docker top # 显示容器的进程信息
docker diff # 显示容器文件系统的前后变化


导出容器:

docker cp # 从容器里向外拷贝文件或目录
docker export # 将容器整个文件系统导出为一个tar包,不带layers、tag等信息


执行:

docker exec # 在容器里执行一个命令,可以执行bash进入交互式


镜像操作:

docker images # 显示本地所有的镜像列表
docker import # 从一个tar包创建一个镜像,往往和export结合使用
docker build # 使用Dockerfile创建镜像(推荐)
docker commit # 从容器创建镜像
docker rmi # 删除一个镜像
docker load # 从一个tar包创建一个镜像,和save配合使用
docker save # 将一个镜像保存为一个tar包,带layers和tag信息
docker history # 显示生成一个镜像的历史命令
docker tag # 为镜像起一个别名


镜像仓库(Registry)操作:

docker login # 登录到一个registry
docker search # 从registry仓库搜索镜像
docker pull # 从仓库下载镜像到本地
docker push # 将一个镜像push到registry仓库中

 

模型部署

部署的是多标签分类,和之前的文章相关

https://blog.csdn.net/weixin_48185819/article/details/110039323#comments_13993041

在下载的文件中有文件 model_exporter.py  , 需要修改里面的相关参数,和 run_multilabels_classifier.py 保持统一

另外 model_exporter.py  中需要修改 label_ids 这个部分,下面已经修改好了,可以自动对应 labels_num


    def transfer(self):
        gpu_config = tf.ConfigProto()
        gpu_config.gpu_options.allow_growth = True
        sess = tf.Session(config=gpu_config)
        print("going to restore checkpoint")
        bert_config = modeling.BertConfig.from_json_file(self.bert_config_file)

        input_ids = tf.placeholder(tf.int32, [1, self.max_seq_length], name="input_ids")
        input_mask = tf.placeholder(tf.int32, [1, self.max_seq_length], name="input_mask")
        segment_ids = tf.placeholder(tf.int32, [1, self.max_seq_length], name="segment_ids")
        # multi task classication problem need to modify this
        #label_ids = tf.placeholder(tf.int32, [1], name="label_ids")
        label_ids = tf.placeholder(tf.int32, [1,self.labels_num], name="label_ids")
        total_loss, per_example_loss, logits, probabilities = self._create_model(
            bert_config, False, input_ids, input_mask, segment_ids,
            label_ids, self.labels_num, False)
        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint(FLAGS.data_path))
        tf.saved_model.simple_save(sess,
                                   FLAGS.export_path,
                                   inputs={
                                       'label_ids': label_ids,
                                       'input_ids': input_ids,
                                       'input_mask': input_mask,
                                       'segment_ids': segment_ids
                                   },
                                   outputs={"probabilities": probabilities})
        print('savedModel export finished')

生成模型的pb文件

python model_exporter.py \
  --data_path=./dataset_output \
  --labels_num= 标签数量(分类数量) \
  --export_path=./dataset_output/pb \

 

将模型挂载到之前docker容器中,命令还是一样,就是路径和模型名字的区别

docker run -t --rm -p 8507:8501 \
    -v "XXX/Multi_Label_Classifier_finetune" \      pb模型所在位置
    -e MODEL_NAME=Multi_Label_Classifier_finetune \    模型名称,注意要统一,不然会找不到模型
    --name=XX\     容器名称(可选)
    tensorflow/serving &

 

有其他写法要指定两个端口,后续和grpc的请求模式有关(目前不是很懂)

docker run -t --rm -p 8500:8500 -p 8507:8501 \
    -v "XXX/Multi_Label_Classifier_finetune" \      pb模型所在位置
    -e MODEL_NAME=Multi_Label_Classifier_finetune \    模型名称,注意要统一,不然会找不到模型
    --name=XX\     容器名称(可选)
    tensorflow/serving &

 

client请求

模型部署以后,docker ps 可以看到服务在运行

CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                                                      NAMES
000011aaa        tensorflow/serving   "/usr/bin/tf_serving…"   20 days ago          Up 2 days           0.0.0.0:8500->8500/tcp, 0.0.0.0:8507->8501/tcp             Tom

然后需要在run_classifier_predict_online.py 文件上进行修改,使用grpc方式进行请求,这里只给出了主要的grpc方法,还有其他例如维度、路径等,需要根据需求和模型变化自行修改

def predict_online_grpc(line):
  import grpc
  import requests
  import tensorflow as tf
  from tensorflow_serving.apis import predict_pb2
  from tensorflow_serving.apis import prediction_service_pb2_grpc

  label = line[0] #tokenization.convert_to_unicode(line[0]) # this should compatible with format you defined in processor.
  text_a = line[1] #tokenization.convert_to_unicode(line[1])
  text_b = line[2] #tokenization.convert_to_unicode(line[2])
  example= InputExample(guid=0, text_a=text_a, text_b=text_b, label=label)
  feature = convert_single_example(0, example, label_list,FLAGS.max_seq_length, tokenizer)
  input_ids = np.reshape([feature.input_ids],(1,FLAGS.max_seq_length))
  input_mask = np.reshape([feature.input_mask],(1,FLAGS.max_seq_length))
  segment_ids =  np.reshape([feature.segment_ids],(FLAGS.max_seq_length))
  label_ids =[feature.label_id]

  channel = grpc.insecure_channel(FLAGS.server)
  stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
  request = predict_pb2.PredictRequest()
  request.model_spec.name = "Multi_Label_Classifier_finetune"
  request.model_spec.signature_name = "serving_default"
  request.inputs['input_ids'].CopyFrom(
      tf.contrib.util.make_tensor_proto(input_ids.astype(dtype=np.int32), shape=[1, 512]))
  request.inputs['input_mask'].CopyFrom(
      tf.contrib.util.make_tensor_proto(input_mask.astype(dtype=np.int32), shape=[1, 512]))
  request.inputs['segment_ids'].CopyFrom(
      tf.contrib.util.make_tensor_proto(segment_ids.astype(dtype=np.int32), shape=[1, 512]))
  request.inputs['label_ids'].CopyFrom(
      tf.contrib.util.make_tensor_proto(label_ids, shape=[1]))
  result = stub.Predict(request)
  print(result)  

 

问题

--export_path=./dataset_output/pb \ 这个在部署时会出现一些问题,可能和TensorFlow serving 热更新有关

建议写成 --export_path=./dataset_output/pb/1 \       1代表版本1,后续新模型可排序为2,3,4等

 

tensorflow 1.9.0作为一个“上古”版本的tensorflow,很多文档都是不全的或者说是难以找到,好不容易摸清楚了涉及到哪些API,我被一个报错给弄懵了,“Op type not registered ‘PyFunc’ in binary running on xxxxxx”,后来查了一下才知道,原来tensorflow-serving基于C++构建,将模型参数保存下来后,全部用C++来运行,所以不支持一些原生python的操作,比如tensorflow.py_func,然而需要部署的那一份代码里面有大量py_func,除非将全部的py_func用tensorflow原生方法代替,不然没办法用tensorflow-serving。

成功运行容器之后还遇到了一个坑,就是RESTful api的端口和gRPC的端口不一样,前者对应8501,后者需要另设8500

 

在服务器重启或断电重启后,docker不能自动重新启动,可以加入    --restart=always   --privileged=true 命令进行设置

注意 --rm 和 --restart 冲突,不能同时使用

docker run -t -p 8500:8500 -p 8507:8501 \
    -v "XXX/Multi_Label_Classifier_finetune" \      pb模型所在位置
    -e MODEL_NAME=Multi_Label_Classifier_finetune \    模型名称,注意要统一,不然会找不到模型
    --name=XX\     容器名称(可选)
    --restart=always \
    --privileged=true \
    tensorflow/serving &

 

 

Reference

https://blog.csdn.net/zong596568821xp/article/details/99715005

https://github.com/tensorflow/serving

https://www.cnblogs.com/zhaojingyu/p/11599473.html

https://www.cnblogs.com/nullau/p/13999184.html

https://www.jianshu.com/p/d11a5c3dc757

https://www.runoob.com/docker/docker-container-usage.html

 

你可能感兴趣的:(TensorFlow)