{ "cells": [ { "cell_type": "markdown", "id": "chronic-ethiopia", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Introduction to Azure ML SDK\n", "> Presentation notebook from 'Azure Saturday, Hamburg 2021' event. \n", "- toc: true \n", "- badges: true\n", "- comments: true\n", "- categories: [azureml, sdk, mlops, python, dataops]\n", "- hide: false" ] }, { "cell_type": "markdown", "id": "automatic-marina", "metadata": {}, "source": [ "## Azure Saturday Hamburg, Feb 20, 2021\n", "\n", "### Sandeep Pawar \n", "\n", "#### Twitter : @PawarBI | LinkedIn: in/sanpawar | Blog : PawarBI.com" ] }, { "cell_type": "markdown", "id": "eligible-finding", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "overall-kinase", "metadata": {}, "source": [ "*Note:* This notebook was presented at [Azure Saturday Hamburg](https://www.eventleaf.com/azuresaturdayhamburg)" ] }, { "cell_type": "markdown", "id": "stock-supervision", "metadata": {}, "source": [ "# Agenda\n", "\n", "#### - Machine Learning Process - *As Advertised*\n", " - Motivation\n", " - Demo\n", "\n", "#### - Glimpse of *Real* Machine Learning Process - *Using Azure ML*\n", " - What is Azure ML Service\n", " - Classes in Azure ML SDK\n", " - Workspace\n", " - DataOps using Datastore & Datasets\n", " - Experiments\n", " - Model Deployment\n" ] }, { "cell_type": "markdown", "id": "integrated-planning", "metadata": {}, "source": [ "# Motivation\n", "\n", "Before I talk about Azure ML, I would like to first provide some motivation for why we want to learn and use Azure ML. \n", "\n", "The goal of this presentation is not to show how to create machine learning models but rather, how to use Azure ML to operationalize the machine learning models at scale. I will create an example machine learning model but really the focus is understanding the common 'design patterns' in Azure ML. If you are familiar with theory of machine lerning, this presentation/example notebook will help you understand the often neglected MLOps part of ML. If you do not have experience with creating ML models or are new to Python/Azure, focus on the logical process rather than the exact mechanics. You can always revisit this example notebook or Microsoft Learn but hopefully from this session you will understand, at a high-level, how to use Azure ML to deploy ML models in production." ] }, { "cell_type": "markdown", "id": "talented-enzyme", "metadata": {}, "source": [ "## Machine Learning Process - *As Advertised*\n", "\n", "Let's start with a typical machine learning process. You will see plenty of tutorials on how to create machine learning models. Just type in \"Machine learning process\" in Google and you will see below results. Most of these describe the process broadly as follows: \n", "\n", " - Obtain data\n", " - Clean data\n", " - EDA\n", " - Preprocess the data\n", " - Build model(s)\n", " - Validate the model\n", " - Serialize the model \n" ] }, { "cell_type": "markdown", "id": "honey-dispatch", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "according-transformation", "metadata": {}, "source": [ "Let's follow this process to build a model. \n", "\n", "#### Data \n", "I will use a dataset from UCI Machine Learning reporsitory called [\"Bank Marketing Data Set\"](https://archive.ics.uci.edu/ml/datasets/Bank+Marketing). You may have seen this in many tutorials. I chose this dataset because the focus of this presentation is learning Azure ML so I wanted to pick something that most can understand and I recently gave a presentation on [Machine Learning Model Interpretability](https://youtu.be/0ocVtXU8o1I) using the same dataset. In case you are interested in that topic, you will already be familiar with this dataset after this presentation. \n", "\n", "This dataset has 20 features, mix of numerical and categorical features, and a target label with \"Yes/No\" values. It's a binary classification problem and the goal is to predict if a customer will sign up for a bank term deposit. Feel free to explore the dataset on your own before proceeding. " ] }, { "cell_type": "code", "execution_count": 16, "id": "spread-botswana", "metadata": {}, "outputs": [], "source": [ "#collapse-hide\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.model_selection import train_test_split\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "\n", "from sklearn.ensemble import RandomForestClassifier\n", "\n", "from sklearn.metrics import roc_auc_score, accuracy_score\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "from sklearn.preprocessing import OneHotEncoder, FunctionTransformer, StandardScaler\n", "from sklearn.compose import ColumnTransformer\n", "from sklearn.pipeline import Pipeline\n", "\n", "from sklearn import metrics\n", "from interpret import show\n", "from interpret.perf import ROC\n", "\n", "from sklearn import metrics\n", "\n", "import seaborn as sns" ] }, { "cell_type": "code", "execution_count": 17, "id": "funded-joyce", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.2.1\n" ] } ], "source": [ "print(pd.__version__)\n" ] }, { "cell_type": "markdown", "id": "impressed-layout", "metadata": {}, "source": [ "#### Obtain data\n", "\n", "There are 32950 observations and 20 features. Each observation describes a potential customer with their details such as job, age, martial status etc. and also the macro economic conditions (employment rate, bond rate etc. when that customer was last contacted. " ] }, { "cell_type": "code", "execution_count": 18, "id": "universal-danish", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(32950, 21)\n" ] }, { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>age</th>\n", " <th>job</th>\n", " <th>marital</th>\n", " <th>education</th>\n", " <th>default</th>\n", " <th>housing</th>\n", " <th>loan</th>\n", " <th>contact</th>\n", " <th>month</th>\n", " <th>day_of_week</th>\n", " <th>...</th>\n", " <th>campaign</th>\n", " <th>pdays</th>\n", " <th>previous</th>\n", " <th>poutcome</th>\n", " <th>emp.var.rate</th>\n", " <th>cons.price.idx</th>\n", " <th>cons.conf.idx</th>\n", " <th>euribor3m</th>\n", " <th>nr.employed</th>\n", " <th>y</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>57</td>\n", " <td>technician</td>\n", " <td>married</td>\n", " <td>high.school</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>yes</td>\n", " <td>cellular</td>\n", " <td>may</td>\n", " <td>mon</td>\n", " <td>...</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>1</td>\n", " <td>failure</td>\n", " <td>-1.8</td>\n", " <td>92.893</td>\n", " <td>-46.2</td>\n", " <td>1.299</td>\n", " <td>5099.1</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>55</td>\n", " <td>unknown</td>\n", " <td>married</td>\n", " <td>unknown</td>\n", " <td>unknown</td>\n", " <td>yes</td>\n", " <td>no</td>\n", " <td>telephone</td>\n", " <td>may</td>\n", " <td>thu</td>\n", " <td>...</td>\n", " <td>2</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.1</td>\n", " <td>93.994</td>\n", " <td>-36.4</td>\n", " <td>4.860</td>\n", " <td>5191.0</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>33</td>\n", " <td>blue-collar</td>\n", " <td>married</td>\n", " <td>basic.9y</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>cellular</td>\n", " <td>may</td>\n", " <td>fri</td>\n", " <td>...</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>1</td>\n", " <td>failure</td>\n", " <td>-1.8</td>\n", " <td>92.893</td>\n", " <td>-46.2</td>\n", " <td>1.313</td>\n", " <td>5099.1</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>3</th>\n", " <td>36</td>\n", " <td>admin.</td>\n", " <td>married</td>\n", " <td>high.school</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>telephone</td>\n", " <td>jun</td>\n", " <td>fri</td>\n", " <td>...</td>\n", " <td>4</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.4</td>\n", " <td>94.465</td>\n", " <td>-41.8</td>\n", " <td>4.967</td>\n", " <td>5228.1</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>4</th>\n", " <td>27</td>\n", " <td>housemaid</td>\n", " <td>married</td>\n", " <td>high.school</td>\n", " <td>no</td>\n", " <td>yes</td>\n", " <td>no</td>\n", " <td>cellular</td>\n", " <td>jul</td>\n", " <td>fri</td>\n", " <td>...</td>\n", " <td>2</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.4</td>\n", " <td>93.918</td>\n", " <td>-42.7</td>\n", " <td>4.963</td>\n", " <td>5228.1</td>\n", " <td>no</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "<p>5 rows × 21 columns</p>\n", "</div>" ], "text/plain": [ " age job marital education default housing loan contact \\\n", "0 57 technician married high.school no no yes cellular \n", "1 55 unknown married unknown unknown yes no telephone \n", "2 33 blue-collar married basic.9y no no no cellular \n", "3 36 admin. married high.school no no no telephone \n", "4 27 housemaid married high.school no yes no cellular \n", "\n", " month day_of_week ... campaign pdays previous poutcome emp.var.rate \\\n", "0 may mon ... 1 999 1 failure -1.8 \n", "1 may thu ... 2 999 0 nonexistent 1.1 \n", "2 may fri ... 1 999 1 failure -1.8 \n", "3 jun fri ... 4 999 0 nonexistent 1.4 \n", "4 jul fri ... 2 999 0 nonexistent 1.4 \n", "\n", " cons.price.idx cons.conf.idx euribor3m nr.employed y \n", "0 92.893 -46.2 1.299 5099.1 no \n", "1 93.994 -36.4 4.860 5191.0 no \n", "2 92.893 -46.2 1.313 5099.1 no \n", "3 94.465 -41.8 4.967 5228.1 no \n", "4 93.918 -42.7 4.963 5228.1 no \n", "\n", "[5 rows x 21 columns]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#https://archive.ics.uci.edu/ml/datasets/Bank+Marketing\n", "\n", "path = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\"\n", "data = pd.read_csv(path)\n", "\n", "print(data.shape)\n", "data.head()" ] }, { "cell_type": "markdown", "id": "conservative-blackjack", "metadata": {}, "source": [ "#### Clean the data\n", "\n", "The column names contain some columns with periods ('.') in them. We will clean the column names, change dtype of some columns to categoricals and also binarize the target to [1,0] instead of yes/no. " ] }, { "cell_type": "code", "execution_count": 19, "id": "registered-reply", "metadata": {}, "outputs": [], "source": [ "#Define functions to clean the data\n", "\n", "def clean_col_names(df):\n", " \n", " df.columns = [col.replace('.','_') for col in df.columns]\n", " \n", " return df\n", "\n", "def clean_dtype(df):\n", " \n", " cat_cols = ['job','marital','education','default','housing','loan','contact','month','day_of_week','poutcome']\n", " \n", " for col in cat_cols:\n", " df.loc[:,col] = df[col].astype('category')\n", " \n", " return df\n", "\n", "def binarize_y(y):\n", " y = (y=='yes').astype(int)\n", " \n", " return y\n", "\n" ] }, { "cell_type": "code", "execution_count": 20, "id": "wireless-binding", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>age</th>\n", " <th>job</th>\n", " <th>marital</th>\n", " <th>education</th>\n", " <th>default</th>\n", " <th>housing</th>\n", " <th>loan</th>\n", " <th>contact</th>\n", " <th>month</th>\n", " <th>day_of_week</th>\n", " <th>...</th>\n", " <th>campaign</th>\n", " <th>pdays</th>\n", " <th>previous</th>\n", " <th>poutcome</th>\n", " <th>emp_var_rate</th>\n", " <th>cons_price_idx</th>\n", " <th>cons_conf_idx</th>\n", " <th>euribor3m</th>\n", " <th>nr_employed</th>\n", " <th>y</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>0</th>\n", " <td>57</td>\n", " <td>technician</td>\n", " <td>married</td>\n", " <td>high.school</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>yes</td>\n", " <td>cellular</td>\n", " <td>may</td>\n", " <td>mon</td>\n", " <td>...</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>1</td>\n", " <td>failure</td>\n", " <td>-1.8</td>\n", " <td>92.893</td>\n", " <td>-46.2</td>\n", " <td>1.299</td>\n", " <td>5099.1</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>1</th>\n", " <td>55</td>\n", " <td>unknown</td>\n", " <td>married</td>\n", " <td>unknown</td>\n", " <td>unknown</td>\n", " <td>yes</td>\n", " <td>no</td>\n", " <td>telephone</td>\n", " <td>may</td>\n", " <td>thu</td>\n", " <td>...</td>\n", " <td>2</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.1</td>\n", " <td>93.994</td>\n", " <td>-36.4</td>\n", " <td>4.860</td>\n", " <td>5191.0</td>\n", " <td>no</td>\n", " </tr>\n", " <tr>\n", " <th>2</th>\n", " <td>33</td>\n", " <td>blue-collar</td>\n", " <td>married</td>\n", " <td>basic.9y</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>cellular</td>\n", " <td>may</td>\n", " <td>fri</td>\n", " <td>...</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>1</td>\n", " <td>failure</td>\n", " <td>-1.8</td>\n", " <td>92.893</td>\n", " <td>-46.2</td>\n", " <td>1.313</td>\n", " <td>5099.1</td>\n", " <td>no</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "<p>3 rows × 21 columns</p>\n", "</div>" ], "text/plain": [ " age job marital education default housing loan contact \\\n", "0 57 technician married high.school no no yes cellular \n", "1 55 unknown married unknown unknown yes no telephone \n", "2 33 blue-collar married basic.9y no no no cellular \n", "\n", " month day_of_week ... campaign pdays previous poutcome emp_var_rate \\\n", "0 may mon ... 1 999 1 failure -1.8 \n", "1 may thu ... 2 999 0 nonexistent 1.1 \n", "2 may fri ... 1 999 1 failure -1.8 \n", "\n", " cons_price_idx cons_conf_idx euribor3m nr_employed y \n", "0 92.893 -46.2 1.299 5099.1 no \n", "1 93.994 -36.4 4.860 5191.0 no \n", "2 92.893 -46.2 1.313 5099.1 no \n", "\n", "[3 rows x 21 columns]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Copy and Clean the data\n", "\n", "df = data.copy()\n", "df = clean_col_names(df)\n", "df = clean_dtype(df)\n", "\n", "\n", "df.head(3)" ] }, { "cell_type": "markdown", "id": "royal-easter", "metadata": {}, "source": [ "##### Split the data" ] }, { "cell_type": "markdown", "id": "forced-world", "metadata": {}, "source": [ "Before conducting the exploratory data analysis (EDA), we will split the data into train and test. EDA should *always* be performed on the training data only to prevent information leakage, i.e overfitting. Test set should be used for final model evaluation.\n", "\n", "Quick note - I have dropped the `duration` column because based on my analysis explained [here](https://youtu.be/0ocVtXU8o1I), this feature leaks information so I am dropping it. Watch the presentation if you would like to understand how creating interpretable models can help avoid such data leakage. " ] }, { "cell_type": "code", "execution_count": 21, "id": "chicken-aluminum", "metadata": {}, "outputs": [], "source": [ "X = df.drop(['y','duration'], axis=1)\n", "y = df.y\n", "\n", "y = binarize_y(y)" ] }, { "cell_type": "code", "execution_count": 22, "id": "shaped-settlement", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training set: 26360 \n", "Test set: 6590\n" ] } ], "source": [ "cat_cols = ['job','marital','education','default','housing','loan','contact','month','day_of_week','poutcome']\n", "num_cols = list(set(X.columns)-set(cat_cols))\n", "\n", "\n", "x1,x2, y1,y2 = train_test_split(X,y, stratify=y, train_size=0.80, shuffle=True, random_state = 0)\n", "\n", "print(\"Training set:\",len(x1),\"\\nTest set:\",len(x2))" ] }, { "cell_type": "markdown", "id": "ahead-block", "metadata": {}, "source": [ "Training set has 26,360 observations and test has 6590 observations. The 80/20 split is arbitrary at this point. You can create [learning curves](https://www.dataquest.io/blog/learning-curves-machine-learning/) to figure out how much data you need for training. It will also depend on the algorithm you are using. " ] }, { "cell_type": "code", "execution_count": 8, "id": "cardiovascular-centre", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<div>\n", "<style scoped>\n", " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", "\n", " .dataframe tbody tr th {\n", " vertical-align: top;\n", " }\n", "\n", " .dataframe thead th {\n", " text-align: right;\n", " }\n", "</style>\n", "<table border=\"1\" class=\"dataframe\">\n", " <thead>\n", " <tr style=\"text-align: right;\">\n", " <th></th>\n", " <th>age</th>\n", " <th>job</th>\n", " <th>marital</th>\n", " <th>education</th>\n", " <th>default</th>\n", " <th>housing</th>\n", " <th>loan</th>\n", " <th>contact</th>\n", " <th>month</th>\n", " <th>day_of_week</th>\n", " <th>campaign</th>\n", " <th>pdays</th>\n", " <th>previous</th>\n", " <th>poutcome</th>\n", " <th>emp_var_rate</th>\n", " <th>cons_price_idx</th>\n", " <th>cons_conf_idx</th>\n", " <th>euribor3m</th>\n", " <th>nr_employed</th>\n", " </tr>\n", " </thead>\n", " <tbody>\n", " <tr>\n", " <th>23612</th>\n", " <td>55</td>\n", " <td>technician</td>\n", " <td>divorced</td>\n", " <td>professional.course</td>\n", " <td>unknown</td>\n", " <td>yes</td>\n", " <td>no</td>\n", " <td>cellular</td>\n", " <td>aug</td>\n", " <td>tue</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.4</td>\n", " <td>93.444</td>\n", " <td>-36.1</td>\n", " <td>4.965</td>\n", " <td>5228.1</td>\n", " </tr>\n", " <tr>\n", " <th>32560</th>\n", " <td>40</td>\n", " <td>admin.</td>\n", " <td>single</td>\n", " <td>university.degree</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>telephone</td>\n", " <td>jun</td>\n", " <td>mon</td>\n", " <td>2</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.4</td>\n", " <td>94.465</td>\n", " <td>-41.8</td>\n", " <td>4.958</td>\n", " <td>5228.1</td>\n", " </tr>\n", " <tr>\n", " <th>15168</th>\n", " <td>51</td>\n", " <td>technician</td>\n", " <td>divorced</td>\n", " <td>unknown</td>\n", " <td>unknown</td>\n", " <td>no</td>\n", " <td>no</td>\n", " <td>telephone</td>\n", " <td>jul</td>\n", " <td>fri</td>\n", " <td>1</td>\n", " <td>999</td>\n", " <td>0</td>\n", " <td>nonexistent</td>\n", " <td>1.4</td>\n", " <td>93.918</td>\n", " <td>-42.7</td>\n", " <td>4.962</td>\n", " <td>5228.1</td>\n", " </tr>\n", " </tbody>\n", "</table>\n", "</div>" ], "text/plain": [ " age job marital education default housing loan \\\n", "23612 55 technician divorced professional.course unknown yes no \n", "32560 40 admin. single university.degree no no no \n", "15168 51 technician divorced unknown unknown no no \n", "\n", " contact month day_of_week campaign pdays previous poutcome \\\n", "23612 cellular aug tue 1 999 0 nonexistent \n", "32560 telephone jun mon 2 999 0 nonexistent \n", "15168 telephone jul fri 1 999 0 nonexistent \n", "\n", " emp_var_rate cons_price_idx cons_conf_idx euribor3m nr_employed \n", "23612 1.4 93.444 -36.1 4.965 5228.1 \n", "32560 1.4 94.465 -41.8 4.958 5228.1 \n", "15168 1.4 93.918 -42.7 4.962 5228.1 " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x1.head(3)" ] }, { "cell_type": "markdown", "id": "thirty-intent", "metadata": {}, "source": [ "#### Exploratory Data Analysis\n", "\n", "The data is clean for our demonstration purposes. Before building the model, you should invest significant time in understanding the data first. This is definitely the most important part of building a reliable machine learning model. In this demo, I am going to skip this step and leave it up to you. " ] }, { "cell_type": "code", "execution_count": 9, "id": "faced-knitting", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 720x720 with 20 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "#For demonstration, using only few numerical columns and 1000 random observations\n", "sns.pairplot(x1[['emp_var_rate','cons_price_idx','cons_conf_idx','euribor3m']].sample(1000),diag_kind='kde');" ] }, { "cell_type": "code", "execution_count": 10, "id": "owned-participation", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0.887936\n", "1 0.112064\n", "Name: y, dtype: float64\n" ] }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "print((y1.value_counts(normalize=True)))\n", "(y1.value_counts(normalize=True)).plot(kind='bar');" ] }, { "cell_type": "markdown", "id": "satisfied-affiliate", "metadata": {}, "source": [ "In the bar chart above, 1 is 'yes' and 0 is 'no'. As you can see, ~89% customers did not sign up for the term deposit and 11% did. Thus, the target labels are not balanced (i.e not ~50/50%). This will affect the model performance metric we choose. For imbalanced dataset, using `accuracy` as the metric can lead to incorrect results. `ROC-AUC` is often used in such situations. This is a big topic so for now we just need to know that based on the EDA we see that the target is imbalanced and we will have to keep it in mind when building the model." ] }, { "cell_type": "markdown", "id": "loving-reflection", "metadata": {}, "source": [ "#### Preprocess the data\n" ] }, { "cell_type": "markdown", "id": "oriented-curtis", "metadata": {}, "source": [ "In the previous steps we split the data and now we are ready to build the ML pipeline. We build the preprocessing pipeline for catgorical and numerical columns using `Pipeline()` from sklearn.\n", "\n", "Categorical columns will be encoded using `OneHotEncoder` and numerical features will be scaled using `StandardScaler`. Standard scaler will bring all numerical features to mean = 0 and std dev = 1. There are various ways of encoding and scaling but for demo purposes we will stick with this. \n", "\n", "Also note that not all ML algorithms need encoding and scaling. Linear methods such as Logistic Regression do while tree-based algorithms (Random Forest, GBMs) don't. We will still preprocess the data so we can use the same pipeline for different algirithms, if needed. \n" ] }, { "cell_type": "code", "execution_count": 11, "id": "opponent-glucose", "metadata": {}, "outputs": [], "source": [ "#Get column index for each of the columns types \n", "\n", "cat_nums = [list(x1.columns).index(col) for col in cat_cols]\n", "num_nums = [list(x1.columns).index(col) for col in num_cols]" ] }, { "cell_type": "code", "execution_count": 12, "id": "bright-causing", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4, 5, 6, 7, 8, 9, 13]\n", "[14, 0, 15, 12, 11, 10, 17, 16, 18]\n" ] } ], "source": [ "print(cat_nums)\n", "print(num_nums)" ] }, { "cell_type": "markdown", "id": "rational-teens", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": 13, "id": "killing-diagram", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9, 13]),\n", " ('num',\n", " Pipeline(steps=[('std', StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17, 16, 18])])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#One hot encode\n", "cat_ohe_step = ('ohe', OneHotEncoder(sparse=False,\n", " handle_unknown='ignore'))\n", "#Build Pipeline\n", "cat_pipe = Pipeline([cat_ohe_step])\n", "num_pipe = Pipeline([('std', StandardScaler())])\n", "transformers = [\n", " ('cat', cat_pipe, cat_nums),\n", " ('num', num_pipe, num_nums)\n", "]\n", "ct = ColumnTransformer(transformers=transformers)\n", "\n", "ct" ] }, { "cell_type": "markdown", "id": "administrative-appreciation", "metadata": {}, "source": [ "Visualize the preprocessing steps:" ] }, { "cell_type": "code", "execution_count": 14, "id": "likely-action", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<style>div.sk-top-container {color: black;background-color: white;}div.sk-toggleable {background-color: white;}label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.2em 0.3em;box-sizing: border-box;text-align: center;}div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}div.sk-estimator {font-family: monospace;background-color: #f0f8ff;margin: 0.25em 0.25em;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;}div.sk-estimator:hover {background-color: #d4ebff;}div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 2em;bottom: 0;left: 50%;}div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;}div.sk-item {z-index: 1;}div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;}div.sk-parallel-item {display: flex;flex-direction: column;position: relative;background-color: white;}div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}div.sk-parallel-item:only-child::after {width: 0;}div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0.2em;box-sizing: border-box;padding-bottom: 0.1em;background-color: white;position: relative;}div.sk-label label {font-family: monospace;font-weight: bold;background-color: white;display: inline-block;line-height: 1.2em;}div.sk-label-container {position: relative;z-index: 2;text-align: center;}div.sk-container {display: inline-block;position: relative;}</style><div class=\"sk-top-container\"><div class=\"sk-container\"><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"7b7a6ed5-6823-4e8a-be28-eb59184036bd\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"7b7a6ed5-6823-4e8a-be28-eb59184036bd\">ColumnTransformer</label><div class=\"sk-toggleable__content\"><pre>ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9, 13]),\n", " ('num',\n", " Pipeline(steps=[('std', StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17, 16, 18])])</pre></div></div></div><div class=\"sk-parallel\"><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"55ed1997-5c52-41ee-8ad8-b87d3c039d98\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"55ed1997-5c52-41ee-8ad8-b87d3c039d98\">cat</label><div class=\"sk-toggleable__content\"><pre>[1, 2, 3, 4, 5, 6, 7, 8, 9, 13]</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"bc5c0c0f-465f-42d4-b6f2-2aa75fe7cd03\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"bc5c0c0f-465f-42d4-b6f2-2aa75fe7cd03\">OneHotEncoder</label><div class=\"sk-toggleable__content\"><pre>OneHotEncoder(handle_unknown='ignore', sparse=False)</pre></div></div></div></div></div></div></div></div><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"5fe22998-4ef9-41cf-94ac-dca017fee752\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"5fe22998-4ef9-41cf-94ac-dca017fee752\">num</label><div class=\"sk-toggleable__content\"><pre>[14, 0, 15, 12, 11, 10, 17, 16, 18]</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"40d28cda-2103-4150-b156-020c7e3906c3\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"40d28cda-2103-4150-b156-020c7e3906c3\">StandardScaler</label><div class=\"sk-toggleable__content\"><pre>StandardScaler()</pre></div></div></div></div></div></div></div></div></div></div></div></div>" ], "text/plain": [ "ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9, 13]),\n", " ('num',\n", " Pipeline(steps=[('std', StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17, 16, 18])])" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Visualize the Preprocessing steps\n", "\n", "from sklearn import set_config\n", "\n", "set_config(display='diagram') \n", "\n", "ct" ] }, { "cell_type": "markdown", "id": "seven-furniture", "metadata": {}, "source": [ "#### Build the model\n", "\n", "I am going to use **Random forest** algorithm. Random Forest often gives a good baseline performance right out of the box in most scenarios without overfitting. Also, Random Forest has a nice feature - ['Out of Bag' (OOB)](https://en.wikipedia.org/wiki/Out-of-bag_error) score. It will help us estimate model performance over multiple bootstrapped samples thus providing a good proxy for cross-validation performance. I am using OOB here, just to save model building time. In a real project, you will carefully construct a CV scheme. \n", "\n", "We use the above pipeline of transformations with the Random Forest estimator with default parameters. Set the `oob_score=True` to get the OOB score. Also, `class_weight` is set to `balanced` to mitigate class imbalance.\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "proud-allah", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<style>div.sk-top-container {color: black;background-color: white;}div.sk-toggleable {background-color: white;}label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.2em 0.3em;box-sizing: border-box;text-align: center;}div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}div.sk-estimator {font-family: monospace;background-color: #f0f8ff;margin: 0.25em 0.25em;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;}div.sk-estimator:hover {background-color: #d4ebff;}div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 2em;bottom: 0;left: 50%;}div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;}div.sk-item {z-index: 1;}div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;}div.sk-parallel-item {display: flex;flex-direction: column;position: relative;background-color: white;}div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}div.sk-parallel-item:only-child::after {width: 0;}div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0.2em;box-sizing: border-box;padding-bottom: 0.1em;background-color: white;position: relative;}div.sk-label label {font-family: monospace;font-weight: bold;background-color: white;display: inline-block;line-height: 1.2em;}div.sk-label-container {position: relative;z-index: 2;text-align: center;}div.sk-container {display: inline-block;position: relative;}</style><div class=\"sk-top-container\"><div class=\"sk-container\"><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"c8ab40bc-f1d0-45df-9f3b-12b6f547731a\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"c8ab40bc-f1d0-45df-9f3b-12b6f547731a\">Pipeline</label><div class=\"sk-toggleable__content\"><pre>Pipeline(steps=[('ct',\n", " ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9,\n", " 13]),\n", " ('num',\n", " Pipeline(steps=[('std',\n", " StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17,\n", " 16, 18])])),\n", " ('rf',\n", " RandomForestClassifier(class_weight='balanced', oob_score=True,\n", " random_state=0))])</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"5136d72f-3d1d-43d5-b38b-15eb55eab08c\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"5136d72f-3d1d-43d5-b38b-15eb55eab08c\">ct: ColumnTransformer</label><div class=\"sk-toggleable__content\"><pre>ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9, 13]),\n", " ('num',\n", " Pipeline(steps=[('std', StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17, 16, 18])])</pre></div></div></div><div class=\"sk-parallel\"><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"36b1ebcb-9464-414a-833c-f10631a8a999\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"36b1ebcb-9464-414a-833c-f10631a8a999\">cat</label><div class=\"sk-toggleable__content\"><pre>[1, 2, 3, 4, 5, 6, 7, 8, 9, 13]</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"d5682005-de0d-4c82-a808-00b37b7dd47d\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"d5682005-de0d-4c82-a808-00b37b7dd47d\">OneHotEncoder</label><div class=\"sk-toggleable__content\"><pre>OneHotEncoder(handle_unknown='ignore', sparse=False)</pre></div></div></div></div></div></div></div></div><div class=\"sk-parallel-item\"><div class=\"sk-item\"><div class=\"sk-label-container\"><div class=\"sk-label sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"aefca277-ab0c-4ff2-839e-370ff9882d3c\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"aefca277-ab0c-4ff2-839e-370ff9882d3c\">num</label><div class=\"sk-toggleable__content\"><pre>[14, 0, 15, 12, 11, 10, 17, 16, 18]</pre></div></div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"59f8ff65-058e-4989-875b-7b9f6854f98a\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"59f8ff65-058e-4989-875b-7b9f6854f98a\">StandardScaler</label><div class=\"sk-toggleable__content\"><pre>StandardScaler()</pre></div></div></div></div></div></div></div></div></div></div><div class=\"sk-item\"><div class=\"sk-estimator sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"d4533a61-61f2-419d-9808-c4541fa2123c\" type=\"checkbox\" ><label class=\"sk-toggleable__label\" for=\"d4533a61-61f2-419d-9808-c4541fa2123c\">RandomForestClassifier</label><div class=\"sk-toggleable__content\"><pre>RandomForestClassifier(class_weight='balanced', oob_score=True, random_state=0)</pre></div></div></div></div></div></div></div>" ], "text/plain": [ "Pipeline(steps=[('ct',\n", " ColumnTransformer(transformers=[('cat',\n", " Pipeline(steps=[('ohe',\n", " OneHotEncoder(handle_unknown='ignore',\n", " sparse=False))]),\n", " [1, 2, 3, 4, 5, 6, 7, 8, 9,\n", " 13]),\n", " ('num',\n", " Pipeline(steps=[('std',\n", " StandardScaler())]),\n", " [14, 0, 15, 12, 11, 10, 17,\n", " 16, 18])])),\n", " ('rf',\n", " RandomForestClassifier(class_weight='balanced', oob_score=True,\n", " random_state=0))])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pipe = Pipeline([\n", " ('ct', ct),\n", " ('rf', RandomForestClassifier(oob_score=True,\n", " random_state=0, \n", " class_weight = 'balanced')),\n", " ])\n", "\n", "#Fit the model\n", "pipe.fit(x1,y1)\n", "\n" ] }, { "cell_type": "markdown", "id": "virtual-mirror", "metadata": {}, "source": [ "#### Validate the model" ] }, { "cell_type": "code", "execution_count": 49, "id": "rubber-guatemala", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "OOB AUC is: 0.63\n", "OOB AUC on test set is: 0.63\n" ] } ], "source": [ "#Access the RF estimator from the pipeline\n", "rf = pipe[-1]\n", "\n", "# OOB, by default, gives Accuracy score. This is a slightly imbalanced dataset,\n", "# so we will calculate AUC on OOB predictions\n", "\n", "oob_pred = np.argmax(rf.oob_decision_function_,axis=1)\n", "auc1 = metrics.roc_auc_score(y1, oob_pred)\n", "\n", "print(\"OOB AUC is: \",np.round(auc1,2))\n", "print(\"OOB AUC on test set is: \",np.round(roc_auc_score(y2, pipe.predict(x2)),2))\n", "\n" ] }, { "cell_type": "markdown", "id": "fantastic-chase", "metadata": {}, "source": [ "Although the AUC is not very high, OOB gave an excellent estimation of the test score. We are happy with the model and it's ready to be used for future predictions." ] }, { "cell_type": "markdown", "id": "romance-vacation", "metadata": {}, "source": [ "#### Serialize the Model" ] }, { "cell_type": "markdown", "id": "committed-encounter", "metadata": {}, "source": [ "Serialize the model using `joblib`" ] }, { "cell_type": "code", "execution_count": 50, "id": "terminal-insider", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['baseline_rf.pkl']" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import joblib\n", "\n", "joblib.dump(pipe, 'baseline_rf.pkl')" ] }, { "cell_type": "markdown", "id": "nominated-brave", "metadata": {}, "source": [ "Test the pickle file on the test set again to make sure it's working as expected." ] }, { "cell_type": "code", "execution_count": 51, "id": "boxed-killing", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 0, 1, ..., 0, 0, 0])" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "joblib.load('baseline_rf.pkl').predict(x2)" ] }, { "cell_type": "markdown", "id": "designed-northwest", "metadata": {}, "source": [ "**Success !**" ] }, { "cell_type": "markdown", "id": "beneficial-review", "metadata": {}, "source": [ "We followed the entire process that's laid out in the 'typical' machine learning process. But is that how it works in real life? \n", "\n", "The answer is - A Resounding **NO**. \n", "\n", "This is in fact just the fraction of the actual process. In reality it's very convoluted, non-linear process with multiple stakeholders/teams involved in creating the final model. You have business stakeholders defining the goals and business objectives, IT/Data Engineers who work on extracting/staging the data, Data scientist creating the models, software engineers integrating it with the product and business intelligence developers consuming the predictions in a dashboard. All these teams collaborate with each other, going through many iterations before finalizng a model.\n", "\n", "\n", "\n", "\n", "Ref: https://chapeau.freevariable.com/static/202010/mlops-tube.png" ] }, { "cell_type": "markdown", "id": "affecting-section", "metadata": {}, "source": [ "Things that are missing from this 'Typical' process are:\n", " \n", " - Multiple stakeholders and collaborators. Identifying business objectives and tying it to model metric\n", " - Computational resources needed to run the ML models. If you are working on a dataset with millions of rows or a DNN model, you will very likely need GPUs \n", " - Experimentation design: algorithms, preprocessing steps, feature selection, feature engineering. You will create 1000s of models before identifying few model candidates that meet the business objectives.\n", " - Experiment tracking: You will need to efficiently track these 1000s of ML experiments to understand the patterns\n", " - Data versioning: You will work with several different versions of the data. By the time you arrive at the final model, the data used for training & evaluating that model will be very different from what you started with. You or your colleagues will need to use a different version of that data for som eother project. \n", " - Track model artifacts: Each model will have its dependency requirements, input/output schema, hyper parameters\n", " - Package the model: Containerize the model with the dependendencies \n", " - Deploy & monitor: scale, data security, performance monitoring, data drift, model interpretability" ] }, { "cell_type": "markdown", "id": "periodic-prime", "metadata": {}, "source": [ "This is where **Azure Machine Learning Service** helps! It's a fully managed cloud service that lets you:\n", " - Work in collaboration while giving control on data security and resources\n", " - Scale the compute targets as needed\n", " - Track data and model versions\n", " - Experiment with thousands of models and keep track of them\n", " - Deploy the models based on requirements (real-time, batch, IoT) \n", " - Monitor in production\n", " - Trace the model back to data and model artificats\n", " - DevOps" ] }, { "cell_type": "markdown", "id": "occupational-nature", "metadata": {}, "source": [ "Azure ML is a **Fully managed MLOPS Platform** that will help you manage the machine learning process based on project requirements." ] }, { "cell_type": "markdown", "id": "executive-infection", "metadata": {}, "source": [ "# Azure ML Service" ] }, { "cell_type": "markdown", "id": "historical-membership", "metadata": {}, "source": [ "Hopefully above example gave you reasons to learn and understand why MLOps is important. With Scikit-learn you can create the models *but* it won't help you put those models in production. We will now see how to operationalize this model using Azure ML. " ] }, { "cell_type": "markdown", "id": "enabling-contrast", "metadata": {}, "source": [ "### Tour of Azure ML Service" ] }, { "cell_type": "markdown", "id": "maritime-indonesian", "metadata": {}, "source": [ "Create a free Azure account by visiting the Azure page: https://azure.microsoft.com/en-us/services/machine-learning/\n", "The account is free and you get $200 credit for the first 30 days. Create a 'Pay-As-You-Go' subscription so you will incur costs for only the services you use. Be careful of creating compute resources. Shut them down when you are not using them to avoid a costly surprises. If you are a student, you may get some additional benefits. " ] }, { "cell_type": "markdown", "id": "million-prompt", "metadata": {}, "source": [ "##### Create Pay As You Go subscription\n", "\n", "" ] }, { "cell_type": "markdown", "id": "great-opportunity", "metadata": {}, "source": [ "#### Create Azure ML Resource\n", "\n", "[From MS Docs: ](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal#what-is-a-resource-group)A resource group is a container that holds related resources for an Azure solution. The resource group can include all the resources for the solution, or only those resources that you want to manage as a group. You decide how you want to allocate resources to resource groups based on what makes the most sense for your organization. Generally, add resources that share the same lifecycle to the same resource group so you can easily deploy, update, and delete them as a group.\n", "\n", "The resource group stores metadata about the resources. Therefore, when you specify a location for the resource group, you are specifying where that metadata is stored. For compliance reasons, you may need to ensure that your data is stored in a particular region." ] }, { "cell_type": "markdown", "id": "departmental-favor", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "right-disposition", "metadata": {}, "source": [ "#### What's in Azure Resources ?\n", "\n", "\n", "Ref: https://miro.medium.com/max/700/0*2B9p3J0A0efCL2J0.jpg" ] }, { "cell_type": "markdown", "id": "promotional-synthetic", "metadata": {}, "source": [ "You can access and manage these resources in Azure ML studio using GUI. Some of these resources can also be managed using Azure ML SDK. As you create machine learning models, you will need to access these resources based on project requirements. The Python sdk will allow you to access them in your notebook on the fly. If the resources don't exist, you can create them programmatically. \n", "\n", "###### Architecture\n", "" ] }, { "cell_type": "markdown", "id": "official-payday", "metadata": {}, "source": [ "### Azure ML Python SDK" ] }, { "cell_type": "markdown", "id": "exciting-orange", "metadata": {}, "source": [ "I highly recommend creating a virtual enviroment that's specific to Azure ML projects to manage dependencies, especially for Azure Auto ML. Azure AutoML dependencies are often hard to resolve.\n", "\n", "\n", "\n", "Create a virtual enviroment (e.g `evenv`) and install Azure ML : `pip install --upgrade azureml-sdk[notebooks,automl]`.\n", "You can read more [here](https://docs.microsoft.com/en-us/python/api/overview/azure/ml/install?view=azure-ml-py)" ] }, { "cell_type": "code", "execution_count": 52, "id": "modular-balance", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I am working with, Azure ML sdk ver: 1.20.0\n" ] } ], "source": [ "print(\"I am working with, Azure ML sdk ver: \",azureml.core.VERSION)" ] }, { "cell_type": "markdown", "id": "juvenile-integration", "metadata": {}, "source": [ "##### Azure ML Classes\n", "\n", "You will use these classes to operationalize the model in Azure ML. This list is defnitely not exchaustive, but 80% of the time you will be working with these classes in Azure ML. I encourage you to read the Microsoft documentation for each of these. " ] }, { "cell_type": "markdown", "id": "included-password", "metadata": {}, "source": [ "\n", "|Feature | Description | Class |\n", "|--|--|--|\n", "| Workspace |Foundational resource in the cloud to manage experiments, models |`Workspace(..)` \n", "| Compute Instance |Fully managed development environment (DVSM) |`ComputeInstance(..)`\n", "| Compute Cluster |Fully managed multi-node, scaleable compute |`ComputeTarget(..)`\n", "| Datastore |Azure Data storage |`Datastore(..)`\n", "| Dataset |Abstracted File or Tabular data stored in Datastore |`Dataset(..)`\n", "| Experiment |ML Experiment folder |`Experiment(..)`\n", "| Run |An instance of an experiment with artifacts |`Run(..)`\n", "| Log |Log metrics, artifacts related to run |`Environment(..)`\n", "| Environment |Package environment and dependencies |`.log(..)`\n", "| ScriptRunConfig |Configuration to run experiments |`ScriptRunConfig(..)`\n", "|Model |Manage, register, deploy models in the cloud |`Model(..)`\n", "|Webservice |Containerized packages for deployment, Endpoints |`Webservice(..)`\n" ] }, { "cell_type": "markdown", "id": "educated-richardson", "metadata": {}, "source": [ "### Workspace" ] }, { "cell_type": "markdown", "id": "bigger-transformation", "metadata": {}, "source": [ "You may have different workspaces for different teams, projects etc. In fact, it's recommended to create different resource groups so all the project data, metadata, artifacts remain in that workspace. Especially if you are just trying Azure ML so you can just delete that resource without incurring any charges for any resources in the future. To create or access a workspace, we use `Workspace()` class. The easiest way is to download the `config.json` file from the resource group to your working directory. It has all the tenant, subscription information to connect to that workspace. You will be prompted to authenticate your credentials." ] }, { "cell_type": "markdown", "id": "eight-season", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 53, "id": "supported-substitute", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Connecting to the Workspace....\n", "Workspacename: demows , \n", "Workspace location: centralus\n" ] } ], "source": [ "from azureml.core import Workspace\n", "\n", "print(\"Connecting to the Workspace....\", end=\"\",sep='\\n')\n", "\n", "ws = Workspace.from_config()\n", "\n", "print(\"\\nWorkspacename:\",ws.name, \", \\nWorkspace location:\", ws.location)" ] }, { "cell_type": "markdown", "id": "solid-graduation", "metadata": {}, "source": [ "We are connected to the workspace, now we can access the assets and artifacts in this workspace" ] }, { "cell_type": "markdown", "id": "tracked-notion", "metadata": {}, "source": [ "### Datastore" ] }, { "cell_type": "markdown", "id": "seventh-editor", "metadata": {}, "source": [ "When the ML resource was created, Azure automatically created and attached a Blob storage to this workspace. That's the power of managed resources ! You can always attach other Blob, ADLSg2 storage accounts as needed. Let's access this default datastore." ] }, { "cell_type": "code", "execution_count": 54, "id": "general-information", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "workspacefilestore AzureFile\n", "workspaceblobstore AzureBlob\n" ] } ], "source": [ "# List all datastores registered in the current workspace\n", "datastores = ws.datastores\n", "for name, datastore in datastores.items():\n", " print(name, datastore.datastore_type)" ] }, { "cell_type": "markdown", "id": "reflected-panama", "metadata": {}, "source": [ "We have two blog storage accounts in this workspace. Let's conenct to the default datastore." ] }, { "cell_type": "code", "execution_count": 55, "id": "protecting-pearl", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{\n", " \"name\": \"workspaceblobstore\",\n", " \"container_name\": \"azureml-blobstore-dca32a5a-2be2-43c0-a924-9f9d9b7c7789\",\n", " \"account_name\": \"demows8142312183\",\n", " \"protocol\": \"https\",\n", " \"endpoint\": \"core.windows.net\"\n", "}" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "datastore = ws.get_default_datastore()\n", "datastore" ] }, { "cell_type": "markdown", "id": "affected-agreement", "metadata": {}, "source": [ "Remember this is the 'datastore'. We haven't accessed any datasets in this datastore yet. Your data engineering team, for example, can do ETL using ADF, Synapse Analytics, Power Query etc. for you and register a dataset in this datastore.You can also add other datasets to this datastore. We will register the current bank marketing data to this datastore. Once registered, your other team members can access this dataset by just pointing to that dataset. " ] }, { "cell_type": "markdown", "id": "brave-burden", "metadata": {}, "source": [ "### Dataset" ] }, { "cell_type": "markdown", "id": "editorial-cartoon", "metadata": {}, "source": [ "Sometimes you may find it easier to use the GUI in the Azure ML studio to register a dataset. The GUI is more interactive and can also generate dataset profile. \n", "\n", "Although it may not seem like a big deal, but being able to register, track, version the datasets seamlessly is one of the most important steps in creating reliable machine lerning models. In the model creation process, you will generate different versions of the data. By versioning and tracking, you will be able to trace which dataset was used for the training the deployed model and thus debug the models in production. \n", "\n", "Don't take my work for it. See what renowned ML researchers, Andrew Ng and Francois Chollet (creator of Tensfor Flow), say about importance of data collection, versioning. [Ref](https://twitter.com/AndrewYNg/status/1353814743190913024)" ] }, { "cell_type": "markdown", "id": "intelligent-surfing", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "speaking-mumbai", "metadata": {}, "source": [ "Following DataOps ptactices will pay rich dividends and avoid many headaches when yu have to debug and re-train models in the future.\n", "\n", "The dataset you create in Azure ML is actually an abstraction/reference to the stored data and its metadata [(ref)](https://docs.microsoft.com/en-us/azure/machine-learning/concept-data#reference-data-in-storage-with-datasets). The datasets are lazily evaluated, which means:\n", " - No additional storage cost\n", " - Data versioning\n", " - No risk of changing original data\n", " " ] }, { "cell_type": "markdown", "id": "advanced-louisiana", "metadata": {}, "source": [ "##### Register a dataset" ] }, { "cell_type": "code", "execution_count": 56, "id": "polar-double", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registering dataset to the cloud...\n", "\n", "Data registration successful\n", "\n", " { 'bankmarketing': DatasetRegistration(id='5845bcb9-fde0-4419-bba8-55b9e8296da8', name='bankmarketing', version=1, description='This is the original data', tags={})}\n" ] } ], "source": [ "from azureml.core import Dataset\n", "from azureml.data.dataset_factory import DataType\n", "\n", "#First create a dataset object\n", "ds1 = Dataset.Tabular.from_delimited_files(path=path)\n", "\n", "#Register this dataset to the datastore\n", "print(\"Registering dataset to the cloud...\", end=\"\")\n", "\n", "\n", "ds1 = ds1.register(workspace = ws,\n", " name= \"bankmarketing\",\n", " description = \"This is the original data\")\n", "\n", "print(\"\\n\\nData registration successful\\n\\n\", Dataset.get_all(ws) )" ] }, { "cell_type": "markdown", "id": "adaptive-kernel", "metadata": {}, "source": [ "We have registered the original dataset to the default datastore." ] }, { "cell_type": "markdown", "id": "practical-drain", "metadata": {}, "source": [ "##### Create a new version of the dataset\n", "\n", "If you noticed in the training data used above, I dropped the `duration` column. We will create another version of the same dataset `ds1` by dropping the `duration` column and call it `ds2`." ] }, { "cell_type": "code", "execution_count": 57, "id": "closed-input", "metadata": {}, "outputs": [], "source": [ "ds2 = ds1.drop_columns('duration')\n", "\n", "ds2 = ds2.register(workspace = ws,\n", " name= \"bankmarketing\",\n", " description = \"Duration column dropped\",\n", " create_new_version=True)\n" ] }, { "cell_type": "code", "execution_count": 58, "id": "occupied-sustainability", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'bankmarketing': DatasetRegistration(id='9980d47e-df35-45ec-972a-b68a8dd64bf6', name='bankmarketing', version=2, description='Duration column dropped', tags={})}" ] }, "execution_count": 58, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Dataset.get_all(ws)" ] }, { "cell_type": "markdown", "id": "professional-editing", "metadata": {}, "source": [ "Note that we still have only 1 dataset in the Datastore. We just replaced the first dataset with the new version. Notice the version number `version=2` above. We can retrieve any version when needed. We have only 1 file in the datastore not 2. This is how, by creating abstraction, we are able to save storage cost. If needed, you can add properties, tags, description to the dataset for future reference. In fact it's a good practice to do so for tracebility.\n", "\n", "By default when you reference a dataset, it will always pull the latest version, unless specified." ] }, { "cell_type": "markdown", "id": "looking-temperature", "metadata": {}, "source": [ "We are still not done with the dataset. For training, we cleaned the data and split it into train/test. Those also need to be registered to the datastore. We don't have to but that's good DataOps/MLOps practice. Also, anytime you create a cross-validation folds for your final model training/validation, register those in the datastore too for reproducibility. To keep things simple, I am going to upload the train and test data created above to the datastore as one single csv file. Also note that you can directly register a pandas dataframe as a dataset. " ] }, { "cell_type": "code", "execution_count": 59, "id": "artificial-aspect", "metadata": {}, "outputs": [], "source": [ "#Create one single file with training and testing data\n", "#Add a column to label which data is train and test\n", "#This way we can keep the data in one single file\n", "# Besure sure to drop the 'data' columns before training and testing.\n", "\n", "train = x1.copy()\n", "train['target'] = y1\n", "train['data'] = 'train'\n", "\n", "test = x2.copy()\n", "test['target'] = y2\n", "test['data'] = 'test'\n", "\n", "train_test_data = train.append(test)\n", "\n" ] }, { "cell_type": "code", "execution_count": 60, "id": "coated-macintosh", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Method register_pandas_dataframe: This is an experimental method, and may change at any time.<br/>For more information, see https://aka.ms/azuremlexperimental.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Validating arguments.\n", "Arguments validated.\n", "Successfully obtained datastore reference and path.\n", "Uploading file to bank_train_test/e9360b10-63b9-4d6b-be50-53add1436e99/\n", "Successfully uploaded file to datastore.\n", "Creating and registering a new dataset.\n", "Successfully created and registered a new dataset.\n" ] } ], "source": [ "# Register the pandas dataframe as a dataset \n", "# Add tags for traceability\n", "\n", "from azureml.data.dataset_factory import TabularDatasetFactory\n", "\n", "ds3 = (TabularDatasetFactory\n", " .register_pandas_dataframe(\n", " train_test_data,\n", " target=(datastore,'bank_train_test'),\n", " name='bank_train_test',\n", " tags = {'Author':'Sandeep','Project':'Bank Marketing'},\n", " show_progress=True)\n", " )" ] }, { "cell_type": "code", "execution_count": 61, "id": "athletic-mitchell", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bank_train_test\n", "bankmarketing\n" ] } ], "source": [ "for dataset in Dataset.get_all(ws):\n", " print(dataset)" ] }, { "cell_type": "markdown", "id": "objective-engineering", "metadata": {}, "source": [ "Just for illustration purposes, if we want to retrieve a dataset by name, we can use the `get_by_name()` method. We can also see the `id` (i.e unique id) for the dataset. We will log this as an artifact during model building so we can trace the exact train/test used for future reference." ] }, { "cell_type": "code", "execution_count": 62, "id": "promising-founder", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{\n", " \"source\": [\n", " \"('workspaceblobstore', 'bank_train_test/e9360b10-63b9-4d6b-be50-53add1436e99/')\"\n", " ],\n", " \"definition\": [\n", " \"GetDatastoreFiles\",\n", " \"ReadParquetFile\",\n", " \"DropColumns\"\n", " ],\n", " \"registration\": {\n", " \"id\": \"7b81a6c0-1e72-4948-86bb-ddac0e4e5d77\",\n", " \"name\": \"bank_train_test\",\n", " \"version\": 1,\n", " \"tags\": {\n", " \"Author\": \"Sandeep\",\n", " \"Project\": \"Bank Marketing\"\n", " },\n", " \"workspace\": \"Workspace.create(name='demows', subscription_id='4cedc5dd-e3ad-468d-bf66-32e31bdb9148', resource_group='1-f4dcfa62-playground-sandbox')\"\n", " }\n", "}" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Dataset.get_by_name(ws, 'bank_train_test')" ] }, { "cell_type": "code", "execution_count": 64, "id": "minute-complexity", "metadata": {}, "outputs": [], "source": [ "ds_uid = '7b81a6c0-1e72-4948-86bb-ddac0e4e5d77'" ] }, { "cell_type": "markdown", "id": "special-publicity", "metadata": {}, "source": [ "Datastore has two datasets now which can be accessed anytime or versioned." ] }, { "cell_type": "markdown", "id": "quick-robinson", "metadata": {}, "source": [ "### Compute " ] }, { "cell_type": "markdown", "id": "afraid-thong", "metadata": {}, "source": [ "We can train the model locally and deploy it to the cloud. But if you want to scale-up the process by parallelizing model training, you can use the compute cluster. There two types of compute:\n", "\n", " - Compute Instance: This is like a managed VM with R,Python, Jupyter installed. You can use it for remote training but can also be accessed from the Studio for development. \n", " \n", " - Compute Cluster: This is a scalable multi-node compute, meaning if your training requires lot of compute power (e.g. 12 machines with 24 cores each) you can push the training to the compute cluster to do that. This can also be used for batch-inferencing.\n", " \n", "For example purposes, I will show how to create it but won't use it. Compute is expensive. Companies often create compute quota to limit cost and use remote compute for hyperparameter tuning or large jobs. Note that if you are using Azure ML pipelines, you have to use Compute instance/cluster and local training is not available.\n", "\n", "I generally prefer creating compute using GUI because you can see the cost of each compute option. " ] }, { "cell_type": "code", "execution_count": 65, "id": "indie-terrace", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DS12V2 exists already\n", "\n", "Running\n" ] } ], "source": [ "from azureml.core.compute import ComputeTarget, AmlCompute\n", "\n", "\n", "compute_name = \"DS12V2\"\n", "\n", "try:\n", " vm = ComputeTarget(ws, compute_name)\n", " print(f\"{compute_name} exists already\")\n", "except:\n", " compute_config = AmlCompute.provisioning_configuration(vm_size=\"Standard_D2_V2\", max_nodes=4)\n", " vm = ComputeTarget.create(ws, compute_name, compute_config)\n", " \n", "vm.wait_for_completion(show_output=True)" ] }, { "cell_type": "markdown", "id": "driving-globe", "metadata": {}, "source": [ "### Experiment" ] }, { "cell_type": "markdown", "id": "changing-processor", "metadata": {}, "source": [ "This is the heart of machine learning and where all the magic happens. When you are working on a machine learning project, it's rarely a linear process as we discussed above. You try many different algorithms, debug them, understand how they work, try different preprocessing steps, feature engineering, data augmentation etc,, which means you will end up creating thousands of models per project. To keep track of all these experimental runs, Azure ML provides the `Experiment` class. \n", "\n", "Think of `Experiment()` as a big giant folder where you save the model runs and the artifacts associated with that experiment. At the end of your experiment, you will see how each model performed based on selected metric and choose the right model for your project. The steps you will follow for each experimental run:\n", "\n", " - Create Experiment object\n", " - Start run\n", " - Log metrics\n", " - Get run/experiment details\n", " \n", "Just for demonstration purposes, I will create a `Demo Experiment` and log values `1,2,3` for metric called `demo_metric`. " ] }, { "cell_type": "code", "execution_count": 66, "id": "wooden-malaysia", "metadata": {}, "outputs": [ { "data": { "text/html": [ "<table style=\"width:100%\"><tr><th>Name</th><th>Workspace</th><th>Report Page</th><th>Docs Page</th></tr><tr><td>Demo_Experiment</td><td>demows</td><td><a href=\"https://ml.azure.com/experiments/Demo_Experiment?wsid=/subscriptions/4cedc5dd-e3ad-468d-bf66-32e31bdb9148/resourcegroups/1-f4dcfa62-playground-sandbox/workspaces/demows\" target=\"_blank\" rel=\"noopener\">Link to Azure Machine Learning studio</a></td><td><a href=\"https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.experiment.Experiment?view=azure-ml-py\" target=\"_blank\" rel=\"noopener\">Link to Documentation</a></td></tr></table>" ], "text/plain": [ "Experiment(Name: Demo_Experiment,\n", "Workspace: demows)" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from azureml.core import Experiment\n", "\n", "exp1 = Experiment(workspace=ws, name=\"Demo_Experiment\")\n", "\n", "exp1" ] }, { "cell_type": "markdown", "id": "arranged-fifth", "metadata": {}, "source": [ "If you click on the above link, it will take you directly to the Azure ML Studio Experiment page. We will created the Experiment, i.e folder. Now, we *run* some experiments" ] }, { "cell_type": "code", "execution_count": 67, "id": "binding-freight", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "8ab071769b804bb1a45b352ce3865140", "version_major": 2, "version_minor": 0 }, "text/plain": [ "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/Demo_Experiment/runs/e41963a9-ce60-411b-93e8-30849ac12ed2?wsid=/subscriptions/4cedc5dd-e3ad-468d-bf66-32e31bdb9148/resourcegroups/1-f4dcfa62-playground-sandbox/workspaces/demows\", \"run_id\": \"e41963a9-ce60-411b-93e8-30849ac12ed2\", \"run_properties\": {\"run_id\": \"e41963a9-ce60-411b-93e8-30849ac12ed2\", \"created_utc\": \"2021-02-22T17:30:19.558499Z\", \"properties\": {\"ContentSnapshotId\": \"3b0e7ceb-338e-4d0d-bb10-d1bde356c31f\"}, \"tags\": {}, \"end_time_utc\": \"2021-02-22T17:30:33.00177Z\", \"status\": \"Completed\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:00:13\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"demo_metric\", \"run_id\": \"e41963a9-ce60-411b-93e8-30849ac12ed2\", \"categories\": [0, 1, 2], \"series\": [{\"data\": [1, 2, 3]}]}], \"run_logs\": \"\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.20.0\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#Start run\n", "from azureml.widgets import RunDetails\n", "\n", "\n", "demo_run = exp1.start_logging()\n", "\n", "#Start Logging\n", "demo_run.log('demo_metric' , 1)\n", "demo_run.log('demo_metric' , 2)\n", "demo_run.log('demo_metric' , 3)\n", "\n", "#Stop logging\n", "\n", "demo_run.complete()\n", "\n", "RunDetails(demo_run).show()\n" ] }, { "cell_type": "markdown", "id": "convenient-september", "metadata": {}, "source": [ "Remember to use `run.complete()` to stop the run. A better and easier way is to use `with` as follows. When the run is complete, it will be completed automatically.\n", "\n", "For the bank marketing project, we created a random forest model using default hyper params. To demonstrate how create experiments, we will train four RF models by changing the `max_depth` parameter. When `max_depth` is None, it's just a stump of a tree. As we grow the depth, features are split and will identify non-linear patterns in the data. We will try `max_depth` = [None, 5,7,9]. In a real project, you will perform hyperparameter optimization using RandomSearch, Baysian Optimization using SKLearn, HyperOpt, HyperDrive etc. \n" ] }, { "cell_type": "code", "execution_count": 68, "id": "fallen-canberra", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "max_depth: None , oob_auc: 0.626\n", "max_depth: 5 , oob_auc: 0.74\n", "max_depth: 7 , oob_auc: 0.746\n", "max_depth: 9 , oob_auc: 0.746\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d00ac55f06b545b4bbca1b55e851d358", "version_major": 2, "version_minor": 0 }, "text/plain": [ "_UserRunWidget(widget_settings={'childWidgetDisplay': 'popup', 'send_telemetry': False, 'log_level': 'INFO', '…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/aml.mini.widget.v1": "{\"status\": \"Completed\", \"workbench_run_details_uri\": \"https://ml.azure.com/experiments/Bank_Marketing/runs/feee8fa9-f7f0-40ca-8766-163f88def905?wsid=/subscriptions/4cedc5dd-e3ad-468d-bf66-32e31bdb9148/resourcegroups/1-f4dcfa62-playground-sandbox/workspaces/demows\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"run_properties\": {\"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"created_utc\": \"2021-02-22T17:31:15.164474Z\", \"properties\": {\"ContentSnapshotId\": \"c560339c-c187-45a7-b3d6-2bb9c8932ce1\"}, \"tags\": {}, \"end_time_utc\": \"2021-02-22T17:31:22.63437Z\", \"status\": \"Completed\", \"log_files\": {}, \"log_groups\": [], \"run_duration\": \"0:00:07\"}, \"child_runs\": [], \"children_metrics\": {}, \"run_metrics\": [{\"name\": \"Model\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [\"Random Forest\"]}]}, {\"name\": \"Dataset\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [\"7b81a6c0-1e72-4948-86bb-ddac0e4e5d77\"]}]}, {\"name\": \"max_depth\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [9]}]}, {\"name\": \"input_columns\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], \"series\": [{\"data\": [\"age\", \"job\", \"marital\", \"education\", \"default\", \"housing\", \"loan\", \"contact\", \"month\", \"day_of_week\", \"campaign\", \"pdays\", \"previous\", \"poutcome\", \"emp_var_rate\", \"cons_price_idx\", \"cons_conf_idx\", \"euribor3m\", \"nr_employed\"]}]}, {\"name\": \"oob_score\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [\"True\"]}]}, {\"name\": \"class_weight\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [\"balanced\"]}]}, {\"name\": \"oob_auc\", \"run_id\": \"feee8fa9-f7f0-40ca-8766-163f88def905\", \"categories\": [0], \"series\": [{\"data\": [0.7460066717843007]}]}], \"run_logs\": \"\\nRun is completed.\", \"graph\": {}, \"widget_settings\": {\"childWidgetDisplay\": \"popup\", \"send_telemetry\": false, \"log_level\": \"INFO\", \"sdk_version\": \"1.20.0\"}, \"loading\": false}" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#Create another experiment called bank\n", "from azureml.core import Run \n", "\n", "#Define new experiment\n", "bank = Experiment(workspace=ws, name=\"Bank_Marketing\", )\n", "\n", "#Define Hyperparameter to tune\n", "max_depth=[None,5,7,9] \n", " \n", "#Run the experiment\n", "\n", "for depth in max_depth:\n", " \n", " with bank.start_logging() as run: #snapshot only the snapshot directory snapshot_directory = 'snapshot'\n", "\n", " #Log max_depth\n", " run.log('Model', 'Random Forest')\n", " run.log('Dataset', ds_uid)\n", " run.log('max_depth', int(0 if depth is None else depth))\n", " run.log_list(\"input_columns\", list(x1.columns))\n", " \n", " #train the pipeline\n", " pipe2 = Pipeline([\n", " ('ct', ct),\n", " ('rf', RandomForestClassifier(oob_score=True,\n", " random_state=0, \n", " class_weight = 'balanced', \n", " max_depth = depth )),\n", " ])\n", "\n", " pipe2.fit(x1,y1)\n", " \n", "\n", " rf2 = pipe2[-1]\n", "\n", " #Log model details\n", " run.log('oob_score', 'True')\n", " run.log('class_weight', 'balanced')\n", "\n", "\n", " oob_pred2 = np.argmax(rf2.oob_decision_function_,axis=1)\n", " auc2 = metrics.roc_auc_score(y1, oob_pred2)\n", " \n", " #Log metrics\n", " run.log('oob_auc', auc2)\n", " \n", " print(\"max_depth: \",depth,\" , oob_auc: \", np.round(auc2,3))\n", " \n", "RunDetails(run).show()\n" ] }, { "cell_type": "markdown", "id": "saved-nursing", "metadata": {}, "source": [ "By increasing the max_depth, AUC increased from 62% to 74% ! \n", "\n", "In the experiment above, we logged the model class, dataset used, hyper parameter, input columns, AUC score etc. After the experiment is complete, you can visit the Studio to see the output and/or interact with the model artifacts. \n", "\n", "Note that by default, when you create an experiment, Azure ML will take a snapshot of the working folder. See below. Depending on your needs this is a good/bad thing. You may not want to snapshot all the files and folder. You can specify an `amlingnore` or `gitignore` file to indicate which files/folders to ignore. Another option is to specify which directory to snapshot. For example, above I specified `start_logging(snapshot_directory = 'snapshot')` to snapshot the `snapshot` folder. This helps reproducibility. You can save data, yaml, config files etc so you or your colleagues can reproduce the results months later. The maximum snapshot limit is 300MB. If your directory exceeds that the run will fail. You can increase the limit but you will incur storage costs. Also, directories `./output` and `./logs` are special. They will always be automatically uploaded as snapshot.\n", "\n", "I recommending 'snapshotting' only the required model artifacts and specifying which folder to snapshot.\n", "\n" ] }, { "cell_type": "markdown", "id": "foreign-institute", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "dress-joint", "metadata": {}, "source": [ "### Model Packaging - (Registration & Deployment)" ] }, { "cell_type": "markdown", "id": "grand-syria", "metadata": {}, "source": [ "We ran some experiments with `max_depth` hyperparameters and found that using `max_depth` = [5,7,9] will improve the results significantly. Let's use 'one-standard error' rule [(Ref: ESL, pp61)](https://web.stanford.edu/~hastie/ElemStatLearn//printings/ESLII_print12_toc.pdf) to pick a parsomonious model. We will pick `max_depth=5` for create a pickle file and deploy it in service.\n", "\n", "Model accuracy is not the only metric, in fact it shouldn't be, to select a model. Focus should be on selecting simple, parsimonious models that are interpretable & explainable. Watch my interpretability presentation for more details. For now, we will assume this is the right model for us. " ] }, { "cell_type": "code", "execution_count": 69, "id": "persistent-lindsay", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['bank_model.pkl']" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "final_model = Pipeline([\n", " ('ct', ct),\n", " ('rf', RandomForestClassifier(oob_score=True,\n", " random_state=0, \n", " class_weight = 'balanced',\n", " max_depth = 5)),\n", " ])\n", "final_model.fit(x1,y1)\n", "\n", "joblib.dump(final_model, 'bank_model.pkl')" ] }, { "cell_type": "code", "execution_count": 70, "id": "athletic-stick", "metadata": {}, "outputs": [], "source": [ "test_final_model = joblib.load('bank_model.pkl')" ] }, { "cell_type": "code", "execution_count": 71, "id": "sustained-equipment", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0, 0, 0, ..., 1, 1, 1])" ] }, "execution_count": 71, "metadata": {}, "output_type": "execute_result" } ], "source": [ "test_final_model.predict(x1.iloc[0:])" ] }, { "cell_type": "code", "execution_count": 79, "id": "celtic-morgan", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "AUC on Test set: , 0.74\n" ] } ], "source": [ "print(\"AUC on Test set: ,\", np.round(metrics.roc_auc_score(y2, test_final_model.predict(x2)),2))" ] }, { "cell_type": "markdown", "id": "partial-adams", "metadata": {}, "source": [ "Excellent, OOB score is same as the test (very rare!)." ] }, { "cell_type": "markdown", "id": "fancy-northeast", "metadata": {}, "source": [ "There are actually multiple ways to register and deploy a model as webservice. Typically, you will first create training script, register an environment, create inference schema, register model, create deployment config etc. But there is a shorter way to do all of that in one single step. Usually you will go through everything step-by-step but for demonstration purposes, I will roll these steps into one by using `ResourceConfiguration` class. Also, note that this is for real-time inferencing using Azure Container Instance. You should always deploy the model locally first for debugging, testing before deploying it to the cloud. For batch-inferencing, follow [these steps](https://docs.microsoft.com/en-us/learn/modules/deploy-batch-inference-pipelines-with-azure-machine-learning/). \n", "\n", "You can also register and deploy using the interface in the Studio.\n", "\n", "We will also save the sample features and labels for future reference and model debugging. " ] }, { "cell_type": "code", "execution_count": 81, "id": "hungry-nickname", "metadata": {}, "outputs": [], "source": [ "np.savetxt('features.csv', np.array(x1), delimiter=',', fmt='%s')\n", "np.savetxt('labels.csv', y1, delimiter=',')" ] }, { "cell_type": "code", "execution_count": 82, "id": "different-excerpt", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Uploading an estimated of 2 files\n", "Uploading ./features.csv\n", "Uploaded ./features.csv, 1 files out of an estimated total of 2\n", "Uploading ./labels.csv\n", "Uploaded ./labels.csv, 2 files out of an estimated total of 2\n", "Uploaded 2 files\n" ] } ], "source": [ "\n", "datastore.upload_files(files=['./features.csv', './labels.csv'],\n", " target_path='sample_data/',\n", " overwrite=True)\n", "\n", "input_dataset = Dataset.Tabular.from_delimited_files(path=[(datastore, 'sample_data/features.csv')])\n", "output_dataset = Dataset.Tabular.from_delimited_files(path=[(datastore, 'sample_data/labels.csv')])" ] }, { "cell_type": "code", "execution_count": 83, "id": "preceding-arthritis", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registering model bank_model\n" ] } ], "source": [ "from azureml.core import Model\n", "from azureml.core.resource_configuration import ResourceConfiguration\n", "\n", "model = (Model.register(workspace = ws, \n", " model_name = \"bank_model\", #name for the model\n", " model_path = './bank_model.pkl', #Specify the .pkl file\n", " model_framework=Model.Framework.SCIKITLEARN, #This will automatically create environment & schema\n", " sample_input_dataset=input_dataset, #Sample input\n", " sample_output_dataset=output_dataset, #Sample output \n", " resource_configuration=ResourceConfiguration(cpu=1, memory_in_gb=0.5), #ACI config to use\n", " description='Bank Marketing model to predict of customer will sign up',\n", " tags = {'Author':'Sandeep', \n", " 'Date':'2/18/2021', \n", " 'Model':'RandomForest', \n", " 'Dataset':ds_uid} \n", " ))" ] }, { "cell_type": "code", "execution_count": 84, "id": "joint-circle", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Name: bank_model\n", "Version: 1\n" ] } ], "source": [ "print('Name:', model.name)\n", "print('Version:', model.version)" ] }, { "cell_type": "code", "execution_count": 85, "id": "colonial-seafood", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Tips: You can try get_logs(): https://aka.ms/debugimage#dockerlog or local deployment: https://aka.ms/debugimage#debug-locally to debug if deployment takes longer than 10 minutes.\n", "Running............................................................................................\n", "Succeeded\n", "ACI service creation operation finished, operation \"Succeeded\"\n" ] } ], "source": [ "service = Model.deploy(ws, \"service3\", [model])\n", "service.wait_for_deployment(show_output=True)" ] }, { "cell_type": "code", "execution_count": 86, "id": "varying-basis", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AciWebservice(workspace=Workspace.create(name='demows', subscription_id='4cedc5dd-e3ad-468d-bf66-32e31bdb9148', resource_group='1-f4dcfa62-playground-sandbox'), name=service3, image_id=None, compute_type=None, state=ACI, scoring_uri=Healthy, tags=http://ec1b6ac7-1a4a-4d2e-8f98-ed8f8628ba44.centralus.azurecontainer.io/score, properties={}, created_by={'hasInferenceSchema': 'True', 'hasHttps': 'False'})" ] }, "execution_count": 86, "metadata": {}, "output_type": "execute_result" } ], "source": [ "service" ] }, { "cell_type": "code", "execution_count": 87, "id": "dominican-advantage", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Healthy'" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "service.state" ] }, { "cell_type": "markdown", "id": "white-constitution", "metadata": {}, "source": [ "Webservcie status is `Healthy` and ready to be used in production for inferencing." ] }, { "cell_type": "markdown", "id": "signal-audio", "metadata": {}, "source": [ "#### Create an input data to test inferencing\n", "\n", "We will send 10 sample observations to test if the service is responding and returning expected output. You can also test this using the scoring_uri." ] }, { "cell_type": "code", "execution_count": 89, "id": "therapeutic-lottery", "metadata": {}, "outputs": [], "source": [ "import json \n", "\n", "input = json.dumps({'data':x1.iloc[:10,:].to_dict('list'),'method': 'predict'})\n", "headers = {'Content-Type': 'application/json'}" ] }, { "cell_type": "code", "execution_count": 90, "id": "treated-baghdad", "metadata": {}, "outputs": [], "source": [ "output = service.run(input)" ] }, { "cell_type": "code", "execution_count": 91, "id": "eastern-nowhere", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'predict': [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]}" ] }, "execution_count": 91, "metadata": {}, "output_type": "execute_result" } ], "source": [ "output" ] }, { "cell_type": "markdown", "id": "employed-apache", "metadata": {}, "source": [ "We got the response back with predictions. Service is running and ready for action.\n", "If you are trying this example, be sure to delet the service and the weorkspace to avoid charges." ] }, { "cell_type": "code", "execution_count": 87, "id": "micro-andrew", "metadata": {}, "outputs": [], "source": [ "\n", "service.delete()" ] }, { "cell_type": "markdown", "id": "decreased-interim", "metadata": {}, "source": [ "#### Monitor the Webservice" ] }, { "cell_type": "markdown", "id": "peaceful-excerpt", "metadata": {}, "source": [ "To monitor the performance of the webservice and the deployed model, we need to do few things:\n", "\n", " - Stress test the model for distribution shifts and loads\n", " - Collect webservice performance metric using **Application Insights**. This will help us collect:\n", " - Responses\n", " - Request rates, response time, failure rates\n", " - Exceptions\n", " \n", " - Monitor **Concept Drift**\n", " - Performance of the ML model will likely degrade over time due to change in distribution of the input data\n", " - By monitoring drift, we can measure the drift and decide when to re-train the model \n", " \n", " - Collect **Model Interpretability** data during inferencing\n", " - This is to track how model is creating predictions and if predictions are fair \n", " \n", " \n", "This is big topic and will require a separate presentation. But just know that with Azure ML service, you can monitor the model performance in production environment." ] }, { "cell_type": "markdown", "id": "cognitive-cathedral", "metadata": {}, "source": [ "### Next Steps\n", "\n", "This was just the introduction to give you flavor for how to use Azure ML sdk. There are more advanced methods available depending on the project needs. I would encourage you to research those on your own from MS Docs and MS Learn." ] }, { "cell_type": "markdown", "id": "generic-melbourne", "metadata": {}, "source": [ " - Azure ML Pipelines\n", " - Azure ML HyperDrive\n", " - Azure Auto ML\n", " - Azure ML Studio Designer" ] }, { "cell_type": "markdown", "id": "exempt-research", "metadata": {}, "source": [ "### Resources" ] }, { "cell_type": "markdown", "id": "defined-upgrade", "metadata": {}, "source": [ " - [Microsoft Learn](https://docs.microsoft.com/en-us/learn/paths/create-machine-learn-models/)\n", " - [Microsoft Documentation](https://docs.microsoft.com/en-us/python/api/overview/azure/ml/?view=azure-ml-py)\n" ] }, { "cell_type": "markdown", "id": "soviet-consumer", "metadata": {}, "source": [ "Thank you ! I hope you found this helpful. As always, feel free to get in touch if you have any questions. " ] } ], "metadata": { "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.7.9" } }, "nbformat": 4, "nbformat_minor": 5 }