如何使用 base64 编码图像为 TensorFlow Serving REST 接口制作模型?

Ale*_*yan 12 tensorflow-serving

我的理解是,我应该能够从 Google 的 AI Hub 获取 TensorFlow 模型,将其部署到 TensorFlow Serving 并使用它通过使用 curl 的 REST 请求发布图像来进行预测。

我目前在 AI Hub 上找不到任何 bbox 预测器,但我确实在 TensorFlow 模型动物园中找到了一个:

http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz

我已将模型部署到 TensorFlow 服务,但文档并不清楚 REST 请求的 JSON 中应包含哪些内容。

我的理解是

  1. 模型的 SignatureDefinition 决定了 JSON 应该是什么样子
  2. 我应该对图像进行 base64 编码

我能够像这样获得模型的签名定义:

>python tensorflow/tensorflow/python/tools/saved_model_cli.py show --dir /Users/alexryan/alpine/git/tfserving-tutorial3/model-volume/models/bbox/1/ --all

MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['in'] tensor_info:
        dtype: DT_UINT8
        shape: (-1, -1, -1, 3)
        name: image_tensor:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['out'] tensor_info:
        dtype: DT_FLOAT
        shape: unknown_rank
        name: detection_boxes:0
  Method name is: tensorflow/serving/predict
Run Code Online (Sandbox Code Playgroud)

认为这里的形状信息告诉我模型可以处理任何尺寸的图像?

Tensorboard 中的输入层如下所示: 在此处输入图片说明

但是如何将此 SignatureDefinition 转换为有效的 JSON 请求?
我假设我应该使用 predict API ...

谷歌的文档说......

网址

POST http://host:port/v1/models/ ${MODEL_NAME}[/versions/${MODEL_VERSION}]:预测

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

请求格式
predict 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>
}
Run Code Online (Sandbox Code Playgroud)

编码二进制值 JSON 使用 UTF-8 编码。如果您有需要二进制的输入特征或张量值(如图像字节),则必须对数据进行 Base64 编码并将其封装在以 b64 作为键的 JSON 对象中,如下所示:

{ "b64": "base64 encoded string" }
Run Code Online (Sandbox Code Playgroud)

您可以将此对象指定为输入特征或张量的值。同样的格式也用于编码输出响应。

具有图像(二进制数据)和标题特征的分类请求如下所示:

{   "signature_name": "classify_objects",   "examples": [
    {
      "image": { "b64": "aW1hZ2UgYnl0ZXM=" },
      "caption": "seaside"
    },
    {
      "image": { "b64": "YXdlc29tZSBpbWFnZSBieXRlcw==" },
      "caption": "mountains"
    }   ] }
Run Code Online (Sandbox Code Playgroud)

不确定性包括:

  • 我应该在我的 JSON 中使用“实例”吗
  • 我应该对 JPG 或 PNG 或其他东西进行 base64 编码吗?
  • 图像应该具有特定的宽度和高度吗?

Serving Image-Based Deep Learning Models with TensorFlow-Serving's RESTful API中,建议采用以下格式:

{
  "instances": [
                  {"b64": "iVBORw"},
                  {"b64": "pT4rmN"},
                  {"b64": "w0KGg2"}
                 ]
}
Run Code Online (Sandbox Code Playgroud)

我使用了这张图片:https : //tensorflow.org/images/blogs/serving/cat.jpg

和 base64 编码它像这样:

  # Download the image
  dl_request = requests.get(IMAGE_URL, stream=True)
  dl_request.raise_for_status()

  # Compose a JSON Predict request (send JPEG image in base64).
  jpeg_bytes = base64.b64encode(dl_request.content).decode('utf-8')
  predict_request = '{"instances" : [{"b64": "%s"}]}' % jpeg_bytes
Run Code Online (Sandbox Code Playgroud)

但是当我使用 curl 发布 base64 编码的图像时,如下所示:

{"instances" : [{"b64": "/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAA
...
KACiiigAooooAKKKKACiiigAooooA//Z"}]}
Run Code Online (Sandbox Code Playgroud)

我得到这样的回应:

>./test_local_tfs.sh 
HEADER=|Content-Type:application/json;charset=UTF-8|
   URL=|http://127.0.0.1:8501/v1/models/saved_model/versions/1:predict|
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8501 (#0)
> POST /v1/models/saved_model/versions/1:predict HTTP/1.1
> Host: 127.0.0.1:8501
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json;charset=UTF-8
> Content-Length: 85033
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
< Date: Tue, 17 Sep 2019 10:47:18 GMT
< Content-Length: 85175
< 
{ "error": "Failed to process element: 0 of \'instances\' list. Error: Invalid argument: JSON Value: {\n    \"b64\": \"/9j/4AAQSkZJRgABAQAAS
...
ooooA//Z\"\n} Type: Object is not of expected type: uint8" }
Run Code Online (Sandbox Code Playgroud)

我已经尝试将同一文件的本地版本转换为 base64 像这样(确认 dtype 是 uint8)...

  img = cv2.imread('cat.jpg')   
  print('dtype: ' +  str(img.dtype))                                                                                                                                                                                
  _, buf = cv2.imencode('.jpg', img)
  jpeg_bytes = base64.b64encode(buf).decode('utf-8')
  predict_request = '{"instances" : [{"b64": "%s"}]}' % jpeg_bytes
Run Code Online (Sandbox Code Playgroud)

但是发布这个 JSON 会产生同样的错误。

但是,当 json 像这样格式化时......

{'instances': [[[[112, 71, 48], [104, 63, 40], [107, 70, 20], [108, 72, 21], [109, 77, 0], [106, 75, 0], [92, 66, 0], [106, 80, 0], [101, 80, 0], [98, 77, 0], [100, 75, 0], [104, 80, 0], [114, 88, 17], [94, 68, 0], [85, 54, 0], [103, 72, 11], [93, 62, 0], [120, 89, 25], [131, 101, 37], [125, 95, 31], [119, 91, 27], [121, 93, 29], [133, 105, 40], [119, 91, 27], [119, 96, 56], [120, 97, 57], [119, 96, 53], [102, 78, 36], [132, 103, 44], [117, 88, 28], [125, 89, 4], [128, 93, 8], [133, 94, 0], [126, 87, 0], [110, 74, 0], [123, 87, 2], [120, 92, 30], [124, 95, 33], [114, 90, 32], 
...
, [43, 24, 33], [30, 17, 36], [24, 11, 30], [29, 20, 38], [37, 28, 46]]]]}
Run Code Online (Sandbox Code Playgroud)

... 有用。问题是这个 json 文件的大小 > 11 MB。

如何使 json 的 base64 编码版本工作?

更新:似乎我们必须编辑预训练模型以在输入层接受 base64 图像

本文介绍了如何编辑模型...... 中:使用 TensorFlow-Serving 的 RESTful API 提供基于图像的深度学习模型 ......不幸的是,它假设我们可以访问生成模型的代码。

user260826 的解决方案提供了使用估算器的解决方法,但它假定模型是 keras 模型。在这种情况下不是真的。

是否有一种通用方法可以使用适用于任何 TensorFlow 模型格式的 base64 编码图像使模型为 TensorFlow Serving REST 接口做好准备?

gog*_*sca 1

正如您提到的,JSON 是一种非常低效的方法,因为有效负载通常超过原始文件大小,您需要转换模型,以便能够使用Base64编码处理写入字符串的图像字节:

{"b64": base64_encoded_string}
Run Code Online (Sandbox Code Playgroud)

这种新的转换将减少用于将图像从预测客户端传输到基础设施的预测时间和带宽利用率。

我最近使用了带有 TF Hub 和 Keras 的迁移学习模型,该模型使用 JSON 作为输入,正如您提到的,这对于预测来说并不是最佳的。我使用以下函数来覆盖它:

使用以下代码,我们添加一个新的服务函数,它将能够处理 Base64 编码的图像。

使用 TF 估计器模型:

h5_model_path = os.path.join('models/h5/best_model.h5')
tf_model_path = os.path.join('models/tf')
estimator = keras.estimator.model_to_estimator(
    keras_model_path=h5_model_path,
    model_dir=tf_model_path)

def image_preprocessing(image):
    """
    This implements the standard preprocessing that needs to be applied to the
    image tensors before passing them to the model. This is used for all input
    types.
    """
    image = tf.expand_dims(image, 0)
    image = tf.image.resize_bilinear(image, [HEIGHT, WIDTH], align_corners=False)
    image = tf.squeeze(image, axis=[0])
    image = tf.cast(image, dtype=tf.uint8)
    return image

def serving_input_receiver_fn():
    def prepare_image(image_str_tensor):
        image = tf.image.decode_jpeg(image_str_tensor, channels=CHANNELS)
        return image_preprocessing(image)

    input_ph = tf.placeholder(tf.string, shape=[None])
    images_tensor = tf.map_fn(
        prepare_image, input_ph, back_prop=False, dtype=tf.uint8)
    images_tensor = tf.image.convert_image_dtype(images_tensor, dtype=tf.float32)

    return tf.estimator.export.ServingInputReceiver(
        {'input': images_tensor},
        {'image_bytes': input_ph})

export_path = os.path.join('/tmp/models/json_b64', version)
if os.path.exists(export_path):  # clean up old exports with this version
    shutil.rmtree(export_path)
estimator.export_savedmodel(
    export_path,
    serving_input_receiver_fn=serving_input_receiver_fn)
Run Code Online (Sandbox Code Playgroud)

这里有一个很好的例子

  • 有使用 TF2 的示例吗? (6认同)