{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Copyright (c) Microsoft Corporation. All rights reserved.\n", "\n", "Licensed under the MIT License." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/deployment/deploy-to-local/register-model-deploy-local.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Register model and deploy locally\n", "\n", "This example shows how to deploy a web service in step-by-step fashion:\n", "\n", " 1. Register model\n", " 2. Deploy the image as a web service in a local Docker container.\n", " 3. Quickly test changes to your entry script by reloading the local service.\n", " 4. Optionally, you can also make changes to model, conda or extra_docker_file_steps and update local service" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Prerequisites\n", "If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure you go through the [configuration](../../../configuration.ipynb) Notebook first if you haven't." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Check core SDK version number\n", "import azureml.core\n", "\n", "print(\"SDK version:\", azureml.core.VERSION)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialize Workspace\n", "\n", "Initialize a workspace object from persisted configuration." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from azureml.core import Workspace\n", "\n", "ws = Workspace.from_config()\n", "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep='\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create trained model\n", "\n", "For this example, we will train a small model on scikit-learn's [diabetes dataset](https://scikit-learn.org/stable/datasets/toy_dataset.html#diabetes-dataset). " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import joblib\n", "\n", "from sklearn.datasets import load_diabetes\n", "from sklearn.linear_model import Ridge\n", "\n", "dataset_x, dataset_y = load_diabetes(return_X_y=True)\n", "\n", "sk_model = Ridge().fit(dataset_x, dataset_y)\n", "\n", "joblib.dump(sk_model, \"sklearn_regression_model.pkl\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Register Model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we are registering the serialized file `sklearn_regression_model.pkl` in the current directory as a model with the name `sklearn_regression_model` in the workspace.\n", "\n", "You can add tags and descriptions to your models. Using tags, you can track useful information such as the name and version of the machine learning library used to train the model, framework, category, target customer etc. Note that tags must be alphanumeric." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "register model from file" ] }, "outputs": [], "source": [ "from azureml.core.model import Model\n", "\n", "model = Model.register(model_path=\"sklearn_regression_model.pkl\",\n", " model_name=\"sklearn_regression_model\",\n", " tags={'area': \"diabetes\", 'type': \"regression\"},\n", " description=\"Ridge regression model to predict diabetes\",\n", " workspace=ws)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Environment" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sklearn\n", "\n", "from azureml.core.environment import Environment\n", "\n", "environment = Environment(\"LocalDeploy\")\n", "environment.python.conda_dependencies.add_pip_package(\"inference-schema[numpy-support]\")\n", "environment.python.conda_dependencies.add_pip_package(\"joblib\")\n", "environment.python.conda_dependencies.add_pip_package(\"scikit-learn=={}\".format(sklearn.__version__))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Provide the Scoring Script\n", "\n", "This Python script handles the model execution inside the service container. The `init()` method loads the model file, and `run(data)` is called for every input to the service." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile score.py\n", "import joblib\n", "import json\n", "import numpy as np\n", "import os\n", "\n", "from inference_schema.schema_decorators import input_schema, output_schema\n", "from inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType\n", "\n", "def init():\n", " global model\n", " # AZUREML_MODEL_DIR is an environment variable created during deployment.\n", " # It is the path to the model folder (./azureml-models/$MODEL_NAME/$VERSION)\n", " # For multiple models, it points to the folder containing all deployed models (./azureml-models)\n", " model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_regression_model.pkl')\n", " # Deserialize the model file back into a sklearn model.\n", " model = joblib.load(model_path)\n", "\n", "input_sample = np.array([[10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]])\n", "output_sample = np.array([3726.995])\n", "\n", "@input_schema('data', NumpyParameterType(input_sample))\n", "@output_schema(NumpyParameterType(output_sample))\n", "def run(data):\n", " try:\n", " result = model.predict(data)\n", " # You can return any JSON-serializable object.\n", " return result.tolist()\n", " except Exception as e:\n", " error = str(e)\n", " return error" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Inference Configuration" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from azureml.core.model import InferenceConfig\n", "\n", "inference_config = InferenceConfig(entry_script=\"score.py\",\n", " environment=environment)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deploy Model as a Local Docker Web Service\n", "\n", "*Make sure you have Docker installed and running.*\n", "\n", "Note that the service creation can take few minutes.\n", "\n", "NOTE:\n", "\n", "The Docker image runs as a Linux container. If you are running Docker for Windows, you need to ensure the Linux Engine is running:\n", "\n", " # PowerShell command to switch to Linux engine\n", " & 'C:\\Program Files\\Docker\\Docker\\DockerCli.exe' -SwitchLinuxEngine" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [ "sample-localwebservice-deploy" ] }, "outputs": [], "source": [ "from azureml.core.webservice import LocalWebservice\n", "\n", "# This is optional, if not provided Docker will choose a random unused port.\n", "deployment_config = LocalWebservice.deploy_configuration(port=6789)\n", "\n", "local_service = Model.deploy(ws, \"test\", [model], inference_config, deployment_config)\n", "\n", "local_service.wait_for_deployment()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Local service port: {}'.format(local_service.port))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Check Status and Get Container Logs\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(local_service.get_logs())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test Web Service" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Call the web service with some input data to get a prediction." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "sample_input = json.dumps({\n", " 'data': dataset_x[0:2].tolist()\n", "})\n", "\n", "local_service.run(sample_input)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reload Service\n", "\n", "You can update your score.py file and then call `reload()` to quickly restart the service. This will only reload your execution script and dependency files, it will not rebuild the underlying Docker image. As a result, `reload()` is fast, but if you do need to rebuild the image -- to add a new Conda or pip package, for instance -- you will have to call `update()`, instead (see below)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile score.py\n", "import joblib\n", "import json\n", "import numpy as np\n", "import os\n", "\n", "from inference_schema.schema_decorators import input_schema, output_schema\n", "from inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType\n", "\n", "def init():\n", " global model\n", " # AZUREML_MODEL_DIR is an environment variable created during deployment.\n", " # It is the path to the model folder (./azureml-models/$MODEL_NAME/$VERSION)\n", " # For multiple models, it points to the folder containing all deployed models (./azureml-models)\n", " model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_regression_model.pkl')\n", " # Deserialize the model file back into a sklearn model.\n", " model = joblib.load(model_path)\n", "\n", "input_sample = np.array([[10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]])\n", "output_sample = np.array([3726.995])\n", "\n", "@input_schema('data', NumpyParameterType(input_sample))\n", "@output_schema(NumpyParameterType(output_sample))\n", "def run(data):\n", " try:\n", " result = model.predict(data)\n", " # You can return any JSON-serializable object.\n", " return 'Hello from the updated score.py: ' + str(result.tolist())\n", " except Exception as e:\n", " error = str(e)\n", " return error" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "local_service.reload()\n", "print(\"--------------------------------------------------------------\")\n", "\n", "# After calling reload(), run() will return the updated message.\n", "local_service.run(sample_input)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Update Service\n", "\n", "If you want to change your model(s), Conda dependencies or deployment configuration, call `update()` to rebuild the Docker image.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "local_service.update(models=[model],\n", " inference_config=inference_config,\n", " deployment_config=deployment_config)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deploy model to AKS cluster based on the LocalWebservice's configuration." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This is a one time setup for AKS Cluster. You can reuse this cluster for multiple deployments after it has been created. If you delete the cluster or the resource group that contains it, then you would have to recreate it.\n", "from azureml.core.compute import AksCompute, ComputeTarget\n", "from azureml.core.compute_target import ComputeTargetException\n", "\n", "# Choose a name for your AKS cluster\n", "aks_name = 'my-aks-9' \n", "\n", "# Verify the cluster does not exist already\n", "try:\n", " aks_target = ComputeTarget(workspace=ws, name=aks_name)\n", " print('Found existing cluster, use it.')\n", "except ComputeTargetException:\n", " # Use the default configuration (can also provide parameters to customize)\n", " prov_config = AksCompute.provisioning_configuration()\n", "\n", " # Create the cluster\n", " aks_target = ComputeTarget.create(workspace = ws, \n", " name = aks_name, \n", " provisioning_configuration = prov_config)\n", "\n", "if aks_target.get_status() != \"Succeeded\":\n", " aks_target.wait_for_completion(show_output=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from azureml.core.webservice import AksWebservice\n", "# Set the web service configuration (using default here)\n", "aks_config = AksWebservice.deploy_configuration()\n", "\n", "# # Enable token auth and disable (key) auth on the webservice\n", "# aks_config = AksWebservice.deploy_configuration(token_auth_enabled=True, auth_enabled=False)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "aks_service_name ='aks-service-1'\n", "\n", "aks_service = local_service.deploy_to_cloud(name=aks_service_name,\n", " deployment_config=aks_config,\n", " deployment_target=aks_target)\n", "\n", "aks_service.wait_for_deployment(show_output = True)\n", "print(aks_service.state)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Test aks service\n", "\n", "sample_input = json.dumps({\n", " 'data': dataset_x[0:2].tolist()\n", "})\n", "\n", "aks_service.run(sample_input)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Delete the service if not needed.\n", "aks_service.delete()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Delete Service" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "local_service.delete()" ] } ], "metadata": { "authors": [ { "name": "keriehm" } ], "category": "tutorial", "compute": [ "Local" ], "datasets": [ "None" ], "deployment": [ "Local" ], "exclude_from_index": false, "framework": [ "None" ], "friendly_name": "Register a model and deploy locally", "index_order": 1, "kernelspec": { "display_name": "Python 3.6", "language": "python", "name": "python36" }, "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.8" }, "star_tag": [], "tags": [ "None" ], "task": "Deployment" }, "nbformat": 4, "nbformat_minor": 2 }