tutorials/e2e-ds-experience/e2e-ml-workflow.ipynb (1,321 lines of code) (raw):
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tutorial: Create production ML pipelines with Python SDK v2 (preview) in a Jupyter notebook\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"**Learning Objectives** - By the end of this two part tutorial, you should be able to use Azure Machine Learning (Azure ML) to productionize your ML project.\n",
"\n",
"This means you will be able to leverage the AzureML Python SDK to:\n",
"\n",
"- connect to your Azure ML workspace\n",
"- create Azure ML data assets\n",
"- create reusable Azure ML components\n",
"- create, validate and run Azure ML pipelines\n",
"- deploy the newly-trained model as an endpoint\n",
"- call the Azure ML endpoint for inferencing"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"**Motivations** - This tutorial is intended to introduce Azure ML to data scientists who want to scale up or publish their ML projects. By completing a familiar end-to-end project, which starts by loading the data and ends by creating and calling an online inference endpoint, the user should become familiar with the core concepts of Azure ML and their most common usage. Each step of this tutorial can be modified or performed in other ways that might have security or scalability advantages. We will cover some of those in the Part II of this tutorial, however, we suggest the reader use the provide links in each section to learn more on each topic."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"**Requirements** - In order to benefit from this tutorial, you need to have:\n",
"- basic understanding of Machine Learning projects workflow\n",
"- an Azure subscription. If you don't have an Azure subscription, [create a free account](https://aka.ms/AMLFree) before you begin.\n",
"- a working Azure ML workspace. A workspace can be created via Azure Portal, Azure CLI, or Python SDK. [Read more](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-workspace?tabs=python).\n",
"- a Python environmnet\n",
"- [installed Azure Machine Learning Python SDK v2](https://github.com/Azure/azureml-examples/blob/sdk-preview/sdk/setup.sh)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Introduction\n",
"\n",
"In this tutorial, you'll create an Azure ML pipeline to train a model for credit default prediction. The pipeline handles the data preparation, training and registering the trained model. You'll then run the pipeline, deploy the model and use it.\n",
"\n",
"The image below shows the pipeline as you'll see it in the AzureML portal once submitted. It's a rather simple pipeline we'll use to walk you through the AzureML SDK v2.\n",
"\n",
"The two steps are first data preparation and second training. \n",
"\n",
"\n",
"\n",
"An AzureML pipeline that runs from local components, requires several dependent files. Fo better understanding of the project structure, we produce all these dependencies in the notebook cells. By the end of this tutorial, the project structure should look like:\n",
"```\n",
"e2e-ds-experience \n",
" components\n",
" data_prep\n",
" data_prep.py \n",
" train\n",
" train.py\n",
" train.yml \n",
" dependencies\n",
" conda.yaml\n",
" deploy\n",
" sample-request.json\n",
" media\n",
" metrics.jpg\n",
" pipeline-overview.jpg\n",
" user-logs.jpg\n",
" e2e-ml-workflow.ipynb\n",
"```\n",
"After running this notebook, you should be able to create the project direcetly in the IDE of your choice, instead.\n",
"\n",
"## Set up the pipeline resources\n",
"\n",
"The Azure ML framework can be used from CLI, Python SDK, or studio interface. In this example, you'll use the AzureML Python SDK v2 to create a pipeline. \n",
"\n",
"Before creating the pipeline, you'll set up the resources the pipeline will use:\n",
"\n",
"* The dataset for training\n",
"* The software environment to run the pipeline\n",
"\n",
"## Connect to the workspace\n",
"\n",
"Before we dive in the code, you'll need to connect to your Azure ML workspace. The workspace is the top-level resource for Azure Machine Learning, providing a centralized place to work with all the artifacts you create when you use Azure Machine Learning.\n",
"\n",
"We are using `DefaultAzureCredential` to get access to workspace. \n",
"`DefaultAzureCredential` should be capable of handling most Azure SDK authentication scenarios. \n",
"\n",
"Reference for more available credentials if it does not work for you: [configure credential example](../../configuration.ipynb), [azure-identity reference doc](https://docs.microsoft.com/en-us/python/api/azure-identity/azure.identity?view=azure-python)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "import-mlclient"
},
"outputs": [],
"source": [
"# Handle to the workspace\n",
"from azure.ai.ml import MLClient\n",
"\n",
"# Authentication package\n",
"from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential\n",
"\n",
"try:\n",
" credential = DefaultAzureCredential()\n",
" # Check if given credential can get token successfully.\n",
" credential.get_token(\"https://management.azure.com/.default\")\n",
"except Exception as ex:\n",
" # Fall back to InteractiveBrowserCredential in case DefaultAzureCredential not work\n",
" credential = InteractiveBrowserCredential()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"In the next cell, enter your Subscription ID, Resource Group name and Workspace name. To find your Subscription ID:\n",
"1. In the upper right Azure Machine Learning Studio toolbar, select your workspace name.\n",
"1. At the bottom, select **View all properties in Azure Portal**\n",
"1. Copy the value from Azure Portal into the code."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "ml_client"
},
"outputs": [],
"source": [
"# Get a handle to the workspace\n",
"ml_client = MLClient(\n",
" credential=credential,\n",
" subscription_id=\"<SUBSCRIPTION_ID>\",\n",
" resource_group_name=\"<RESOURCE_GROUP>\",\n",
" workspace_name=\"<AML_WORKSPACE_NAME>\",\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The result is a handler to the workspace that you'll use to manage other resources and jobs.\n",
"\n",
"> [!IMPORTANT]\n",
"> Creating MLClient will not connect to the workspace. The client initialization is lazy, it will wait for the first time it needs to make a call (in the notebook below, that will happen during dataset registration).\n",
"\n",
"## Register data from an external url\n",
"\n",
"The data you use for training is usually in one of the locations below:\n",
"\n",
"* Local machine\n",
"* Web\n",
"* Big Data Storage services (for example, Azure Blob, Azure Data Lake Storage, SQL)\n",
" \n",
"Azure ML uses a [`Data`](https://docs.microsoft.com/azure/machine-learning/how-to-create-register-data-assets?tabs=Python-SDK) object to register a reusable definition of data, and consume data within a pipeline. In the section below, you'll consume some data from web url as one example. `Data` assets ets from other sources can be created as well."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "credit_data"
},
"outputs": [],
"source": [
"from azure.ai.ml.entities import Data\n",
"from azure.ai.ml.constants import AssetTypes\n",
"\n",
"web_path = \"https://azuremlexamples.blob.core.windows.net/datasets/credit_card/default%20of%20credit%20card%20clients.csv\"\n",
"\n",
"credit_data = Data(\n",
" name=\"creditcard_blob_csv_defaults\",\n",
" path=web_path,\n",
" type=AssetTypes.URI_FILE,\n",
" description=\"Dataset for credit card defaults\",\n",
" tags={\"source_type\": \"web\", \"source\": \"AzureML examples blob\"},\n",
" version=\"1.0.0\",\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"This code just created a `Data` asset, it is ready to be consumed as an input by the pipeline that you'll define in the next sections. In addition, you can register the data to your workspace so it becomes reusable across pipelines.\n",
"\n",
"Registering the data asset will enable you to:\n",
"\n",
"* reuse and share the data asset in future pipelines\n",
"* use versions to track the modification to the data asset\n",
"* use the data asset from Azure ML designer, which is Azure ML's GUI for pipeline authoring\n",
"\n",
"Since this is the first time that you're making a call to the workspace, you may be asked to authenticate. Once the authentication is complete, you'll then see the dataset registration completion message."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "update-credit_data"
},
"outputs": [],
"source": [
"credit_data = ml_client.data.create_or_update(credit_data)\n",
"print(\n",
" f\"Dataset with name {credit_data.name} was registered to workspace, the dataset version is {credit_data.version}\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create a job environment for pipeline steps\n",
"\n",
"So far, you've created a development environment on the compute instance, your development machine. You'll also need an [environment](https://docs.microsoft.com/azure/machine-learning/concept-environments) to use for each step of the pipeline. Each step can have its own environment, or you can use some common environments for multiple steps.\n",
"\n",
"In this example, you'll create a conda environment for your jobs, using a conda yaml file.\n",
"First, create a directory to store the file in."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "dependencies_dir"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"dependencies_dir = \"./dependencies\"\n",
"os.makedirs(dependencies_dir, exist_ok=True)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, create the file in the dependencies directory."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "conda.yaml"
},
"outputs": [],
"source": [
"%%writefile {dependencies_dir}/conda.yaml\n",
"name: model-env\n",
"channels:\n",
" - conda-forge\n",
"dependencies:\n",
" - python=3.8\n",
" - numpy=1.21.2\n",
" - pip=21.2.4\n",
" - scikit-learn=0.24.2\n",
" - scipy=1.7.1\n",
" - pandas>=1.1,<1.2\n",
" - pip:\n",
" - inference-schema[numpy-support]==1.3.0\n",
" - xlrd==2.0.1\n",
" - azureml-mlflow==1.42.0"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"The specification contains some usual packages, that you'll use in your pipeline (numpy, pip).\n",
"\n",
"\n",
"Use the *yaml* file to create and register this custom environment in your workspace:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "custom_env_name"
},
"outputs": [],
"source": [
"from azure.ai.ml.entities import Environment\n",
"\n",
"custom_env_name = \"aml-scikit-learn\"\n",
"\n",
"pipeline_job_env = Environment(\n",
" name=custom_env_name,\n",
" description=\"Custom environment for Credit Card Defaults pipeline\",\n",
" tags={\"scikit-learn\": \"0.24.2\"},\n",
" conda_file=os.path.join(dependencies_dir, \"conda.yaml\"),\n",
" image=\"mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:latest\",\n",
" version=\"0.1.1\",\n",
")\n",
"pipeline_job_env = ml_client.environments.create_or_update(pipeline_job_env)\n",
"\n",
"print(\n",
" f\"Environment with name {pipeline_job_env.name} is registered to workspace, the environment version is {pipeline_job_env.version}\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Build the training pipeline\n",
"\n",
"Now that you have all assets required to run your pipeline, it's time to build the pipeline itself, using the Azure ML Python SDK v2.\n",
"\n",
"Azure ML pipelines are reusable ML workflows that usually consist of several components. The typical life of a component is:\n",
"\n",
"* Write the yaml specification of the component.\n",
"* Optionally, register the component with a name and version in your workspace, to make it reusable and shareable.\n",
"* Load that component from the pipeline code.\n",
"* Implement the pipeline using this component inputs, outputs and parameters.\n",
"* Submit the pipeline.\n",
"\n",
"## Create component 1: data prep (using programmatic definition)\n",
"\n",
"Let's start by creating the first component. This component handles the preprocessing of the data. The preprocessing task is performed in the *data_prep.py* python file.\n",
"\n",
"First create a source folder for the data_prep component:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "data_prep_src_dir"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"data_prep_src_dir = \"./components/data_prep\"\n",
"os.makedirs(data_prep_src_dir, exist_ok=True)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"This script performs the simple task of splitting the data into train and test datasets. \n",
"\n",
"[MLFlow](https://mlflow.org/docs/latest/tracking.html) will be used to log the parameters and metrics during our pipeline run."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "def-main"
},
"outputs": [],
"source": [
"%%writefile {data_prep_src_dir}/data_prep.py\n",
"import os\n",
"import argparse\n",
"import pandas as pd\n",
"from sklearn.model_selection import train_test_split\n",
"import logging\n",
"import mlflow\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Main function of the script.\"\"\"\n",
"\n",
" # input and output arguments\n",
" parser = argparse.ArgumentParser()\n",
" parser.add_argument(\"--data\", type=str, help=\"path to input data\")\n",
" parser.add_argument(\"--test_train_ratio\", type=float, required=False, default=0.25)\n",
" parser.add_argument(\"--train_data\", type=str, help=\"path to train data\")\n",
" parser.add_argument(\"--test_data\", type=str, help=\"path to test data\")\n",
" args = parser.parse_args()\n",
"\n",
" # Start Logging\n",
" mlflow.start_run()\n",
"\n",
" print(\" \".join(f\"{k}={v}\" for k, v in vars(args).items()))\n",
"\n",
" print(\"input data:\", args.data)\n",
"\n",
" credit_df = pd.read_csv(args.data, header=1, index_col=0)\n",
"\n",
" mlflow.log_metric(\"num_samples\", credit_df.shape[0])\n",
" mlflow.log_metric(\"num_features\", credit_df.shape[1] - 1)\n",
"\n",
" credit_train_df, credit_test_df = train_test_split(\n",
" credit_df,\n",
" test_size=args.test_train_ratio,\n",
" )\n",
"\n",
" # output paths are mounted as folder, therefore, we are adding a filename to the path\n",
" credit_train_df.to_csv(os.path.join(args.train_data, \"data.csv\"), index=False)\n",
"\n",
" credit_test_df.to_csv(os.path.join(args.test_data, \"data.csv\"), index=False)\n",
"\n",
" # Stop Logging\n",
" mlflow.end_run()\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" main()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that you have a script that can perform the desired task, we can create an Azure ML Component from it. Azure ML support various types of components for performing ML tasks, such as running scripts, data transfer, etc.\n",
"\n",
"A component can be created by calling the component instantiators, or directly writing the defining yaml file. \n",
"\n",
"You'll use the general purpose **command** that can run command line actions. This command line action can be directly calling system commands or running a script. The inputs/outputs are accessible in the command via the `${{ ... }}` notation. For the second component of this tutorial you will use `yaml` definitions.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "data_prep_component"
},
"outputs": [],
"source": [
"from azure.ai.ml import command\n",
"from azure.ai.ml import Input, Output\n",
"\n",
"data_prep_component = command(\n",
" name=\"data_prep_credit_defaults\",\n",
" display_name=\"Data preparation for training\",\n",
" description=\"reads a .xl input, split the input to train and test\",\n",
" inputs={\n",
" \"data\": Input(type=\"uri_folder\"),\n",
" \"test_train_ratio\": Input(type=\"number\"),\n",
" },\n",
" outputs=dict(\n",
" train_data=Output(type=\"uri_folder\", mode=\"rw_mount\"),\n",
" test_data=Output(type=\"uri_folder\", mode=\"rw_mount\"),\n",
" ),\n",
" # The source folder of the component\n",
" code=data_prep_src_dir,\n",
" command=\"\"\"python data_prep.py \\\n",
" --data ${{inputs.data}} --test_train_ratio ${{inputs.test_train_ratio}} \\\n",
" --train_data ${{outputs.train_data}} --test_data ${{outputs.test_data}} \\\n",
" \"\"\",\n",
" environment=f\"{pipeline_job_env.name}:{pipeline_job_env.version}\",\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"Optionally, register the component in the workspace for future re-use. **command()** is a component builder, in order to fetch the component itself, we need to call the **.component** property from it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Now we register the component to the workspace\n",
"data_prep_component = ml_client.create_or_update(data_prep_component.component)\n",
"\n",
"# Create (register) the component in your workspace\n",
"print(\n",
" f\"Component {data_prep_component.name} with Version {data_prep_component.version} is registered\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create component 2: training (using yaml definition)\n",
"\n",
"The second component that you'll create will consume the training and test data, train a tree based model and return the output model. You'll use Azure ML logging capabilities to record and visualize the learning progress.\n",
"\n",
"You used the `command` class to create your first component. This time you'll use the yaml definition to define the second component. Each method has its own advantages. A yaml definition can actually be checked-in along the code, and would provide a readable history tracking. Also, the same yaml file can be used in the CLI for component deficnition. The programmatic method using `command` can be easier with built-in class documentation and code completion.\n",
"\n",
"\n",
"Create the directory for this component:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "train_src_dir"
},
"outputs": [],
"source": [
"import os\n",
"\n",
"train_src_dir = \"./components/train\"\n",
"os.makedirs(train_src_dir, exist_ok=True)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Create the training script in the directory:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "train.py"
},
"outputs": [],
"source": [
"%%writefile {train_src_dir}/train.py\n",
"import argparse\n",
"from sklearn.ensemble import GradientBoostingClassifier\n",
"from sklearn.metrics import classification_report\n",
"import os\n",
"import pandas as pd\n",
"import mlflow\n",
"\n",
"\n",
"def select_first_file(path):\n",
" \"\"\"Selects first file in folder, use under assumption there is only one file in folder\n",
" Args:\n",
" path (str): path to directory or file to choose\n",
" Returns:\n",
" str: full path of selected file\n",
" \"\"\"\n",
" files = os.listdir(path)\n",
" return os.path.join(path, files[0])\n",
"\n",
"\n",
"# Start Logging\n",
"mlflow.start_run()\n",
"\n",
"# enable autologging\n",
"mlflow.sklearn.autolog()\n",
"\n",
"os.makedirs(\"./outputs\", exist_ok=True)\n",
"\n",
"\n",
"def main():\n",
" \"\"\"Main function of the script.\"\"\"\n",
"\n",
" # input and output arguments\n",
" parser = argparse.ArgumentParser()\n",
" parser.add_argument(\"--train_data\", type=str, help=\"path to train data\")\n",
" parser.add_argument(\"--test_data\", type=str, help=\"path to test data\")\n",
" parser.add_argument(\"--n_estimators\", required=False, default=100, type=int)\n",
" parser.add_argument(\"--learning_rate\", required=False, default=0.1, type=float)\n",
" parser.add_argument(\"--registered_model_name\", type=str, help=\"model name\")\n",
" parser.add_argument(\"--model\", type=str, help=\"path to model file\")\n",
" args = parser.parse_args()\n",
"\n",
" # paths are mounted as folder, therefore, we are selecting the file from folder\n",
" train_df = pd.read_csv(select_first_file(args.train_data))\n",
"\n",
" # Extracting the label column\n",
" y_train = train_df.pop(\"default payment next month\")\n",
"\n",
" # convert the dataframe values to array\n",
" X_train = train_df.values\n",
"\n",
" # paths are mounted as folder, therefore, we are selecting the file from folder\n",
" test_df = pd.read_csv(select_first_file(args.test_data))\n",
"\n",
" # Extracting the label column\n",
" y_test = test_df.pop(\"default payment next month\")\n",
"\n",
" # convert the dataframe values to array\n",
" X_test = test_df.values\n",
"\n",
" print(f\"Training with data of shape {X_train.shape}\")\n",
"\n",
" clf = GradientBoostingClassifier(\n",
" n_estimators=args.n_estimators, learning_rate=args.learning_rate\n",
" )\n",
" clf.fit(X_train, y_train)\n",
"\n",
" y_pred = clf.predict(X_test)\n",
"\n",
" print(classification_report(y_test, y_pred))\n",
"\n",
" # Registering the model to the workspace\n",
" print(\"Registering the model via MLFlow\")\n",
" mlflow.sklearn.log_model(\n",
" sk_model=clf,\n",
" registered_model_name=args.registered_model_name,\n",
" artifact_path=args.registered_model_name,\n",
" )\n",
"\n",
" # Saving the model to a file\n",
" mlflow.sklearn.save_model(\n",
" sk_model=clf,\n",
" path=os.path.join(args.model, \"trained_model\"),\n",
" )\n",
"\n",
" # Stop Logging\n",
" mlflow.end_run()\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" main()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see in this training script, once the model is trained, the model file is saved and registered to the workspace. Now you can use the registered model in inferencing endpoints.\n",
"\n",
"\n",
"For the environment of this step, you'll use one of the built-in (curated) Azure ML environments. The tag `azureml`, tells the system to use look for the name in curated environments.\n",
"\n",
"First, create the *yaml* file describing the component:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "train.yml"
},
"outputs": [],
"source": [
"%%writefile {train_src_dir}/train.yml\n",
"# <component>\n",
"name: train_credit_defaults_model\n",
"display_name: Train Credit Defaults Model\n",
"# version: 1 # Not specifying a version will automatically update the version\n",
"type: command\n",
"inputs:\n",
" train_data: \n",
" type: uri_folder\n",
" test_data: \n",
" type: uri_folder\n",
" learning_rate:\n",
" type: number \n",
" registered_model_name:\n",
" type: string\n",
"outputs:\n",
" model:\n",
" type: uri_folder\n",
"code: .\n",
"environment:\n",
" # for this step, we'll use an AzureML curate environment\n",
" azureml://registries/azureml/environments/sklearn-1.5/labels/latest\n",
"command: >-\n",
" python train.py \n",
" --train_data ${{inputs.train_data}} \n",
" --test_data ${{inputs.test_data}} \n",
" --learning_rate ${{inputs.learning_rate}}\n",
" --registered_model_name ${{inputs.registered_model_name}} \n",
" --model ${{outputs.model}}\n",
"# </component>\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Once the `yaml` file and the script are ready, you can create your component using `load_component()`. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "train_component"
},
"outputs": [],
"source": [
"# importing the Component Package\n",
"from azure.ai.ml import load_component\n",
"\n",
"# Loading the component from the yml file\n",
"train_component = load_component(source=os.path.join(train_src_dir, \"train.yml\"))"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now create and register the component:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "update-train_component"
},
"outputs": [],
"source": [
"# Now we register the component to the workspace\n",
"train_component = ml_client.create_or_update(train_component)\n",
"\n",
"# Create (register) the component in your workspace\n",
"print(\n",
" f\"Component {train_component.name} with Version {train_component.version} is registered\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create the pipeline from components\n",
"\n",
"Now that both your components are defined and registered, you can start implementing the pipeline."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, you'll use *input data*, *split ratio* and *registered model name* as input variables. Then call the components and connect them via their inputs /outputs identifiers. The outputs of each step can be accessed via the `.outputs` property."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
}
},
"source": [
"The python functions returned by `load_component()` work as any regular python function that we'll use within a pipeline to call each step.\n",
"\n",
"To code the pipeline, we use a specific `@dsl.pipeline` decorator that identifies the Azure ML pipelines. In the decorator, we can specify the pipeline description and default resources like compute (serverless is used here) and storage. Like a python function, pipelines can have inputs, you can then create multiple instances of a single pipeline with different inputs.\n",
"\n",
"Here, we used *input data*, *split ratio* and *registered model name* as input variables. We then call the components and connect them via their inputs /outputs identifiers. The outputs of each step can be accessed via the `.outputs` property."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "pipeline"
},
"outputs": [],
"source": [
"# the dsl decorator tells the sdk that we are defining an Azure ML pipeline\n",
"from azure.ai.ml import dsl, Input, Output\n",
"\n",
"\n",
"@dsl.pipeline(\n",
" compute=\"serverless\",\n",
" description=\"E2E data_perp-train pipeline\",\n",
")\n",
"def credit_defaults_pipeline(\n",
" pipeline_job_data_input,\n",
" pipeline_job_test_train_ratio,\n",
" pipeline_job_learning_rate,\n",
" pipeline_job_registered_model_name,\n",
"):\n",
" # using data_prep_function like a python call with its own inputs\n",
" data_prep_job = data_prep_component(\n",
" data=pipeline_job_data_input,\n",
" test_train_ratio=pipeline_job_test_train_ratio,\n",
" )\n",
"\n",
" # using train_func like a python call with its own inputs\n",
" train_job = train_component(\n",
" train_data=data_prep_job.outputs.train_data, # note: using outputs from previous step\n",
" test_data=data_prep_job.outputs.test_data, # note: using outputs from previous step\n",
" learning_rate=pipeline_job_learning_rate, # note: using a pipeline input as parameter\n",
" registered_model_name=pipeline_job_registered_model_name,\n",
" )\n",
"\n",
" # a pipeline returns a dictionary of outputs\n",
" # keys will code for the pipeline output identifier\n",
" return {\n",
" \"pipeline_job_train_data\": data_prep_job.outputs.train_data,\n",
" \"pipeline_job_test_data\": data_prep_job.outputs.test_data,\n",
" }"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Now use your pipeline definition to instantiate a pipeline with your dataset, split rate of choice and the name you picked for your model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "registered_model_name"
},
"outputs": [],
"source": [
"registered_model_name = \"credit_defaults_model\"\n",
"\n",
"# Let's instantiate the pipeline with the parameters of our choice\n",
"pipeline = credit_defaults_pipeline(\n",
" pipeline_job_data_input=Input(type=\"uri_file\", path=credit_data.path),\n",
" pipeline_job_test_train_ratio=0.25,\n",
" pipeline_job_learning_rate=0.05,\n",
" pipeline_job_registered_model_name=registered_model_name,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Submit the job \n",
"\n",
"It's now time to submit the job to run in Azure ML. This time you'll use `create_or_update` on `ml_client.jobs`.\n",
"\n",
"Here you'll also pass an experiment name. An experiment is a container for all the iterations one does on a certain project. All the jobs submitted under the same experiment name would be listed next to each other in Azure ML studio.\n",
"\n",
"Once completed, the pipeline will register a model in your workspace as a result of training."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "returned_job"
},
"outputs": [],
"source": [
"import webbrowser\n",
"\n",
"# submit the pipeline job\n",
"pipeline_job = ml_client.jobs.create_or_update(\n",
" pipeline,\n",
" # Project's name\n",
" experiment_name=\"e2e_registered_components\",\n",
")\n",
"# open the pipeline in web browser\n",
"webbrowser.open(pipeline_job.studio_url)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"You can track the progress of your pipeline, by using the link generated in the cell above or in this notebook using the following code:\n",
"```python\n",
" ml_client.jobs.stream(pipeline_job.name)\n",
"```\n",
"\n",
"When you select on each component, you'll see more information about the results of that component. \n",
"There are two important parts to look for at this stage:\n",
"* `Outputs+logs` > `user_logs` > `std_log.txt`\n",
"This section shows the script run sdtout."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"* `Outputs+logs` > `Metric`\n",
"This section shows different logged metrics. In this example. mlflow `autologging`, has automatically logged the training metrics."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Deploy the model as an online endpoint\n",
"\n",
"Now deploy your machine learning model as a web service in the Azure cloud, an [`online endpoint`](https://docs.microsoft.com/azure/machine-learning/concept-endpoints).\n",
"\n",
"To deploy a machine learning service, you usually need:\n",
"\n",
"* The model assets (filed, metadata) that you want to deploy. You've already registered these assets in your training component.\n",
"* Some code to run as a service. The code executes the model on a given input request. This entry script receives data submitted to a deployed web service and passes it to the model, then returns the model's response to the client. The script is specific to your model. The entry script must understand the data that the model expects and returns. When using a MLFlow model, as in this tutorial, this script is automatically created for you. Samples of scoring scripts can be found [here](https://github.com/Azure/azureml-examples/tree/sdk-preview/sdk/endpoints/online).\n",
"\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create a new online endpoint\n",
"\n",
"Now that you have a registered model and an inference script, it's time to create your online endpoint. The endpoint name needs to be unique in the entire Azure region. For this tutorial, you'll create a unique name using [`UUID`](https://en.wikipedia.org/wiki/Universally_unique_identifier#:~:text=A%20universally%20unique%20identifier%20(UUID,%2C%20for%20practical%20purposes%2C%20unique.)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "online_endpoint_name"
},
"outputs": [],
"source": [
"import uuid\n",
"\n",
"# Creating a unique name for the endpoint\n",
"online_endpoint_name = \"credit-endpoint-\" + str(uuid.uuid4())[:8]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "endpoint"
},
"outputs": [],
"source": [
"from azure.ai.ml.entities import (\n",
" ManagedOnlineEndpoint,\n",
" ManagedOnlineDeployment,\n",
" Model,\n",
" Environment,\n",
")\n",
"\n",
"# create an online endpoint\n",
"endpoint = ManagedOnlineEndpoint(\n",
" name=online_endpoint_name,\n",
" description=\"this is an online endpoint\",\n",
" auth_mode=\"key\",\n",
" tags={\n",
" \"training_dataset\": \"credit_defaults\",\n",
" \"model_type\": \"sklearn.GradientBoostingClassifier\",\n",
" },\n",
")\n",
"\n",
"endpoint_result = ml_client.begin_create_or_update(endpoint).result()\n",
"\n",
"print(\n",
" f\"Endpint {endpoint_result.name} provisioning state: {endpoint_result.provisioning_state}\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Once you've created an endpoint, you can retrieve it as below:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "update-endpoint"
},
"outputs": [],
"source": [
"endpoint = ml_client.online_endpoints.get(name=online_endpoint_name)\n",
"\n",
"print(\n",
" f'Endpint \"{endpoint.name}\" with provisioning state \"{endpoint.provisioning_state}\" is retrieved'\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Deploy the model to the endpoint\n",
"\n",
"Once the endpoint is created, deploy the model with the entry script. Each endpoint can have multiple deployments and direct traffic to these deployments can be specified using rules. Here you'll create a single deployment that handles 100% of the incoming traffic. We have chosen a color name for the deployment, for example, *blue*, *green*, *red* deployments, which is arbitrary.\n",
"\n",
"You can check the *Models* page on the Azure ML studio, to identify the latest version of your registered model. Alternatively, the code below will retrieve the latest version number for you to use."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "latest_model_version"
},
"outputs": [],
"source": [
"# Let's pick the latest version of the model\n",
"latest_model_version = max(\n",
" [int(m.version) for m in ml_client.models.list(name=registered_model_name)]\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"Deploy the latest version of the model. \n",
"\n",
"> [!NOTE]\n",
"> Expect this deployment to take approximately 6 to 8 minutes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "model"
},
"outputs": [],
"source": [
"# picking the model to deploy. Here we use the latest version of our registered model\n",
"model = ml_client.models.get(name=registered_model_name, version=latest_model_version)\n",
"\n",
"\n",
"# create an online deployment.\n",
"blue_deployment = ManagedOnlineDeployment(\n",
" name=\"blue\",\n",
" endpoint_name=online_endpoint_name,\n",
" model=model,\n",
" instance_type=\"Standard_F4s_v2\",\n",
" instance_count=1,\n",
")\n",
"\n",
"blue_deployment_results = ml_client.online_deployments.begin_create_or_update(\n",
" blue_deployment\n",
").result()\n",
"\n",
"print(\n",
" f\"Deployment {blue_deployment_results.name} provisioning state: {blue_deployment_results.provisioning_state}\"\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"### Test with a sample query\n",
"\n",
"Now that the model is deployed to the endpoint, you can run inference with it.\n",
"\n",
"Create a sample request file following the design expected in the run method in the score script."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "sample-request.json"
},
"outputs": [],
"source": [
"deploy_dir = \"./deploy\"\n",
"os.makedirs(deploy_dir, exist_ok=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"name": "write-sample-request"
},
"outputs": [],
"source": [
"%%writefile {deploy_dir}/sample-request.json\n",
"{\n",
" \"input_data\": {\n",
" \"columns\": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],\n",
" \"index\": [0, 1],\n",
" \"data\": [\n",
" [20000,2,2,1,24,2,2,-1,-1,-2,-2,3913,3102,689,0,0,0,0,689,0,0,0,0],\n",
" [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 10, 9, 8]\n",
" ]\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"Python"
],
"id": ""
},
"name": "ml_client.online_endpoints.invoke"
},
"outputs": [],
"source": [
"# test the blue deployment with some sample data\n",
"ml_client.online_endpoints.invoke(\n",
" endpoint_name=online_endpoint_name,\n",
" request_file=\"./deploy/sample-request.json\",\n",
" deployment_name=\"blue\",\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Clean up resources\n",
"\n",
"If you're not going to use the endpoint, delete it to stop using the resource. Make sure no other deployments are using an endpoint before you delete it.\n",
"\n",
"\n",
"> [!NOTE]\n",
"> Expect this step to take approximately 6 to 8 minutes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"attributes": {
"classes": [
"python "
],
"id": ""
},
"name": "ml_client.online_endpoints.begin_delete"
},
"outputs": [],
"source": [
"ml_client.online_endpoints.begin_delete(name=online_endpoint_name)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next Steps\n",
"\n",
"Learn more about [Azure ML logging](https://github.com/Azure/azureml-examples/blob/sdk-preview/notebooks/mlflow/mlflow-v1-comparison.ipynb)."
]
}
],
"metadata": {
"celltoolbar": "Edit Metadata",
"description": {
"description": "Create production ML pipelines with Python SDK v2 in a Jupyter notebook"
},
"kernelspec": {
"display_name": "Python 3",
"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.9.17"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
}
},
"nbformat": 4,
"nbformat_minor": 1
}