如何使用base64编码图像为TensorFlow Serving REST接口做好模型准备?

时间:2019-09-16 21:04:05

标签: tensorflow-serving

我的理解是,我应该能够从Google的AI集线器中获取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

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

Tensorboard中的输入层如下所示: enter image description here

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

Google's doc说...

  

URL

     

开机自检       http://host:port/v1/models/ $ {MODEL_NAME} [/ versions / $ {MODEL_VERSION}]:预测

     

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

     

请求格式
  预测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>
}
     

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

{ "b64": "base64 encoded string" }
     

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

     

具有图像(二进制数据)和字幕功能的分类请求   如下所示:

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

不确定性包括:

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

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

{
  "instances": [
                  {"b64": "iVBORw"},
                  {"b64": "pT4rmN"},
                  {"b64": "w0KGg2"}
                 ]
}

我使用了这张图片: 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

但是当我使用curl来发布base64编码的图像时,像这样:

{"instances" : [{"b64": "/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAA
...
KACiiigAooooAKKKKACiiigAooooA//Z"}]}

我得到这样的答复:

>./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" }

我已经尝试将相同文件的本地版本转换为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

但是发布此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]]]]}

...有效。 问题是此json文件的大小> 11 MB。

如何使json的base64编码版本有效?

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

本文介绍了如何编辑模型... Medium: Serving Image-Based Deep Learning Models with TensorFlow-Serving’s RESTful API ……不幸的是,它假定我们可以访问生成模型的代码。

user260826的解决方案使用估计器提供了一种变通方法,但是它假定该模型是keras模型。在这种情况下不正确。

是否存在一种通用方法来使模型准备好用于TensorFlow Serving REST接口,并具有可与任何TensorFlow模型格式一起使用的base64编码图像?

4 个答案:

答案 0 :(得分:1)

这不是一个答案,但是我没有足够的声誉来发表评论,所以...

这是到目前为止我发现的最有用的信息:

https://github.com/tensorflow/serving/issues/994#issuecomment-410165268

但是我仍然无法完全弄清它,因此,如果您的工作正常,请发布更新。

答案 1 :(得分:1)

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

{"b64": base64_encoded_string}

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

我最近在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)

一个很好的例子here

答案 2 :(得分:1)

第一步是以适当的格式导出训练好的模型。像这样使用export_inference_graph.py

python export_inference_graph \
    --input_type encoded_image_string_tensor \
    --pipeline_config_path path/to/ssd_inception_v2.config \
    --trained_checkpoint_prefix path/to/model.ckpt \
    --output_directory path/to/exported_model_directory

在上面的代码片段中,重要的是指定

-input_type encoding_image_string_tensor

导出模型后,使用新导出的模型照常运行tensorflow服务器。

推断代码如下:

from __future__ import print_function
import base64
import requests

SERVER_URL = 'http://localhost:8501/v1/models/vedNet:predict'

IMAGE_URL = 'test_images/19_inp.jpg'


def main():
    with open(IMAGE_URL, "rb") as image_file:
        jpeg_bytes = base64.b64encode(image_file.read()).decode('utf-8')
        predict_request = '{"instances" : [{"b64": "%s"}]}' % jpeg_bytes
        response = requests.post(SERVER_URL, predict_request)
        response.raise_for_status()
        prediction = response.json()['predictions'][0]

if __name__ == '__main__':
  main()

答案 3 :(得分:0)

我一直在为同样的问题而苦苦挣扎。最后我可以让它工作。我只需要为模型添加一个新签名:

import tensorflow as tf

model = tf.saved_model.load("/path/to/the/original/model")
# This is the current signature, that only accepts image tensors as input
signature = model.signatures["default"]

@tf.function()
def my_predict(image_b64):
    # Model doesn't support batch!!
    img_dec = tf.image.decode_png(image_b64[0], channels=3)
    img_tensor = tf.image.convert_image_dtype(img_dec, tf.float32)[tf.newaxis, ...]
    prediction = signature(img_tensor)
    return prediction


# Create new signature, to read b64 images
new_signature = my_predict.get_concrete_function(
    image_b64=tf.TensorSpec([None], dtype=tf.string, name="image_b64")
)

tf.saved_model.save(
    model,
    export_dir="/path/to/the/saved/model",
    signatures=new_signature
)

最后,在服务之后,我可以通过这样的输入进行预测:

{
  "instances": [
    {
      "b64": "youBase64ImageHere"
    }
  ]
}