In this chapter, we will discuss the business logic pattern of serving models. In this pattern, we add some business logic, along with the model inference code. This is essential for successfully serving models because model inference code alone can’t meet the client’s requirements. We need additional business logic, such as user authentication, data validation, and feature transformation.
At a high level, we are going to cover the following main topics in this chapter:
In this chapter, we will use the same libraries that we used in previous chapters. You should have Postman or another REST API client installed to be able to send API calls and see the response. All the code for this chapter is provided at this link: https://github.com/PacktPublishing/Machine-Learning-Model-Serving-Patterns-and-Best-Practices/tree/main/Chapter%2011.
If you ModuleNotFoundError appears while trying to import a library, then you should install that module using the pip3 install <module_name> command.
In this section, we will introduce you to the business logic pattern of serving models.
When we bring our model to production, some business logic will be required. Business logic is any code that is not directly related to inference by the ML model. We have used some business logic throughout the book for various purposes, such as checking the threshold for updating models and checking the input type. Some examples of business logic are as follows:
We have learned about some examples of business logic under different categories. Next, we will how we can divide a lot of the business logic into two types of business logic.
In the previous section, we looked at different types of business logic. However, based on the location of the business logic code in the inference file, we can divide the business logic in the ML model into two types:
The location of these types can be seen in Figure 11.1. We can see that the model is called only if the pre-inference logic passes. Otherwise, an error is shown to the caller. After the model is invoked, we have the post-inference logic.
Figure 11.1 – Location of the pre-inference and post-inference business logic
In this section, we have seen some of the business logic that is used as part of serving an ML model. We have also seen that business logic can come before the model inference as well as after the model inference. In the next section, we will see technical approaches to some business logic, along with examples.
In the last section, we have learned about the different types of business logic along with examples. Some business logic is exclusive to ML serving, such as data validation, feature transformation, and so on. Some business logic is common to any kind of application – for example, user authentication, writing logs, and accessing databases. We will explain some business logic that is crucial to ML in the following sub-sections.
Data validation is very important in serving ML models. In most of the models in our previous chapters, we have assumed the user will pass data in the right format, but that may not always be the case. For example, let’s say the model needs input in the format of [[int, int]] to make inferences, and if the user does not follow the format, we will get errors.
For example, let’s consider the following code snippet:
model = RandomForestRegressor(max_depth=2) model.fit(X, y) print(model.predict([0, 0]))
Here, we are trying to pass [0, 0] to the model for prediction. However, we get the following error:
raise ValueError( ValueError: Expected 2D array, got 1D array instead: array=[0. 0.].
Therefore, we can add validation business logic before passing the input to the model that will check whether the shape of the input is correct.
We can add the following business logic before passing the input to the model for prediction:
Xt = np.array([0, 0]) if len(Xt.shape) != 2: print(f"Shape {Xt.shape} is not correct ") else: print(model.predict(Xt))
We are passing Xt data of the wrong shape in the preceding code snippet, so we get the following output:
Shape (2,) is not correct
We previously got an error that stopped our program. Now, as we are validating the input, we can avoid that exception. We can also raise a 4XX error from this if block by telling the client that the error is a client-side error with a readable error message.
We can also check the type of data that is passed as input to the model before the model is called. For example, let’s look at the following inference, where the input is a string that can’t be converted into a floating-point number:
Xt = np.array([["Zero", "Zero"]]) print(model.predict(Xt))
Therefore, we get the following error from the code snippet:
ValueError: could not convert string to float: 'Zero'
To solve this error, we can add the following validation code:
Xt = np.array([["Zero", "Zero"]]) try: Xt = Xt.astype(np.float64) print("Floating point data", Xt) print(model.predict(Xt)) except: print("Data type is not correct!")
The preceding code snippet will throw the following output:
Data type is not correct!
However, if you pass the Xt = np.array([["0", "0"]]) input, you will get the following output:
Floating point data [[0. 0.]] [6.941918]
So, the 0 string could be converted into a floating point. We can add many other validations such as this. We have to think about the kinds of validation that are needed for our model. The validations may be different for different models. The data validation happens before inference and that’s why this logic can be called pre-inference logic.
Feature transformation is very important for an ML model. Converting the raw data into some suitable features is very important if we want a good ML model. This transformation is also needed during inference. For example, suppose a feature in the input data is climate and can contain values of ["Sunny", "Cloudy", "Rainy"]. Now, the question is how we can pass this information to an ML model. The ML model can only deal with numeric data. The solution to this is one-hot encoding. For example, see the following code snippet:
import pandas as pd df = pd.DataFrame({"climate": ["Sunny","Rainy","Cloudy"]}) print("Initial data") print(df.head()) df2 = pd.get_dummies(df) print("Data after one hot encoding") print(df2)
In this code snippet, we are encoding the data that will convert the categorical data for the climate feature into numerical data. The output from the preceding code snippet is as follows:
Initial data climate 0 Sunny 1 Rainy 2 Cloudy Data after one hot encoding climate_Cloudy climate_Rainy climate_Sunny 0 0 0 1 1 0 1 0 2 1 0 0
After encoding, three features are created from the single climate feature. The new features are climate_Cloudy, climate_Rainy, and climate_Sunny. The value for the particular feature is 0 in the row for which the feature is present. For example, for the first row at index 0, the climate is Sunny, that’s why the value of climate_Sunny for the first row is 1.
The feature transformation logic needs to come before the model inference or training. That’s why this business logic is a pre-inference logic.
Sometimes, the predictions from the model are in a raw format that can’t be understood by the client. For example, let’s say we have a model predicting the names of flowers. The dataset that is used to train the model has four flowers:
The model can only work with numeric data. Therefore, the model can’t provide these names directly during prediction. After the predictions or inferences are done, we need a post-processing logic to map the predictions to the actual names. For example, the model detecting the four flowers can simply provide one of the following predictions:
Now, suppose we got a batch prediction from the model as follows:
[0, 0, 0, 1, 0, 1, 0, 2, 3]
We need to map this prediction to the following:
['Rose', 'Rose', 'Rose', 'Sunflower', 'Rose', 'Sunflower', 'Marigold', 'Lotus']
To do this mapping, we will add business logic such as the following:
response = [0, 0, 0, 1, 0, 1, 0, 2, 3] mapping = {0: "Rose", 1: "Sunflower", 2: "Marigold", 3: "Lotus"} converter = lambda x : mapping[x] final_response = [converter(x) for x in response] print(final_response)
We get the following output from the preceding code snippet:
['Rose', 'Rose', 'Rose', 'Sunflower', 'Rose', 'Sunflower', 'Rose', 'Marigold', 'Lotus']
So, we can see that final_response is a user-friendly response that can be returned to the client. As this business logic is used after the inference, we call it post-inference logic.
In this section, we have discussed some business logic with code examples that are critical for ML model serving and can be found in almost all serving cases.
In this chapter, we discussed the business logic pattern of serving ML models. We saw how different business logic can be added as preconditions before calling a model. We discussed different kinds of business logic that are essential for serving ML models. With this chapter, we have concluded our discussions of all the patterns of model serving that we wanted to cover in this book.
In the next few chapters, we will discuss some tools for serving ML models, starting with TensorFlow Serving.
18.188.36.239