1. 程式人生 > >用Docker容器自帶的tensorflow serving部署模型對外服務(成功率100%)

用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協議。

參考地址: