Article Pre-Requisite
Consuming machine learning model
before exposing an API to consume the model, a series of test needed to ensure the model can be consumed.
get the joblib file from previous article.
load the joblib model and put in some test input
the input format would be : ['cell', 'module', 'pack', 'energy_density', 'power_density', 'c_rate', 'cycle_life', 'efficiency', 'self_discharge_rate', 'thickness', 'calendering_pressure', 'min_temp', 'max_temp']
import joblib
from sklearn.ensemble import RandomForestClassifier
model: RandomForestClassifier = joblib.load('battery_model.joblib')
predict.
import joblib
from fastapi import FastAPI
from sklearn.ensemble import RandomForestClassifier
model: RandomForestClassifier = joblib.load('battery_model.joblib')
features : list[float] = [15.5,346.55,2639.2,257.96,382.48,1.91,2530,97.38,1.03,76.43,123.02,-25.0,65.0]
prediction = model.predict([features])
print(prediction)
Expose to API
there are two ways to exposing the model to an API (so that FE/any system can call the model and supplying the input features).
MLFlow
Installation
Install pyarrow
pip install pyarrow
Install mlflow (dependent on pyarrow)
pip install mlflow
install pyenv
brew install pyenv
Run ML Flow UI
ensure mlflow is properly installed
mlflow -version
execute following command to run mlflow server
mlflow ui
open on the browser
http://127.0.0.1:5000/
MLFlow by default is exposed and listening to port 5000, open it on browser
Integrate local code to MlFlow
integrate local train.py with ML Flow
things that will be integrated: log model, experiment tracking, and performance monitoring
update train.py
#Pandas
import pandas as pd
from pandas import DataFrame
#SkLearn
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
#Model Generator
import joblib
# mlflow
import mlflow
import mlflow.sklearn
from mlflow.models.signature import infer_signature
# Load csv dataset to data frame
batteryGradingDataSet = './battery_classification_dataset_100.csv'
batterGradingDataFrame : DataFrame = pd.read_csv(batteryGradingDataSet)
# Cleansing & Transform
batterGradingDataFrame[['min_temp', 'max_temp']] = batterGradingDataFrame['operating_temperature'].str.extract(r'(-?\d+)\s*to\s*(-?\d+)').astype(float)
batterGradingDataFrame.drop(columns='operating_temperature', inplace=True)
# List down feature column
labelIndex = "performance_grade"
featureColumn : list[str] = []
for col in batterGradingDataFrame.columns:
if col != "id" and col != labelIndex:
featureColumn.append(col)
x = batterGradingDataFrame[featureColumn]
x.astype("float64")
y = batterGradingDataFrame[labelIndex]
print(batterGradingDataFrame.head())
print(featureColumn)
# Apply Standard Scaler
scaler = StandardScaler()
x_scaled = scaler.fit_transform(x)
# Test Splitting & Random Seed
test_size = 0.2
random_seed = 42
# Split the data
x_train, x_test, y_train, y_test = train_test_split(
x_scaled, # Your input features (after scaling)
y, # Your target/label values
test_size=test_size, # 20% test, 80% train
random_state=random_seed, # Ensures reproducibility
stratify=y # Keep class distribution balanced in both sets
)
#Train model with classification model (random forest)
# Integrate training process with ML flow.
with mlflow.start_run() as run:
run_id = run.info.run_id
mlflow.set_experiment("Battery Grading")
model = RandomForestClassifier(random_state=random_seed)
model.fit(x_train, y_train)
#Evaluate model (get the accuracy)
y_pred = model.predict(x_test)
accuracy = accuracy_score(y_test, y_pred)
print(classification_report(y_test, y_pred))
mlflow.log_param("random_seed", random_seed)
mlflow.log_metric("accuracy", accuracy)
#Export the model for further use without training
joblib.dump(model, 'battery_performance_model.joblib')
signature = infer_signature(x)
# Log the model along with the signature and input example
mlflow.sklearn.log_model(
sk_model=model,
artifact_path=f"battery_performance_model_{run_id}",
signature=signature,
input_example=x
)
Run the train.py model
execute following command
python3 train.py
check ML Flow UI, the experiment logs and history will be shown.
Executing artifact
since the artifact is logged (log_model), every run of the experinment it will create an artifact. This artifact can be deployed over HTTP protocol and can be directly used for testing (HTTP Invocation)
go to artifact on the specific run that would like to be deployed
copy the run id and execute following command
mlflow models serve -m runs:/<runId>/battery_performance_model_<runId> -p 1234 --no-conda
this will expose the artifact to port 1234
Result
invoke the API
curl --location 'http://localhost:1234/invocations' \
--header 'Content-Type: application/json' \
--data '{
"dataframe_split": {
"columns": [
"cell",
"module",
"pack",
"energy_density",
"power_density",
"c_rate",
"cycle_life",
"efficiency",
"self_discharge_rate",
"thickness",
"calendering_pressure",
"min_temp",
"max_temp"
],
"data": [
[
15.5,
346.55,
2639.2,
257.96,
382.48,
1.91,
2530,
97.38,
1.03,
76.43,
123.02,
-25,
65
],
[
6.17,
163.32,
1369.28,
130.04,
259.38,
0.81,
1013,
85.25,
3.36,
99.37,
77.24,
5,
40
]
]
}
}'
once the curl is executed, it will return following result:
exposed joblib model on Mlflow : Invocation
if everything is successful then the API will return prediction for each submitted feature (on the JSON Body).
its recommended to use mlflow server for testing purposes, to actually deploy on production server its best to use FastAPI to have full control over the API Server (eg: security, validation, integration with other BE services, etc)
Fast API
Create API Services that Consume JobLib Model
Install fastapi uvicorn (used to server on the fastapi basically the equivalent of nodemon on nodejs (if you’re familiar with nodejs))
pip install fastapi uvicorn
create an POST endpoint (“/batteryperformance”) that receive following faeture/json body parameter
['cell', 'module', 'pack', 'energy_density', 'power_density', 'c_rate', 'cycle_life', 'efficiency', 'self_discharge_rate', 'thickness', 'calendering_pressure', 'min_temp', 'max_temp']
app.py
import joblib
from fastapi import FastAPI
from sklearn.ensemble import RandomForestClassifier
from pydantic import BaseModel
import numpy as np
model: RandomForestClassifier = joblib.load('battery_model.joblib')
app = FastAPI()
class Features(BaseModel):
cell : float
module: float
pack : float
energy_density :float
power_density: float
c_rate : float
cycle_life : float
efficiency : float
self_discharge_rate : float
thickness : float
calendering_pressure : float
min_temp :float
max_temp : float
#features : list[float] = [15.5,346.55,2639.2,257.96,382.48,1.91,2530,97.38,1.03,76.43,123.02,-25.0,65.0]
#prediction = model.predict([features])
#print(prediction)
@app.post("/batteryperfromance/")
def predict(features: Features):
feature_list = [
features.cell,
features.module,
features.pack,
features.energy_density,
features.power_density,
features.c_rate,
features.cycle_life,
features.efficiency,
features.self_discharge_rate,
features.thickness,
features.calendering_pressure,
features.min_temp,
features.max_temp,
]
input_data = np.array(feature_list).reshape(1, -1)
prediction = model.predict(input_data)
return {"batterygrade": prediction[0]}
run the builder/server (uvicorn)
uvicorn app:app --reload --host 0.0.0.0 --port 5001
to run on the background
nohup uvicorn app:app --reload --host 0.0.0.0 --port 5001
test invocation
curl --location 'http://localhost:5001/batteryperfromance' \
--header 'Content-Type: application/json' \
--data '{
"cell": 15.5,
"module": 346.55,
"pack": 2639.2,
"energy_density": 257.96,
"power_density": 382.48,
"c_rate": 1.91,
"cycle_life": 2530,
"efficiency": 97.38,
"self_discharge_rate": 1.03,
"thickness": 76.43,
"calendering_pressure": 123.02,
"min_temp": -25.0,
"max_temp": 65.0
}'
API Invocation Result
test via postman