使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM