mirror of
https://github.com/Azure/MachineLearningNotebooks.git
synced 2025-12-19 17:17:04 -05:00
543 lines
23 KiB
Plaintext
543 lines
23 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Copyright (c) Microsoft Corporation. All rights reserved.\n",
|
|
"Licensed under the MIT License."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
""
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Using Azure Machine Learning Pipelines for Batch Inference for CSV Files\n",
|
|
"\n",
|
|
"In this notebook, we will demonstrate how to make predictions on large quantities of data asynchronously using the ML pipelines with Azure Machine Learning. Batch inference (or batch scoring) provides cost-effective inference, with unparalleled throughput for asynchronous applications. Batch prediction pipelines can scale to perform inference on terabytes of production data. Batch prediction is optimized for high throughput, fire-and-forget predictions for a large collection of data.\n",
|
|
"\n",
|
|
"> **Note**\n",
|
|
"This notebook uses public preview functionality (ParallelRunStep). Please install azureml-contrib-pipeline-steps package before running this notebook.\n",
|
|
"```\n",
|
|
"pip install azureml-contrib-pipeline-steps\n",
|
|
"```\n",
|
|
"> **Tip**\n",
|
|
"If your system requires low-latency processing (to process a single document or small set of documents quickly), use [real-time scoring](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-consume-web-service) instead of batch prediction.\n",
|
|
"\n",
|
|
"In this example we will take use a machine learning model already trained to predict different types of iris flowers and run that trained model on some of the data in a CSV file which has characteristics of different iris flowers. However, the same example can be extended to manipulating data to any embarrassingly-parallel processing through a python script.\n",
|
|
"\n",
|
|
"The outline of this notebook is as follows:\n",
|
|
"\n",
|
|
"- Create a DataStore referencing the CSV files stored in a blob container.\n",
|
|
"- Register the pretrained model into the model registry. \n",
|
|
"- Use the registered model to do batch inference on the CSV files in the data blob container.\n",
|
|
"\n",
|
|
"## Prerequisites\n",
|
|
"If you are using an Azure Machine Learning Notebook VM, you are all set. Otherwise, make sure you go through the configuration Notebook located at https://github.com/Azure/MachineLearningNotebooks first. This sets you up with a working config file that has information on your workspace, subscription id, etc. \n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Connect to workspace\n",
|
|
"Create a workspace object from the existing workspace. Workspace.from_config() reads the file config.json and loads the details into an object named ws."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core import Workspace\n",
|
|
"\n",
|
|
"ws = Workspace.from_config()\n",
|
|
"print('Workspace name: ' + ws.name, \n",
|
|
" 'Azure region: ' + ws.location, \n",
|
|
" 'Subscription id: ' + ws.subscription_id, \n",
|
|
" 'Resource group: ' + ws.resource_group, sep = '\\n')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create or Attach existing compute resource\n",
|
|
"By using Azure Machine Learning Compute, a managed service, data scientists can train machine learning models on clusters of Azure virtual machines. Examples include VMs with GPU support. In this tutorial, you create Azure Machine Learning Compute as your training environment. The code below creates the compute clusters for you if they don't already exist in your workspace.\n",
|
|
"\n",
|
|
"**Creation of compute takes approximately 5 minutes. If the AmlCompute with that name is already in your workspace the code will skip the creation process.**"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"from azureml.core.compute import AmlCompute, ComputeTarget\n",
|
|
"from azureml.core.compute_target import ComputeTargetException\n",
|
|
"\n",
|
|
"# choose a name for your cluster\n",
|
|
"compute_name = os.environ.get(\"AML_COMPUTE_CLUSTER_NAME\", \"cpu-cluster\")\n",
|
|
"compute_min_nodes = os.environ.get(\"AML_COMPUTE_CLUSTER_MIN_NODES\", 0)\n",
|
|
"compute_max_nodes = os.environ.get(\"AML_COMPUTE_CLUSTER_MAX_NODES\", 4)\n",
|
|
"\n",
|
|
"# This example uses CPU VM. For using GPU VM, set SKU to STANDARD_NC6\n",
|
|
"vm_size = os.environ.get(\"AML_COMPUTE_CLUSTER_SKU\", \"STANDARD_D2_V2\")\n",
|
|
"\n",
|
|
"\n",
|
|
"if compute_name in ws.compute_targets:\n",
|
|
" compute_target = ws.compute_targets[compute_name]\n",
|
|
" if compute_target and type(compute_target) is AmlCompute:\n",
|
|
" print('found compute target. just use it. ' + compute_name)\n",
|
|
"else:\n",
|
|
" print('creating a new compute target...')\n",
|
|
" provisioning_config = AmlCompute.provisioning_configuration(vm_size = vm_size,\n",
|
|
" min_nodes = compute_min_nodes, \n",
|
|
" max_nodes = compute_max_nodes)\n",
|
|
"\n",
|
|
" # create the cluster\n",
|
|
" compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n",
|
|
" \n",
|
|
" # can poll for a minimum number of nodes and for a specific timeout.\n",
|
|
" # if no min node count is provided it will use the scale settings for the cluster\n",
|
|
" compute_target.wait_for_completion(show_output=True, min_node_count=None, timeout_in_minutes=20)\n",
|
|
" \n",
|
|
" # For a more detailed view of current AmlCompute status, use get_status()\n",
|
|
" print(compute_target.get_status().serialize())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create a datastore containing sample images\n",
|
|
"The input dataset used for this notebook is CSV data which has attributes of different iris flowers. We have created a public blob container `sampledata` on an account named `pipelinedata`, containing iris data set. In the next step, we create a datastore with the name `iris_datastore`, which points to this container. In the call to `register_azure_blob_container` below, setting the `overwrite` flag to `True` overwrites any datastore that was created previously with that name. \n",
|
|
"\n",
|
|
"This step can be changed to point to your blob container by providing your own `datastore_name`, `container_name`, and `account_name`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core.datastore import Datastore\n",
|
|
"\n",
|
|
"account_name = \"pipelinedata\"\n",
|
|
"datastore_name=\"iris_datastore_data\"\n",
|
|
"container_name=\"sampledata\"\n",
|
|
"\n",
|
|
"iris_data = Datastore.register_azure_blob_container(ws, \n",
|
|
" datastore_name=datastore_name, \n",
|
|
" container_name= container_name, \n",
|
|
" account_name=account_name, \n",
|
|
" overwrite=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create a TabularDataset\n",
|
|
"A [TabularDataSet](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) references single or multiple files which contain data in a tabular structure (ie like CSV files) in your datastores or public urls. TabularDatasets provides you with the ability to download or mount the files to your compute. By creating a dataset, you create a reference to the data source location. If you applied any subsetting transformations to the dataset, they will be stored in the dataset as well. The data remains in its existing location, so no extra storage cost is incurred."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core.dataset import Dataset\n",
|
|
"\n",
|
|
"iris_ds_name = 'iris_data'\n",
|
|
"\n",
|
|
"path_on_datastore = iris_data.path('iris/')\n",
|
|
"input_iris_ds = Dataset.Tabular.from_delimited_files(path=path_on_datastore, validate=False)\n",
|
|
"registered_iris_ds = input_iris_ds.register(ws, iris_ds_name, create_new_version=True)\n",
|
|
"named_iris_ds = registered_iris_ds.as_named_input(iris_ds_name)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Intermediate/Output Data\n",
|
|
"Intermediate data (or output of a Step) is represented by [PipelineData](https://docs.microsoft.com/en-us/python/api/azureml-pipeline-core/azureml.pipeline.core.pipelinedata?view=azure-ml-py) object. PipelineData can be produced by one step and consumed in another step by providing the PipelineData object as an output of one step and the input of one or more steps.\n",
|
|
"\n",
|
|
"**Constructing PipelineData**\n",
|
|
"- name: [Required] Name of the data item within the pipeline graph\n",
|
|
"- datastore_name: Name of the Datastore to write this output to\n",
|
|
"- output_name: Name of the output\n",
|
|
"- output_mode: Specifies \"upload\" or \"mount\" modes for producing output (default: mount)\n",
|
|
"- output_path_on_compute: For \"upload\" mode, the path to which the module writes this output during execution\n",
|
|
"- output_overwrite: Flag to overwrite pre-existing data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.pipeline.core import PipelineData\n",
|
|
"\n",
|
|
"datastore = ws.get_default_datastore()\n",
|
|
"output_folder = PipelineData(name='inferences', datastore=datastore)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Registering the Model with the Workspace\n",
|
|
"Get the pretrained model from a publicly available Azure Blob container, then register it to use in your workspace"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"model_container_name=\"iris-model\"\n",
|
|
"model_datastore_name=\"iris_model_datastore\"\n",
|
|
"\n",
|
|
"model_datastore = Datastore.register_azure_blob_container(ws, \n",
|
|
" datastore_name=model_datastore_name, \n",
|
|
" container_name= model_container_name, \n",
|
|
" account_name=account_name, \n",
|
|
" overwrite=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core.model import Model\n",
|
|
"\n",
|
|
"model_datastore.download('iris_model.pkl')\n",
|
|
"\n",
|
|
"# register downloaded model\n",
|
|
"model = Model.register(model_path = \"iris_model.pkl/iris_model.pkl\",\n",
|
|
" model_name = \"iris\", # this is the name the model is registered as\n",
|
|
" tags = {'pretrained': \"iris\"},\n",
|
|
" workspace = ws)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Using your model to make batch predictions\n",
|
|
"To use the model to make batch predictions, you need an **entry script** and a list of **dependencies**:\n",
|
|
"\n",
|
|
"#### An entry script\n",
|
|
"This script accepts requests, scores the requests by using the model, and returns the results.\n",
|
|
"- __init()__ - Typically this function loads the model into a global object. This function is run only once at the start of batch processing per worker node/process. init method can make use of following environment variables (ParallelRunStep input):\n",
|
|
" 1.\tAZUREML_BI_OUTPUT_PATH \u00e2\u20ac\u201c output folder path\n",
|
|
"- __run(mini_batch)__ - The method to be parallelized. Each invocation will have one minibatch.<BR>\n",
|
|
"__mini_batch__: Batch inference will invoke run method and pass either a list or Pandas DataFrame as an argument to the method. Each entry in min_batch will be - a filepath if input is a FileDataset, a Pandas DataFrame if input is a TabularDataset.<BR>\n",
|
|
"__run__ method response: run() method should return a Pandas DataFrame or an array. For append_row output_action, these returned elements are appended into the common output file. For summary_only, the contents of the elements are ignored. For all output actions, each returned output element indicates one successful inference of input element in the input mini-batch.\n",
|
|
" User should make sure that enough data is included in inference result to map input to inference. Inference output will be written in output file and not guaranteed to be in order, user should use some key in the output to map it to input.\n",
|
|
" \n",
|
|
"\n",
|
|
"#### Dependencies\n",
|
|
"Helper scripts or Python/Conda packages required to run the entry script or model.\n",
|
|
"\n",
|
|
"The deployment configuration for the compute target that hosts the deployed model. This configuration describes things like memory and CPU requirements needed to run the model.\n",
|
|
"\n",
|
|
"These items are encapsulated into an inference configuration and a deployment configuration. The inference configuration references the entry script and other dependencies. You define these configurations programmatically when you use the SDK to perform the deployment. You define them in JSON files when you use the CLI.\n",
|
|
"\n",
|
|
"## Print inferencing script"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"scripts_folder = \"Code\"\n",
|
|
"script_file = \"iris_score.py\"\n",
|
|
"\n",
|
|
"# peek at contents\n",
|
|
"with open(os.path.join(scripts_folder, script_file)) as inference_file:\n",
|
|
" print(inference_file.read())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Build and run the batch inference pipeline\n",
|
|
"The data, models, and compute resource are now available. Let's put all these together in a pipeline."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Specify the environment to run the script\n",
|
|
"Specify the conda dependencies for your script. This will allow us to install pip packages as well as configure the inference environment."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core import Environment\n",
|
|
"from azureml.core.runconfig import CondaDependencies\n",
|
|
"\n",
|
|
"predict_conda_deps = CondaDependencies.create(pip_packages=[ \"scikit-learn==0.20.3\" ])\n",
|
|
"\n",
|
|
"predict_env = Environment(name=\"predict_environment\")\n",
|
|
"predict_env.python.conda_dependencies = predict_conda_deps\n",
|
|
"predict_env.docker.enabled = True\n",
|
|
"predict_env.spark.precache_packages = False"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create the configuration to wrap the inference script"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.contrib.pipeline.steps import ParallelRunStep, ParallelRunConfig\n",
|
|
"\n",
|
|
"# In a real-world scenario, you'll want to shape your process per node and nodes to fit your problem domain.\n",
|
|
"parallel_run_config = ParallelRunConfig(\n",
|
|
" source_directory=scripts_folder,\n",
|
|
" entry_script=script_file, # the user script to run against each input\n",
|
|
" mini_batch_size='5MB',\n",
|
|
" error_threshold=5,\n",
|
|
" output_action='append_row',\n",
|
|
" environment=predict_env,\n",
|
|
" compute_target=compute_target, \n",
|
|
" node_count=3,\n",
|
|
" run_invocation_timeout=600)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Create the pipeline step\n",
|
|
"Create the pipeline step using the script, environment configuration, and parameters. Specify the compute target you already attached to your workspace as the target of execution of the script. We will use ParallelRunStep to create the pipeline step."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"distributed_csv_iris_step = ParallelRunStep(\n",
|
|
" name='example-iris',\n",
|
|
" inputs=[named_iris_ds],\n",
|
|
" output=output_folder,\n",
|
|
" parallel_run_config=parallel_run_config,\n",
|
|
" models=[model],\n",
|
|
" arguments=['--model_name', 'iris'],\n",
|
|
" allow_reuse=True\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Run the pipeline\n",
|
|
"At this point you can run the pipeline and examine the output it produced. The Experiment object is used to track the run of the pipeline"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from azureml.core import Experiment\n",
|
|
"from azureml.pipeline.core import Pipeline\n",
|
|
"\n",
|
|
"pipeline = Pipeline(workspace=ws, steps=[distributed_csv_iris_step])\n",
|
|
"\n",
|
|
"pipeline_run = Experiment(ws, 'iris').submit(pipeline)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# this will output a table with link to the run details in azure portal\n",
|
|
"pipeline_run"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## View progress of Pipeline run\n",
|
|
"\n",
|
|
"The progress of the pipeline is able to be viewed either through azureml.widgets or a console feed from PipelineRun.wait_for_completion()."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# GUI\n",
|
|
"from azureml.widgets import RunDetails\n",
|
|
"RunDetails(pipeline_run).show() "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Console logs\n",
|
|
"pipeline_run.wait_for_completion(show_output=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## View Results\n",
|
|
"In the iris_score.py file above you can see that the Result with the prediction of the iris variety gets returned and then appended to the original input of the row from the csv file. These results are written to the DataStore specified in the PipelineData object as the output data, which in this case is called *inferences*. This contains the outputs from all of the worker nodes used in the compute cluster. You can download this data to view the results ... below just filters to a random 20 rows"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import pandas as pd\n",
|
|
"import shutil\n",
|
|
"\n",
|
|
"shutil.rmtree(\"iris_results\", ignore_errors=True)\n",
|
|
"\n",
|
|
"prediction_run = next(pipeline_run.get_children())\n",
|
|
"prediction_output = prediction_run.get_output_data(\"inferences\")\n",
|
|
"prediction_output.download(local_path=\"iris_results\")\n",
|
|
"\n",
|
|
"\n",
|
|
"for root, dirs, files in os.walk(\"iris_results\"):\n",
|
|
" for file in files:\n",
|
|
" if file.endswith('parallel_run_step.txt'):\n",
|
|
" result_file = os.path.join(root,file)\n",
|
|
"\n",
|
|
"# cleanup output format\n",
|
|
"df = pd.read_csv(result_file, delimiter=\" \", header=None)\n",
|
|
"df.columns = [\"sepal.length\", \"sepal.width\", \"petal.length\", \"petal.width\", \"variety\"]\n",
|
|
"print(\"Prediction has \", df.shape[0], \" rows\")\n",
|
|
"\n",
|
|
"random_subset = df.sample(n=20)\n",
|
|
"random_subset.head(20)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Cleanup compute resources\n",
|
|
"For re-occurring jobs, it may be wise to keep compute the compute resources and allow compute nodes to scale down to 0. However, since this is just a single run job, we are free to release the allocated compute resources."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# uncomment below and run if compute resources are no longer needed \n",
|
|
"# compute_target.delete()"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"authors": [
|
|
{
|
|
"name": "joringer"
|
|
},
|
|
{
|
|
"name": "asraniwa"
|
|
},
|
|
{
|
|
"name": "pansav"
|
|
},
|
|
{
|
|
"name": "tracych"
|
|
}
|
|
],
|
|
"category": "Other notebooks",
|
|
"compute": [
|
|
"AML Compute"
|
|
],
|
|
"datasets": [
|
|
"IRIS"
|
|
],
|
|
"deployment": [
|
|
"None"
|
|
],
|
|
"exclude_from_index": false,
|
|
"framework": [
|
|
"None"
|
|
],
|
|
"friendly_name": "IRIS data inferencing using ParallelRunStep",
|
|
"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.2"
|
|
},
|
|
"tags": [
|
|
"Batch Inferencing",
|
|
"Pipeline"
|
|
],
|
|
"task": "Recognize flower type"
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |