前傳:
相信很多人和我一樣,在試圖安裝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