|
import ast |
|
import json |
|
import os |
|
|
|
import numpy as np |
|
import pandas as pd |
|
from autogluon.multimodal import MultiModalPredictor |
|
from constants import ( |
|
ALLOWED_INPUT_FORMATS, |
|
ALLOWED_OUTPUT_FORMATS, |
|
BRACKET_FORMATTER, |
|
COMMA_DELIMITER, |
|
IMAGE_COLUMN_NAME, |
|
JSON_FORMAT, |
|
LABELS, |
|
NUM_GPU, |
|
PREDICTED_LABEL, |
|
PROBABILITIES, |
|
PROBABILITY, |
|
SAGEMAKER_INFERENCE_OUTPUT, |
|
) |
|
from utils import infer_type_and_cast_value |
|
|
|
INFERENCE_OUTPUT = ( |
|
infer_type_and_cast_value(os.getenv(SAGEMAKER_INFERENCE_OUTPUT)) |
|
if SAGEMAKER_INFERENCE_OUTPUT in os.environ |
|
else [PREDICTED_LABEL] |
|
) |
|
NUM_GPUS = infer_type_and_cast_value(os.getenv(NUM_GPU)) |
|
|
|
|
|
def generate_single_csv_line_inference_selection(data): |
|
"""Generate a single csv line response. |
|
|
|
:param data: list of output generated from the model |
|
:return: csv line for the predictions |
|
""" |
|
contents: str |
|
for single_prediction in data: |
|
contents = ( |
|
BRACKET_FORMATTER.format(single_prediction) |
|
if isinstance(single_prediction, list) |
|
else str(single_prediction) |
|
) |
|
return contents |
|
|
|
|
|
def model_fn(model_dir): |
|
"""Load model from previously saved artifact. |
|
|
|
:param model_dir: local path to the model directory |
|
:return: loaded model |
|
""" |
|
predictor = MultiModalPredictor.load(model_dir) |
|
if NUM_GPUS is not None: |
|
predictor._config.env.num_gpus = NUM_GPUS |
|
|
|
return predictor |
|
|
|
|
|
def convert_to_json_compatible_type(value): |
|
"""Convert the input value to a JSON compatible type. |
|
|
|
:param value: input value |
|
:return: JSON compatible value |
|
""" |
|
string_value = "{}".format(value) |
|
try: |
|
return ast.literal_eval(string_value) |
|
except Exception: |
|
return string_value |
|
|
|
|
|
def transform_fn(model, request_body, input_content_type, output_content_type): |
|
"""Transform function for serving inference requests. |
|
|
|
If INFERENCE_OUTPUT is provided, then the predictions are generated in the requested format and concatenated in the |
|
same order. Otherwise, prediction_labels are generated by default. |
|
|
|
:param model: loaded model |
|
:param request_body: request body |
|
:param input_content_type: content type of the input |
|
:param output_content_type: content type of the response |
|
:return: prediction response |
|
""" |
|
if input_content_type.lower() not in ALLOWED_INPUT_FORMATS: |
|
raise Exception( |
|
f"{input_content_type} input content type not supported. Supported formats are {ALLOWED_INPUT_FORMATS}" |
|
) |
|
|
|
if output_content_type.lower() not in ALLOWED_OUTPUT_FORMATS: |
|
raise Exception( |
|
f"{output_content_type} output content type not supported. Supported formats are {ALLOWED_OUTPUT_FORMATS}" |
|
) |
|
|
|
data = pd.DataFrame({IMAGE_COLUMN_NAME: [request_body]}) |
|
result_dict = dict() |
|
|
|
result = [] |
|
inference_output_list = ( |
|
INFERENCE_OUTPUT if isinstance(INFERENCE_OUTPUT, list) else [INFERENCE_OUTPUT] |
|
) |
|
for output_type in inference_output_list: |
|
if output_type == PREDICTED_LABEL: |
|
prediction = model.predict(data) |
|
result_dict[PREDICTED_LABEL] = convert_to_json_compatible_type(prediction.squeeze()) |
|
elif output_type == PROBABILITIES: |
|
predict_probs = model.predict_proba(data) |
|
prediction = predict_probs.to_numpy() |
|
result_dict[PROBABILITIES] = predict_probs.squeeze().tolist() |
|
elif output_type == LABELS: |
|
labels = model.class_labels |
|
prediction = np.array([labels]).astype("str") |
|
result_dict[LABELS] = labels.tolist() |
|
else: |
|
predict_probabilities = model.predict_proba(data).to_numpy() |
|
prediction = np.max(predict_probabilities, axis=1) |
|
result_dict[PROBABILITY] = prediction.squeeze().tolist() |
|
result.append(generate_single_csv_line_inference_selection(prediction.tolist())) |
|
response = COMMA_DELIMITER.join(result) |
|
|
|
if output_content_type == JSON_FORMAT: |
|
response = json.dumps(result_dict) |
|
|
|
return response, output_content_type |
|
|