{ "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": [ "# Tutorial #2: Deploy an image classification model in Azure Container Instance (ACI)\n", "\n", "This tutorial is **part two of a two-part tutorial series**. In the [previous tutorial](01.train-models.ipynb), you trained machine learning models and then registered the best one in your workspace on the cloud. \n", "\n", "Now, you're ready to deploy the model as a web service in [Azure Container Instances](https://docs.microsoft.com/azure/container-instances/) (ACI). A web service is an image, in this case a Docker image, that encapsulates the scoring logic and the model itself. \n", "\n", "In this part of the tutorial, you use Azure Machine Learning service (Preview) to:\n", "\n", "> * Set up your testing environment\n", "> * Retrieve the model from your workspace\n", "> * Test the model locally\n", "> * Deploy the model to ACI\n", "> * Test the deployed model\n", "\n", "ACI is not ideal for production deployments, but it is great for testing and understanding the workflow. For scalable production deployments, consider using AKS.\n", "\n", "\n", "## Prerequisites\n", "\n", "Complete the model training in the [Tutorial #1: Train an image classification model with Azure Machine Learning](01.train-models.ipynb) notebook. \n", "\n", "\n", "## Set up the environment\n", "\n", "Start by setting up a testing environment.\n", "\n", "### Import packages\n", "\n", "Import the Python packages needed for this tutorial." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "import numpy as np\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", " \n", "import azureml\n", "from azureml.core import Workspace, Run\n", "\n", "# display the core SDK version number\n", "print(\"Azure ML SDK Version: \", azureml.core.VERSION)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Retrieve the model\n", "\n", "You registered a model in your workspace in the previous tutorial. Now, load this workspace and download the model to your local directory." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from azureml.core import Workspace\n", "from azureml.core.model import Model\n", "\n", "ws = Workspace.from_config()\n", "model=Model(ws, 'sklearn_mnist')\n", "model.download(target_dir = '.')\n", "import os \n", "# verify the downloaded model file\n", "os.stat('./sklearn_mnist_model.pkl')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test model locally\n", "\n", "Before deploying, make sure your model is working locally by:\n", "* Loading test data\n", "* Predicting test data\n", "* Examining the confusion matrix\n", "\n", "### Load test data\n", "\n", "Load the test data from the **./data/** directory created during the training tutorial." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from utils import load_data\n", "\n", "# note we also shrink the intensity values (X) from 0-255 to 0-1. This helps the neural network converge faster\n", "\n", "X_test = load_data('./data/test-images.gz', False) / 255.0\n", "y_test = load_data('./data/test-labels.gz', True).reshape(-1)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Predict test data\n", "\n", "Feed the test dataset to the model to get predictions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pickle\n", "from sklearn.externals import joblib\n", "\n", "clf = joblib.load('./sklearn_mnist_model.pkl')\n", "y_hat = clf.predict(X_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Examine the confusion matrix\n", "\n", "Generate a confusion matrix to see how many samples from the test set are classified correctly. Notice the mis-classified value for the incorrect predictions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import confusion_matrix\n", "\n", "conf_mx = confusion_matrix(y_test, y_hat)\n", "print(conf_mx)\n", "print('Overall accuracy:', np.average(y_hat == y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use `matplotlib` to display the confusion matrix as a graph. In this graph, the X axis represents the actual values, and the Y axis represents the predicted values. The color in each grid represents the error rate. The lighter the color, the higher the error rate is. For example, many 5's are mis-classified as 3's. Hence you see a bright grid at (5,3)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row_sums = conf_mx.sum(axis = 1, keepdims = True)\n", "norm_conf_mx = conf_mx / row_sums\n", "np.fill_diagonal(norm_conf_mx, 0)\n", "\n", "fig = plt.figure(figsize = (8,5))\n", "ax = fig.add_subplot(111)\n", "cax = ax.matshow(norm_conf_mx, cmap = plt.cm.bone)\n", "ticks = np.arange(0, 10, 1)\n", "ax.set_xticks(ticks)\n", "ax.set_yticks(ticks)\n", "ax.set_xticklabels(ticks)\n", "ax.set_yticklabels(ticks)\n", "fig.colorbar(cax)\n", "plt.ylabel('true labels', fontsize=14)\n", "plt.xlabel('predicted values', fontsize=14)\n", "plt.savefig('conf.png')\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deploy as web service\n", "\n", "Once you've tested the model and are satisfied with the results, deploy the model as a web service hosted in ACI. \n", "\n", "To build the correct environment for ACI, provide the following:\n", "* A scoring script to show how to use the model\n", "* An environment file to show what packages need to be installed\n", "* A configuration file to build the ACI\n", "* The model you trained before\n", "\n", "### Create scoring script\n", "\n", "Create the scoring script, called score.py, used by the web service call to show how to use the model.\n", "\n", "You must include two required functions into the scoring script:\n", "* The `init()` function, which typically loads the model into a global object. This function is executed only once when the Docker container is started. \n", "\n", "* The `run(input_data)` function uses the model to predict a value based on the input data. Inputs and outputs to the run typically use JSON for serialization and de-serialization, but other formats are supported." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile score.py\n", "import json\n", "import numpy as np\n", "import os\n", "import pickle\n", "from sklearn.externals import joblib\n", "from sklearn.linear_model import LogisticRegression\n", "\n", "#from azureml.assets.persistence.persistence import get_model_path\n", "from azureml.core.model import Model\n", "\n", "def init():\n", " global model\n", " # retreive the local path to the model using the model name\n", " model_path = Model.get_model_path('sklearn_mnist')\n", " model = joblib.load(model_path)\n", "\n", "def run(raw_data):\n", " data = np.array(json.loads(raw_data)['data'])\n", " # make prediction\n", " y_hat = model.predict(data)\n", " return json.dumps(y_hat.tolist())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create environment file\n", "\n", "Next, create an environment file, called myenv.yml, that specifies all of the script's package dependencies. This file is used to ensure that all of those dependencies are installed in the Docker image. This model needs `scikit-learn` and `azureml-sdk`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile myenv.yml\n", "name: myenv\n", "channels:\n", " - defaults\n", "dependencies:\n", " - scikit-learn\n", " - pip:\n", " # Required packages for AzureML execution, history, and data preparation.\n", " - --extra-index-url https://azuremlsdktestpypi.azureedge.net/sdk-release/Preview/E7501C02541B433786111FE8E140CAA1\n", " - azureml-core" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create configuration file\n", "\n", "Create a deployment configuration file and specify the number of CPUs and gigabyte of RAM needed for your ACI container. While it depends on your model, the default of 1 core and 1 gigabyte of RAM is usually sufficient for many models. If you feel you need more later, you would have to recreate the image and redeploy the service." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from azureml.core.webservice import AciWebservice\n", "\n", "aciconfig = AciWebservice.deploy_configuration(cpu_cores = 1, \n", " memory_gb = 1, \n", " tags = {\"data\": \"MNIST\", \"method\" : \"sklearn\"}, \n", " description = 'Predict MNIST with sklearn')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Deploy in ACI\n", "Estimated time to complete: **about 7-8 minutes**\n", "\n", "Configure the image and deploy. The following code goes through these steps:\n", "\n", "1. Build an image using:\n", " * The scoring file (`score.py`)\n", " * The environment file (`myenv.yml`)\n", " * The model file\n", "1. Register that image under the workspace. \n", "1. Send the image to the ACI container.\n", "1. Start up a container in ACI using the image.\n", "1. Get the web service HTTP endpoint." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%time\n", "from azureml.core.webservice import Webservice\n", "from azureml.core.image import ContainerImage\n", "\n", "# configure the image\n", "image_config = ContainerImage.image_configuration(execution_script = \"score.py\", \n", " runtime = \"python\", \n", " conda_file = \"myenv.yml\")\n", "\n", "service = Webservice.deploy_from_model(workspace = ws,\n", " name = 'sklearn-mnist-model',\n", " deployment_config = aciconfig,\n", " models = [model],\n", " image_config = image_config)\n", "\n", "service.wait_for_deployment(show_output = True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get the scoring web service's HTTP endpoint, which accepts REST client calls. This endpoint can be shared with anyone who wants to test the web service or integrate it into an application." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(service.scoring_uri)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test deployed service\n", "\n", "Earlier you scored all the test data with the local version of the model. Now, you can test the deployed model with a random sample of 30 images from the test data. \n", "\n", "The following code goes through these steps:\n", "1. Send the data as a JSON array to the web service hosted in ACI. \n", "\n", "1. Use the SDK's `run` API to invoke the service. You can also make raw calls using any HTTP tool such as curl.\n", "\n", "1. Print the returned predictions and plot them along with the input images. Red font and inverse image (white on black) is used to highlight the misclassified samples. \n", "\n", " Since the model accuracy is high, you might have to run the following code a few times before you can see a misclassified sample." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "# find 30 random samples from test set\n", "n = 30\n", "sample_indices = np.random.permutation(X_test.shape[0])[0:n]\n", "\n", "test_samples = json.dumps({\"data\": X_test[sample_indices].tolist()})\n", "test_samples = bytes(test_samples, encoding = 'utf8')\n", "\n", "# predict using the deployed model\n", "result = json.loads(service.run(input_data = test_samples))\n", "\n", "# compare actual value vs. the predicted values:\n", "i = 0\n", "plt.figure(figsize = (20, 1))\n", "\n", "for s in sample_indices:\n", " plt.subplot(1, n, i + 1)\n", " plt.axhline('')\n", " plt.axvline('')\n", " \n", " # use different color for misclassified sample\n", " font_color = 'red' if y_test[s] != result[i] else 'black'\n", " clr_map = plt.cm.gray if y_test[s] != result[i] else plt.cm.Greys\n", " \n", " plt.text(x = 10, y = -10, s = result[i], fontsize = 18, color = font_color)\n", " plt.imshow(X_test[s].reshape(28, 28), cmap = clr_map)\n", " \n", " i = i + 1\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Clean up resources\n", "\n", "To keep the resource group and workspace for other tutorials and exploration, you can delete only the ACI deployment using this API call:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "service.delete()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "If you're not going to use what you've created here, delete the resources you just created with this quickstart so you don't incur any charges. In the Azure portal, select and delete your resource group. You can also keep the resource group, but delete a single workspace by displaying the workspace properties and selecting the Delete button.\n", "\n", "\n", "## Next steps\n", "\n", "In this Azure Machine Learning tutorial, you used Python to:\n", "\n", "> * Set up your testing environment\n", "> * Retrieve the model from your workspace\n", "> * Test the model locally\n", "> * Deploy the model to ACI\n", "> * Test the deployed model\n", " \n", "You can also try out the [Automatic algorithm selection tutorial](03.auto-train-models.ipynb) to see how Azure Machine Learning can auto-select and tune the best algorithm for your model and build that model for you." ] } ], "metadata": { "kernelspec": { "display_name": "Python [default]", "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.6" }, "msauthor": "sgilley" }, "nbformat": 4, "nbformat_minor": 2 }