• 用Docker容器自带的tensorflow serving部署模型对外服务(成功率100%)


    前传:

    相信很多人和我一样,在试图安装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的源码,源码中有一些训练好的例子模型

    $cd /root/software/
    $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容器来运行部署这个例子模型

    $docker run -p 8501:8501
    --mount type=bind,
    source=/root/software/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_two_cpu,
    target=/models/half_plus_two
    -e MODEL_NAME=half_plus_two -t tensorflow/serving &
    参数说明:

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

    综合解释:
    将source目录中的例子模型,挂载到-t指定的docker容器中的target目录,并启动
    这步注意,如果执行报错无法识别type=bind, 那应该是source的路径有问题

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

    $curl -d '{"instances": [1.0, 2.0, 5.0]}'
    -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, 这是我模型的名称,然后

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

    best_validation.data-00000-of-00001 best_validation.index best_validation.meta checkpoint
    相信大家都看出来了,这个是用这种方式保存的:

    saver = tf.train.Saver()
    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模型的下的文件的结构根本不一样,怎么办呢? 其实应该对模型的格式进行转换。 代码如下:

    # coding: utf-8

    from __future__ import print_function
    import pdb
    import time
    import os
    import tensorflow as tf
    import tensorflow.contrib.keras as kr

    from cnn_rnn_model import TCNNRNNConfig, TextCNNRNN

    save_path = 'model_saver/textcnnrnn/best_validation'
    try:
    bool(type(unicode))
    except NameError:
    unicode = str

    config = TCNNRNNConfig()

    def build_and_saved_wdl():

    model = TextCNNRNN(config) #我自己的模型结构是在这个类中定义的,基于自己的模型进行替换

    session = tf.Session()
    session.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.restore(sess=session, save_path=save_path)

    # 将训练好的模型保存在model_name下,版本为2,当然你的版本可以随便写
    builder = tf.saved_model.builder.SavedModelBuilder("./model_name/2")
    inputs = {
    #注意,这里是你预测模型的时候需要传的参数,调用模型的时候,传参必须和这里一致
    #这里的model.input_x和model.keep_prob就是模型里面定义的输入placeholder
    "input_x": tf.saved_model.utils.build_tensor_info(model.input_x),
    "keep_prob": tf.saved_model.utils.build_tensor_info(model.keep_prob)
    }

    #model.y_pred_cls是模型的输出, 预测的时候就是计算这个表达式
    output = {"output": tf.saved_model.utils.build_tensor_info(model.y_pred_cls)}
    prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(
    inputs=inputs,
    outputs=output,
    method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
    )

    builder.add_meta_graph_and_variables(
    session,
    [tf.saved_model.tag_constants.SERVING],
    {tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: prediction_signature}
    )
    builder.save()


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

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

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

    7、调用模型

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

    curl -d '{"instances": [1.0, 2.0, 5.0]}'
    -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参数:

    class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
    if isinstance(obj, np.ndarray):
    return obj.tolist()
    return json.JSONEncoder.default(self, obj)
    p_data = {"keep_prob": 1.0, "input_x": x_test[0]}
    param = {"instances": [p_data]}
    param = json.dumps(param, cls=NumpyEncoder)
    res = requests.post('http://localhost:8501/v1/models/find_lemma_category:predict', data=param)
    这样就大功告成了!

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

    data1 = {"keep_prob": 1.0, "input_x": x_test[0]}
    data2 = {"keep_prob": 1.0, "input_x": x_test[1]}
    data3 = {"keep_prob": 1.0, "input_x": x_test[2]}
    param = {"instances": [data1, data2, data3]}
    param = json.dumps(param, cls=NumpyEncoder)
    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

     
    ————————————————
    版权声明:本文为CSDN博主「生活不只*眼前的苟且」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u011734144/article/details/82107610

  • 相关阅读:
    [USACO08MAR]土地征用Land Acquisition
    树链剖分
    [AHOI2008]紧急集合 / 聚会
    P1852 [国家集训队]跳跳棋
    Ant Trip(欧拉回路+并查集)
    单词游戏
    POJ3694 Network
    C++ STL小总结
    2-Sat专题
    6大名家带你穿越大半个中国去深思
  • 原文地址:https://www.cnblogs.com/zhwl/p/12737264.html
Copyright © 2020-2023  润新知