使用docker部署模型的好處在於,避免了與繁瑣的環境配置打交道。使用docker,不須要手動安裝Python,更不須要安裝numpy、tensorflow各類包,直接一個docker就包含了所有。docker的方式是現在部署項目的第一選擇。python
docker安裝須要兩個命令:
sudo apt-get install docker
sudo apt-get install docker.iogit
好的學習資料沒必要遠求
docker --help
docker run --helpgithub
docker ps 查看當前正在運行的實例
docker images查看現有的鏡像
docker kill 實例名稱或者實例ID 殺死正在運行的實例
docker pull 鏡像名稱 從遠程docker倉庫拉下來別人已經配置好的鏡像docker
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 重啓服務ubuntu
docker就是一個鏡像,咱們須要作的就是把docker和外部世界創建聯繫。最緊密的聯繫有以下三種:api
sudo docker pull tensorflow/serving 把serving的鏡像拉下來
git clone https://github.com/tensorflow/serving 複製一份現有的模型,固然也可使用本身的模型,這個倉庫是tensorflow serving的整個源碼庫,裏面給出了一些demo,咱們只須要demo那一部分網絡
使用docker命令啓動服務session
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掉該實例再從新啓動新實例curl
創建映射時,都是形如「宿主機: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這個鏡像的默認配置,鏡像的默認配置就像電路板的引腳同樣,是固定的。
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_METHOD_NAME = "tensorflow/serving/classify" PREDICT_METHOD_NAME = "tensorflow/serving/predict" REGRESS_METHOD_NAME = "tensorflow/serving/regress"
整體來講,定義這些常量名其實並沒有卵用,咱們只用predict就足夠了。
serving是一個很是通用的庫,它不只可以用來對tensorflow模型服務,也能夠對其它機器學習模型服務。
serving只負責tensorflow模型部分,因此咱們須要把用到的函數所有定義出來,這自己就至關於一種RPC。
serving可以起到解耦的做用,對於大項目來講是一件好事,可是對於小項目來講略嫌囉嗦。