在客户端调用以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服务器,并输出返回的预测值和概率。(其中使用了 图像处理库jimpHTTP库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的完整使用方式可参考 文档