| 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 |
|
|