Last active
March 4, 2022 21:01
-
-
Save ca-scribner/30216b73ab14effb9cb08b8a81bfe5ec to your computer and use it in GitHub Desktop.
An example SeldonDeployment using mlflow to store the model
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"id": "ec61465a-45cf-48da-9009-326865abdb86", | |
"metadata": {}, | |
"source": [ | |
"Modified from [here](https://github.com/Barteus/kubeflow-examples/blob/main/seldon-mlflow-minio/mlflow-demo.ipynb)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "7a87f02f-acb3-4370-9a8f-5a8791afbc5b", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import os\n", | |
"import warnings\n", | |
"import sys\n", | |
"\n", | |
"import pandas as pd\n", | |
"import numpy as np\n", | |
"from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score\n", | |
"from sklearn.model_selection import train_test_split\n", | |
"from sklearn.linear_model import ElasticNet\n", | |
"from urllib.parse import urlparse\n", | |
"import mlflow\n", | |
"import mlflow.sklearn\n", | |
"\n", | |
"import logging\n", | |
"\n", | |
"logging.basicConfig(level=logging.WARN)\n", | |
"logger = logging.getLogger(__name__)\n", | |
"\n", | |
"def eval_metrics(actual, pred):\n", | |
" rmse = np.sqrt(mean_squared_error(actual, pred))\n", | |
" mae = mean_absolute_error(actual, pred)\n", | |
" r2 = r2_score(actual, pred)\n", | |
" return rmse, mae, r2\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "65ced82d-7ec8-4e6d-8917-a6a7cb6dd325", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from IPython.core.magic import register_line_cell_magic\n", | |
"\n", | |
"@register_line_cell_magic\n", | |
"def writetemplate(line, cell):\n", | |
" with open(line, \"w\") as f:\n", | |
" f.write(cell.format(**globals()))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "f605157f-1480-45ee-96b9-1f815f1a73a2", | |
"metadata": {}, | |
"source": [ | |
"# Create a model" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"id": "2d24e043-084f-4186-9e37-d6e11f85d636", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"warnings.filterwarnings(\"ignore\")\n", | |
"np.random.seed(40)\n", | |
"\n", | |
"# Read the wine-quality csv file from the URL\n", | |
"csv_url = (\n", | |
" \"http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv\"\n", | |
")\n", | |
"try:\n", | |
" data = pd.read_csv(csv_url, sep=\";\")\n", | |
"except Exception as e:\n", | |
" logger.exception(\n", | |
" \"Unable to download training & test CSV, check your internet connection. Error: %s\", e\n", | |
" )\n", | |
"\n", | |
"# Split the data into training and test sets. (0.75, 0.25) split.\n", | |
"train, test = train_test_split(data)\n", | |
"\n", | |
"# The predicted column is \"quality\" which is a scalar from [3, 9]\n", | |
"train_x = train.drop([\"quality\"], axis=1)\n", | |
"test_x = test.drop([\"quality\"], axis=1)\n", | |
"train_y = train[[\"quality\"]]\n", | |
"test_y = test[[\"quality\"]]\n", | |
"\n", | |
"alpha = 0.5\n", | |
"l1_ratio = 0.5" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "1955a5d8-c30b-410d-8f9b-c7e8ce00a47d", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Elasticnet model (alpha=0.500000, l1_ratio=0.500000):\n", | |
" RMSE: 0.793164022927685\n", | |
" MAE: 0.6271946374319586\n", | |
" R2: 0.10862644997792636\n" | |
] | |
} | |
], | |
"source": [ | |
"lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)\n", | |
"lr.fit(train_x, train_y)\n", | |
"\n", | |
"predicted_qualities = lr.predict(test_x)\n", | |
"\n", | |
"(rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)\n", | |
"\n", | |
"print(\"Elasticnet model (alpha=%f, l1_ratio=%f):\" % (alpha, l1_ratio))\n", | |
"print(\" RMSE: %s\" % rmse)\n", | |
"print(\" MAE: %s\" % mae)\n", | |
"print(\" R2: %s\" % r2)\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "4b4c0384-1dd5-4536-b2a2-6b5a029feb82", | |
"metadata": {}, | |
"source": [ | |
"# Do an example prediction" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"id": "ccc7c314-e352-42f4-99da-0a5f6bb6331c", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"[[ 0 1 2 3 4 5 6 7 8 9 10]]\n" | |
] | |
} | |
], | |
"source": [ | |
"dummy_data = np.array(range(11))[np.newaxis,:]\n", | |
"print(dummy_data)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"id": "b67cb495-d2e0-4c18-99b8-da44960cf3fc", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([5.73970836])" | |
] | |
}, | |
"execution_count": 6, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"lr.predict(dummy_data)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "40a5fda4-82ca-44b9-bd53-a0598af538ed", | |
"metadata": {}, | |
"source": [ | |
"# Store parameters and model in mlflow" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"id": "129d336b-b800-4bc0-aeef-ecc41aafacb6", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"mlflow_tracking_uri='http://10.152.183.193:5000'\n", | |
"mlflow_s3_endpoint_url='http://10.152.183.66:9000'\n", | |
"aws_access_key_id='minio'\n", | |
"aws_secret_access_key='X9KGX9W9C7GVTKPV4T1MMTP4V9ZT60'\n" | |
] | |
} | |
], | |
"source": [ | |
"mlflow_ip = !kubectl -n kubeflow get svc mlflow-server -o jsonpath='{.spec.clusterIP}'\n", | |
"mlflow_tracking_uri = f\"http://{mlflow_ip[0]}:5000\"\n", | |
"minio_ip = !kubectl -n kubeflow get svc minio -o jsonpath='{.spec.clusterIP}'\n", | |
"mlflow_s3_endpoint_url = f\"http://{minio_ip[0]}:9000\"\n", | |
"aws_access_key_id = \"minio\"\n", | |
"# Could get this from `kubectl get secret mlflow-server-seldon-init-container-s3-credentials -o jsonpath='{.data.RCLONE_CONFIG_S3_SECRET_ACCESS_KEY}' | base64 -d`\n", | |
"aws_secret_access_key = \"X9KGX9W9C7GVTKPV4T1MMTP4V9ZT60\"\n", | |
"\n", | |
"print(f\"{mlflow_tracking_uri=}\")\n", | |
"print(f\"{mlflow_s3_endpoint_url=}\")\n", | |
"print(f\"{aws_access_key_id=}\")\n", | |
"print(f\"{aws_secret_access_key=}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"id": "871f9626-a4bf-4d0d-87f4-db330b166d25", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# From mlflow-server service\n", | |
"os.environ['MLFLOW_TRACKING_URI'] = mlflow_tracking_uri\n", | |
"# from minio service\n", | |
"os.environ[\"MLFLOW_S3_ENDPOINT_URL\"] = mlflow_s3_endpoint_url\n", | |
"os.environ[\"AWS_ACCESS_KEY_ID\"] = aws_access_key_id\n", | |
"os.environ[\"AWS_SECRET_ACCESS_KEY\"] = aws_secret_access_key" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"id": "38309cf2-1cea-4095-966f-2d45777cc405", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Note: doing this directly without specifying a run will implicitly create a run (in the default experiment?)\n", | |
"# and set that run as the mlflow.active_run()\n", | |
"# Could also do this inside a `with mlflow.start_run(run_name='test_run'):` block\n", | |
"mlflow.log_param(\"alpha\", alpha)\n", | |
"mlflow.log_param(\"l1_ratio\", l1_ratio)\n", | |
"mlflow.log_metric(\"rmse\", rmse)\n", | |
"mlflow.log_metric(\"r2\", r2)\n", | |
"mlflow.log_metric(\"mae\", mae)\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"id": "71f66dd3-4568-4068-b44f-d4e4ddab726e", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Bucket mlflow already exists - using existing bucket\n" | |
] | |
} | |
], | |
"source": [ | |
"# Patch to create bucket in minio if it doesn't exist already\n", | |
"import boto3\n", | |
"import botocore\n", | |
"import os\n", | |
"\n", | |
"def create_default_bucket(default_bucket):\n", | |
" \"\"\"Checks whether default_bucket exists and is accessible, creating it if it does not\"\"\"\n", | |
" c = boto3.client(\n", | |
" 's3',\n", | |
" endpoint_url=os.environ[\"MLFLOW_S3_ENDPOINT_URL\"],\n", | |
" aws_access_key_id=os.environ[\"AWS_ACCESS_KEY_ID\"],\n", | |
" aws_secret_access_key=os.environ[\"AWS_SECRET_ACCESS_KEY\"],\n", | |
" )\n", | |
" try:\n", | |
" # If this succeeded, bucket exists already and we have access to it\n", | |
" c.head_bucket(Bucket=default_bucket)\n", | |
" print(f\"Bucket {default_bucket} already exists - using existing bucket\")\n", | |
" except botocore.exceptions.ClientError:\n", | |
" # Otherwise bucket is missing or do not have access. Try to create it\n", | |
" try:\n", | |
" print(f\"Creating bucket {default_bucket}\")\n", | |
" c.create_bucket(Bucket=default_bucket)\n", | |
" except botocore.exceptions.ClientError as e:\n", | |
" # Cannot create bucket. Block charm\n", | |
" message = \"Could not access/create the default artifact storage bucket.\" \\\n", | |
" f\" Got error: {str(e)}\"\n", | |
" raise ValueError(message)\n", | |
"\n", | |
"create_default_bucket(\"mlflow\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"id": "76f9722f-1cf9-4da4-9e05-be72b70b6eb8", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"Registered model 'ElasticnetWineModel' already exists. Creating a new version of this model...\n", | |
"2022/03/04 15:58:21 INFO mlflow.tracking._model_registry.client: Waiting up to 300 seconds for model version to finish creation. Model name: ElasticnetWineModel, version 2\n", | |
"Created version '2' of model 'ElasticnetWineModel'.\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"ModelInfo(artifact_path='model', flavors={'python_function': {'model_path': 'model.pkl', 'loader_module': 'mlflow.sklearn', 'python_version': '3.9.7', 'env': 'conda.yaml'}, 'sklearn': {'pickled_model': 'model.pkl', 'sklearn_version': '1.0.2', 'serialization_format': 'cloudpickle'}}, model_uri='runs:/7ea073ed2bc643e7bb5f593e53269d4d/model', model_uuid='5b89ca5b8b554f7c8b0fdd07e3c80d90', run_id='7ea073ed2bc643e7bb5f593e53269d4d', saved_input_example_info=None, signature_dict=None, utc_time_created='2022-03-04 20:58:19.053647')" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"mlflow.sklearn.log_model(lr, \"model\", registered_model_name=\"ElasticnetWineModel\", )" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "b1bfb394-2d87-4ff7-abbc-dbcabf44568c", | |
"metadata": {}, | |
"source": [ | |
"# If needed, create the secrets (if you're using an old mlflow charm, this wont be created for you)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"id": "023bf506-4e93-4e22-af34-5cc82087ab66", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"namespace_for_seldondeployment = \"admin\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 55, | |
"id": "7fd84793-fefc-49b8-a53a-389683cfa9d1", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from base64 import b64encode\n", | |
"\n", | |
"def _b64_encode_dict(d):\n", | |
" \"\"\"Returns the dict with values being base64 encoded.\"\"\"\n", | |
" # Why do we encode and decode in utf-8 first?\n", | |
" return {k: b64encode(v.encode(\"utf-8\")).decode(\"utf-8\") for k, v in d.items()}\n", | |
"\n", | |
"def _seldon_credentials_dict(obj_storage):\n", | |
" \"\"\"Returns a dict of seldon init-container object storage credentials, base64 encoded.\"\"\"\n", | |
" credentials = {\n", | |
" \"RCLONE_CONFIG_S3_TYPE\": \"s3\",\n", | |
" \"RCLONE_CONFIG_S3_PROVIDER\": \"minio\",\n", | |
" \"RCLONE_CONFIG_S3_ACCESS_KEY_ID\": obj_storage[\"access-key\"],\n", | |
" \"RCLONE_CONFIG_S3_SECRET_ACCESS_KEY\": obj_storage[\"secret-key\"],\n", | |
" \"RCLONE_CONFIG_S3_ENDPOINT\": f\"http://{obj_storage['service']}.{obj_storage['namespace']}:{obj_storage['port']}\",\n", | |
" \"RCLONE_CONFIG_S3_ENV_AUTH\": \"false\",\n", | |
" }\n", | |
" return _b64_encode_dict(credentials)\n", | |
"\n", | |
"obj_storage = {\n", | |
" \"access-key\": \"minio\",\n", | |
" \"secret-key\": \"X9KGX9W9C7GVTKPV4T1MMTP4V9ZT60\",\n", | |
" \"service\": \"minio\",\n", | |
" \"namespace\": \"kubeflow\",\n", | |
" \"port\": \"9000\",\n", | |
"}\n", | |
"\n", | |
"secret_data = _seldon_credentials_dict(obj_storage)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 56, | |
"id": "a8f36ff3-4801-43c2-a8c9-68d9fa4bbe50", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"{'RCLONE_CONFIG_S3_TYPE': 'czM=',\n", | |
" 'RCLONE_CONFIG_S3_PROVIDER': 'bWluaW8=',\n", | |
" 'RCLONE_CONFIG_S3_ACCESS_KEY_ID': 'bWluaW8=',\n", | |
" 'RCLONE_CONFIG_S3_SECRET_ACCESS_KEY': 'WDlLR1g5VzlDN0dWVEtQVjRUMU1NVFA0VjlaVDYw',\n", | |
" 'RCLONE_CONFIG_S3_ENDPOINT': 'aHR0cDovL21pbmlvLmt1YmVmbG93OjkwMDA=',\n", | |
" 'RCLONE_CONFIG_S3_ENV_AUTH': 'ZmFsc2U='}" | |
] | |
}, | |
"execution_count": 56, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"secret_data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 57, | |
"id": "12f38d55-eafa-409f-86bd-9c51ef0533c8", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"' RCLONE_CONFIG_S3_TYPE: czM=\\n RCLONE_CONFIG_S3_PROVIDER: bWluaW8=\\n RCLONE_CONFIG_S3_ACCESS_KEY_ID: bWluaW8=\\n RCLONE_CONFIG_S3_SECRET_ACCESS_KEY: WDlLR1g5VzlDN0dWVEtQVjRUMU1NVFA0VjlaVDYw\\n RCLONE_CONFIG_S3_ENDPOINT: aHR0cDovL21pbmlvLmt1YmVmbG93OjkwMDA=\\n RCLONE_CONFIG_S3_ENV_AUTH: ZmFsc2U='" | |
] | |
}, | |
"execution_count": 57, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"serialized_secret_data = \"\\n\".join((f\" {k}: {v}\" for k, v in secret_data.items()))\n", | |
"serialized_secret_data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 58, | |
"id": "0192e7e5-1bf6-4c3d-8b77-64fdd8f76894", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%%writetemplate seldon_secret.yaml\n", | |
"apiVersion: v1\n", | |
"kind: Secret\n", | |
"metadata:\n", | |
" name: mlflow-server-seldon-init-container-s3-credentials\n", | |
"type: bootstrap.kubernetes.io/token\n", | |
"data:\n", | |
"{serialized_secret_data}" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 59, | |
"id": "c7d59bf6-0b6e-46c8-b88b-8ee3a5d32ad2", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"secret/mlflow-server-seldon-init-container-s3-credentials configured\n" | |
] | |
} | |
], | |
"source": [ | |
"!kubectl apply -f seldon_secret.yaml -n $namespace_for_seldondeployment" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "2dc185ae-3b10-462a-84e7-feeac70d614c", | |
"metadata": { | |
"tags": [] | |
}, | |
"source": [ | |
"# Deploy the model using Seldon" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"id": "80f9a2d4-fccb-4023-8a8a-a044b07574e5", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"artifact_uri='s3://mlflow/0/7ea073ed2bc643e7bb5f593e53269d4d/artifacts'\n" | |
] | |
} | |
], | |
"source": [ | |
"run = mlflow.active_run()\n", | |
"artifact_uri = run.info.artifact_uri\n", | |
"print(f\"{artifact_uri=}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"id": "f5f5c5fb-cfaa-4395-adb0-b6a165c7f58b", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"%%writetemplate sd_example.yaml\n", | |
"\n", | |
"apiVersion: machinelearning.seldon.io/v1alpha2\n", | |
"kind: SeldonDeployment\n", | |
"metadata:\n", | |
" name: mlflow\n", | |
"spec:\n", | |
" name: wines\n", | |
" predictors:\n", | |
" - componentSpecs:\n", | |
" - spec:\n", | |
" # We are setting high failureThreshold as installing conda dependencies\n", | |
" # can take long time and we want to avoid k8s killing the container prematurely\n", | |
" containers:\n", | |
" - name: classifier\n", | |
" image: seldonio/mlflowserver:1.14.0-dev # Patches a package conflict in the default version\n", | |
" livenessProbe:\n", | |
" initialDelaySeconds: 80\n", | |
" failureThreshold: 200\n", | |
" periodSeconds: 5\n", | |
" successThreshold: 1\n", | |
" httpGet:\n", | |
" path: /health/ping\n", | |
" port: http\n", | |
" scheme: HTTP\n", | |
" readinessProbe:\n", | |
" initialDelaySeconds: 80\n", | |
" failureThreshold: 200\n", | |
" periodSeconds: 5\n", | |
" successThreshold: 1\n", | |
" httpGet:\n", | |
" path: /health/ping\n", | |
" port: http\n", | |
" scheme: HTTP\n", | |
" graph:\n", | |
" children: []\n", | |
" implementation: MLFLOW_SERVER\n", | |
" # mlflow/experimentid/runid/artifacts/model\n", | |
" modelUri: {artifact_uri}/model\n", | |
" envSecretRefName: mlflow-server-seldon-init-container-s3-credentials\n", | |
" name: classifier\n", | |
" name: default\n", | |
" replicas: 1\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"id": "14ac443c-45c3-4807-9179-9956a7842ea3", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"seldondeployment.machinelearning.seldon.io/mlflow created\n" | |
] | |
} | |
], | |
"source": [ | |
"!kubectl create -f sd_example.yaml" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"id": "8fabd68e-c2d1-4e1a-853f-a3817fa8b62e", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Waiting for deployment \"mlflow-default-0-classifier\" rollout to finish: 0 of 1 updated replicas are available...\n", | |
"deployment \"mlflow-default-0-classifier\" successfully rolled out\n" | |
] | |
} | |
], | |
"source": [ | |
"jsonpath=\"'{.items[0].metadata.name}'\"\n", | |
"deployment_name = !kubectl get deploy -l seldon-deployment-id=mlflow -o jsonpath=$jsonpath\n", | |
"deployment_name = deployment_name[0]\n", | |
"!kubectl rollout status deploy/$deployment_name\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "df3df1a7-0330-481b-8cd7-651c293648c9", | |
"metadata": {}, | |
"source": [ | |
"# Predict with the deployment" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "a557a7bc-4a73-4455-a6cd-70033ea3277a", | |
"metadata": {}, | |
"source": [ | |
"### Create some dummy data" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"id": "a44bd73e-ac5c-48db-a975-6dc17a009cce", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"data_escaped='\\\\{\\\\\"data\\\\\":\\\\ \\\\{\\\\\"ndarray\\\\\":\\\\ \\\\[\\\\[0,\\\\ 1,\\\\ 2,\\\\ 3,\\\\ 4,\\\\ 5,\\\\ 6,\\\\ 7,\\\\ 8,\\\\ 9,\\\\ 10\\\\]\\\\]\\\\}\\\\}'\n" | |
] | |
} | |
], | |
"source": [ | |
"import json\n", | |
"# data_json = json.dumps(dummy_data.tolist())\n", | |
"data_json = json.dumps({\"data\": {\"ndarray\": dummy_data.tolist()}})\n", | |
"\n", | |
"# There must be a better way, but I couldn't figure out a better way to get data into the curl statement with proper escaping\n", | |
"import re\n", | |
"data_escaped = re.escape(data_json)\n", | |
"data_escaped = data_escaped.replace('\"', '\\\\\"')\n", | |
"print(f\"{data_escaped=}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"id": "618a074c-7d45-4a02-9c1b-31ffd209d71f", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"seldondeployment_predict_endpoint='http://10.152.183.135:9000/predict'\n" | |
] | |
} | |
], | |
"source": [ | |
"# # SeldonDeployment Service name\n", | |
"# !kubectl get svc -l seldon-deployment-id=mlflow,seldon.io/model=true -o jsonpath='{.items[0].metadata.name}'\n", | |
"\n", | |
"# SeldonDeployment Service IP\n", | |
"seldondeployment_predict_ip = !kubectl get svc -l seldon-deployment-id=mlflow,seldon.io/model=true -o jsonpath='{.items[0].spec.clusterIP}'\n", | |
"seldondeployment_predict_endpoint = f\"http://{seldondeployment_predict_ip[0]}:9000/predict\"\n", | |
"print(f\"{seldondeployment_predict_endpoint=}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"id": "ae931bfa-a25e-46af-b9ad-0cbee699d499", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"{\"data\":{\"names\":[],\"ndarray\":[5.739708362528547]},\"meta\":{\"requestPath\":{\"classifier\":\"seldonio/mlflowserver:1.14.0-dev\"}}}\n" | |
] | |
} | |
], | |
"source": [ | |
"!curl $seldondeployment_predict_endpoint -X POST -H 'Content-Type: application/json' -d $data_escaped" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "dff40e90-44cf-422e-b33c-bcedd0823443", | |
"metadata": {}, | |
"source": [ | |
"Where we see the ndarray returned is the same as our original prediction above" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "11100d52-eec6-46a9-8f0f-c627c6e55617", | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "mlflow-secrets-venv2", | |
"language": "python", | |
"name": "mlflow-secrets-venv2" | |
}, | |
"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.9.7" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment