用Docker容器自帶的tensorflow serving部署模型對外服務(成功率100%)
前傳:
相信很多人和我一樣,在試圖安裝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協議。
參考地址: