15

谷歌云文档(请参阅预测输入中的二进制数据)指出:

您的编码字符串必须格式化为带有一个名为 b64 的键的 JSON 对象。以下 Python 示例使用 base64 库对原始 JPEG 数据的缓冲区进行编码以创建实例:

{"image_bytes":{"b64": base64.b64encode(jpeg_data)}}

在您的 TensorFlow 模型代码中,您必须为输入和输出张量命名别名,以便它们以“_bytes”结尾。

我想更多地了解这个过程在谷歌云端是如何工作的。

  • ml-engine 是否自动将“b64”字符串之后的任何内容解码为字节数据?

  • 当请求具有这种嵌套结构时,它是否只将“b64”部分传递给服务输入函数并删除“image_bytes”键?

  • 每个请求是单独传递给服务输入函数还是批量传递?

  • 我们是否在服务输入函数返回的 ServingInputReceiver 中定义输入输出别名?

我发现没有办法创建使用这种嵌套结构来定义特征占位符的服务输入函数。我只在我的中使用“b64”,我不确定 gcloud ml-engine 在接收请求时会做什么。

此外,在本地使用 进行预测时,使用gcloud ml-engine local predict嵌套结构发送请求失败,(意外的关键 image_bytes,因为它未在服务输入函数中定义)。但是在使用 进行预测时gcloud ml-engine predict,即使服务输入函数不包含对“image_bytes”的引用,也可以使用嵌套结构发送请求。gcloud predict 在省略“image_bytes”并仅传入“b64”时也有效。

一个示例服务输入函数

def serving_input_fn():
    feature_placeholders = {'b64': tf.placeholder(dtype=tf.string,
                                                  shape=[None],
                                                  name='source')}
    single_image = tf.decode_raw(feature_placeholders['b64'], tf.float32)
    inputs = {'image': single_image}
    return tf.estimator.export.ServingInputReceiver(inputs, feature_placeholders)

我给出了使用图像的示例,但我假设这同样适用于以字节和 base64 编码形式发送的所有类型的数据。

有很多 stackoverflow 问题包含对需要在信息片段中包含“_bytes”的引用,但如果有人可以更详细地解释发生了什么,我会发现它很有用,因为这样我就不会那么受到打击并在格式化请求时错过。

关于此主题的 Stackoverflow 问题

4

1 回答 1

25

为了帮助澄清您的一些问题,请允许我从预测请求的基本结构开始:

{"instances": [<instance>, <instance>, ...]}

JSON 对象在哪里instance(dict/map,我将在下文中使用 Python 术语“dict”),属性/键是输入的名称,其值包含该输入的数据。

云服务的作用(并gcloud ml-engine local predict使用与服务相同的底层库)是获取字典列表(可以被认为是数据行),然后将其转换为列表字典(可以被认为是包含批次实例的柱状数据)具有与原始数据中相同的键。例如,

{"instances": [{"x": 1, "y": "a"}, {"x": 3, "y": "b"}, {"x": 5, "y": "c"}]}

变成(内部)

{"x": [1, 3, 5], "y": ["a", "b", "c"]}

此 dict 中的键(因此,在原始请求中的实例中)必须对应于传递给ServingInputFnReceiver. 从这个例子中可以明显看出,服务“批处理”了所有数据,这意味着所有实例都作为一个批处理输入到图中。这就是为什么输入形状的外部维度必须None- 它是批处理维度,并且在发出请求之前是未知的(因为每个请求可能有不同数量的实例)。导出图表以接受上述请求时,您可以定义如下函数:

def serving_input_fn():
  inputs = {'x': tf.placeholder(dtype=tf.int32, shape=[None]),
            'y': tf.placeholder(dtype=tf.string, shape=[None]}
  return tf.estimator.export.ServingInputReceiver(inputs, inputs) 

由于 JSON 不(直接)支持二进制数据,而且 TensorFlow 无法区分“字符串”和“字节”,我们需要特别对待二进制数据。首先,我们需要输入的名称以“_bytes”结尾,以帮助区分文本字符串和字节字符串。使用上面的示例,假设y包含二进制数据而不是文本。我们将声明以下内容:

def serving_input_fn():
  inputs = {'x': tf.placeholder(dtype=tf.int32, shape=[None]),
            'y_bytes': tf.placeholder(dtype=tf.string, shape=[None]}
  return tf.estimator.export.ServingInputReceiver(inputs, inputs) 

请注意,唯一改变的是使用y_bytes而不是y作为输入的名称。

接下来,我们需要对数据进行实际的base64编码;在任何可以接受字符串的地方,我们都可以使用像这样的对象:{"b64": ""}。调整正在运行的示例,请求可能如下所示:

{
  "instances": [
    {"x": 1, "y_bytes": {"b64": "YQ=="}},
    {"x": 3, "y_bytes": {"b64": "Yg=="}},
    {"x": 5, "y_bytes": {"b64": "Yw=="}}
  ]
}

在这种情况下,服务完全按照它之前的方式执行,但增加了一个步骤:在发送到 TensorFlow 之前,它会自动对字符串进行 base64 解码(并用字节“替换” {"b64": ...} 对象)。所以 TensorFlow 实际上最终得到了一个 dict ,就像以前一样:

{"x": [1, 3, 5], "y_bytes": ["a", "b", "c"]}

(请注意,输入的名称没有改变。)

当然,base64 文本数据有点毫无意义;您通常会这样做,例如,对于无法通过 JSON 以任何其他方式发送的图像数据,但我希望上面的示例无论如何都足以说明这一点。

还有一点很重要:该服务支持一种速记。当您的 TensorFlow 模型只有一个输入时,无需在实例列表中的每个对象中不断重复该输入的名称。为了说明,想象导出一个只有 的模型x

def serving_input_fn():
  inputs = {'x': tf.placeholder(dtype=tf.int32, shape=[None])}
  return tf.estimator.export.ServingInputReceiver(inputs, inputs) 

“长格式”请求如下所示:

{"instances": [{"x": 1}, {"x": 3}, {"x": 5}]}

相反,您可以使用速记方式发送请求,如下所示:

{"instances": [1, 3, 5]}

请注意,这甚至适用于 base64 编码数据。因此,例如,如果x我们只导出而不是导出y_bytes,我们可以简化来自以下的请求:

{
  "instances": [
    {"y_bytes": {"b64": "YQ=="}},
    {"y_bytes": {"b64": "Yg=="}},
    {"y_bytes": {"b64": "Yw=="}}
  ]
}

至:

{
  "instances": [
    {"b64": "YQ=="},
    {"b64": "Yg=="},
    {"b64": "Yw=="}
  ]
}

在许多情况下,这只是一个小小的胜利,但它肯定有助于提高可读性,例如,当输入包含 CSV 数据时。

因此,将其完全适应您的特定场景,您的服务功能应该如下所示:

def serving_input_fn():
  feature_placeholders = {
    'image_bytes': tf.placeholder(dtype=tf.string, shape=[None], name='source')}
    single_image = tf.decode_raw(feature_placeholders['image_bytes'], tf.float32)
    return tf.estimator.export.ServingInputReceiver(feature_placeholders, feature_placeholders)

与您当前代码的显着差异:

  • 输入的名称不是 b64,但是image_bytes(可以是以 结尾的任何内容_bytes
  • feature_placeholders被用作两个参数 ServingInputReceiver

示例请求可能如下所示:

{
  "instances": [
    {"image_bytes": {"b64": "YQ=="}},
    {"image_bytes": {"b64": "Yg=="}},
    {"image_bytes": {"b64": "Yw=="}}
  ]
}

或者,可选地,简而言之:

{
  "instances": [
    {"b64": "YQ=="},
    {"b64": "Yg=="},
    {"b64": "Yw=="}
  ]
}

最后一点。gcloud ml-engine local predictgcloud ml-engine predict根据传入的文件内容构造请求。需要注意的是,文件内容当前不是一个完整的、有效的请求,而是--json-instances文件的每一行都成为实例列表中的一个条目。 . 特别是在您的情况下,该文件将如下所示(换行符在这里有意义):

{"image_bytes": {"b64": "YQ=="}}
{"image_bytes": {"b64": "Yg=="}}
{"image_bytes": {"b64": "Yw=="}}

或等效的速记。gcloud将采用每一行并构造上面显示的实际请求。

于 2018-03-08T16:26:45.357 回答