TensorFlow Serving
==========================
當我們將模型訓練完畢後,往往需要將模型在開發環境中部署。最常見的方式是在伺服器上提供一個 API,即客戶端向伺服器的某個 API 發送特定格式的請求,伺服器收到請求資料後透過模型進行計算,並返回結果。如果只是做一個 Demo,不考慮並發性和性能問題,其實配合 `Flask `_ 等 Python 下的 Web 框架就能非常輕鬆地實現伺服器 API。不過,如果是在真的實際開發環境中部署,這樣的方式就顯得力不從心了。這時,TensorFlow 為我們提供了 TensorFlow Serving 這一組件,能夠幫助我們在實際開發環境中靈活且高性能地部署機器學習模型。
TensorFlow Serving 安裝
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TensorFlow Serving 可以使用 apt-get 或 Docker安裝。在開發環境中,推薦 `使用Docker部署TensorFlow Serving `_ 。不過此處出於教學目的,介紹環境依賴較少的 `apt-get安裝 `_ 。
.. warning:: 軟體的安裝方法往往具有時效性,本節的更新日期為2019年8月。若遇到問題,建議參考 `TensorFlow網站上的最新安裝說明 `_ 進行操作。
首先設置安裝來源:
::
# 添加Google的TensorFlow Serving安裝來源
echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list
# 添加gpg key
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
更新安裝來源後,即可使用 apt-get 安裝 TensorFlow Serving
::
sudo apt-get update
sudo apt-get install tensorflow-model-server
.. hint:: 在執行 curl 和 apt-get 命令時,可能需要設置代理伺服器。
curl 設置代理伺服器的方式為 ``-x`` 選項或設置 ``http_proxy`` 環境變量,即
::
export http_proxy=http://代理伺服器IP:埠號
或
::
curl -x http://代理伺服器IP:埠號 URL
apt-get設置代理的方式為 ``-o`` 選項,即
::
sudo apt-get -o Acquire::http::proxy="http://代理伺服器IP:埠號" ...
Windows 10 下,可以在 `Linux子系統(WSL) `_ 使用相同的方式安裝 TensorFlow Serving。
TensorFlow Serving 模型部署
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TensorFlow Serving 可以直接讀取 SavedModel 格式的模型進行部署(匯出模型到 SavedModel 文件的方法見 :ref:`前文 ` )。使用以下指令即可:
::
tensorflow_model_server \
--rest_api_port=埠號(如8501) \
--model_name=模型名 \
--model_base_path="SavedModel格式模型的資料夾絕對地址(不含版本號碼)"
.. note:: TensorFlow Serving 支援即時更新模型,其典型的模型資料夾結構如下:
::
/saved_model_files
/1 # 版本號碼為1的模型文件
/assets
/variables
saved_model.pb
...
/N # 版本號碼為N的模型文件
/assets
/variables
saved_model.pb
上面 1~N 的子資料夾代表不同版本號的模型。當指定 ``--model_base_path`` 時,只需要指定根目錄的 **絕對路徑** (不是相對路徑)即可。例如,如果上述資料夾結構存放在 ``home/snowkylin`` 資料夾內,則 ``--model_base_path`` 應該設置為 ``home/snowkylin/saved_model_files`` (不附帶模型版本號碼)。TensorFlow Serving 會自動選擇版本號碼最大的模型進行載入。
Keras Sequential 模式模型的部署
---------------------------------------------------
由於 Sequential 模式的輸入和輸出都很固定,因此這種類型的模型很容易部署,不需要其他額外操作。例如,要將 :ref:`前文使用 SavedModel 匯出的 MNIST 手寫體識別模型 ` (使用Keras Sequential模式建立)以 ``MLP`` 的模型名在埠號 ``8501`` 進行部署,可以直接使用以下指令:
::
tensorflow_model_server \
--rest_api_port=8501 \
--model_name=MLP \
--model_base_path="/home/.../.../saved" # 資料夾絕對路徑根據自身情況填寫,無需加入版本號碼
然後就可以按照 :ref:`後文的介紹 ` ,使用 gRPC 或者 RESTful API 在客戶端呼叫模型了。
自定義 Keras 模型的部署
---------------------------------------------------
使用繼承 ``tf.keras.Model`` 類建立的自定義 Keras 模型的自由度相對更高。因此使用 TensorFlow Serving 部署模型時,對導出的 SavedModel 文件也有更多的要求:
- 匯出 SavedModel 格式的方法(比如 ``call`` )不僅需要使用 ``@tf.function`` 修飾,還要在修飾時指定 ``input_signature`` 參數,以說明輸入的形狀。該參數傳入一個由 ``tf.TensorSpec`` 組成的列表,指定每個輸入張量的形狀和類型。例如,對於 MNIST 手寫體數字辨識,我們的輸入是一個 ``[None, 28, 28, 1]`` 的四維張量( ``None`` 表示第一維即 Batch Size 的大小不固定),此時我們可以將模型的 ``call`` 方法做以下修飾:
.. code-block:: python
:emphasize-lines: 4
class MLP(tf.keras.Model):
...
@tf.function(input_signature=[tf.TensorSpec([None, 28, 28, 1], tf.float32)])
def call(self, inputs):
...
- 將模型使用 ``tf.saved_model.save`` 匯出時,需要通過 ``signature`` 參數提供待匯出的函數的簽名(Signature)。簡單說來,由於自定義的模型類別裡可能有多個方法都需要匯出,因此,需要告訴 TensorFlow Serving 每個方法在被客戶端呼叫時分別叫做什麼名字。例如,如果我們希望客戶端在呼叫模型時使用 ``call`` 這一簽名來呼叫 ``model.call`` 方法時,我們可以在匯出時傳入 ``signature`` 參數,以 ``dict`` 的形式告知匯出的方法對應的名稱,程式碼如下:
.. code-block:: python
:emphasize-lines: 3
model = MLP()
...
tf.saved_model.save(model, "saved_with_signature/1", signatures={"call": model.call})
以上兩步均完成後,即可使用以下指令部署:
::
tensorflow_model_server \
--rest_api_port=8501 \
--model_name=MLP \
--model_base_path="/home/.../.../saved_with_signature" # 修改為自己模型的絕對地址
.. _call_serving_api:
在客戶端呼叫以 TensorFlow Serving 部署的模型
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..
https://www.tensorflow.org/tfx/serving/api_rest
http://www.ruanyifeng.com/blog/2014/05/restful_api.html
TensorFlow Serving 支援以 gRPC和RESTful API 調用以 TensorFlow Serving 部署的模型。本手冊主要介紹較為通用的 RESTful API 方法。
RESTful API 以標準的 HTTP POST 方法進行通信,請求和回覆均為 JSON 對象。為了呼叫伺服器端的模型,我們在客戶端向伺服器發送以下格式的請求:
伺服器 URI: ``http://伺服器地址:埠號/v1/models/模型名:predict``
請求內容:
::
{
"signature_name": "需要呼叫的函數簽名(Sequential模式不需要)",
"instances": 輸入資料
}
回覆為:
::
{
"predictions": 返回值
}
Python 客戶端範例
------------------------------------------------------
以下範例使用 `Python的Requests庫 `_ (你可能需要使用 ``pip install requests`` 安裝該函式庫)向本機的TensorFlow Serving 伺服器發送 MNIST 測試集的前 10 幅圖像並返回預測結果,同時與測試集的真實標籤進行比較。
.. literalinclude:: /_static/code/zh-hant/savedmodel/keras/client.py
輸出:
::
[7 2 1 0 4 1 4 9 6 9]
[7 2 1 0 4 1 4 9 5 9]
可以發現,預測結果與真實標籤值非常接近。
對於自定義的 Keras 模型,在發送的資料中加入 ``signature_name`` 鍵值即可,將上面程式碼 ``data`` 建立過程改為
.. literalinclude:: /_static/code/zh-hant/savedmodel/custom/client.py
:lines: 8-11
Node.js客戶端範例(Ziyang)
------------------------------------------------------
以下範例使用 `Node.js `_ 將下圖轉換為 28*28 的灰階圖,發送給本機的 TensorFlow Serving 伺服器,並輸出返回的預測結果和機率。(其中使用了 `圖像處理庫jimp `_ 和 `HTTP庫superagent `_ ,可使用 ``npm install jimp`` 和 ``npm install superagent`` 安裝)
.. figure:: /_static/image/serving/test_pic_tag_5.png
:align: center
``test_pic_tag_5.png`` :一個由作者手寫的數字5。(運行下麵的代碼時可下載該圖片並放在與代碼同一目錄下)
.. literalinclude:: /_static/code/zh-hant/savedmodel/keras/client.js
:language: javascript
執行結果為:
::
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
我們猜這個數字是5,機率是0.846008837
可以發現、輸出結果符合預期。
.. note:: 如果你不熟悉 HTTP POST,可以參考 `這裡 `_ 。事實上,當你在用瀏覽器填寫表單(比方說性格測試)並點擊“提交”按鈕,然後獲得返回結果(比如說“你的性格是 ISTJ”)時,就很有可能是在向伺服器發送一個 HTTP POST 請求並獲得了伺服器的回覆。
RESTful API 是一個流行的 API 設計理論,可以參考 `這裡 `_ 查看簡介。
關於 TensorFlow Serving 的 RESTful API 的完整使用方式可參考 `文件 `_ 。
.. raw:: html