<jupyter_start><jupyter_text>Outlier Example![architecture](architecture.png)In this example we will:  * [Describe the project structure](Project-Structure)  * [Train some models](Train-Models)  * [Create Tempo artifacts](Create-Tempo-Artifacts)  * [Run unit tests](Unit-Tests)  * [Save python environment for our classifier](Save-Classifier-Environment)  * [Test Locally on Docker](Test-Locally-on-Docker)  * [Production on Kubernetes via Tempo](Production-Option-1-(Deploy-to-Kubernetes-with-Tempo))  * [Prodiuction on Kuebrnetes via GitOps](Production-Option-2-(Gitops))  PrerequisitesThis notebooks needs to be run in the `tempo-examples` conda environment defined below. Create from project root folder:```bashconda env create --name tempo-examples --file conda/tempo-examples.yaml```  Project Structure<jupyter_code>!tree -P "*.py"  -I "__init__.py|__pycache__" -L 2<jupyter_output><empty_output><jupyter_text>Train Models * This section is where as a data scientist you do your work of training models and creating artfacts. * For this example we train sklearn and xgboost classification models for the iris dataset.<jupyter_code>import os
import logging
import numpy as np
import tempo

from tempo.utils import logger
from src.constants import ARTIFACTS_FOLDER

logger.setLevel(logging.ERROR)
logging.basicConfig(level=logging.ERROR)
from src.data import Cifar10
data = Cifar10()<jupyter_output><empty_output><jupyter_text>Download pretrained Resnet32 Tensorflow model for CIFAR10<jupyter_code>!rclone --config ./rclone-gcs.conf copy gs://seldon-models/tfserving/cifar10/resnet32 ./artifacts/model<jupyter_output><empty_output><jupyter_text>Download or train an outlier detector on CIFAR10 data<jupyter_code>load_pretrained = True
if load_pretrained:  # load pre-trained detector
    !rclone --config ./rclone-gcs.conf copy gs://seldon-models/tempo/cifar10/outlier/cifar10 ./artifacts/outlier/cifar10
else:
    from src.outlier import train_outlier_detector
    train_outlier_detector(data, ARTIFACTS_FOLDER)<jupyter_output><empty_output><jupyter_text>Create Tempo Artifacts<jupyter_code>from src.tempo import create_outlier_cls, create_model, create_svc_cls

cifar10_model = create_model()
OutlierModel = create_outlier_cls()
outlier = OutlierModel()
Cifar10Svc = create_svc_cls(outlier, cifar10_model)
svc = Cifar10Svc()
# %load src/tempo.py
import json
import os

import numpy as np
from alibi_detect.base import NumpyEncoder
from src.constants import ARTIFACTS_FOLDER, MODEL_FOLDER, OUTLIER_FOLDER

from tempo.kfserving.protocol import KFServingV1Protocol, KFServingV2Protocol
from tempo.serve.metadata import ModelFramework
from tempo.serve.model import Model
from tempo.serve.pipeline import PipelineModels
from tempo.serve.utils import model, pipeline, predictmethod


def create_outlier_cls():
    @model(
        name="outlier",
        platform=ModelFramework.Custom,
        protocol=KFServingV2Protocol(),
        uri="s3://tempo/outlier/cifar10/outlier",
        local_folder=os.path.join(ARTIFACTS_FOLDER, OUTLIER_FOLDER),
    )
    class OutlierModel(object):
        def __init__(self):
            from alibi_detect.utils.saving import load_detector

            model = self.get_tempo()
            models_folder = model.details.local_folder
            print(f"Loading from {models_folder}")
            self.od = load_detector(os.path.join(models_folder, "cifar10"))

        @predictmethod
        def outlier(self, payload: np.ndarray) -> dict:
            od_preds = self.od.predict(
                payload,
                outlier_type="instance",  # use 'feature' or 'instance' level
                return_feature_score=True,
                # scores used to determine outliers
                return_instance_score=True,
            )

            return json.loads(json.dumps(od_preds, cls=NumpyEncoder))

    return OutlierModel


def create_model():

    cifar10_model = Model(
        name="resnet32",
        protocol=KFServingV1Protocol(),
        platform=ModelFramework.Tensorflow,
        uri="gs://seldon-models/tfserving/cifar10/resnet32",
        local_folder=os.path.join(ARTIFACTS_FOLDER, MODEL_FOLDER),
    )

    return cifar10_model


def create_svc_cls(outlier, model):
    @pipeline(
        name="cifar10-service",
        protocol=KFServingV2Protocol(),
        uri="s3://tempo/outlier/cifar10/svc",
        local_folder=os.path.join(ARTIFACTS_FOLDER, "svc"),
        models=PipelineModels(outlier=outlier, cifar10=model),
    )
    class Cifar10Svc(object):
        @predictmethod
        def predict(self, payload: np.ndarray) -> np.ndarray:
            r = self.models.outlier(payload=payload)
            if r["data"]["is_outlier"][0]:
                return np.array([])
            else:
                return self.models.cifar10(payload)

    return Cifar10Svc<jupyter_output><empty_output><jupyter_text>Unit Tests * Here we run our unit tests to ensure the orchestration works before running on the actual models.<jupyter_code># %load tests/test_tempo.py
import numpy as np
from src.tempo import create_model, create_outlier_cls, create_svc_cls


def test_svc_outlier():
    model = create_model()
    OutlierModel = create_outlier_cls()
    outlier = OutlierModel()
    Cifar10Svc = create_svc_cls(outlier, model)
    svc = Cifar10Svc()
    svc.models.outlier = lambda payload: {"data": {"is_outlier": [1]}}
    svc.models.cifar10 = lambda input: np.array([[0.2]])
    res = svc(np.array([1]))
    assert res.shape[0] == 0


def test_svc_inlier():
    model = create_model()
    OutlierModel = create_outlier_cls()
    outlier = OutlierModel()
    Cifar10Svc = create_svc_cls(outlier, model)
    svc = Cifar10Svc()
    svc.models.outlier = lambda payload: {"data": {"is_outlier": [0]}}
    svc.models.cifar10 = lambda input: np.array([[0.2]])
    res = svc(np.array([1]))
    assert res.shape[0] == 1

!python -m pytest tests/<jupyter_output><empty_output><jupyter_text>Save Outlier and Svc Environments<jupyter_code>!cat artifacts/outlier/conda.yaml
!cat artifacts/svc/conda.yaml
tempo.save(OutlierModel)
tempo.save(Cifar10Svc)<jupyter_output><empty_output><jupyter_text>Test Locally on DockerHere we test our models using production images but running locally on Docker. This allows us to ensure the final production deployed model will behave as expected when deployed.<jupyter_code>from tempo import deploy
remote_model = deploy(svc)
from src.utils import show_image
show_image(data.X_test[0:1])
remote_model.predict(payload=data.X_test[0:1])
from src.utils import create_cifar10_outlier

outlier_img = create_cifar10_outlier(data)
show_image(outlier_img)
remote_model.predict(payload=outlier_img)
remote_model.undeploy()<jupyter_output><empty_output><jupyter_text>Production Option 1 (Deploy to Kubernetes with Tempo) * Here we illustrate how to run the final models in "production" on Kubernetes by using Tempo to deploy  Prerequisites Create a Kind Kubernetes cluster with Minio and Seldon Core installed using Ansible as described [here](https://tempo.readthedocs.io/en/latest/overview/quickstart.htmlkubernetes-cluster-with-seldon-core).<jupyter_code>!kubectl apply -f k8s/rbac -n production
from tempo.examples.minio import create_minio_rclone
import os

create_minio_rclone(os.getcwd()+"/rclone-minio.conf")
tempo.upload(cifar10_model)
tempo.upload(outlier)
tempo.upload(svc)
from tempo.serve.metadata import KubernetesOptions
from tempo.seldon.k8s import SeldonCoreOptions
runtime_options = SeldonCoreOptions(
        k8s_options=KubernetesOptions(
            namespace="production",
            authSecretName="minio-secret"
        )
    )
from tempo import deploy
remote_model = deploy(svc, options=runtime_options)
from src.utils import show_image

show_image(data.X_test[0:1])
remote_model.predict(payload=data.X_test[0:1])
from src.utils import create_cifar10_outlier

outlier_img = create_cifar10_outlier(data)
show_image(outlier_img)
remote_model.predict(payload=outlier_img)
remote_model.undeploy()<jupyter_output><empty_output><jupyter_text>Production Option 2 (Gitops) * We create yaml to provide to our DevOps team to deploy to a production cluster * We add Kustomize patches to modify the base Kubernetes yaml created by Tempo<jupyter_code>from tempo.seldon import SeldonKubernetesRuntime

k8s_runtime = SeldonKubernetesRuntime(runtime_options)
yaml_str = k8s_runtime.manifest(svc)

with open(os.getcwd()+"/k8s/tempo.yaml","w") as f:
    f.write(yaml_str)
!kustomize build k8s<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09636427815037063, 'fraction_numerical': 0.013883986351335451, 'mean_word_length': 4.57742782152231, 'pattern_counts': {'":': 4, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '298662', 'license': 'Apache-2.0', 'n_tokens_mistral': 2966, 'n_tokens_neox': 2762, 'n_words': 612, 'path': 'docs/examples/outlier/README.ipynb', 'repo_name': 'SachinVarghese/tempo', 'size': '8499'}