MLOps from Zero to Hero : Build your ML model

mlops
Artificial Intelligence
Data Science
Author

Farid Azouaou

Published

October 3, 2024

Motivation

One of the most challenging parts while building Data Science projects is how to ship into production your freshly developed Machine Learning model and keep track of it and ensure a full control of the model life cycle of the model.

Productionizing Machine Learning models and turn it into a real product requires mastering an expertise other than AI, ML & mathematics.

Now we have to deal with IT, infrastructure, security & monitoring.

Build a Machine Learning model

Before starting developing your Machine Learning models, make sure to use end-to-end frameworks rather than a standalone package.

Because this prevents your build spaghetti code to perform testing, validation & others operations.

For R users, I recommend tidymodels , mlr3 & for python users, keras & scikit-learn remain widely used in the community.

Training , testing & validation

We inspire from the example given by tidymodels authors by using hotel booking data, in order to build a a binary classification machine learning model that predicts if the customers will have children with them or not.

library(dplyr) # used for data manipulation
library(tidymodels) # end to end machine learning development

# import data using readr package 

hotels <- readr::read_csv('https://tidymodels.org/start/case-study/hotels.csv') %>%
    dplyr::mutate(dplyr::across(dplyr::where(is.character), as.factor))

# visualize the first rows of hotel data
hotels%>%
  head(20)%>%
  knitr::kable()
hotel lead_time stays_in_weekend_nights stays_in_week_nights adults children meal country market_segment distribution_channel is_repeated_guest previous_cancellations previous_bookings_not_canceled reserved_room_type assigned_room_type booking_changes deposit_type days_in_waiting_list customer_type average_daily_rate required_car_parking_spaces total_of_special_requests arrival_date
City_Hotel 217 1 3 2 none BB DEU Offline_TA/TO TA/TO 0 0 0 A A 0 No_Deposit 0 Transient-Party 80.75 none 1 2016-09-01
City_Hotel 2 0 1 2 none BB PRT Direct Direct 0 0 0 D K 0 No_Deposit 0 Transient 170.00 none 3 2017-08-25
Resort_Hotel 95 2 5 2 none BB GBR Online_TA TA/TO 0 0 0 A A 2 No_Deposit 0 Transient 8.00 none 2 2016-11-19
Resort_Hotel 143 2 6 2 none HB ROU Online_TA TA/TO 0 0 0 A A 0 No_Deposit 0 Transient 81.00 none 1 2016-04-26
Resort_Hotel 136 1 4 2 none HB PRT Direct Direct 0 0 0 F F 0 No_Deposit 0 Transient 157.60 none 4 2016-12-28
City_Hotel 67 2 2 2 none SC GBR Online_TA TA/TO 0 0 0 A A 0 No_Deposit 0 Transient 49.09 none 1 2016-03-13
Resort_Hotel 47 0 2 2 children BB ESP Direct Direct 0 0 0 C C 0 No_Deposit 0 Transient 289.00 none 1 2017-08-23
City_Hotel 56 0 3 0 children BB ESP Online_TA TA/TO 0 0 0 B A 0 No_Deposit 0 Transient 82.44 none 1 2016-12-08
City_Hotel 80 0 4 2 none BB FRA Online_TA TA/TO 0 0 0 D D 0 No_Deposit 0 Transient 135.00 none 1 2017-05-02
City_Hotel 6 2 2 2 children BB FRA Online_TA TA/TO 0 0 0 A A 0 No_Deposit 0 Transient 180.00 none 1 2016-08-07
City_Hotel 130 1 2 2 none BB FRA Offline_TA/TO TA/TO 0 0 0 A D 0 No_Deposit 0 Transient 71.00 none 0 2016-04-15
City_Hotel 27 0 1 1 none BB NLD Online_TA TA/TO 0 0 0 D D 0 No_Deposit 0 Transient 120.12 none 1 2016-05-18
Resort_Hotel 16 1 2 2 none BB GBR Corporate Corporate 0 0 0 A A 0 No_Deposit 0 Transient-Party 40.00 none 0 2017-02-17
Resort_Hotel 46 0 2 2 none BB PRT Offline_TA/TO TA/TO 0 0 0 D D 0 No_Deposit 0 Transient 162.00 none 0 2016-12-30
City_Hotel 297 1 1 2 none BB FRA Groups TA/TO 0 0 0 A A 0 No_Deposit 236 Transient-Party 65.00 none 0 2016-05-14
City_Hotel 423 1 1 2 none HB DEU Offline_TA/TO TA/TO 0 0 0 A A 0 No_Deposit 0 Transient-Party 122.40 none 1 2017-07-22
City_Hotel 22 1 2 2 none BB FRA Online_TA TA/TO 0 0 0 D D 0 No_Deposit 0 Transient 121.33 none 0 2017-03-13
Resort_Hotel 96 4 7 2 none HB GBR Groups TA/TO 0 0 0 A A 4 No_Deposit 0 Transient-Party 74.18 none 0 2017-03-12
City_Hotel 0 1 0 1 none BB PRT Corporate Corporate 0 0 0 E E 0 No_Deposit 0 Transient 139.00 parking 0 2016-07-18
Resort_Hotel 7 1 2 2 none HB NULL Online_TA TA/TO 0 0 1 E I 1 No_Deposit 0 Transient 78.42 parking 1 2016-10-31

As a part of ML model developement, we split the intial data into 3 differents distincts subsets :

  • Training: used to train the model, allowing it to learn patterns and relationships in the data

  • Validation: helps in making decisions such as choosing the best model architecture or selecting regularization parameters

  • Testing: provides an unbiased evaluation of the model’s final performance to ensure generalizability to unseen data

set.seed(123)
hotel_split <- rsample::initial_validation_split(hotels, strata = children)
hotel_train <- rsample::training(hotel_split)
hotel_test  <- rsample::testing(hotel_split)

## to use for monitoring example:
hotel_validation <- rsample::validation(hotel_split)

# build ml workflow using tidymodels components

set.seed(234) # to make sure that generated random numbers are similar 

hotels_ml_wf  <-
  workflow(
    children ~ average_daily_rate + reserved_room_type +
      lead_time + country + adults,
    boost_tree(mode = "classification", trees = 10)%>%  
      set_engine("xgboost", eval_metric = "auc") # use xgboost with Area Under the Curve as a evaluation metric
  )

# fit the model 
hotels_ml <- hotels_ml_wf %>%
  fit(data = hotel_train)

Prepare for deployment: ML inference

One of the common ways of serving your ML model, is ML inference by exposing the prediction module as a RESTful API.

By doing so, end users will be able to perform predictions using third party applications or via different scripts/programming language.

Make your model available

Before serving the model as an API, first we need to make sure that our model, can be accessed by the server. For that, a complementary package called pins is available to store the ML object in one of the following storage systems:

  • Local folder
  • AWS S3 / S3 Minio
  • Azure Blob Storage
  • Digital Ocean
  • GCP Storage
  • MS365 OneDrive

Two variants are available for python & R:

To make sure that all works fine, we create a local board, and later we’ll use another storage system for final deployent:

# create a board based on a folder 
ml_board <- pins::board_folder(path = "./ml_models",versioned = TRUE)

Now that we have our board ready, next steps is to use another package called vetiver that enables a fluid tool that guarantees a standardized ML model deployment, versioning and monitoring framework.

Below is an example how we create a vetiverized ;) ML object and how to save into our previously created board.

ml_model_name <- "hotels_ml"
# create vetiver ML model
ml_model_v <- vetiver::vetiver_model(
  model = hotels_ml,
  model_name = ml_model_name,
  description  = "ML model for predicting whther a customer will bring children with him or not") 

#  save it into pin board
ml_board%>%
  vetiver::vetiver_pin_write(board = .,
                             vetiver_model = ml_model_v)

turn your ML model into an API

The next steps is to prepare the API server that connects to the ml object stored in the board and expose it as a POST request.

For R users, plumber package is the one to go with.

On other side (python), fastApi is the well know framework for building RESTFul APIs

Vetiver offers a complementary function that does this for your, what you is to provide both board & the model name.

vetiver::vetiver_write_plumber(board = ml_board, name = ml_model_name)

A new file (plumber.R) will be generated, ready to serve your model prediction inference as an API, below is the script contained in the file.

# Generated by the vetiver package; edit with care

library(pins)
library(plumber)
library(rapidoc)
library(vetiver)
b <- board_folder(path = "ml_models")
v <- vetiver_pin_read(b, "hotels_ml", version = "20241001T113033Z-4a8d0")

#* @plumber
function(pr) {
    pr %>% vetiver_api(v)
}

ml api using vetiver & plumber

By serving the API via plumber.R file (run API), a swagger UI will be displayed in your browser as illustrated int the image below:

ml swagger using vetiver & plumber

As a best practice step, it is required to set a static the port & the host where to serve you API.

We add a new line at the beginning of the plumber.R file:

options("plumber.port" = 8080, "plumber.host" = "0.0.0.0")

library(pins)
library(plumber)
library(rapidoc)
library(vetiver)
b <- board_folder(path = "ml_models")
v <- vetiver_pin_read(b, "hotels_ml", version = "20241001T113033Z-4a8d0")

#* @plumber
function(pr) {
  pr %>% vetiver_api(v)
}

Now we have set them, serve the API in the active session and open a new session R or python (or any other language)), and invoke the ML API locally in order to generate predictions.

Request using R

Use httr2 package to send API requests, as illustrated the script below:

#  load library
library(httr2)

# define the body that contains input values to be used for prediction
input_data <- data.frame(average_daily_rate = 180, reserved_room_type = "",
                         lead_time = 45,
                         country = "DE" ,
                         adults = 2)
req_body <- list(input_data = input_data)

# specify target end point for prediction
main_url <- "http://127.0.0.1:8080/"
target_endpoint <- "predict"  

# initialize the request
req_url <- httr2::request(main_url)

# add request specifications
ml_api_req <-
  req_url %>% # initialize request
  req_url_path_append(target_endpoint) %>%  # add endpoint
  # req_headers(token = token,user = user_mail) %>% # add header
  req_method(method = "POST")%>% # add request method
  req_body_json(data = input_data)%>% # add body content
  req_error(is_error = function(res) FALSE) 

# perform an API request and get the result
ml_api_resp <- ml_api_req%>%  # whether to handle errors
  req_perform() %>%  # send a request
  resp_body_json()%>% # convert json response object 
  print() # print response result

Request using python

We use the both requests & json for requesting and preparing the reponse:

# import libaries
import requests 
import json

import pandas as pd
import numpy as np

# prepare input data 
Columns = ['average_daily_rate', 'reserved_room_type', 'lead_time', 'country', 'adults']
values = [[180, 'A', 45 , 'DE', 2]]

df = pd.DataFrame( data = values , columns = Columns ) 

df = df.to_dict('records')
# add input data for which we want to get prediciton
body = {'input_data': df}

# df

# specify target end point for prediction
target_endpoint = "MAIN_URL" #"thaink2_sales_pred"

main_url <- "http://127.0.0.1:8080/" + target_endpoint

# send request
response = requests.post(main_url, data = json.dumps(body), headers = headers)

# printing request content
print(response.content)

Summary

What we have described the sections above, is a part of a full process to deploy end-to-end machine learning/deep learning models on a large scale.

In the upcoming sessions we’ll tackle complementary components such as docker, CI/CD & kubernetes and more additional tools and best practices to build a successful complete project.