• 在客户端调用以TensorFlow Serving部署的模型
    • Python客户端示例
    • Node.js客户端示例(Ziyang)

    在客户端调用以TensorFlow Serving部署的模型

    TensorFlow Serving支持以gRPC和RESTful API调用以TensorFlow Serving部署的模型。本手册主要介绍较为通用的RESTful API方法。

    RESTful API以标准的HTTP POST方法进行交互,请求和回复均为JSON对象。为了调用服务器端的模型,我们在客户端向服务器发送以下格式的请求:

    服务器URI: http://服务器地址:端口号/v1/models/模型名:predict

    请求内容:

    1. {
    2. "signature_name": "需要调用的函数签名(Sequential模式不需要)",
    3. "instances": 输入数据
    4. }

    回复为:

    1. {
    2. "predictions": 返回值
    3. }

    Python客户端示例

    以下示例使用 Python的Requests库 (你可能需要使用 pip install requests 安装该库)向本机的TensorFlow Serving服务器发送MNIST测试集的前10幅图像并返回预测结果,同时与测试集的真实标签进行比较。

    1. import json
    2. import numpy as np
    3. import requests
    4. from zh.model.utils import MNISTLoader
    5.  
    6.  
    7. data_loader = MNISTLoader()
    8. data = json.dumps({
    9. "instances": data_loader.test_data[0:3].tolist()
    10. })
    11. headers = {"content-type": "application/json"}
    12. json_response = requests.post(
    13. 'http://localhost:8501/v1/models/MLP:predict',
    14. data=data, headers=headers)
    15. predictions = np.array(json.loads(json_response.text)['predictions'])
    16. print(np.argmax(predictions, axis=-1))
    17. print(data_loader.test_label[0:10])

    输出:

    1. [7 2 1 0 4 1 4 9 6 9]
    2. [7 2 1 0 4 1 4 9 5 9]

    可见预测结果与真实标签值非常接近。

    对于自定义的Keras模型,在发送的数据中加入 signature_name 键值即可,即将上面代码的 data 建立过程改为

    1. data = json.dumps({
    2. "signature_name": "call",
    3. "instances": data_loader.test_data[0:10].tolist()
    4. })

    Node.js客户端示例(Ziyang)

    以下示例使用 Node.js 将下图转换为28*28的灰度图,发送给本机的TensorFlow Serving服务器,并输出返回的预测值和概率。(其中使用了 图像处理库jimp 和 HTTP库superagent ,可使用 npm install jimpnpm install superagent 安装)

    ../../_images/test_pic_tag_5.pngtest_pic_tag_5.png :一个由作者手写的数字5。(运行下面的代码时可下载该图片并放在与代码同一目录下)

    1. const Jimp = require('jimp')
    2. const superagent = require('superagent')
    3.  
    4. const url = 'http://localhost:8501/v1/models/MLP:predict'
    5.  
    6. const getPixelGrey = (pic, x, y) => {
    7. const pointColor = pic.getPixelColor(x, y)
    8. const { r, g, b } = Jimp.intToRGBA(pointColor)
    9. const gray = +(r * 0.299 + g * 0.587 + b * 0.114).toFixed(0)
    10. return [ gray / 255 ]
    11. }
    12.  
    13. const getPicGreyArray = async (fileName) => {
    14. const pic = await Jimp.read(fileName)
    15. const resizedPic = pic.resize(28, 28)
    16. const greyArray = []
    17. for ( let i = 0; i< 28; i ++ ) {
    18. let line = []
    19. for (let j = 0; j < 28; j ++) {
    20. line.push(getPixelGrey(resizedPic, j, i))
    21. }
    22. console.log(line.map(_ => _ > 0.3 ? ' ' : '1').join(' '))
    23. greyArray.push(line)
    24. }
    25. return greyArray
    26. }
    27.  
    28. const evaluatePic = async (fileName) => {
    29. const arr = await getPicGreyArray(fileName)
    30. const result = await superagent.post(url)
    31. .send({
    32. instances: [arr]
    33. })
    34. result.body.predictions.map(res => {
    35. const sortedRes = res.map((_, i) => [_, i])
    36. .sort((a, b) => b[0] - a[0])
    37. console.log(`我们猜这个数字是${sortedRes[0][1]},概率是${sortedRes[0][0]}`)
    38. })
    39. }
    40.  
    41. evaluatePic('test_pic_tag_5.png')

    运行结果为:

    1. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    2. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    3. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    4. 1 1 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. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    6. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    7. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    8. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    9. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    10. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    11. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    12. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    13. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    14. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    15. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    16. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    17. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    18. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    19. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    20. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    21. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    22. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    23. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    24. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    25. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    26. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    27. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    28. 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
    29. 我们猜这个数字是5,概率是0.846008837

    可见输出结果符合预期。

    注解

    如果你不熟悉HTTP POST,可以参考 这里 。事实上,当你在用浏览器填写表单(比方说性格测试)并点击“提交”按钮,然后获得返回结果(比如说“你的性格是ISTJ”)时,就很有可能是在向服务器发送一个HTTP POST请求并获得了服务器的回复。

    RESTful API是一个流行的API设计理论,可以参考 这里 获得简要介绍。

    关于TensorFlow Serving的RESTful API的完整使用方式可参考 文档 。