Deploy#
Sources
Deployment page in offcial MLflow documentation;
Deploy mlflow model to Kubernetes article in the official site.
Setup#
import subprocess
import pandas as pd
from pathlib import Path
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
import mlflow
from mlflow.models.signature import ModelSignature
from mlflow.types.schema import Schema, ColSpec
We need docker containers with mlflow.
%%bash
docker run -p 5000:5000 -dt --name mlflow_deploy --rm \
ghcr.io/mlflow/mlflow \
bash -c "mlflow server --host 0.0.0.0 --port 5000"
4ea5fdd3eeab8fa057ad9c1ad1575039129e8e4b75a187f0c0449d9986c41156
To be able to work with these started mlflow, we need some setup.
mlflow.set_tracking_uri("http://localhost:5000")
exp_name = "penguin_classification"
mlflow.create_experiment(exp_name)
'448934722381259260'
When you have finished playing with the notebook, do not forget to stop the container.
%%bash
docker stop mlflow_deploy
mlflow_deploy
Add model to registry#
We will consider simple example - model that callasify penguins based on parameters of their “culmen”.
Create run#
Create a run with the model in question. Everything is basic except the signature
parameter of the sklearn.log_model
method.
So by code:
input_schema = Schema([
ColSpec("double", "Culmen Length (mm)"),
ColSpec("double", "Culmen Depth (mm)"),
])
We have defined that the model takes two parameters: Culmen Length (mm)
and Culmen Depth (mm)
. This is important because it’s exactly the number that should be passed to the model implementation.
input_schema = Schema([
ColSpec("double", "Culmen Length (mm)"),
ColSpec("double", "Culmen Depth (mm)"),
])
output_schema = Schema([ColSpec("string")])
signature = ModelSignature(inputs=input_schema, outputs=output_schema)
mlflow.set_experiment(exp_name)
with mlflow.start_run() as run:
run_id = run.info.run_id
print(f"Started run {run_id}")
# Load dataset
print("Load dataset...")
culmen_columns = ["Culmen Length (mm)", "Culmen Depth (mm)"]
target_column = "Species"
data = pd.read_csv(
Path("deploy_files")/"penguins_classification.csv"
)
print("Prepare a train-test-split...")
data, target = data[culmen_columns], data[target_column]
data_train, data_test, target_train, target_test = train_test_split(
data, target, random_state=0)
# Initialize and fit a classifier
max_depth = 3
max_leaf_nodes = 4
print(f"Initialize and fit a DecisionTreeClassifier with max_depth={max_depth}, max_leaf_nodes{max_leaf_nodes}")
mlflow.log_params(
{"max_depth": max_depth,
"max_leaf_nodes": max_leaf_nodes}
)
tree = DecisionTreeClassifier(
max_depth=max_depth,
max_leaf_nodes=max_leaf_nodes
)
tree.fit(data_train, target_train)
# Calculate test scores
test_score = tree.score(data_test, target_test)
mlflow.log_metric("test_accuracy", test_score)
print(f"Result: Accuracy of the DecisionTreeClassifier: {test_score:.1%}")
# Log the model
mlflow.sklearn.log_model(tree, "model", signature=signature)
Started run 1d48d9284dd141018bba82d5cf84ef9f
Load dataset...
Prepare a train-test-split...
Initialize and fit a DecisionTreeClassifier with max_depth=3, max_leaf_nodes4
Result: Accuracy of the DecisionTreeClassifier: 96.5%
Add run to registry#
There is a special storage for models - the model registry, so the following cell adds the model from the previous run to the registry.
model_name = "penguins_clf"
result = mlflow.register_model(
f"runs:/{run_id}/model", model_name
)
Successfully registered model 'penguins_clf'.
2024/06/07 17:27:48 INFO mlflow.store.model_registry.abstract_store: Waiting up to 300 seconds for model version to finish creation. Model name: penguins_clf, version 1
Created version '1' of model 'penguins_clf'.
Run MLFlow side API#
Now you can run a Flask-based API that will render the model by executing a command:
MLFLOW_TRACKING_URI=http://localhost:5000 mlflow models serve --no-conda -m "models:/penguins_clf/1" -p 4242
The following cell starts a new termilan window that will handle the api.
start_api_command = 'MLFLOW_TRACKING_URI=http://localhost:5000 mlflow models serve --no-conda -m "models:/penguins_clf/1" -p 4242'
ans = subprocess.run([
'gnome-terminal', '--', 'bash', '-c', start_api_command
])
After running the api, you can access it. So here is curl asking it - as a result we got a list with classes of penguins:
%%bash
curl http://127.0.0.1:4242/invocations -s -H 'Content-Type: application/json' -d '{
"inputs":[
{"Culmen Length (mm)": 1,"Culmen Depth (mm)": 3},
{"Culmen Length (mm)": 14,"Culmen Depth (mm)": 120},
{"Culmen Length (mm)": 200,"Culmen Depth (mm)": 100}
]}'
{"predictions": ["Adelie", "Adelie", "Chinstrap"]}
Or identical request using python:
import requests
url = "http://127.0.0.1:4242/invocations"
headers = {
'Content-Type': 'application/json',
}
data = {
"inputs": [
{"Culmen Length (mm)": 1, "Culmen Depth (mm)": 3},
{"Culmen Length (mm)": 14, "Culmen Depth (mm)": 120},
{"Culmen Length (mm)": 200, "Culmen Depth (mm)": 100}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.text)
{"predictions": ["Adelie", "Adelie", "Chinstrap"]}
Build docker image#
You can create a docker image that represents an api by using command mlflow models build-docker
. full command will take veiw:
MLFLOW_TRACKING_URI=http://localhost:5000 mlflow models build-docker -m "models:/penguins_clf/1" -n penguins_image --enable-mlserver
Note The --enable-mlserver
option tells the container to use MLServer
as a wrapper. Flask is the default option.
After running the previous command, you’ll have the image penguins_container
in the local docker demon.
!docker images | grep penguins_image
penguins_image latest dddd7cdc5ca2 2 minutes ago 1.25GB
Now let’s try to run this container:
!docker run --rm -p 4243:8080 -d --name penguins_container penguins_image
69c8c7939075e083c5e5e76bc1c669dbb5982332a6679925920685f23bb42500
Note We refer to port 8080 of the container docker because this is the default in mlflow.
%%bash
curl http://127.0.0.1:4243/invocations -s -H 'Content-Type: application/json' -d '{
"inputs":[
{"Culmen Length (mm)": 1,"Culmen Depth (mm)": 3},
{"Culmen Length (mm)": 14,"Culmen Depth (mm)": 120},
{"Culmen Length (mm)": 200,"Culmen Depth (mm)": 100}
]}'
{"predictions": ["Adelie", "Adelie", "Chinstrap"]}
Don’t forget to stop the container with model API:
!docker stop penguins_container
penguins_container