[译]TensorFlow Serving RESTful API

今年六月TensorFlow Serving在以往的gRPC API之外,开始支持RESTful API了,使得访问更加符合常用的JSON习惯,本文翻译自官方文档,提供RESTful API的使用指南,如与官网有出入,以官网为准,以下为正文。


除了gRPC APIs,TensorFlow ModelServer也开始支持使用RESTful API在TensorFlow模型上进行分类、回归、和预测了。本文介绍使用这些API的端点和request/response格式。

TensorFlow ModelServer通过host:port接受下面这种RESTful API请求:

POST http://host:port/<URI>:<VERB>
URI: /v1/models/${MODEL_NAME}[/versions/${MODEL_VERSION}]
VERB: classify|regress|predict

其中“/versions/${MODEL_VERSION}”是可选的,如果省略,则使用最新的版本。

该API基本遵循gRPC版本的PredictionService API。

请求URL的示例:

http://host:port/v1/models/iris:classify
http://host:port/v1/models/mnist/versions/314:predict

请求和回复都是JSON对象。该对象的组成取决于请求类型或操作。细节请查看下面的API特性一节。

为防错误,所有的API都会在返回体中返回一个JSON对象,其中“error”作为key,错误信息则是value:

{
"error": <error message string>
}

分类和回归API

请求格式

分类和回归的API的请求体必须是一个遵循下述格式的JSON对象:

{
  // Optional: serving signature to use.
  // If unspecifed default serving signature is used.
  "signature_name": <string>,

  // Optional: Common context shared by all examples.
  // Features that appear here MUST NOT appear in examples (below).
  "context": {
    "<feature_name3>": <value>|<list>
    "<feature_name4>": <value>|<list>
  },

  // List of Example objects
  "examples": [
    {
      // Example 1
      "<feature_name1>": <value>|<list>,
      "<feature_name2>": <value>|<list>,
      ...
    },
    {
      // Example 2
      "<feature_name1>": <value>|<list>,
      "<feature_name2>": <value>|<list>,
      ...
    }
    ...
  ]
}

其中“<value>”是一个JSON数字(整数或小数)或字符串,“<list>”则是一系列该<value>。查看下面的编码二进制值
一节可获知如何表示二进制(比特流)值。该格式和gRPC的“ClassificationRequest”和“RegressionRequest”接口很像。这些版本都接受Example对象的list。

回复格式

分类请求会在返回体中返回一个格式如下的JSON对象:

{
  "result": [
    // List of class label/score pairs for first Example (in request)
    [ [<label1>, <score1>], [<label2>, <score2>], ... ],

    // List of class label/score pairs for next Example (in request)
    [ [<label1>, <score1>], [<label2>, <score2>], ... ],
    ...
  ]
}

其中“<label>”是字符串(如果模型没有关联分数的label,可以为空字符串" ")。
“<score>”是小数(浮点型)。

回归请求会在返回体中返回一个格式如下的JSON对象:

{
  // One regression value for each example in the request in the same order.
  "result": [ <value1>, <value2>, <value3>, ...]
}

“<value>”是个小数。

gRPC API的用户会注意到这些格式和“ClassificationRequest”和“RegressionRequest”接口很像。

预测 API

请求格式

预测API的请求体必须是如下格式的JSON对象:

{
  // (Optional) Serving signature to use.
  // If unspecifed default serving signature is used.
  "signature_name": <string>,

  // Input Tensors in row ("instances") or columnar ("inputs") format.
  // A request can have either of them but NOT both.
  "instances": <value>|<(nested)list>|<list-of-objects>
  "inputs": <value>|<(nested)list>|<object>
}

以行的形式说明输入的tensor。

该格式和gRPC API和CMLE predict API的PredictRequest接口类似。如果所有命名的输入的tensor都有同样的0维,则使用这个格式。如果不是,则使用下面的列的形式。

在行形式中,输入的JSON请求中以instances为key。

如果只有一个命名的输入,则指定instances key的值作为输入值:

{
  // List of 3 scalar tensors.
  "instances": [ "foo", "bar", "baz" ]
}

{
  // List of 2 tensors each of [1, 2] shape
  "instances": [ [[1, 2]], [[3, 4]] ]
}

因为不需要手动展平list,tensor会以内嵌的形式表示。

对于多命名的输入,每个条目都需要成为包含输入的“名称/tensor”对的对象。比如说,下面就是一个有两个instances的请求,每个都有三个命名的输入tensor的集合:

{
 "instances": [
   {
     "tag": "foo",
     "signal": [1, 2, 3, 4, 5],
     "sensor": [[1, 2], [3, 4]]
   },
   {
     "tag": "bar",
     "signal": [3, 4, 1, 2, 5]],
     "sensor": [[4, 5], [6, 8]]
   }
 ]
}

注意,每个命名的输入("tag", "signal", "sensor")都假设有着同样的0维(在上面的例子中是2,因为在instances list中有两个对象)。如果你命名了不同0维的输入,就要使用下面描述的列形式。

以列的形式说明输入的tensor。

如果各个命名的输入的0维不一样,或者你想要一个更加紧凑的表现形式,就使用列的形式来说明你的输入tensor。该形式和gPRC的Predict请求的输入很像。

在列形式中,inputs被作为JSON请求的key。

inputs的值可以是单个输入tensor,或者是一个输入map(以其本身的嵌入格式排列)。每个输入可以是任意形式,不需要像上面的行形式一样包含相同的0维(也就是批尺寸batch size)。

上个例子的列形式如下:

{
 "inputs": {
   "tag": ["foo", "bar"],
   "signal": [[1, 2, 3, 4, 5], [3, 4, 1, 2, 5]],
   "sensor": [[[1, 2], [3, 4]], [[4, 5], [6, 8]]]
 }
}

注意,inputs是个JSON对象,不是像instances(行形式中所用)一样的list。并且所有的命名的输入都是一起说明的,不同于行形式的分到单独的行中去。这让表现形式更紧凑(但可能可读性不太好)。

回复格式

预测请求会在回复体中返回一个JSON对象。

行形式的请求有如下格式的回复:

{
  "predictions": <value>|<(nested)list>|<list-of-objects>
}

如果模型的输出只包含一个命名的tensor,我们省略名字和predictions key map,直接使用标量或者值的list。如果模型输出多个命名的tensor,我们输出对象list,和上面提到的行形式输入类似。

列形式的请求有如下格式的回复:

{
  "outputs": <value>|<(nested)list>|<object>
}

如果模型的输出只包含一个命名的tensor,我们省略名字和outputs key map,直接使用标量或者值的list。如果模型输出多个命名的tensor,我们输出对象,其每个key都和输出的tensor名对应,和上面提到的列形式输入类似。

输出二进制值

TensorFlow不区分非二进制和二进制值。所有的都是DT_STRING类型。tensor名中有_bytes后缀的表示有二进制值,每个值有着下面 编码二进制值中不同的编码。

JSON映射

RESTful APIs支持JSON的标准编码,使得不同系统间共享数据更简单。对于支持的类型,会按照下面的表进行一一对应编码。下表没列出的类型说明未支持。

TensorFlow数据类型 JSON值 JSON示例 备注
DT_BOOL true, false true, false -
DT_STRING string "Hello World!" 如果DT_STRING 表示的是二进制值(比如序列化的图片比特流),会以Base64编码。查看编码二进制值获取更多内容
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 number 1, -10, 0 JSON值为十进制整数
DT_FLOAT, DT_DOUBLE number 1.1, -10.0, 0, NaN, Infinity JSON值会是一个数字或者特殊标示值NaN和Infinity,查看JSON一致性获取更多内容。指数符号也是接受的。

编码二进制值

JSON使用UTF-8编码。如果你输入了需要变成二进制的feature或者tensor值(比如图片比特流),你必须用Base64编码数据,并且将其放入有b64作为key的JSON对象,如下:

{ "b64": <base64 encoded string> }

你可以将该值作为feature或者tensor的值,回复体也会以同样的形式编码。

一个有着image(二进制数据)和caption features 的分类请求如下:

{
  "signature_name": "classify_objects",
  "examples": [
    {
      "image": { "b64": "aW1hZ2UgYnl0ZXM=" },
      "caption": "seaside"
    },
    {
      "image": { "b64": "YXdlc29tZSBpbWFnZSBieXRlcw==" },
      "caption": "mountains"
    }
  ]
}

JSON一致性

很多feature或者tensor都是浮点型。除了有限的值外(e.g. 3.14, 1.0 等等),可以使用NaN或者无限值(Infinity 和-Infinity)。不幸的是JSON标准(RFC 7159)不识别这些值(即使JavaScript标准识别)。

REST API允许请求和回复体包含这些值。也就是说如下的请求是有效的:

{
  "example": [
    {
      "sensor_readings": [ 1.0, -3.14, Nan, Infinity ]
    }
  ]
}

遵循严格标准的JSON解析器会拒绝它并返回解析错误。为了准确地处理你的代码中的请求和回复,请使用支持这些标识的JSON解析器。

NaN, Infinity, -Infinity标识能被proto3、Python JSON模块和JavaScript语言识别。

示例

我们使用 half_plus_three模型来看看REST APIs的操作。

从REST API端口启动ModelServer

按照setup instructions来在你的系统上安装TensorFlow ModelServer。然后从git 仓库下载half_plus_three模型:

$ mkdir -p /tmp/tfserving
$ cd /tmp/tfserving
$ git clone --depth=1 https://github.com/tensorflow/serving

使用--rest_api_port选项来启动ModelServer输出REST API端口:

   --model_name=half_plus_three \
   --model_base_path=$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata/saved_model_half_plus_three/
使用REST API调用ModelServer

在不同的终端,使用curl 工具来进行REST API调用。一个预测调用如下所示:

$ curl -d '{"instances": [1.0,2.0,5.0]}' -X POST http://localhost:8501/v1/models/half_plus_three:predict
{
    "predictions": [3.5, 4.0, 5.5]
}

回归调用如下:

$ curl -d '{"signature_name": "tensorflow/serving/regress", "examples": [{"x": 1.0}, {"x": 2.0}]}' \
  -X POST http://localhost:8501/v1/models/half_plus_three:regress
{
    "results": [3.5, 4.0]
}

注意,回归可用于非默认签名,必须明确说明。不正确的请求URL或者body会返回错误状态.

curl -i -d '{"instances": [1.0,5.0]}' -X POST http://localhost:8501/v1/models/half:predict
HTTP/1.1 404 Not Found
Content-Type: application/json
Date: Wed, 06 Jun 2018 23:20:12 GMT
Content-Length: 65

{ "error": "Servable not found for request: Latest(half)" }

查看作者首页
官网原文

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容