【问题标题】:What does google cloud ml-engine do when a Json request contains "_bytes" or "b64"?当 Json 请求包含“_bytes”或“b64”时,google cloud ml-engine 会做什么?
【发布时间】:2018-03-08 12:07:31
【问题描述】:

谷歌云文档(see Binary data in prediction input) 指出:

您的编码字符串必须格式化为 JSON 对象,其中包含一个 名为 b64 的密钥。以下 Python 示例对原始缓冲区进行编码 JPEG数据使用base64库制作实例:

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

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

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

  • ml 引擎是否自动解码“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 问题包含对需要在 sn-ps 信息中包含“_bytes”的引用,但如果有人可以更详细地解释发生了什么事情,我会发现它很有用,然后我会格式化请求时,不要太碰碰运气。

有关此主题的 Stackoverflow 问题

【问题讨论】:

    标签: gcloud tensorflow-serving google-cloud-ml tensorflow-estimator


    【解决方案1】:

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

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

    其中instance 是一个 JSON 对象(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"]}
    

    此字典中的键(因此,在原始请求中的实例中)必须与传递给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 用作 both 参数 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 将获取每一行并构造如上所示的实际请求。

    【讨论】:

    • 非常棒的答案!我还有两点需要澄清(1)(正如我从你的“JSON 不(直接)......”句子中理解的那样)通过用“_bytes”通知类型的 tensorflow,然后将该值作为字节类型读入?如果没有“_bytes”,张量流会将其读取为字符串? (2) feature_placeholders 是服务输入函数的两个参数的输入,那么模型如何访问解码后的单个图像?当然,这需要作为服务输入函数的输出提供给模型吗?否则模型只能访问未解码的字节
    • (1) TF 只有一种类型——字节。基本上,如果您使用名称 _bytes,则数据应为 {"b64": ...},但内部 TF 类型始终为字节,即使对于“字符串”也是如此。 (2) 你可能是对的。如果您已尝试上述说明但它们不起作用,请告诉我,我可以更新帖子。
    猜你喜欢
    • 2017-12-19
    • 2017-12-03
    • 1970-01-01
    • 2017-09-05
    • 1970-01-01
    • 1970-01-01
    • 2020-12-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多