├── Dockerfile ├── README.md ├── app ├── ML_artifact │ └── RFC_pipeline.sav └── main.py ├── loan-examples ├── 1.json └── 2.json ├── other └── images │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── a └── requirements.txt /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM frolvlad/alpine-miniconda3:python3.7 2 | 3 | COPY requirements.txt . 4 | 5 | RUN pip install -r requirements.txt 6 | 7 | EXPOSE 80 8 | 9 | COPY ./app /app 10 | 11 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Machine-learning-pipeline-deployment-using-Docker-and-FastAPI 2 | 3 | This project is about deploying the trained machine learning pipeline using FastAPI and Docker. The ml pipeline which will be deployed is taken from my repository [Credit-Risk-Analysis-for-european-peer-to-peer-lending-firm-Bandora](https://github.com/Arslan-Mehmood1/Credit-Risk-Analysis-for-european-peer-to-peer-lending-firm-Bandora). 4 | 5 | The ml pipeline includes a RandomForestClassifier for classifying the loan borrowers as **defaulted / not-defaulted**. 6 | 7 | ## Building API using FastAPI framework 8 | The API code must be in **'main.py'** file within a directory **'app'** according to FastAPI guidelines. 9 | 10 | Import the required packages 11 | ```python 12 | # Imports for server 13 | import pickle 14 | import pandas as pd 15 | from fastapi import FastAPI 16 | from pydantic import BaseModel 17 | 18 | # App name 19 | app = FastAPI(title="Loan Default Classifier for lending firm Bandora") 20 | ``` 21 | 22 | ### Representing the loan data point 23 | To represent a sample of loan details along with the data type of each atttribute, a class needs to be defined using the ```BaseModel``` from the pydantic library. 24 | ```python 25 | # defining base class for Loan to represent a data point for predictions 26 | class Loan(BaseModel): 27 | LanguageCode : object 28 | HomeOwnershipType : object 29 | Restructured : object 30 | IncomeTotal : float 31 | LiabilitiesTotal : float 32 | LoanDuration : float 33 | AppliedAmount : float 34 | Amount : float 35 | Interest : float 36 | EMI : float 37 | PreviousRepaymentsBeforeLoan : float 38 | MonthlyPaymentDay : float 39 | PrincipalPaymentsMade : float 40 | InterestAndPenaltyPaymentsMade : float 41 | PrincipalBalance : float 42 | InterestAndPenaltyBalance : float 43 | Bids : float 44 | Rating : object 45 | ``` 46 | 47 | ### Loading the trained Machine learning pipeline 48 | The trained machine learning pipeline needs to be loaded into memory, so it can be used for predictions in future. 49 | 50 | One way is to load the machine learning pipeline during the startup of our ```Server```. To do this, the function needs to be decorated with ```@app.on_event("startup")```. This decorator ensures that the function loading the ml pipeline is triggered right when the Server starts. 51 | 52 | The ml pipeline is stored in `app/ML_artifact' directory. 53 | 54 | ```python 55 | @app.on_event("startup") 56 | def load_ml_pipeline(): 57 | # loading the machine learning pipeline from pickle .sav format 58 | global RFC_pipeline 59 | RFC_pipeline = pickle.load(open('app/ML_artifact/RFC_pipeline.sav', 'rb')) 60 | ``` 61 | 62 | ### Server Endpoint for Prediction 63 | Finally, an endpoint on our server handles the **prediction requests** and return the value predicted by our deployed ml pipeline. 64 | 65 | The endpoint is **server/predict** with a **POST** operation. 66 | 67 | Finally, a JSON response is returned containing the prediction 68 | 69 | ```python 70 | # Defining the function for handling the prediction requests, it will be run by ```/predict``` endpoint of server 71 | # and expects an instance inference request of Loan class to make prediction 72 | 73 | @app.post("/predict") 74 | def predict(inference_request : Loan): 75 | # creating a pandas dataframe to be fed to RandomForestClassifier pipeline for prediction 76 | input_dictionary = { 77 | "LanguageCode" : inference_request.LanguageCode, 78 | "HomeOwnershipType": inference_request.HomeOwnershipType, 79 | "Restructured" : inference_request.Restructured, 80 | "IncomeTotal" : inference_request.IncomeTotal, 81 | "LiabilitiesTotal" : inference_request.LiabilitiesTotal, 82 | "LoanDuration" : inference_request.LoanDuration, 83 | "AppliedAmount" : inference_request.AppliedAmount, 84 | "Amount": inference_request.Amount, 85 | "Interest":inference_request.Interest, 86 | "EMI": inference_request.EMI, 87 | "PreviousRepaymentsBeforeLoan" : inference_request.PreviousRepaymentsBeforeLoan, 88 | "MonthlyPaymentDay" :inference_request.MonthlyPaymentDay, 89 | "PrincipalPaymentsMade" : inference_request.PrincipalPaymentsMade, 90 | "InterestAndPenaltyPaymentsMade" : inference_request.InterestAndPenaltyPaymentsMade, 91 | "PrincipalBalance" : inference_request.PrincipalBalance, 92 | "InterestAndPenaltyBalance" : inference_request.InterestAndPenaltyBalance, 93 | "Bids" : inference_request.Bids, 94 | "Rating" : inference_request.Rating 95 | } 96 | inference_request_Data = pd.DataFrame(input_dictionary,index=[0]) 97 | prediction = RFC_pipeline.predict(inference_request_Data) 98 | 99 | # Returning prediction 100 | if prediction == 0: 101 | return {"Prediction": "Not Defaulted"} 102 | else: 103 | return {"Prediction": "Defaulted"} 104 | ``` 105 | ### Server 106 | As our API has been built, the Uvicorn Server can be use the API to serve the prediction requests. But for now, this server will be dockerized. And final predictions will be served by the Docker container. 107 | 108 | ## Dockerizing the Server 109 | The Docker container will be run on localhost. 110 | ``` 111 | .. 112 | └── Base dir 113 | ├── app/ 114 | │ ├── main.py (server code) 115 | │ └── ML_artifact (dir containing the RFC_pipeline.sav) 116 | ├── requirements.txt (Python dependencies) 117 | ├── loan-examples/ (loan examples to test the server) 118 | ├── README.md (this file) 119 | └── Dockerfile 120 | ``` 121 | ## Creating the Dockerfile 122 | Now in the base directory, a file is created ```Dockerfile``. The ```Dockerfile``` contain all the instructions required to build the docker image. 123 | 124 | ```Dockerfile 125 | FROM frolvlad/alpine-miniconda3:python3.7 126 | 127 | COPY requirements.txt . 128 | 129 | RUN pip install -r requirements.txt 130 | 131 | EXPOSE 80 132 | 133 | COPY ./app /app 134 | 135 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 136 | ``` 137 | 138 | ### Base Image 139 | The `FROM` instruction allows to use a pre-existing image as base of our new docker image, instead of writing our docker image from the scratch. This allows the software in pre-existing image to be available in our new docker image. 140 | 141 | In this case `frolvlad/alpine-miniconda3:python3.7` is used as base image. 142 | - it contains python 3.7 143 | - also contains an alpine version of linux, which is a distribution created to be very small in size. 144 | 145 | Other existing images, can be used as base image of our new docker image, but size of those is a lot heavier. So using the one mentioned, as it a great image for required task. 146 | 147 | ### Installing Dependencies 148 | Now our docker image has environment with python installed, so the dependencies required for serving the inference requests need to be installed in our docker image. 149 | 150 | The dependencies are written in requirements.txt file in our base dir. This file needs to be copied in our docker image `COPY requirements.txt .` and then the dependencies are installed by `RUN pip install -r requirements.txt` 151 | 152 | ### Exposing the port 153 | Our server will listen to inference requests on port 80. 154 | ```Dockerfile 155 | EXPOSE 80 156 | ``` 157 | 158 | ### Copying our App into Docker image 159 | Our app should be inside the docker image. 160 | ```Dockerfile 161 | COPY ./app /app 162 | ``` 163 | 164 | ### Spinning up the server 165 | Dockers are efficient at carrying out single task. When a docker container is run, the `CMD` commands get executed only once. This is the command which will start our server by specifying the `host` and `post`, when a docker container created from our docker image is started. 166 | ```Dockerfile 167 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 168 | ``` 169 | 170 | ## Build the Docker Image 171 | Now in base dir, the Dockerfile is present. The Docker image is built using the `docker build` command: 172 | ```Dockerfile 173 | docker build -t ml_pipeline:RFC 174 | ``` 175 | The `-t` flags is used for specifying the **name:tag** of docker image. 176 | 177 | ## Run the Docker Container 178 | Now that the docker image is created. To run a docker container out of it: 179 | ```Dockerfile 180 | docker run -p 80:80 ml_pipeline:RFC 181 | ``` 182 | The `-p 80:80` flag performs port mapping operations. The container and as well as local machine, has own set of ports. As our container is exposed on port 80, so it needs to be mapped to a port on local machine which is also 80. 183 |

184 | 185 |

186 | 187 | ## Make Inference Requests to Dockerized Server 188 | Now that our server is listening on port 80, a `POST` request can be made for predicting the class of loan. 189 | 190 | The requests should contain the data in `JSON` format. 191 | ```JSON 192 | { 193 | "LanguageCode": "estonian" , 194 | "AppliedAmount": 191.7349 , 195 | "Amount": 140.6057 , 196 | "Interest" : 25 , 197 | "LoanDuration" : 1, 198 | "EMI":3655.7482, 199 | "HomeOwnershipType": "owner", 200 | "IncomeTotal" : 1300.0, 201 | "LiabilitiesTotal" : 0, 202 | "MonthlyPaymentDay":15, 203 | "Rating" : "f", 204 | "Restructured" : "no", 205 | "PrincipalPaymentsMade" : 140.6057, 206 | "InterestAndPenaltyPaymentsMade" : 2.0227, 207 | "PrincipalBalance" : 0, 208 | "InterestAndPenaltyBalance" : 0, 209 | "PreviousRepaymentsBeforeLoan" :258.6256, 210 | "Bids" : 140.6057 211 | } 212 | ``` 213 | ### FastAPI built-in Client 214 | FastAPI has a built-in client to interact with the deployed server. 215 |

216 | 217 | 218 |

219 | 220 | ### Using `curl` to send request 221 | `curl` command can be used to send the inference request to deployed server. 222 | ```bash 223 | curl -X POST http://localhost:80/predict \ 224 | -d @./loan-examples/1.json \ 225 | -H "Content-Type: application/json" 226 | ``` 227 | Three flags are used with `curl`: 228 | `-X`: to specify the type of request like `POST` 229 | `-d`: data to be sent with request 230 | `-H`: header to specify the type of data sent with request 231 | 232 | The directory `loan-examples` has 2 json files containing the loan samples for prediction, for testing the deployed dockerized server. 233 | -------------------------------------------------------------------------------- /app/ML_artifact/RFC_pipeline.sav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arslan-Mehmood1/Machine-learning-pipeline-deployment-using-Docker-and-FastAPI/1d98f8047628f56f340f470f88326c5d07c3ef78/app/ML_artifact/RFC_pipeline.sav -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | # Imports for server 2 | import pickle 3 | import pandas as pd 4 | from fastapi import FastAPI 5 | from pydantic import BaseModel 6 | 7 | # App name 8 | app = FastAPI(title="Loan Default Classifier for lending firm Bandora") 9 | 10 | # defining base class for Loan to represent a data point for predictions 11 | class Loan(BaseModel): 12 | LanguageCode : object 13 | HomeOwnershipType : object 14 | Restructured : object 15 | IncomeTotal : float 16 | LiabilitiesTotal : float 17 | LoanDuration : float 18 | AppliedAmount : float 19 | Amount : float 20 | Interest : float 21 | EMI : float 22 | PreviousRepaymentsBeforeLoan : float 23 | MonthlyPaymentDay : float 24 | PrincipalPaymentsMade : float 25 | InterestAndPenaltyPaymentsMade : float 26 | PrincipalBalance : float 27 | InterestAndPenaltyBalance : float 28 | Bids : float 29 | Rating : object 30 | 31 | 32 | @app.on_event("startup") 33 | def load_ml_pipeline(): 34 | # loading the machine learning pipeline from pickle .sav format 35 | global RFC_pipeline 36 | RFC_pipeline = pickle.load(open('app/ML_artifact/RFC_pipeline.sav', 'rb')) 37 | 38 | # Defining the function for handling the prediction requests, it will be run by '/predict' endpoint of server 39 | # and expects a Loan class datapoint for prediction 40 | @app.post("/predict") 41 | def predict(inference_request : Loan): 42 | # creating a pandas dataframe to be fed to RandomForestClassifier pipeline for prediction 43 | input_dictionary = { 44 | "LanguageCode" : inference_request.LanguageCode, 45 | "HomeOwnershipType": inference_request.HomeOwnershipType, 46 | "Restructured" : inference_request.Restructured, 47 | "IncomeTotal" : inference_request.IncomeTotal, 48 | "LiabilitiesTotal" : inference_request.LiabilitiesTotal, 49 | "LoanDuration" : inference_request.LoanDuration, 50 | "AppliedAmount" : inference_request.AppliedAmount, 51 | "Amount": inference_request.Amount, 52 | "Interest":inference_request.Interest, 53 | "EMI": inference_request.EMI, 54 | "PreviousRepaymentsBeforeLoan" : inference_request.PreviousRepaymentsBeforeLoan, 55 | "MonthlyPaymentDay" :inference_request.MonthlyPaymentDay, 56 | "PrincipalPaymentsMade" : inference_request.PrincipalPaymentsMade, 57 | "InterestAndPenaltyPaymentsMade" : inference_request.InterestAndPenaltyPaymentsMade, 58 | "PrincipalBalance" : inference_request.PrincipalBalance, 59 | "InterestAndPenaltyBalance" : inference_request.InterestAndPenaltyBalance, 60 | "Bids" : inference_request.Bids, 61 | "Rating" : inference_request.Rating 62 | } 63 | inference_request_Data = pd.DataFrame(input_dictionary,index=[0]) 64 | prediction = RFC_pipeline.predict(inference_request_Data) 65 | 66 | # Returning prediction 67 | if prediction == 0: 68 | return {"Prediction": "Not Defaulted"} 69 | else: 70 | return {"Prediction": "Defaulted"} 71 | 72 | 73 | -------------------------------------------------------------------------------- /loan-examples/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "LanguageCode": "estonian" , 3 | "AppliedAmount": 191.7349 , 4 | "Amount": 140.6057 , 5 | "Interest" : 25 , 6 | "LoanDuration" : 1, 7 | "EMI":3655.7482, 8 | "HomeOwnershipType": "owner", 9 | "IncomeTotal" : 1300.0, 10 | "LiabilitiesTotal" : 0, 11 | "MonthlyPaymentDay":15, 12 | "Rating" : "f", 13 | "Restructured" : "no", 14 | "PrincipalPaymentsMade" : 140.6057, 15 | "InterestAndPenaltyPaymentsMade" : 2.0227, 16 | "PrincipalBalance" : 0, 17 | "InterestAndPenaltyBalance" : 0, 18 | "PreviousRepaymentsBeforeLoan" :258.6256, 19 | "Bids" : 140.6057 20 | } 21 | -------------------------------------------------------------------------------- /loan-examples/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "LanguageCode": "estonian", 3 | "AppliedAmount": 319.5582, 4 | "Amount": 319.5409, 5 | "Interest" : 25 , 6 | "LoanDuration" : 20, 7 | "EMI":7988.5225, 8 | "HomeOwnershipType": "owner", 9 | "IncomeTotal" : 1300.0, 10 | "LiabilitiesTotal" : 0, 11 | "MonthlyPaymentDay":25, 12 | "Rating" : "f", 13 | "Restructured" : "yes", 14 | "PrincipalPaymentsMade" : 203.1909, 15 | "InterestAndPenaltyPaymentsMade" : 59.7626, 16 | "PrincipalBalance" : 116.35, 17 | "InterestAndPenaltyBalance" : 414.07, 18 | "PreviousRepaymentsBeforeLoan" :0, 19 | "Bids" : 319.5580 20 | } 21 | -------------------------------------------------------------------------------- /other/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arslan-Mehmood1/Machine-learning-pipeline-deployment-using-Docker-and-FastAPI/1d98f8047628f56f340f470f88326c5d07c3ef78/other/images/1.png -------------------------------------------------------------------------------- /other/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arslan-Mehmood1/Machine-learning-pipeline-deployment-using-Docker-and-FastAPI/1d98f8047628f56f340f470f88326c5d07c3ef78/other/images/2.png -------------------------------------------------------------------------------- /other/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arslan-Mehmood1/Machine-learning-pipeline-deployment-using-Docker-and-FastAPI/1d98f8047628f56f340f470f88326c5d07c3ef78/other/images/3.png -------------------------------------------------------------------------------- /other/images/a: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | scikit-learn==1.0.2 4 | pandas --------------------------------------------------------------------------------