TensorFlow Serving簡介


一、TensorFlow Serving簡介

TensorFlow Serving是GOOGLE開源的一個服務系統,適用於部署機器學習模型,靈活、性能高、可用於生產環境。 TensorFlow Serving可以輕松部署新算法和實驗,同時保持相同的服務器架構和API,它具有以下特性:

  • 支持模型版本控制和回滾
  • 支持並發,實現高吞吐量
  • 開箱即用,並且可定制化
  • 支持多模型服務
  • 支持批處理
  • 支持熱更新
  • 支持分布式模型
  • 易於使用的inference api
  • 為gRPC expose port 8500,為REST API expose port 8501

二、安裝與測試

安裝TensorFlow Serving有三種方法:docker,二進制,源碼編譯,這里只介紹通過docker安裝的步驟。

  1. 安裝docker,通過docker拉取tensorflow serving鏡像
# 此處拉取的是cpu serving鏡像,如果要使用gpu,需要安裝對應的docker和對應的gpu serving鏡像
docker pull tensorflow/serving
  1. 拉取源碼,部署源碼中的half_plus_two模型測試serving是否可用
git clone https://github.com/tensorflow/serving

TESTDATA="$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata"
# 設置端口轉發,以下兩條命令都可以啟動服務
docker run -dt -p 8501:8501 -v "$TESTDATA/saved_model_half_plus_two_cpu:/models/half_plus_two" -e MODEL_NAME=half_plus_two tensorflow/serving
docker run -d -p 8501:8501 --mounttype=bind,source=$TESTDATA/saved_model_half_plus_two_cpu/,target=/models/half_plus_two -e MODEL_NAME=half_plus_two -t --name testserver tensorflow/serving

# 在服務器本機測試模型是否正常工作,這里需要注意,源碼中half_plus_two的模型版本是00000123,但在訪問時也必須輸入v1而不是v000000123
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]}這個結果說明模型部署成功
  1. 命令說明
docker常用命令
# 啟動/停止容器
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
docker啟動服務時參數
--mount:   表示要進行掛載
source:    指定要運行部署的模型地址, 也就是掛載的源,這個是在宿主機上的servable模型目錄(pb格式模型而不是checkpoint模型)
target:     這個是要掛載的目標位置,也就是掛載到docker容器中的哪個位置,這是docker容器中的目錄,模型默認掛在/models/目錄下,如果改變路徑會出現找不到model的錯誤
-t:         指定的是掛載到哪個容器
-d:         后台運行
-p:         指定主機到docker容器的端口映射
-e:         環境變量
-v:         docker數據卷
--name:     指定容器name,后續使用比用container_id更方便

三、模型格式轉換

我們平時使用tf.Saver()保存的模型是checkpoint格式的,但是在TensorFlow Serving中一個servable的模型目錄中是一個pb格式文件和一個名為variables的目錄,因此需要在模型保存時就保存好可部署的模型格式,或者將已經訓練好的checkpoint轉換為servable format。

-ckpt_model
        -checkpoint
        -***.ckpt.data-00000-of-00001
        -***.ckpt.index
        -***.ckpt.meta
#轉換為
-servable_model
        -version
                -saved_model.pb
                -variables

以下以命名實體識別空洞卷積模型為例,展示如何轉換模型格式。

#coding:utf-8 import sys, os, io import tensorflow as tf def restore_and_save(input_checkpoint, export_path_base): checkpoint_file = tf.train.latest_checkpoint(input_checkpoint) graph = tf.Graph() with graph.as_default(): session_conf = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False) sess = tf.Session(config=session_conf) with sess.as_default(): # 載入保存好的meta graph,恢復圖中變量,通過SavedModelBuilder保存可部署的模型 saver = tf.train.import_meta_graph("{}.meta".format(checkpoint_file)) saver.restore(sess, checkpoint_file) print (graph.get_name_scope()) export_path_base = export_path_base export_path = os.path.join( tf.compat.as_bytes(export_path_base), tf.compat.as_bytes(str(count))) print('Exporting trained model to', export_path) builder = tf.saved_model.builder.SavedModelBuilder(export_path) # 建立簽名映射,需要包括計算圖中的placeholder(ChatInputs, SegInputs, Dropout)和我們需要的結果(project/logits,crf_loss/transitions) """ build_tensor_info:建立一個基於提供的參數構造的TensorInfo protocol buffer, 輸入:tensorflow graph中的tensor; 輸出:基於提供的參數(tensor)構建的包含TensorInfo的protocol buffer get_operation_by_name:通過name獲取checkpoint中保存的變量,能夠進行這一步的前提是在模型保存的時候給對應的變量賦予name """ char_inputs =tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("ChatInputs").outputs[0]) seg_inputs =tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("SegInputs").outputs[0]) dropout =tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("Dropout").outputs[0]) logits =tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("project/logits").outputs[0]) transition_params =tf.saved_model.utils.build_tensor_info(graph.get_operation_by_name("crf_loss/transitions").outputs[0]) """ signature_constants:SavedModel保存和恢復操作的簽名常量。 在序列標注的任務中,這里的method_name是"tensorflow/serving/predict" """ # 定義模型的輸入輸出,建立調用接口與tensor簽名之間的映射 labeling_signature = ( tf.saved_model.signature_def_utils.build_signature_def( inputs={ "charinputs": char_inputs, "dropout": dropout, "seginputs": seg_inputs, }, outputs={ "logits": logits, "transitions": transition_params }, method_name="tensorflow/serving/predict")) """ tf.group : 創建一個將多個操作分組的操作,返回一個可以執行所有輸入的操作 """ legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op') """ add_meta_graph_and_variables:建立一個Saver來保存session中的變量, 輸出對應的原圖的定義,這個函數假設保存的變量已經被初始化; 對於一個SavedModelBuilder,這個API必須被調用一次來保存meta graph; 對於后面添加的圖結構,可以使用函數 add_meta_graph()來進行添加 """ # 建立模型名稱與模型簽名之間的映射 builder.add_meta_graph_and_variables( sess, [tf.saved_model.tag_constants.SERVING], # 保存模型的方法名,與客戶端的request.model_spec.signature_name對應 signature_def_map={ tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: labeling_signature}, legacy_init_op=legacy_init_op) builder.save() print("Build Done") ### 測試模型轉換 tf.flags.DEFINE_string("ckpt_path", "source_ckpt/IDCNN", "path of source checkpoints") tf.flags.DEFINE_string("pb_path", "servable-models/IDCNN", "path of servable models") tf.flags.DEFINE_integer("version", 1, "the number of model version") tf.flags.DEFINE_string("classes", 'LOC', "multi-models to be converted") FLAGS = tf.flags.FLAGS classes = FLAGS.classes input_checkpoint = FLAGS.ckpt_path + "/" + classes model_path = FLAGS.pb_path + '/' + classes # 版本號控制 count = FLAGS.version modify = False if not os.path.exists(model_path): os.mkdir(model_path) else: for v in os.listdir(model_path): print(type(v), v) if int(v) >= count: count = int(v) modify = True if modify: count += 1 # 模型格式轉換 restore_and_save(input_checkpoint, model_path) 

四、多模型部署

在現實情況里,一個任務可能需要用到多個模型,例如命名實體識別我訓練了多個模型,對每個句子都需要匯總所有模型的結果,這時就需要用到多模型部署。在安裝與測試一節中,我們介紹的是一個服務中只部署一個模型,這一節介紹下如何通過TF-Serving進行多模型部署。
多模型部署時,無法在命令行中指定MODEL_NAME了,需要編寫一個如下的json配置文件,這里取名為model.config。

model_config_list: { config: { name: "model1", base_path: "/models/model1", model_platform: "tensorflow", model_version_policy: { all: {} }, config: { name: "model2", base_path: "/models/model2", model_platform: "tensorflow", model_version_policy: { latest: { num_versions: 1 } } }, config: { name: "model3", base_path: "/models/model3", model_platform: "tensorflow", model_version_policy: { specific: { versions: 1 } } } } 

model_version_policy可以刪除,默認是部署最新版本的模型,如果想要部署指定版本或者全部版本,需要單獨設定。

啟動多模型服務命令時,需要將所有模型和配置文件逐個綁定,然后指定配置文件路徑

sudo docker run -d -p 8500:8500 --mounttype=bind,source=/path/to/source_models/model1/,target=/models/model1 --mounttype=bind,source=/path/to/source_models/model2/,target=/models/model2 --mounttype=bind,source=/path/to/source_models/model3/,target=/models/model3 --mounttype=bind,source=/path/to/source_models/model.config,target=/models/model.config -t --name ner tensorflow/serving --model_config_file=/models/model.config 

五、模型調用

在客戶端調用模型可以安裝tf-serving提供的包

pip install tensorflow-serving-api
# python3 需要安裝tensorflow-serving-api-python3
# 注意tensorflow和tensorflow serving的版本最好一致(此代碼版本:tensorflow==1.4.0, tensorflow-serving-api==1.4.0)
  1. 建立連接
channel = implementations.insecure_channel("127.0.0.1", 8500)
stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
request = predict_pb2.PredictRequest()

# 指定啟動tensorflow serving時配置的model_name和是保存模型時的方法名
request.model_spec.name = "model1"
request.model_spec.signature_name = "serving_default"
  1. 構造輸入tensor
# 將文本進行預處理,得到list格式的輸入數據,然后將其轉為tensor后序列化
charinputs, seginputs = get_input(sentence) request.inputs["charinputs"].ParseFromString(tf.contrib.util.make_tensor_proto(charinputs, dtype=tf.int32).SerializeToString()) request.inputs["seginputs"].ParseFromString(tf.contrib.util.make_tensor_proto(seginputs, dtype=tf.int32).SerializeToString()) request.inputs["dropout"].ParseFromString(tf.contrib.util.make_tensor_proto(1.0, dtype=tf.float32).SerializeToString()) 
  1. 獲取模型輸入結果
response = stub.Predict(request, timeout)

results = {}
for key in response.outputs:
    tensor_proto = response.outputs[key]
    results[key] =  tf.contrib.util.make_ndarray(tensor_proto)

# 從results中取所需要的結果,不一定是這兩個變量哦
logits = results["logits"]
transitions = results["transitions"]

# 后處理
tags = get_output(logits, transitions)

TensorFlow模型的計算圖,一般輸入的類型都是張量,你需要提前把你的圖像、文本或者其它數據先進行預處理,轉換成張量才能輸入到模型當中。而一般來說,這個數據預處理過程不會寫進計算圖里面,因此當你想使用TensorFlow Serving的時候,需要在客戶端上寫一大堆數據預處理代碼,然后把張量通過gRPC發送到serving,最后接收結果。現實情況是你不可能要求每一個用戶都要寫一大堆預處理和后處理代碼,用戶只需使用簡單POST一個請求,然后接收最終結果即可。因此,這些預處理和后處理代碼必須由一個“中間人”來處理,這個“中間人”就是Web服務。關於此部分可以以后再詳細介紹。

六、可能遇到的錯誤

docker: Error response from daemon: driver failed programming external connectivity on endpoint adoring_liskov (be1e57454fe716affc90cd6ba1d7fce7030f0de85f4262b5796aec3fbbafd7b9): (iptables failed: iptables --wait -t filter -A DOCKER ! -i docker0 -o docker0 -p tcp -d 172.17.0.2 --dport 8501 -j ACCEPT: iptables: No chain/target/match by that name. (exit status 1)). 

docker daemon的bug,出現此情況需要重啟docker。

找不到servable model所在路徑時,需要檢查source和target路徑是否正確;target model設在/models/下一層,不要多層嵌套;檢查版本號。


免責聲明!

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



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