<jupyter_start><jupyter_text>Chassis.ml demo Easily build MLflow models into {KFServing, Modzy} Docker images This demo will show you how we can train a model, define custom pre- and post-processing steps, save it in MLflow format and then build it into a container image and push it to docker hub with a single command.By easily connecting MLflow models to Docker images with a simple Python SDK for data scientists & ML engineers, Chassis is the missing link between MLflow and DevOps. Prerequisites* [Docker Hub](https://hub.docker.com/) account (free one is fine)* The browser you're reading this in :-)<jupyter_code>import chassisml
import sklearn
import mlflow.pyfunc
from joblib import dump, load<jupyter_output><empty_output><jupyter_text>Train the modelThis will train a sklearn model and it will be saved as a joblib file inside the `model` directory.The goal for Chassis service is to create an image that exposes this model.<jupyter_code>from sklearn import datasets, svm
from sklearn.model_selection import train_test_split

digits = datasets.load_digits()
data = digits.images.reshape((len(digits.images), -1))

# Create a classifier: a support vector classifier
clf = svm.SVC(gamma=0.001)

# Split data into 50% train and 50% test subsets
X_train, X_test, y_train, y_test = train_test_split(
    data, digits.target, test_size=0.5, shuffle=False)

# Learn the digits on the train subset
clf.fit(X_train, y_train)
dump(clf, './model.joblib')
# Wrap your model in a pyfunc and provide auxiliary functionality through extension of the
# mlflow PythonModel class with methods pre_process, post_process, and explain

class CustomModel(mlflow.pyfunc.PythonModel):
    _model = load('./model.joblib')
    
    def load_context(self, context):
        self.model = self._model

    def predict(self, context, inputs):
        processed_inputs = self.pre_process(inputs)
        inference_results = self.model.predict(processed_inputs)
        return self.post_process(inference_results)

    def pre_process(self, inputs):
        return inputs / 2

    def post_process(self, inference_results):
        structured_results = []
        for inference_result in inference_results:
            inference_result = {
                "classPredictions": [
                    {"class": str(inference_result), "score": str(1)}
                ]
            }
            structured_output = {
                "data": {
                    "result": inference_result,
                    "explanation": None,
                    "drift": None,
                }
            }
            structured_results.append(structured_output)
        return structured_results

    def explain(self, images):
        pass
# Define conda environment with all required dependencies for your model

conda_env = {
    "channels": ["defaults", "conda-forge", "pytorch"],
    "dependencies": [
        "python=3.8.5",
        "pytorch",
        "torchvision",
        "pip",
        {
            "pip": [
                "mlflow",
                "lime",
                "sklearn"
            ],
        },
    ],
    "name": "linear_env"
}<jupyter_output><empty_output><jupyter_text>Save the modelTransform the model into MLFlow format.<jupyter_code>!rm -rf mlflow_custom_pyfunc_svm
model_save_path = "mlflow_custom_pyfunc_svm"
mlflow.pyfunc.save_model(path=model_save_path, python_model=CustomModel(), conda_env=conda_env)<jupyter_output><empty_output><jupyter_text>Load the MLFlow model and test it.<jupyter_code>import json

classifier = mlflow.pyfunc.load_model(model_save_path)
predictions = classifier.predict(X_test)
print(json.dumps(predictions[0], indent=4))<jupyter_output><empty_output><jupyter_text>We check that the model has been correctly saved inside the `model` directory.<jupyter_code>!ls ./mlflow_custom_pyfunc_svm<jupyter_output><empty_output><jupyter_text>Get Docker Hub credentials securelyNow we prompt the user (you!) for your docker hub username and password in such a way that the value itself doesn't get written into the notebook, which is sensible security best-practice.<jupyter_code>import getpass
import base64
username = getpass.getpass("docker hub username")
password = getpass.getpass("docker hub password")<jupyter_output><empty_output><jupyter_text>Now we can construct the metadata that the chassis service needs to build and publish the container to docker hub:<jupyter_code>image_data = {
    'name': f'{username}/chassisml-sklearn-demo:latest',
    'version': '0.0.1',
    'model_name': 'digits',
    'model_path': './mlflow_custom_pyfunc_svm',
    'registry_auth': base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
}<jupyter_output><empty_output><jupyter_text>Launch the jobImportant fields that we should fill in here are:* `image_data`: the values defined above* `base_url`: the name of the service that runs Chassis (that is running in the same k8s cluster that this notebook is running in)* `deploy`: whether to publish this image to Docker Hub<jupyter_code>res = chassisml.publish(
    image_data=image_data,
    deploy=True,
    base_url='http://chassis:5000'
)

error = res.get('error')
job_id = res.get('job_id')

if error:
    print('Error:', error)
else:
    print('Job ID:', job_id)<jupyter_output><empty_output><jupyter_text>After the request is made, Chassis launches a job that runs Kaniko and builds the docker image based on the values provided.You can get the id of the job created from the result of the request. This id can be used to ask for the status of the job.<jupyter_code>chassisml.get_job_status(job_id)<jupyter_output><empty_output><jupyter_text>**Poll the job a few times until it's finished.** You can also use `kubectl get pods -A` and `kubectl logs` to watch the build in progress in the testfaster SSH tab.Now, we should be able to see the created image listed in the registry. This means that the service has correctly created the image and uploaded it.  Pull the docker imageNow that the job has finished, we can check that the image has been pushed to [Docker Hub](https://hub.docker.com). Log into Docker Hub and check that the tag has been pushed.  (optional) Download the tar fileWe can also download the docker image that has been generated in the form of a tar file.<jupyter_code>dst = './downloaded_image.tar'
chassisml.download_tar(job_id, dst)
!ls -ltrh ./
from IPython.display import display, FileLink

local_file = FileLink(dst, result_html_prefix="Click here to download: ")
display(local_file)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08890251379521766, 'fraction_numerical': 0.005211526670754138, 'mean_word_length': 4.026964560862866, 'pattern_counts': {'":': 11, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '400128', 'license': 'Apache-2.0', 'n_tokens_mistral': 2003, 'n_tokens_neox': 1909, 'n_words': 645, 'path': 'examples/testfaster/svc_demo.ipynb', 'repo_name': 'combinator-ml/terraform-k8s-modelconverter', 'size': '6524'}