|
| 1 | +--- |
| 2 | +title: Deploy an encrypted image classification service |
| 3 | +titleSuffix: Azure Machine Learning |
| 4 | +description: Learn how to use Microsoft SEAL to deploy an encrypted prediction service for image classification |
| 5 | +author: luisquintanilla |
| 6 | +ms.author: luquinta |
| 7 | +ms.date: 05/14/2020 |
| 8 | +services: machine-learning |
| 9 | +ms.service: machine-learning |
| 10 | +ms.subservice: core |
| 11 | +ms.topic: conceptual |
| 12 | +#intent: As a data scientist, I want to deploy a service that uses homomorphic encryption to make predictions on encrypted image data |
| 13 | +--- |
| 14 | + |
| 15 | +# Deploy an image classification model for encrypted inferencing in Azure Container Instance (ACI) |
| 16 | + |
| 17 | +This tutorial is **a new addition to the two-part series**. In the [previous tutorial](img-classification-part1-training.ipynb), you trained machine learning models and then registered a model in your workspace on the cloud. |
| 18 | + |
| 19 | +Now, you're ready to deploy the model as a encrypted inferencing 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. |
| 20 | + |
| 21 | +In this part of the tutorial, you use Azure Machine Learning service (Preview) to: |
| 22 | + |
| 23 | +> * Set up your testing environment |
| 24 | +> * Retrieve the model from your workspace |
| 25 | +> * Test the model locally |
| 26 | +> * Deploy the model to ACI |
| 27 | +> * Test the deployed model |
| 28 | +
|
| 29 | +ACI is a great solution for testing and understanding the workflow. For scalable production deployments, consider using Azure Kubernetes Service. For more information, see [how to deploy and where](https://docs.microsoft.com/azure/machine-learning/service/how-to-deploy-and-where). |
| 30 | + |
| 31 | + |
| 32 | +## Prerequisites |
| 33 | + |
| 34 | +Complete the model training in the [Tutorial #1: Train an image classification model with Azure Machine Learning](train-models.ipynb) notebook. |
| 35 | + |
| 36 | + |
| 37 | +<!-- #endregion --> |
| 38 | + |
| 39 | +```python |
| 40 | +# If you did NOT complete the tutorial, you can instead run this cell |
| 41 | +# This will register a model and download the data needed for this tutorial |
| 42 | +# These prerequisites are created in the training tutorial |
| 43 | +# Feel free to skip this cell if you completed the training tutorial |
| 44 | + |
| 45 | +# register a model |
| 46 | +from azureml.core import Workspace |
| 47 | +ws = Workspace.from_config() |
| 48 | + |
| 49 | +from azureml.core.model import Model |
| 50 | + |
| 51 | +model_name = "sklearn_mnist" |
| 52 | +model = Model.register(model_path="sklearn_mnist_model.pkl", |
| 53 | + model_name=model_name, |
| 54 | + tags={"data": "mnist", "model": "classification"}, |
| 55 | + description="Mnist handwriting recognition", |
| 56 | + workspace=ws) |
| 57 | + |
| 58 | + |
| 59 | +``` |
| 60 | + |
| 61 | +### Setup the Environment |
| 62 | + |
| 63 | +Add `encrypted-inference` package as a conda dependency |
| 64 | + |
| 65 | +```python |
| 66 | +from azureml.core.environment import Environment |
| 67 | +from azureml.core.conda_dependencies import CondaDependencies |
| 68 | + |
| 69 | +# to install required packages |
| 70 | +env = Environment('tutorial-env') |
| 71 | +cd = CondaDependencies.create(pip_packages=['azureml-dataprep[pandas,fuse]>=1.1.14', 'azureml-defaults', 'azure-storage-blob', 'encrypted-inference==0.9'], conda_packages = ['scikit-learn==0.22.1']) |
| 72 | + |
| 73 | +env.python.conda_dependencies = cd |
| 74 | + |
| 75 | +# Register environment to re-use later |
| 76 | +env.register(workspace = ws) |
| 77 | +``` |
| 78 | + |
| 79 | +## Set up the environment |
| 80 | + |
| 81 | +Start by setting up a testing environment. |
| 82 | + |
| 83 | +### Import packages |
| 84 | + |
| 85 | +Import the Python packages needed for this tutorial. |
| 86 | + |
| 87 | +```python tags=["check version"] |
| 88 | +%matplotlib inline |
| 89 | +import numpy as np |
| 90 | +import matplotlib.pyplot as plt |
| 91 | + |
| 92 | +import azureml.core |
| 93 | + |
| 94 | +# display the core SDK version number |
| 95 | +print("Azure ML SDK Version: ", azureml.core.VERSION) |
| 96 | +``` |
| 97 | + |
| 98 | +#### Install Homomorphic Encryption based library for Secure Inferencing |
| 99 | + |
| 100 | +Our library is based on [Microsoft SEAL](https://github.com/Microsoft/SEAL) and pubished to [PyPi.org](https://pypi.org/project/encrypted-inference) as an easy to use package |
| 101 | + |
| 102 | +```python |
| 103 | +!pip install encrypted-inference==0.9 |
| 104 | +``` |
| 105 | + |
| 106 | +## Deploy as web service |
| 107 | + |
| 108 | +Deploy the model as a web service hosted in ACI. |
| 109 | + |
| 110 | +To build the correct environment for ACI, provide the following: |
| 111 | +* A scoring script to show how to use the model |
| 112 | +* A configuration file to build the ACI |
| 113 | +* The model you trained before |
| 114 | + |
| 115 | +### Create scoring script |
| 116 | + |
| 117 | +Create the scoring script, called score.py, used by the web service call to show how to use the model. |
| 118 | + |
| 119 | +You must include two required functions into the scoring script: |
| 120 | +* The `init()` function, which typically loads the model into a global object. This function is run only once when the Docker container is started. |
| 121 | + |
| 122 | +* 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. The function fetches homomorphic encryption based public keys that are uploaded by the service caller. |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +```python |
| 127 | +%%writefile score.py |
| 128 | +import json |
| 129 | +import os |
| 130 | +import pickle |
| 131 | +import joblib |
| 132 | +from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient, PublicAccess |
| 133 | +from encrypted.inference.eiserver import EIServer |
| 134 | + |
| 135 | +def init(): |
| 136 | + global model |
| 137 | + # AZUREML_MODEL_DIR is an environment variable created during deployment. |
| 138 | + # It is the path to the model folder (./azureml-models/$MODEL_NAME/$VERSION) |
| 139 | + # For multiple models, it points to the folder containing all deployed models (./azureml-models) |
| 140 | + model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_mnist_model.pkl') |
| 141 | + model = joblib.load(model_path) |
| 142 | + |
| 143 | + global server |
| 144 | + server = EIServer(model.coef_, model.intercept_, verbose=True) |
| 145 | + |
| 146 | +def run(raw_data): |
| 147 | + |
| 148 | + json_properties = json.loads(raw_data) |
| 149 | + |
| 150 | + key_id = json_properties['key_id'] |
| 151 | + conn_str = json_properties['conn_str'] |
| 152 | + container = json_properties['container'] |
| 153 | + data = json_properties['data'] |
| 154 | + |
| 155 | + # download the Galois keys from blob storage |
| 156 | + #TODO optimize by caching the keys locally |
| 157 | + blob_service_client = BlobServiceClient.from_connection_string(conn_str=conn_str) |
| 158 | + blob_client = blob_service_client.get_blob_client(container=container, blob=key_id) |
| 159 | + public_keys = blob_client.download_blob().readall() |
| 160 | + |
| 161 | + result = {} |
| 162 | + # make prediction |
| 163 | + result = server.predict(data, public_keys) |
| 164 | + |
| 165 | + # you can return any data type as long as it is JSON-serializable |
| 166 | + return result |
| 167 | +``` |
| 168 | + |
| 169 | +### Create configuration file |
| 170 | + |
| 171 | +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. |
| 172 | + |
| 173 | +```python tags=["configure web service", "aci"] |
| 174 | +from azureml.core.webservice import AciWebservice |
| 175 | + |
| 176 | +aciconfig = AciWebservice.deploy_configuration(cpu_cores=1, |
| 177 | + memory_gb=1, |
| 178 | + tags={"data": "MNIST", "method" : "sklearn"}, |
| 179 | + description='Encrypted Predict MNIST with sklearn + SEAL') |
| 180 | +``` |
| 181 | + |
| 182 | +### Deploy in ACI |
| 183 | +Estimated time to complete: **about 2-5 minutes** |
| 184 | + |
| 185 | +Configure the image and deploy. The following code goes through these steps: |
| 186 | + |
| 187 | +1. Create environment object containing dependencies needed by the model using the environment file (`myenv.yml`) |
| 188 | +1. Create inference configuration necessary to deploy the model as a web service using: |
| 189 | + * The scoring file (`score.py`) |
| 190 | + * envrionment object created in previous step |
| 191 | +1. Deploy the model to the ACI container. |
| 192 | +1. Get the web service HTTP endpoint. |
| 193 | + |
| 194 | +```python tags=["configure image", "create image", "deploy web service", "aci"] |
| 195 | +%%time |
| 196 | +from azureml.core.webservice import Webservice |
| 197 | +from azureml.core.model import InferenceConfig |
| 198 | +from azureml.core.environment import Environment |
| 199 | +from azureml.core import Workspace |
| 200 | +from azureml.core.model import Model |
| 201 | + |
| 202 | +ws = Workspace.from_config() |
| 203 | +model = Model(ws, 'sklearn_mnist') |
| 204 | + |
| 205 | +myenv = Environment.get(workspace=ws, name="tutorial-env") |
| 206 | +inference_config = InferenceConfig(entry_script="score.py", environment=myenv) |
| 207 | + |
| 208 | +service = Model.deploy(workspace=ws, |
| 209 | + name='sklearn-mnist-svc', |
| 210 | + models=[model], |
| 211 | + inference_config=inference_config, |
| 212 | + deployment_config=aciconfig) |
| 213 | + |
| 214 | +service.wait_for_deployment(show_output=True) |
| 215 | +``` |
| 216 | + |
| 217 | +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. |
| 218 | + |
| 219 | +```python tags=["get scoring uri"] |
| 220 | +print(service.scoring_uri) |
| 221 | +``` |
| 222 | + |
| 223 | +## Test the model |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +### Download test data |
| 228 | +Download the test data to the **./data/** directory |
| 229 | + |
| 230 | +```python |
| 231 | +import os |
| 232 | +from azureml.core import Dataset |
| 233 | +from azureml.opendatasets import MNIST |
| 234 | + |
| 235 | +data_folder = os.path.join(os.getcwd(), 'data') |
| 236 | +os.makedirs(data_folder, exist_ok=True) |
| 237 | + |
| 238 | +mnist_file_dataset = MNIST.get_file_dataset() |
| 239 | +mnist_file_dataset.download(data_folder, overwrite=True) |
| 240 | +``` |
| 241 | + |
| 242 | +### Load test data |
| 243 | + |
| 244 | +Load the test data from the **./data/** directory created during the training tutorial. |
| 245 | + |
| 246 | +```python |
| 247 | +from utils import load_data |
| 248 | +import os |
| 249 | +import glob |
| 250 | + |
| 251 | +data_folder = os.path.join(os.getcwd(), 'data') |
| 252 | +# note we also shrink the intensity values (X) from 0-255 to 0-1. This helps the neural network converge faster |
| 253 | +X_test = load_data(glob.glob(os.path.join(data_folder,"**/t10k-images-idx3-ubyte.gz"), recursive=True)[0], False) / 255.0 |
| 254 | +y_test = load_data(glob.glob(os.path.join(data_folder,"**/t10k-labels-idx1-ubyte.gz"), recursive=True)[0], True).reshape(-1) |
| 255 | +``` |
| 256 | + |
| 257 | +<!-- #region --> |
| 258 | +### Predict test data |
| 259 | + |
| 260 | +Feed the test dataset to the model to get predictions. |
| 261 | + |
| 262 | + |
| 263 | +The following code goes through these steps: |
| 264 | + |
| 265 | +1. Create our Homomorphic Encryption based client |
| 266 | + |
| 267 | +1. Upload HE generated public keys |
| 268 | + |
| 269 | +1. Encrypt the data |
| 270 | + |
| 271 | +1. Send the data as JSON to the web service hosted in ACI. |
| 272 | + |
| 273 | +1. Use the SDK's `run` API to invoke the service. You can also make raw calls using any HTTP tool such as curl. |
| 274 | +<!-- #endregion --> |
| 275 | + |
| 276 | +#### Create our Homomorphic Encryption based client |
| 277 | + |
| 278 | +Create a new EILinearRegressionClient and setup the public keys |
| 279 | + |
| 280 | +```python |
| 281 | +from encrypted.inference.eiclient import EILinearRegressionClient |
| 282 | + |
| 283 | +# Create a new Encrypted inference client and a new secret key. |
| 284 | +edp = EILinearRegressionClient(verbose=True) |
| 285 | + |
| 286 | +public_keys_blob, public_keys_data = edp.get_public_keys() |
| 287 | + |
| 288 | +``` |
| 289 | + |
| 290 | +#### Upload HE generated public keys |
| 291 | + |
| 292 | +Upload the public keys to the workspace default blob store. This will allow us to share the keys with the inference server |
| 293 | + |
| 294 | +```python |
| 295 | +import azureml.core |
| 296 | +from azureml.core import Workspace, Datastore |
| 297 | +import os |
| 298 | + |
| 299 | +ws = Workspace.from_config() |
| 300 | + |
| 301 | +datastore = ws.get_default_datastore() |
| 302 | +container_name=datastore.container_name |
| 303 | + |
| 304 | +# Create a local file and write the keys to it |
| 305 | +public_keys = open(public_keys_blob, "wb") |
| 306 | +public_keys.write(public_keys_data) |
| 307 | +public_keys.close() |
| 308 | + |
| 309 | +# Upload the file to blob store |
| 310 | +datastore.upload_files([public_keys_blob]) |
| 311 | + |
| 312 | +# Delete the local file |
| 313 | +os.remove(public_keys_blob) |
| 314 | +``` |
| 315 | + |
| 316 | +#### Encrypt the data |
| 317 | + |
| 318 | +```python |
| 319 | +#choose any one sample from the test data |
| 320 | +sample_index = 1 |
| 321 | + |
| 322 | +#encrypt the data |
| 323 | +raw_data = edp.encrypt(X_test[sample_index]) |
| 324 | + |
| 325 | +``` |
| 326 | + |
| 327 | +#### Send the test data to the webservice hosted in ACI |
| 328 | + |
| 329 | +Feed the test dataset to the model to get predictions. We will need to send the connection string to the blob storage where the public keys were uploaded |
| 330 | + |
| 331 | + |
| 332 | +```python |
| 333 | +import json |
| 334 | +from azureml.core import Webservice |
| 335 | + |
| 336 | +service = Webservice(ws, 'sklearn-mnist-svc') |
| 337 | + |
| 338 | +#pass the connection string for blob storage to give the server access to the uploaded public keys |
| 339 | +conn_str_template = 'DefaultEndpointsProtocol={};AccountName={};AccountKey={};EndpointSuffix=core.windows.net' |
| 340 | +conn_str = conn_str_template.format(datastore.protocol, datastore.account_name, datastore.account_key) |
| 341 | + |
| 342 | +#build the json |
| 343 | +data = json.dumps({"data": raw_data, "key_id" : public_keys_blob, "conn_str" : conn_str, "container" : container_name }) |
| 344 | +data = bytes(data, encoding='ASCII') |
| 345 | + |
| 346 | +print ('Making an encrypted inference web service call ') |
| 347 | +eresult = service.run(input_data=data) |
| 348 | + |
| 349 | +print ('Received encrypted inference results') |
| 350 | +``` |
| 351 | + |
| 352 | +#### Decrypt the data |
| 353 | + |
| 354 | +Use the client to decrypt the results |
| 355 | + |
| 356 | +```python |
| 357 | +import numpy as np |
| 358 | + |
| 359 | +results = edp.decrypt(eresult) |
| 360 | + |
| 361 | +print ('Decrypted the results ', results) |
| 362 | + |
| 363 | +#Apply argmax to identify the prediction result |
| 364 | +prediction = np.argmax(results) |
| 365 | + |
| 366 | +print ( ' Prediction : ', prediction) |
| 367 | +print ( ' Actual Label : ', y_test[sample_index]) |
| 368 | +``` |
| 369 | + |
| 370 | +## Clean up resources |
| 371 | + |
| 372 | +To keep the resource group and workspace for other tutorials and exploration, you can delete only the ACI deployment using this API call: |
| 373 | + |
| 374 | +```python tags=["delete web service"] |
| 375 | +service.delete() |
| 376 | +``` |
| 377 | + |
| 378 | +<!-- #region --> |
| 379 | + |
| 380 | +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. |
| 381 | + |
| 382 | +## Next steps |
| 383 | + |
| 384 | +In this Azure Machine Learning tutorial, you used Python to: |
| 385 | + |
| 386 | +> * Set up your testing environment |
| 387 | +> * Retrieve the model from your workspace |
| 388 | +> * Test the model locally |
| 389 | +> * Deploy the model to ACI |
| 390 | +> * Test the deployed model |
| 391 | + |
| 392 | +You can also try out the [regression tutorial](regression-part1-data-prep.ipynb). |
| 393 | +<!-- #endregion --> |
| 394 | + |
| 395 | + |
0 commit comments