|
| 1 | +--- |
| 2 | +title: Deploy an encrypted inferencing 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/18/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 data |
| 13 | +--- |
| 14 | + |
| 15 | +# How to deploy an encrypted inferencing web service |
| 16 | + |
| 17 | +Learn how to deploy an image classification model as an encrypted inferencing web service in [Azure Container Instances](https://docs.microsoft.com/azure/container-instances/) (ACI). The web service is a Docker container image that contains the model and scoring logic. |
| 18 | + |
| 19 | +In this guide, you use Azure Machine Learning service to: |
| 20 | + |
| 21 | +> [!div class="checklist"] |
| 22 | +> * Configure your environments |
| 23 | +> * Deploy encrypted inferencing web service |
| 24 | +> * Prepare test data |
| 25 | +> * Make encrypted predictions |
| 26 | +> * Clean up resources |
| 27 | +
|
| 28 | +ACI is a great solution for testing and understanding the model deployment 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). |
| 29 | + |
| 30 | +The encryption method used in this sample is [homomorphic encryption](https://github.com/Microsoft/SEAL#homomorphic-encryption). Homomorphic encryption allows for computations to be done on encrypted data without requiring access to a secret (decryption) key. The results of the computations are encrypted and can be revealed only by the owner of the secret key. |
| 31 | + |
| 32 | +## Prerequisites |
| 33 | + |
| 34 | +This guide assumes that you have an image classification model registered in Azure Machine Learning. If not, register the model using a [pretrained model](https://github.com/Azure/MachineLearningNotebooks/raw/master/tutorials/image-classification-mnist-data/sklearn_mnist_model.pkl) or create your own by completing the [train an image classification model with Azure Machine Learning tutorial](tutorial-train-models-with-aml.md). |
| 35 | + |
| 36 | +## Configure local environment |
| 37 | + |
| 38 | +In a Jupyter notebook |
| 39 | + |
| 40 | +1. Import the Python packages needed for this sample. |
| 41 | + |
| 42 | + ```python |
| 43 | + %matplotlib inline |
| 44 | + import numpy as np |
| 45 | + import matplotlib.pyplot as plt |
| 46 | + |
| 47 | + import azureml.core |
| 48 | + |
| 49 | + # display the core SDK version number |
| 50 | + print("Azure ML SDK Version: ", azureml.core.VERSION) |
| 51 | + ``` |
| 52 | + |
| 53 | +2. Install homomorphic encryption library for secure inferencing. |
| 54 | + |
| 55 | + > [!NOTE] |
| 56 | + > The `encrypted-inference` package is currently in preview. |
| 57 | + |
| 58 | + [`encrypted-inference`](https://pypi.org/project/encrypted-inference) is a library that contains bindings for encrypted inferencing based on [Microsoft SEAL](https://github.com/Microsoft/SEAL). |
| 59 | + |
| 60 | + ```python |
| 61 | + !pip install encrypted-inference==0.9 |
| 62 | + ``` |
| 63 | + |
| 64 | +## Configure the inferencing environment |
| 65 | + |
| 66 | +Create an environment for inferencing and add `encrypted-inference` package as a conda dependency. |
| 67 | + |
| 68 | +```python |
| 69 | +from azureml.core.environment import Environment |
| 70 | +from azureml.core.conda_dependencies import CondaDependencies |
| 71 | + |
| 72 | +# to install required packages |
| 73 | +env = Environment('tutorial-env') |
| 74 | +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']) |
| 75 | + |
| 76 | +env.python.conda_dependencies = cd |
| 77 | + |
| 78 | +# Register environment to re-use later |
| 79 | +env.register(workspace = ws) |
| 80 | +``` |
| 81 | + |
| 82 | +## Deploy encrypted inferencing web service |
| 83 | + |
| 84 | +Deploy the model as a web service hosted in ACI. |
| 85 | + |
| 86 | +To build the correct environment for ACI, provide the following: |
| 87 | + |
| 88 | +* A scoring script to show how to use the model |
| 89 | +* A configuration file to build the ACI |
| 90 | +* A trained model |
| 91 | + |
| 92 | +### Create scoring script |
| 93 | + |
| 94 | +Create the scoring script `score.py` used by the web service for inferencing. |
| 95 | + |
| 96 | +You must include two required functions into the scoring script: |
| 97 | + |
| 98 | +* The `init()` function, which typically loads the model into a global object. This function is run only once when the Docker container is started. |
| 99 | +* 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. |
| 100 | + |
| 101 | +```python |
| 102 | +%%writefile score.py |
| 103 | +import json |
| 104 | +import os |
| 105 | +import pickle |
| 106 | +import joblib |
| 107 | +from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient, PublicAccess |
| 108 | +from encrypted.inference.eiserver import EIServer |
| 109 | + |
| 110 | +def init(): |
| 111 | + global model |
| 112 | + # AZUREML_MODEL_DIR is an environment variable created during deployment. |
| 113 | + # It is the path to the model folder (./azureml-models/$MODEL_NAME/$VERSION) |
| 114 | + # For multiple models, it points to the folder containing all deployed models (./azureml-models) |
| 115 | + model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_mnist_model.pkl') |
| 116 | + model = joblib.load(model_path) |
| 117 | + |
| 118 | + global server |
| 119 | + server = EIServer(model.coef_, model.intercept_, verbose=True) |
| 120 | + |
| 121 | +def run(raw_data): |
| 122 | + |
| 123 | + json_properties = json.loads(raw_data) |
| 124 | + |
| 125 | + key_id = json_properties['key_id'] |
| 126 | + conn_str = json_properties['conn_str'] |
| 127 | + container = json_properties['container'] |
| 128 | + data = json_properties['data'] |
| 129 | + |
| 130 | + # download the public keys from blob storage |
| 131 | + blob_service_client = BlobServiceClient.from_connection_string(conn_str=conn_str) |
| 132 | + blob_client = blob_service_client.get_blob_client(container=container, blob=key_id) |
| 133 | + public_keys = blob_client.download_blob().readall() |
| 134 | + |
| 135 | + result = {} |
| 136 | + # make prediction |
| 137 | + result = server.predict(data, public_keys) |
| 138 | + |
| 139 | + # you can return any data type as long as it is JSON-serializable |
| 140 | + return result |
| 141 | +``` |
| 142 | + |
| 143 | +### Create configuration file |
| 144 | + |
| 145 | +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. |
| 146 | + |
| 147 | +```python |
| 148 | +from azureml.core.webservice import AciWebservice |
| 149 | + |
| 150 | +aciconfig = AciWebservice.deploy_configuration(cpu_cores=1, |
| 151 | + memory_gb=1, |
| 152 | + tags={"data": "MNIST", "method" : "sklearn"}, |
| 153 | + description='Encrypted Predict MNIST with sklearn + SEAL') |
| 154 | +``` |
| 155 | + |
| 156 | +### Deploy to Azure Container Instances |
| 157 | + |
| 158 | +Estimated time to complete: **about 2-5 minutes** |
| 159 | + |
| 160 | +Configure the image and deploy. The following code goes through these steps: |
| 161 | + |
| 162 | +1. Create environment object containing dependencies needed by the model using the environment file (`myenv.yml`) |
| 163 | +1. Create inference configuration necessary to deploy the model as a web service using: |
| 164 | + * The scoring file (`score.py`) |
| 165 | + * Environment object created in the previous step |
| 166 | +1. Deploy the model to the ACI container. |
| 167 | +1. Get the web service HTTP endpoint. |
| 168 | + |
| 169 | +```python |
| 170 | +%%time |
| 171 | +from azureml.core.webservice import Webservice |
| 172 | +from azureml.core.model import InferenceConfig |
| 173 | +from azureml.core.environment import Environment |
| 174 | +from azureml.core import Workspace |
| 175 | +from azureml.core.model import Model |
| 176 | + |
| 177 | +ws = Workspace.from_config() |
| 178 | +model = Model(ws, 'sklearn_mnist') |
| 179 | + |
| 180 | +myenv = Environment.get(workspace=ws, name="tutorial-env") |
| 181 | +inference_config = InferenceConfig(entry_script="score.py", environment=myenv) |
| 182 | + |
| 183 | +service = Model.deploy(workspace=ws, |
| 184 | + name='sklearn-encrypted-mnist-svc', |
| 185 | + models=[model], |
| 186 | + inference_config=inference_config, |
| 187 | + deployment_config=aciconfig) |
| 188 | + |
| 189 | +service.wait_for_deployment(show_output=True) |
| 190 | +``` |
| 191 | + |
| 192 | +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. |
| 193 | + |
| 194 | +```python |
| 195 | +print(service.scoring_uri) |
| 196 | +``` |
| 197 | + |
| 198 | +## Prepare test data |
| 199 | + |
| 200 | +1. Download the test data. In this case, it's saved into a directory called *data*. |
| 201 | + |
| 202 | + ```python |
| 203 | + import os |
| 204 | + from azureml.core import Dataset |
| 205 | + from azureml.opendatasets import MNIST |
| 206 | + |
| 207 | + data_folder = os.path.join(os.getcwd(), 'data') |
| 208 | + os.makedirs(data_folder, exist_ok=True) |
| 209 | + |
| 210 | + mnist_file_dataset = MNIST.get_file_dataset() |
| 211 | + mnist_file_dataset.download(data_folder, overwrite=True) |
| 212 | + ``` |
| 213 | + |
| 214 | +1. Load the test data from the *data* directory. |
| 215 | + |
| 216 | + ```python |
| 217 | + from utils import load_data |
| 218 | + import os |
| 219 | + import glob |
| 220 | + |
| 221 | + data_folder = os.path.join(os.getcwd(), 'data') |
| 222 | + # note we also shrink the intensity values (X) from 0-255 to 0-1. This helps the neural network converge faster |
| 223 | + X_test = load_data(glob.glob(os.path.join(data_folder,"**/t10k-images-idx3-ubyte.gz"), recursive=True)[0], False) / 255.0 |
| 224 | + y_test = load_data(glob.glob(os.path.join(data_folder,"**/t10k-labels-idx1-ubyte.gz"), recursive=True)[0], True).reshape(-1) |
| 225 | + ``` |
| 226 | + |
| 227 | +## Make encrypted predictions |
| 228 | + |
| 229 | +Use the test dataset with the model to get predictions. |
| 230 | + |
| 231 | +To make encrypted predictions: |
| 232 | + |
| 233 | +1. Create a new `EILinearRegressionClient`, a homomorphic encryption based client, and public keys. |
| 234 | + |
| 235 | + ```python |
| 236 | + from encrypted.inference.eiclient import EILinearRegressionClient |
| 237 | + |
| 238 | + # Create a new Encrypted inference client and a new secret key. |
| 239 | + edp = EILinearRegressionClient(verbose=True) |
| 240 | + |
| 241 | + public_keys_blob, public_keys_data = edp.get_public_keys() |
| 242 | + ``` |
| 243 | + |
| 244 | +1. Upload homomorphic encryption generated public keys to the workspace default blob store. This will allow you to share the keys with the inference server. |
| 245 | + |
| 246 | + ```python |
| 247 | + import azureml.core |
| 248 | + from azureml.core import Workspace, Datastore |
| 249 | + import os |
| 250 | + |
| 251 | + ws = Workspace.from_config() |
| 252 | + |
| 253 | + datastore = ws.get_default_datastore() |
| 254 | + container_name=datastore.container_name |
| 255 | + |
| 256 | + # Create a local file and write the keys to it |
| 257 | + public_keys = open(public_keys_blob, "wb") |
| 258 | + public_keys.write(public_keys_data) |
| 259 | + public_keys.close() |
| 260 | + |
| 261 | + # Upload the file to blob store |
| 262 | + datastore.upload_files([public_keys_blob]) |
| 263 | + |
| 264 | + # Delete the local file |
| 265 | + os.remove(public_keys_blob) |
| 266 | + ``` |
| 267 | + |
| 268 | +1. Encrypt the test data |
| 269 | + |
| 270 | + ```python |
| 271 | + #choose any one sample from the test data |
| 272 | + sample_index = 1 |
| 273 | + |
| 274 | + #encrypt the data |
| 275 | + raw_data = edp.encrypt(X_test[sample_index]) |
| 276 | + |
| 277 | + ``` |
| 278 | + |
| 279 | +1. Use the SDK's `run` API to invoke the service and provide 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. |
| 280 | + |
| 281 | + ```python |
| 282 | + import json |
| 283 | + from azureml.core import Webservice |
| 284 | + |
| 285 | + service = Webservice(ws, 'sklearn-encrypted-mnist-svc') |
| 286 | + |
| 287 | + #pass the connection string for blob storage to give the server access to the uploaded public keys |
| 288 | + conn_str_template = 'DefaultEndpointsProtocol={};AccountName={};AccountKey={};EndpointSuffix=core.windows.net' |
| 289 | + conn_str = conn_str_template.format(datastore.protocol, datastore.account_name, datastore.account_key) |
| 290 | + |
| 291 | + #build the json |
| 292 | + data = json.dumps({"data": raw_data, "key_id" : public_keys_blob, "conn_str" : conn_str, "container" : container_name }) |
| 293 | + data = bytes(data, encoding='ASCII') |
| 294 | + |
| 295 | + print ('Making an encrypted inference web service call ') |
| 296 | + eresult = service.run(input_data=data) |
| 297 | + |
| 298 | + print ('Received encrypted inference results') |
| 299 | + ``` |
| 300 | + |
| 301 | +1. Use the client to decrypt the results. |
| 302 | + |
| 303 | + ```python |
| 304 | + import numpy as np |
| 305 | + |
| 306 | + results = edp.decrypt(eresult) |
| 307 | + |
| 308 | + print ('Decrypted the results ', results) |
| 309 | + |
| 310 | + #Apply argmax to identify the prediction result |
| 311 | + prediction = np.argmax(results) |
| 312 | + |
| 313 | + print ( ' Prediction : ', prediction) |
| 314 | + print ( ' Actual Label : ', y_test[sample_index]) |
| 315 | + ``` |
| 316 | + |
| 317 | +## Clean up resources |
| 318 | + |
| 319 | +Delete the web service created in this sample: |
| 320 | + |
| 321 | +```python |
| 322 | +service.delete() |
| 323 | +``` |
| 324 | + |
| 325 | +If you no longer plan to use the Azure resources you’ve created, delete them. This prevents you from being charged for unutilized resources that are still running. See this guide on how to [clean up resources](how-to-manage-workspace.md#clean-up-resources) to learn more. |
0 commit comments