Source code for service

#!flask/bin/python
"""
Flask application to serve Machine Learning models
"""
import os
import flask
import json
import argparse
import logging
import numpy as np

from time import time

from python.utils.encoder import ExtendedEncoder, returns_json
from python.factory import ModelFactory

# Version of this APP template
__version__ = '2.0.0'
# Read env variables
DEBUG = os.environ.get('DEBUG', True)
MODEL_NAME = os.environ.get('MODEL_NAME', 'model.joblib')
ENVIRONMENT = os.environ.get('ENVIRONMENT', 'local')
MODEL_TYPE = os.environ.get('MODEL_TYPE', 'SKLEARN_MODEL')
SERVICE_START_TIMESTAMP = time()
# Create Flask Application
app = flask.Flask(__name__)
# Customize Flask Application
app.logger.setLevel(logging.DEBUG if DEBUG else logging.ERROR)
app.json_encoder = ExtendedEncoder
# Create Model instance
model = ModelFactory.create_model(MODEL_NAME, MODEL_TYPE)
# laod saved model
app.logger.info('ENVIRONMENT: {}'.format(ENVIRONMENT))
app.logger.info('Using template version: {}'.format(__version__))
app.logger.info('Loading model...')
model.load()


[docs]@app.route('/predict', methods=['POST']) @returns_json def predict(): """Make preditcions and explain them Model inference using input data. This is the main function. URL Params: proba (int): 1 in order to compute probabilities for classification models or 0 to return predicted class (classification) or value (regression). Default 0. explain (int): 1 in order to compute moeldel explanations for the predicted value. This will return a status 500 when the model does not support explanations. Default 0. Payload: JSON string that can take two forms: The first, the payload is a record or a list of records with one value per feature. This will be directly interpreted as the input for the model. The second, the payload is a dictionary with 1 or 2 elements. The key "_data" is mandatory because this will be the input for the model and its format is expected to be a record or a list of records. On the other hand the key "_samples" (optional) will be used to obtain different explanations (see :func:`~model.Model.explain`) """ # Parameters do_proba = int(flask.request.args.get('proba', 0)) do_explain = int(flask.request.args.get('explain', 0)) input = json.loads(flask.request.data or '{}') if isinstance(input, dict): samples = input.get('_samples', None) data = input.get('_data', {}) if len(data.items()): input = data else: samples = None # Predict before_time = time() try: predict_function = 'predict_proba' if do_proba else 'predict' prediction = getattr(model, predict_function)(input) except Exception as err: return flask.Response(str(err), status=500) result = {'prediction': prediction} # Explain if do_explain: try: explanation = model.explain(input, samples=samples) except Exception as err: return flask.Response(str(err), status=500) else: result['explanation'] = explanation after_time = time() # log to_be_logged = { 'input': flask.request.data, 'params': flask.request.args, 'request_id': flask.request.headers.get('X-Correlation-ID'), 'result': result, 'model_info': model.info, 'elapsed_time': after_time - before_time } app.logger.debug(to_be_logged) return result
[docs]@app.route('/info', methods=['GET']) @returns_json def info(): """Model information Get the model information: metadata, type, classifier, etc. """ try: info = model.info except Exception as err: return flask.Response(str(err), status=500) else: return info
[docs]@app.route('/features', methods=['GET']) @returns_json def features(): """Model features Get the model accepted features. This includes feature inportance if the model allows it. """ try: features = model.features() except Exception as err: return flask.Response(str(err), status=500) else: return features
[docs]@app.route('/preprocess', methods=['POST']) @returns_json def preprocess(): """Preporcess input data Get the preprocessed version of the input data. If the model does not include preprocessing steps, this method will return the same data as the input. """ input = json.loads(flask.request.data or '{}') try: data = model.preprocess(input) except Exception as err: return flask.Response(str(err), status=500) else: return data
[docs]@app.route('/health') def health_check(): return flask.Response("up", status=200)
[docs]@app.route('/ready') def readiness_check(): if model.is_ready(): return flask.Response("ready", status=200) else: return flask.Response("not ready", status=503)
[docs]@app.route('/service-info') @returns_json def service_info(): """Service information Get information about the service: up-time, varsion of the template, name of the served model, etc. """ info = { 'version-template': __version__, 'running-since': SERVICE_START_TIMESTAMP, 'serving-model-file': MODEL_NAME, 'serving-model-family': model.family, 'debug': DEBUG} return info
if __name__ == '__main__': app.run( debug=DEBUG, host=os.environ.get('HOST', 'localhost'), port=os.environ.get('PORT', '5000'))