zoukankan      html  css  js  c++  java
  • 使用tensorflow-serving部署tensorflow模型

    使用docker部署模型的好处在于,避免了与繁琐的环境配置打交道。使用docker,不需要手动安装Python,更不需要安装numpy、tensorflow各种包,直接一个docker就包含了全部。docker的方式是如今部署项目的第一选择。

    一、docker用法初探

    1、安装

    docker安装需要两个命令:
    sudo apt-get install docker
    sudo apt-get install docker.io

    好的学习资料不必远求
    docker --help
    docker run --help

    2、基础命令

    docker ps 查看当前正在运行的实例
    docker images查看现有的镜像
    docker kill 实例名称或者实例ID 杀死正在运行的实例
    docker pull 镜像名称 从远程docker仓库拉下来别人已经配置好的镜像

    3、避免每次都sudo

    docker命令默认只能root权限使用,这样实在有些繁琐。docker安装完成之后,docker组已经存在了,但是当前用户不在docker组里面,所以只需要把当前用户添加到docker组即可。
    groups 查看当前用户所在的那些组
    groupadd docker 添加组,这一步其实没有必要,因为docker组已经存在了
    sudo usermod -aG docker $USER 把当前用户添加到docker组,还有另外一种方法:sudo gpasswd -a ${USER} docker
    newgrp - docker 刷新docker组
    sudo service docker restart 重启服务

    4、docker映射

    docker就是一个镜像,我们需要做的就是把docker和外部世界建立联系。最紧密的联系有如下三种:

    • 网络映射:IP和端口号
    • 磁盘映射
      使用docker -v 宿主机目录:docker目录,-v表示virtual,虚拟的路径
      一律采用绝对路径,不要投机取巧使用相对路径,这样可以给自己减少许多麻烦,大智若愚正是此意。
    • 环境变量
      使用docker -e one=two,three=four 这样的命令,e表示environment

    二、快速运行tensorflow-serving

    sudo docker pull tensorflow/serving 把serving的镜像拉下来
    git clone https://github.com/tensorflow/serving 复制一份现有的模型,当然也可以使用自己的模型,这个仓库是tensorflow serving的整个源码库,里面给出了一些demo,我们只需要demo那一部分

    使用docker命令启动服务

    TESTDATA="$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata"
    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 &
    

    docker -e设置环境变量
    docker -p设置端口映射
    docker -v设置磁盘映射
    docker run -t 表示是否允许伪TTY
    docker run --rm表示如果实例已经存在,则先remove掉该实例再重新启动新实例

    建立映射时,都是形如“宿主机:docker容器”这种格式。

    最后万事俱备,只欠东风了。

    # 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] }
    

    容器启动之后,使用docker ps查看有哪些实例,使用docker kill 实例ID 杀死实例,使用docker inspect 实例ID查看实例详情。

    建立磁盘映射除了使用-v参数,也可以使用mount参数。

    docker run -p 8501:8501 --mount type=bind,  source=/tmp/tfserving/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 &
    

    三、tensorflow-serving的默认配置

    我们需要了解tensorflow/serving这个镜像的默认配置,镜像的默认配置就像电路板的引脚一样,是固定的。

    serving镜像提供了两种调用方式:gRPC和HTTP请求。gRPC默认端口是8500,HTTP请求的默认端口是8501,serving镜像中的程序会自动加载镜像内/models下的模型,通过MODEL_NAME指定/models下的哪个模型。

    四、从头编写一个完整的例子

    Tensorflow官网上、github上的例子都比较旧,需要做些小修改才可使用。例如旧版的导出使用了contrib中的session_bundle,而这个包已经不鼓励使用了。

    旧版的serving模型导出

    from tensorflow import gfile
    from tensorflow.contrib.session_bundle import exporter
    
    export_path = "/tmp/half_plus_two"
    if not gfile.Exists(export_path):
        gfile.MkDir(export_path)
    with tf.Session() as sess:
        # Make model parameters a&b variables instead of constants to
        # exercise the variable reloading mechanisms.
        a = tf.Variable(0.5)
        b = tf.Variable(2.0)
    
        # Calculate, y = a*x + b
        # here we use a placeholder 'x' which is fed at inference time.
        x = tf.placeholder(tf.float32)
        y = tf.add(tf.multiply(a, x), b)
    
        # Run an export.
        tf.global_variables_initializer().run()
        export = exporter.Exporter(tf.train.Saver())
        export.init(named_graph_signatures={
            "inputs": exporter.generic_signature({"x": x}),
            "outputs": exporter.generic_signature({"y": y}),
            "regress": exporter.regression_signature(x, y)
        })
        export.export(export_path, tf.constant(123), sess)
    
    

    新版的模型导出:主要使用saved_model包下的工具

    import os
    
    import tensorflow as tf
    
    
    def signature(function_dict):
        signature_dict = {}
        for k, v in function_dict.items():
            inputs = {k: tf.saved_model.utils.build_tensor_info(v) for k, v in v['inputs'].items()}
            outputs = {k: tf.saved_model.utils.build_tensor_info(v) for k, v in v['outputs'].items()}
            signature_dict[k] = tf.saved_model.build_signature_def(inputs=inputs, outputs=outputs, method_name=v['method_name'])
        return signature_dict
    
    
    output_dir = "/tmp/counter"
    for i in range(100000, 9999999):
        cur = os.path.join(output_dir, str(i))
        if not tf.gfile.Exists(cur):
            output_dir = cur
            break
    method_name = tf.saved_model.signature_constants.PREDICT_METHOD_NAME
    print('outputdir', output_dir)
    with tf.Graph().as_default(), tf.Session() as sess:
        counter = tf.Variable(0.0, dtype=tf.float32, name="counter")
        with tf.name_scope("incr_counter_op", values=[counter]):
            incr_counter = counter.assign_add(1.0)
        delta = tf.placeholder(dtype=tf.float32, name="delta")
        with tf.name_scope("incr_counter_by_op", values=[counter, delta]):
            incr_counter_by = counter.assign_add(delta)
        with tf.name_scope("reset_counter_op", values=[counter]):
            reset_counter = counter.assign(0.0)
        nothing = tf.placeholder(dtype=tf.int32, shape=(None,))
        sess.run(tf.global_variables_initializer())
        signature_def_map = signature({
            "get_counter": {"inputs": {"nothing": nothing},
                            "outputs": {"output": counter},
                            "method_name": method_name},
            "incr_counter": {"inputs": {"nothing": nothing},
                             "outputs": {"output": incr_counter},
                             "method_name": method_name},
            "incr_counter_by": {"inputs": {'delta': delta, },
                                "outputs": {'output': incr_counter_by},
                                "method_name": method_name},
            "reset_counter": {"inputs": {"nothing": nothing},
                              "outputs": {"output": reset_counter},
                              "method_name": method_name}
        })
        builder = tf.saved_model.builder.SavedModelBuilder(output_dir)
        builder.add_meta_graph_and_variables(sess, tags=[tf.saved_model.tag_constants.SERVING], signature_def_map=signature_def_map)
        builder.save()
        print("over")
    
    

    以上代码有一个注意事项:
    使用nothing作为inputs中的占位元素,这样做是因为模型的每个函数的inputs都不能为空,否则触发错误:
    "error": "Failed to get input map for signature: get_counter" 。这似乎是tensorflow似乎有bug,调用get_counter方法是不管用的,传入的inputs为空,就无法调用成功。

    运行此servable

    $docker run -p 8555:8501 -v /home/ubuntu/counter:/models/counter -e MODEL_NAME=counter tensorflow/serving &
    
    获取模型状态
    $curl http://localhost:8555/v1/models/saved_model_counter 
    
    {
     "model_version_status": [
      {
       "version": "1343",
       "state": "AVAILABLE",
       "status": {
        "error_code": "OK",
        "error_message": ""
       }
      }
     ]
    }
    
    使用metadata获取可用模型元数据
    ~$curl http://localhost:8555/v1/models/saved_model_counter/metadata
    {
    "model_spec":{
     ......
    
    调用增长函数
    ~$curl http://localhost:8555/v1/models/counter:predict -X POST -d '{"signature_name":"incr_couter","inputs":[0]}'
    {
        "outputs": 1.0
    }
    
    调用increase_by
    第一种方式inputs传入列表
    curl http://localhost:8555/v1/models/counter:predict -X POST -d '{"signature_name":"incr_couter_by","inputs":[3]}'
    第二种方式inputs传入字典
    ~$curl http://localhost:8555/v1/models/counter:predict -X POST -d '{"signature_name":"incr_counter_by","inputs":{"delta":3.0}}'
    

    tf.saved_model.signature_constants这个包非常简单,它定义了以下三类变量签名

    • 分类classify
    • 回归regress
    • 预测predict

    这三类分别具有:

    • 函数签名
    CLASSIFY_METHOD_NAME = "tensorflow/serving/classify"
    PREDICT_METHOD_NAME = "tensorflow/serving/predict"
    REGRESS_METHOD_NAME = "tensorflow/serving/regress"
    
    • Inputs
    • Outputs
      其中CLASSIFY的output有两种:score、class。score返回各个类别的期望值,class返回各个类别的离散值。

    总体来说,定义这些常量名其实并无卵用,我们只用predict就足够了。

    五、对tensorflow/serving的整体认识

    serving是一个非常通用的库,它不仅能够用来对tensorflow模型服务,也可以对其它机器学习模型服务。
    serving只负责tensorflow模型部分,所以我们需要把用到的函数全部定义出来,这本身就相当于一种RPC。
    serving能够起到解耦的作用,对于大项目来说是一件好事,但是对于小项目来说略嫌啰嗦。

    参考资料

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

  • 相关阅读:
    vim插件管理利器
    Ubuntu下安装java
    Eclipse的vim插件viPlugin的安装
    Ubuntu下如何检查文件的md5,sha-512码
    C语言的工具集
    ubuntu下禁用和恢复触摸板
    linux下开发C语言需要安装的manpages手册
    linux下C++的man文件安装
    源码编译安装git
    EditText属性详解
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/9928363.html
Copyright © 2011-2022 走看看