Introducing the ml_base Package

pip install ml_base

Creating a Simple Model

To show how to work with the MLModel base class we’ll create a simple model that we can make predictions with. We’ll use the scikit-learn library, so we’ll need to install it:

pip install scikit-learn
from sklearn import datasets
from sklearn import svm
import pickle
# loading the Iris dataset
iris = datasets.load_iris()
# instantiating an SVM model from scikit-learn
svm_model = svm.SVC(gamma=1.0, C=1.0)
# fitting the model
svm_model.fit(iris.data[:-1], iris.target[:-1])
# serializing the model and saving it
file = open(“svc_model.pickle”, ‘wb’)
pickle.dump(svm_model, file)
file.close()

Creating a Wrapper Class for Your Model

Now that we have a model object, we’ll define a class that implements the prediction functionality for the code:

import os
from numpy import array

class IrisModel(object):
def __init__(self):
dir_path = os.path.abspath(‘’)
file = open(os.path.join(dir_path, “svc_model.pickle”), ‘rb’)
self._svm_model = pickle.load(file)
file.close()
def predict(self, data: dict):
X = array([data[“sepal_length”],
data[“sepal_width”],
data[“petal_length”],
data[“petal_width”]]).reshape(1, -1)
y_hat = int(self._svm_model.predict(X)[0])
targets = [‘setosa’, ‘versicolor’, ‘virginica’]
species = targets[y_hat]
return {“species”: species}
model = IrisModel()prediction = model.predict(data={
“sepal_length”:1.0,
“sepal_width”:1.1,
“petal_length”: 1.2,
“petal_width”: 1.3})
prediction
{‘species’: ‘virginica’}

Creating an MLModel Class for Your Model

The model is already much easier to use because it provides the prediction from a class. The user of the model doesn’t need to worry about loading the pickled model object, or converting the model’s input into a numpy array. However, we are still not using the MLModel abstract base class, now we’ll implement a part of the MLModel’s interface to show how it works:

from ml_base import MLModel
class IrisModel(MLModel):
@property
def display_name(self):
return “Iris Model”
@property
def qualified_name(self):
return “iris_model”
@property
def description(self):
return “A model to predict the species of a flower based on its measurements.”
@property
def version(self):
return “1.0.0”

@property
def input_schema(self):
raise NotImplementedError()
@property
def output_schema(self):
raise NotImplementedError()
def __init__(self):
dir_path = os.path.abspath(‘’)
file = open(os.path.join(dir_path, “svc_model.pickle”), ‘rb’)
self._svm_model = pickle.load(file)
file.close()
def predict(self, data: dict):
X = array([data[“sepal_length”],
data[“sepal_width”],
data[“petal_length”],
data[“petal_width”]]).reshape(1, -1)
y_hat = int(self._svm_model.predict(X)[0])
targets = [‘setosa’, ‘versicolor’, ‘virginica’]
species = targets[y_hat]
return {“species”: species}
model = IrisModel()
print(model.qualified_name)
iris_model
print(model.display_name)
Iris Model
print(model.description)
A model to predict the species of a flower based on its measurements.
print(model.version)
1.0.0

Adding Schemas to Your Model

To add schema information to the model class, we’ll use the pydantic package. The pydantic package allows us to state the schema requirements of the model’s input and output programmatically as Python classes:

from pydantic import BaseModel, Field
from pydantic import ValidationError
from enum import Enum

class ModelInput(BaseModel):
sepal_length: float = Field(
gt=5.0, lt=8.0,
description=”The length of the sepal of the flower.”)
sepal_width: float = Field(
gt=2.0, lt=6.0,
description=”The width of the sepal of the flower.”)
petal_length: float = Field(
gt=1.0, lt=6.8,
description=”The length of the petal of the flower.”)
petal_width: float = Field(
gt=0.0, lt=3.0,
description=”The width of the petal of the flower.”)

class Species(str, Enum):
iris_setosa = “Iris setosa”
iris_versicolor = “Iris versicolor”
iris_virginica = “Iris virginica”

class ModelOutput(BaseModel):
species: Species = Field(
description=”The predicted species of the flower.”)
from ml_base.ml_model import MLModel,   
MLModelSchemaValidationException

class IrisModel(MLModel):
@property
def display_name(self):
return “Iris Model”
@property
def qualified_name(self):
return “iris_model”
@property
def description(self):
return “A model to predict the species of a flower based on its
measurements.”
@property
def version(self):
return “1.0.0”
@property
def input_schema(self):
return ModelInput
@property
def output_schema(self):
return ModelOutput
def __init__(self):
dir_path = os.path.abspath(‘’)
with open(os.path.join(dir_path, “svc_model.pickle”), ‘rb’)
as f:
self._svm_model = pickle.load(f)

def predict(self, data: dict):
model_input = ModelInput(**data)
# creating a numpy array using the fields in the input object
X = array([model_input.sepal_length,
model_input.sepal_width,
model_input.petal_length,
model_input.petal_width]).reshape(1, -1)
# making a prediction, at this point it’s a number
y_hat = int(self._svm_model.predict(X)[0])
# converting the prediction from a number to a string
targets = [“Iris setosa”, “Iris versicolor”, “Iris virginica”]
species = targets[y_hat]
# returning the prediction inside an object
return ModelOutput(species=species)
model = IrisModel()prediction = model.predict(data={
“sepal_length”: 6.0, “sepal_width”: 2.1,
“petal_length”: 1.2, “petal_width”: 1.3})
print(prediction)
ModelOutput(species=<Species.iris_virginica: ‘Iris virginica’>)
model = IrisModel()
model.input_schema.schema()
{
‘title’: ‘ModelInput’,
‘type’: ‘object’,
‘properties’: {
‘sepal_length’: {
‘title’: ‘Sepal Length’,
‘description’: ‘The length of the sepal of the flower.’,
‘exclusiveMinimum’: 5.0,
‘exclusiveMaximum’: 8.0,
‘type’: ‘number’
},
‘sepal_width’: {
‘title’: ‘Sepal Width’,
‘description’: ‘The width of the sepal of the flower.’,
‘exclusiveMinimum’: 2.0,
‘exclusiveMaximum’: 6.0,
‘type’: ‘number’
},
‘petal_length’: {
‘title’: ‘Petal Length’,
‘description’: ‘The length of the petal of the flower.’,
‘exclusiveMinimum’: 1.0,
‘exclusiveMaximum’: 6.8,
‘type’: ‘number’
},
‘petal_width’: {
‘title’: ‘Petal Width’,
‘description’: ‘The width of the petal of the flower.’,
‘exclusiveMinimum’: 0.0,
‘exclusiveMaximum’: 3.0,
‘type’: ‘number’
}
},
‘required’: [
‘sepal_length’,
‘sepal_width’,
‘petal_length’,
‘petal_width’
]
}
model.output_schema.schema()
{
‘title’: ‘ModelOutput’,
‘type’: ‘object’,
‘properties’: {
‘species’: {‘$ref’: ‘#/definitions/Species’}
},
‘required’: [‘species’],
‘definitions’: {
‘Species’: {
‘title’: ‘Species’,
‘description’: ‘An enumeration.’,
‘enum’: [‘Iris setosa’, ‘Iris versicolor’, ‘Iris virginica’],
‘type’: ‘string’
}
}
}

Using the ModelManager Class

The ModelManager class is provided to help manage model objects. It is a singleton class that is designed to enable model instances to be instantiated once during the lifecycle of a process and accessed many times:

from ml_base.utilities import ModelManagermodel_manager = ModelManager()
print(id(model_manager))
another_model_manager = ModelManager()
print(id(another_model_manager))
4851978496
4851978496
model_manager.load_model(“__main__.IrisModel”)
another_iris_model = IrisModel()
try:
model_manager.add_model(another_iris_model)
except ValueError as e:
print(e)
A model with the same qualified name is already in the ModelManager singleton.
model_manager.get_models()
[
{
‘display_name’: ‘Iris Model’,
‘qualified_name’: ‘iris_model’,
‘description’: ‘A model to predict the species of a flower based on its measurements.’,
‘version’: ‘1.0.0’
}
]
model_manager.get_model_metadata(“iris_model”)
{
‘display_name’: ‘Iris Model’,
‘qualified_name’: ‘iris_model’,
‘description’: ‘A model to predict the species of a flower based on its measurements.’,
‘version’: ‘1.0.0’,
‘input_schema’: {
‘title’: ‘ModelInput’,
‘type’: ‘object’,
‘properties’: {
‘sepal_length’: {
‘title’: ‘Sepal Length’,
‘description’: ‘The length of the sepal of the flower.’,
‘exclusiveMinimum’: 5.0,
‘exclusiveMaximum’: 8.0,
‘type’: ‘number’
},
‘sepal_width’: {
‘title’: ‘Sepal Width’,
‘description’: ‘The width of the sepal of the flower.’,
‘exclusiveMinimum’: 2.0,
‘exclusiveMaximum’: 6.0,
‘type’: ‘number’
},
‘petal_length’: {
‘title’: ‘Petal Length’,
‘description’: ‘The length of the petal of the flower.’,
‘exclusiveMinimum’: 1.0,
‘exclusiveMaximum’: 6.8,
‘type’: ‘number’
},
‘petal_width’: {
‘title’: ‘Petal Width’,
‘description’: ‘The width of the petal of the flower.’,
‘exclusiveMinimum’: 0.0,
‘exclusiveMaximum’: 3.0,
‘type’: ‘number’
}
},
‘required’: [
‘sepal_length’,
‘sepal_width’,
‘petal_length’,
‘petal_width’
]
},
‘output_schema’: {
‘title’: ‘ModelOutput’,
‘type’: ‘object’,
‘properties’: {
‘species’: {
‘$ref’: ‘#/definitions/Species’
}
},
‘required’: [‘species’],
‘definitions’: {
‘Species’: {
‘title’: ‘Species’,
‘description’: ‘An enumeration.’,
‘enum’: [
‘Iris setosa’,
‘Iris versicolor’,
‘Iris virginica’
],
‘type’: ‘string’
}
}
}
}
iris_model = model_manager.get_model(“iris_model”)
print(iris_model.display_name)
Iris Model
model_manager.remove_model(“iris_model”)
model_manager.get_models()
[]
model_manager.clear_instance()
model_manager = ModelManager()

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Brian Schmidt

Brian Schmidt

Coder and machine learning enthusiast