diff --git a/examples/models/mlflow_model/Makefile b/examples/models/mlflow_model/Makefile new file mode 100644 index 0000000000..c62e78ec28 --- /dev/null +++ b/examples/models/mlflow_model/Makefile @@ -0,0 +1,7 @@ + + +.PHONY: clean +clean: + rm -rf mlflow + rm -rf mlruns + rm -rf __pycache__ diff --git a/examples/models/mlflow_model/MyMlflowModel.py b/examples/models/mlflow_model/MyMlflowModel.py new file mode 100644 index 0000000000..20b54a1277 --- /dev/null +++ b/examples/models/mlflow_model/MyMlflowModel.py @@ -0,0 +1,17 @@ +from mlflow import pyfunc +import os +import pandas as pd + +class MyMlflowModel(object): + + def __init__(self): + self.pyfunc_model = pyfunc.load_pyfunc("mlruns/0/"+next(os.walk('mlruns/0'))[1][0]+"/artifacts/model") + + def predict(self,X,features_names): + if not features_names is None and len(features_names)>0: + df = pd.DataFrame(data=X,columns=features_names) + else: + df = pd.DataFrame(data=X) + return self.pyfunc_model.predict(df) + + diff --git a/examples/models/mlflow_model/contract.json b/examples/models/mlflow_model/contract.json new file mode 100644 index 0000000000..8f6ba3a4fa --- /dev/null +++ b/examples/models/mlflow_model/contract.json @@ -0,0 +1,80 @@ +{ + "features":[ + { + "name":"alcohol", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,12] + }, + { + "name":"chlorides", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"citric acid", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"density", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"fixed acidity", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"free sulphur dioxide", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"pH", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,7] + }, + { + "name":"residual sugar", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"sulphates", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + }, + { + "name":"total sulphur dioxide", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,100] + }, + { + "name":"volatile acidity", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + } + ], + "targets":[ + { + "name":"class", + "dtype":"FLOAT", + "ftype":"continuous", + "range":[0,1] + } + ] +} + + diff --git a/examples/models/mlflow_model/deployment.json b/examples/models/mlflow_model/deployment.json new file mode 100644 index 0000000000..5f1577594b --- /dev/null +++ b/examples/models/mlflow_model/deployment.json @@ -0,0 +1,46 @@ +{ + "apiVersion": "machinelearning.seldon.io/v1alpha2", + "kind": "SeldonDeployment", + "metadata": { + "labels": { + "app": "seldon" + }, + "name": "mlflow-example" + }, + "spec": { + "name": "mlflow-dep", + "oauth_key": "oauth-key", + "oauth_secret": "oauth-secret", + "predictors": [ + { + "componentSpecs": [{ + "spec": { + "containers": [ + { + "image": "mlflow_model:0.1", + "imagePullPolicy": "IfNotPresent", + "name": "model", + "resources": { + "requests": { + "memory": "1Mi" + } + } + } + ], + "terminationGracePeriodSeconds": 20 + } + }], + "graph": { + "children": [], + "name": "model", + "endpoint": { + "type" : "REST" + }, + "type": "MODEL" + }, + "name": "mlflow-pred", + "replicas": 1 + } + ] + } +} diff --git a/examples/models/mlflow_model/environment_rest b/examples/models/mlflow_model/environment_rest new file mode 100644 index 0000000000..17cb1e71db --- /dev/null +++ b/examples/models/mlflow_model/environment_rest @@ -0,0 +1,4 @@ +MODEL_NAME=MyMlflowModel +API_TYPE=REST +SERVICE_TYPE=MODEL +PERSISTENCE=0 diff --git a/examples/models/mlflow_model/mlflow.ipynb b/examples/models/mlflow_model/mlflow.ipynb new file mode 100644 index 0000000000..c109b52a7e --- /dev/null +++ b/examples/models/mlflow_model/mlflow.ipynb @@ -0,0 +1,228 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run MLFlow Model in Seldon Core\n", + "\n", + "This notebook shows how you can easily train a model using [MLFlow](https://mlflow.org/) and serve requests within Seldon Core on Kubernetes.\n", + "\n", + "Dependencies\n", + "\n", + " * ```pip install seldon-core```\n", + " * ```pip install mlflow```\n", + " \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train Example MlFlow Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!git clone https://github.com/mlflow/mlflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!python mlflow/examples/sklearn_elasticnet_wine/train.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Inference Locally" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pygmentize MyMlflowModel.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!s2i build -E environment_rest . seldonio/seldon-core-s2i-python3:0.5-SNAPSHOT mlflow_model:0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!docker run --name \"mlflow_model\" -d --rm -p 5000:5000 mlflow_model:0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!curl -H \"Content-Type: application/x-www-form-urlencoded\" -g 0.0.0.0:5000/predict -d 'json={\"data\":{\"names\":[\"alcohol\", \"chlorides\", \"citric acid\", \"density\", \"fixed acidity\", \"free sulfur dioxide\", \"pH\", \"residual sugar\", \"sulphates\", \"total sulfur dioxide\", \"volatile acidity\"],\"ndarray\":[[12.8, 0.029, 0.48, 0.98, 6.2, 29, 3.33, 1.2, 0.39, 75, 0.66]]}}'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!curl -H \"Content-Type: application/x-www-form-urlencoded\" -g 0.0.0.0:5000/predict -d 'json={\"data\":{\"ndarray\":[[12.8, 0.029, 0.48, 0.98, 6.2, 29, 3.33, 1.2, 0.39, 75, 0.66]]}}'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!docker rm mlflow_model --force" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Inference on Minikube\n", + "\n", + "**Due to a [minikube/s2i issue](https://github.com/SeldonIO/seldon-core/issues/253) you will need [s2i >= 1.1.13](https://github.com/openshift/source-to-image/releases/tag/v1.1.13)**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!minikube start --memory 4096" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!kubectl create clusterrolebinding kube-system-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!helm init" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!kubectl rollout status deploy/tiller-deploy -n kube-system" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!helm install ../../../helm-charts/seldon-core-crd --name seldon-core-crd --set usage_metrics.enabled=true\n", + "!helm install ../../../helm-charts/seldon-core --name seldon-core " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!eval $(minikube docker-env) && s2i build -E environment_rest . seldonio/seldon-core-s2i-python3:0.5-SNAPSHOT mlflow_model:0.1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!kubectl create -f deployment.json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!kubectl get seldondeployments mlflow-example -o jsonpath='{.status}' " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!seldon-core-api-tester contract.json \\\n", + " `minikube ip` `kubectl get svc -l app=seldon-apiserver-container-app -o jsonpath='{.items[0].spec.ports[0].nodePort}'` \\\n", + " --oauth-key oauth-key --oauth-secret oauth-secret -p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!minikube delete" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/models/mlflow_model/requirements.txt b/examples/models/mlflow_model/requirements.txt new file mode 100644 index 0000000000..b64f82a290 --- /dev/null +++ b/examples/models/mlflow_model/requirements.txt @@ -0,0 +1,3 @@ +mlflow +sklearn +pandas diff --git a/readme.md b/readme.md index 6b929511bf..a4152434ca 100644 --- a/readme.md +++ b/readme.md @@ -80,6 +80,8 @@ Seldon-core allows various types of components to be built and plugged into the * [ResNet ONNX Classifier using Intel nGraph](./examples/models/onnx_resnet50/onnx_resnet50.ipynb) * PMML * [PySpark MNIST Classifier](https://github.com/SeldonIO/JPMML-utils/blob/master/examples/pyspark_pmml/mnist.ipynb) + * MlFlow + * [MlFlow sklearn classififer](./examples/models/mlflow_model/mlflow.ipynb) * **Routers** * [Epsilon-greedy multi-armed bandits for real time optimization of models](components/routers/epsilon-greedy) ([GCP example](https://github.com/SeldonIO/seldon-core/blob/master/notebooks/epsilon_greedy_gcp.ipynb), [Kubeflow example](https://github.com/kubeflow/example-seldon)) @@ -103,6 +105,7 @@ Seldon-core allows various types of components to be built and plugged into the * [Tensorflow Serving](./integrations/tfserving) * [Intel OpenVINO](./examples/models/openvino) * A [Helm chart](./helm-charts/seldon-openvino) for easy integration and an [example notebook](./examples/models/openvino/openvino-squeezenet.ipynb) using OpenVINO to serve imagenet model within Seldon Core. + * [MLFlow](./examples/models/mlflow_model/mlflow.ipynb) ## Install