How to build models as products using MLOps? Part 4 | Deploying Models in APIs with kedro-fast-api

Ricardo Raspini Motta
Indicium Engineering
9 min readJan 14, 2022

--

In case you missed the other posts in this series, check them out:

Now in this part 4, our machine learning models are already trained, the code is reproducible and easy to maintain, and we already have control of the metrics.

But we don’t have a usable product yet. Or rather, we didn’t have one until now.

To do this we are going to use one of the most common ways of integrating technology services, which is an API or Application programming interface.

The main advantage of an API is that with it you can interact with an application without knowing how it was implemented. Basically, it is like going to a restaurant, placing an order, and receiving it without knowing how it was prepared inside the kitchen.

Attention: in this post we are going to use the concept of class creation in Python; in case it is unfamiliar to you, we suggest studying object-oriented Python. There is a lot of free material available on the internet, including videos on YouTube.

To create an API is to create an access point for other applications to receive the results of your model (the ready meal) from a request containing the inputs of the model (the order placed with the waiter).

An API can be public in a URL on the Internet (where you usually control security using an access key) or in a local port on an internal network.

Because of its versatility and security, an API is a great tool for deploying your models. To do this, we will use Kedro together with a new tool: the Kedro Fast API, an extension created by our team at Indicium.

kedro-fast-api

That’s right! We have developed a plugin for API creation using Fast API to enable the creation with few additional configuration files, and take advantage of all the benefits we have seen from Kedro to structure this whole process to keep it organized, sustainable and scalable.

Indicium has made a Kedro extension for API creation and we will present here this new approach and how to create your first production-ready API. The repository of the project can be found at this link.

Next, we will show you this structure in more detail.

Installation

The installation of the package can be done from the following command in the terminal, in the same folder as the Kedro project:

$ pip install kedro-fast-api

This installation will be confirmed by the command kedro info, and this command will return including kedro-fast-api:

Initializing

After the installation, you need to start the plugin using the command:

$ kedro fast-api init

This initialization will create the default plugin files, which are:

  • conf/api.yml
  • conf/base/api_pipeline/catalog.yml
  • src/api_pipeline/nodes.py
  • src/api_pipeline/catalog.py

The files nodes.py and pipeline.py will be created in a new Kedro pipeline called api_pipeline, which will be responsible for saving the predictor (we will talk about this later).

The center of the process is the pipeline predictor, which is the object that actually makes the model predictions.

The Predictor

In short, the predictor is an model’s encapsulation so that it can be stored in memory (using a kedro catalog and saving in pickle format) to be used with any input data, in any environment. This prepares the model to be shipped anywhere in the galaxy.

For this purpose, the node file creates a class called predictor, which has the predict method.

The object created from this class is saved instantiated using your machine learning model. Since it is saved as an instantiated object, the inputs needed to make a prediction can be passed as method arguments, since the model was included in the object during its save.

Therefore, the predictor (here called MLPredictor) provides that the prediction can be made using the following Python command:

MLPredictor.predict(inputs)

Here, inputs are the inputs to the machine learning model, and outputs are the results of the predictions, which will be returned as a Python dictionary, i.e. as basically is the response from an API in JSON.

As we mentioned earlier, the Kedro node will execute only one command to instantiate the class (create the object with the model inside it) and save the predictor in memory, making the whole structure work perfectly.

For clarity, take a look at the structure of the nodes.py file:

import pandas as pdclass MLPredictor:  def __init__(self, model):
self.model = model
def predict(self, args_API: pd.DataFrame):
df_args = args_API
prediction = self.model.predict(df_args)
return {“prediction”: prediction}
def save_predictor(model):
predictor = MLPredictor(model)
return predictor

The Kedro node is the save_predictor function, which will receive only the already trained model. You can also create more complex predictors, which can include creating features or using multiple models, but the structure will be the same.

So when you save, the model will be inside the predictor through the self.model object. And the pipeline.py file will be:

from kedro.pipeline import Pipeline, node
from .nodes import save_predictor
def create_pipeline(**kwargs):
return Pipeline(
[
node(
func=save_predictor,
name=”save_predictor”,
inputs=”model”, ### Replace with model’s name
outputs=”MLPredictor”,
)
]
)

In this case, the input is the model “model”, whose name should be updated with the name of the model itself used in the project. You can also see that the inputs of the kedro node are not the inputs of the model itself. The kedro node entries are the files needed to create a file capable of making predictions (class parameters), and the model entries are the predict method entries (method parameters)

The default predictor does not do any operations on the data. The Pandas DataFrame received by the API (via the data dictionary) is passed directly to the predict method. However, there is no restriction on including intermediate steps for feature engineering, for example. The predict method can alter the DataFrame to make it the most appropriate for correct prediction.

Furthermore, you can include more than one trained model, and include additional answers in the predict method’s output dictionary. This flexibility is one of the main advantages of this method.

You can also replicate the files and create different predictors that are created in the same pipeline, allowing more than one API to be created with just one configuration file (we will see later how to create routes for the different predictors).

Configuration files

The first configuration file we are going to deal with is an old familiar one: catalog.yml. This catalog will be related to saving the predictor, and has the following structure:

MLPredictor:
type: pickle.PickleDataSet
filepath: data/09_predictor/MLPredictor.pickle
versioned: true

As we have seen previously in other posts in this series, we can save a Python object as a Pickle dataset using Kedro.

Besides it, we have the api.yml file, which is the file with the information needed to create the actual API. Its default file has the following structure:

routes:
model_API:
# Catalog reference to .pickle model
predictor: MLPredictor
# Inputs passed as a python dictionary
# allowed types int, float, str, bool and enum
parameters:
input_1:
# enum type requires options (numbers as options won’t work)
type: enum
options:
- some
- options
- here
input_2:
type: int
input_3:
type: float
input_4:
type: bool
input_5:
type: str

Let’s take a look at it step by step. The structure is that of the well-known YAML. The file starts with the routes information: each route is a different API, and the route name is the name that appears in the URL, right after the address (for example: localhost:8000/model_API).

In this example the configured route is model_API. It specifies the following properties:

  • predictor: the name of the predictor saved in the catalog
  • parameters: the parameters are the API entries, and you must specify their name and data type, but you can also pre-determine the accepted values.

That’s it! After setting these properties, the API is ready to be created.

Creating the API

The command for creating the API is:

$ kedro fast-api run

Yes! That’s it.

If everything is correct, the API will be created from the configuration file in the conf/api.yml folder.

If you need to specify another file, use the command:

$ kedro fast-api run -p path_to_file/API_file_name.yml

Now the API will be available for accessing and creating the first predictions. The access can be done at the following URL:

localhost:8000/model_API/

Using the API

The kedro-fast-api generates an interface to make requests manually, where each of the fields detailed in the api.yml appears with their respective formats and accepted values. This way you can manually fill in the fields in the interface and generate a request.

You can also create a description and add more details to your API. The page found will look like this:

An API Docs example

By clicking “GET” under “users”, and then “Try it out”, you can fill in the fields and get a response from the template. If all goes well, your results will appear directly below, plus a link for you to do this in an automated way (for example using Python requests). So, using the default link, you can automate the writing of the URL and the process in other environments that have access to the address where the API is located.

An example of a result using the Pokémon project (mentioned in other posts in this series), is the prediction for the type of Pokémon from its characteristics.

An API results example

Now, with the API ready, you can pass it to your team’s development team so that it can be deployed.

Best practices

Document your code:

  • The person who is going to use your model did not participate in its construction and, therefore, does not know the inputs, outputs, nor the methodology used; make it easy to read and interpret the code; make necessary changes and corrections and whatever else you need to facilitate your team’s daily routine.

Write readable code:

  • Pay attention to the variable names; include an indication of what each variable is instead of using names like “x” or the classic “df”, which make the code difficult to read, use “customers” or “orders”;
  • Be linear whenever possible, avoid random orders of functions in the body of the code;
  • Use separate functions to execute complex steps in your code to aid readability. The tip is: if a function has more than one logical level, break it into smaller functions;

Never repeat code:

  • If you had to copy and paste a piece of code, it should be a function called twice;
  • Use function names that already give you an idea of what is happening in the function, for example: clean_data_frame(data_frame)

Try to predict errors:

  • If you know your model will break if a column is sent with a different value than those used in training the model, include a test in your predictor that returns specific error dictionaries. For example:
if error:  return {‘error’: ‘error description’}

This way, the API return will be an error, and the person will be able to know what happened and how to change their inputs to prevent it from happening again. If your model breaks internally, there will be no information about it. The same would happen if a cook burns your steak, doesn’t tell you what happened, and you get an empty plate with no explanation. We certainly don’t want that to happen.

By following these best practices, your code will be easier to maintain both for you in the future and for your API users. That said, we’ve come to the end of the last leg of this Machine learning series: how to build models as products using MLOps.

Our model was ready, and over the course of the articles in the series, we learned how to get it structured, with metrics tracking, and deployed to an API.

Want to learn more about machine learning, data science, analytics, and more?

Subscribe to our newsletter to receive all our content first hand.

--

--

Ricardo Raspini Motta
Indicium Engineering

Data Scientist, Mechanical Engineer, passionate about data and convinced learner.