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" # 修改为自己模型的绝对地址
.. _zh_hans_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/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/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/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
.. role:: raw-html(raw)
:format: html
可见输出结果符合预期。
.. note:: 如果你不熟悉HTTP POST,可以参考 `这里 `_ 。事实上,当你在用浏览器填写表单(比方说性格测试)并点击“提交”按钮,然后获得返回结果(比如说“你的性格是ISTJ”)时,就很有可能是在向服务器发送一个HTTP POST请求并获得了服务器的回复。
RESTful API是一个流行的API设计理论,可以参考 `这里 `_ 获得简要介绍。
关于TensorFlow Serving的RESTful API的完整使用方式可参考 `文档 `_ 。
.. raw:: html