From cf41b279b19edfa4c8c9257bd7a7da93a43cf8ea Mon Sep 17 00:00:00 2001 From: punkerpunker Date: Sun, 5 Nov 2023 13:52:21 +0000 Subject: [PATCH 1/2] Base64 image input --- README.md | 4 +++ deployments/tensorflow-serving/README.md | 35 +++++++++++++++++++ private_detector/private_detector.py | 43 ++++++++++++++++++++++++ train.py | 1 + 4 files changed, 83 insertions(+) create mode 100644 deployments/tensorflow-serving/README.md diff --git a/README.md b/README.md index 9fed215..dd05aa5 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ python3 inference.py \ +## Serving + +See [Tensorflow Serving example](deployments/tensorflow-serving/README.md) + ## Additional Training You can finetune the model yourself on your own data, to do so is fairly simple - though you will need the checkpoint files as can be found in `saved_checkpoint/` in `private_detector.zip` diff --git a/deployments/tensorflow-serving/README.md b/deployments/tensorflow-serving/README.md new file mode 100644 index 0000000..d4f4e22 --- /dev/null +++ b/deployments/tensorflow-serving/README.md @@ -0,0 +1,35 @@ +## Private Detector on Tensorflow Serving + +Tensorflow serving image could be pulled via: + +```shell +docker pull +``` + +Run: + +```shell +docker run -p 8501:8501 -e MODEL_NAME=private_detector -t +``` + +Ports exposed: +* REST API: **8501** +* GRPC: **8502** + +Calling a model: + +```python +import base64 +import json +import requests + +image_string = open("./test.jpeg", "rb").read() +endpoint = "http://localhost:8501/v1/models/private_detector:predict" +jpeg_bytes = base64.b64encode(image_string).decode('utf-8') +predict_request = {"instances" : [{"b64": jpeg_bytes}]} + +response = requests.post(endpoint, json.dumps(predict_request)) +print(response.json()) + +>>> {'predictions': [[0.0535223149, 0.946477652]]} +``` \ No newline at end of file diff --git a/private_detector/private_detector.py b/private_detector/private_detector.py index af4687f..385f393 100644 --- a/private_detector/private_detector.py +++ b/private_detector/private_detector.py @@ -12,6 +12,7 @@ from .utils.efficientnet_config import EfficientNetV2Config from .utils.effnetv2_model import EffNetV2Model from .utils.tensorboard_callback import Callback +from .utils.preprocess import pad_resize_image, preprocess_for_evaluation class PrivateDetector(): @@ -558,3 +559,45 @@ def __call__(self, inputs): inference_model, output_dir ) + + def save_base64_serving(self, output_dir: str, image_size: int = 480, input_dtype: tf.DType = tf.float16): + """ + Save model as a SavedModel ready for inference, accepting base64 image, returning class probabilities + + Parameters + ---------- + output_dir : str + Directory to save model to + image_size : int + Image size which the image was trained with + input_dtype: tf.DType + Input dtype image was trained with + """ + image_size = image_size or self.eval_image_size + + # This function will act as a tensorflow Lambda layer + def preprocess_input_base64(base64_input_bytes): + def decode_bytes(img_bytes): + img = tf.image.decode_jpeg(img_bytes, channels=3) + img = preprocess_for_evaluation(img, image_size, input_dtype) + img = tf.image.resize(img, [image_size, image_size]) + img = tf.image.convert_image_dtype(img,input_dtype) + return img + + base64_input_bytes = tf.reshape(base64_input_bytes, (-1,)) + return tf.map_fn(lambda img_bytes: + decode_bytes(img_bytes), + elems=base64_input_bytes, + fn_output_signature=input_dtype) + + # Adding base64 input layer and a decoding layers + inputs = tf.keras.layers.Input(shape=(), dtype=tf.string, name='b64_input_bytes') + decode_inputs = tf.keras.layers.Lambda(preprocess_input_base64, name='decode_image_bytes')(inputs) + model_base64 = self.model(decode_inputs) + + # Adding a softmax layer on top to get probabilities as the model's response + softmax = tf.keras.layers.Softmax() + model_base64_softmax = softmax(model_base64) + serving_model = tf.keras.Model(inputs, model_base64_softmax) + + tf.saved_model.save(serving_model, output_dir) diff --git a/train.py b/train.py index 035973d..052e1f9 100644 --- a/train.py +++ b/train.py @@ -214,6 +214,7 @@ def train(train_id: str, logger.info(f'Training complete, saving model to {train_id}') model.save(train_id) + model.save_base64_serving(train_id, input_dtype=dtype) logger.info(f'Model saved to {train_id}') From 71d593df176fab9c37ad7202d906f7f253c39237 Mon Sep 17 00:00:00 2001 From: punkerpunker Date: Sun, 5 Nov 2023 14:01:14 +0000 Subject: [PATCH 2/2] docker image tag --- deployments/tensorflow-serving/README.md | 4 ++-- private_detector/private_detector.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/deployments/tensorflow-serving/README.md b/deployments/tensorflow-serving/README.md index d4f4e22..f01689f 100644 --- a/deployments/tensorflow-serving/README.md +++ b/deployments/tensorflow-serving/README.md @@ -3,13 +3,13 @@ Tensorflow serving image could be pulled via: ```shell -docker pull +docker pull vazhega/private-detector:0.2.0 ``` Run: ```shell -docker run -p 8501:8501 -e MODEL_NAME=private_detector -t +docker run -p 8501:8501 -e MODEL_NAME=private_detector -t vazhega/private-detector:0.2.0 ``` Ports exposed: diff --git a/private_detector/private_detector.py b/private_detector/private_detector.py index 385f393..50c2a3d 100644 --- a/private_detector/private_detector.py +++ b/private_detector/private_detector.py @@ -12,7 +12,7 @@ from .utils.efficientnet_config import EfficientNetV2Config from .utils.effnetv2_model import EffNetV2Model from .utils.tensorboard_callback import Callback -from .utils.preprocess import pad_resize_image, preprocess_for_evaluation +from .utils.preprocess import preprocess_for_evaluation class PrivateDetector():