From 71e061b1933ef4af47297984f3c929a26ad7d4d0 Mon Sep 17 00:00:00 2001 From: amlrelsa-ms Date: Wed, 16 Feb 2022 16:32:55 +0000 Subject: [PATCH] update samples from Release-114 as a part of 1.38.0 SDK stable release --- ...fication-bank-marketing-all-features.ipynb | 1851 +++--- ...-ml-classification-credit-card-fraud.ipynb | 976 ++- .../auto-ml-classification-text-dnn.ipynb | 1177 ++-- .../classification-text-dnn/helper.py | 77 +- .../classification-text-dnn/infer.py | 42 +- .../auto-ml-continuous-retraining.ipynb | 1153 ++-- .../continuous-retraining/check_data.py | 2 +- .../continuous-retraining/register_model.py | 10 +- .../upload_weather_data.py | 122 +- ...-ml-forecasting-backtest-many-models.ipynb | 1442 ++-- .../update_env.yml | 3 + ...ml-forecasting-backtest-single-model.ipynb | 1434 ++-- .../auto-ml-forecasting-bike-share.ipynb | 1431 ++-- .../auto-ml-forecasting-energy-demand.ipynb | 1555 ++--- .../auto-ml-forecasting-function.ipynb | 1783 +++-- .../auto-ml-forecasting-github-dau.ipynb | 725 ++ .../github_dau_2011-2018_test.csv | 455 ++ .../github_dau_2011-2018_train.csv | 2286 +++++++ .../forecasting-github-dau/helper.py | 183 + .../forecasting-github-dau/infer.py | 386 ++ .../README.md | 94 + ...-forecasting-hierarchical-timeseries.ipynb | 1270 ++-- .../media/data-table.png | Bin 0 -> 67000 bytes .../media/deploy-button.png | Bin 0 -> 17108 bytes .../media/food-chain.PNG | Bin 0 -> 1739 bytes .../media/hierarchy-sample-ms.PNG | Bin 0 -> 168634 bytes .../media/retail-org-2.PNG | Bin 0 -> 1535 bytes .../media/retail-org.PNG | Bin 0 -> 1852 bytes .../media/workflow.PNG | Bin 0 -> 31372 bytes .../update_env.yml | 3 + .../forecasting-many-models/README.md | 122 + .../auto-ml-forecasting-many-models.ipynb | 1484 ++--- .../images/01_userfilesupdate.PNG | Bin 0 -> 33143 bytes .../images/Flow_map.png | Bin 0 -> 313320 bytes .../images/ai show.gif | Bin 0 -> 2760226 bytes .../images/computes_view.png | Bin 0 -> 108111 bytes .../images/create_notebook_vm.png | Bin 0 -> 161742 bytes .../images/mmsa-overview.png | Bin 0 -> 81788 bytes .../forecasting-many-models/images/mmsa.png | Bin 0 -> 70132 bytes .../images/terminal.png | Bin 0 -> 646229 bytes .../forecasting-many-models/update_env.yml | 3 + ...to-ml-forecasting-orange-juice-sales.ipynb | 1672 ++--- ...nivariate-recipe-experiment-settings.ipynb | 982 +-- ...ing-univariate-recipe-run-experiment.ipynb | 1182 ++-- .../README.md | 18 + ...ssification-multiclass-batch-scoring.ipynb | 950 +++ .../scripts/batch_scoring.py | 69 + .../ui_outputs.PNG | Bin 0 -> 264731 bytes .../image-classification-multiclass/README.md | 15 + ...o-ml-image-classification-multiclass.ipynb | 744 +++ ..._classification_multiclass_predictions.jpg | Bin 0 -> 36934 bytes .../test_image.jpg | Bin 0 -> 278893 bytes .../image-classification-multilabel/README.md | 15 + ...o-ml-image-classification-multilabel.ipynb | 742 +++ ..._classification_multilabel_predictions.jpg | Bin 0 -> 158619 bytes .../test_image.jpg | Bin 0 -> 163983 bytes .../image-instance-segmentation/README.md | 15 + .../auto-ml-image-instance-segmentation.ipynb | 769 +++ ...mple_instance_segmentation_predictions.jpg | Bin 0 -> 151284 bytes .../jsonl_converter.py | 213 + .../test_image.jpg | Bin 0 -> 160099 bytes .../image-object-detection/README.md | 15 + .../auto-ml-image-object-detection.ipynb | 835 +++ .../image-object-detection/coco2jsonl.py | 127 + .../example_object_detection_predictions.jpg | Bin 0 -> 153231 bytes .../odFridgeObjects_coco.json | 5837 +++++++++++++++++ .../image-object-detection/test_image.jpg | Bin 0 -> 160099 bytes .../yolo_onnx_preprocessing_utils.py | 327 + ...assification-credit-card-fraud-local.ipynb | 1759 ++--- ...ation-metric-and-confidence-interval.ipynb | 698 ++ .../metrics/imgs/best-model.png | Bin 0 -> 17811 bytes .../metrics/imgs/binary-metrics.png | Bin 0 -> 28434 bytes .../metrics/imgs/child-runs.png | Bin 0 -> 19553 bytes .../metrics/imgs/confidence-intervals.png | Bin 0 -> 73373 bytes .../imgs/regression-confidence-interval.png | Bin 0 -> 83270 bytes .../metrics/imgs/test-run-id.png | Bin 0 -> 31680 bytes .../metrics/imgs/test-run.png | Bin 0 -> 30242 bytes ...regression-explanation-featurization.ipynb | 1853 +++--- .../score_explain.py | 31 +- .../train_explainer.py | 82 +- .../regression/auto-ml-regression.ipynb | 943 ++- ...facial-expression-recognition-deploy.ipynb | 2 +- .../onnx/onnx-inference-mnist-deploy.ipynb | 2 +- .../scripts/prepdata/cleanse.py | 16 +- ...erparameter-tune-deploy-with-chainer.ipynb | 1 - ...yperparameter-tune-deploy-with-keras.ipynb | 1 - ...erparameter-tune-deploy-with-pytorch.ipynb | 1 - 87 files changed, 27934 insertions(+), 12051 deletions(-) create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/update_env.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-github-dau/auto-ml-forecasting-github-dau.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_test.csv create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_train.csv create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-github-dau/helper.py create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-github-dau/infer.py create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/data-table.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/deploy-button.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/food-chain.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/hierarchy-sample-ms.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org-2.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/workflow.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/update_env.yml create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/01_userfilesupdate.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/Flow_map.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/ai show.gif create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/computes_view.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/create_notebook_vm.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/mmsa-overview.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/mmsa.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/terminal.png create mode 100644 how-to-use-azureml/automated-machine-learning/forecasting-many-models/update_env.yml create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass-batch-scoring/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass-batch-scoring/auto-ml-image-classification-multiclass-batch-scoring.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass-batch-scoring/scripts/batch_scoring.py create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass-batch-scoring/ui_outputs.PNG create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass/auto-ml-image-classification-multiclass.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass/example_image_classification_multiclass_predictions.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multiclass/test_image.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multilabel/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multilabel/auto-ml-image-classification-multilabel.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multilabel/example_image_classification_multilabel_predictions.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-classification-multilabel/test_image.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-instance-segmentation/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/image-instance-segmentation/auto-ml-image-instance-segmentation.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/image-instance-segmentation/example_instance_segmentation_predictions.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-instance-segmentation/jsonl_converter.py create mode 100644 how-to-use-azureml/automated-machine-learning/image-instance-segmentation/test_image.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/README.md create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/auto-ml-image-object-detection.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/coco2jsonl.py create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/example_object_detection_predictions.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/odFridgeObjects_coco.json create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/test_image.jpg create mode 100644 how-to-use-azureml/automated-machine-learning/image-object-detection/yolo_onnx_preprocessing_utils.py create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/binary-classification-metric-and-confidence-interval.ipynb create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/best-model.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/binary-metrics.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/child-runs.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/confidence-intervals.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/regression-confidence-interval.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/test-run-id.png create mode 100644 how-to-use-azureml/automated-machine-learning/metrics/imgs/test-run.png diff --git a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing-all-features/auto-ml-classification-bank-marketing-all-features.ipynb b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing-all-features/auto-ml-classification-bank-marketing-all-features.ipynb index a7dd83291..0bf2132b1 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-bank-marketing-all-features/auto-ml-classification-bank-marketing-all-features.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-bank-marketing-all-features/auto-ml-classification-bank-marketing-all-features.ipynb @@ -1,920 +1,935 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-bank-marketing-all-features/auto-ml-classification-bank-marketing.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Classification with Deployment using a Bank Marketing Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Deploy](#Deploy)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "\n", - "In this example we use the UCI Bank Marketing dataset to showcase how you can use AutoML for a classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if the client will subscribe to a term deposit with the bank.\n", - "\n", - "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "Please find the ONNX related documentations [here](https://github.com/onnx/onnx).\n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an experiment using an existing workspace.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using local compute with ONNX compatible config on.\n", - "4. Explore the results, featurization transparency options and save the ONNX model\n", - "5. Inference with the ONNX model.\n", - "6. Register the model.\n", - "7. Create a container image.\n", - "8. Create an Azure Container Instance (ACI) service.\n", - "9. Test the ACI service.\n", - "\n", - "In addition this notebook showcases the following features\n", - "- **Blocking** certain pipelines\n", - "- Specifying **target metrics** to indicate stopping criteria\n", - "- Handling **missing data** in the input" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import pandas as pd\n", - "import os\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.core.dataset import Dataset\n", - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.interpret import ExplanationClient" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Accessing the Azure ML workspace requires authentication with Azure.\n", - "\n", - "The default authentication is interactive authentication using the default tenant. Executing the `ws = Workspace.from_config()` line in the cell below will prompt for authentication the first time that it is run.\n", - "\n", - "If you have multiple Azure tenants, you can specify the tenant by replacing the `ws = Workspace.from_config()` line in the cell below with the following:\n", - "\n", - "```\n", - "from azureml.core.authentication import InteractiveLoginAuthentication\n", - "auth = InteractiveLoginAuthentication(tenant_id = 'mytenantid')\n", - "ws = Workspace.from_config(auth = auth)\n", - "```\n", - "\n", - "If you need to run in an environment where interactive login is not possible, you can use Service Principal authentication by replacing the `ws = Workspace.from_config()` line in the cell below with the following:\n", - "\n", - "```\n", - "from azureml.core.authentication import ServicePrincipalAuthentication\n", - "auth = auth = ServicePrincipalAuthentication('mytenantid', 'myappid', 'mypassword')\n", - "ws = Workspace.from_config(auth = auth)\n", - "```\n", - "For more details, see [aka.ms/aml-notebook-auth](http://aka.ms/aml-notebook-auth)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for experiment\n", - "experiment_name = 'automl-classification-bmarketing-all'\n", - "\n", - "experiment=Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", - "\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your CPU cluster\n", - "cpu_cluster_name = \"cpu-cluster-4\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=cpu_cluster_name)\n", - " print('Found existing cluster, use it.')\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(vm_size='STANDARD_DS12_V2',\n", - " max_nodes=6)\n", - " compute_target = ComputeTarget.create(ws, cpu_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Leverage azure compute to load the bank marketing dataset as a Tabular Dataset into the dataset variable. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = pd.read_csv(\"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\")\n", - "data.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Add missing values in 75% of the lines.\n", - "import numpy as np\n", - "\n", - "missing_rate = 0.75\n", - "n_missing_samples = int(np.floor(data.shape[0] * missing_rate))\n", - "missing_samples = np.hstack((np.zeros(data.shape[0] - n_missing_samples, dtype=np.bool), np.ones(n_missing_samples, dtype=np.bool)))\n", - "rng = np.random.RandomState(0)\n", - "rng.shuffle(missing_samples)\n", - "missing_features = rng.randint(0, data.shape[1], n_missing_samples)\n", - "data.values[np.where(missing_samples)[0], missing_features] = np.nan" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if not os.path.isdir('data'):\n", - " os.mkdir('data')\n", - " \n", - "# Save the train data to a csv to be uploaded to the datastore\n", - "pd.DataFrame(data).to_csv(\"data/train_data.csv\", index=False)\n", - "\n", - "ds = ws.get_default_datastore()\n", - "ds.upload(src_dir='./data', target_path='bankmarketing', overwrite=True, show_progress=True)\n", - "\n", - " \n", - "\n", - "# Upload the training data as a tabular dataset for access during training on remote compute\n", - "train_data = Dataset.Tabular.from_delimited_files(path=ds.path('bankmarketing/train_data.csv'))\n", - "label = \"y\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Validation Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "validation_data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_validate.csv\"\n", - "validation_dataset = Dataset.Tabular.from_delimited_files(validation_data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Test Data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_test.csv\"\n", - "test_dataset = Dataset.Tabular.from_delimited_files(test_data)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression or forecasting|\n", - "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", - "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", - "|**blocked_models** | *List* of *strings* indicating machine learning algorithms for AutoML to avoid in this run.

Allowed values for **Classification**
LogisticRegression
SGD
MultinomialNaiveBayes
BernoulliNaiveBayes
SVM
LinearSVM
KNN
DecisionTree
RandomForest
ExtremeRandomTrees
LightGBM
GradientBoosting
TensorFlowDNN
TensorFlowLinearClassifier

Allowed values for **Regression**
ElasticNet
GradientBoosting
DecisionTree
KNN
LassoLars
SGD
RandomForest
ExtremeRandomTrees
LightGBM
TensorFlowLinearRegressor
TensorFlowDNN

Allowed values for **Forecasting**
ElasticNet
GradientBoosting
DecisionTree
KNN
LassoLars
SGD
RandomForest
ExtremeRandomTrees
LightGBM
TensorFlowLinearRegressor
TensorFlowDNN
Arima
Prophet|\n", - "|**allowed_models** | *List* of *strings* indicating machine learning algorithms for AutoML to use in this run. Same values listed above for **blocked_models** allowed for **allowed_models**.|\n", - "|**experiment_exit_score**| Value indicating the target for *primary_metric*.
Once the target is surpassed the run terminates.|\n", - "|**experiment_timeout_hours**| Maximum amount of time in hours that all iterations combined can take before the experiment terminates.|\n", - "|**enable_early_stopping**| Flag to enble early termination if the score is not improving in the short term.|\n", - "|**featurization**| 'auto' / 'off' Indicator for whether featurization step should be done automatically or not. Note: If the input data is sparse, featurization cannot be turned on.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**training_data**|Input dataset, containing both features and label column.|\n", - "|**label_column_name**|The name of the label column.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"experiment_timeout_hours\" : 0.3,\n", - " \"enable_early_stopping\" : True,\n", - " \"iteration_timeout_minutes\": 5,\n", - " \"max_concurrent_iterations\": 4,\n", - " \"max_cores_per_iteration\": -1,\n", - " #\"n_cross_validations\": 2,\n", - " \"primary_metric\": 'AUC_weighted',\n", - " \"featurization\": 'auto',\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " compute_target=compute_target,\n", - " experiment_exit_score = 0.9984,\n", - " blocked_models = ['KNN','LinearSVM'],\n", - " enable_onnx_compatible_models=True,\n", - " training_data = train_data,\n", - " label_column_name = label,\n", - " validation_data = validation_dataset,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run the following cell to access previous runs. Uncomment the cell below and update the run_id." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#from azureml.train.automl.run import AutoMLRun\n", - "#remote_run = AutoMLRun(experiment=experiment, run_id='thresh else 'black')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Delete a Web Service\n", - "\n", - "Deletes the specified web service." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "aci_service.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This Bank Marketing dataset is made available under the Creative Commons (CCO: Public Domain) License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: https://creativecommons.org/publicdomain/zero/1.0/ and is available at: https://www.kaggle.com/janiobachmann/bank-marketing-dataset .\n", - "\n", - "_**Acknowledgements**_\n", - "This data set is originally available within the UCI Machine Learning Database: https://archive.ics.uci.edu/ml/datasets/bank+marketing\n", - "\n", - "[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification with Deployment using a Bank Marketing Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Deploy](#Deploy)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the UCI Bank Marketing dataset to showcase how you can use AutoML for a classification problem and deploy it to an Azure Container Instance (ACI). The classification goal is to predict if the client will subscribe to a term deposit with the bank.\n", + "\n", + "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "Please find the ONNX related documentations [here](https://github.com/onnx/onnx).\n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using local compute with ONNX compatible config on.\n", + "4. Explore the results, featurization transparency options and save the ONNX model\n", + "5. Inference with the ONNX model.\n", + "6. Register the model.\n", + "7. Create a container image.\n", + "8. Create an Azure Container Instance (ACI) service.\n", + "9. Test the ACI service.\n", + "\n", + "In addition this notebook showcases the following features\n", + "- **Blocking** certain pipelines\n", + "- Specifying **target metrics** to indicate stopping criteria\n", + "- Handling **missing data** in the input" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "import os\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.dataset import Dataset\n", + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.interpret import ExplanationClient" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Accessing the Azure ML workspace requires authentication with Azure.\n", + "\n", + "The default authentication is interactive authentication using the default tenant. Executing the `ws = Workspace.from_config()` line in the cell below will prompt for authentication the first time that it is run.\n", + "\n", + "If you have multiple Azure tenants, you can specify the tenant by replacing the `ws = Workspace.from_config()` line in the cell below with the following:\n", + "\n", + "```\n", + "from azureml.core.authentication import InteractiveLoginAuthentication\n", + "auth = InteractiveLoginAuthentication(tenant_id = 'mytenantid')\n", + "ws = Workspace.from_config(auth = auth)\n", + "```\n", + "\n", + "If you need to run in an environment where interactive login is not possible, you can use Service Principal authentication by replacing the `ws = Workspace.from_config()` line in the cell below with the following:\n", + "\n", + "```\n", + "from azureml.core.authentication import ServicePrincipalAuthentication\n", + "auth = auth = ServicePrincipalAuthentication('mytenantid', 'myappid', 'mypassword')\n", + "ws = Workspace.from_config(auth = auth)\n", + "```\n", + "For more details, see [aka.ms/aml-notebook-auth](http://aka.ms/aml-notebook-auth)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = \"automl-classification-bmarketing-all\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Experiment Name\"] = experiment.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", + "\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "cpu_cluster_name = \"cpu-cluster-4\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=cpu_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " compute_target = ComputeTarget.create(ws, cpu_cluster_name, compute_config)\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Leverage azure compute to load the bank marketing dataset as a Tabular Dataset into the dataset variable. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.read_csv(\n", + " \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_train.csv\"\n", + ")\n", + "data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Add missing values in 75% of the lines.\n", + "import numpy as np\n", + "\n", + "missing_rate = 0.75\n", + "n_missing_samples = int(np.floor(data.shape[0] * missing_rate))\n", + "missing_samples = np.hstack(\n", + " (\n", + " np.zeros(data.shape[0] - n_missing_samples, dtype=np.bool),\n", + " np.ones(n_missing_samples, dtype=np.bool),\n", + " )\n", + ")\n", + "rng = np.random.RandomState(0)\n", + "rng.shuffle(missing_samples)\n", + "missing_features = rng.randint(0, data.shape[1], n_missing_samples)\n", + "data.values[np.where(missing_samples)[0], missing_features] = np.nan" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if not os.path.isdir(\"data\"):\n", + " os.mkdir(\"data\")\n", + "# Save the train data to a csv to be uploaded to the datastore\n", + "pd.DataFrame(data).to_csv(\"data/train_data.csv\", index=False)\n", + "\n", + "ds = ws.get_default_datastore()\n", + "ds.upload(\n", + " src_dir=\"./data\", target_path=\"bankmarketing\", overwrite=True, show_progress=True\n", + ")\n", + "\n", + "\n", + "# Upload the training data as a tabular dataset for access during training on remote compute\n", + "train_data = Dataset.Tabular.from_delimited_files(\n", + " path=ds.path(\"bankmarketing/train_data.csv\")\n", + ")\n", + "label = \"y\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Validation Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "validation_data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_validate.csv\"\n", + "validation_dataset = Dataset.Tabular.from_delimited_files(validation_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test Data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/bankmarketing_test.csv\"\n", + "test_dataset = Dataset.Tabular.from_delimited_files(test_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression or forecasting|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**blocked_models** | *List* of *strings* indicating machine learning algorithms for AutoML to avoid in this run.

Allowed values for **Classification**
LogisticRegression
SGD
MultinomialNaiveBayes
BernoulliNaiveBayes
SVM
LinearSVM
KNN
DecisionTree
RandomForest
ExtremeRandomTrees
LightGBM
GradientBoosting
TensorFlowDNN
TensorFlowLinearClassifier

Allowed values for **Regression**
ElasticNet
GradientBoosting
DecisionTree
KNN
LassoLars
SGD
RandomForest
ExtremeRandomTrees
LightGBM
TensorFlowLinearRegressor
TensorFlowDNN

Allowed values for **Forecasting**
ElasticNet
GradientBoosting
DecisionTree
KNN
LassoLars
SGD
RandomForest
ExtremeRandomTrees
LightGBM
TensorFlowLinearRegressor
TensorFlowDNN
Arima
Prophet|\n", + "|**allowed_models** | *List* of *strings* indicating machine learning algorithms for AutoML to use in this run. Same values listed above for **blocked_models** allowed for **allowed_models**.|\n", + "|**experiment_exit_score**| Value indicating the target for *primary_metric*.
Once the target is surpassed the run terminates.|\n", + "|**experiment_timeout_hours**| Maximum amount of time in hours that all iterations combined can take before the experiment terminates.|\n", + "|**enable_early_stopping**| Flag to enble early termination if the score is not improving in the short term.|\n", + "|**featurization**| 'auto' / 'off' Indicator for whether featurization step should be done automatically or not. Note: If the input data is sparse, featurization cannot be turned on.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**training_data**|Input dataset, containing both features and label column.|\n", + "|**label_column_name**|The name of the label column.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"experiment_timeout_hours\": 0.3,\n", + " \"enable_early_stopping\": True,\n", + " \"iteration_timeout_minutes\": 5,\n", + " \"max_concurrent_iterations\": 4,\n", + " \"max_cores_per_iteration\": -1,\n", + " # \"n_cross_validations\": 2,\n", + " \"primary_metric\": \"AUC_weighted\",\n", + " \"featurization\": \"auto\",\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"classification\",\n", + " debug_log=\"automl_errors.log\",\n", + " compute_target=compute_target,\n", + " experiment_exit_score=0.9984,\n", + " blocked_models=[\"KNN\", \"LinearSVM\"],\n", + " enable_onnx_compatible_models=True,\n", + " training_data=train_data,\n", + " label_column_name=label,\n", + " validation_data=validation_dataset,\n", + " **automl_settings,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Execution of local runs is synchronous. Depending on the data and the number of iterations this can run for a while. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Run the following cell to access previous runs. Uncomment the cell below and update the run_id." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from azureml.train.automl.run import AutoMLRun\n", + "# remote_run = AutoMLRun(experiment=experiment, run_id=' thresh else \"black\",\n", + " )\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delete a Web Service\n", + "\n", + "Deletes the specified web service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aci_service.delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Bank Marketing dataset is made available under the Creative Commons (CCO: Public Domain) License: https://creativecommons.org/publicdomain/zero/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: https://creativecommons.org/publicdomain/zero/1.0/ and is available at: https://www.kaggle.com/janiobachmann/bank-marketing-dataset .\n", + "\n", + "_**Acknowledgements**_\n", + "This data set is originally available within the UCI Machine Learning Database: https://archive.ics.uci.edu/ml/datasets/bank+marketing\n", + "\n", + "[Moro et al., 2014] S. Moro, P. Cortez and P. Rita. A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems, Elsevier, 62:22-31, June 2014" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "ratanase" + } + ], + "category": "tutorial", + "compute": [ + "AML" + ], + "datasets": [ + "Bankmarketing" + ], + "deployment": [ + "ACI" + ], + "exclude_from_index": false, + "framework": [ + "None" + ], + "friendly_name": "Automated ML run with basic edition features.", + "index_order": 5, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + }, + "tags": [ + "featurization", + "explainability", + "remote_run", + "AutomatedML" ], - "metadata": { - "authors": [ - { - "name": "ratanase" - } - ], - "category": "tutorial", - "compute": [ - "AML" - ], - "datasets": [ - "Bankmarketing" - ], - "deployment": [ - "ACI" - ], - "exclude_from_index": false, - "framework": [ - "None" - ], - "friendly_name": "Automated ML run with basic edition features.", - "index_order": 5, - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - }, - "tags": [ - "featurization", - "explainability", - "remote_run", - "AutomatedML" - ], - "task": "Classification" - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "task": "Classification" + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb index 5ef43e3bd..970448257 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.ipynb @@ -1,497 +1,483 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-credit-card-fraud/auto-ml-classification-credit-card-fraud.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Classification of credit card fraudulent transactions on remote compute **_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Train](#Train)\n", - "1. [Results](#Results)\n", - "1. [Test](#Test)\n", - "1. [Acknowledgements](#Acknowledgements)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "\n", - "In this example we use the associated credit card dataset to showcase how you can use AutoML for a simple classification problem. The goal is to predict if a credit card transaction is considered a fraudulent charge.\n", - "\n", - "This notebook is using remote compute to train the model.\n", - "\n", - "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", - "\n", - "In this notebook you will learn how to:\n", - "1. Create an experiment using an existing workspace.\n", - "2. Configure AutoML using `AutoMLConfig`.\n", - "3. Train the model using remote compute.\n", - "4. Explore the results.\n", - "5. Test the fitted model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import pandas as pd\n", - "import os\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.core.dataset import Dataset\n", - "from azureml.train.automl import AutoMLConfig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for experiment\n", - "experiment_name = 'automl-classification-ccard-remote'\n", - "\n", - "experiment=Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "A compute target is required to execute the Automated ML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", - "\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your CPU cluster\n", - "cpu_cluster_name = \"cpu-cluster-1\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=cpu_cluster_name)\n", - " print('Found existing cluster, use it.')\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(vm_size='STANDARD_DS12_V2',\n", - " max_nodes=6)\n", - " compute_target = ComputeTarget.create(ws, cpu_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Data\n", - "\n", - "Load the credit card dataset from a csv file containing both training features and labels. The features are inputs to the model, while the training labels represent the expected output of the model. Next, we'll split the data using random_split and extract the training data for the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/creditcard.csv\"\n", - "dataset = Dataset.Tabular.from_delimited_files(data)\n", - "training_data, validation_data = dataset.random_split(percentage=0.8, seed=223)\n", - "label_column_name = 'Class'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|classification or regression|\n", - "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", - "|**enable_early_stopping**|Stop the run if the metric score is not showing improvement.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**training_data**|Input dataset, containing both features and label column.|\n", - "|**label_column_name**|The name of the label column.|\n", - "\n", - "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"n_cross_validations\": 3,\n", - " \"primary_metric\": 'AUC_weighted',\n", - " \"enable_early_stopping\": True,\n", - " \"max_concurrent_iterations\": 2, # This is a limit for testing purpose, please increase it as per cluster size\n", - " \"experiment_timeout_hours\": 0.25, # This is a time limit for testing purposes, remove it for real use cases, this will drastically limit ablity to find the best model possible\n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " compute_target = compute_target,\n", - " training_data = training_data,\n", - " label_column_name = label_column_name,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Call the `submit` method on the experiment object and pass the run configuration. Depending on the data and the number of iterations this can run for a while. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output = False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# If you need to retrieve a run that already started, use the following code\n", - "#from azureml.train.automl.run import AutoMLRun\n", - "#remote_run = AutoMLRun(experiment = experiment, run_id = '')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Widget for Monitoring Runs\n", - "\n", - "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", - "\n", - "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "widget-rundetails-sample" - ] - }, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(remote_run).show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Explain model\n", - "\n", - "Automated ML models can be explained and visualized using the SDK Explainability library. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analyze results\n", - "\n", - "### Retrieve the Best Model\n", - "\n", - "Below we select the best pipeline from our iterations. The `get_output` method returns the best run and the fitted model. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run, fitted_model = remote_run.get_output()\n", - "fitted_model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Print the properties of the model\n", - "The fitted_model is a python object and you can read the different properties of the object.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test the fitted model\n", - "\n", - "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# convert the test data to dataframe\n", - "X_test_df = validation_data.drop_columns(columns=[label_column_name]).to_pandas_dataframe()\n", - "y_test_df = validation_data.keep_columns(columns=[label_column_name], validate=True).to_pandas_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# call the predict functions on the model\n", - "y_pred = fitted_model.predict(X_test_df)\n", - "y_pred" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calculate metrics for the prediction\n", - "\n", - "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", - "from the trained model that was returned." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.metrics import confusion_matrix\n", - "import numpy as np\n", - "import itertools\n", - "\n", - "cf =confusion_matrix(y_test_df.values,y_pred)\n", - "plt.imshow(cf,cmap=plt.cm.Blues,interpolation='nearest')\n", - "plt.colorbar()\n", - "plt.title('Confusion Matrix')\n", - "plt.xlabel('Predicted')\n", - "plt.ylabel('Actual')\n", - "class_labels = ['False','True']\n", - "tick_marks = np.arange(len(class_labels))\n", - "plt.xticks(tick_marks,class_labels)\n", - "plt.yticks([-0.5,0,1,1.5],['','False','True',''])\n", - "# plotting text value inside cells\n", - "thresh = cf.max() / 2.\n", - "for i,j in itertools.product(range(cf.shape[0]),range(cf.shape[1])):\n", - " plt.text(j,i,format(cf[i,j],'d'),horizontalalignment='center',color='white' if cf[i,j] >thresh else 'black')\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Acknowledgements" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This Credit Card fraud Detection dataset is made available under the Open Database License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/ and is available at: https://www.kaggle.com/mlg-ulb/creditcardfraud\n", - "\n", - "The dataset has been collected and analysed during a research collaboration of Worldline and the Machine Learning Group (http://mlg.ulb.ac.be) of ULB (Universit\u00c3\u00a9 Libre de Bruxelles) on big data mining and fraud detection.\n", - "More details on current and past projects on related topics are available on https://www.researchgate.net/project/Fraud-detection-5 and the page of the DefeatFraud project\n", - "\n", - "Please cite the following works:\n", - "\n", - "Andrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi. Calibrating Probability with Undersampling for Unbalanced Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015\n", - "\n", - "Dal Pozzolo, Andrea; Caelen, Olivier; Le Borgne, Yann-Ael; Waterschoot, Serge; Bontempi, Gianluca. Learned lessons in credit card fraud detection from a practitioner perspective, Expert systems with applications,41,10,4915-4928,2014, Pergamon\n", - "\n", - "Dal Pozzolo, Andrea; Boracchi, Giacomo; Caelen, Olivier; Alippi, Cesare; Bontempi, Gianluca. Credit card fraud detection: a realistic modeling and a novel learning strategy, IEEE transactions on neural networks and learning systems,29,8,3784-3797,2018,IEEE\n", - "\n", - "Dal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", - "\n", - "Carcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Mazzer, Yannis; Bontempi, Gianluca. Scarff: a scalable framework for streaming credit card fraud detection with Spark, Information fusion,41, 182-194,2018,Elsevier\n", - "\n", - "Carcillo, Fabrizio; Le Borgne, Yann-A\u00c3\u00abl; Caelen, Olivier; Bontempi, Gianluca. Streaming active learning strategies for real-life credit card fraud detection: assessment and visualization, International Journal of Data Science and Analytics, 5,4,285-300,2018,Springer International Publishing\n", - "\n", - "Bertrand Lebichot, Yann-A\u00c3\u00abl Le Borgne, Liyun He, Frederic Obl\u00c3\u00a9, Gianluca Bontempi Deep-Learning Domain Adaptation Techniques for Credit Cards Fraud Detection, INNSBDDL 2019: Recent Advances in Big Data and Deep Learning, pp 78-88, 2019\n", - "\n", - "Fabrizio Carcillo, Yann-A\u00c3\u00abl Le Borgne, Olivier Caelen, Frederic Obl\u00c3\u00a9, Gianluca Bontempi Combining Unsupervised and Supervised Learning in Credit Card Fraud Detection Information Sciences, 2019" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "ratanase" - } - ], - "category": "tutorial", - "compute": [ - "AML Compute" - ], - "datasets": [ - "Creditcard" - ], - "deployment": [ - "None" - ], - "exclude_from_index": false, - "file_extension": ".py", - "framework": [ - "None" - ], - "friendly_name": "Classification of credit card fraudulent transactions using Automated ML", - "index_order": 5, - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - }, - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Classification of credit card fraudulent transactions on remote compute **_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Train](#Train)\n", + "1. [Results](#Results)\n", + "1. [Test](#Test)\n", + "1. [Acknowledgements](#Acknowledgements)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "In this example we use the associated credit card dataset to showcase how you can use AutoML for a simple classification problem. The goal is to predict if a credit card transaction is considered a fraudulent charge.\n", + "\n", + "This notebook is using remote compute to train the model.\n", + "\n", + "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration](../../../configuration.ipynb) notebook first if you haven't already to establish your connection to the AzureML Workspace. \n", + "\n", + "In this notebook you will learn how to:\n", + "1. Create an experiment using an existing workspace.\n", + "2. Configure AutoML using `AutoMLConfig`.\n", + "3. Train the model using remote compute.\n", + "4. Explore the results.\n", + "5. Test the fitted model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "import os\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.dataset import Dataset\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for experiment\n", + "experiment_name = \"automl-classification-ccard-remote\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Experiment Name\"] = experiment.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "A compute target is required to execute the Automated ML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", + "\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "cpu_cluster_name = \"cpu-cluster-1\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=cpu_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " compute_target = ComputeTarget.create(ws, cpu_cluster_name, compute_config)\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Data\n", + "\n", + "Load the credit card dataset from a csv file containing both training features and labels. The features are inputs to the model, while the training labels represent the expected output of the model. Next, we'll split the data using random_split and extract the training data for the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/creditcard.csv\"\n", + "dataset = Dataset.Tabular.from_delimited_files(data)\n", + "training_data, validation_data = dataset.random_split(percentage=0.8, seed=223)\n", + "label_column_name = \"Class\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|classification or regression|\n", + "|**primary_metric**|This is the metric that you want to optimize. Classification supports the following primary metrics:
accuracy
AUC_weighted
average_precision_score_weighted
norm_macro_recall
precision_score_weighted|\n", + "|**enable_early_stopping**|Stop the run if the metric score is not showing improvement.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**training_data**|Input dataset, containing both features and label column.|\n", + "|**label_column_name**|The name of the label column.|\n", + "\n", + "**_You can find more information about primary metrics_** [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-configure-auto-train#primary-metric)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"n_cross_validations\": 3,\n", + " \"primary_metric\": \"average_precision_score_weighted\",\n", + " \"enable_early_stopping\": True,\n", + " \"max_concurrent_iterations\": 2, # This is a limit for testing purpose, please increase it as per cluster size\n", + " \"experiment_timeout_hours\": 0.25, # This is a time limit for testing purposes, remove it for real use cases, this will drastically limit ablity to find the best model possible\n", + " \"verbosity\": logging.INFO,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"classification\",\n", + " debug_log=\"automl_errors.log\",\n", + " compute_target=compute_target,\n", + " training_data=training_data,\n", + " label_column_name=label_column_name,\n", + " **automl_settings,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Depending on the data and the number of iterations this can run for a while. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# If you need to retrieve a run that already started, use the following code\n", + "# from azureml.train.automl.run import AutoMLRun\n", + "# remote_run = AutoMLRun(experiment = experiment, run_id = '')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Widget for Monitoring Runs\n", + "\n", + "The widget will first report a \"loading\" status while running the first iteration. After completing the first iteration, an auto-updating graph and table will be shown. The widget will refresh once per minute, so you should see the graph update as child runs complete.\n", + "\n", + "**Note:** The widget displays a link at the bottom. Use this link to open a web interface to explore the individual run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { "tags": [ - "remote_run", - "AutomatedML" - ], - "task": "Classification", - "version": "3.6.7" - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "widget-rundetails-sample" + ] + }, + "outputs": [], + "source": [ + "from azureml.widgets import RunDetails\n", + "\n", + "RunDetails(remote_run).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Explain model\n", + "\n", + "Automated ML models can be explained and visualized using the SDK Explainability library. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analyze results\n", + "\n", + "### Retrieve the Best Model\n", + "\n", + "Below we select the best pipeline from our iterations. The `get_output` method returns the best run and the fitted model. Overloads on `get_output` allow you to retrieve the best run and fitted model for *any* logged metric or for a particular *iteration*." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run, fitted_model = remote_run.get_output()\n", + "fitted_model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Print the properties of the model\n", + "The fitted_model is a python object and you can read the different properties of the object.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test the fitted model\n", + "\n", + "Now that the model is trained, split the data in the same way the data was split for training (The difference here is the data is being split locally) and then run the test data through the trained model to get the predicted values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# convert the test data to dataframe\n", + "X_test_df = validation_data.drop_columns(\n", + " columns=[label_column_name]\n", + ").to_pandas_dataframe()\n", + "y_test_df = validation_data.keep_columns(\n", + " columns=[label_column_name], validate=True\n", + ").to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# call the predict functions on the model\n", + "y_pred = fitted_model.predict(X_test_df)\n", + "y_pred" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calculate metrics for the prediction\n", + "\n", + "Now visualize the data on a scatter plot to show what our truth (actual) values are compared to the predicted values \n", + "from the trained model that was returned." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.metrics import confusion_matrix\n", + "import numpy as np\n", + "import itertools\n", + "\n", + "cf = confusion_matrix(y_test_df.values, y_pred)\n", + "plt.imshow(cf, cmap=plt.cm.Blues, interpolation=\"nearest\")\n", + "plt.colorbar()\n", + "plt.title(\"Confusion Matrix\")\n", + "plt.xlabel(\"Predicted\")\n", + "plt.ylabel(\"Actual\")\n", + "class_labels = [\"False\", \"True\"]\n", + "tick_marks = np.arange(len(class_labels))\n", + "plt.xticks(tick_marks, class_labels)\n", + "plt.yticks([-0.5, 0, 1, 1.5], [\"\", \"False\", \"True\", \"\"])\n", + "# plotting text value inside cells\n", + "thresh = cf.max() / 2.0\n", + "for i, j in itertools.product(range(cf.shape[0]), range(cf.shape[1])):\n", + " plt.text(\n", + " j,\n", + " i,\n", + " format(cf[i, j], \"d\"),\n", + " horizontalalignment=\"center\",\n", + " color=\"white\" if cf[i, j] > thresh else \"black\",\n", + " )\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Acknowledgements" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Credit Card fraud Detection dataset is made available under the Open Database License: http://opendatacommons.org/licenses/odbl/1.0/. Any rights in individual contents of the database are licensed under the Database Contents License: http://opendatacommons.org/licenses/dbcl/1.0/ and is available at: https://www.kaggle.com/mlg-ulb/creditcardfraud\n", + "\n", + "The dataset has been collected and analysed during a research collaboration of Worldline and the Machine Learning Group (http://mlg.ulb.ac.be) of ULB (Université Libre de Bruxelles) on big data mining and fraud detection.\n", + "More details on current and past projects on related topics are available on https://www.researchgate.net/project/Fraud-detection-5 and the page of the DefeatFraud project\n", + "\n", + "Please cite the following works:\n", + "\n", + "Andrea Dal Pozzolo, Olivier Caelen, Reid A. Johnson and Gianluca Bontempi. Calibrating Probability with Undersampling for Unbalanced Classification. In Symposium on Computational Intelligence and Data Mining (CIDM), IEEE, 2015\n", + "\n", + "Dal Pozzolo, Andrea; Caelen, Olivier; Le Borgne, Yann-Ael; Waterschoot, Serge; Bontempi, Gianluca. Learned lessons in credit card fraud detection from a practitioner perspective, Expert systems with applications,41,10,4915-4928,2014, Pergamon\n", + "\n", + "Dal Pozzolo, Andrea; Boracchi, Giacomo; Caelen, Olivier; Alippi, Cesare; Bontempi, Gianluca. Credit card fraud detection: a realistic modeling and a novel learning strategy, IEEE transactions on neural networks and learning systems,29,8,3784-3797,2018,IEEE\n", + "\n", + "Dal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", + "\n", + "Carcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-Aël; Caelen, Olivier; Mazzer, Yannis; Bontempi, Gianluca. Scarff: a scalable framework for streaming credit card fraud detection with Spark, Information fusion,41, 182-194,2018,Elsevier\n", + "\n", + "Carcillo, Fabrizio; Le Borgne, Yann-Aël; Caelen, Olivier; Bontempi, Gianluca. Streaming active learning strategies for real-life credit card fraud detection: assessment and visualization, International Journal of Data Science and Analytics, 5,4,285-300,2018,Springer International Publishing\n", + "\n", + "Bertrand Lebichot, Yann-Aël Le Borgne, Liyun He, Frederic Oblé, Gianluca Bontempi Deep-Learning Domain Adaptation Techniques for Credit Cards Fraud Detection, INNSBDDL 2019: Recent Advances in Big Data and Deep Learning, pp 78-88, 2019\n", + "\n", + "Fabrizio Carcillo, Yann-Aël Le Borgne, Olivier Caelen, Frederic Oblé, Gianluca Bontempi Combining Unsupervised and Supervised Learning in Credit Card Fraud Detection Information Sciences, 2019" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "ratanase" + } + ], + "category": "tutorial", + "compute": [ + "AML Compute" + ], + "datasets": [ + "Creditcard" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "file_extension": ".py", + "framework": [ + "None" + ], + "friendly_name": "Classification of credit card fraudulent transactions using Automated ML", + "index_order": 5, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + }, + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "tags": [ + "remote_run", + "AutomatedML" + ], + "task": "Classification", + "version": "3.6.7" + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/auto-ml-classification-text-dnn.ipynb b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/auto-ml-classification-text-dnn.ipynb index ec98657b4..83cfbbbcc 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/auto-ml-classification-text-dnn.ipynb +++ b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/auto-ml-classification-text-dnn.ipynb @@ -1,590 +1,591 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/classification-text-dnn/auto-ml-classification-text-dnn.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Text Classification Using Deep Learning**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Data](#Data)\n", - "1. [Train](#Train)\n", - "1. [Evaluate](#Evaluate)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "This notebook demonstrates classification with text data using deep learning in AutoML.\n", - "\n", - "AutoML highlights here include using deep neural networks (DNNs) to create embedded features from text data. Depending on the compute cluster the user provides, AutoML tried out Bidirectional Encoder Representations from Transformers (BERT) when a GPU compute is used, and Bidirectional Long-Short Term neural network (BiLSTM) when a CPU compute is used, thereby optimizing the choice of DNN for the uesr's setup.\n", - "\n", - "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", - "\n", - "Notebook synopsis:\n", - "\n", - "1. Creating an Experiment in an existing Workspace\n", - "2. Configuration and remote run of AutoML for a text dataset (20 Newsgroups dataset from scikit-learn) for classification\n", - "3. Registering the best model for future use\n", - "4. Evaluating the final model on a test set" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import logging\n", - "import os\n", - "import shutil\n", - "\n", - "import pandas as pd\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.core.dataset import Dataset\n", - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "from azureml.core.run import Run\n", - "from azureml.widgets import RunDetails\n", - "from azureml.core.model import Model \n", - "from helper import run_inference, get_result_df\n", - "from azureml.train.automl import AutoMLConfig\n", - "from sklearn.datasets import fetch_20newsgroups" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# Choose an experiment name.\n", - "experiment_name = 'automl-classification-text-dnn'\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace Name'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Experiment Name'] = experiment.name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up a compute cluster\n", - "This section uses a user-provided compute cluster (named \"dnntext-cluster\" in this example). If a cluster with this name does not exist in the user's workspace, the below code will create a new cluster. You can choose the parameters of the cluster as mentioned in the comments.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", - "\n", - "Whether you provide/select a CPU or GPU cluster, AutoML will choose the appropriate DNN for that setup - BiLSTM or BERT text featurizer will be included in the candidate featurizers on CPU and GPU respectively. If your goal is to obtain the most accurate model, we recommend you use GPU clusters since BERT featurizers usually outperform BiLSTM featurizers." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "num_nodes = 2\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"dnntext-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print('Found existing cluster, use it.')\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(vm_size = \"STANDARD_NC6\", # CPU for BiLSTM, such as \"STANDARD_DS12_V2\" \n", - " # To use BERT (this is recommended for best performance), select a GPU such as \"STANDARD_NC6\" \n", - " # or similar GPU option\n", - " # available in your workspace\n", - " max_nodes = num_nodes)\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get data\n", - "For this notebook we will use 20 Newsgroups data from scikit-learn. We filter the data to contain four classes and take a sample as training data. Please note that for accuracy improvement, more data is needed. For this notebook we provide a small-data example so that you can use this template to use with your larger sized data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_dir = \"text-dnn-data\" # Local directory to store data\n", - "blobstore_datadir = data_dir # Blob store directory to store data in\n", - "target_column_name = 'y'\n", - "feature_column_name = 'X'\n", - "\n", - "def get_20newsgroups_data():\n", - " '''Fetches 20 Newsgroups data from scikit-learn\n", - " Returns them in form of pandas dataframes\n", - " '''\n", - " remove = ('headers', 'footers', 'quotes')\n", - " categories = [\n", - " 'rec.sport.baseball',\n", - " 'rec.sport.hockey',\n", - " 'comp.graphics',\n", - " 'sci.space',\n", - " ]\n", - "\n", - " data = fetch_20newsgroups(subset = 'train', categories = categories,\n", - " shuffle = True, random_state = 42,\n", - " remove = remove)\n", - " data = pd.DataFrame({feature_column_name: data.data, target_column_name: data.target})\n", - "\n", - " data_train = data[:200]\n", - " data_test = data[200:300] \n", - "\n", - " data_train = remove_blanks_20news(data_train, feature_column_name, target_column_name)\n", - " data_test = remove_blanks_20news(data_test, feature_column_name, target_column_name)\n", - " \n", - " return data_train, data_test\n", - " \n", - "def remove_blanks_20news(data, feature_column_name, target_column_name):\n", - " \n", - " data[feature_column_name] = data[feature_column_name].replace(r'\\n', ' ', regex=True).apply(lambda x: x.strip())\n", - " data = data[data[feature_column_name] != '']\n", - " \n", - " return data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Fetch data and upload to datastore for use in training" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_train, data_test = get_20newsgroups_data()\n", - "\n", - "if not os.path.isdir(data_dir):\n", - " os.mkdir(data_dir)\n", - " \n", - "train_data_fname = data_dir + '/train_data.csv'\n", - "test_data_fname = data_dir + '/test_data.csv'\n", - "\n", - "data_train.to_csv(train_data_fname, index=False)\n", - "data_test.to_csv(test_data_fname, index=False)\n", - "\n", - "datastore = ws.get_default_datastore()\n", - "datastore.upload(src_dir=data_dir, target_path=blobstore_datadir,\n", - " overwrite=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "train_dataset = Dataset.Tabular.from_delimited_files(path = [(datastore, blobstore_datadir + '/train_data.csv')])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prepare AutoML run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook uses the blocked_models parameter to exclude some models that can take a longer time to train on some text datasets. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"experiment_timeout_minutes\": 30,\n", - " \"primary_metric\": 'AUC_weighted',\n", - " \"max_concurrent_iterations\": num_nodes, \n", - " \"max_cores_per_iteration\": -1,\n", - " \"enable_dnn\": True,\n", - " \"enable_early_stopping\": True,\n", - " \"validation_size\": 0.3,\n", - " \"verbosity\": logging.INFO,\n", - " \"enable_voting_ensemble\": False,\n", - " \"enable_stack_ensemble\": False,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " compute_target=compute_target,\n", - " training_data=train_dataset,\n", - " label_column_name=target_column_name,\n", - " blocked_models = ['LightGBM', 'XGBoostClassifier'],\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Submit AutoML Run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_run = experiment.submit(automl_config, show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Displaying the run objects gives you links to the visual tools in the Azure Portal. Go try them!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve the Best Model\n", - "Below we select the best model pipeline from our iterations, use it to test on test data on the same compute cluster." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For local inferencing, you can load the model locally via. the method `remote_run.get_output()`. For more information on the arguments expected by this method, you can run `remote_run.get_output??`.\n", - "Note that when the model contains BERT, this step will require pytorch and pytorch-transformers installed in your local environment. The exact versions of these packages can be found in the **automl_env.yml** file located in the local copy of your MachineLearningNotebooks folder here:\n", - "MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/automl_env.yml\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Retrieve the best Run object\n", - "best_run = automl_run.get_best_child()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can now see what text transformations are used to convert text data to features for this dataset, including deep learning transformations based on BiLSTM or Transformer (BERT is one implementation of a Transformer) models." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Download the featurization summary JSON file locally\n", - "best_run.download_file(\"outputs/featurization_summary.json\", \"featurization_summary.json\")\n", - "\n", - "# Render the JSON as a pandas DataFrame\n", - "with open(\"featurization_summary.json\", \"r\") as f:\n", - " records = json.load(f)\n", - "\n", - "featurization_summary = pd.DataFrame.from_records(records)\n", - "featurization_summary['Transformations'].tolist()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Registering the best model\n", - "We now register the best fitted model from the AutoML Run for use in future deployments. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Get results stats, extract the best model from AutoML run, download and register the resultant best model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "summary_df = get_result_df(automl_run)\n", - "best_dnn_run_id = summary_df['run_id'].iloc[0]\n", - "best_dnn_run = Run(experiment, best_dnn_run_id)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_dir = 'Model' # Local folder where the model will be stored temporarily\n", - "if not os.path.isdir(model_dir):\n", - " os.mkdir(model_dir)\n", - " \n", - "best_dnn_run.download_file('outputs/model.pkl', model_dir + '/model.pkl')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Register the model in your Azure Machine Learning Workspace. If you previously registered a model, please make sure to delete it so as to replace it with this new model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Register the model\n", - "model_name = 'textDNN-20News'\n", - "model = Model.register(model_path = model_dir + '/model.pkl',\n", - " model_name = model_name,\n", - " tags=None,\n", - " workspace=ws)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate on Test Data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now use the best fitted model from the AutoML Run to make predictions on the test set. \n", - "\n", - "Test set schema should match that of the training set." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_dataset = Dataset.Tabular.from_delimited_files(path = [(datastore, blobstore_datadir + '/test_data.csv')])\n", - "\n", - "# preview the first 3 rows of the dataset\n", - "test_dataset.take(3).to_pandas_dataframe()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_experiment = Experiment(ws, experiment_name + \"_test\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "script_folder = os.path.join(os.getcwd(), 'inference')\n", - "os.makedirs(script_folder, exist_ok=True)\n", - "shutil.copy('infer.py', script_folder)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_run = run_inference(test_experiment, compute_target, script_folder, best_dnn_run,\n", - " test_dataset, target_column_name, model_name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display computed metrics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "RunDetails(test_run).show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_run.wait_for_completion()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pd.Series(test_run.get_metrics())" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Text Classification Using Deep Learning**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Evaluate](#Evaluate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates classification with text data using deep learning in AutoML.\n", + "\n", + "AutoML highlights here include using deep neural networks (DNNs) to create embedded features from text data. Depending on the compute cluster the user provides, AutoML tried out Bidirectional Encoder Representations from Transformers (BERT) when a GPU compute is used, and Bidirectional Long-Short Term neural network (BiLSTM) when a CPU compute is used, thereby optimizing the choice of DNN for the uesr's setup.\n", + "\n", + "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", + "\n", + "Notebook synopsis:\n", + "\n", + "1. Creating an Experiment in an existing Workspace\n", + "2. Configuration and remote run of AutoML for a text dataset (20 Newsgroups dataset from scikit-learn) for classification\n", + "3. Registering the best model for future use\n", + "4. Evaluating the final model on a test set" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "import os\n", + "import shutil\n", + "\n", + "import pandas as pd\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.dataset import Dataset\n", + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "from azureml.core.run import Run\n", + "from azureml.widgets import RunDetails\n", + "from azureml.core.model import Model\n", + "from helper import run_inference, get_result_df\n", + "from azureml.train.automl import AutoMLConfig\n", + "from sklearn.datasets import fetch_20newsgroups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# Choose an experiment name.\n", + "experiment_name = \"automl-classification-text-dnn\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace Name\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Experiment Name\"] = experiment.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set up a compute cluster\n", + "This section uses a user-provided compute cluster (named \"dnntext-cluster\" in this example). If a cluster with this name does not exist in the user's workspace, the below code will create a new cluster. You can choose the parameters of the cluster as mentioned in the comments.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", + "\n", + "Whether you provide/select a CPU or GPU cluster, AutoML will choose the appropriate DNN for that setup - BiLSTM or BERT text featurizer will be included in the candidate featurizers on CPU and GPU respectively. If your goal is to obtain the most accurate model, we recommend you use GPU clusters since BERT featurizers usually outperform BiLSTM featurizers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "num_nodes = 2\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"dnntext-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_NC6\", # CPU for BiLSTM, such as \"STANDARD_D2_V2\"\n", + " # To use BERT (this is recommended for best performance), select a GPU such as \"STANDARD_NC6\"\n", + " # or similar GPU option\n", + " # available in your workspace\n", + " max_nodes=num_nodes,\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get data\n", + "For this notebook we will use 20 Newsgroups data from scikit-learn. We filter the data to contain four classes and take a sample as training data. Please note that for accuracy improvement, more data is needed. For this notebook we provide a small-data example so that you can use this template to use with your larger sized data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_dir = \"text-dnn-data\" # Local directory to store data\n", + "blobstore_datadir = data_dir # Blob store directory to store data in\n", + "target_column_name = \"y\"\n", + "feature_column_name = \"X\"\n", + "\n", + "\n", + "def get_20newsgroups_data():\n", + " \"\"\"Fetches 20 Newsgroups data from scikit-learn\n", + " Returns them in form of pandas dataframes\n", + " \"\"\"\n", + " remove = (\"headers\", \"footers\", \"quotes\")\n", + " categories = [\n", + " \"rec.sport.baseball\",\n", + " \"rec.sport.hockey\",\n", + " \"comp.graphics\",\n", + " \"sci.space\",\n", + " ]\n", + "\n", + " data = fetch_20newsgroups(\n", + " subset=\"train\",\n", + " categories=categories,\n", + " shuffle=True,\n", + " random_state=42,\n", + " remove=remove,\n", + " )\n", + " data = pd.DataFrame(\n", + " {feature_column_name: data.data, target_column_name: data.target}\n", + " )\n", + "\n", + " data_train = data[:200]\n", + " data_test = data[200:300]\n", + "\n", + " data_train = remove_blanks_20news(\n", + " data_train, feature_column_name, target_column_name\n", + " )\n", + " data_test = remove_blanks_20news(data_test, feature_column_name, target_column_name)\n", + "\n", + " return data_train, data_test\n", + "\n", + "\n", + "def remove_blanks_20news(data, feature_column_name, target_column_name):\n", + "\n", + " data[feature_column_name] = (\n", + " data[feature_column_name]\n", + " .replace(r\"\\n\", \" \", regex=True)\n", + " .apply(lambda x: x.strip())\n", + " )\n", + " data = data[data[feature_column_name] != \"\"]\n", + "\n", + " return data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Fetch data and upload to datastore for use in training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_train, data_test = get_20newsgroups_data()\n", + "\n", + "if not os.path.isdir(data_dir):\n", + " os.mkdir(data_dir)\n", + "\n", + "train_data_fname = data_dir + \"/train_data.csv\"\n", + "test_data_fname = data_dir + \"/test_data.csv\"\n", + "\n", + "data_train.to_csv(train_data_fname, index=False)\n", + "data_test.to_csv(test_data_fname, index=False)\n", + "\n", + "datastore = ws.get_default_datastore()\n", + "datastore.upload(src_dir=data_dir, target_path=blobstore_datadir, overwrite=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "train_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, blobstore_datadir + \"/train_data.csv\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare AutoML run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook uses the blocked_models parameter to exclude some models that can take a longer time to train on some text datasets. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"experiment_timeout_minutes\": 30,\n", + " \"primary_metric\": \"accuracy\",\n", + " \"max_concurrent_iterations\": num_nodes,\n", + " \"max_cores_per_iteration\": -1,\n", + " \"enable_dnn\": True,\n", + " \"enable_early_stopping\": True,\n", + " \"validation_size\": 0.3,\n", + " \"verbosity\": logging.INFO,\n", + " \"enable_voting_ensemble\": False,\n", + " \"enable_stack_ensemble\": False,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"classification\",\n", + " debug_log=\"automl_errors.log\",\n", + " compute_target=compute_target,\n", + " training_data=train_dataset,\n", + " label_column_name=target_column_name,\n", + " blocked_models=[\"LightGBM\", \"XGBoostClassifier\"],\n", + " **automl_settings,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Submit AutoML Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_run = experiment.submit(automl_config, show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Displaying the run objects gives you links to the visual tools in the Azure Portal. Go try them!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve the Best Model\n", + "Below we select the best model pipeline from our iterations, use it to test on test data on the same compute cluster." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For local inferencing, you can load the model locally via. the method `remote_run.get_output()`. For more information on the arguments expected by this method, you can run `remote_run.get_output??`.\n", + "Note that when the model contains BERT, this step will require pytorch and pytorch-transformers installed in your local environment. The exact versions of these packages can be found in the **automl_env.yml** file located in the local copy of your azureml-examples folder here: \"azureml-examples/python-sdk/tutorials/automl-with-azureml\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Retrieve the best Run object\n", + "best_run = automl_run.get_best_child()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can now see what text transformations are used to convert text data to features for this dataset, including deep learning transformations based on BiLSTM or Transformer (BERT is one implementation of a Transformer) models." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the featurization summary JSON file locally\n", + "best_run.download_file(\n", + " \"outputs/featurization_summary.json\", \"featurization_summary.json\"\n", + ")\n", + "\n", + "# Render the JSON as a pandas DataFrame\n", + "with open(\"featurization_summary.json\", \"r\") as f:\n", + " records = json.load(f)\n", + "\n", + "featurization_summary = pd.DataFrame.from_records(records)\n", + "featurization_summary[\"Transformations\"].tolist()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Registering the best model\n", + "We now register the best fitted model from the AutoML Run for use in future deployments. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get results stats, extract the best model from AutoML run, download and register the resultant best model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "summary_df = get_result_df(automl_run)\n", + "best_dnn_run_id = summary_df[\"run_id\"].iloc[0]\n", + "best_dnn_run = Run(experiment, best_dnn_run_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_dir = \"Model\" # Local folder where the model will be stored temporarily\n", + "if not os.path.isdir(model_dir):\n", + " os.mkdir(model_dir)\n", + "\n", + "best_dnn_run.download_file(\"outputs/model.pkl\", model_dir + \"/model.pkl\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Register the model in your Azure Machine Learning Workspace. If you previously registered a model, please make sure to delete it so as to replace it with this new model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Register the model\n", + "model_name = \"textDNN-20News\"\n", + "model = Model.register(\n", + " model_path=model_dir + \"/model.pkl\", model_name=model_name, tags=None, workspace=ws\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluate on Test Data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use the best fitted model from the AutoML Run to make predictions on the test set. \n", + "\n", + "Test set schema should match that of the training set." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, blobstore_datadir + \"/test_data.csv\")]\n", + ")\n", + "\n", + "# preview the first 3 rows of the dataset\n", + "test_dataset.take(3).to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_experiment = Experiment(ws, experiment_name + \"_test\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "script_folder = os.path.join(os.getcwd(), \"inference\")\n", + "os.makedirs(script_folder, exist_ok=True)\n", + "shutil.copy(\"infer.py\", script_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_run = run_inference(\n", + " test_experiment,\n", + " compute_target,\n", + " script_folder,\n", + " best_dnn_run,\n", + " test_dataset,\n", + " target_column_name,\n", + " model_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Display computed metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RunDetails(test_run).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_run.wait_for_completion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.Series(test_run.get_metrics())" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "anshirga" + } + ], + "compute": [ + "AML Compute" + ], + "datasets": [ + "None" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "None" + ], + "friendly_name": "DNN Text Featurization", + "index_order": 2, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + }, + "tags": [ + "None" ], - "metadata": { - "authors": [ - { - "name": "anshirga" - } - ], - "compute": [ - "AML Compute" - ], - "datasets": [ - "None" - ], - "deployment": [ - "None" - ], - "exclude_from_index": false, - "framework": [ - "None" - ], - "friendly_name": "DNN Text Featurization", - "index_order": 2, - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - }, - "tags": [ - "None" - ], - "task": "Text featurization using DNNs for classification" - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "task": "Text featurization using DNNs for classification" + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/helper.py b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/helper.py index 66a125492..90d67f83a 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/helper.py +++ b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/helper.py @@ -4,52 +4,65 @@ from azureml.core.run import Run -def run_inference(test_experiment, compute_target, script_folder, train_run, - test_dataset, target_column_name, model_name): +def run_inference( + test_experiment, + compute_target, + script_folder, + train_run, + test_dataset, + target_column_name, + model_name, +): inference_env = train_run.get_environment() - est = Estimator(source_directory=script_folder, - entry_script='infer.py', - script_params={ - '--target_column_name': target_column_name, - '--model_name': model_name - }, - inputs=[ - test_dataset.as_named_input('test_data') - ], - compute_target=compute_target, - environment_definition=inference_env) + est = Estimator( + source_directory=script_folder, + entry_script="infer.py", + script_params={ + "--target_column_name": target_column_name, + "--model_name": model_name, + }, + inputs=[test_dataset.as_named_input("test_data")], + compute_target=compute_target, + environment_definition=inference_env, + ) run = test_experiment.submit( - est, tags={ - 'training_run_id': train_run.id, - 'run_algorithm': train_run.properties['run_algorithm'], - 'valid_score': train_run.properties['score'], - 'primary_metric': train_run.properties['primary_metric'] - }) - - run.log("run_algorithm", run.tags['run_algorithm']) + est, + tags={ + "training_run_id": train_run.id, + "run_algorithm": train_run.properties["run_algorithm"], + "valid_score": train_run.properties["score"], + "primary_metric": train_run.properties["primary_metric"], + }, + ) + + run.log("run_algorithm", run.tags["run_algorithm"]) return run def get_result_df(remote_run): children = list(remote_run.get_children(recursive=True)) - summary_df = pd.DataFrame(index=['run_id', 'run_algorithm', - 'primary_metric', 'Score']) + summary_df = pd.DataFrame( + index=["run_id", "run_algorithm", "primary_metric", "Score"] + ) goal_minimize = False for run in children: - if('run_algorithm' in run.properties and 'score' in run.properties): - summary_df[run.id] = [run.id, run.properties['run_algorithm'], - run.properties['primary_metric'], - float(run.properties['score'])] - if('goal' in run.properties): - goal_minimize = run.properties['goal'].split('_')[-1] == 'min' + if "run_algorithm" in run.properties and "score" in run.properties: + summary_df[run.id] = [ + run.id, + run.properties["run_algorithm"], + run.properties["primary_metric"], + float(run.properties["score"]), + ] + if "goal" in run.properties: + goal_minimize = run.properties["goal"].split("_")[-1] == "min" summary_df = summary_df.T.sort_values( - 'Score', - ascending=goal_minimize).drop_duplicates(['run_algorithm']) - summary_df = summary_df.set_index('run_algorithm') + "Score", ascending=goal_minimize + ).drop_duplicates(["run_algorithm"]) + summary_df = summary_df.set_index("run_algorithm") return summary_df diff --git a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/infer.py b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/infer.py index cd3f8257f..28fd10b37 100644 --- a/how-to-use-azureml/automated-machine-learning/classification-text-dnn/infer.py +++ b/how-to-use-azureml/automated-machine-learning/classification-text-dnn/infer.py @@ -12,19 +12,22 @@ parser = argparse.ArgumentParser() parser.add_argument( - '--target_column_name', type=str, dest='target_column_name', - help='Target Column Name') + "--target_column_name", + type=str, + dest="target_column_name", + help="Target Column Name", +) parser.add_argument( - '--model_name', type=str, dest='model_name', - help='Name of registered model') + "--model_name", type=str, dest="model_name", help="Name of registered model" +) args = parser.parse_args() target_column_name = args.target_column_name model_name = args.model_name -print('args passed are: ') -print('Target column name: ', target_column_name) -print('Name of registered model: ', model_name) +print("args passed are: ") +print("Target column name: ", target_column_name) +print("Name of registered model: ", model_name) model_path = Model.get_model_path(model_name) # deserialize the model file back into a sklearn model @@ -32,13 +35,16 @@ run = Run.get_context() # get input dataset by name -test_dataset = run.input_datasets['test_data'] +test_dataset = run.input_datasets["test_data"] -X_test_df = test_dataset.drop_columns(columns=[target_column_name]) \ - .to_pandas_dataframe() -y_test_df = test_dataset.with_timestamp_columns(None) \ - .keep_columns(columns=[target_column_name]) \ - .to_pandas_dataframe() +X_test_df = test_dataset.drop_columns( + columns=[target_column_name] +).to_pandas_dataframe() +y_test_df = ( + test_dataset.with_timestamp_columns(None) + .keep_columns(columns=[target_column_name]) + .to_pandas_dataframe() +) predicted = model.predict_proba(X_test_df) @@ -47,11 +53,13 @@ # Use the AutoML scoring module train_labels = model.classes_ -class_labels = np.unique(np.concatenate((y_test_df.values, np.reshape(train_labels, (-1, 1))))) +class_labels = np.unique( + np.concatenate((y_test_df.values, np.reshape(train_labels, (-1, 1)))) +) classification_metrics = list(constants.CLASSIFICATION_SCALAR_SET) -scores = scoring.score_classification(y_test_df.values, predicted, - classification_metrics, - class_labels, train_labels) +scores = scoring.score_classification( + y_test_df.values, predicted, classification_metrics, class_labels, train_labels +) print("scores:") print(scores) diff --git a/how-to-use-azureml/automated-machine-learning/continuous-retraining/auto-ml-continuous-retraining.ipynb b/how-to-use-azureml/automated-machine-learning/continuous-retraining/auto-ml-continuous-retraining.ipynb index 497f8b75a..edd11ff77 100644 --- a/how-to-use-azureml/automated-machine-learning/continuous-retraining/auto-ml-continuous-retraining.ipynb +++ b/how-to-use-azureml/automated-machine-learning/continuous-retraining/auto-ml-continuous-retraining.ipynb @@ -1,572 +1,585 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved. \n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/continous-retraining/auto-ml-continuous-retraining.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning \n", - "**Continuous retraining using Pipelines and Time-Series TabularDataset**\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "2. [Setup](#Setup)\n", - "3. [Compute](#Compute)\n", - "4. [Run Configuration](#Run-Configuration)\n", - "5. [Data Ingestion Pipeline](#Data-Ingestion-Pipeline)\n", - "6. [Training Pipeline](#Training-Pipeline)\n", - "7. [Publish Retraining Pipeline and Schedule](#Publish-Retraining-Pipeline-and-Schedule)\n", - "8. [Test Retraining](#Test-Retraining)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "In this example we use AutoML and Pipelines to enable contious retraining of a model based on updates to the training dataset. We will create two pipelines, the first one to demonstrate a training dataset that gets updated over time. We leverage time-series capabilities of `TabularDataset` to achieve this. The second pipeline utilizes pipeline `Schedule` to trigger continuous retraining. \n", - "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", - "In this notebook you will learn how to:\n", - "* Create an Experiment in an existing Workspace.\n", - "* Configure AutoML using AutoMLConfig.\n", - "* Create data ingestion pipeline to update a time-series based TabularDataset\n", - "* Create training pipeline to prepare data, run AutoML, register the model and setup pipeline triggers.\n", - "\n", - "## Setup\n", - "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "from sklearn import datasets\n", - "\n", - "import azureml.core\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.core.workspace import Workspace\n", - "from azureml.train.automl import AutoMLConfig" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Accessing the Azure ML workspace requires authentication with Azure.\n", - "\n", - "The default authentication is interactive authentication using the default tenant. Executing the ws = Workspace.from_config() line in the cell below will prompt for authentication the first time that it is run.\n", - "\n", - "If you have multiple Azure tenants, you can specify the tenant by replacing the ws = Workspace.from_config() line in the cell below with the following:\n", - "```\n", - "from azureml.core.authentication import InteractiveLoginAuthentication\n", - "auth = InteractiveLoginAuthentication(tenant_id = 'mytenantid')\n", - "ws = Workspace.from_config(auth = auth)\n", - "```\n", - "If you need to run in an environment where interactive login is not possible, you can use Service Principal authentication by replacing the ws = Workspace.from_config() line in the cell below with the following:\n", - "```\n", - "from azureml.core.authentication import ServicePrincipalAuthentication\n", - "auth = auth = ServicePrincipalAuthentication('mytenantid', 'myappid', 'mypassword')\n", - "ws = Workspace.from_config(auth = auth)\n", - "```\n", - "For more details, see aka.ms/aml-notebook-auth" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "dstor = ws.get_default_datastore()\n", - "\n", - "# Choose a name for the run history container in the workspace.\n", - "experiment_name = 'retrain-noaaweather'\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output['Subscription ID'] = ws.subscription_id\n", - "output['Workspace'] = ws.name\n", - "output['Resource Group'] = ws.resource_group\n", - "output['Location'] = ws.location\n", - "output['Run History Name'] = experiment_name\n", - "pd.set_option('display.max_colwidth', -1)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Compute \n", - "\n", - "#### Create or Attach existing AmlCompute\n", - "\n", - "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", - "\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your CPU cluster\n", - "amlcompute_cluster_name = \"cont-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print('Found existing cluster, use it.')\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(vm_size='STANDARD_DS12_V2',\n", - " max_nodes=4)\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.runconfig import CondaDependencies, RunConfiguration\n", - "\n", - "# create a new RunConfig object\n", - "conda_run_config = RunConfiguration(framework=\"python\")\n", - "\n", - "# Set compute target to AmlCompute\n", - "conda_run_config.target = compute_target\n", - "\n", - "conda_run_config.environment.docker.enabled = True\n", - "\n", - "cd = CondaDependencies.create(pip_packages=['azureml-sdk[automl]', 'applicationinsights', 'azureml-opendatasets', 'azureml-defaults'], \n", - " conda_packages=['numpy==1.16.2'], \n", - " pin_sdk_version=False)\n", - "conda_run_config.environment.python.conda_dependencies = cd\n", - "\n", - "print('run config is ready')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data Ingestion Pipeline \n", - "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You can replace this with your own dataset, or you can skip this pipeline if you already have a time-series based `TabularDataset`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# The name and target column of the Dataset to create \n", - "dataset = \"NOAA-Weather-DS4\"\n", - "target_column_name = \"temperature\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### Upload Data Step\n", - "The data ingestion pipeline has a single step with a script to query the latest weather data and upload it to the blob store. During the first run, the script will create and register a time-series based `TabularDataset` with the past one week of weather data. For each subsequent run, the script will create a partition in the blob store by querying NOAA for new weather data since the last modified time of the dataset (`dataset.data_changed_time`) and creating a data.csv file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline, PipelineParameter\n", - "from azureml.pipeline.steps import PythonScriptStep\n", - "\n", - "ds_name = PipelineParameter(name=\"ds_name\", default_value=dataset)\n", - "upload_data_step = PythonScriptStep(script_name=\"upload_weather_data.py\", \n", - " allow_reuse=False,\n", - " name=\"upload_weather_data\",\n", - " arguments=[\"--ds_name\", ds_name],\n", - " compute_target=compute_target, \n", - " runconfig=conda_run_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit Pipeline Run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_pipeline = Pipeline(\n", - " description=\"pipeline_with_uploaddata\",\n", - " workspace=ws, \n", - " steps=[upload_data_step])\n", - "data_pipeline_run = experiment.submit(data_pipeline, pipeline_parameters={\"ds_name\":dataset})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_pipeline_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Training Pipeline\n", - "### Prepare Training Data Step\n", - "\n", - "Script to check if new data is available since the model was last trained. If no new data is available, we cancel the remaining pipeline steps. We need to set allow_reuse flag to False to allow the pipeline to run even when inputs don't change. We also need the name of the model to check the time the model was last trained." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import PipelineData\n", - "\n", - "# The model name with which to register the trained model in the workspace.\n", - "model_name = PipelineParameter(\"model_name\", default_value=\"noaaweatherds\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_prep_step = PythonScriptStep(script_name=\"check_data.py\", \n", - " allow_reuse=False,\n", - " name=\"check_data\",\n", - " arguments=[\"--ds_name\", ds_name,\n", - " \"--model_name\", model_name],\n", - " compute_target=compute_target, \n", - " runconfig=conda_run_config)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Dataset\n", - "train_ds = Dataset.get_by_name(ws, dataset)\n", - "train_ds = train_ds.drop_columns([\"partition_date\"])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### AutoMLStep\n", - "Create an AutoMLConfig and a training step." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.train.automl import AutoMLConfig\n", - "from azureml.pipeline.steps import AutoMLStep\n", - "\n", - "automl_settings = {\n", - " \"iteration_timeout_minutes\": 10,\n", - " \"experiment_timeout_hours\": 0.25,\n", - " \"n_cross_validations\": 3,\n", - " \"primary_metric\": 'normalized_root_mean_squared_error',\n", - " \"max_concurrent_iterations\": 3,\n", - " \"max_cores_per_iteration\": -1,\n", - " \"verbosity\": logging.INFO,\n", - " \"enable_early_stopping\": True\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'regression',\n", - " debug_log = 'automl_errors.log',\n", - " path = \".\",\n", - " compute_target=compute_target,\n", - " training_data = train_ds,\n", - " label_column_name = target_column_name,\n", - " **automl_settings\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import PipelineData, TrainingOutput\n", - "\n", - "metrics_output_name = 'metrics_output'\n", - "best_model_output_name = 'best_model_output'\n", - "\n", - "metrics_data = PipelineData(name='metrics_data',\n", - " datastore=dstor,\n", - " pipeline_output_name=metrics_output_name,\n", - " training_output=TrainingOutput(type='Metrics'))\n", - "model_data = PipelineData(name='model_data',\n", - " datastore=dstor,\n", - " pipeline_output_name=best_model_output_name,\n", - " training_output=TrainingOutput(type='Model'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_step = AutoMLStep(\n", - " name='automl_module',\n", - " automl_config=automl_config,\n", - " outputs=[metrics_data, model_data],\n", - " allow_reuse=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register Model Step\n", - "Script to register the model to the workspace. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "register_model_step = PythonScriptStep(script_name=\"register_model.py\",\n", - " name=\"register_model\",\n", - " allow_reuse=False,\n", - " arguments=[\"--model_name\", model_name, \"--model_path\", model_data, \"--ds_name\", ds_name],\n", - " inputs=[model_data],\n", - " compute_target=compute_target,\n", - " runconfig=conda_run_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit Pipeline Run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_pipeline = Pipeline(\n", - " description=\"training_pipeline\",\n", - " workspace=ws, \n", - " steps=[data_prep_step, automl_step, register_model_step])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_pipeline_run = experiment.submit(training_pipeline, pipeline_parameters={\n", - " \"ds_name\": dataset, \"model_name\": \"noaaweatherds\"})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_pipeline_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Publish Retraining Pipeline and Schedule\n", - "Once we are happy with the pipeline, we can publish the training pipeline to the workspace and create a schedule to trigger on blob change. The schedule polls the blob store where the data is being uploaded and runs the retraining pipeline if there is a data change. A new version of the model will be registered to the workspace once the run is complete." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pipeline_name = \"Retraining-Pipeline-NOAAWeather\"\n", - "\n", - "published_pipeline = training_pipeline.publish(\n", - " name=pipeline_name, \n", - " description=\"Pipeline that retrains AutoML model\")\n", - "\n", - "published_pipeline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Schedule\n", - "schedule = Schedule.create(workspace=ws, name=\"RetrainingSchedule\",\n", - " pipeline_parameters={\"ds_name\": dataset, \"model_name\": \"noaaweatherds\"},\n", - " pipeline_id=published_pipeline.id, \n", - " experiment_name=experiment_name, \n", - " datastore=dstor,\n", - " wait_for_provisioning=True,\n", - " polling_interval=1440)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Test Retraining\n", - "Here we setup the data ingestion pipeline to run on a schedule, to verify that the retraining pipeline runs as expected. \n", - "\n", - "Note: \n", - "* Azure NOAA Weather data is updated daily and retraining will not trigger if there is no new data available. \n", - "* Depending on the polling interval set in the schedule, the retraining may take some time trigger after data ingestion pipeline completes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pipeline_name = \"DataIngestion-Pipeline-NOAAWeather\"\n", - "\n", - "published_pipeline = training_pipeline.publish(\n", - " name=pipeline_name, \n", - " description=\"Pipeline that updates NOAAWeather Dataset\")\n", - "\n", - "published_pipeline" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Schedule\n", - "schedule = Schedule.create(workspace=ws, name=\"RetrainingSchedule-DataIngestion\",\n", - " pipeline_parameters={\"ds_name\":dataset},\n", - " pipeline_id=published_pipeline.id, \n", - " experiment_name=experiment_name, \n", - " datastore=dstor,\n", - " wait_for_provisioning=True,\n", - " polling_interval=1440)" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning \n", + "**Continuous retraining using Pipelines and Time-Series TabularDataset**\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "2. [Setup](#Setup)\n", + "3. [Compute](#Compute)\n", + "4. [Run Configuration](#Run-Configuration)\n", + "5. [Data Ingestion Pipeline](#Data-Ingestion-Pipeline)\n", + "6. [Training Pipeline](#Training-Pipeline)\n", + "7. [Publish Retraining Pipeline and Schedule](#Publish-Retraining-Pipeline-and-Schedule)\n", + "8. [Test Retraining](#Test-Retraining)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "In this example we use AutoML and Pipelines to enable contious retraining of a model based on updates to the training dataset. We will create two pipelines, the first one to demonstrate a training dataset that gets updated over time. We leverage time-series capabilities of `TabularDataset` to achieve this. The second pipeline utilizes pipeline `Schedule` to trigger continuous retraining. \n", + "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", + "In this notebook you will learn how to:\n", + "* Create an Experiment in an existing Workspace.\n", + "* Configure AutoML using AutoMLConfig.\n", + "* Create data ingestion pipeline to update a time-series based TabularDataset\n", + "* Create training pipeline to prepare data, run AutoML, register the model and setup pipeline triggers.\n", + "\n", + "## Setup\n", + "As part of the setup you have already created an Azure ML `Workspace` object. For AutoML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "from matplotlib import pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "from sklearn import datasets\n", + "\n", + "import azureml.core\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Accessing the Azure ML workspace requires authentication with Azure.\n", + "\n", + "The default authentication is interactive authentication using the default tenant. Executing the ws = Workspace.from_config() line in the cell below will prompt for authentication the first time that it is run.\n", + "\n", + "If you have multiple Azure tenants, you can specify the tenant by replacing the ws = Workspace.from_config() line in the cell below with the following:\n", + "```\n", + "from azureml.core.authentication import InteractiveLoginAuthentication\n", + "auth = InteractiveLoginAuthentication(tenant_id = 'mytenantid')\n", + "ws = Workspace.from_config(auth = auth)\n", + "```\n", + "If you need to run in an environment where interactive login is not possible, you can use Service Principal authentication by replacing the ws = Workspace.from_config() line in the cell below with the following:\n", + "```\n", + "from azureml.core.authentication import ServicePrincipalAuthentication\n", + "auth = auth = ServicePrincipalAuthentication('mytenantid', 'myappid', 'mypassword')\n", + "ws = Workspace.from_config(auth = auth)\n", + "```\n", + "For more details, see aka.ms/aml-notebook-auth" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "dstor = ws.get_default_datastore()\n", + "\n", + "# Choose a name for the run history container in the workspace.\n", + "experiment_name = \"retrain-noaaweather\"\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Run History Name\"] = experiment_name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute \n", + "\n", + "#### Create or Attach existing AmlCompute\n", + "\n", + "You will need to create a compute target for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", + "\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "amlcompute_cluster_name = \"cont-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=4\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.runconfig import CondaDependencies, RunConfiguration\n", + "\n", + "# create a new RunConfig object\n", + "conda_run_config = RunConfiguration(framework=\"python\")\n", + "\n", + "# Set compute target to AmlCompute\n", + "conda_run_config.target = compute_target\n", + "\n", + "conda_run_config.environment.docker.enabled = True\n", + "\n", + "cd = CondaDependencies.create(\n", + " pip_packages=[\n", + " \"azureml-sdk[automl]\",\n", + " \"applicationinsights\",\n", + " \"azureml-opendatasets\",\n", + " \"azureml-defaults\",\n", + " ],\n", + " conda_packages=[\"numpy==1.16.2\"],\n", + " pin_sdk_version=False,\n", + ")\n", + "conda_run_config.environment.python.conda_dependencies = cd\n", + "\n", + "print(\"run config is ready\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data Ingestion Pipeline \n", + "For this demo, we will use NOAA weather data from [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). You can replace this with your own dataset, or you can skip this pipeline if you already have a time-series based `TabularDataset`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The name and target column of the Dataset to create\n", + "dataset = \"NOAA-Weather-DS4\"\n", + "target_column_name = \"temperature\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "### Upload Data Step\n", + "The data ingestion pipeline has a single step with a script to query the latest weather data and upload it to the blob store. During the first run, the script will create and register a time-series based `TabularDataset` with the past one week of weather data. For each subsequent run, the script will create a partition in the blob store by querying NOAA for new weather data since the last modified time of the dataset (`dataset.data_changed_time`) and creating a data.csv file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline, PipelineParameter\n", + "from azureml.pipeline.steps import PythonScriptStep\n", + "\n", + "ds_name = PipelineParameter(name=\"ds_name\", default_value=dataset)\n", + "upload_data_step = PythonScriptStep(\n", + " script_name=\"upload_weather_data.py\",\n", + " allow_reuse=False,\n", + " name=\"upload_weather_data\",\n", + " arguments=[\"--ds_name\", ds_name],\n", + " compute_target=compute_target,\n", + " runconfig=conda_run_config,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit Pipeline Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_pipeline = Pipeline(\n", + " description=\"pipeline_with_uploaddata\", workspace=ws, steps=[upload_data_step]\n", + ")\n", + "data_pipeline_run = experiment.submit(\n", + " data_pipeline, pipeline_parameters={\"ds_name\": dataset}\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_pipeline_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Training Pipeline\n", + "### Prepare Training Data Step\n", + "\n", + "Script to check if new data is available since the model was last trained. If no new data is available, we cancel the remaining pipeline steps. We need to set allow_reuse flag to False to allow the pipeline to run even when inputs don't change. We also need the name of the model to check the time the model was last trained." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import PipelineData\n", + "\n", + "# The model name with which to register the trained model in the workspace.\n", + "model_name = PipelineParameter(\"model_name\", default_value=\"noaaweatherds\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_prep_step = PythonScriptStep(\n", + " script_name=\"check_data.py\",\n", + " allow_reuse=False,\n", + " name=\"check_data\",\n", + " arguments=[\"--ds_name\", ds_name, \"--model_name\", model_name],\n", + " compute_target=compute_target,\n", + " runconfig=conda_run_config,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Dataset\n", + "\n", + "train_ds = Dataset.get_by_name(ws, dataset)\n", + "train_ds = train_ds.drop_columns([\"partition_date\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### AutoMLStep\n", + "Create an AutoMLConfig and a training step." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.train.automl import AutoMLConfig\n", + "from azureml.pipeline.steps import AutoMLStep\n", + "\n", + "automl_settings = {\n", + " \"iteration_timeout_minutes\": 10,\n", + " \"experiment_timeout_hours\": 0.25,\n", + " \"n_cross_validations\": 3,\n", + " \"primary_metric\": \"r2_score\",\n", + " \"max_concurrent_iterations\": 3,\n", + " \"max_cores_per_iteration\": -1,\n", + " \"verbosity\": logging.INFO,\n", + " \"enable_early_stopping\": True,\n", + "}\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"regression\",\n", + " debug_log=\"automl_errors.log\",\n", + " path=\".\",\n", + " compute_target=compute_target,\n", + " training_data=train_ds,\n", + " label_column_name=target_column_name,\n", + " **automl_settings,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import PipelineData, TrainingOutput\n", + "\n", + "metrics_output_name = \"metrics_output\"\n", + "best_model_output_name = \"best_model_output\"\n", + "\n", + "metrics_data = PipelineData(\n", + " name=\"metrics_data\",\n", + " datastore=dstor,\n", + " pipeline_output_name=metrics_output_name,\n", + " training_output=TrainingOutput(type=\"Metrics\"),\n", + ")\n", + "model_data = PipelineData(\n", + " name=\"model_data\",\n", + " datastore=dstor,\n", + " pipeline_output_name=best_model_output_name,\n", + " training_output=TrainingOutput(type=\"Model\"),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_step = AutoMLStep(\n", + " name=\"automl_module\",\n", + " automl_config=automl_config,\n", + " outputs=[metrics_data, model_data],\n", + " allow_reuse=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register Model Step\n", + "Script to register the model to the workspace. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "register_model_step = PythonScriptStep(\n", + " script_name=\"register_model.py\",\n", + " name=\"register_model\",\n", + " allow_reuse=False,\n", + " arguments=[\n", + " \"--model_name\",\n", + " model_name,\n", + " \"--model_path\",\n", + " model_data,\n", + " \"--ds_name\",\n", + " ds_name,\n", + " ],\n", + " inputs=[model_data],\n", + " compute_target=compute_target,\n", + " runconfig=conda_run_config,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit Pipeline Run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_pipeline = Pipeline(\n", + " description=\"training_pipeline\",\n", + " workspace=ws,\n", + " steps=[data_prep_step, automl_step, register_model_step],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_pipeline_run = experiment.submit(\n", + " training_pipeline,\n", + " pipeline_parameters={\"ds_name\": dataset, \"model_name\": \"noaaweatherds\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_pipeline_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Publish Retraining Pipeline and Schedule\n", + "Once we are happy with the pipeline, we can publish the training pipeline to the workspace and create a schedule to trigger on blob change. The schedule polls the blob store where the data is being uploaded and runs the retraining pipeline if there is a data change. A new version of the model will be registered to the workspace once the run is complete." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline_name = \"Retraining-Pipeline-NOAAWeather\"\n", + "\n", + "published_pipeline = training_pipeline.publish(\n", + " name=pipeline_name, description=\"Pipeline that retrains AutoML model\"\n", + ")\n", + "\n", + "published_pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Schedule\n", + "\n", + "schedule = Schedule.create(\n", + " workspace=ws,\n", + " name=\"RetrainingSchedule\",\n", + " pipeline_parameters={\"ds_name\": dataset, \"model_name\": \"noaaweatherds\"},\n", + " pipeline_id=published_pipeline.id,\n", + " experiment_name=experiment_name,\n", + " datastore=dstor,\n", + " wait_for_provisioning=True,\n", + " polling_interval=1440,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test Retraining\n", + "Here we setup the data ingestion pipeline to run on a schedule, to verify that the retraining pipeline runs as expected. \n", + "\n", + "Note: \n", + "* Azure NOAA Weather data is updated daily and retraining will not trigger if there is no new data available. \n", + "* Depending on the polling interval set in the schedule, the retraining may take some time trigger after data ingestion pipeline completes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline_name = \"DataIngestion-Pipeline-NOAAWeather\"\n", + "\n", + "published_pipeline = training_pipeline.publish(\n", + " name=pipeline_name, description=\"Pipeline that updates NOAAWeather Dataset\"\n", + ")\n", + "\n", + "published_pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Schedule\n", + "\n", + "schedule = Schedule.create(\n", + " workspace=ws,\n", + " name=\"RetrainingSchedule-DataIngestion\",\n", + " pipeline_parameters={\"ds_name\": dataset},\n", + " pipeline_id=published_pipeline.id,\n", + " experiment_name=experiment_name,\n", + " datastore=dstor,\n", + " wait_for_provisioning=True,\n", + " polling_interval=1440,\n", + ")" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "vivijay" + } ], - "metadata": { - "authors": [ - { - "name": "vivijay" - } - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/continuous-retraining/check_data.py b/how-to-use-azureml/automated-machine-learning/continuous-retraining/check_data.py index 628ea9611..aec68d422 100644 --- a/how-to-use-azureml/automated-machine-learning/continuous-retraining/check_data.py +++ b/how-to-use-azureml/automated-machine-learning/continuous-retraining/check_data.py @@ -31,7 +31,7 @@ model = Model(ws, args.model_name) last_train_time = model.created_time print("Model was last trained on {0}.".format(last_train_time)) -except Exception: +except Exception as e: print("Could not get last model train time.") last_train_time = datetime.min.replace(tzinfo=pytz.UTC) diff --git a/how-to-use-azureml/automated-machine-learning/continuous-retraining/register_model.py b/how-to-use-azureml/automated-machine-learning/continuous-retraining/register_model.py index 4c9a34a7a..aa37ee86b 100644 --- a/how-to-use-azureml/automated-machine-learning/continuous-retraining/register_model.py +++ b/how-to-use-azureml/automated-machine-learning/continuous-retraining/register_model.py @@ -25,9 +25,11 @@ # Register model with training dataset -model = Model.register(workspace=ws, - model_path=args.model_path, - model_name=args.model_name, - datasets=datasets) +model = Model.register( + workspace=ws, + model_path=args.model_path, + model_name=args.model_name, + datasets=datasets, +) print("Registered version {0} of model {1}".format(model.version, model.name)) diff --git a/how-to-use-azureml/automated-machine-learning/continuous-retraining/upload_weather_data.py b/how-to-use-azureml/automated-machine-learning/continuous-retraining/upload_weather_data.py index 444dcbea0..28f30a65b 100644 --- a/how-to-use-azureml/automated-machine-learning/continuous-retraining/upload_weather_data.py +++ b/how-to-use-azureml/automated-machine-learning/continuous-retraining/upload_weather_data.py @@ -16,26 +16,82 @@ else: ws = run.experiment.workspace -usaf_list = ['725724', '722149', '723090', '722159', '723910', '720279', - '725513', '725254', '726430', '720381', '723074', '726682', - '725486', '727883', '723177', '722075', '723086', '724053', - '725070', '722073', '726060', '725224', '725260', '724520', - '720305', '724020', '726510', '725126', '722523', '703333', - '722249', '722728', '725483', '722972', '724975', '742079', - '727468', '722193', '725624', '722030', '726380', '720309', - '722071', '720326', '725415', '724504', '725665', '725424', - '725066'] +usaf_list = [ + "725724", + "722149", + "723090", + "722159", + "723910", + "720279", + "725513", + "725254", + "726430", + "720381", + "723074", + "726682", + "725486", + "727883", + "723177", + "722075", + "723086", + "724053", + "725070", + "722073", + "726060", + "725224", + "725260", + "724520", + "720305", + "724020", + "726510", + "725126", + "722523", + "703333", + "722249", + "722728", + "725483", + "722972", + "724975", + "742079", + "727468", + "722193", + "725624", + "722030", + "726380", + "720309", + "722071", + "720326", + "725415", + "724504", + "725665", + "725424", + "725066", +] def get_noaa_data(start_time, end_time): - columns = ['usaf', 'wban', 'datetime', 'latitude', 'longitude', 'elevation', - 'windAngle', 'windSpeed', 'temperature', 'stationName', 'p_k'] + columns = [ + "usaf", + "wban", + "datetime", + "latitude", + "longitude", + "elevation", + "windAngle", + "windSpeed", + "temperature", + "stationName", + "p_k", + ] isd = NoaaIsdWeather(start_time, end_time, cols=columns) noaa_df = isd.to_pandas_dataframe() df_filtered = noaa_df[noaa_df["usaf"].isin(usaf_list)] df_filtered.reset_index(drop=True) - print("Received {0} rows of training data between {1} and {2}".format( - df_filtered.shape[0], start_time, end_time)) + print( + "Received {0} rows of training data between {1} and {2}".format( + df_filtered.shape[0], start_time, end_time + ) + ) return df_filtered @@ -54,11 +110,12 @@ def get_noaa_data(start_time, end_time): try: ds = Dataset.get_by_name(ws, args.ds_name) end_time_last_slice = ds.data_changed_time.replace(tzinfo=None) - print("Dataset {0} last updated on {1}".format(args.ds_name, - end_time_last_slice)) + print("Dataset {0} last updated on {1}".format(args.ds_name, end_time_last_slice)) except Exception: print(traceback.format_exc()) - print("Dataset with name {0} not found, registering new dataset.".format(args.ds_name)) + print( + "Dataset with name {0} not found, registering new dataset.".format(args.ds_name) + ) register_dataset = True end_time = datetime(2021, 5, 1, 0, 0) end_time_last_slice = end_time - relativedelta(weeks=2) @@ -66,26 +123,35 @@ def get_noaa_data(start_time, end_time): train_df = get_noaa_data(end_time_last_slice, end_time) if train_df.size > 0: - print("Received {0} rows of new data after {1}.".format( - train_df.shape[0], end_time_last_slice)) - folder_name = "{}/{:04d}/{:02d}/{:02d}/{:02d}/{:02d}/{:02d}".format(args.ds_name, end_time.year, - end_time.month, end_time.day, - end_time.hour, end_time.minute, - end_time.second) + print( + "Received {0} rows of new data after {1}.".format( + train_df.shape[0], end_time_last_slice + ) + ) + folder_name = "{}/{:04d}/{:02d}/{:02d}/{:02d}/{:02d}/{:02d}".format( + args.ds_name, + end_time.year, + end_time.month, + end_time.day, + end_time.hour, + end_time.minute, + end_time.second, + ) file_path = "{0}/data.csv".format(folder_name) # Add a new partition to the registered dataset os.makedirs(folder_name, exist_ok=True) train_df.to_csv(file_path, index=False) - dstor.upload_files(files=[file_path], - target_path=folder_name, - overwrite=True, - show_progress=True) + dstor.upload_files( + files=[file_path], target_path=folder_name, overwrite=True, show_progress=True + ) else: print("No new data since {0}.".format(end_time_last_slice)) if register_dataset: - ds = Dataset.Tabular.from_delimited_files(dstor.path("{}/**/*.csv".format( - args.ds_name)), partition_format='/{partition_date:yyyy/MM/dd/HH/mm/ss}/data.csv') + ds = Dataset.Tabular.from_delimited_files( + dstor.path("{}/**/*.csv".format(args.ds_name)), + partition_format="/{partition_date:yyyy/MM/dd/HH/mm/ss}/data.csv", + ) ds.register(ws, name=args.ds_name) diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb index 610948ed5..8ca8cdaa7 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb @@ -1,725 +1,725 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Many Models with Backtesting - Automated ML\n", - "**_Backtest many models time series forecasts with Automated Machine Learning_**\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this notebook we are using a synthetic dataset to demonstrate the back testing in many model scenario. This allows us to check historical performance of AutoML on a historical data. To do that we step back on the backtesting period by the data set several times and split the data to train and test sets. Then these data sets are used for training and evaluation of model.
\n", - "\n", - "Thus, it is a quick way of evaluating AutoML as if it was in production. Here, we do not test historical performance of a particular model, for this see the [notebook](../forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb). Instead, the best model for every backtest iteration can be different since AutoML chooses the best model for a given training set.\n", - "![Backtesting](Backtesting.png)\n", - "\n", - "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prerequisites\n", - "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1.0 Set up workspace, datastore, experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003526897 - } - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "import azureml.core\n", - "from azureml.core import Workspace, Datastore\n", - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "from pandas.tseries.frequencies import to_offset\n", - "\n", - "# Set up your workspace\n", - "ws = Workspace.from_config()\n", - "ws.get_details()\n", - "\n", - "# Set up your datastores\n", - "dstore = ws.get_default_datastore()\n", - "\n", - "output = {}\n", - "output[\"SDK version\"] = azureml.core.VERSION\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Default datastore name\"] = dstore.name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook is compatible with Azure ML SDK version 1.35.1 or later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose an experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003540729 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Experiment\n", - "\n", - "experiment = Experiment(ws, \"automl-many-models-backtest\")\n", - "\n", - "print(\"Experiment name: \" + experiment.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.0 Data\n", - "\n", - "#### 2.1 Data generation\n", - "For this notebook we will generate the artificial data set with two [time series IDs](https://docs.microsoft.com/en-us/python/api/azureml-automl-core/azureml.automl.core.forecasting_parameters.forecastingparameters?view=azure-ml-py). Then we will generate backtest folds and will upload it to the default BLOB storage and create a [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simulate data: 2 grains - 700\n", - "TIME_COLNAME = \"date\"\n", - "TARGET_COLNAME = \"value\"\n", - "TIME_SERIES_ID_COLNAME = \"ts_id\"\n", - "\n", - "sample_size = 700\n", - "# Set the random seed for reproducibility of results.\n", - "np.random.seed(20)\n", - "X1 = pd.DataFrame(\n", - " {\n", - " TIME_COLNAME: pd.date_range(start=\"2018-01-01\", periods=sample_size),\n", - " TARGET_COLNAME: np.random.normal(loc=100, scale=20, size=sample_size),\n", - " TIME_SERIES_ID_COLNAME: \"ts_A\",\n", - " }\n", - ")\n", - "X2 = pd.DataFrame(\n", - " {\n", - " TIME_COLNAME: pd.date_range(start=\"2018-01-01\", periods=sample_size),\n", - " TARGET_COLNAME: np.random.normal(loc=100, scale=20, size=sample_size),\n", - " TIME_SERIES_ID_COLNAME: \"ts_B\",\n", - " }\n", - ")\n", - "\n", - "X = pd.concat([X1, X2], ignore_index=True, sort=False)\n", - "print(\"Simulated dataset contains {} rows \\n\".format(X.shape[0]))\n", - "X.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we will generate 8 backtesting folds with backtesting period of 7 days and with the same forecasting horizon. We will add the column \"backtest_iteration\", which will identify the backtesting period by the last training date." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "offset_type = \"7D\"\n", - "NUMBER_OF_BACKTESTS = 8 # number of train/test sets to generate\n", - "\n", - "dfs_train = []\n", - "dfs_test = []\n", - "for ts_id, df_one in X.groupby(TIME_SERIES_ID_COLNAME):\n", - "\n", - " data_end = df_one[TIME_COLNAME].max()\n", - "\n", - " for i in range(NUMBER_OF_BACKTESTS):\n", - " train_cutoff_date = data_end - to_offset(offset_type)\n", - " df_one = df_one.copy()\n", - " df_one[\"backtest_iteration\"] = \"iteration_\" + str(train_cutoff_date)\n", - " train = df_one[df_one[TIME_COLNAME] <= train_cutoff_date]\n", - " test = df_one[\n", - " (df_one[TIME_COLNAME] > train_cutoff_date)\n", - " & (df_one[TIME_COLNAME] <= data_end)\n", - " ]\n", - " data_end = train[TIME_COLNAME].max()\n", - " dfs_train.append(train)\n", - " dfs_test.append(test)\n", - "\n", - "X_train = pd.concat(dfs_train, sort=False, ignore_index=True)\n", - "X_test = pd.concat(dfs_test, sort=False, ignore_index=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 2.2 Create the Tabular Data Set.\n", - "\n", - "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", - "\n", - "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore(class)?view=azure-ml-py) documentation on how to access data from Datastore.\n", - "\n", - "In this next step, we will upload the data and create a TabularDataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.data.dataset_factory import TabularDatasetFactory\n", - "\n", - "ds = ws.get_default_datastore()\n", - "# Upload saved data to the default data store.\n", - "train_data = TabularDatasetFactory.register_pandas_dataframe(\n", - " X_train, target=(ds, \"data_mm\"), name=\"data_train\"\n", - ")\n", - "test_data = TabularDatasetFactory.register_pandas_dataframe(\n", - " X_test, target=(ds, \"data_mm\"), name=\"data_test\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3.0 Build the training pipeline\n", - "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose a compute target\n", - "\n", - "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", - "\n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007037308 - } - }, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "\n", - "# Name your cluster\n", - "compute_name = \"backtest-mm\"\n", - "\n", - "\n", - "if compute_name in ws.compute_targets:\n", - " compute_target = ws.compute_targets[compute_name]\n", - " if compute_target and type(compute_target) is AmlCompute:\n", - " print(\"Found compute target: \" + compute_name)\n", - "else:\n", - " print(\"Creating a new compute target...\")\n", - " provisioning_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", - " )\n", - " # Create the compute target\n", - " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", - "\n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min node count is provided it will use the scale settings for the cluster\n", - " compute_target.wait_for_completion(\n", - " show_output=True, min_node_count=None, timeout_in_minutes=20\n", - " )\n", - "\n", - " # For a more detailed view of current cluster status, use the 'status' property\n", - " print(compute_target.status.serialize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up training parameters\n", - "\n", - "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings including the name of the time column, the maximum forecast horizon, and the partition column name definition. Please note, that in this case we are setting grain_column_names to be the time series ID column plus iteration, because we want to train a separate model for each time series and iteration.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **task** | forecasting |\n", - "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
normalized_root_mean_squared_error
normalized_mean_absolute_error |\n", - "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", - "| **label_column_name** | The name of the label column. |\n", - "| **max_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", - "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", - "| **time_column_name** | The name of your time column. |\n", - "| **grain_column_names** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", - "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", - "| **partition_column_names** | The names of columns used to group your models. For timeseries, the groups must not split up individual time-series. That is, each group must contain one or more whole time-series. |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007061544 - } - }, - "outputs": [], - "source": [ - "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", - " ManyModelsTrainParameters,\n", - ")\n", - "\n", - "partition_column_names = [TIME_SERIES_ID_COLNAME, \"backtest_iteration\"]\n", - "automl_settings = {\n", - " \"task\": \"forecasting\",\n", - " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", - " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", - " \"iterations\": 15,\n", - " \"experiment_timeout_hours\": 0.25, # This also needs to be changed based on the dataset. For larger data set this number needs to be bigger.\n", - " \"label_column_name\": TARGET_COLNAME,\n", - " \"n_cross_validations\": 3,\n", - " \"time_column_name\": TIME_COLNAME,\n", - " \"max_horizon\": 6,\n", - " \"grain_column_names\": partition_column_names,\n", - " \"track_child_runs\": False,\n", - "}\n", - "\n", - "mm_paramters = ManyModelsTrainParameters(\n", - " automl_settings=automl_settings, partition_column_names=partition_column_names\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up many models pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Parallel run step is leveraged to train multiple models at once. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The process_count_per_node is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **experiment** | The experiment used for training. |\n", - "| **train_data** | The file dataset to be used as input to the training run. |\n", - "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long. |\n", - "| **process_count_per_node** | Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance. |\n", - "| **train_pipeline_parameters** | The set of configuration parameters defined in the previous section. |\n", - "\n", - "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", - "\n", - "\n", - "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", - " experiment=experiment,\n", - " train_data=train_data,\n", - " compute_target=compute_target,\n", - " node_count=2,\n", - " process_count_per_node=2,\n", - " run_invocation_timeout=920,\n", - " train_pipeline_parameters=mm_paramters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit the pipeline to run\n", - "Next we submit our pipeline to run. The whole training pipeline takes about 20 minutes using a STANDARD_DS12_V2 VM with our current ParallelRunConfig setting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run = experiment.submit(training_pipeline)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the run status, if training_run is in completed state, continue to next section. Otherwise, check the portal for failures." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4.0 Backtesting\n", - "Now that we selected the best AutoML model for each backtest fold, we will use these models to generate the forecasts and compare with the actuals." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up output dataset for inference data\n", - "Output of inference can be represented as [OutputFileDatasetConfig](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.output_dataset_config.outputdatasetconfig?view=azure-ml-py) object and OutputFileDatasetConfig can be registered as a dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.data import OutputFileDatasetConfig\n", - "\n", - "output_inference_data_ds = OutputFileDatasetConfig(\n", - " name=\"many_models_inference_output\",\n", - " destination=(dstore, \"backtesting/inference_data/\"),\n", - ").register_on_complete(name=\"backtesting_data_ds\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For many models we need to provide the ManyModelsInferenceParameters object.\n", - "\n", - "#### ManyModelsInferenceParameters arguments\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **partition_column_names** | List of column names that identifies groups. |\n", - "| **target_column_name** | \\[Optional\\] Column name only if the inference dataset has the target. |\n", - "| **time_column_name** | Column name only if it is timeseries. |\n", - "| **many_models_run_id** | \\[Optional\\] Many models pipeline run id where models were trained. |\n", - "\n", - "#### get_many_models_batch_inference_steps arguments\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **experiment** | The experiment used for inference run. |\n", - "| **inference_data** | The data to use for inferencing. It should be the same schema as used for training.\n", - "| **compute_target** | The compute target that runs the inference pipeline.|\n", - "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku). |\n", - "| **process_count_per_node** | The number of processes per node.\n", - "| **train_run_id** | \\[Optional\\] The run id of the hierarchy training, by default it is the latest successful training many model run in the experiment. |\n", - "| **train_experiment_name** | \\[Optional\\] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline. |\n", - "| **process_count_per_node** | \\[Optional\\] The number of processes per node, by default it's 4. |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", - "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", - " ManyModelsInferenceParameters,\n", - ")\n", - "\n", - "mm_parameters = ManyModelsInferenceParameters(\n", - " partition_column_names=partition_column_names,\n", - " time_column_name=TIME_COLNAME,\n", - " target_column_name=TARGET_COLNAME,\n", - ")\n", - "\n", - "inference_steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", - " experiment=experiment,\n", - " inference_data=test_data,\n", - " node_count=2,\n", - " process_count_per_node=2,\n", - " compute_target=compute_target,\n", - " run_invocation_timeout=300,\n", - " output_datastore=output_inference_data_ds,\n", - " train_run_id=training_run.id,\n", - " train_experiment_name=training_run.experiment.name,\n", - " inference_pipeline_parameters=mm_parameters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "inference_pipeline = Pipeline(ws, steps=inference_steps)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inference_run = experiment.submit(inference_pipeline)\n", - "inference_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5.0 Retrieve results and calculate metrics\n", - "\n", - "The pipeline returns one file with the predictions for each times series ID and outputs the result to the forecasting_output Blob container. The details of the blob container is listed in 'forecasting_output.txt' under Outputs+logs. \n", - "\n", - "The next code snippet does the following:\n", - "1. Downloads the contents of the output folder that is passed in the parallel run step \n", - "2. Reads the parallel_run_step.txt file that has the predictions as pandas dataframe \n", - "3. Saves the table in csv format and \n", - "4. Displays the top 10 rows of the predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps.utilities import get_output_from_mm_pipeline\n", - "\n", - "forecasting_results_name = \"forecasting_results\"\n", - "forecasting_output_name = \"many_models_inference_output\"\n", - "forecast_file = get_output_from_mm_pipeline(\n", - " inference_run, forecasting_results_name, forecasting_output_name\n", - ")\n", - "df = pd.read_csv(forecast_file, delimiter=\" \", header=None, parse_dates=[0])\n", - "df.columns = list(X_train.columns) + [\"predicted_level\"]\n", - "print(\n", - " \"Prediction has \", df.shape[0], \" rows. Here the first 10 rows are being displayed.\"\n", - ")\n", - "# Save the scv file with header to read it in the next step.\n", - "df.rename(columns={TARGET_COLNAME: \"actual_level\"}, inplace=True)\n", - "df.to_csv(os.path.join(forecasting_results_name, \"forecast.csv\"), index=False)\n", - "df.head(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## View metrics\n", - "We will read in the obtained results and run the helper script, which will generate metrics and create the plots of predicted versus actual values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from assets.score import calculate_scores_and_build_plots\n", - "\n", - "backtesting_results = \"backtesting_mm_results\"\n", - "os.makedirs(backtesting_results, exist_ok=True)\n", - "calculate_scores_and_build_plots(\n", - " forecasting_results_name, backtesting_results, automl_settings\n", - ")\n", - "pd.DataFrame({\"File\": os.listdir(backtesting_results)})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The directory contains a set of files with results:\n", - "- forecast.csv contains forecasts for all backtest iterations. The backtest_iteration column contains iteration identifier with the last training date as a suffix\n", - "- scores.csv contains all metrics. If data set contains several time series, the metrics are given for all combinations of time series id and iterations, as well as scores for all iterations and time series ids, which are marked as \"all_sets\"\n", - "- plots_fcst_vs_actual.pdf contains the predictions vs forecast plots for each iteration and, eash time series is saved as separate plot.\n", - "\n", - "For demonstration purposes we will display the table of metrics for one of the time series with ID \"ts0\". We will create the utility function, which will build the table with metrics." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_metrics_for_ts(all_metrics, ts):\n", - " \"\"\"\n", - " Get the metrics for the time series with ID ts and return it as pandas data frame.\n", - "\n", - " :param all_metrics: The table with all the metrics.\n", - " :param ts: The ID of a time series of interest.\n", - " :return: The pandas DataFrame with metrics for one time series.\n", - " \"\"\"\n", - " results_df = None\n", - " for ts_id, one_series in all_metrics.groupby(\"time_series_id\"):\n", - " if not ts_id.startswith(ts):\n", - " continue\n", - " iteration = ts_id.split(\"|\")[-1]\n", - " df = one_series[[\"metric_name\", \"metric\"]]\n", - " df.rename({\"metric\": iteration}, axis=1, inplace=True)\n", - " df.set_index(\"metric_name\", inplace=True)\n", - " if results_df is None:\n", - " results_df = df\n", - " else:\n", - " results_df = results_df.merge(\n", - " df, how=\"inner\", left_index=True, right_index=True\n", - " )\n", - " results_df.sort_index(axis=1, inplace=True)\n", - " return results_df\n", - "\n", - "\n", - "metrics_df = pd.read_csv(os.path.join(backtesting_results, \"scores.csv\"))\n", - "ts = \"ts_A\"\n", - "get_metrics_for_ts(metrics_df, ts)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Forecast vs actuals plots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import IFrame\n", - "\n", - "IFrame(\"./backtesting_mm_results/plots_fcst_vs_actual.pdf\", width=800, height=300)" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Many Models with Backtesting - Automated ML\n", + "**_Backtest many models time series forecasts with Automated Machine Learning_**\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this notebook we are using a synthetic dataset to demonstrate the back testing in many model scenario. This allows us to check historical performance of AutoML on a historical data. To do that we step back on the backtesting period by the data set several times and split the data to train and test sets. Then these data sets are used for training and evaluation of model.
\n", + "\n", + "Thus, it is a quick way of evaluating AutoML as if it was in production. Here, we do not test historical performance of a particular model, for this see the [notebook](../forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb). Instead, the best model for every backtest iteration can be different since AutoML chooses the best model for a given training set.\n", + "![Backtesting](Backtesting.png)\n", + "\n", + "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prerequisites\n", + "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.0 Set up workspace, datastore, experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003526897 } - ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "categories": [ - "how-to-use-azureml", - "automated-machine-learning" - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import azureml.core\n", + "from azureml.core import Workspace, Datastore\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from pandas.tseries.frequencies import to_offset\n", + "\n", + "# Set up your workspace\n", + "ws = Workspace.from_config()\n", + "ws.get_details()\n", + "\n", + "# Set up your datastores\n", + "dstore = ws.get_default_datastore()\n", + "\n", + "output = {}\n", + "output[\"SDK version\"] = azureml.core.VERSION\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Default datastore name\"] = dstore.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.1 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose an experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003540729 } + }, + "outputs": [], + "source": [ + "from azureml.core import Experiment\n", + "\n", + "experiment = Experiment(ws, \"automl-many-models-backtest\")\n", + "\n", + "print(\"Experiment name: \" + experiment.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.0 Data\n", + "\n", + "#### 2.1 Data generation\n", + "For this notebook we will generate the artificial data set with two [time series IDs](https://docs.microsoft.com/en-us/python/api/azureml-automl-core/azureml.automl.core.forecasting_parameters.forecastingparameters?view=azure-ml-py). Then we will generate backtest folds and will upload it to the default BLOB storage and create a [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# simulate data: 2 grains - 700\n", + "TIME_COLNAME = \"date\"\n", + "TARGET_COLNAME = \"value\"\n", + "TIME_SERIES_ID_COLNAME = \"ts_id\"\n", + "\n", + "sample_size = 700\n", + "# Set the random seed for reproducibility of results.\n", + "np.random.seed(20)\n", + "X1 = pd.DataFrame(\n", + " {\n", + " TIME_COLNAME: pd.date_range(start=\"2018-01-01\", periods=sample_size),\n", + " TARGET_COLNAME: np.random.normal(loc=100, scale=20, size=sample_size),\n", + " TIME_SERIES_ID_COLNAME: \"ts_A\",\n", + " }\n", + ")\n", + "X2 = pd.DataFrame(\n", + " {\n", + " TIME_COLNAME: pd.date_range(start=\"2018-01-01\", periods=sample_size),\n", + " TARGET_COLNAME: np.random.normal(loc=100, scale=20, size=sample_size),\n", + " TIME_SERIES_ID_COLNAME: \"ts_B\",\n", + " }\n", + ")\n", + "\n", + "X = pd.concat([X1, X2], ignore_index=True, sort=False)\n", + "print(\"Simulated dataset contains {} rows \\n\".format(X.shape[0]))\n", + "X.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will generate 8 backtesting folds with backtesting period of 7 days and with the same forecasting horizon. We will add the column \"backtest_iteration\", which will identify the backtesting period by the last training date." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "offset_type = \"7D\"\n", + "NUMBER_OF_BACKTESTS = 8 # number of train/test sets to generate\n", + "\n", + "dfs_train = []\n", + "dfs_test = []\n", + "for ts_id, df_one in X.groupby(TIME_SERIES_ID_COLNAME):\n", + "\n", + " data_end = df_one[TIME_COLNAME].max()\n", + "\n", + " for i in range(NUMBER_OF_BACKTESTS):\n", + " train_cutoff_date = data_end - to_offset(offset_type)\n", + " df_one = df_one.copy()\n", + " df_one[\"backtest_iteration\"] = \"iteration_\" + str(train_cutoff_date)\n", + " train = df_one[df_one[TIME_COLNAME] <= train_cutoff_date]\n", + " test = df_one[\n", + " (df_one[TIME_COLNAME] > train_cutoff_date)\n", + " & (df_one[TIME_COLNAME] <= data_end)\n", + " ]\n", + " data_end = train[TIME_COLNAME].max()\n", + " dfs_train.append(train)\n", + " dfs_test.append(test)\n", + "\n", + "X_train = pd.concat(dfs_train, sort=False, ignore_index=True)\n", + "X_test = pd.concat(dfs_test, sort=False, ignore_index=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.2 Create the Tabular Data Set.\n", + "\n", + "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", + "\n", + "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore(class)?view=azure-ml-py) documentation on how to access data from Datastore.\n", + "\n", + "In this next step, we will upload the data and create a TabularDataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.data.dataset_factory import TabularDatasetFactory\n", + "\n", + "ds = ws.get_default_datastore()\n", + "# Upload saved data to the default data store.\n", + "train_data = TabularDatasetFactory.register_pandas_dataframe(\n", + " X_train, target=(ds, \"data_mm\"), name=\"data_train\"\n", + ")\n", + "test_data = TabularDatasetFactory.register_pandas_dataframe(\n", + " X_test, target=(ds, \"data_mm\"), name=\"data_test\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3.0 Build the training pipeline\n", + "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose a compute target\n", + "\n", + "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", + "\n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007037308 + } + }, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "\n", + "# Name your cluster\n", + "compute_name = \"backtest-mm\"\n", + "\n", + "\n", + "if compute_name in ws.compute_targets:\n", + " compute_target = ws.compute_targets[compute_name]\n", + " if compute_target and type(compute_target) is AmlCompute:\n", + " print(\"Found compute target: \" + compute_name)\n", + "else:\n", + " print(\"Creating a new compute target...\")\n", + " provisioning_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " # Create the compute target\n", + " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", + "\n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min node count is provided it will use the scale settings for the cluster\n", + " compute_target.wait_for_completion(\n", + " show_output=True, min_node_count=None, timeout_in_minutes=20\n", + " )\n", + "\n", + " # For a more detailed view of current cluster status, use the 'status' property\n", + " print(compute_target.status.serialize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up training parameters\n", + "\n", + "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings including the name of the time column, the maximum forecast horizon, and the partition column name definition. Please note, that in this case we are setting grain_column_names to be the time series ID column plus iteration, because we want to train a separate model for each time series and iteration.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **task** | forecasting |\n", + "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
normalized_root_mean_squared_error
normalized_mean_absolute_error |\n", + "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", + "| **label_column_name** | The name of the label column. |\n", + "| **max_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", + "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", + "| **time_column_name** | The name of your time column. |\n", + "| **grain_column_names** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", + "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", + "| **partition_column_names** | The names of columns used to group your models. For timeseries, the groups must not split up individual time-series. That is, each group must contain one or more whole time-series. |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007061544 + } + }, + "outputs": [], + "source": [ + "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", + " ManyModelsTrainParameters,\n", + ")\n", + "\n", + "partition_column_names = [TIME_SERIES_ID_COLNAME, \"backtest_iteration\"]\n", + "automl_settings = {\n", + " \"task\": \"forecasting\",\n", + " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", + " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", + " \"iterations\": 15,\n", + " \"experiment_timeout_hours\": 0.25, # This also needs to be changed based on the dataset. For larger data set this number needs to be bigger.\n", + " \"label_column_name\": TARGET_COLNAME,\n", + " \"n_cross_validations\": 3,\n", + " \"time_column_name\": TIME_COLNAME,\n", + " \"max_horizon\": 6,\n", + " \"grain_column_names\": partition_column_names,\n", + " \"track_child_runs\": False,\n", + "}\n", + "\n", + "mm_paramters = ManyModelsTrainParameters(\n", + " automl_settings=automl_settings, partition_column_names=partition_column_names\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up many models pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parallel run step is leveraged to train multiple models at once. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The process_count_per_node is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **experiment** | The experiment used for training. |\n", + "| **train_data** | The file dataset to be used as input to the training run. |\n", + "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long. |\n", + "| **process_count_per_node** | Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance. |\n", + "| **train_pipeline_parameters** | The set of configuration parameters defined in the previous section. |\n", + "\n", + "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", + "\n", + "\n", + "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", + " experiment=experiment,\n", + " train_data=train_data,\n", + " compute_target=compute_target,\n", + " node_count=2,\n", + " process_count_per_node=2,\n", + " run_invocation_timeout=920,\n", + " train_pipeline_parameters=mm_paramters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit the pipeline to run\n", + "Next we submit our pipeline to run. The whole training pipeline takes about 20 minutes using a STANDARD_DS12_V2 VM with our current ParallelRunConfig setting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run = experiment.submit(training_pipeline)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the run status, if training_run is in completed state, continue to next section. Otherwise, check the portal for failures." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4.0 Backtesting\n", + "Now that we selected the best AutoML model for each backtest fold, we will use these models to generate the forecasts and compare with the actuals." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up output dataset for inference data\n", + "Output of inference can be represented as [OutputFileDatasetConfig](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.output_dataset_config.outputdatasetconfig?view=azure-ml-py) object and OutputFileDatasetConfig can be registered as a dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.data import OutputFileDatasetConfig\n", + "\n", + "output_inference_data_ds = OutputFileDatasetConfig(\n", + " name=\"many_models_inference_output\",\n", + " destination=(dstore, \"backtesting/inference_data/\"),\n", + ").register_on_complete(name=\"backtesting_data_ds\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For many models we need to provide the ManyModelsInferenceParameters object.\n", + "\n", + "#### ManyModelsInferenceParameters arguments\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **partition_column_names** | List of column names that identifies groups. |\n", + "| **target_column_name** | \\[Optional\\] Column name only if the inference dataset has the target. |\n", + "| **time_column_name** | Column name only if it is timeseries. |\n", + "| **many_models_run_id** | \\[Optional\\] Many models pipeline run id where models were trained. |\n", + "\n", + "#### get_many_models_batch_inference_steps arguments\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **experiment** | The experiment used for inference run. |\n", + "| **inference_data** | The data to use for inferencing. It should be the same schema as used for training.\n", + "| **compute_target** | The compute target that runs the inference pipeline.|\n", + "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku). |\n", + "| **process_count_per_node** | The number of processes per node.\n", + "| **train_run_id** | \\[Optional\\] The run id of the hierarchy training, by default it is the latest successful training many model run in the experiment. |\n", + "| **train_experiment_name** | \\[Optional\\] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline. |\n", + "| **process_count_per_node** | \\[Optional\\] The number of processes per node, by default it's 4. |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", + "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", + " ManyModelsInferenceParameters,\n", + ")\n", + "\n", + "mm_parameters = ManyModelsInferenceParameters(\n", + " partition_column_names=partition_column_names,\n", + " time_column_name=TIME_COLNAME,\n", + " target_column_name=TARGET_COLNAME,\n", + ")\n", + "\n", + "inference_steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", + " experiment=experiment,\n", + " inference_data=test_data,\n", + " node_count=2,\n", + " process_count_per_node=2,\n", + " compute_target=compute_target,\n", + " run_invocation_timeout=300,\n", + " output_datastore=output_inference_data_ds,\n", + " train_run_id=training_run.id,\n", + " train_experiment_name=training_run.experiment.name,\n", + " inference_pipeline_parameters=mm_parameters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "inference_pipeline = Pipeline(ws, steps=inference_steps)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inference_run = experiment.submit(inference_pipeline)\n", + "inference_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5.0 Retrieve results and calculate metrics\n", + "\n", + "The pipeline returns one file with the predictions for each times series ID and outputs the result to the forecasting_output Blob container. The details of the blob container is listed in 'forecasting_output.txt' under Outputs+logs. \n", + "\n", + "The next code snippet does the following:\n", + "1. Downloads the contents of the output folder that is passed in the parallel run step \n", + "2. Reads the parallel_run_step.txt file that has the predictions as pandas dataframe \n", + "3. Saves the table in csv format and \n", + "4. Displays the top 10 rows of the predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps.utilities import get_output_from_mm_pipeline\n", + "\n", + "forecasting_results_name = \"forecasting_results\"\n", + "forecasting_output_name = \"many_models_inference_output\"\n", + "forecast_file = get_output_from_mm_pipeline(\n", + " inference_run, forecasting_results_name, forecasting_output_name\n", + ")\n", + "df = pd.read_csv(forecast_file, delimiter=\" \", header=None, parse_dates=[0])\n", + "df.columns = list(X_train.columns) + [\"predicted_level\"]\n", + "print(\n", + " \"Prediction has \", df.shape[0], \" rows. Here the first 10 rows are being displayed.\"\n", + ")\n", + "# Save the scv file with header to read it in the next step.\n", + "df.rename(columns={TARGET_COLNAME: \"actual_level\"}, inplace=True)\n", + "df.to_csv(os.path.join(forecasting_results_name, \"forecast.csv\"), index=False)\n", + "df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## View metrics\n", + "We will read in the obtained results and run the helper script, which will generate metrics and create the plots of predicted versus actual values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from assets.score import calculate_scores_and_build_plots\n", + "\n", + "backtesting_results = \"backtesting_mm_results\"\n", + "os.makedirs(backtesting_results, exist_ok=True)\n", + "calculate_scores_and_build_plots(\n", + " forecasting_results_name, backtesting_results, automl_settings\n", + ")\n", + "pd.DataFrame({\"File\": os.listdir(backtesting_results)})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The directory contains a set of files with results:\n", + "- forecast.csv contains forecasts for all backtest iterations. The backtest_iteration column contains iteration identifier with the last training date as a suffix\n", + "- scores.csv contains all metrics. If data set contains several time series, the metrics are given for all combinations of time series id and iterations, as well as scores for all iterations and time series ids, which are marked as \"all_sets\"\n", + "- plots_fcst_vs_actual.pdf contains the predictions vs forecast plots for each iteration and, eash time series is saved as separate plot.\n", + "\n", + "For demonstration purposes we will display the table of metrics for one of the time series with ID \"ts0\". We will create the utility function, which will build the table with metrics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_metrics_for_ts(all_metrics, ts):\n", + " \"\"\"\n", + " Get the metrics for the time series with ID ts and return it as pandas data frame.\n", + "\n", + " :param all_metrics: The table with all the metrics.\n", + " :param ts: The ID of a time series of interest.\n", + " :return: The pandas DataFrame with metrics for one time series.\n", + " \"\"\"\n", + " results_df = None\n", + " for ts_id, one_series in all_metrics.groupby(\"time_series_id\"):\n", + " if not ts_id.startswith(ts):\n", + " continue\n", + " iteration = ts_id.split(\"|\")[-1]\n", + " df = one_series[[\"metric_name\", \"metric\"]]\n", + " df.rename({\"metric\": iteration}, axis=1, inplace=True)\n", + " df.set_index(\"metric_name\", inplace=True)\n", + " if results_df is None:\n", + " results_df = df\n", + " else:\n", + " results_df = results_df.merge(\n", + " df, how=\"inner\", left_index=True, right_index=True\n", + " )\n", + " results_df.sort_index(axis=1, inplace=True)\n", + " return results_df\n", + "\n", + "\n", + "metrics_df = pd.read_csv(os.path.join(backtesting_results, \"scores.csv\"))\n", + "ts = \"ts_A\"\n", + "get_metrics_for_ts(metrics_df, ts)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Forecast vs actuals plots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import IFrame\n", + "\n", + "IFrame(\"./backtesting_mm_results/plots_fcst_vs_actual.pdf\", width=800, height=300)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "categories": [ + "how-to-use-azureml", + "automated-machine-learning" + ], + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/update_env.yml b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/update_env.yml new file mode 100644 index 000000000..d0b193dab --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-many-models/update_env.yml @@ -0,0 +1,3 @@ +dependencies: +- pip: + - azureml-contrib-automl-pipeline-steps diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb index 64cada912..81104a4f9 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-backtest-single-model/auto-ml-forecasting-backtest-single-model.ipynb @@ -1,719 +1,719 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License.\n", - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/automl-forecasting-function.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated MachineLearning\n", - "_**The model backtesting**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "2. [Setup](#Setup)\n", - "3. [Data](#Data)\n", - "4. [Prepare remote compute and data.](#prepare_remote)\n", - "5. [Create the configuration for AutoML backtesting](#train)\n", - "6. [Backtest AutoML](#backtest_automl)\n", - "7. [View metrics](#Metrics)\n", - "8. [Backtest the best model](#backtest_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "Model backtesting is used to evaluate its performance on historical data. To do that we step back on the backtesting period by the data set several times and split the data to train and test sets. Then these data sets are used for training and evaluation of model.
\n", - "This notebook is intended to demonstrate backtesting on a single model, this is the best solution for small data sets with a few or one time series in it. For scenarios where we would like to choose the best AutoML model for every backtest iteration, please see [AutoML Forecasting Backtest Many Models Example](../forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb) notebook.\n", - "![Backtesting](Backtesting.png)\n", - "This notebook demonstrates two ways of backtesting:\n", - "- AutoML backtesting: we will train separate AutoML models for historical data\n", - "- Model backtesting: from the first run we will select the best model trained on the most recent data, retrain it on the past data and evaluate." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import numpy as np\n", - "import pandas as pd\n", - "import shutil\n", - "\n", - "import azureml.core\n", - "from azureml.core import Experiment, Model, Workspace" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook is compatible with Azure ML SDK version 1.35.1 or later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As part of the setup you have already created a Workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "output = {}\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"SKU\"] = ws.sku\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data\n", - "For the demonstration purposes we will simulate one year of daily data. To do this we need to specify the following parameters: time column name, time series ID column names and label column name. Our intention is to forecast for two weeks ahead." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TIME_COLUMN_NAME = \"date\"\n", - "TIME_SERIES_ID_COLUMN_NAMES = \"time_series_id\"\n", - "LABEL_COLUMN_NAME = \"y\"\n", - "FORECAST_HORIZON = 14\n", - "FREQUENCY = \"D\"\n", - "\n", - "\n", - "def simulate_timeseries_data(\n", - " train_len: int,\n", - " test_len: int,\n", - " time_column_name: str,\n", - " target_column_name: str,\n", - " time_series_id_column_name: str,\n", - " time_series_number: int = 1,\n", - " freq: str = \"H\",\n", - "):\n", - " \"\"\"\n", - " Return the time series of designed length.\n", - "\n", - " :param train_len: The length of training data (one series).\n", - " :type train_len: int\n", - " :param test_len: The length of testing data (one series).\n", - " :type test_len: int\n", - " :param time_column_name: The desired name of a time column.\n", - " :type time_column_name: str\n", - " :param time_series_number: The number of time series in the data set.\n", - " :type time_series_number: int\n", - " :param freq: The frequency string representing pandas offset.\n", - " see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html\n", - " :type freq: str\n", - " :returns: the tuple of train and test data sets.\n", - " :rtype: tuple\n", - "\n", - " \"\"\"\n", - " data_train = [] # type: List[pd.DataFrame]\n", - " data_test = [] # type: List[pd.DataFrame]\n", - " data_length = train_len + test_len\n", - " for i in range(time_series_number):\n", - " X = pd.DataFrame(\n", - " {\n", - " time_column_name: pd.date_range(\n", - " start=\"2000-01-01\", periods=data_length, freq=freq\n", - " ),\n", - " target_column_name: np.arange(data_length).astype(float)\n", - " + np.random.rand(data_length)\n", - " + i * 5,\n", - " \"ext_predictor\": np.asarray(range(42, 42 + data_length)),\n", - " time_series_id_column_name: np.repeat(\"ts{}\".format(i), data_length),\n", - " }\n", - " )\n", - " data_train.append(X[:train_len])\n", - " data_test.append(X[train_len:])\n", - " train = pd.concat(data_train)\n", - " label_train = train.pop(target_column_name).values\n", - " test = pd.concat(data_test)\n", - " label_test = test.pop(target_column_name).values\n", - " return train, label_train, test, label_test\n", - "\n", - "\n", - "n_test_periods = FORECAST_HORIZON\n", - "n_train_periods = 365\n", - "X_train, y_train, X_test, y_test = simulate_timeseries_data(\n", - " train_len=n_train_periods,\n", - " test_len=n_test_periods,\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " target_column_name=LABEL_COLUMN_NAME,\n", - " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAMES,\n", - " time_series_number=2,\n", - " freq=FREQUENCY,\n", - ")\n", - "X_train[LABEL_COLUMN_NAME] = y_train" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's see what the training data looks like." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_train.tail()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prepare remote compute and data. \n", - "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace), is paired with the storage account, which contains the default data store. We will use it to upload the artificial data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.data.dataset_factory import TabularDatasetFactory\n", - "\n", - "ds = ws.get_default_datastore()\n", - "# Upload saved data to the default data store.\n", - "train_data = TabularDatasetFactory.register_pandas_dataframe(\n", - " X_train, target=(ds, \"data\"), name=\"data_backtest\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You will need to create a compute target for backtesting. In this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute), you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your CPU cluster\n", - "amlcompute_cluster_name = \"backtest-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print(\"Found existing cluster, use it.\")\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", - " )\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create the configuration for AutoML backtesting \n", - "\n", - "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings including the name of the time column, the maximum forecast horizon, and the partition column name definition.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **task** | forecasting |\n", - "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
normalized_root_mean_squared_error
normalized_mean_absolute_error |\n", - "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", - "| **label_column_name** | The name of the label column. |\n", - "| **max_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", - "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", - "| **time_column_name** | The name of your time column. |\n", - "| **grain_column_names** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "automl_settings = {\n", - " \"task\": \"forecasting\",\n", - " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", - " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", - " \"iterations\": 15,\n", - " \"experiment_timeout_hours\": 1, # This also needs to be changed based on the dataset. For larger data set this number needs to be bigger.\n", - " \"label_column_name\": LABEL_COLUMN_NAME,\n", - " \"n_cross_validations\": 3,\n", - " \"time_column_name\": TIME_COLUMN_NAME,\n", - " \"max_horizon\": FORECAST_HORIZON,\n", - " \"track_child_runs\": False,\n", - " \"grain_column_names\": TIME_SERIES_ID_COLUMN_NAMES,\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Backtest AutoML \n", - "First we set backtesting parameters: we will step back by 30 days and will make 5 such steps; for each step we will forecast for next two weeks." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# The number of periods to step back on each backtest iteration.\n", - "BACKTESTING_PERIOD = 30\n", - "# The number of times we will back test the model.\n", - "NUMBER_OF_BACKTESTS = 5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To train AutoML on backtesting folds we will use the [Azure Machine Learning pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/concept-ml-pipelines). It will generate backtest folds, then train model for each of them and calculate the accuracy metrics. To run pipeline, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve (here, it is a forecasting), while a Run corresponds to a specific approach to the problem." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from uuid import uuid1\n", - "\n", - "from pipeline_helper import get_backtest_pipeline\n", - "\n", - "pipeline_exp = Experiment(ws, \"automl-backtesting\")\n", - "\n", - "# We will create the unique identifier to mark our models.\n", - "model_uid = str(uuid1())\n", - "\n", - "pipeline = get_backtest_pipeline(\n", - " experiment=pipeline_exp,\n", - " dataset=train_data,\n", - " # The STANDARD_DS12_V2 has 4 vCPU per node, we will set 2 process per node to be safe.\n", - " process_per_node=2,\n", - " # The maximum number of nodes for our compute is 6.\n", - " node_count=6,\n", - " compute_target=compute_target,\n", - " automl_settings=automl_settings,\n", - " step_size=BACKTESTING_PERIOD,\n", - " step_number=NUMBER_OF_BACKTESTS,\n", - " model_uid=model_uid,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run the pipeline and wait for results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pipeline_run = pipeline_exp.submit(pipeline)\n", - "pipeline_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After the run is complete, we can download the results. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "metrics_output = pipeline_run.get_pipeline_output(\"results\")\n", - "metrics_output.download(\"backtest_metrics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## View metrics\n", - "To distinguish these metrics from the model backtest, which we will obtain in the next section, we will move the directory with metrics out of the backtest_metrics and will remove the parent folder. We will create the utility function for that." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def copy_scoring_directory(new_name):\n", - " scores_path = os.path.join(\"backtest_metrics\", \"azureml\")\n", - " directory_list = [os.path.join(scores_path, d) for d in os.listdir(scores_path)]\n", - " latest_file = max(directory_list, key=os.path.getctime)\n", - " print(\n", - " f\"The output directory {latest_file} was created on {pd.Timestamp(os.path.getctime(latest_file), unit='s')} GMT.\"\n", - " )\n", - " shutil.move(os.path.join(latest_file, \"results\"), new_name)\n", - " shutil.rmtree(\"backtest_metrics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Move the directory and list its contents." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "copy_scoring_directory(\"automl_backtest\")\n", - "pd.DataFrame({\"File\": os.listdir(\"automl_backtest\")})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The directory contains a set of files with results:\n", - "- forecast.csv contains forecasts for all backtest iterations. The backtest_iteration column contains iteration identifier with the last training date as a suffix\n", - "- scores.csv contains all metrics. If data set contains several time series, the metrics are given for all combinations of time series id and iterations, as well as scores for all iterations and time series id are marked as \"all_sets\"\n", - "- plots_fcst_vs_actual.pdf contains the predictions vs forecast plots for each iteration and time series.\n", - "\n", - "For demonstration purposes we will display the table of metrics for one of the time series with ID \"ts0\". Again, we will create the utility function, which will be re used in model backtesting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_metrics_for_ts(all_metrics, ts):\n", - " \"\"\"\n", - " Get the metrics for the time series with ID ts and return it as pandas data frame.\n", - "\n", - " :param all_metrics: The table with all the metrics.\n", - " :param ts: The ID of a time series of interest.\n", - " :return: The pandas DataFrame with metrics for one time series.\n", - " \"\"\"\n", - " results_df = None\n", - " for ts_id, one_series in all_metrics.groupby(\"time_series_id\"):\n", - " if not ts_id.startswith(ts):\n", - " continue\n", - " iteration = ts_id.split(\"|\")[-1]\n", - " df = one_series[[\"metric_name\", \"metric\"]]\n", - " df.rename({\"metric\": iteration}, axis=1, inplace=True)\n", - " df.set_index(\"metric_name\", inplace=True)\n", - " if results_df is None:\n", - " results_df = df\n", - " else:\n", - " results_df = results_df.merge(\n", - " df, how=\"inner\", left_index=True, right_index=True\n", - " )\n", - " results_df.sort_index(axis=1, inplace=True)\n", - " return results_df\n", - "\n", - "\n", - "metrics_df = pd.read_csv(os.path.join(\"automl_backtest\", \"scores.csv\"))\n", - "ts_id = \"ts0\"\n", - "get_metrics_for_ts(metrics_df, ts_id)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Forecast vs actuals plots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import IFrame\n", - "\n", - "IFrame(\"./automl_backtest/plots_fcst_vs_actual.pdf\", width=800, height=300)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Backtest the best model \n", - "\n", - "For model backtesting we will use the same parameters we used to backtest AutoML. All the models, we have obtained in the previous run were registered in our workspace. To identify the model, each was assigned a tag with the last trainig date." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_list = Model.list(ws, tags={\"experiment\": \"automl-backtesting\"})\n", - "model_data = {\"name\": [], \"last_training_date\": []}\n", - "for model in model_list:\n", - " if (\n", - " \"last_training_date\" not in model.tags\n", - " or \"model_uid\" not in model.tags\n", - " or model.tags[\"model_uid\"] != model_uid\n", - " ):\n", - " continue\n", - " model_data[\"name\"].append(model.name)\n", - " model_data[\"last_training_date\"].append(\n", - " pd.Timestamp(model.tags[\"last_training_date\"])\n", - " )\n", - "df_models = pd.DataFrame(model_data)\n", - "df_models.sort_values([\"last_training_date\"], inplace=True)\n", - "df_models.reset_index(inplace=True, drop=True)\n", - "df_models" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will backtest the model trained on the most recet data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_name = df_models[\"name\"].iloc[-1]\n", - "model_name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrain the models.\n", - "Assemble the pipeline, which will retrain the best model from AutoML run on historical data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pipeline_exp = Experiment(ws, \"model-backtesting\")\n", - "\n", - "pipeline = get_backtest_pipeline(\n", - " experiment=pipeline_exp,\n", - " dataset=train_data,\n", - " # The STANDARD_DS12_V2 has 4 vCPU per node, we will set 2 process per node to be safe.\n", - " process_per_node=2,\n", - " # The maximum number of nodes for our compute is 6.\n", - " node_count=6,\n", - " compute_target=compute_target,\n", - " automl_settings=automl_settings,\n", - " step_size=BACKTESTING_PERIOD,\n", - " step_number=NUMBER_OF_BACKTESTS,\n", - " model_name=model_name,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Launch the backtesting pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pipeline_run = pipeline_exp.submit(pipeline)\n", - "pipeline_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The metrics are stored in the pipeline output named \"score\". The next code will download the table with metrics." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "metrics_output = pipeline_run.get_pipeline_output(\"results\")\n", - "metrics_output.download(\"backtest_metrics\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again, we will copy the data files from the downloaded directory, but in this case we will call the folder \"model_backtest\"; it will contain the same files as the one for AutoML backtesting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "copy_scoring_directory(\"model_backtest\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, we will display the metrics." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model_metrics_df = pd.read_csv(os.path.join(\"model_backtest\", \"scores.csv\"))\n", - "get_metrics_for_ts(model_metrics_df, ts_id)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Forecast vs actuals plots." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import IFrame\n", - "\n", - "IFrame(\"./model_backtest/plots_fcst_vs_actual.pdf\", width=800, height=300)" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License.\n", + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/automl-forecasting-function.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated MachineLearning\n", + "_**The model backtesting**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "2. [Setup](#Setup)\n", + "3. [Data](#Data)\n", + "4. [Prepare remote compute and data.](#prepare_remote)\n", + "5. [Create the configuration for AutoML backtesting](#train)\n", + "6. [Backtest AutoML](#backtest_automl)\n", + "7. [View metrics](#Metrics)\n", + "8. [Backtest the best model](#backtest_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "Model backtesting is used to evaluate its performance on historical data. To do that we step back on the backtesting period by the data set several times and split the data to train and test sets. Then these data sets are used for training and evaluation of model.
\n", + "This notebook is intended to demonstrate backtesting on a single model, this is the best solution for small data sets with a few or one time series in it. For scenarios where we would like to choose the best AutoML model for every backtest iteration, please see [AutoML Forecasting Backtest Many Models Example](../forecasting-backtest-many-models/auto-ml-forecasting-backtest-many-models.ipynb) notebook.\n", + "![Backtesting](Backtesting.png)\n", + "This notebook demonstrates two ways of backtesting:\n", + "- AutoML backtesting: we will train separate AutoML models for historical data\n", + "- Model backtesting: from the first run we will select the best model trained on the most recent data, retrain it on the past data and evaluate." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import shutil\n", + "\n", + "import azureml.core\n", + "from azureml.core import Experiment, Model, Workspace" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.1 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As part of the setup you have already created a Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"SKU\"] = ws.sku\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data\n", + "For the demonstration purposes we will simulate one year of daily data. To do this we need to specify the following parameters: time column name, time series ID column names and label column name. Our intention is to forecast for two weeks ahead." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "TIME_COLUMN_NAME = \"date\"\n", + "TIME_SERIES_ID_COLUMN_NAMES = \"time_series_id\"\n", + "LABEL_COLUMN_NAME = \"y\"\n", + "FORECAST_HORIZON = 14\n", + "FREQUENCY = \"D\"\n", + "\n", + "\n", + "def simulate_timeseries_data(\n", + " train_len: int,\n", + " test_len: int,\n", + " time_column_name: str,\n", + " target_column_name: str,\n", + " time_series_id_column_name: str,\n", + " time_series_number: int = 1,\n", + " freq: str = \"H\",\n", + "):\n", + " \"\"\"\n", + " Return the time series of designed length.\n", + "\n", + " :param train_len: The length of training data (one series).\n", + " :type train_len: int\n", + " :param test_len: The length of testing data (one series).\n", + " :type test_len: int\n", + " :param time_column_name: The desired name of a time column.\n", + " :type time_column_name: str\n", + " :param time_series_number: The number of time series in the data set.\n", + " :type time_series_number: int\n", + " :param freq: The frequency string representing pandas offset.\n", + " see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html\n", + " :type freq: str\n", + " :returns: the tuple of train and test data sets.\n", + " :rtype: tuple\n", + "\n", + " \"\"\"\n", + " data_train = [] # type: List[pd.DataFrame]\n", + " data_test = [] # type: List[pd.DataFrame]\n", + " data_length = train_len + test_len\n", + " for i in range(time_series_number):\n", + " X = pd.DataFrame(\n", + " {\n", + " time_column_name: pd.date_range(\n", + " start=\"2000-01-01\", periods=data_length, freq=freq\n", + " ),\n", + " target_column_name: np.arange(data_length).astype(float)\n", + " + np.random.rand(data_length)\n", + " + i * 5,\n", + " \"ext_predictor\": np.asarray(range(42, 42 + data_length)),\n", + " time_series_id_column_name: np.repeat(\"ts{}\".format(i), data_length),\n", + " }\n", + " )\n", + " data_train.append(X[:train_len])\n", + " data_test.append(X[train_len:])\n", + " train = pd.concat(data_train)\n", + " label_train = train.pop(target_column_name).values\n", + " test = pd.concat(data_test)\n", + " label_test = test.pop(target_column_name).values\n", + " return train, label_train, test, label_test\n", + "\n", + "\n", + "n_test_periods = FORECAST_HORIZON\n", + "n_train_periods = 365\n", + "X_train, y_train, X_test, y_test = simulate_timeseries_data(\n", + " train_len=n_train_periods,\n", + " test_len=n_test_periods,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=LABEL_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAMES,\n", + " time_series_number=2,\n", + " freq=FREQUENCY,\n", + ")\n", + "X_train[LABEL_COLUMN_NAME] = y_train" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what the training data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_train.tail()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare remote compute and data. \n", + "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace), is paired with the storage account, which contains the default data store. We will use it to upload the artificial data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.data.dataset_factory import TabularDatasetFactory\n", + "\n", + "ds = ws.get_default_datastore()\n", + "# Upload saved data to the default data store.\n", + "train_data = TabularDatasetFactory.register_pandas_dataframe(\n", + " X_train, target=(ds, \"data\"), name=\"data_backtest\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will need to create a compute target for backtesting. In this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute), you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "amlcompute_cluster_name = \"backtest-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create the configuration for AutoML backtesting \n", + "\n", + "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings including the name of the time column, the maximum forecast horizon, and the partition column name definition.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **task** | forecasting |\n", + "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
normalized_root_mean_squared_error
normalized_mean_absolute_error |\n", + "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", + "| **label_column_name** | The name of the label column. |\n", + "| **max_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", + "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", + "| **time_column_name** | The name of your time column. |\n", + "| **grain_column_names** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "automl_settings = {\n", + " \"task\": \"forecasting\",\n", + " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", + " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", + " \"iterations\": 15,\n", + " \"experiment_timeout_hours\": 1, # This also needs to be changed based on the dataset. For larger data set this number needs to be bigger.\n", + " \"label_column_name\": LABEL_COLUMN_NAME,\n", + " \"n_cross_validations\": 3,\n", + " \"time_column_name\": TIME_COLUMN_NAME,\n", + " \"max_horizon\": FORECAST_HORIZON,\n", + " \"track_child_runs\": False,\n", + " \"grain_column_names\": TIME_SERIES_ID_COLUMN_NAMES,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Backtest AutoML \n", + "First we set backtesting parameters: we will step back by 30 days and will make 5 such steps; for each step we will forecast for next two weeks." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The number of periods to step back on each backtest iteration.\n", + "BACKTESTING_PERIOD = 30\n", + "# The number of times we will back test the model.\n", + "NUMBER_OF_BACKTESTS = 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To train AutoML on backtesting folds we will use the [Azure Machine Learning pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/concept-ml-pipelines). It will generate backtest folds, then train model for each of them and calculate the accuracy metrics. To run pipeline, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve (here, it is a forecasting), while a Run corresponds to a specific approach to the problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from uuid import uuid1\n", + "\n", + "from pipeline_helper import get_backtest_pipeline\n", + "\n", + "pipeline_exp = Experiment(ws, \"automl-backtesting\")\n", + "\n", + "# We will create the unique identifier to mark our models.\n", + "model_uid = str(uuid1())\n", + "\n", + "pipeline = get_backtest_pipeline(\n", + " experiment=pipeline_exp,\n", + " dataset=train_data,\n", + " # The STANDARD_DS12_V2 has 4 vCPU per node, we will set 2 process per node to be safe.\n", + " process_per_node=2,\n", + " # The maximum number of nodes for our compute is 6.\n", + " node_count=6,\n", + " compute_target=compute_target,\n", + " automl_settings=automl_settings,\n", + " step_size=BACKTESTING_PERIOD,\n", + " step_number=NUMBER_OF_BACKTESTS,\n", + " model_uid=model_uid,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the pipeline and wait for results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline_run = pipeline_exp.submit(pipeline)\n", + "pipeline_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After the run is complete, we can download the results. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "metrics_output = pipeline_run.get_pipeline_output(\"results\")\n", + "metrics_output.download(\"backtest_metrics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## View metrics\n", + "To distinguish these metrics from the model backtest, which we will obtain in the next section, we will move the directory with metrics out of the backtest_metrics and will remove the parent folder. We will create the utility function for that." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def copy_scoring_directory(new_name):\n", + " scores_path = os.path.join(\"backtest_metrics\", \"azureml\")\n", + " directory_list = [os.path.join(scores_path, d) for d in os.listdir(scores_path)]\n", + " latest_file = max(directory_list, key=os.path.getctime)\n", + " print(\n", + " f\"The output directory {latest_file} was created on {pd.Timestamp(os.path.getctime(latest_file), unit='s')} GMT.\"\n", + " )\n", + " shutil.move(os.path.join(latest_file, \"results\"), new_name)\n", + " shutil.rmtree(\"backtest_metrics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Move the directory and list its contents." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "copy_scoring_directory(\"automl_backtest\")\n", + "pd.DataFrame({\"File\": os.listdir(\"automl_backtest\")})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The directory contains a set of files with results:\n", + "- forecast.csv contains forecasts for all backtest iterations. The backtest_iteration column contains iteration identifier with the last training date as a suffix\n", + "- scores.csv contains all metrics. If data set contains several time series, the metrics are given for all combinations of time series id and iterations, as well as scores for all iterations and time series id are marked as \"all_sets\"\n", + "- plots_fcst_vs_actual.pdf contains the predictions vs forecast plots for each iteration and time series.\n", + "\n", + "For demonstration purposes we will display the table of metrics for one of the time series with ID \"ts0\". Again, we will create the utility function, which will be re used in model backtesting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_metrics_for_ts(all_metrics, ts):\n", + " \"\"\"\n", + " Get the metrics for the time series with ID ts and return it as pandas data frame.\n", + "\n", + " :param all_metrics: The table with all the metrics.\n", + " :param ts: The ID of a time series of interest.\n", + " :return: The pandas DataFrame with metrics for one time series.\n", + " \"\"\"\n", + " results_df = None\n", + " for ts_id, one_series in all_metrics.groupby(\"time_series_id\"):\n", + " if not ts_id.startswith(ts):\n", + " continue\n", + " iteration = ts_id.split(\"|\")[-1]\n", + " df = one_series[[\"metric_name\", \"metric\"]]\n", + " df.rename({\"metric\": iteration}, axis=1, inplace=True)\n", + " df.set_index(\"metric_name\", inplace=True)\n", + " if results_df is None:\n", + " results_df = df\n", + " else:\n", + " results_df = results_df.merge(\n", + " df, how=\"inner\", left_index=True, right_index=True\n", + " )\n", + " results_df.sort_index(axis=1, inplace=True)\n", + " return results_df\n", + "\n", + "\n", + "metrics_df = pd.read_csv(os.path.join(\"automl_backtest\", \"scores.csv\"))\n", + "ts_id = \"ts0\"\n", + "get_metrics_for_ts(metrics_df, ts_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Forecast vs actuals plots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import IFrame\n", + "\n", + "IFrame(\"./automl_backtest/plots_fcst_vs_actual.pdf\", width=800, height=300)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Backtest the best model \n", + "\n", + "For model backtesting we will use the same parameters we used to backtest AutoML. All the models, we have obtained in the previous run were registered in our workspace. To identify the model, each was assigned a tag with the last trainig date." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_list = Model.list(ws, tags={\"experiment\": \"automl-backtesting\"})\n", + "model_data = {\"name\": [], \"last_training_date\": []}\n", + "for model in model_list:\n", + " if (\n", + " \"last_training_date\" not in model.tags\n", + " or \"model_uid\" not in model.tags\n", + " or model.tags[\"model_uid\"] != model_uid\n", + " ):\n", + " continue\n", + " model_data[\"name\"].append(model.name)\n", + " model_data[\"last_training_date\"].append(\n", + " pd.Timestamp(model.tags[\"last_training_date\"])\n", + " )\n", + "df_models = pd.DataFrame(model_data)\n", + "df_models.sort_values([\"last_training_date\"], inplace=True)\n", + "df_models.reset_index(inplace=True, drop=True)\n", + "df_models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will backtest the model trained on the most recet data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_name = df_models[\"name\"].iloc[-1]\n", + "model_name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrain the models.\n", + "Assemble the pipeline, which will retrain the best model from AutoML run on historical data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline_exp = Experiment(ws, \"model-backtesting\")\n", + "\n", + "pipeline = get_backtest_pipeline(\n", + " experiment=pipeline_exp,\n", + " dataset=train_data,\n", + " # The STANDARD_DS12_V2 has 4 vCPU per node, we will set 2 process per node to be safe.\n", + " process_per_node=2,\n", + " # The maximum number of nodes for our compute is 6.\n", + " node_count=6,\n", + " compute_target=compute_target,\n", + " automl_settings=automl_settings,\n", + " step_size=BACKTESTING_PERIOD,\n", + " step_number=NUMBER_OF_BACKTESTS,\n", + " model_name=model_name,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Launch the backtesting pipeline." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pipeline_run = pipeline_exp.submit(pipeline)\n", + "pipeline_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The metrics are stored in the pipeline output named \"score\". The next code will download the table with metrics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "metrics_output = pipeline_run.get_pipeline_output(\"results\")\n", + "metrics_output.download(\"backtest_metrics\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, we will copy the data files from the downloaded directory, but in this case we will call the folder \"model_backtest\"; it will contain the same files as the one for AutoML backtesting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "copy_scoring_directory(\"model_backtest\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we will display the metrics." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_metrics_df = pd.read_csv(os.path.join(\"model_backtest\", \"scores.csv\"))\n", + "get_metrics_for_ts(model_metrics_df, ts_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Forecast vs actuals plots." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import IFrame\n", + "\n", + "IFrame(\"./model_backtest/plots_fcst_vs_actual.pdf\", width=800, height=300)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "None" ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "category": "tutorial", - "compute": [ - "Remote" - ], - "datasets": [ - "None" - ], - "deployment": [ - "None" - ], - "exclude_from_index": false, - "framework": [ - "Azure ML AutoML" - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "Azure ML AutoML" + ], + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb index 48056752e..81b5e8e80 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.ipynb @@ -1,714 +1,725 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "**BikeShare Demand Forecasting**\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "1. [Setup](#Setup)\n", - "1. [Compute](#Compute)\n", - "1. [Data](#Data)\n", - "1. [Train](#Train)\n", - "1. [Featurization](#Featurization)\n", - "1. [Evaluate](#Evaluate)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "This notebook demonstrates demand forecasting for a bike-sharing service using AutoML.\n", - "\n", - "AutoML highlights here include built-in holiday featurization, accessing engineered feature names, and working with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", - "\n", - "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", - "\n", - "Notebook synopsis:\n", - "1. Creating an Experiment in an existing Workspace\n", - "2. Configuration and local run of AutoML for a time-series model with lag and holiday features \n", - "3. Viewing the engineered names for featurized data and featurization summary for all raw features\n", - "4. Evaluating the fitted model using a rolling test " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import logging\n", - "from datetime import datetime\n", - "\n", - "import azureml.core\n", - "import numpy as np\n", - "import pandas as pd\n", - "from azureml.automl.core.featurization import FeaturizationConfig\n", - "from azureml.core import Dataset, Experiment, Workspace\n", - "from azureml.train.automl import AutoMLConfig\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for the run history container in the workspace\n", - "experiment_name = \"automl-bikeshareforecasting\"\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"SKU\"] = ws.sku\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Run History Name\"] = experiment_name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Compute\n", - "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", - "\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"bike-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print(\"Found existing cluster, use it.\")\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_DS12_V2\", max_nodes=4\n", - " )\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data\n", - "\n", - "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace) is paired with the storage account, which contains the default data store. We will use it to upload the bike share data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "datastore = ws.get_default_datastore()\n", - "datastore.upload_files(\n", - " files=[\"./bike-no.csv\"], target_path=\"dataset/\", overwrite=True, show_progress=True\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's set up what we know about the dataset. \n", - "\n", - "**Target column** is what we want to forecast.\n", - "\n", - "**Time column** is the time axis along which to predict." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "target_column_name = \"cnt\"\n", - "time_column_name = \"date\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.Tabular.from_delimited_files(\n", - " path=[(datastore, \"dataset/bike-no.csv\")]\n", - ").with_timestamp_columns(fine_grain_timestamp=time_column_name)\n", - "\n", - "# Drop the columns 'casual' and 'registered' as these columns are a breakdown of the total and therefore a leak.\n", - "dataset = dataset.drop_columns(columns=[\"casual\", \"registered\"])\n", - "\n", - "dataset.take(5).to_pandas_dataframe().reset_index(drop=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Split the data\n", - "\n", - "The first split we make is into train and test sets. Note we are splitting on time. Data before 9/1 will be used for training, and data after and including 9/1 will be used for testing." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# select data that occurs before a specified date\n", - "train = dataset.time_before(datetime(2012, 8, 31), include_boundary=True)\n", - "train.to_pandas_dataframe().tail(5).reset_index(drop=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test = dataset.time_after(datetime(2012, 9, 1), include_boundary=True)\n", - "test.to_pandas_dataframe().head(5).reset_index(drop=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Forecasting Parameters\n", - "To define forecasting parameters for your experiment training, you can leverage the ForecastingParameters class. The table below details the forecasting parameter we will be passing into our experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**time_column_name**|The name of your time column.|\n", - "|**forecast_horizon**|The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly).|\n", - "|**country_or_region_for_holidays**|The country/region used to generate holiday features. These should be ISO 3166 two-letter country/region codes (i.e. 'US', 'GB').|\n", - "|**target_lags**|The target_lags specifies how far back we will construct the lags of the target variable.|\n", - "|**freq**|Forecast frequency. This optional parameter represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train\n", - "\n", - "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|forecasting|\n", - "|**primary_metric**|This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error\n", - "|**blocked_models**|Models in blocked_models won't be used by AutoML. All supported models can be found at [here](https://docs.microsoft.com/en-us/python/api/azureml-train-automl-client/azureml.train.automl.constants.supportedmodels.forecasting?view=azure-ml-py).|\n", - "|**experiment_timeout_hours**|Experimentation timeout in hours.|\n", - "|**training_data**|Input dataset, containing both features and label column.|\n", - "|**label_column_name**|The name of the label column.|\n", - "|**compute_target**|The remote compute for training.|\n", - "|**n_cross_validations**|Number of cross validation splits.|\n", - "|**enable_early_stopping**|If early stopping is on, training will stop when the primary metric is no longer improving.|\n", - "|**forecasting_parameters**|A class that holds all the forecasting related parameters.|\n", - "\n", - "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setting forecaster maximum horizon \n", - "\n", - "The forecast horizon is the number of periods into the future that the model should predict. Here, we set the horizon to 14 periods (i.e. 14 days). Notice that this is much shorter than the number of days in the test set; we will need to use a rolling test to evaluate the performance on the whole test set. For more discussion of forecast horizons and guiding principles for setting them, please see the [energy demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "forecast_horizon = 14" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Convert prediction type to integer\n", - "The featurization configuration can be used to change the default prediction type from decimal numbers to integer. This customization can be used in the scenario when the target column is expected to contain whole values as the number of rented bikes per day." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "featurization_config = FeaturizationConfig()\n", - "# Force the target column, to be integer type.\n", - "featurization_config.add_prediction_transform_type(\"Integer\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Config AutoML" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", - "\n", - "forecasting_parameters = ForecastingParameters(\n", - " time_column_name=time_column_name,\n", - " forecast_horizon=forecast_horizon,\n", - " country_or_region_for_holidays=\"US\", # set country_or_region will trigger holiday featurizer\n", - " target_lags=\"auto\", # use heuristic based lag setting\n", - " freq=\"D\", # Set the forecast frequency to be daily\n", - ")\n", - "\n", - "automl_config = AutoMLConfig(\n", - " task=\"forecasting\",\n", - " primary_metric=\"normalized_root_mean_squared_error\",\n", - " featurization=featurization_config,\n", - " blocked_models=[\"ExtremeRandomTrees\"],\n", - " experiment_timeout_hours=0.3,\n", - " training_data=train,\n", - " label_column_name=target_column_name,\n", - " compute_target=compute_target,\n", - " enable_early_stopping=True,\n", - " n_cross_validations=3,\n", - " max_concurrent_iterations=4,\n", - " max_cores_per_iteration=-1,\n", - " verbosity=logging.INFO,\n", - " forecasting_parameters=forecasting_parameters,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will now run the experiment, you can go to Azure ML portal to view the run details. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve the Best Run details\n", - "Below we retrieve the best Run object from among all the runs in the experiment." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run = remote_run.get_best_child()\n", - "best_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Featurization\n", - "\n", - "We can look at the engineered feature names generated in time-series featurization via. the JSON file named 'engineered_feature_names.json' under the run outputs. Note that a number of named holiday periods are represented. We recommend that you have at least one year of data when using this feature to ensure that all yearly holidays are captured in the training featurization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Download the JSON file locally\n", - "best_run.download_file(\"outputs/engineered_feature_names.json\", \"engineered_feature_names.json\")\n", - "with open(\"engineered_feature_names.json\", \"r\") as f:\n", - " records = json.load(f)\n", - "\n", - "records" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### View the featurization summary\n", - "\n", - "You can also see what featurization steps were performed on different raw features in the user data. For each raw feature in the user data, the following information is displayed:\n", - "\n", - "- Raw feature name\n", - "- Number of engineered features formed out of this raw feature\n", - "- Type detected\n", - "- If feature was dropped\n", - "- List of feature transformations for the raw feature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Download the featurization summary JSON file locally\n", - "best_run.download_file(\"outputs/featurization_summary.json\", \"featurization_summary.json\")\n", - "\n", - "# Render the JSON as a pandas DataFrame\n", - "with open(\"featurization_summary.json\", \"r\") as f:\n", - " records = json.load(f)\n", - "fs = pd.DataFrame.from_records(records)\n", - "\n", - "# View a summary of the featurization \n", - "fs[[\"RawFeatureName\", \"TypeDetected\", \"Dropped\", \"EngineeredFeatureCount\", \"Transformations\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now use the best fitted model from the AutoML Run to make forecasts for the test set. We will do batch scoring on the test dataset which should have the same schema as training dataset.\n", - "\n", - "The scoring will run on a remote compute. In this example, it will reuse the training compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_experiment = Experiment(ws, experiment_name + \"_test\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieving forecasts from the model\n", - "To run the forecast on the remote compute we will use a helper script: forecasting_script. This script contains the utility methods which will be used by the remote estimator. We copy the script to the project folder to upload it to remote compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import shutil\n", - "\n", - "script_folder = os.path.join(os.getcwd(), \"forecast\")\n", - "os.makedirs(script_folder, exist_ok=True)\n", - "shutil.copy(\"forecasting_script.py\", script_folder)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For brevity, we have created a function called run_forecast that submits the test data to the best model determined during the training run and retrieves forecasts. The test set is longer than the forecast horizon specified at train time, so the forecasting script uses a so-called rolling evaluation to generate predictions over the whole test set. A rolling evaluation iterates the forecaster over the test set, using the actuals in the test set to make lag features as needed. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from run_forecast import run_rolling_forecast\n", - "\n", - "remote_run = run_rolling_forecast(\n", - " test_experiment, compute_target, best_run, test, target_column_name\n", - ")\n", - "remote_run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Download the prediction result for metrics calculation\n", - "The test data with predictions are saved in artifact outputs/predictions.csv. You can download it and calculation some error metrics for the forecasts and vizualize the predictions vs. the actuals." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.download_file(\"outputs/predictions.csv\", \"predictions.csv\")\n", - "df_all = pd.read_csv(\"predictions.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.shared import constants\n", - "from azureml.automl.runtime.shared.score import scoring\n", - "from sklearn.metrics import mean_absolute_error, mean_squared_error\n", - "from matplotlib import pyplot as plt\n", - "\n", - "# use automl metrics module\n", - "scores = scoring.score_regression(\n", - " y_test=df_all[target_column_name],\n", - " y_pred=df_all[\"predicted\"],\n", - " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", - ")\n", - "\n", - "print(\"[Test data scores]\\n\")\n", - "for key, value in scores.items():\n", - " print(\"{}: {:.3f}\".format(key, value))\n", - "\n", - "# Plot outputs\n", - "%matplotlib inline\n", - "test_pred = plt.scatter(df_all[target_column_name], df_all[\"predicted\"], color=\"b\")\n", - "test_test = plt.scatter(\n", - " df_all[target_column_name], df_all[target_column_name], color=\"g\"\n", - ")\n", - "plt.legend(\n", - " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For more details on what metrics are included and how they are calculated, please refer to [supported metrics](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#regressionforecasting-metrics). You could also calculate residuals, like described [here](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#residuals).\n", - "\n", - "\n", - "Since we did a rolling evaluation on the test set, we can analyze the predictions by their forecast horizon relative to the rolling origin. The model was initially trained at a forecast horizon of 14, so each prediction from the model is associated with a horizon value from 1 to 14. The horizon values are in a column named, \"horizon_origin,\" in the prediction set. For example, we can calculate some of the error metrics grouped by the horizon:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from metrics_helper import MAPE, APE\n", - "\n", - "df_all.groupby(\"horizon_origin\").apply(\n", - " lambda df: pd.Series(\n", - " {\n", - " \"MAPE\": MAPE(df[target_column_name], df[\"predicted\"]),\n", - " \"RMSE\": np.sqrt(\n", - " mean_squared_error(df[target_column_name], df[\"predicted\"])\n", - " ),\n", - " \"MAE\": mean_absolute_error(df[target_column_name], df[\"predicted\"]),\n", - " }\n", - " )\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To drill down more, we can look at the distributions of APE (absolute percentage error) by horizon. From the chart, it is clear that the overall MAPE is being skewed by one particular point where the actual value is of small absolute value." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "df_all_APE = df_all.assign(APE=APE(df_all[target_column_name], df_all[\"predicted\"]))\n", - "APEs = [\n", - " df_all_APE[df_all[\"horizon_origin\"] == h].APE.values\n", - " for h in range(1, forecast_horizon + 1)\n", - "]\n", - "\n", - "%matplotlib inline\n", - "plt.boxplot(APEs)\n", - "plt.yscale(\"log\")\n", - "plt.xlabel(\"horizon\")\n", - "plt.ylabel(\"APE (%)\")\n", - "plt.title(\"Absolute Percentage Errors by Forecast Horizon\")\n", - "\n", - "plt.show()" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-bike-share/auto-ml-forecasting-bike-share.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "**BikeShare Demand Forecasting**\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Compute](#Compute)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Featurization](#Featurization)\n", + "1. [Evaluate](#Evaluate)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates demand forecasting for a bike-sharing service using AutoML.\n", + "\n", + "AutoML highlights here include built-in holiday featurization, accessing engineered feature names, and working with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", + "\n", + "Make sure you have executed the [configuration notebook](../../../configuration.ipynb) before running this notebook.\n", + "\n", + "Notebook synopsis:\n", + "1. Creating an Experiment in an existing Workspace\n", + "2. Configuration and local run of AutoML for a time-series model with lag and holiday features \n", + "3. Viewing the engineered names for featurized data and featurization summary for all raw features\n", + "4. Evaluating the fitted model using a rolling test " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "from datetime import datetime\n", + "\n", + "import azureml.core\n", + "import numpy as np\n", + "import pandas as pd\n", + "from azureml.automl.core.featurization import FeaturizationConfig\n", + "from azureml.core import Dataset, Experiment, Workspace\n", + "from azureml.train.automl import AutoMLConfig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.0 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for the run history container in the workspace\n", + "experiment_name = \"automl-bikeshareforecasting\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"SKU\"] = ws.sku\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Run History Name\"] = experiment_name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute\n", + "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist.\n", + "\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"bike-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=4\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data\n", + "\n", + "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace) is paired with the storage account, which contains the default data store. We will use it to upload the bike share data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = ws.get_default_datastore()\n", + "datastore.upload_files(\n", + " files=[\"./bike-no.csv\"], target_path=\"dataset/\", overwrite=True, show_progress=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's set up what we know about the dataset. \n", + "\n", + "**Target column** is what we want to forecast.\n", + "\n", + "**Time column** is the time axis along which to predict." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "target_column_name = \"cnt\"\n", + "time_column_name = \"date\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, \"dataset/bike-no.csv\")]\n", + ").with_timestamp_columns(fine_grain_timestamp=time_column_name)\n", + "\n", + "# Drop the columns 'casual' and 'registered' as these columns are a breakdown of the total and therefore a leak.\n", + "dataset = dataset.drop_columns(columns=[\"casual\", \"registered\"])\n", + "\n", + "dataset.take(5).to_pandas_dataframe().reset_index(drop=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Split the data\n", + "\n", + "The first split we make is into train and test sets. Note we are splitting on time. Data before 9/1 will be used for training, and data after and including 9/1 will be used for testing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# select data that occurs before a specified date\n", + "train = dataset.time_before(datetime(2012, 8, 31), include_boundary=True)\n", + "train.to_pandas_dataframe().tail(5).reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test = dataset.time_after(datetime(2012, 9, 1), include_boundary=True)\n", + "test.to_pandas_dataframe().head(5).reset_index(drop=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Forecasting Parameters\n", + "To define forecasting parameters for your experiment training, you can leverage the ForecastingParameters class. The table below details the forecasting parameter we will be passing into our experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**time_column_name**|The name of your time column.|\n", + "|**forecast_horizon**|The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly).|\n", + "|**country_or_region_for_holidays**|The country/region used to generate holiday features. These should be ISO 3166 two-letter country/region codes (i.e. 'US', 'GB').|\n", + "|**target_lags**|The target_lags specifies how far back we will construct the lags of the target variable.|\n", + "|**freq**|Forecast frequency. This optional parameter represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|forecasting|\n", + "|**primary_metric**|This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error\n", + "|**blocked_models**|Models in blocked_models won't be used by AutoML. All supported models can be found at [here](https://docs.microsoft.com/en-us/python/api/azureml-train-automl-client/azureml.train.automl.constants.supportedmodels.forecasting?view=azure-ml-py).|\n", + "|**experiment_timeout_hours**|Experimentation timeout in hours.|\n", + "|**training_data**|Input dataset, containing both features and label column.|\n", + "|**label_column_name**|The name of the label column.|\n", + "|**compute_target**|The remote compute for training.|\n", + "|**n_cross_validations**|Number of cross validation splits.|\n", + "|**enable_early_stopping**|If early stopping is on, training will stop when the primary metric is no longer improving.|\n", + "|**forecasting_parameters**|A class that holds all the forecasting related parameters.|\n", + "\n", + "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting forecaster maximum horizon \n", + "\n", + "The forecast horizon is the number of periods into the future that the model should predict. Here, we set the horizon to 14 periods (i.e. 14 days). Notice that this is much shorter than the number of days in the test set; we will need to use a rolling test to evaluate the performance on the whole test set. For more discussion of forecast horizons and guiding principles for setting them, please see the [energy demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "forecast_horizon = 14" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Convert prediction type to integer\n", + "The featurization configuration can be used to change the default prediction type from decimal numbers to integer. This customization can be used in the scenario when the target column is expected to contain whole values as the number of rented bikes per day." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "featurization_config = FeaturizationConfig()\n", + "# Force the target column, to be integer type.\n", + "featurization_config.add_prediction_transform_type(\"Integer\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Config AutoML" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", + "\n", + "forecasting_parameters = ForecastingParameters(\n", + " time_column_name=time_column_name,\n", + " forecast_horizon=forecast_horizon,\n", + " country_or_region_for_holidays=\"US\", # set country_or_region will trigger holiday featurizer\n", + " target_lags=\"auto\", # use heuristic based lag setting\n", + " freq=\"D\", # Set the forecast frequency to be daily\n", + ")\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"forecasting\",\n", + " primary_metric=\"normalized_root_mean_squared_error\",\n", + " featurization=featurization_config,\n", + " blocked_models=[\"ExtremeRandomTrees\"],\n", + " experiment_timeout_hours=0.3,\n", + " training_data=train,\n", + " label_column_name=target_column_name,\n", + " compute_target=compute_target,\n", + " enable_early_stopping=True,\n", + " n_cross_validations=3,\n", + " max_concurrent_iterations=4,\n", + " max_cores_per_iteration=-1,\n", + " verbosity=logging.INFO,\n", + " forecasting_parameters=forecasting_parameters,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now run the experiment, you can go to Azure ML portal to view the run details. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve the Best Run details\n", + "Below we retrieve the best Run object from among all the runs in the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run = remote_run.get_best_child()\n", + "best_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Featurization\n", + "\n", + "We can look at the engineered feature names generated in time-series featurization via. the JSON file named 'engineered_feature_names.json' under the run outputs. Note that a number of named holiday periods are represented. We recommend that you have at least one year of data when using this feature to ensure that all yearly holidays are captured in the training featurization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the JSON file locally\n", + "best_run.download_file(\n", + " \"outputs/engineered_feature_names.json\", \"engineered_feature_names.json\"\n", + ")\n", + "with open(\"engineered_feature_names.json\", \"r\") as f:\n", + " records = json.load(f)\n", + "\n", + "records" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### View the featurization summary\n", + "\n", + "You can also see what featurization steps were performed on different raw features in the user data. For each raw feature in the user data, the following information is displayed:\n", + "\n", + "- Raw feature name\n", + "- Number of engineered features formed out of this raw feature\n", + "- Type detected\n", + "- If feature was dropped\n", + "- List of feature transformations for the raw feature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the featurization summary JSON file locally\n", + "best_run.download_file(\n", + " \"outputs/featurization_summary.json\", \"featurization_summary.json\"\n", + ")\n", + "\n", + "# Render the JSON as a pandas DataFrame\n", + "with open(\"featurization_summary.json\", \"r\") as f:\n", + " records = json.load(f)\n", + "fs = pd.DataFrame.from_records(records)\n", + "\n", + "# View a summary of the featurization\n", + "fs[\n", + " [\n", + " \"RawFeatureName\",\n", + " \"TypeDetected\",\n", + " \"Dropped\",\n", + " \"EngineeredFeatureCount\",\n", + " \"Transformations\",\n", + " ]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now use the best fitted model from the AutoML Run to make forecasts for the test set. We will do batch scoring on the test dataset which should have the same schema as training dataset.\n", + "\n", + "The scoring will run on a remote compute. In this example, it will reuse the training compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_experiment = Experiment(ws, experiment_name + \"_test\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieving forecasts from the model\n", + "To run the forecast on the remote compute we will use a helper script: forecasting_script. This script contains the utility methods which will be used by the remote estimator. We copy the script to the project folder to upload it to remote compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import shutil\n", + "\n", + "script_folder = os.path.join(os.getcwd(), \"forecast\")\n", + "os.makedirs(script_folder, exist_ok=True)\n", + "shutil.copy(\"forecasting_script.py\", script_folder)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For brevity, we have created a function called run_forecast that submits the test data to the best model determined during the training run and retrieves forecasts. The test set is longer than the forecast horizon specified at train time, so the forecasting script uses a so-called rolling evaluation to generate predictions over the whole test set. A rolling evaluation iterates the forecaster over the test set, using the actuals in the test set to make lag features as needed. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from run_forecast import run_rolling_forecast\n", + "\n", + "remote_run = run_rolling_forecast(\n", + " test_experiment, compute_target, best_run, test, target_column_name\n", + ")\n", + "remote_run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Download the prediction result for metrics calculation\n", + "The test data with predictions are saved in artifact outputs/predictions.csv. You can download it and calculation some error metrics for the forecasts and vizualize the predictions vs. the actuals." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.download_file(\"outputs/predictions.csv\", \"predictions.csv\")\n", + "df_all = pd.read_csv(\"predictions.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.shared import constants\n", + "from azureml.automl.runtime.shared.score import scoring\n", + "from sklearn.metrics import mean_absolute_error, mean_squared_error\n", + "from matplotlib import pyplot as plt\n", + "\n", + "# use automl metrics module\n", + "scores = scoring.score_regression(\n", + " y_test=df_all[target_column_name],\n", + " y_pred=df_all[\"predicted\"],\n", + " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", + ")\n", + "\n", + "print(\"[Test data scores]\\n\")\n", + "for key, value in scores.items():\n", + " print(\"{}: {:.3f}\".format(key, value))\n", + "\n", + "# Plot outputs\n", + "%matplotlib inline\n", + "test_pred = plt.scatter(df_all[target_column_name], df_all[\"predicted\"], color=\"b\")\n", + "test_test = plt.scatter(\n", + " df_all[target_column_name], df_all[target_column_name], color=\"g\"\n", + ")\n", + "plt.legend(\n", + " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For more details on what metrics are included and how they are calculated, please refer to [supported metrics](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#regressionforecasting-metrics). You could also calculate residuals, like described [here](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#residuals).\n", + "\n", + "\n", + "Since we did a rolling evaluation on the test set, we can analyze the predictions by their forecast horizon relative to the rolling origin. The model was initially trained at a forecast horizon of 14, so each prediction from the model is associated with a horizon value from 1 to 14. The horizon values are in a column named, \"horizon_origin,\" in the prediction set. For example, we can calculate some of the error metrics grouped by the horizon:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from metrics_helper import MAPE, APE\n", + "\n", + "df_all.groupby(\"horizon_origin\").apply(\n", + " lambda df: pd.Series(\n", + " {\n", + " \"MAPE\": MAPE(df[target_column_name], df[\"predicted\"]),\n", + " \"RMSE\": np.sqrt(\n", + " mean_squared_error(df[target_column_name], df[\"predicted\"])\n", + " ),\n", + " \"MAE\": mean_absolute_error(df[target_column_name], df[\"predicted\"]),\n", + " }\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To drill down more, we can look at the distributions of APE (absolute percentage error) by horizon. From the chart, it is clear that the overall MAPE is being skewed by one particular point where the actual value is of small absolute value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df_all_APE = df_all.assign(APE=APE(df_all[target_column_name], df_all[\"predicted\"]))\n", + "APEs = [\n", + " df_all_APE[df_all[\"horizon_origin\"] == h].APE.values\n", + " for h in range(1, forecast_horizon + 1)\n", + "]\n", + "\n", + "%matplotlib inline\n", + "plt.boxplot(APEs)\n", + "plt.yscale(\"log\")\n", + "plt.xlabel(\"horizon\")\n", + "plt.ylabel(\"APE (%)\")\n", + "plt.title(\"Absolute Percentage Errors by Forecast Horizon\")\n", + "\n", + "plt.show()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "BikeShare" + ], + "deployment": [ + "None" ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "category": "tutorial", - "compute": [ - "Remote" - ], - "datasets": [ - "BikeShare" - ], - "deployment": [ - "None" - ], - "exclude_from_index": false, - "file_extension": ".py", - "framework": [ - "Azure ML AutoML" - ], - "friendly_name": "Forecasting BikeShare Demand", - "index_order": 1, - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - }, - "mimetype": "text/x-python", - "name": "python", - "npconvert_exporter": "python", - "pygments_lexer": "ipython3", - "tags": [ - "Forecasting" - ], - "task": "Forecasting", + "exclude_from_index": false, + "file_extension": ".py", + "framework": [ + "Azure ML AutoML" + ], + "friendly_name": "Forecasting BikeShare Demand", + "index_order": 1, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file + "mimetype": "text/x-python", + "name": "python", + "npconvert_exporter": "python", + "pygments_lexer": "ipython3", + "tags": [ + "Forecasting" + ], + "task": "Forecasting", + "version": 3 + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb index fe04dbaab..52f9a955c 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.ipynb @@ -1,774 +1,785 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "_**Forecasting using the Energy Demand Dataset**_\n", - "\n", - "## Contents\n", - "1. [Introduction](#introduction)\n", - "1. [Setup](#setup)\n", - "1. [Data and Forecasting Configurations](#data)\n", - "1. [Train](#train)\n", - "1. [Generate and Evaluate the Forecast](#forecast)\n", - "\n", - "Advanced Forecasting\n", - "1. [Advanced Training](#advanced_training)\n", - "1. [Advanced Results](#advanced_results)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Introduction\n", - "\n", - "In this example we use the associated New York City energy demand dataset to showcase how you can use AutoML for a simple forecasting problem and explore the results. The goal is predict the energy demand for the next 48 hours based on historic time-series data.\n", - "\n", - "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration notebook](../../../configuration.ipynb) first, if you haven't already, to establish your connection to the AzureML Workspace.\n", - "\n", - "In this notebook you will learn how to:\n", - "1. Creating an Experiment using an existing Workspace\n", - "1. Configure AutoML using 'AutoMLConfig'\n", - "1. Train the model using AmlCompute\n", - "1. Explore the engineered features and results\n", - "1. Generate the forecast and compute the out-of-sample accuracy metrics\n", - "1. Configuration and remote run of AutoML for a time-series model with lag and rolling window features\n", - "1. Run and explore the forecast with lagging features" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import logging\n", - "\n", - "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n", - "from matplotlib import pyplot as plt\n", - "import pandas as pd\n", - "import numpy as np\n", - "import warnings\n", - "import os\n", - "\n", - "# Squash warning messages for cleaner output in the notebook\n", - "warnings.showwarning = lambda *args, **kwargs: None\n", - "\n", - "import azureml.core\n", - "from azureml.core import Experiment, Workspace, Dataset\n", - "from azureml.train.automl import AutoMLConfig\n", - "from datetime import datetime" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for the run history container in the workspace\n", - "experiment_name = \"automl-forecasting-energydemand\"\n", - "\n", - "# # project folder\n", - "# project_folder = './sample_projects/automl-forecasting-energy-demand'\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Run History Name\"] = experiment_name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create or Attach existing AmlCompute\n", - "A compute target is required to execute a remote Automated ML run. \n", - "\n", - "[Azure Machine Learning Compute](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) is a managed-compute infrastructure that allows the user to easily create a single or multi-node compute. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "#### Creation of AmlCompute takes approximately 5 minutes. \n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", - "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your cluster.\n", - "amlcompute_cluster_name = \"energy-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print(\"Found existing cluster, use it.\")\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", - " )\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Data\n", - "\n", - "We will use energy consumption [data from New York City](http://mis.nyiso.com/public/P-58Blist.htm) for model training. The data is stored in a tabular format and includes energy demand and basic weather data at an hourly frequency. \n", - "\n", - "With Azure Machine Learning datasets you can keep a single copy of data in your storage, easily access data during model training, share data and collaborate with other users. Below, we will upload the datatset and create a [tabular dataset](https://docs.microsoft.com/bs-latn-ba/azure/machine-learning/service/how-to-create-register-datasets#dataset-types) to be used training and prediction." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's set up what we know about the dataset.\n", - "\n", - "Target column is what we want to forecast.

\n", - "Time column is the time axis along which to predict.\n", - "\n", - "The other columns, \"temp\" and \"precip\", are implicitly designated as features." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "target_column_name = \"demand\"\n", - "time_column_name = \"timeStamp\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dataset = Dataset.Tabular.from_delimited_files(\n", - " path=\"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/nyc_energy.csv\"\n", - ").with_timestamp_columns(fine_grain_timestamp=time_column_name)\n", - "dataset.take(5).to_pandas_dataframe().reset_index(drop=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The NYC Energy dataset is missing energy demand values for all datetimes later than August 10th, 2017 5AM. Below, we trim the rows containing these missing values from the end of the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Cut off the end of the dataset due to large number of nan values\n", - "dataset = dataset.time_before(datetime(2017, 10, 10, 5))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Split the data into train and test sets" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The first split we make is into train and test sets. Note that we are splitting on time. Data before and including August 8th, 2017 5AM will be used for training, and data after will be used for testing." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# split into train based on time\n", - "train = dataset.time_before(datetime(2017, 8, 8, 5), include_boundary=True)\n", - "train.to_pandas_dataframe().reset_index(drop=True).sort_values(time_column_name).tail(5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# split into test based on time\n", - "test = dataset.time_between(datetime(2017, 8, 8, 6), datetime(2017, 8, 10, 5))\n", - "test.to_pandas_dataframe().reset_index(drop=True).head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setting the maximum forecast horizon\n", - "\n", - "The forecast horizon is the number of periods into the future that the model should predict. It is generally recommend that users set forecast horizons to less than 100 time periods (i.e. less than 100 hours in the NYC energy example). Furthermore, **AutoML's memory use and computation time increase in proportion to the length of the horizon**, so consider carefully how this value is set. If a long horizon forecast really is necessary, consider aggregating the series to a coarser time scale. \n", - "\n", - "Learn more about forecast horizons in our [Auto-train a time-series forecast model](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-auto-train-forecast#configure-and-run-experiment) guide.\n", - "\n", - "In this example, we set the horizon to 48 hours." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "forecast_horizon = 48" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Forecasting Parameters\n", - "To define forecasting parameters for your experiment training, you can leverage the ForecastingParameters class. The table below details the forecasting parameter we will be passing into our experiment.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**time_column_name**|The name of your time column.|\n", - "|**forecast_horizon**|The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly).|\n", - "|**freq**|Forecast frequency. This optional parameter represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Train\n", - "\n", - "Instantiate an AutoMLConfig object. This config defines the settings and data used to run the experiment. We can provide extra configurations within 'automl_settings', for this forecasting task we add the forecasting parameters to hold all the additional forecasting parameters.\n", - "\n", - "|Property|Description|\n", - "|-|-|\n", - "|**task**|forecasting|\n", - "|**primary_metric**|This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", - "|**blocked_models**|Models in blocked_models won't be used by AutoML. All supported models can be found at [here](https://docs.microsoft.com/en-us/python/api/azureml-train-automl-client/azureml.train.automl.constants.supportedmodels.forecasting?view=azure-ml-py).|\n", - "|**experiment_timeout_hours**|Maximum amount of time in hours that the experiment take before it terminates.|\n", - "|**training_data**|The training data to be used within the experiment.|\n", - "|**label_column_name**|The name of the label column.|\n", - "|**compute_target**|The remote compute for training.|\n", - "|**n_cross_validations**|Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way.|\n", - "|**enable_early_stopping**|Flag to enble early termination if the score is not improving in the short term.|\n", - "|**forecasting_parameters**|A class holds all the forecasting related parameters.|\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", - "\n", - "forecasting_parameters = ForecastingParameters(\n", - " time_column_name=time_column_name,\n", - " forecast_horizon=forecast_horizon,\n", - " freq=\"H\", # Set the forecast frequency to be hourly\n", - ")\n", - "\n", - "automl_config = AutoMLConfig(\n", - " task=\"forecasting\",\n", - " primary_metric=\"normalized_root_mean_squared_error\",\n", - " blocked_models=[\"ExtremeRandomTrees\", \"AutoArima\", \"Prophet\"],\n", - " experiment_timeout_hours=0.3,\n", - " training_data=train,\n", - " label_column_name=target_column_name,\n", - " compute_target=compute_target,\n", - " enable_early_stopping=True,\n", - " n_cross_validations=3,\n", - " verbosity=logging.INFO,\n", - " forecasting_parameters=forecasting_parameters,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Call the `submit` method on the experiment object and pass the run configuration. Depending on the data and the number of iterations this can run for a while.\n", - "One may specify `show_output = True` to print currently running iterations to the console." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run = experiment.submit(automl_config, show_output=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve the Best Run details\n", - "Below we retrieve the best Run object from among all the runs in the experiment." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run = remote_run.get_best_child()\n", - "best_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Featurization\n", - "We can look at the engineered feature names generated in time-series featurization via. the JSON file named 'engineered_feature_names.json' under the run outputs. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Download the JSON file locally\n", - "best_run.download_file(\"outputs/engineered_feature_names.json\", \"engineered_feature_names.json\")\n", - "with open(\"engineered_feature_names.json\", \"r\") as f:\n", - " records = json.load(f)\n", - "\n", - "records" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### View featurization summary\n", - "You can also see what featurization steps were performed on different raw features in the user data. For each raw feature in the user data, the following information is displayed:\n", - "\n", - "+ Raw feature name\n", - "+ Number of engineered features formed out of this raw feature\n", - "+ Type detected\n", - "+ If feature was dropped\n", - "+ List of feature transformations for the raw feature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Download the featurization summary JSON file locally\n", - "best_run.download_file(\"outputs/featurization_summary.json\", \"featurization_summary.json\")\n", - "\n", - "# Render the JSON as a pandas DataFrame\n", - "with open(\"featurization_summary.json\", \"r\") as f:\n", - " records = json.load(f)\n", - "fs = pd.DataFrame.from_records(records)\n", - "\n", - "# View a summary of the featurization \n", - "fs[[\"RawFeatureName\", \"TypeDetected\", \"Dropped\", \"EngineeredFeatureCount\", \"Transformations\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Forecasting\n", - "\n", - "Now that we have retrieved the best pipeline/model, it can be used to make predictions on test data. We will do batch scoring on the test dataset which should have the same schema as training dataset.\n", - "\n", - "The inference will run on a remote compute. In this example, it will re-use the training compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_experiment = Experiment(ws, experiment_name + \"_inference\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retreiving forecasts from the model\n", - "We have created a function called `run_forecast` that submits the test data to the best model determined during the training run and retrieves forecasts. This function uses a helper script `forecasting_script` which is uploaded and expecuted on the remote compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from run_forecast import run_remote_inference\n", - "\n", - "remote_run_infer = run_remote_inference(\n", - " test_experiment=test_experiment,\n", - " compute_target=compute_target,\n", - " train_run=best_run,\n", - " test_dataset=test,\n", - " target_column_name=target_column_name,\n", - ")\n", - "remote_run_infer.wait_for_completion(show_output=False)\n", - "\n", - "# download the inference output file to the local machine\n", - "remote_run_infer.download_file(\"outputs/predictions.csv\", \"predictions.csv\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Evaluate\n", - "To evaluate the accuracy of the forecast, we'll compare against the actual sales quantities for some select metrics, included the mean absolute percentage error (MAPE). For more metrics that can be used for evaluation after training, please see [supported metrics](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#regressionforecasting-metrics), and [how to calculate residuals](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#residuals)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# load forecast data frame\n", - "fcst_df = pd.read_csv(\"predictions.csv\", parse_dates=[time_column_name])\n", - "fcst_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.shared import constants\n", - "from azureml.automl.runtime.shared.score import scoring\n", - "from matplotlib import pyplot as plt\n", - "\n", - "# use automl metrics module\n", - "scores = scoring.score_regression(\n", - " y_test=fcst_df[target_column_name],\n", - " y_pred=fcst_df[\"predicted\"],\n", - " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", - ")\n", - "\n", - "print(\"[Test data scores]\\n\")\n", - "for key, value in scores.items():\n", - " print(\"{}: {:.3f}\".format(key, value))\n", - "\n", - "# Plot outputs\n", - "%matplotlib inline\n", - "test_pred = plt.scatter(fcst_df[target_column_name], fcst_df[\"predicted\"], color=\"b\")\n", - "test_test = plt.scatter(\n", - " fcst_df[target_column_name], fcst_df[target_column_name], color=\"g\"\n", - ")\n", - "plt.legend(\n", - " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Advanced Training \n", - "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, time series identifier columns and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Using lags and rolling window features\n", - "Now we will configure the target lags, that is the previous values of the target variables, meaning the prediction is no longer horizon-less. We therefore must still specify the `forecast_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features.\n", - "\n", - "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the iteration_timeout_minutes parameter value to get results." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "advanced_forecasting_parameters = ForecastingParameters(\n", - " time_column_name=time_column_name,\n", - " forecast_horizon=forecast_horizon,\n", - " target_lags=12,\n", - " target_rolling_window_size=4,\n", - ")\n", - "\n", - "automl_config = AutoMLConfig(\n", - " task=\"forecasting\",\n", - " primary_metric=\"normalized_root_mean_squared_error\",\n", - " blocked_models=[\n", - " \"ElasticNet\",\n", - " \"ExtremeRandomTrees\",\n", - " \"GradientBoosting\",\n", - " \"XGBoostRegressor\",\n", - " \"ExtremeRandomTrees\",\n", - " \"AutoArima\",\n", - " \"Prophet\",\n", - " ], # These models are blocked for tutorial purposes, remove this for real use cases.\n", - " experiment_timeout_hours=0.3,\n", - " training_data=train,\n", - " label_column_name=target_column_name,\n", - " compute_target=compute_target,\n", - " enable_early_stopping=True,\n", - " n_cross_validations=3,\n", - " verbosity=logging.INFO,\n", - " forecasting_parameters=advanced_forecasting_parameters,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We now start a new remote run, this time with lag and rolling window featurization. AutoML applies featurizations in the setup stage, prior to iterating over ML models. The full training set is featurized first, followed by featurization of each of the CV splits. Lag and rolling window features introduce additional complexity, so the run will take longer than in the previous example that lacked these featurizations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "advanced_remote_run = experiment.submit(automl_config, show_output=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "advanced_remote_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Retrieve the Best Run details" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run_lags = remote_run.get_best_child()\n", - "best_run_lags" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Advanced Results\n", - "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, time series identifier columns and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test_experiment_advanced = Experiment(ws, experiment_name + \"_inference_advanced\")\n", - "advanced_remote_run_infer = run_remote_inference(\n", - " test_experiment=test_experiment_advanced,\n", - " compute_target=compute_target,\n", - " train_run=best_run_lags,\n", - " test_dataset=test,\n", - " target_column_name=target_column_name,\n", - " inference_folder=\"./forecast_advanced\",\n", - ")\n", - "advanced_remote_run_infer.wait_for_completion(show_output=False)\n", - "\n", - "# download the inference output file to the local machine\n", - "advanced_remote_run_infer.download_file(\n", - " \"outputs/predictions.csv\", \"predictions_advanced.csv\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fcst_adv_df = pd.read_csv(\"predictions_advanced.csv\", parse_dates=[time_column_name])\n", - "fcst_adv_df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.shared import constants\n", - "from azureml.automl.runtime.shared.score import scoring\n", - "from matplotlib import pyplot as plt\n", - "\n", - "# use automl metrics module\n", - "scores = scoring.score_regression(\n", - " y_test=fcst_adv_df[target_column_name],\n", - " y_pred=fcst_adv_df[\"predicted\"],\n", - " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", - ")\n", - "\n", - "print(\"[Test data scores]\\n\")\n", - "for key, value in scores.items():\n", - " print(\"{}: {:.3f}\".format(key, value))\n", - "\n", - "# Plot outputs\n", - "%matplotlib inline\n", - "test_pred = plt.scatter(\n", - " fcst_adv_df[target_column_name], fcst_adv_df[\"predicted\"], color=\"b\"\n", - ")\n", - "test_test = plt.scatter(\n", - " fcst_adv_df[target_column_name], fcst_adv_df[target_column_name], color=\"g\"\n", - ")\n", - "plt.legend(\n", - " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", - ")\n", - "plt.show()" - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand/auto-ml-forecasting-energy-demand.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "_**Forecasting using the Energy Demand Dataset**_\n", + "\n", + "## Contents\n", + "1. [Introduction](#introduction)\n", + "1. [Setup](#setup)\n", + "1. [Data and Forecasting Configurations](#data)\n", + "1. [Train](#train)\n", + "1. [Generate and Evaluate the Forecast](#forecast)\n", + "\n", + "Advanced Forecasting\n", + "1. [Advanced Training](#advanced_training)\n", + "1. [Advanced Results](#advanced_results)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction\n", + "\n", + "In this example we use the associated New York City energy demand dataset to showcase how you can use AutoML for a simple forecasting problem and explore the results. The goal is predict the energy demand for the next 48 hours based on historic time-series data.\n", + "\n", + "If you are using an Azure Machine Learning Compute Instance, you are all set. Otherwise, go through the [configuration notebook](../../../configuration.ipynb) first, if you haven't already, to establish your connection to the AzureML Workspace.\n", + "\n", + "In this notebook you will learn how to:\n", + "1. Creating an Experiment using an existing Workspace\n", + "1. Configure AutoML using 'AutoMLConfig'\n", + "1. Train the model using AmlCompute\n", + "1. Explore the engineered features and results\n", + "1. Generate the forecast and compute the out-of-sample accuracy metrics\n", + "1. Configuration and remote run of AutoML for a time-series model with lag and rolling window features\n", + "1. Run and explore the forecast with lagging features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "\n", + "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n", + "from matplotlib import pyplot as plt\n", + "import pandas as pd\n", + "import numpy as np\n", + "import warnings\n", + "import os\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", + "\n", + "import azureml.core\n", + "from azureml.core import Experiment, Workspace, Dataset\n", + "from azureml.train.automl import AutoMLConfig\n", + "from datetime import datetime" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.0 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As part of the setup you have already created an Azure ML `Workspace` object. For Automated ML you will need to create an `Experiment` object, which is a named object in a `Workspace` used to run experiments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for the run history container in the workspace\n", + "experiment_name = \"automl-forecasting-energydemand\"\n", + "\n", + "# # project folder\n", + "# project_folder = './sample_projects/automl-forecasting-energy-demand'\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Run History Name\"] = experiment_name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create or Attach existing AmlCompute\n", + "A compute target is required to execute a remote Automated ML run. \n", + "\n", + "[Azure Machine Learning Compute](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) is a managed-compute infrastructure that allows the user to easily create a single or multi-node compute. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "#### Creation of AmlCompute takes approximately 5 minutes. \n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process.\n", + "As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read [this article](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your cluster.\n", + "amlcompute_cluster_name = \"energy-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Data\n", + "\n", + "We will use energy consumption [data from New York City](http://mis.nyiso.com/public/P-58Blist.htm) for model training. The data is stored in a tabular format and includes energy demand and basic weather data at an hourly frequency. \n", + "\n", + "With Azure Machine Learning datasets you can keep a single copy of data in your storage, easily access data during model training, share data and collaborate with other users. Below, we will upload the datatset and create a [tabular dataset](https://docs.microsoft.com/bs-latn-ba/azure/machine-learning/service/how-to-create-register-datasets#dataset-types) to be used training and prediction." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's set up what we know about the dataset.\n", + "\n", + "Target column is what we want to forecast.

\n", + "Time column is the time axis along which to predict.\n", + "\n", + "The other columns, \"temp\" and \"precip\", are implicitly designated as features." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "target_column_name = \"demand\"\n", + "time_column_name = \"timeStamp\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataset = Dataset.Tabular.from_delimited_files(\n", + " path=\"https://automlsamplenotebookdata.blob.core.windows.net/automl-sample-notebook-data/nyc_energy.csv\"\n", + ").with_timestamp_columns(fine_grain_timestamp=time_column_name)\n", + "dataset.take(5).to_pandas_dataframe().reset_index(drop=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The NYC Energy dataset is missing energy demand values for all datetimes later than August 10th, 2017 5AM. Below, we trim the rows containing these missing values from the end of the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Cut off the end of the dataset due to large number of nan values\n", + "dataset = dataset.time_before(datetime(2017, 10, 10, 5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Split the data into train and test sets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first split we make is into train and test sets. Note that we are splitting on time. Data before and including August 8th, 2017 5AM will be used for training, and data after will be used for testing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# split into train based on time\n", + "train = dataset.time_before(datetime(2017, 8, 8, 5), include_boundary=True)\n", + "train.to_pandas_dataframe().reset_index(drop=True).sort_values(time_column_name).tail(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# split into test based on time\n", + "test = dataset.time_between(datetime(2017, 8, 8, 6), datetime(2017, 8, 10, 5))\n", + "test.to_pandas_dataframe().reset_index(drop=True).head(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting the maximum forecast horizon\n", + "\n", + "The forecast horizon is the number of periods into the future that the model should predict. It is generally recommend that users set forecast horizons to less than 100 time periods (i.e. less than 100 hours in the NYC energy example). Furthermore, **AutoML's memory use and computation time increase in proportion to the length of the horizon**, so consider carefully how this value is set. If a long horizon forecast really is necessary, consider aggregating the series to a coarser time scale. \n", + "\n", + "Learn more about forecast horizons in our [Auto-train a time-series forecast model](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-auto-train-forecast#configure-and-run-experiment) guide.\n", + "\n", + "In this example, we set the horizon to 48 hours." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "forecast_horizon = 48" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Forecasting Parameters\n", + "To define forecasting parameters for your experiment training, you can leverage the ForecastingParameters class. The table below details the forecasting parameter we will be passing into our experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**time_column_name**|The name of your time column.|\n", + "|**forecast_horizon**|The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly).|\n", + "|**freq**|Forecast frequency. This optional parameter represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Train\n", + "\n", + "Instantiate an AutoMLConfig object. This config defines the settings and data used to run the experiment. We can provide extra configurations within 'automl_settings', for this forecasting task we add the forecasting parameters to hold all the additional forecasting parameters.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|forecasting|\n", + "|**primary_metric**|This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error|\n", + "|**blocked_models**|Models in blocked_models won't be used by AutoML. All supported models can be found at [here](https://docs.microsoft.com/en-us/python/api/azureml-train-automl-client/azureml.train.automl.constants.supportedmodels.forecasting?view=azure-ml-py).|\n", + "|**experiment_timeout_hours**|Maximum amount of time in hours that the experiment take before it terminates.|\n", + "|**training_data**|The training data to be used within the experiment.|\n", + "|**label_column_name**|The name of the label column.|\n", + "|**compute_target**|The remote compute for training.|\n", + "|**n_cross_validations**|Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way.|\n", + "|**enable_early_stopping**|Flag to enble early termination if the score is not improving in the short term.|\n", + "|**forecasting_parameters**|A class holds all the forecasting related parameters.|\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the experiment_timeout_hours parameter value to get results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", + "\n", + "forecasting_parameters = ForecastingParameters(\n", + " time_column_name=time_column_name,\n", + " forecast_horizon=forecast_horizon,\n", + " freq=\"H\", # Set the forecast frequency to be hourly\n", + ")\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"forecasting\",\n", + " primary_metric=\"normalized_root_mean_squared_error\",\n", + " blocked_models=[\"ExtremeRandomTrees\", \"AutoArima\", \"Prophet\"],\n", + " experiment_timeout_hours=0.3,\n", + " training_data=train,\n", + " label_column_name=target_column_name,\n", + " compute_target=compute_target,\n", + " enable_early_stopping=True,\n", + " n_cross_validations=3,\n", + " verbosity=logging.INFO,\n", + " forecasting_parameters=forecasting_parameters,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the `submit` method on the experiment object and pass the run configuration. Depending on the data and the number of iterations this can run for a while.\n", + "One may specify `show_output = True` to print currently running iterations to the console." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve the Best Run details\n", + "Below we retrieve the best Run object from among all the runs in the experiment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run = remote_run.get_best_child()\n", + "best_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Featurization\n", + "We can look at the engineered feature names generated in time-series featurization via. the JSON file named 'engineered_feature_names.json' under the run outputs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the JSON file locally\n", + "best_run.download_file(\n", + " \"outputs/engineered_feature_names.json\", \"engineered_feature_names.json\"\n", + ")\n", + "with open(\"engineered_feature_names.json\", \"r\") as f:\n", + " records = json.load(f)\n", + "\n", + "records" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### View featurization summary\n", + "You can also see what featurization steps were performed on different raw features in the user data. For each raw feature in the user data, the following information is displayed:\n", + "\n", + "+ Raw feature name\n", + "+ Number of engineered features formed out of this raw feature\n", + "+ Type detected\n", + "+ If feature was dropped\n", + "+ List of feature transformations for the raw feature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Download the featurization summary JSON file locally\n", + "best_run.download_file(\n", + " \"outputs/featurization_summary.json\", \"featurization_summary.json\"\n", + ")\n", + "\n", + "# Render the JSON as a pandas DataFrame\n", + "with open(\"featurization_summary.json\", \"r\") as f:\n", + " records = json.load(f)\n", + "fs = pd.DataFrame.from_records(records)\n", + "\n", + "# View a summary of the featurization\n", + "fs[\n", + " [\n", + " \"RawFeatureName\",\n", + " \"TypeDetected\",\n", + " \"Dropped\",\n", + " \"EngineeredFeatureCount\",\n", + " \"Transformations\",\n", + " ]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Forecasting\n", + "\n", + "Now that we have retrieved the best pipeline/model, it can be used to make predictions on test data. We will do batch scoring on the test dataset which should have the same schema as training dataset.\n", + "\n", + "The inference will run on a remote compute. In this example, it will re-use the training compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_experiment = Experiment(ws, experiment_name + \"_inference\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieving forecasts from the model\n", + "We have created a function called `run_forecast` that submits the test data to the best model determined during the training run and retrieves forecasts. This function uses a helper script `forecasting_script` which is uploaded and expecuted on the remote compute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from run_forecast import run_remote_inference\n", + "\n", + "remote_run_infer = run_remote_inference(\n", + " test_experiment=test_experiment,\n", + " compute_target=compute_target,\n", + " train_run=best_run,\n", + " test_dataset=test,\n", + " target_column_name=target_column_name,\n", + ")\n", + "remote_run_infer.wait_for_completion(show_output=False)\n", + "\n", + "# download the inference output file to the local machine\n", + "remote_run_infer.download_file(\"outputs/predictions.csv\", \"predictions.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Evaluate\n", + "To evaluate the accuracy of the forecast, we'll compare against the actual sales quantities for some select metrics, included the mean absolute percentage error (MAPE). For more metrics that can be used for evaluation after training, please see [supported metrics](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#regressionforecasting-metrics), and [how to calculate residuals](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-understand-automated-ml#residuals)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load forecast data frame\n", + "fcst_df = pd.read_csv(\"predictions.csv\", parse_dates=[time_column_name])\n", + "fcst_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.shared import constants\n", + "from azureml.automl.runtime.shared.score import scoring\n", + "from matplotlib import pyplot as plt\n", + "\n", + "# use automl metrics module\n", + "scores = scoring.score_regression(\n", + " y_test=fcst_df[target_column_name],\n", + " y_pred=fcst_df[\"predicted\"],\n", + " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", + ")\n", + "\n", + "print(\"[Test data scores]\\n\")\n", + "for key, value in scores.items():\n", + " print(\"{}: {:.3f}\".format(key, value))\n", + "\n", + "# Plot outputs\n", + "%matplotlib inline\n", + "test_pred = plt.scatter(fcst_df[target_column_name], fcst_df[\"predicted\"], color=\"b\")\n", + "test_test = plt.scatter(\n", + " fcst_df[target_column_name], fcst_df[target_column_name], color=\"g\"\n", + ")\n", + "plt.legend(\n", + " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced Training \n", + "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, time series identifier columns and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using lags and rolling window features\n", + "Now we will configure the target lags, that is the previous values of the target variables, meaning the prediction is no longer horizon-less. We therefore must still specify the `forecast_horizon` that the model will learn to forecast. The `target_lags` keyword specifies how far back we will construct the lags of the target variable, and the `target_rolling_window_size` specifies the size of the rolling window over which we will generate the `max`, `min` and `sum` features.\n", + "\n", + "This notebook uses the blocked_models parameter to exclude some models that take a longer time to train on this dataset. You can choose to remove models from the blocked_models list but you may need to increase the iteration_timeout_minutes parameter value to get results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "advanced_forecasting_parameters = ForecastingParameters(\n", + " time_column_name=time_column_name,\n", + " forecast_horizon=forecast_horizon,\n", + " target_lags=12,\n", + " target_rolling_window_size=4,\n", + ")\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"forecasting\",\n", + " primary_metric=\"normalized_root_mean_squared_error\",\n", + " blocked_models=[\n", + " \"ElasticNet\",\n", + " \"ExtremeRandomTrees\",\n", + " \"GradientBoosting\",\n", + " \"XGBoostRegressor\",\n", + " \"ExtremeRandomTrees\",\n", + " \"AutoArima\",\n", + " \"Prophet\",\n", + " ], # These models are blocked for tutorial purposes, remove this for real use cases.\n", + " experiment_timeout_hours=0.3,\n", + " training_data=train,\n", + " label_column_name=target_column_name,\n", + " compute_target=compute_target,\n", + " enable_early_stopping=True,\n", + " n_cross_validations=3,\n", + " verbosity=logging.INFO,\n", + " forecasting_parameters=advanced_forecasting_parameters,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now start a new remote run, this time with lag and rolling window featurization. AutoML applies featurizations in the setup stage, prior to iterating over ML models. The full training set is featurized first, followed by featurization of each of the CV splits. Lag and rolling window features introduce additional complexity, so the run will take longer than in the previous example that lacked these featurizations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "advanced_remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "advanced_remote_run.wait_for_completion()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Retrieve the Best Run details" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "best_run_lags = remote_run.get_best_child()\n", + "best_run_lags" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced Results\n", + "We did not use lags in the previous model specification. In effect, the prediction was the result of a simple regression on date, time series identifier columns and any additional features. This is often a very good prediction as common time series patterns like seasonality and trends can be captured in this manner. Such simple regression is horizon-less: it doesn't matter how far into the future we are predicting, because we are not using past data. In the previous example, the horizon was only used to split the data for cross-validation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_experiment_advanced = Experiment(ws, experiment_name + \"_inference_advanced\")\n", + "advanced_remote_run_infer = run_remote_inference(\n", + " test_experiment=test_experiment_advanced,\n", + " compute_target=compute_target,\n", + " train_run=best_run_lags,\n", + " test_dataset=test,\n", + " target_column_name=target_column_name,\n", + " inference_folder=\"./forecast_advanced\",\n", + ")\n", + "advanced_remote_run_infer.wait_for_completion(show_output=False)\n", + "\n", + "# download the inference output file to the local machine\n", + "advanced_remote_run_infer.download_file(\n", + " \"outputs/predictions.csv\", \"predictions_advanced.csv\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fcst_adv_df = pd.read_csv(\"predictions_advanced.csv\", parse_dates=[time_column_name])\n", + "fcst_adv_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.shared import constants\n", + "from azureml.automl.runtime.shared.score import scoring\n", + "from matplotlib import pyplot as plt\n", + "\n", + "# use automl metrics module\n", + "scores = scoring.score_regression(\n", + " y_test=fcst_adv_df[target_column_name],\n", + " y_pred=fcst_adv_df[\"predicted\"],\n", + " metrics=list(constants.Metric.SCALAR_REGRESSION_SET),\n", + ")\n", + "\n", + "print(\"[Test data scores]\\n\")\n", + "for key, value in scores.items():\n", + " print(\"{}: {:.3f}\".format(key, value))\n", + "\n", + "# Plot outputs\n", + "%matplotlib inline\n", + "test_pred = plt.scatter(\n", + " fcst_adv_df[target_column_name], fcst_adv_df[\"predicted\"], color=\"b\"\n", + ")\n", + "test_test = plt.scatter(\n", + " fcst_adv_df[target_column_name], fcst_adv_df[target_column_name], color=\"g\"\n", + ")\n", + "plt.legend(\n", + " (test_pred, test_test), (\"prediction\", \"truth\"), loc=\"upper left\", fontsize=8\n", + ")\n", + "plt.show()" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "categories": [ + "how-to-use-azureml", + "automated-machine-learning" ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "categories": [ - "how-to-use-azureml", - "automated-machine-learning" - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-forecast-function/auto-ml-forecasting-function.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-forecast-function/auto-ml-forecasting-function.ipynb index 23ae3e425..b140752fc 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-forecast-function/auto-ml-forecasting-function.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-forecast-function/auto-ml-forecasting-function.ipynb @@ -1,894 +1,893 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Automated Machine Learning\n", - "\n", - "#### Forecasting away from training data\n", - "\n", - "\n", - "## Contents\n", - "1. [Introduction](#Introduction)\n", - "2. [Setup](#Setup)\n", - "3. [Data](#Data)\n", - "4. [Prepare remote compute and data.](#prepare_remote)\n", - "4. [Create the configuration and train a forecaster](#train)\n", - "5. [Forecasting from the trained model](#forecasting)\n", - "6. [Forecasting away from training data](#forecasting_away)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Introduction\n", - "This notebook demonstrates the full interface of the `forecast()` function. \n", - "\n", - "The best known and most frequent usage of `forecast` enables forecasting on test sets that immediately follows training data. \n", - "\n", - "However, in many use cases it is necessary to continue using the model for some time before retraining it. This happens especially in **high frequency forecasting** when forecasts need to be made more frequently than the model can be retrained. Examples are in Internet of Things and predictive cloud resource scaling.\n", - "\n", - "Here we show how to use the `forecast()` function when a time gap exists between training data and prediction period.\n", - "\n", - "Terminology:\n", - "* forecast origin: the last period when the target value is known\n", - "* forecast periods(s): the period(s) for which the value of the target is desired.\n", - "* lookback: how many past periods (before forecast origin) the model function depends on. The larger of number of lags and length of rolling window.\n", - "* prediction context: `lookback` periods immediately preceding the forecast origin\n", - "\n", - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/automl-forecasting-function.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please make sure you have followed the `configuration.ipynb` notebook so that your ML workspace information is saved in the config file." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import pandas as pd\n", - "import numpy as np\n", - "import logging\n", - "import warnings\n", - "\n", - "import azureml.core\n", - "from azureml.core.dataset import Dataset\n", - "from pandas.tseries.frequencies import to_offset\n", - "from azureml.core.compute import AmlCompute\n", - "from azureml.core.compute import ComputeTarget\n", - "from azureml.core.runconfig import RunConfiguration\n", - "from azureml.core.conda_dependencies import CondaDependencies\n", - "\n", - "# Squash warning messages for cleaner output in the notebook\n", - "warnings.showwarning = lambda *args, **kwargs: None\n", - "\n", - "np.set_printoptions(precision=4, suppress=True, linewidth=120)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This sample notebook may use features that are not available in previous versions of the Azure ML SDK." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"This notebook was created using version 1.38.0 of the Azure ML SDK\")\n", - "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.workspace import Workspace\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.train.automl import AutoMLConfig\n", - "\n", - "ws = Workspace.from_config()\n", - "\n", - "# choose a name for the run history container in the workspace\n", - "experiment_name = \"automl-forecast-function-demo\"\n", - "\n", - "experiment = Experiment(ws, experiment_name)\n", - "\n", - "output = {}\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"SKU\"] = ws.sku\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Run History Name\"] = experiment_name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data\n", - "For the demonstration purposes we will generate the data artificially and use them for the forecasting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "TIME_COLUMN_NAME = \"date\"\n", - "TIME_SERIES_ID_COLUMN_NAME = \"time_series_id\"\n", - "TARGET_COLUMN_NAME = \"y\"\n", - "\n", - "\n", - "def get_timeseries(\n", - " train_len: int,\n", - " test_len: int,\n", - " time_column_name: str,\n", - " target_column_name: str,\n", - " time_series_id_column_name: str,\n", - " time_series_number: int = 1,\n", - " freq: str = \"H\",\n", - "):\n", - " \"\"\"\n", - " Return the time series of designed length.\n", - "\n", - " :param train_len: The length of training data (one series).\n", - " :type train_len: int\n", - " :param test_len: The length of testing data (one series).\n", - " :type test_len: int\n", - " :param time_column_name: The desired name of a time column.\n", - " :type time_column_name: str\n", - " :param time_series_number: The number of time series in the data set.\n", - " :type time_series_number: int\n", - " :param freq: The frequency string representing pandas offset.\n", - " see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html\n", - " :type freq: str\n", - " :returns: the tuple of train and test data sets.\n", - " :rtype: tuple\n", - "\n", - " \"\"\"\n", - " data_train = [] # type: List[pd.DataFrame]\n", - " data_test = [] # type: List[pd.DataFrame]\n", - " data_length = train_len + test_len\n", - " for i in range(time_series_number):\n", - " X = pd.DataFrame(\n", - " {\n", - " time_column_name: pd.date_range(\n", - " start=\"2000-01-01\", periods=data_length, freq=freq\n", - " ),\n", - " target_column_name: np.arange(data_length).astype(float)\n", - " + np.random.rand(data_length)\n", - " + i * 5,\n", - " \"ext_predictor\": np.asarray(range(42, 42 + data_length)),\n", - " time_series_id_column_name: np.repeat(\"ts{}\".format(i), data_length),\n", - " }\n", - " )\n", - " data_train.append(X[:train_len])\n", - " data_test.append(X[train_len:])\n", - " X_train = pd.concat(data_train)\n", - " y_train = X_train.pop(target_column_name).values\n", - " X_test = pd.concat(data_test)\n", - " y_test = X_test.pop(target_column_name).values\n", - " return X_train, y_train, X_test, y_test\n", - "\n", - "\n", - "n_test_periods = 6\n", - "n_train_periods = 30\n", - "X_train, y_train, X_test, y_test = get_timeseries(\n", - " train_len=n_train_periods,\n", - " test_len=n_test_periods,\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " target_column_name=TARGET_COLUMN_NAME,\n", - " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", - " time_series_number=2,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's see what the training data looks like." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_train.tail()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# plot the example time series\n", - "import matplotlib.pyplot as plt\n", - "\n", - "whole_data = X_train.copy()\n", - "target_label = \"y\"\n", - "whole_data[target_label] = y_train\n", - "for g in whole_data.groupby(\"time_series_id\"):\n", - " plt.plot(g[1][\"date\"].values, g[1][\"y\"].values, label=g[0])\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prepare remote compute and data. \n", - "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace), is paired with the storage account, which contains the default data store. We will use it to upload the artificial data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We need to save thw artificial data and then upload them to default workspace datastore.\n", - "DATA_PATH = \"fc_fn_data\"\n", - "DATA_PATH_X = \"{}/data_train.csv\".format(DATA_PATH)\n", - "if not os.path.isdir(\"data\"):\n", - " os.mkdir(\"data\")\n", - "pd.DataFrame(whole_data).to_csv(\"data/data_train.csv\", index=False)\n", - "# Upload saved data to the default data store.\n", - "ds = ws.get_default_datastore()\n", - "ds.upload(src_dir=\"./data\", target_path=DATA_PATH, overwrite=True, show_progress=True)\n", - "train_data = Dataset.Tabular.from_delimited_files(path=ds.path(DATA_PATH_X))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "# Choose a name for your CPU cluster\n", - "amlcompute_cluster_name = \"fcfn-cluster\"\n", - "\n", - "# Verify that cluster does not exist already\n", - "try:\n", - " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", - " print(\"Found existing cluster, use it.\")\n", - "except ComputeTargetException:\n", - " compute_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", - " )\n", - " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", - "\n", - "compute_target.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create the configuration and train a forecaster \n", - "First generate the configuration, in which we:\n", - "* Set metadata columns: target, time column and time-series id column names.\n", - "* Validate our data using cross validation with rolling window method.\n", - "* Set normalized root mean squared error as a metric to select the best model.\n", - "* Set early termination to True, so the iterations through the models will stop when no improvements in accuracy score will be made.\n", - "* Set limitations on the length of experiment run to 15 minutes.\n", - "* Finally, we set the task to be forecasting.\n", - "* We apply the lag lead operator to the target value i.e. we use the previous values as a predictor for the future ones.\n", - "* [Optional] Forecast frequency parameter (freq) represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", - "\n", - "lags = [1, 2, 3]\n", - "forecast_horizon = n_test_periods\n", - "forecasting_parameters = ForecastingParameters(\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " forecast_horizon=forecast_horizon,\n", - " time_series_id_column_names=[TIME_SERIES_ID_COLUMN_NAME],\n", - " target_lags=lags,\n", - " freq=\"H\", # Set the forecast frequency to be hourly\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run the model selection and training process. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core.workspace import Workspace\n", - "from azureml.core.experiment import Experiment\n", - "from azureml.train.automl import AutoMLConfig\n", - "\n", - "\n", - "automl_config = AutoMLConfig(\n", - " task=\"forecasting\",\n", - " debug_log=\"automl_forecasting_function.log\",\n", - " primary_metric=\"normalized_root_mean_squared_error\",\n", - " experiment_timeout_hours=0.25,\n", - " enable_early_stopping=True,\n", - " training_data=train_data,\n", - " compute_target=compute_target,\n", - " n_cross_validations=3,\n", - " verbosity=logging.INFO,\n", - " max_concurrent_iterations=4,\n", - " max_cores_per_iteration=-1,\n", - " label_column_name=target_label,\n", - " forecasting_parameters=forecasting_parameters,\n", - ")\n", - "\n", - "remote_run = experiment.submit(automl_config, show_output=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "remote_run.wait_for_completion()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Retrieve the best model to use it further.\n", - "_, fitted_model = remote_run.get_output()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Forecasting from the trained model " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this section we will review the `forecast` interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### X_train is directly followed by the X_test\n", - "\n", - "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", - "\n", - "![Forecasting after training](forecast_function_at_train.png)\n", - "\n", - "We use `X_test` as a **forecast request** to generate the predictions." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Typical path: X_test is known, forecast all upcoming periods" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# The data set contains hourly data, the training set ends at 01/02/2000 at 05:00\n", - "\n", - "# These are predictions we are asking the model to make (does not contain thet target column y),\n", - "# for 6 periods beginning with 2000-01-02 06:00, which immediately follows the training data\n", - "X_test" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred_no_gap, xy_nogap = fitted_model.forecast(X_test)\n", - "\n", - "# xy_nogap contains the predictions in the _automl_target_col column.\n", - "# Those same numbers are output in y_pred_no_gap\n", - "xy_nogap" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Confidence intervals" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Forecasting model may be used for the prediction of forecasting intervals by running ```forecast_quantiles()```. \n", - "This method accepts the same parameters as forecast()." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "quantiles = fitted_model.forecast_quantiles(X_test)\n", - "quantiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Distribution forecasts\n", - "\n", - "Often the figure of interest is not just the point prediction, but the prediction at some quantile of the distribution. \n", - "This arises when the forecast is used to control some kind of inventory, for example of grocery items or virtual machines for a cloud service. In such case, the control point is usually something like \"we want the item to be in stock and not run out 99% of the time\". This is called a \"service level\". Here is how you get quantile forecasts." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# specify which quantiles you would like\n", - "fitted_model.quantiles = [0.01, 0.5, 0.95]\n", - "# use forecast_quantiles function, not the forecast() one\n", - "y_pred_quantiles = fitted_model.forecast_quantiles(X_test)\n", - "\n", - "# quantile forecasts returned in a Dataframe along with the time and time series id columns\n", - "y_pred_quantiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Destination-date forecast: \"just do something\"\n", - "\n", - "In some scenarios, the X_test is not known. The forecast is likely to be weak, because it is missing contemporaneous predictors, which we will need to impute. If you still wish to predict forward under the assumption that the last known values will be carried forward, you can forecast out to \"destination date\". The destination date still needs to fit within the forecast horizon from training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# We will take the destination date as a last date in the test set.\n", - "dest = max(X_test[TIME_COLUMN_NAME])\n", - "y_pred_dest, xy_dest = fitted_model.forecast(forecast_destination=dest)\n", - "\n", - "# This form also shows how we imputed the predictors which were not given. (Not so well! Use with caution!)\n", - "xy_dest" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Forecasting away from training data \n", - "\n", - "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", - "\n", - "![Forecasting after training](forecast_function_away_from_train.png)\n", - "\n", - "The notion of forecast origin comes into play: the forecast origin is **the last period for which we have seen the target value**. This applies per time-series, so each time-series can have a different forecast origin. \n", - "\n", - "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in `y_test` (aligned with corresponding times in `X_test`)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# generate the same kind of test data we trained on,\n", - "# but now make the train set much longer, so that the test set will be in the future\n", - "X_context, y_context, X_away, y_away = get_timeseries(\n", - " train_len=42, # train data was 30 steps long\n", - " test_len=4,\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " target_column_name=TARGET_COLUMN_NAME,\n", - " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", - " time_series_number=2,\n", - ")\n", - "\n", - "# end of the data we trained on\n", - "print(X_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", - "# start of the data we want to predict on\n", - "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a gap of 12 hours between end of training and beginning of `X_away`. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only `X_away` will fail without adding context data for the model to consume." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " y_pred_away, xy_away = fitted_model.forecast(X_away)\n", - " xy_away\n", - "except Exception as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of `y` (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define `y` value to establish the forecast origin.\n", - "\n", - "We will use this helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def make_forecasting_query(\n", - " fulldata, time_column_name, target_column_name, forecast_origin, horizon, lookback\n", - "):\n", - "\n", - " \"\"\"\n", - " This function will take the full dataset, and create the query\n", - " to predict all values of the time series from the `forecast_origin`\n", - " forward for the next `horizon` horizons. Context from previous\n", - " `lookback` periods will be included.\n", - "\n", - "\n", - "\n", - " fulldata: pandas.DataFrame a time series dataset. Needs to contain X and y.\n", - " time_column_name: string which column (must be in fulldata) is the time axis\n", - " target_column_name: string which column (must be in fulldata) is to be forecast\n", - " forecast_origin: datetime type the last time we (pretend to) have target values\n", - " horizon: timedelta how far forward, in time units (not periods)\n", - " lookback: timedelta how far back does the model look\n", - "\n", - " Example:\n", - "\n", - "\n", - " ```\n", - "\n", - " forecast_origin = pd.to_datetime(\"2012-09-01\") + pd.DateOffset(days=5) # forecast 5 days after end of training\n", - " print(forecast_origin)\n", - "\n", - " X_query, y_query = make_forecasting_query(data,\n", - " forecast_origin = forecast_origin,\n", - " horizon = pd.DateOffset(days=7), # 7 days into the future\n", - " lookback = pd.DateOffset(days=1), # model has lag 1 period (day)\n", - " )\n", - "\n", - " ```\n", - " \"\"\"\n", - "\n", - " X_past = fulldata[\n", - " (fulldata[time_column_name] > forecast_origin - lookback)\n", - " & (fulldata[time_column_name] <= forecast_origin)\n", - " ]\n", - "\n", - " X_future = fulldata[\n", - " (fulldata[time_column_name] > forecast_origin)\n", - " & (fulldata[time_column_name] <= forecast_origin + horizon)\n", - " ]\n", - "\n", - " y_past = X_past.pop(target_column_name).values.astype(np.float)\n", - " y_future = X_future.pop(target_column_name).values.astype(np.float)\n", - "\n", - " # Now take y_future and turn it into question marks\n", - " y_query = y_future.copy().astype(\n", - " np.float\n", - " ) # because sometimes life hands you an int\n", - " y_query.fill(np.NaN)\n", - "\n", - " print(\"X_past is \" + str(X_past.shape) + \" - shaped\")\n", - " print(\"X_future is \" + str(X_future.shape) + \" - shaped\")\n", - " print(\"y_past is \" + str(y_past.shape) + \" - shaped\")\n", - " print(\"y_query is \" + str(y_query.shape) + \" - shaped\")\n", - "\n", - " X_pred = pd.concat([X_past, X_future])\n", - " y_pred = np.concatenate([y_past, y_query])\n", - " return X_pred, y_pred" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's see where the context data ends - it ends, by construction, just before the testing data starts." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\n", - " X_context.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", - " [\"min\", \"max\", \"count\"]\n", - " )\n", - ")\n", - "print(\n", - " X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", - " [\"min\", \"max\", \"count\"]\n", - " )\n", - ")\n", - "X_context.tail(5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Since the length of the lookback is 3,\n", - "# we need to add 3 periods from the context to the request\n", - "# so that the model has the data it needs\n", - "\n", - "# Put the X and y back together for a while.\n", - "# They like each other and it makes them happy.\n", - "X_context[TARGET_COLUMN_NAME] = y_context\n", - "X_away[TARGET_COLUMN_NAME] = y_away\n", - "fulldata = pd.concat([X_context, X_away])\n", - "\n", - "# forecast origin is the last point of data, which is one 1-hr period before test\n", - "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", - "# it is indeed the last point of the context\n", - "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", - "print(\"Forecast origin: \" + str(forecast_origin))\n", - "\n", - "# the model uses lags and rolling windows to look back in time\n", - "n_lookback_periods = max(lags)\n", - "lookback = pd.DateOffset(hours=n_lookback_periods)\n", - "\n", - "horizon = pd.DateOffset(hours=forecast_horizon)\n", - "\n", - "# now make the forecast query from context (refer to figure)\n", - "X_pred, y_pred = make_forecasting_query(\n", - " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", - ")\n", - "\n", - "# show the forecast request aligned\n", - "X_show = X_pred.copy()\n", - "X_show[TARGET_COLUMN_NAME] = y_pred\n", - "X_show" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the forecast origin is at 17:00 for both time-series, and periods from 18:00 are to be forecast." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Now everything works\n", - "y_pred_away, xy_away = fitted_model.forecast(X_pred, y_pred)\n", - "\n", - "# show the forecast aligned\n", - "X_show = xy_away.reset_index()\n", - "# without the generated features\n", - "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]\n", - "# prediction is in _automl_target_col" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Forecasting farther than the forecast horizon \n", - "When the forecast destination, or the latest date in the prediction data frame, is farther into the future than the specified forecast horizon, the `forecast()` function will still make point predictions out to the later date using a recursive operation mode. Internally, the method recursively applies the regular forecaster to generate context so that we can forecast further into the future. \n", - "\n", - "To illustrate the use-case and operation of recursive forecasting, we'll consider an example with a single time-series where the forecasting period directly follows the training period and is twice as long as the forecasting horizon given at training time.\n", - "\n", - "![Recursive_forecast_overview](recursive_forecast_overview_small.png)\n", - "\n", - "Internally, we apply the forecaster in an iterative manner and finish the forecast task in two interations. In the first iteration, we apply the forecaster and get the prediction for the first forecast-horizon periods (y_pred1). In the second iteraction, y_pred1 is used as the context to produce the prediction for the next forecast-horizon periods (y_pred2). The combination of (y_pred1 and y_pred2) gives the results for the total forecast periods. \n", - "\n", - "A caveat: forecast accuracy will likely be worse the farther we predict into the future since errors are compounded with recursive application of the forecaster.\n", - "\n", - "![Recursive_forecast_iter1](recursive_forecast_iter1.png)\n", - "![Recursive_forecast_iter2](recursive_forecast_iter2.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# generate the same kind of test data we trained on, but with a single time-series and test period twice as long\n", - "# as the forecast_horizon.\n", - "_, _, X_test_long, y_test_long = get_timeseries(\n", - " train_len=n_train_periods,\n", - " test_len=forecast_horizon * 2,\n", - " time_column_name=TIME_COLUMN_NAME,\n", - " target_column_name=TARGET_COLUMN_NAME,\n", - " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", - " time_series_number=1,\n", - ")\n", - "\n", - "print(X_test_long.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())\n", - "print(X_test_long.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# forecast() function will invoke the recursive forecast method internally.\n", - "y_pred_long, X_trans_long = fitted_model.forecast(X_test_long)\n", - "y_pred_long" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# What forecast() function does in this case is equivalent to iterating it twice over the test set as the following.\n", - "y_pred1, _ = fitted_model.forecast(X_test_long[:forecast_horizon])\n", - "y_pred_all, _ = fitted_model.forecast(\n", - " X_test_long, np.concatenate((y_pred1, np.full(forecast_horizon, np.nan)))\n", - ")\n", - "np.array_equal(y_pred_all, y_pred_long)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Confidence interval and distributional forecasts\n", - "AutoML cannot currently estimate forecast errors beyond the forecast horizon set during training, so the `forecast_quantiles()` function will return missing values for quantiles not equal to 0.5 beyond the forecast horizon. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fitted_model.forecast_quantiles(X_test_long)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly with the simple senarios illustrated above, forecasting farther than the forecast horizon in other senarios like 'multiple time-series', 'Destination-date forecast', and 'forecast away from the training data' are also automatically handled by the `forecast()` function. " - ] - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Automated Machine Learning\n", + "\n", + "#### Forecasting away from training data\n", + "\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "2. [Setup](#Setup)\n", + "3. [Data](#Data)\n", + "4. [Prepare remote compute and data.](#prepare_remote)\n", + "4. [Create the configuration and train a forecaster](#train)\n", + "5. [Forecasting from the trained model](#forecasting)\n", + "6. [Forecasting away from training data](#forecasting_away)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "This notebook demonstrates the full interface of the `forecast()` function. \n", + "\n", + "The best known and most frequent usage of `forecast` enables forecasting on test sets that immediately follows training data. \n", + "\n", + "However, in many use cases it is necessary to continue using the model for some time before retraining it. This happens especially in **high frequency forecasting** when forecasts need to be made more frequently than the model can be retrained. Examples are in Internet of Things and predictive cloud resource scaling.\n", + "\n", + "Here we show how to use the `forecast()` function when a time gap exists between training data and prediction period.\n", + "\n", + "Terminology:\n", + "* forecast origin: the last period when the target value is known\n", + "* forecast periods(s): the period(s) for which the value of the target is desired.\n", + "* lookback: how many past periods (before forecast origin) the model function depends on. The larger of number of lags and length of rolling window.\n", + "* prediction context: `lookback` periods immediately preceding the forecast origin\n", + "\n", + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/automl-forecasting-function.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Please make sure you have followed the `configuration.ipynb` notebook so that your ML workspace information is saved in the config file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "import numpy as np\n", + "import logging\n", + "import warnings\n", + "\n", + "import azureml.core\n", + "from azureml.core.dataset import Dataset\n", + "from pandas.tseries.frequencies import to_offset\n", + "from azureml.core.compute import AmlCompute\n", + "from azureml.core.compute import ComputeTarget\n", + "from azureml.core.runconfig import RunConfiguration\n", + "from azureml.core.conda_dependencies import CondaDependencies\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", + "\n", + "np.set_printoptions(precision=4, suppress=True, linewidth=120)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.0 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.workspace import Workspace\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.train.automl import AutoMLConfig\n", + "\n", + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for the run history container in the workspace\n", + "experiment_name = \"automl-forecast-function-demo\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"SKU\"] = ws.sku\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Run History Name\"] = experiment_name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data\n", + "For the demonstration purposes we will generate the data artificially and use them for the forecasting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "TIME_COLUMN_NAME = \"date\"\n", + "TIME_SERIES_ID_COLUMN_NAME = \"time_series_id\"\n", + "TARGET_COLUMN_NAME = \"y\"\n", + "\n", + "\n", + "def get_timeseries(\n", + " train_len: int,\n", + " test_len: int,\n", + " time_column_name: str,\n", + " target_column_name: str,\n", + " time_series_id_column_name: str,\n", + " time_series_number: int = 1,\n", + " freq: str = \"H\",\n", + "):\n", + " \"\"\"\n", + " Return the time series of designed length.\n", + "\n", + " :param train_len: The length of training data (one series).\n", + " :type train_len: int\n", + " :param test_len: The length of testing data (one series).\n", + " :type test_len: int\n", + " :param time_column_name: The desired name of a time column.\n", + " :type time_column_name: str\n", + " :param time_series_number: The number of time series in the data set.\n", + " :type time_series_number: int\n", + " :param freq: The frequency string representing pandas offset.\n", + " see https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html\n", + " :type freq: str\n", + " :returns: the tuple of train and test data sets.\n", + " :rtype: tuple\n", + "\n", + " \"\"\"\n", + " data_train = [] # type: List[pd.DataFrame]\n", + " data_test = [] # type: List[pd.DataFrame]\n", + " data_length = train_len + test_len\n", + " for i in range(time_series_number):\n", + " X = pd.DataFrame(\n", + " {\n", + " time_column_name: pd.date_range(\n", + " start=\"2000-01-01\", periods=data_length, freq=freq\n", + " ),\n", + " target_column_name: np.arange(data_length).astype(float)\n", + " + np.random.rand(data_length)\n", + " + i * 5,\n", + " \"ext_predictor\": np.asarray(range(42, 42 + data_length)),\n", + " time_series_id_column_name: np.repeat(\"ts{}\".format(i), data_length),\n", + " }\n", + " )\n", + " data_train.append(X[:train_len])\n", + " data_test.append(X[train_len:])\n", + " X_train = pd.concat(data_train)\n", + " y_train = X_train.pop(target_column_name).values\n", + " X_test = pd.concat(data_test)\n", + " y_test = X_test.pop(target_column_name).values\n", + " return X_train, y_train, X_test, y_test\n", + "\n", + "\n", + "n_test_periods = 6\n", + "n_train_periods = 30\n", + "X_train, y_train, X_test, y_test = get_timeseries(\n", + " train_len=n_train_periods,\n", + " test_len=n_test_periods,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what the training data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_train.tail()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# plot the example time series\n", + "import matplotlib.pyplot as plt\n", + "\n", + "whole_data = X_train.copy()\n", + "target_label = \"y\"\n", + "whole_data[target_label] = y_train\n", + "for g in whole_data.groupby(\"time_series_id\"):\n", + " plt.plot(g[1][\"date\"].values, g[1][\"y\"].values, label=g[0])\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prepare remote compute and data. \n", + "The [Machine Learning service workspace](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-workspace), is paired with the storage account, which contains the default data store. We will use it to upload the artificial data and create [tabular dataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py) for training. A tabular dataset defines a series of lazily-evaluated, immutable operations to load data from the data source into tabular representation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# We need to save thw artificial data and then upload them to default workspace datastore.\n", + "DATA_PATH = \"fc_fn_data\"\n", + "DATA_PATH_X = \"{}/data_train.csv\".format(DATA_PATH)\n", + "if not os.path.isdir(\"data\"):\n", + " os.mkdir(\"data\")\n", + "pd.DataFrame(whole_data).to_csv(\"data/data_train.csv\", index=False)\n", + "# Upload saved data to the default data store.\n", + "ds = ws.get_default_datastore()\n", + "ds.upload(src_dir=\"./data\", target_path=DATA_PATH, overwrite=True, show_progress=True)\n", + "train_data = Dataset.Tabular.from_delimited_files(path=ds.path(DATA_PATH_X))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "amlcompute_cluster_name = \"fcfn-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=amlcompute_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=6\n", + " )\n", + " compute_target = ComputeTarget.create(ws, amlcompute_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create the configuration and train a forecaster \n", + "First generate the configuration, in which we:\n", + "* Set metadata columns: target, time column and time-series id column names.\n", + "* Validate our data using cross validation with rolling window method.\n", + "* Set normalized root mean squared error as a metric to select the best model.\n", + "* Set early termination to True, so the iterations through the models will stop when no improvements in accuracy score will be made.\n", + "* Set limitations on the length of experiment run to 15 minutes.\n", + "* Finally, we set the task to be forecasting.\n", + "* We apply the lag lead operator to the target value i.e. we use the previous values as a predictor for the future ones.\n", + "* [Optional] Forecast frequency parameter (freq) represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", + "\n", + "lags = [1, 2, 3]\n", + "forecast_horizon = n_test_periods\n", + "forecasting_parameters = ForecastingParameters(\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " forecast_horizon=forecast_horizon,\n", + " time_series_id_column_names=[TIME_SERIES_ID_COLUMN_NAME],\n", + " target_lags=lags,\n", + " freq=\"H\", # Set the forecast frequency to be hourly\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run the model selection and training process. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core.workspace import Workspace\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.train.automl import AutoMLConfig\n", + "\n", + "\n", + "automl_config = AutoMLConfig(\n", + " task=\"forecasting\",\n", + " debug_log=\"automl_forecasting_function.log\",\n", + " primary_metric=\"normalized_root_mean_squared_error\",\n", + " experiment_timeout_hours=0.25,\n", + " enable_early_stopping=True,\n", + " training_data=train_data,\n", + " compute_target=compute_target,\n", + " n_cross_validations=3,\n", + " verbosity=logging.INFO,\n", + " max_concurrent_iterations=4,\n", + " max_cores_per_iteration=-1,\n", + " label_column_name=target_label,\n", + " forecasting_parameters=forecasting_parameters,\n", + ")\n", + "\n", + "remote_run = experiment.submit(automl_config, show_output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "remote_run.wait_for_completion()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Retrieve the best model to use it further.\n", + "_, fitted_model = remote_run.get_output()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Forecasting from the trained model " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section we will review the `forecast` interface for two main scenarios: forecasting right after the training data, and the more complex interface for forecasting when there is a gap (in the time sense) between training and testing data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### X_train is directly followed by the X_test\n", + "\n", + "Let's first consider the case when the prediction period immediately follows the training data. This is typical in scenarios where we have the time to retrain the model every time we wish to forecast. Forecasts that are made on daily and slower cadence typically fall into this category. Retraining the model every time benefits the accuracy because the most recent data is often the most informative.\n", + "\n", + "![Forecasting after training](forecast_function_at_train.png)\n", + "\n", + "We use `X_test` as a **forecast request** to generate the predictions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Typical path: X_test is known, forecast all upcoming periods" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The data set contains hourly data, the training set ends at 01/02/2000 at 05:00\n", + "\n", + "# These are predictions we are asking the model to make (does not contain thet target column y),\n", + "# for 6 periods beginning with 2000-01-02 06:00, which immediately follows the training data\n", + "X_test" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred_no_gap, xy_nogap = fitted_model.forecast(X_test)\n", + "\n", + "# xy_nogap contains the predictions in the _automl_target_col column.\n", + "# Those same numbers are output in y_pred_no_gap\n", + "xy_nogap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Confidence intervals" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Forecasting model may be used for the prediction of forecasting intervals by running ```forecast_quantiles()```. \n", + "This method accepts the same parameters as forecast()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quantiles = fitted_model.forecast_quantiles(X_test)\n", + "quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Distribution forecasts\n", + "\n", + "Often the figure of interest is not just the point prediction, but the prediction at some quantile of the distribution. \n", + "This arises when the forecast is used to control some kind of inventory, for example of grocery items or virtual machines for a cloud service. In such case, the control point is usually something like \"we want the item to be in stock and not run out 99% of the time\". This is called a \"service level\". Here is how you get quantile forecasts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# specify which quantiles you would like\n", + "fitted_model.quantiles = [0.01, 0.5, 0.95]\n", + "# use forecast_quantiles function, not the forecast() one\n", + "y_pred_quantiles = fitted_model.forecast_quantiles(X_test)\n", + "\n", + "# quantile forecasts returned in a Dataframe along with the time and time series id columns\n", + "y_pred_quantiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Destination-date forecast: \"just do something\"\n", + "\n", + "In some scenarios, the X_test is not known. The forecast is likely to be weak, because it is missing contemporaneous predictors, which we will need to impute. If you still wish to predict forward under the assumption that the last known values will be carried forward, you can forecast out to \"destination date\". The destination date still needs to fit within the forecast horizon from training." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# We will take the destination date as a last date in the test set.\n", + "dest = max(X_test[TIME_COLUMN_NAME])\n", + "y_pred_dest, xy_dest = fitted_model.forecast(forecast_destination=dest)\n", + "\n", + "# This form also shows how we imputed the predictors which were not given. (Not so well! Use with caution!)\n", + "xy_dest" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Forecasting away from training data \n", + "\n", + "Suppose we trained a model, some time passed, and now we want to apply the model without re-training. If the model \"looks back\" -- uses previous values of the target -- then we somehow need to provide those values to the model.\n", + "\n", + "![Forecasting after training](forecast_function_away_from_train.png)\n", + "\n", + "The notion of forecast origin comes into play: the forecast origin is **the last period for which we have seen the target value**. This applies per time-series, so each time-series can have a different forecast origin. \n", + "\n", + "The part of data before the forecast origin is the **prediction context**. To provide the context values the model needs when it looks back, we pass definite values in `y_test` (aligned with corresponding times in `X_test`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# generate the same kind of test data we trained on,\n", + "# but now make the train set much longer, so that the test set will be in the future\n", + "X_context, y_context, X_away, y_away = get_timeseries(\n", + " train_len=42, # train data was 30 steps long\n", + " test_len=4,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=2,\n", + ")\n", + "\n", + "# end of the data we trained on\n", + "print(X_train.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())\n", + "# start of the data we want to predict on\n", + "print(X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a gap of 12 hours between end of training and beginning of `X_away`. (It looks like 13 because all timestamps point to the start of the one hour periods.) Using only `X_away` will fail without adding context data for the model to consume." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " y_pred_away, xy_away = fitted_model.forecast(X_away)\n", + " xy_away\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How should we read that eror message? The forecast origin is at the last time the model saw an actual value of `y` (the target). That was at the end of the training data! The model is attempting to forecast from the end of training data. But the requested forecast periods are past the forecast horizon. We need to provide a define `y` value to establish the forecast origin.\n", + "\n", + "We will use this helper function to take the required amount of context from the data preceding the testing data. It's definition is intentionally simplified to keep the idea in the clear." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def make_forecasting_query(\n", + " fulldata, time_column_name, target_column_name, forecast_origin, horizon, lookback\n", + "):\n", + "\n", + " \"\"\"\n", + " This function will take the full dataset, and create the query\n", + " to predict all values of the time series from the `forecast_origin`\n", + " forward for the next `horizon` horizons. Context from previous\n", + " `lookback` periods will be included.\n", + "\n", + "\n", + "\n", + " fulldata: pandas.DataFrame a time series dataset. Needs to contain X and y.\n", + " time_column_name: string which column (must be in fulldata) is the time axis\n", + " target_column_name: string which column (must be in fulldata) is to be forecast\n", + " forecast_origin: datetime type the last time we (pretend to) have target values\n", + " horizon: timedelta how far forward, in time units (not periods)\n", + " lookback: timedelta how far back does the model look\n", + "\n", + " Example:\n", + "\n", + "\n", + " ```\n", + "\n", + " forecast_origin = pd.to_datetime(\"2012-09-01\") + pd.DateOffset(days=5) # forecast 5 days after end of training\n", + " print(forecast_origin)\n", + "\n", + " X_query, y_query = make_forecasting_query(data,\n", + " forecast_origin = forecast_origin,\n", + " horizon = pd.DateOffset(days=7), # 7 days into the future\n", + " lookback = pd.DateOffset(days=1), # model has lag 1 period (day)\n", + " )\n", + "\n", + " ```\n", + " \"\"\"\n", + "\n", + " X_past = fulldata[\n", + " (fulldata[time_column_name] > forecast_origin - lookback)\n", + " & (fulldata[time_column_name] <= forecast_origin)\n", + " ]\n", + "\n", + " X_future = fulldata[\n", + " (fulldata[time_column_name] > forecast_origin)\n", + " & (fulldata[time_column_name] <= forecast_origin + horizon)\n", + " ]\n", + "\n", + " y_past = X_past.pop(target_column_name).values.astype(np.float)\n", + " y_future = X_future.pop(target_column_name).values.astype(np.float)\n", + "\n", + " # Now take y_future and turn it into question marks\n", + " y_query = y_future.copy().astype(\n", + " np.float\n", + " ) # because sometimes life hands you an int\n", + " y_query.fill(np.NaN)\n", + "\n", + " print(\"X_past is \" + str(X_past.shape) + \" - shaped\")\n", + " print(\"X_future is \" + str(X_future.shape) + \" - shaped\")\n", + " print(\"y_past is \" + str(y_past.shape) + \" - shaped\")\n", + " print(\"y_query is \" + str(y_query.shape) + \" - shaped\")\n", + "\n", + " X_pred = pd.concat([X_past, X_future])\n", + " y_pred = np.concatenate([y_past, y_query])\n", + " return X_pred, y_pred" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see where the context data ends - it ends, by construction, just before the testing data starts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\n", + " X_context.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "print(\n", + " X_away.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].agg(\n", + " [\"min\", \"max\", \"count\"]\n", + " )\n", + ")\n", + "X_context.tail(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Since the length of the lookback is 3,\n", + "# we need to add 3 periods from the context to the request\n", + "# so that the model has the data it needs\n", + "\n", + "# Put the X and y back together for a while.\n", + "# They like each other and it makes them happy.\n", + "X_context[TARGET_COLUMN_NAME] = y_context\n", + "X_away[TARGET_COLUMN_NAME] = y_away\n", + "fulldata = pd.concat([X_context, X_away])\n", + "\n", + "# forecast origin is the last point of data, which is one 1-hr period before test\n", + "forecast_origin = X_away[TIME_COLUMN_NAME].min() - pd.DateOffset(hours=1)\n", + "# it is indeed the last point of the context\n", + "assert forecast_origin == X_context[TIME_COLUMN_NAME].max()\n", + "print(\"Forecast origin: \" + str(forecast_origin))\n", + "\n", + "# the model uses lags and rolling windows to look back in time\n", + "n_lookback_periods = max(lags)\n", + "lookback = pd.DateOffset(hours=n_lookback_periods)\n", + "\n", + "horizon = pd.DateOffset(hours=forecast_horizon)\n", + "\n", + "# now make the forecast query from context (refer to figure)\n", + "X_pred, y_pred = make_forecasting_query(\n", + " fulldata, TIME_COLUMN_NAME, TARGET_COLUMN_NAME, forecast_origin, horizon, lookback\n", + ")\n", + "\n", + "# show the forecast request aligned\n", + "X_show = X_pred.copy()\n", + "X_show[TARGET_COLUMN_NAME] = y_pred\n", + "X_show" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the forecast origin is at 17:00 for both time-series, and periods from 18:00 are to be forecast." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Now everything works\n", + "y_pred_away, xy_away = fitted_model.forecast(X_pred, y_pred)\n", + "\n", + "# show the forecast aligned\n", + "X_show = xy_away.reset_index()\n", + "# without the generated features\n", + "X_show[[\"date\", \"time_series_id\", \"ext_predictor\", \"_automl_target_col\"]]\n", + "# prediction is in _automl_target_col" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Forecasting farther than the forecast horizon \n", + "When the forecast destination, or the latest date in the prediction data frame, is farther into the future than the specified forecast horizon, the `forecast()` function will still make point predictions out to the later date using a recursive operation mode. Internally, the method recursively applies the regular forecaster to generate context so that we can forecast further into the future. \n", + "\n", + "To illustrate the use-case and operation of recursive forecasting, we'll consider an example with a single time-series where the forecasting period directly follows the training period and is twice as long as the forecasting horizon given at training time.\n", + "\n", + "![Recursive_forecast_overview](recursive_forecast_overview_small.png)\n", + "\n", + "Internally, we apply the forecaster in an iterative manner and finish the forecast task in two interations. In the first iteration, we apply the forecaster and get the prediction for the first forecast-horizon periods (y_pred1). In the second iteraction, y_pred1 is used as the context to produce the prediction for the next forecast-horizon periods (y_pred2). The combination of (y_pred1 and y_pred2) gives the results for the total forecast periods. \n", + "\n", + "A caveat: forecast accuracy will likely be worse the farther we predict into the future since errors are compounded with recursive application of the forecaster.\n", + "\n", + "![Recursive_forecast_iter1](recursive_forecast_iter1.png)\n", + "![Recursive_forecast_iter2](recursive_forecast_iter2.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# generate the same kind of test data we trained on, but with a single time-series and test period twice as long\n", + "# as the forecast_horizon.\n", + "_, _, X_test_long, y_test_long = get_timeseries(\n", + " train_len=n_train_periods,\n", + " test_len=forecast_horizon * 2,\n", + " time_column_name=TIME_COLUMN_NAME,\n", + " target_column_name=TARGET_COLUMN_NAME,\n", + " time_series_id_column_name=TIME_SERIES_ID_COLUMN_NAME,\n", + " time_series_number=1,\n", + ")\n", + "\n", + "print(X_test_long.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].min())\n", + "print(X_test_long.groupby(TIME_SERIES_ID_COLUMN_NAME)[TIME_COLUMN_NAME].max())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# forecast() function will invoke the recursive forecast method internally.\n", + "y_pred_long, X_trans_long = fitted_model.forecast(X_test_long)\n", + "y_pred_long" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# What forecast() function does in this case is equivalent to iterating it twice over the test set as the following.\n", + "y_pred1, _ = fitted_model.forecast(X_test_long[:forecast_horizon])\n", + "y_pred_all, _ = fitted_model.forecast(\n", + " X_test_long, np.concatenate((y_pred1, np.full(forecast_horizon, np.nan)))\n", + ")\n", + "np.array_equal(y_pred_all, y_pred_long)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Confidence interval and distributional forecasts\n", + "AutoML cannot currently estimate forecast errors beyond the forecast horizon set during training, so the `forecast_quantiles()` function will return missing values for quantiles not equal to 0.5 beyond the forecast horizon. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fitted_model.forecast_quantiles(X_test_long)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly with the simple senarios illustrated above, forecasting farther than the forecast horizon in other senarios like 'multiple time-series', 'Destination-date forecast', and 'forecast away from the training data' are also automatically handled by the `forecast()` function. " + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "category": "tutorial", + "compute": [ + "Remote" + ], + "datasets": [ + "None" + ], + "deployment": [ + "None" + ], + "exclude_from_index": false, + "framework": [ + "Azure ML AutoML" + ], + "friendly_name": "Forecasting away from training data", + "index_order": 3, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + }, + "tags": [ + "Forecasting", + "Confidence Intervals" ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "category": "tutorial", - "compute": [ - "Remote" - ], - "datasets": [ - "None" - ], - "deployment": [ - "None" - ], - "exclude_from_index": false, - "framework": [ - "Azure ML AutoML" - ], - "friendly_name": "Forecasting away from training data", - "index_order": 3, - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - }, - "tags": [ - "Forecasting", - "Confidence Intervals" - ], - "task": "Forecasting" - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file + "task": "Forecasting" + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/auto-ml-forecasting-github-dau.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/auto-ml-forecasting-github-dau.ipynb new file mode 100644 index 000000000..0b5681f60 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/auto-ml-forecasting-github-dau.ipynb @@ -0,0 +1,725 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-beer-remote/auto-ml-forecasting-beer-remote.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "# Automated Machine Learning\n", + "**Github DAU Forecasting**\n", + "\n", + "## Contents\n", + "1. [Introduction](#Introduction)\n", + "1. [Setup](#Setup)\n", + "1. [Data](#Data)\n", + "1. [Train](#Train)\n", + "1. [Evaluate](#Evaluate)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "## Introduction\n", + "This notebook demonstrates demand forecasting for Github Daily Active Users Dataset using AutoML.\n", + "\n", + "AutoML highlights here include using Deep Learning forecasts, Arima, Prophet, Remote Execution and Remote Inferencing, and working with the `forecast` function. Please also look at the additional forecasting notebooks, which document lagging, rolling windows, forecast quantiles, other ways to use the forecast function, and forecaster deployment.\n", + "\n", + "Make sure you have executed the [configuration](../../../configuration.ipynb) before running this notebook.\n", + "\n", + "Notebook synopsis:\n", + "\n", + "1. Creating an Experiment in an existing Workspace\n", + "2. Configuration and remote run of AutoML for a time-series model exploring Regression learners, Arima, Prophet and DNNs\n", + "4. Evaluating the fitted model using a rolling test " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "## Setup\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "import os\n", + "import azureml.core\n", + "import pandas as pd\n", + "import numpy as np\n", + "import logging\n", + "import warnings\n", + "\n", + "from pandas.tseries.frequencies import to_offset\n", + "\n", + "# Squash warning messages for cleaner output in the notebook\n", + "warnings.showwarning = lambda *args, **kwargs: None\n", + "\n", + "from azureml.core.workspace import Workspace\n", + "from azureml.core.experiment import Experiment\n", + "from azureml.train.automl import AutoMLConfig\n", + "from matplotlib import pyplot as plt\n", + "from sklearn.metrics import mean_absolute_error, mean_squared_error\n", + "from azureml.train.estimator import Estimator" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook is compatible with Azure ML SDK version 1.35.0 or later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "As part of the setup you have already created a Workspace. To run AutoML, you also need to create an Experiment. An Experiment corresponds to a prediction problem you are trying to solve, while a Run corresponds to a specific approach to the problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "ws = Workspace.from_config()\n", + "\n", + "# choose a name for the run history container in the workspace\n", + "experiment_name = \"github-remote-cpu\"\n", + "\n", + "experiment = Experiment(ws, experiment_name)\n", + "\n", + "output = {}\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Run History Name\"] = experiment_name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "### Using AmlCompute\n", + "You will need to create a [compute target](https://docs.microsoft.com/azure/machine-learning/service/concept-azure-machine-learning-architecture#compute-target) for your AutoML run. In this tutorial, you use `AmlCompute` as your training compute resource.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "from azureml.core.compute_target import ComputeTargetException\n", + "\n", + "# Choose a name for your CPU cluster\n", + "cpu_cluster_name = \"github-cluster\"\n", + "\n", + "# Verify that cluster does not exist already\n", + "try:\n", + " compute_target = ComputeTarget(workspace=ws, name=cpu_cluster_name)\n", + " print(\"Found existing cluster, use it.\")\n", + "except ComputeTargetException:\n", + " compute_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_DS12_V2\", max_nodes=4\n", + " )\n", + " compute_target = ComputeTarget.create(ws, cpu_cluster_name, compute_config)\n", + "\n", + "compute_target.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "## Data\n", + "Read Github DAU data from file, and preview data." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "Let's set up what we know about the dataset. \n", + "\n", + "**Target column** is what we want to forecast.\n", + "\n", + "**Time column** is the time axis along which to predict.\n", + "\n", + "**Time series identifier columns** are identified by values of the columns listed `time_series_id_column_names`, for example \"store\" and \"item\" if your data has multiple time series of sales, one series for each combination of store and item sold.\n", + "\n", + "**Forecast frequency (freq)** This optional parameter represents the period with which the forecast is desired, for example, daily, weekly, yearly, etc. Use this parameter for the correction of time series containing irregular data points or for padding of short time series. The frequency needs to be a pandas offset alias. Please refer to [pandas documentation](https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#dateoffset-objects) for more information.\n", + "\n", + "This dataset has only one time series. Please see the [orange juice notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-orange-juice-sales) for an example of a multi-time series dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from pandas import DataFrame\n", + "from pandas import Grouper\n", + "from pandas import concat\n", + "from pandas.plotting import register_matplotlib_converters\n", + "\n", + "register_matplotlib_converters()\n", + "plt.figure(figsize=(20, 10))\n", + "plt.tight_layout()\n", + "\n", + "plt.subplot(2, 1, 1)\n", + "plt.title(\"Github Daily Active User By Year\")\n", + "df = pd.read_csv(\"github_dau_2011-2018_train.csv\", parse_dates=True, index_col=\"date\")\n", + "test_df = pd.read_csv(\n", + " \"github_dau_2011-2018_test.csv\", parse_dates=True, index_col=\"date\"\n", + ")\n", + "plt.plot(df)\n", + "\n", + "plt.subplot(2, 1, 2)\n", + "plt.title(\"Github Daily Active User By Month\")\n", + "groups = df.groupby(df.index.month)\n", + "months = concat([DataFrame(x[1].values) for x in groups], axis=1)\n", + "months = DataFrame(months)\n", + "months.columns = range(1, 49)\n", + "months.boxplot()\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "target_column_name = \"count\"\n", + "time_column_name = \"date\"\n", + "time_series_id_column_names = []\n", + "freq = \"D\" # Daily data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Split Training data into Train and Validation set and Upload to Datastores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from helper import split_fraction_by_grain\n", + "from helper import split_full_for_forecasting\n", + "\n", + "train, valid = split_full_for_forecasting(df, time_column_name)\n", + "train.to_csv(\"train.csv\")\n", + "valid.to_csv(\"valid.csv\")\n", + "test_df.to_csv(\"test.csv\")\n", + "\n", + "datastore = ws.get_default_datastore()\n", + "datastore.upload_files(\n", + " files=[\"./train.csv\"],\n", + " target_path=\"github-dataset/tabular/\",\n", + " overwrite=True,\n", + " show_progress=True,\n", + ")\n", + "datastore.upload_files(\n", + " files=[\"./valid.csv\"],\n", + " target_path=\"github-dataset/tabular/\",\n", + " overwrite=True,\n", + " show_progress=True,\n", + ")\n", + "datastore.upload_files(\n", + " files=[\"./test.csv\"],\n", + " target_path=\"github-dataset/tabular/\",\n", + " overwrite=True,\n", + " show_progress=True,\n", + ")\n", + "\n", + "from azureml.core import Dataset\n", + "\n", + "train_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, \"github-dataset/tabular/train.csv\")]\n", + ")\n", + "valid_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, \"github-dataset/tabular/valid.csv\")]\n", + ")\n", + "test_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, \"github-dataset/tabular/test.csv\")]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "### Setting forecaster maximum horizon \n", + "\n", + "The forecast horizon is the number of periods into the future that the model should predict. Here, we set the horizon to 12 periods (i.e. 12 months). Notice that this is much shorter than the number of months in the test set; we will need to use a rolling test to evaluate the performance on the whole test set. For more discussion of forecast horizons and guiding principles for setting them, please see the [energy demand notebook](https://github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/automated-machine-learning/forecasting-energy-demand). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "forecast_horizon = 12" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "## Train\n", + "\n", + "Instantiate a AutoMLConfig object. This defines the settings and data used to run the experiment.\n", + "\n", + "|Property|Description|\n", + "|-|-|\n", + "|**task**|forecasting|\n", + "|**primary_metric**|This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error\n", + "|**iteration_timeout_minutes**|Time limit in minutes for each iteration.|\n", + "|**training_data**|Input dataset, containing both features and label column.|\n", + "|**label_column_name**|The name of the label column.|\n", + "|**enable_dnn**|Enable Forecasting DNNs|\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from azureml.automl.core.forecasting_parameters import ForecastingParameters\n", + "\n", + "forecasting_parameters = ForecastingParameters(\n", + " time_column_name=time_column_name,\n", + " forecast_horizon=forecast_horizon,\n", + " freq=\"D\", # Set the forecast frequency to be daily\n", + ")\n", + "\n", + "# We will disable the enable_early_stopping flag to ensure the DNN model is recommended for demonstration purpose.\n", + "automl_config = AutoMLConfig(\n", + " task=\"forecasting\",\n", + " primary_metric=\"normalized_root_mean_squared_error\",\n", + " experiment_timeout_hours=1,\n", + " training_data=train_dataset,\n", + " label_column_name=target_column_name,\n", + " validation_data=valid_dataset,\n", + " verbosity=logging.INFO,\n", + " compute_target=compute_target,\n", + " max_concurrent_iterations=4,\n", + " max_cores_per_iteration=-1,\n", + " enable_dnn=True,\n", + " enable_early_stopping=False,\n", + " forecasting_parameters=forecasting_parameters,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "We will now run the experiment, starting with 10 iterations of model search. The experiment can be continued for more iterations if more accurate results are required. Validation errors and current status will be shown when setting `show_output=True` and the execution will be synchronous." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "remote_run = experiment.submit(automl_config, show_output=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "# If you need to retrieve a run that already started, use the following code\n", + "# from azureml.train.automl.run import AutoMLRun\n", + "# remote_run = AutoMLRun(experiment = experiment, run_id = '')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "Displaying the run objects gives you links to the visual tools in the Azure Portal. Go try them!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "### Retrieve the Best Model for Each Algorithm\n", + "Below we select the best pipeline from our iterations. The get_output method on automl_classifier returns the best run and the fitted model for the last fit invocation. There are overloads on get_output that allow you to retrieve the best run and fitted model for any logged metric or a particular iteration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from helper import get_result_df\n", + "\n", + "summary_df = get_result_df(remote_run)\n", + "summary_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from azureml.core.run import Run\n", + "from azureml.widgets import RunDetails\n", + "\n", + "forecast_model = \"TCNForecaster\"\n", + "if not forecast_model in summary_df[\"run_id\"]:\n", + " forecast_model = \"ForecastTCN\"\n", + "\n", + "best_dnn_run_id = summary_df[\"run_id\"][forecast_model]\n", + "best_dnn_run = Run(experiment, best_dnn_run_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "best_dnn_run.parent\n", + "RunDetails(best_dnn_run.parent).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "best_dnn_run\n", + "RunDetails(best_dnn_run).show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "## Evaluate on Test Data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "source": [ + "We now use the best fitted model from the AutoML Run to make forecasts for the test set. \n", + "\n", + "We always score on the original dataset whose schema matches the training set schema." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "from azureml.core import Dataset\n", + "\n", + "test_dataset = Dataset.Tabular.from_delimited_files(\n", + " path=[(datastore, \"github-dataset/tabular/test.csv\")]\n", + ")\n", + "# preview the first 3 rows of the dataset\n", + "test_dataset.take(5).to_pandas_dataframe()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "compute_target = ws.compute_targets[\"github-cluster\"]\n", + "test_experiment = Experiment(ws, experiment_name + \"_test\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "import os\n", + "import shutil\n", + "\n", + "script_folder = os.path.join(os.getcwd(), \"inference\")\n", + "os.makedirs(script_folder, exist_ok=True)\n", + "shutil.copy(\"infer.py\", script_folder)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helper import run_inference\n", + "\n", + "test_run = run_inference(\n", + " test_experiment,\n", + " compute_target,\n", + " script_folder,\n", + " best_dnn_run,\n", + " test_dataset,\n", + " valid_dataset,\n", + " forecast_horizon,\n", + " target_column_name,\n", + " time_column_name,\n", + " freq,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RunDetails(test_run).show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helper import run_multiple_inferences\n", + "\n", + "summary_df = run_multiple_inferences(\n", + " summary_df,\n", + " experiment,\n", + " test_experiment,\n", + " compute_target,\n", + " script_folder,\n", + " test_dataset,\n", + " valid_dataset,\n", + " forecast_horizon,\n", + " target_column_name,\n", + " time_column_name,\n", + " freq,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "for run_name, run_summary in summary_df.iterrows():\n", + " print(run_name)\n", + " print(run_summary)\n", + " run_id = run_summary.run_id\n", + " test_run_id = run_summary.test_run_id\n", + " test_run = Run(test_experiment, test_run_id)\n", + " test_run.wait_for_completion()\n", + " test_score = test_run.get_metrics()[run_summary.primary_metric]\n", + " summary_df.loc[summary_df.run_id == run_id, \"Test Score\"] = test_score\n", + " print(\"Test Score: \", test_score)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hideCode": false, + "hidePrompt": false + }, + "outputs": [], + "source": [ + "summary_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "hide_code_all_hidden": false, + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_test.csv b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_test.csv new file mode 100644 index 000000000..6061b0d21 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_test.csv @@ -0,0 +1,455 @@ +date,count,day_of_week,month_of_year,holiday +2017-06-04,104663,6.0,5.0,0.0 +2017-06-05,155824,0.0,5.0,0.0 +2017-06-06,164908,1.0,5.0,0.0 +2017-06-07,170309,2.0,5.0,0.0 +2017-06-08,164256,3.0,5.0,0.0 +2017-06-09,153406,4.0,5.0,0.0 +2017-06-10,97024,5.0,5.0,0.0 +2017-06-11,103442,6.0,5.0,0.0 +2017-06-12,160768,0.0,5.0,0.0 +2017-06-13,166288,1.0,5.0,0.0 +2017-06-14,163819,2.0,5.0,0.0 +2017-06-15,157593,3.0,5.0,0.0 +2017-06-16,149259,4.0,5.0,0.0 +2017-06-17,95579,5.0,5.0,0.0 +2017-06-18,98723,6.0,5.0,0.0 +2017-06-19,159076,0.0,5.0,0.0 +2017-06-20,163340,1.0,5.0,0.0 +2017-06-21,163344,2.0,5.0,0.0 +2017-06-22,159528,3.0,5.0,0.0 +2017-06-23,146563,4.0,5.0,0.0 +2017-06-24,92631,5.0,5.0,0.0 +2017-06-25,96549,6.0,5.0,0.0 +2017-06-26,153249,0.0,5.0,0.0 +2017-06-27,160357,1.0,5.0,0.0 +2017-06-28,159941,2.0,5.0,0.0 +2017-06-29,156781,3.0,5.0,0.0 +2017-06-30,144709,4.0,5.0,0.0 +2017-07-01,89101,5.0,6.0,0.0 +2017-07-02,93046,6.0,6.0,0.0 +2017-07-03,144113,0.0,6.0,0.0 +2017-07-04,143061,1.0,6.0,1.0 +2017-07-05,154603,2.0,6.0,0.0 +2017-07-06,157200,3.0,6.0,0.0 +2017-07-07,147213,4.0,6.0,0.0 +2017-07-08,92348,5.0,6.0,0.0 +2017-07-09,97018,6.0,6.0,0.0 +2017-07-10,157192,0.0,6.0,0.0 +2017-07-11,161819,1.0,6.0,0.0 +2017-07-12,161998,2.0,6.0,0.0 +2017-07-13,160280,3.0,6.0,0.0 +2017-07-14,146818,4.0,6.0,0.0 +2017-07-15,93041,5.0,6.0,0.0 +2017-07-16,97505,6.0,6.0,0.0 +2017-07-17,156167,0.0,6.0,0.0 +2017-07-18,162855,1.0,6.0,0.0 +2017-07-19,162519,2.0,6.0,0.0 +2017-07-20,159941,3.0,6.0,0.0 +2017-07-21,148460,4.0,6.0,0.0 +2017-07-22,93431,5.0,6.0,0.0 +2017-07-23,98553,6.0,6.0,0.0 +2017-07-24,156202,0.0,6.0,0.0 +2017-07-25,162503,1.0,6.0,0.0 +2017-07-26,158479,2.0,6.0,0.0 +2017-07-27,158192,3.0,6.0,0.0 +2017-07-28,147108,4.0,6.0,0.0 +2017-07-29,93799,5.0,6.0,0.0 +2017-07-30,97920,6.0,6.0,0.0 +2017-07-31,152197,0.0,6.0,0.0 +2017-08-01,158477,1.0,7.0,0.0 +2017-08-02,159089,2.0,7.0,0.0 +2017-08-03,157182,3.0,7.0,0.0 +2017-08-04,146345,4.0,7.0,0.0 +2017-08-05,92534,5.0,7.0,0.0 +2017-08-06,97128,6.0,7.0,0.0 +2017-08-07,151359,0.0,7.0,0.0 +2017-08-08,159895,1.0,7.0,0.0 +2017-08-09,158329,2.0,7.0,0.0 +2017-08-10,155468,3.0,7.0,0.0 +2017-08-11,144914,4.0,7.0,0.0 +2017-08-12,92258,5.0,7.0,0.0 +2017-08-13,95933,6.0,7.0,0.0 +2017-08-14,147706,0.0,7.0,0.0 +2017-08-15,151115,1.0,7.0,0.0 +2017-08-16,157640,2.0,7.0,0.0 +2017-08-17,156600,3.0,7.0,0.0 +2017-08-18,146980,4.0,7.0,0.0 +2017-08-19,94592,5.0,7.0,0.0 +2017-08-20,99320,6.0,7.0,0.0 +2017-08-21,145727,0.0,7.0,0.0 +2017-08-22,160260,1.0,7.0,0.0 +2017-08-23,160440,2.0,7.0,0.0 +2017-08-24,157830,3.0,7.0,0.0 +2017-08-25,145822,4.0,7.0,0.0 +2017-08-26,94706,5.0,7.0,0.0 +2017-08-27,99047,6.0,7.0,0.0 +2017-08-28,152112,0.0,7.0,0.0 +2017-08-29,162440,1.0,7.0,0.0 +2017-08-30,162902,2.0,7.0,0.0 +2017-08-31,159498,3.0,7.0,0.0 +2017-09-01,145689,4.0,8.0,0.0 +2017-09-02,93589,5.0,8.0,0.0 +2017-09-03,100058,6.0,8.0,0.0 +2017-09-04,140865,0.0,8.0,1.0 +2017-09-05,165715,1.0,8.0,0.0 +2017-09-06,167463,2.0,8.0,0.0 +2017-09-07,164811,3.0,8.0,0.0 +2017-09-08,156157,4.0,8.0,0.0 +2017-09-09,101358,5.0,8.0,0.0 +2017-09-10,107915,6.0,8.0,0.0 +2017-09-11,167845,0.0,8.0,0.0 +2017-09-12,172756,1.0,8.0,0.0 +2017-09-13,172851,2.0,8.0,0.0 +2017-09-14,171675,3.0,8.0,0.0 +2017-09-15,159266,4.0,8.0,0.0 +2017-09-16,103547,5.0,8.0,0.0 +2017-09-17,110964,6.0,8.0,0.0 +2017-09-18,170976,0.0,8.0,0.0 +2017-09-19,177864,1.0,8.0,0.0 +2017-09-20,173567,2.0,8.0,0.0 +2017-09-21,172017,3.0,8.0,0.0 +2017-09-22,161357,4.0,8.0,0.0 +2017-09-23,104681,5.0,8.0,0.0 +2017-09-24,111711,6.0,8.0,0.0 +2017-09-25,173517,0.0,8.0,0.0 +2017-09-26,180049,1.0,8.0,0.0 +2017-09-27,178307,2.0,8.0,0.0 +2017-09-28,174157,3.0,8.0,0.0 +2017-09-29,161707,4.0,8.0,0.0 +2017-09-30,110536,5.0,8.0,0.0 +2017-10-01,106505,6.0,9.0,0.0 +2017-10-02,157565,0.0,9.0,0.0 +2017-10-03,164764,1.0,9.0,0.0 +2017-10-04,163383,2.0,9.0,0.0 +2017-10-05,162847,3.0,9.0,0.0 +2017-10-06,153575,4.0,9.0,0.0 +2017-10-07,107472,5.0,9.0,0.0 +2017-10-08,116127,6.0,9.0,0.0 +2017-10-09,174457,0.0,9.0,1.0 +2017-10-10,185217,1.0,9.0,0.0 +2017-10-11,185120,2.0,9.0,0.0 +2017-10-12,180844,3.0,9.0,0.0 +2017-10-13,170178,4.0,9.0,0.0 +2017-10-14,112754,5.0,9.0,0.0 +2017-10-15,121251,6.0,9.0,0.0 +2017-10-16,183906,0.0,9.0,0.0 +2017-10-17,188945,1.0,9.0,0.0 +2017-10-18,187297,2.0,9.0,0.0 +2017-10-19,183867,3.0,9.0,0.0 +2017-10-20,173021,4.0,9.0,0.0 +2017-10-21,115851,5.0,9.0,0.0 +2017-10-22,126088,6.0,9.0,0.0 +2017-10-23,189452,0.0,9.0,0.0 +2017-10-24,194412,1.0,9.0,0.0 +2017-10-25,192293,2.0,9.0,0.0 +2017-10-26,190163,3.0,9.0,0.0 +2017-10-27,177053,4.0,9.0,0.0 +2017-10-28,114934,5.0,9.0,0.0 +2017-10-29,125289,6.0,9.0,0.0 +2017-10-30,189245,0.0,9.0,0.0 +2017-10-31,191480,1.0,9.0,0.0 +2017-11-01,182281,2.0,10.0,0.0 +2017-11-02,186351,3.0,10.0,0.0 +2017-11-03,175422,4.0,10.0,0.0 +2017-11-04,118160,5.0,10.0,0.0 +2017-11-05,127602,6.0,10.0,0.0 +2017-11-06,191067,0.0,10.0,0.0 +2017-11-07,197083,1.0,10.0,0.0 +2017-11-08,194333,2.0,10.0,0.0 +2017-11-09,193914,3.0,10.0,0.0 +2017-11-10,179933,4.0,10.0,1.0 +2017-11-11,121346,5.0,10.0,0.0 +2017-11-12,131900,6.0,10.0,0.0 +2017-11-13,196969,0.0,10.0,0.0 +2017-11-14,201949,1.0,10.0,0.0 +2017-11-15,198424,2.0,10.0,0.0 +2017-11-16,196902,3.0,10.0,0.0 +2017-11-17,183893,4.0,10.0,0.0 +2017-11-18,122767,5.0,10.0,0.0 +2017-11-19,130890,6.0,10.0,0.0 +2017-11-20,194515,0.0,10.0,0.0 +2017-11-21,198601,1.0,10.0,0.0 +2017-11-22,191041,2.0,10.0,0.0 +2017-11-23,170321,3.0,10.0,1.0 +2017-11-24,155623,4.0,10.0,0.0 +2017-11-25,115759,5.0,10.0,0.0 +2017-11-26,128771,6.0,10.0,0.0 +2017-11-27,199419,0.0,10.0,0.0 +2017-11-28,207253,1.0,10.0,0.0 +2017-11-29,205406,2.0,10.0,0.0 +2017-11-30,200674,3.0,10.0,0.0 +2017-12-01,187017,4.0,11.0,0.0 +2017-12-02,129735,5.0,11.0,0.0 +2017-12-03,139120,6.0,11.0,0.0 +2017-12-04,205505,0.0,11.0,0.0 +2017-12-05,208218,1.0,11.0,0.0 +2017-12-06,202480,2.0,11.0,0.0 +2017-12-07,197822,3.0,11.0,0.0 +2017-12-08,180686,4.0,11.0,0.0 +2017-12-09,123667,5.0,11.0,0.0 +2017-12-10,130987,6.0,11.0,0.0 +2017-12-11,193901,0.0,11.0,0.0 +2017-12-12,194997,1.0,11.0,0.0 +2017-12-13,192063,2.0,11.0,0.0 +2017-12-14,186496,3.0,11.0,0.0 +2017-12-15,170812,4.0,11.0,0.0 +2017-12-16,110474,5.0,11.0,0.0 +2017-12-17,118165,6.0,11.0,0.0 +2017-12-18,176843,0.0,11.0,0.0 +2017-12-19,179550,1.0,11.0,0.0 +2017-12-20,173506,2.0,11.0,0.0 +2017-12-21,165910,3.0,11.0,0.0 +2017-12-22,145886,4.0,11.0,0.0 +2017-12-23,95246,5.0,11.0,0.0 +2017-12-24,88781,6.0,11.0,0.0 +2017-12-25,98189,0.0,11.0,1.0 +2017-12-26,121383,1.0,11.0,0.0 +2017-12-27,135300,2.0,11.0,0.0 +2017-12-28,136827,3.0,11.0,0.0 +2017-12-29,127700,4.0,11.0,0.0 +2017-12-30,93014,5.0,11.0,0.0 +2017-12-31,82878,6.0,11.0,0.0 +2018-01-01,86419,0.0,0.0,1.0 +2018-01-02,147428,1.0,0.0,0.0 +2018-01-03,162193,2.0,0.0,0.0 +2018-01-04,163784,3.0,0.0,0.0 +2018-01-05,158606,4.0,0.0,0.0 +2018-01-06,113467,5.0,0.0,0.0 +2018-01-07,118313,6.0,0.0,0.0 +2018-01-08,175623,0.0,0.0,0.0 +2018-01-09,183880,1.0,0.0,0.0 +2018-01-10,183945,2.0,0.0,0.0 +2018-01-11,181769,3.0,0.0,0.0 +2018-01-12,170552,4.0,0.0,0.0 +2018-01-13,115707,5.0,0.0,0.0 +2018-01-14,121191,6.0,0.0,0.0 +2018-01-15,176127,0.0,0.0,1.0 +2018-01-16,188032,1.0,0.0,0.0 +2018-01-17,189871,2.0,0.0,0.0 +2018-01-18,189348,3.0,0.0,0.0 +2018-01-19,177456,4.0,0.0,0.0 +2018-01-20,123321,5.0,0.0,0.0 +2018-01-21,128306,6.0,0.0,0.0 +2018-01-22,186132,0.0,0.0,0.0 +2018-01-23,197618,1.0,0.0,0.0 +2018-01-24,196402,2.0,0.0,0.0 +2018-01-25,192722,3.0,0.0,0.0 +2018-01-26,179415,4.0,0.0,0.0 +2018-01-27,125769,5.0,0.0,0.0 +2018-01-28,133306,6.0,0.0,0.0 +2018-01-29,194151,0.0,0.0,0.0 +2018-01-30,198680,1.0,0.0,0.0 +2018-01-31,198652,2.0,0.0,0.0 +2018-02-01,195472,3.0,1.0,0.0 +2018-02-02,183173,4.0,1.0,0.0 +2018-02-03,124276,5.0,1.0,0.0 +2018-02-04,129054,6.0,1.0,0.0 +2018-02-05,190024,0.0,1.0,0.0 +2018-02-06,198658,1.0,1.0,0.0 +2018-02-07,198272,2.0,1.0,0.0 +2018-02-08,195339,3.0,1.0,0.0 +2018-02-09,183086,4.0,1.0,0.0 +2018-02-10,122536,5.0,1.0,0.0 +2018-02-11,133033,6.0,1.0,0.0 +2018-02-12,185386,0.0,1.0,0.0 +2018-02-13,184789,1.0,1.0,0.0 +2018-02-14,176089,2.0,1.0,0.0 +2018-02-15,171317,3.0,1.0,0.0 +2018-02-16,162693,4.0,1.0,0.0 +2018-02-17,116342,5.0,1.0,0.0 +2018-02-18,122466,6.0,1.0,0.0 +2018-02-19,172364,0.0,1.0,1.0 +2018-02-20,185896,1.0,1.0,0.0 +2018-02-21,188166,2.0,1.0,0.0 +2018-02-22,189427,3.0,1.0,0.0 +2018-02-23,178732,4.0,1.0,0.0 +2018-02-24,132664,5.0,1.0,0.0 +2018-02-25,134008,6.0,1.0,0.0 +2018-02-26,200075,0.0,1.0,0.0 +2018-02-27,207996,1.0,1.0,0.0 +2018-02-28,204416,2.0,1.0,0.0 +2018-03-01,201320,3.0,2.0,0.0 +2018-03-02,188205,4.0,2.0,0.0 +2018-03-03,131162,5.0,2.0,0.0 +2018-03-04,138320,6.0,2.0,0.0 +2018-03-05,207326,0.0,2.0,0.0 +2018-03-06,212462,1.0,2.0,0.0 +2018-03-07,209357,2.0,2.0,0.0 +2018-03-08,194876,3.0,2.0,0.0 +2018-03-09,193761,4.0,2.0,0.0 +2018-03-10,133449,5.0,2.0,0.0 +2018-03-11,142258,6.0,2.0,0.0 +2018-03-12,208753,0.0,2.0,0.0 +2018-03-13,210602,1.0,2.0,0.0 +2018-03-14,214236,2.0,2.0,0.0 +2018-03-15,210761,3.0,2.0,0.0 +2018-03-16,196619,4.0,2.0,0.0 +2018-03-17,133056,5.0,2.0,0.0 +2018-03-18,141335,6.0,2.0,0.0 +2018-03-19,211580,0.0,2.0,0.0 +2018-03-20,219051,1.0,2.0,0.0 +2018-03-21,215435,2.0,2.0,0.0 +2018-03-22,211961,3.0,2.0,0.0 +2018-03-23,196009,4.0,2.0,0.0 +2018-03-24,132390,5.0,2.0,0.0 +2018-03-25,140021,6.0,2.0,0.0 +2018-03-26,205273,0.0,2.0,0.0 +2018-03-27,212686,1.0,2.0,0.0 +2018-03-28,210683,2.0,2.0,0.0 +2018-03-29,189044,3.0,2.0,0.0 +2018-03-30,170256,4.0,2.0,0.0 +2018-03-31,125999,5.0,2.0,0.0 +2018-04-01,126749,6.0,3.0,0.0 +2018-04-02,186546,0.0,3.0,0.0 +2018-04-03,207905,1.0,3.0,0.0 +2018-04-04,201528,2.0,3.0,0.0 +2018-04-05,188580,3.0,3.0,0.0 +2018-04-06,173714,4.0,3.0,0.0 +2018-04-07,125723,5.0,3.0,0.0 +2018-04-08,142545,6.0,3.0,0.0 +2018-04-09,204767,0.0,3.0,0.0 +2018-04-10,212048,1.0,3.0,0.0 +2018-04-11,210517,2.0,3.0,0.0 +2018-04-12,206924,3.0,3.0,0.0 +2018-04-13,191679,4.0,3.0,0.0 +2018-04-14,126394,5.0,3.0,0.0 +2018-04-15,137279,6.0,3.0,0.0 +2018-04-16,208085,0.0,3.0,0.0 +2018-04-17,213273,1.0,3.0,0.0 +2018-04-18,211580,2.0,3.0,0.0 +2018-04-19,206037,3.0,3.0,0.0 +2018-04-20,191211,4.0,3.0,0.0 +2018-04-21,125564,5.0,3.0,0.0 +2018-04-22,136469,6.0,3.0,0.0 +2018-04-23,206288,0.0,3.0,0.0 +2018-04-24,212115,1.0,3.0,0.0 +2018-04-25,207948,2.0,3.0,0.0 +2018-04-26,205759,3.0,3.0,0.0 +2018-04-27,181330,4.0,3.0,0.0 +2018-04-28,130046,5.0,3.0,0.0 +2018-04-29,120802,6.0,3.0,0.0 +2018-04-30,170390,0.0,3.0,0.0 +2018-05-01,169054,1.0,4.0,0.0 +2018-05-02,197891,2.0,4.0,0.0 +2018-05-03,199820,3.0,4.0,0.0 +2018-05-04,186783,4.0,4.0,0.0 +2018-05-05,124420,5.0,4.0,0.0 +2018-05-06,130666,6.0,4.0,0.0 +2018-05-07,196014,0.0,4.0,0.0 +2018-05-08,203058,1.0,4.0,0.0 +2018-05-09,198582,2.0,4.0,0.0 +2018-05-10,191321,3.0,4.0,0.0 +2018-05-11,183639,4.0,4.0,0.0 +2018-05-12,122023,5.0,4.0,0.0 +2018-05-13,128775,6.0,4.0,0.0 +2018-05-14,199104,0.0,4.0,0.0 +2018-05-15,200658,1.0,4.0,0.0 +2018-05-16,201541,2.0,4.0,0.0 +2018-05-17,196886,3.0,4.0,0.0 +2018-05-18,188597,4.0,4.0,0.0 +2018-05-19,121392,5.0,4.0,0.0 +2018-05-20,126981,6.0,4.0,0.0 +2018-05-21,189291,0.0,4.0,0.0 +2018-05-22,203038,1.0,4.0,0.0 +2018-05-23,205330,2.0,4.0,0.0 +2018-05-24,199208,3.0,4.0,0.0 +2018-05-25,187768,4.0,4.0,0.0 +2018-05-26,117635,5.0,4.0,0.0 +2018-05-27,124352,6.0,4.0,0.0 +2018-05-28,180398,0.0,4.0,1.0 +2018-05-29,194170,1.0,4.0,0.0 +2018-05-30,200281,2.0,4.0,0.0 +2018-05-31,197244,3.0,4.0,0.0 +2018-06-01,184037,4.0,5.0,0.0 +2018-06-02,121135,5.0,5.0,0.0 +2018-06-03,129389,6.0,5.0,0.0 +2018-06-04,200331,0.0,5.0,0.0 +2018-06-05,207735,1.0,5.0,0.0 +2018-06-06,203354,2.0,5.0,0.0 +2018-06-07,200520,3.0,5.0,0.0 +2018-06-08,182038,4.0,5.0,0.0 +2018-06-09,120164,5.0,5.0,0.0 +2018-06-10,125256,6.0,5.0,0.0 +2018-06-11,194786,0.0,5.0,0.0 +2018-06-12,200815,1.0,5.0,0.0 +2018-06-13,197740,2.0,5.0,0.0 +2018-06-14,192294,3.0,5.0,0.0 +2018-06-15,173587,4.0,5.0,0.0 +2018-06-16,105955,5.0,5.0,0.0 +2018-06-17,110780,6.0,5.0,0.0 +2018-06-18,174582,0.0,5.0,0.0 +2018-06-19,193310,1.0,5.0,0.0 +2018-06-20,193062,2.0,5.0,0.0 +2018-06-21,187986,3.0,5.0,0.0 +2018-06-22,173606,4.0,5.0,0.0 +2018-06-23,111795,5.0,5.0,0.0 +2018-06-24,116134,6.0,5.0,0.0 +2018-06-25,185919,0.0,5.0,0.0 +2018-06-26,193142,1.0,5.0,0.0 +2018-06-27,188114,2.0,5.0,0.0 +2018-06-28,183737,3.0,5.0,0.0 +2018-06-29,171496,4.0,5.0,0.0 +2018-06-30,107210,5.0,5.0,0.0 +2018-07-01,111053,6.0,6.0,0.0 +2018-07-02,176198,0.0,6.0,0.0 +2018-07-03,184040,1.0,6.0,0.0 +2018-07-04,169783,2.0,6.0,1.0 +2018-07-05,177996,3.0,6.0,0.0 +2018-07-06,167378,4.0,6.0,0.0 +2018-07-07,106401,5.0,6.0,0.0 +2018-07-08,112327,6.0,6.0,0.0 +2018-07-09,182835,0.0,6.0,0.0 +2018-07-10,187694,1.0,6.0,0.0 +2018-07-11,185762,2.0,6.0,0.0 +2018-07-12,184099,3.0,6.0,0.0 +2018-07-13,170860,4.0,6.0,0.0 +2018-07-14,106799,5.0,6.0,0.0 +2018-07-15,108475,6.0,6.0,0.0 +2018-07-16,175704,0.0,6.0,0.0 +2018-07-17,183596,1.0,6.0,0.0 +2018-07-18,179897,2.0,6.0,0.0 +2018-07-19,183373,3.0,6.0,0.0 +2018-07-20,169626,4.0,6.0,0.0 +2018-07-21,106785,5.0,6.0,0.0 +2018-07-22,112387,6.0,6.0,0.0 +2018-07-23,180572,0.0,6.0,0.0 +2018-07-24,186943,1.0,6.0,0.0 +2018-07-25,185744,2.0,6.0,0.0 +2018-07-26,183117,3.0,6.0,0.0 +2018-07-27,168526,4.0,6.0,0.0 +2018-07-28,105936,5.0,6.0,0.0 +2018-07-29,111708,6.0,6.0,0.0 +2018-07-30,179950,0.0,6.0,0.0 +2018-07-31,185930,1.0,6.0,0.0 +2018-08-01,183366,2.0,7.0,0.0 +2018-08-02,182412,3.0,7.0,0.0 +2018-08-03,173429,4.0,7.0,0.0 +2018-08-04,106108,5.0,7.0,0.0 +2018-08-05,110059,6.0,7.0,0.0 +2018-08-06,178355,0.0,7.0,0.0 +2018-08-07,185518,1.0,7.0,0.0 +2018-08-08,183204,2.0,7.0,0.0 +2018-08-09,181276,3.0,7.0,0.0 +2018-08-10,168297,4.0,7.0,0.0 +2018-08-11,106488,5.0,7.0,0.0 +2018-08-12,111786,6.0,7.0,0.0 +2018-08-13,178620,0.0,7.0,0.0 +2018-08-14,181922,1.0,7.0,0.0 +2018-08-15,172198,2.0,7.0,0.0 +2018-08-16,177367,3.0,7.0,0.0 +2018-08-17,166550,4.0,7.0,0.0 +2018-08-18,107011,5.0,7.0,0.0 +2018-08-19,112299,6.0,7.0,0.0 +2018-08-20,176718,0.0,7.0,0.0 +2018-08-21,182562,1.0,7.0,0.0 +2018-08-22,181484,2.0,7.0,0.0 +2018-08-23,180317,3.0,7.0,0.0 +2018-08-24,170197,4.0,7.0,0.0 +2018-08-25,109383,5.0,7.0,0.0 +2018-08-26,113373,6.0,7.0,0.0 +2018-08-27,180142,0.0,7.0,0.0 +2018-08-28,191628,1.0,7.0,0.0 +2018-08-29,191149,2.0,7.0,0.0 +2018-08-30,187503,3.0,7.0,0.0 +2018-08-31,172280,4.0,7.0,0.0 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_train.csv b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_train.csv new file mode 100644 index 000000000..5a409ad26 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/github_dau_2011-2018_train.csv @@ -0,0 +1,2286 @@ +date,count,day_of_week,month_of_year,holiday +2011-03-01,8583,1.0,2.0,0.0 +2011-03-02,8561,2.0,2.0,0.0 +2011-03-03,8406,3.0,2.0,0.0 +2011-03-04,7921,4.0,2.0,0.0 +2011-03-05,5597,5.0,2.0,0.0 +2011-03-06,6400,6.0,2.0,0.0 +2011-03-07,8043,0.0,2.0,0.0 +2011-03-08,8666,1.0,2.0,0.0 +2011-03-09,8344,2.0,2.0,0.0 +2011-03-10,8344,3.0,2.0,0.0 +2011-03-11,8017,4.0,2.0,0.0 +2011-03-12,5756,5.0,2.0,0.0 +2011-03-13,6294,6.0,2.0,0.0 +2011-03-14,8210,0.0,2.0,0.0 +2011-03-15,8882,1.0,2.0,0.0 +2011-03-16,8849,2.0,2.0,0.0 +2011-03-17,8611,3.0,2.0,0.0 +2011-03-18,8160,4.0,2.0,0.0 +2011-03-19,6068,5.0,2.0,0.0 +2011-03-20,6485,6.0,2.0,0.0 +2011-03-21,8596,0.0,2.0,0.0 +2011-03-22,9240,1.0,2.0,0.0 +2011-03-23,9005,2.0,2.0,0.0 +2011-03-24,8653,3.0,2.0,0.0 +2011-03-25,8288,4.0,2.0,0.0 +2011-03-26,6317,5.0,2.0,0.0 +2011-03-27,6793,6.0,2.0,0.0 +2011-03-28,9369,0.0,2.0,0.0 +2011-03-29,8589,1.0,2.0,0.0 +2011-03-30,9100,2.0,2.0,0.0 +2011-03-31,9013,3.0,2.0,0.0 +2011-04-01,8439,4.0,3.0,0.0 +2011-04-02,6142,5.0,3.0,0.0 +2011-04-03,6703,6.0,3.0,0.0 +2011-04-04,9516,0.0,3.0,0.0 +2011-04-05,9736,1.0,3.0,0.0 +2011-04-06,9370,2.0,3.0,0.0 +2011-04-07,9178,3.0,3.0,0.0 +2011-04-08,8862,4.0,3.0,0.0 +2011-04-09,6183,5.0,3.0,0.0 +2011-04-10,6798,6.0,3.0,0.0 +2011-04-11,9661,0.0,3.0,0.0 +2011-04-12,9498,1.0,3.0,0.0 +2011-04-13,9668,2.0,3.0,0.0 +2011-04-14,9651,3.0,3.0,0.0 +2011-04-15,9052,4.0,3.0,0.0 +2011-04-16,6559,5.0,3.0,0.0 +2011-04-17,6826,6.0,3.0,0.0 +2011-04-18,9243,0.0,3.0,0.0 +2011-04-19,9787,1.0,3.0,0.0 +2011-04-20,9259,2.0,3.0,0.0 +2011-04-21,9090,3.0,3.0,0.0 +2011-04-22,7812,4.0,3.0,0.0 +2011-04-23,6081,5.0,3.0,0.0 +2011-04-24,6106,6.0,3.0,0.0 +2011-04-25,7975,0.0,3.0,0.0 +2011-04-26,9656,1.0,3.0,0.0 +2011-04-27,9090,2.0,3.0,0.0 +2011-04-28,8600,3.0,3.0,0.0 +2011-04-29,9050,4.0,3.0,0.0 +2011-04-30,6073,5.0,3.0,0.0 +2011-05-01,6554,6.0,4.0,0.0 +2011-05-02,8287,0.0,4.0,0.0 +2011-05-03,9763,1.0,4.0,0.0 +2011-05-04,10105,2.0,4.0,0.0 +2011-05-05,10113,3.0,4.0,0.0 +2011-05-06,9085,4.0,4.0,0.0 +2011-05-07,6286,5.0,4.0,0.0 +2011-05-08,6674,6.0,4.0,0.0 +2011-05-09,9810,0.0,4.0,0.0 +2011-05-10,9390,1.0,4.0,0.0 +2011-05-11,10237,2.0,4.0,0.0 +2011-05-12,9630,3.0,4.0,0.0 +2011-05-13,9248,4.0,4.0,0.0 +2011-05-14,6785,5.0,4.0,0.0 +2011-05-15,7197,6.0,4.0,0.0 +2011-05-16,9794,0.0,4.0,0.0 +2011-05-17,10042,1.0,4.0,0.0 +2011-05-18,9978,2.0,4.0,0.0 +2011-05-19,10032,3.0,4.0,0.0 +2011-05-20,8662,4.0,4.0,0.0 +2011-05-21,6172,5.0,4.0,0.0 +2011-05-22,6423,6.0,4.0,0.0 +2011-05-23,10039,0.0,4.0,0.0 +2011-05-24,10487,1.0,4.0,0.0 +2011-05-25,10291,2.0,4.0,0.0 +2011-05-26,10188,3.0,4.0,0.0 +2011-05-27,8773,4.0,4.0,0.0 +2011-05-28,6323,5.0,4.0,0.0 +2011-05-29,6728,6.0,4.0,0.0 +2011-05-30,8663,0.0,4.0,1.0 +2011-05-31,10047,1.0,4.0,0.0 +2011-06-01,10183,2.0,5.0,0.0 +2011-06-02,9305,3.0,5.0,0.0 +2011-06-03,9493,4.0,5.0,0.0 +2011-06-04,6682,5.0,5.0,0.0 +2011-06-05,7043,6.0,5.0,0.0 +2011-06-06,9619,0.0,5.0,0.0 +2011-06-07,10108,1.0,5.0,0.0 +2011-06-08,10330,2.0,5.0,0.0 +2011-06-09,9792,3.0,5.0,0.0 +2011-06-10,9287,4.0,5.0,0.0 +2011-06-11,6432,5.0,5.0,0.0 +2011-06-12,6278,6.0,5.0,0.0 +2011-06-13,9515,0.0,5.0,0.0 +2011-06-14,10155,1.0,5.0,0.0 +2011-06-15,9979,2.0,5.0,0.0 +2011-06-16,9880,3.0,5.0,0.0 +2011-06-17,9855,4.0,5.0,0.0 +2011-06-18,6356,5.0,5.0,0.0 +2011-06-19,7028,6.0,5.0,0.0 +2011-06-20,10335,0.0,5.0,0.0 +2011-06-21,10383,1.0,5.0,0.0 +2011-06-22,10391,2.0,5.0,0.0 +2011-06-23,7190,3.0,5.0,0.0 +2011-06-24,9613,4.0,5.0,0.0 +2011-06-25,5890,5.0,5.0,0.0 +2011-06-26,6256,6.0,5.0,0.0 +2011-06-27,8825,0.0,5.0,0.0 +2011-06-28,10263,1.0,5.0,0.0 +2011-06-29,10628,2.0,5.0,0.0 +2011-06-30,10043,3.0,5.0,0.0 +2011-07-01,9403,4.0,6.0,0.0 +2011-07-02,6294,5.0,6.0,0.0 +2011-07-03,6485,6.0,6.0,0.0 +2011-07-04,8954,0.0,6.0,1.0 +2011-07-05,9672,1.0,6.0,0.0 +2011-07-06,10488,2.0,6.0,0.0 +2011-07-07,10199,3.0,6.0,0.0 +2011-07-08,9300,4.0,6.0,0.0 +2011-07-09,6544,5.0,6.0,0.0 +2011-07-10,6898,6.0,6.0,0.0 +2011-07-11,10087,0.0,6.0,0.0 +2011-07-12,10623,1.0,6.0,0.0 +2011-07-13,10201,2.0,6.0,0.0 +2011-07-14,9771,3.0,6.0,0.0 +2011-07-15,9339,4.0,6.0,0.0 +2011-07-16,6690,5.0,6.0,0.0 +2011-07-17,7059,6.0,6.0,0.0 +2011-07-18,10367,0.0,6.0,0.0 +2011-07-19,10123,1.0,6.0,0.0 +2011-07-20,10370,2.0,6.0,0.0 +2011-07-21,10296,3.0,6.0,0.0 +2011-07-22,9479,4.0,6.0,0.0 +2011-07-23,6667,5.0,6.0,0.0 +2011-07-24,6929,6.0,6.0,0.0 +2011-07-25,9924,0.0,6.0,0.0 +2011-07-26,10840,1.0,6.0,0.0 +2011-07-27,10588,2.0,6.0,0.0 +2011-07-28,10195,3.0,6.0,0.0 +2011-07-29,9688,4.0,6.0,0.0 +2011-07-30,6070,5.0,6.0,0.0 +2011-07-31,6858,6.0,6.0,0.0 +2011-08-01,9822,0.0,7.0,0.0 +2011-08-02,10529,1.0,7.0,0.0 +2011-08-03,10392,2.0,7.0,0.0 +2011-08-04,10498,3.0,7.0,0.0 +2011-08-05,9775,4.0,7.0,0.0 +2011-08-06,6653,5.0,7.0,0.0 +2011-08-07,6361,6.0,7.0,0.0 +2011-08-08,10287,0.0,7.0,0.0 +2011-08-09,10742,1.0,7.0,0.0 +2011-08-10,10086,2.0,7.0,0.0 +2011-08-11,10391,3.0,7.0,0.0 +2011-08-12,9614,4.0,7.0,0.0 +2011-08-13,6835,5.0,7.0,0.0 +2011-08-14,6912,6.0,7.0,0.0 +2011-08-15,10075,0.0,7.0,0.0 +2011-08-16,10949,1.0,7.0,0.0 +2011-08-17,11041,2.0,7.0,0.0 +2011-08-18,10742,3.0,7.0,0.0 +2011-08-19,10146,4.0,7.0,0.0 +2011-08-20,6424,5.0,7.0,0.0 +2011-08-21,7248,6.0,7.0,0.0 +2011-08-22,10650,0.0,7.0,0.0 +2011-08-23,11171,1.0,7.0,0.0 +2011-08-24,11385,2.0,7.0,0.0 +2011-08-25,10968,3.0,7.0,0.0 +2011-08-26,10179,4.0,7.0,0.0 +2011-08-27,7129,5.0,7.0,0.0 +2011-08-28,7341,6.0,7.0,0.0 +2011-08-29,10953,0.0,7.0,0.0 +2011-08-30,11251,1.0,7.0,0.0 +2011-08-31,11103,2.0,7.0,0.0 +2011-09-01,11120,3.0,8.0,0.0 +2011-09-02,10610,4.0,8.0,0.0 +2011-09-03,7280,5.0,8.0,0.0 +2011-09-04,7798,6.0,8.0,0.0 +2011-09-05,10391,0.0,8.0,1.0 +2011-09-06,11625,1.0,8.0,0.0 +2011-09-07,11869,2.0,8.0,0.0 +2011-09-08,11653,3.0,8.0,0.0 +2011-09-09,10962,4.0,8.0,0.0 +2011-09-10,7616,5.0,8.0,0.0 +2011-09-11,8209,6.0,8.0,0.0 +2011-09-12,11410,0.0,8.0,0.0 +2011-09-13,12278,1.0,8.0,0.0 +2011-09-14,12162,2.0,8.0,0.0 +2011-09-15,11739,3.0,8.0,0.0 +2011-09-16,11476,4.0,8.0,0.0 +2011-09-17,7297,5.0,8.0,0.0 +2011-09-18,8467,6.0,8.0,0.0 +2011-09-19,11276,0.0,8.0,0.0 +2011-09-20,11934,1.0,8.0,0.0 +2011-09-21,12059,2.0,8.0,0.0 +2011-09-22,12279,3.0,8.0,0.0 +2011-09-23,11209,4.0,8.0,0.0 +2011-09-24,7928,5.0,8.0,0.0 +2011-09-25,8584,6.0,8.0,0.0 +2011-09-26,12586,0.0,8.0,0.0 +2011-09-27,13016,1.0,8.0,0.0 +2011-09-28,12805,2.0,8.0,0.0 +2011-09-29,12525,3.0,8.0,0.0 +2011-09-30,11612,4.0,8.0,0.0 +2011-10-01,7829,5.0,9.0,0.0 +2011-10-02,8493,6.0,9.0,0.0 +2011-10-03,11934,0.0,9.0,0.0 +2011-10-04,12469,1.0,9.0,0.0 +2011-10-05,12576,2.0,9.0,0.0 +2011-10-06,12347,3.0,9.0,0.0 +2011-10-07,11916,4.0,9.0,0.0 +2011-10-08,8281,5.0,9.0,0.0 +2011-10-09,8830,6.0,9.0,0.0 +2011-10-10,12618,0.0,9.0,1.0 +2011-10-11,13105,1.0,9.0,0.0 +2011-10-12,12897,2.0,9.0,0.0 +2011-10-13,12674,3.0,9.0,0.0 +2011-10-14,11783,4.0,9.0,0.0 +2011-10-15,8104,5.0,9.0,0.0 +2011-10-16,8805,6.0,9.0,0.0 +2011-10-17,12899,0.0,9.0,0.0 +2011-10-18,13196,1.0,9.0,0.0 +2011-10-19,13200,2.0,9.0,0.0 +2011-10-20,13142,3.0,9.0,0.0 +2011-10-21,12269,4.0,9.0,0.0 +2011-10-22,8506,5.0,9.0,0.0 +2011-10-23,9133,6.0,9.0,0.0 +2011-10-24,13230,0.0,9.0,0.0 +2011-10-25,13364,1.0,9.0,0.0 +2011-10-26,13443,2.0,9.0,0.0 +2011-10-27,11080,3.0,9.0,0.0 +2011-10-28,10718,4.0,9.0,0.0 +2011-10-29,7997,5.0,9.0,0.0 +2011-10-30,8613,6.0,9.0,0.0 +2011-10-31,12319,0.0,9.0,0.0 +2011-11-01,12598,1.0,10.0,0.0 +2011-11-02,13218,2.0,10.0,0.0 +2011-11-03,12805,3.0,10.0,0.0 +2011-11-04,12883,4.0,10.0,0.0 +2011-11-05,8569,5.0,10.0,0.0 +2011-11-06,9090,6.0,10.0,0.0 +2011-11-07,11174,0.0,10.0,0.0 +2011-11-08,14122,1.0,10.0,0.0 +2011-11-09,12036,2.0,10.0,0.0 +2011-11-10,12966,3.0,10.0,0.0 +2011-11-11,12005,4.0,10.0,1.0 +2011-11-12,8419,5.0,10.0,0.0 +2011-11-13,9036,6.0,10.0,0.0 +2011-11-14,12804,0.0,10.0,0.0 +2011-11-15,13378,1.0,10.0,0.0 +2011-11-16,12693,2.0,10.0,0.0 +2011-11-17,13360,3.0,10.0,0.0 +2011-11-18,11744,4.0,10.0,0.0 +2011-11-19,8190,5.0,10.0,0.0 +2011-11-20,9690,6.0,10.0,0.0 +2011-11-21,12145,0.0,10.0,0.0 +2011-11-22,13212,1.0,10.0,0.0 +2011-11-23,13477,2.0,10.0,0.0 +2011-11-24,12085,3.0,10.0,1.0 +2011-11-25,10505,4.0,10.0,0.0 +2011-11-26,8705,5.0,10.0,0.0 +2011-11-27,9648,6.0,10.0,0.0 +2011-11-28,13613,0.0,10.0,0.0 +2011-11-29,14272,1.0,10.0,0.0 +2011-11-30,13957,2.0,10.0,0.0 +2011-12-01,14827,3.0,11.0,0.0 +2011-12-02,13591,4.0,11.0,0.0 +2011-12-03,9827,5.0,11.0,0.0 +2011-12-04,10540,6.0,11.0,0.0 +2011-12-05,14286,0.0,11.0,0.0 +2011-12-06,14420,1.0,11.0,0.0 +2011-12-07,13800,2.0,11.0,0.0 +2011-12-08,13077,3.0,11.0,0.0 +2011-12-09,13409,4.0,11.0,0.0 +2011-12-10,9537,5.0,11.0,0.0 +2011-12-11,9686,6.0,11.0,0.0 +2011-12-12,14003,0.0,11.0,0.0 +2011-12-13,13616,1.0,11.0,0.0 +2011-12-14,13695,2.0,11.0,0.0 +2011-12-15,13702,3.0,11.0,0.0 +2011-12-16,13328,4.0,11.0,0.0 +2011-12-17,8779,5.0,11.0,0.0 +2011-12-18,9541,6.0,11.0,0.0 +2011-12-19,13250,0.0,11.0,0.0 +2011-12-20,12924,1.0,11.0,0.0 +2011-12-21,12238,2.0,11.0,0.0 +2011-12-22,11812,3.0,11.0,0.0 +2011-12-23,10407,4.0,11.0,0.0 +2011-12-24,6600,5.0,11.0,0.0 +2011-12-25,5670,6.0,11.0,0.0 +2011-12-26,7446,0.0,11.0,1.0 +2011-12-27,9742,1.0,11.0,0.0 +2011-12-28,10019,2.0,11.0,0.0 +2011-12-29,10927,3.0,11.0,0.0 +2011-12-30,10146,4.0,11.0,0.0 +2012-01-01,6587,6.0,0.0,0.0 +2012-01-02,10254,0.0,0.0,1.0 +2012-01-03,12412,1.0,0.0,0.0 +2012-01-04,11806,2.0,0.0,0.0 +2012-01-05,13030,3.0,0.0,0.0 +2012-01-06,13081,4.0,0.0,0.0 +2012-01-07,9688,5.0,0.0,0.0 +2012-01-08,9682,6.0,0.0,0.0 +2012-01-09,12389,0.0,0.0,0.0 +2012-01-10,12888,1.0,0.0,0.0 +2012-01-11,14916,2.0,0.0,0.0 +2012-01-12,13966,3.0,0.0,0.0 +2012-01-13,13629,4.0,0.0,0.0 +2012-01-14,9862,5.0,0.0,0.0 +2012-01-15,10764,6.0,0.0,0.0 +2012-01-16,14066,0.0,0.0,1.0 +2012-01-17,14636,1.0,0.0,0.0 +2012-01-18,14308,2.0,0.0,0.0 +2012-01-19,14301,3.0,0.0,0.0 +2012-01-20,13525,4.0,0.0,0.0 +2012-01-21,10410,5.0,0.0,0.0 +2012-01-22,10384,6.0,0.0,0.0 +2012-01-23,14114,0.0,0.0,0.0 +2012-01-24,14996,1.0,0.0,0.0 +2012-01-25,14904,2.0,0.0,0.0 +2012-01-26,14957,3.0,0.0,0.0 +2012-01-27,15145,4.0,0.0,0.0 +2012-01-28,11182,5.0,0.0,0.0 +2012-01-29,11845,6.0,0.0,0.0 +2012-01-30,15747,0.0,0.0,0.0 +2012-01-31,16974,1.0,0.0,0.0 +2012-02-01,16410,2.0,1.0,0.0 +2012-02-02,15344,3.0,1.0,0.0 +2012-02-03,15275,4.0,1.0,0.0 +2012-02-04,10634,5.0,1.0,0.0 +2012-02-05,11996,6.0,1.0,0.0 +2012-02-06,13976,0.0,1.0,0.0 +2012-02-07,14838,1.0,1.0,0.0 +2012-02-08,15306,2.0,1.0,0.0 +2012-02-09,15598,3.0,1.0,0.0 +2012-02-10,14349,4.0,1.0,0.0 +2012-02-11,11061,5.0,1.0,0.0 +2012-02-12,12209,6.0,1.0,0.0 +2012-02-13,13869,0.0,1.0,0.0 +2012-02-14,15581,1.0,1.0,0.0 +2012-02-15,13850,2.0,1.0,0.0 +2012-02-16,15864,3.0,1.0,0.0 +2012-02-17,15855,4.0,1.0,0.0 +2012-02-18,11506,5.0,1.0,0.0 +2012-02-19,12713,6.0,1.0,0.0 +2012-02-20,15871,0.0,1.0,1.0 +2012-02-21,18141,1.0,1.0,0.0 +2012-02-22,18658,2.0,1.0,0.0 +2012-02-23,18336,3.0,1.0,0.0 +2012-02-24,17493,4.0,1.0,0.0 +2012-02-25,13047,5.0,1.0,0.0 +2012-02-26,13470,6.0,1.0,0.0 +2012-02-27,18588,0.0,1.0,0.0 +2012-02-28,19337,1.0,1.0,0.0 +2012-02-29,18919,2.0,1.0,0.0 +2012-03-01,16831,3.0,2.0,0.0 +2012-03-02,16858,4.0,2.0,0.0 +2012-03-03,12768,5.0,2.0,0.0 +2012-03-04,11378,6.0,2.0,0.0 +2012-03-05,17247,0.0,2.0,0.0 +2012-03-06,19299,1.0,2.0,0.0 +2012-03-07,19070,2.0,2.0,0.0 +2012-03-08,18345,3.0,2.0,0.0 +2012-03-09,17563,4.0,2.0,0.0 +2012-03-10,4558,5.0,2.0,0.0 +2012-03-11,11403,6.0,2.0,0.0 +2012-03-12,19012,0.0,2.0,0.0 +2012-03-13,19453,1.0,2.0,0.0 +2012-03-14,18612,2.0,2.0,0.0 +2012-03-15,18516,3.0,2.0,0.0 +2012-03-16,17712,4.0,2.0,0.0 +2012-03-17,12388,5.0,2.0,0.0 +2012-03-18,13136,6.0,2.0,0.0 +2012-03-19,19017,0.0,2.0,0.0 +2012-03-20,19748,1.0,2.0,0.0 +2012-03-21,19332,2.0,2.0,0.0 +2012-03-22,19193,3.0,2.0,0.0 +2012-03-23,17920,4.0,2.0,0.0 +2012-03-24,12753,5.0,2.0,0.0 +2012-03-25,13249,6.0,2.0,0.0 +2012-03-26,19124,0.0,2.0,0.0 +2012-03-27,19509,1.0,2.0,0.0 +2012-03-28,19821,2.0,2.0,0.0 +2012-03-29,19472,3.0,2.0,0.0 +2012-03-30,18427,4.0,2.0,0.0 +2012-03-31,13115,5.0,2.0,0.0 +2012-04-01,13515,6.0,3.0,0.0 +2012-04-02,18399,0.0,3.0,0.0 +2012-04-03,19605,1.0,3.0,0.0 +2012-04-04,19252,2.0,3.0,0.0 +2012-04-05,18543,3.0,3.0,0.0 +2012-04-06,16503,4.0,3.0,0.0 +2012-04-07,12460,5.0,3.0,0.0 +2012-04-08,12448,6.0,3.0,0.0 +2012-04-09,17445,0.0,3.0,0.0 +2012-04-10,19932,1.0,3.0,0.0 +2012-04-11,20228,2.0,3.0,0.0 +2012-04-12,19756,3.0,3.0,0.0 +2012-04-13,18782,4.0,3.0,0.0 +2012-04-14,13467,5.0,3.0,0.0 +2012-04-15,14327,6.0,3.0,0.0 +2012-04-16,20054,0.0,3.0,0.0 +2012-04-17,20519,1.0,3.0,0.0 +2012-04-18,20550,2.0,3.0,0.0 +2012-04-19,20701,3.0,3.0,0.0 +2012-04-20,19581,4.0,3.0,0.0 +2012-04-21,13836,5.0,3.0,0.0 +2012-04-22,15203,6.0,3.0,0.0 +2012-04-23,21022,0.0,3.0,0.0 +2012-04-24,21531,1.0,3.0,0.0 +2012-04-25,20843,2.0,3.0,0.0 +2012-04-26,20502,3.0,3.0,0.0 +2012-04-27,19350,4.0,3.0,0.0 +2012-04-28,13435,5.0,3.0,0.0 +2012-04-29,13740,6.0,3.0,0.0 +2012-04-30,18399,0.0,3.0,0.0 +2012-05-01,18568,1.0,4.0,0.0 +2012-05-02,20450,2.0,4.0,0.0 +2012-05-03,20346,3.0,4.0,0.0 +2012-05-04,19046,4.0,4.0,0.0 +2012-05-05,13624,5.0,4.0,0.0 +2012-05-06,14067,6.0,4.0,0.0 +2012-05-07,19843,0.0,4.0,0.0 +2012-05-08,20642,1.0,4.0,0.0 +2012-05-09,20494,2.0,4.0,0.0 +2012-05-10,20582,3.0,4.0,0.0 +2012-05-11,19082,4.0,4.0,0.0 +2012-05-12,12969,5.0,4.0,0.0 +2012-05-13,13213,6.0,4.0,0.0 +2012-05-14,19891,0.0,4.0,0.0 +2012-05-15,20429,1.0,4.0,0.0 +2012-05-16,19803,2.0,4.0,0.0 +2012-05-17,18502,3.0,4.0,0.0 +2012-05-18,17863,4.0,4.0,0.0 +2012-05-19,11967,5.0,4.0,0.0 +2012-05-20,12955,6.0,4.0,0.0 +2012-05-21,19504,0.0,4.0,0.0 +2012-05-22,21177,1.0,4.0,0.0 +2012-05-23,20755,2.0,4.0,0.0 +2012-05-24,20334,3.0,4.0,0.0 +2012-05-25,18596,4.0,4.0,0.0 +2012-05-26,11896,5.0,4.0,0.0 +2012-05-27,12267,6.0,4.0,0.0 +2012-05-28,16877,0.0,4.0,1.0 +2012-05-29,20475,1.0,4.0,0.0 +2012-05-30,20843,2.0,4.0,0.0 +2012-05-31,19725,3.0,4.0,0.0 +2012-06-01,18977,4.0,5.0,0.0 +2012-06-02,12762,5.0,5.0,0.0 +2012-06-03,13811,6.0,5.0,0.0 +2012-06-04,19603,0.0,5.0,0.0 +2012-06-05,20407,1.0,5.0,0.0 +2012-06-06,20109,2.0,5.0,0.0 +2012-06-07,20065,3.0,5.0,0.0 +2012-06-08,18897,4.0,5.0,0.0 +2012-06-09,12974,5.0,5.0,0.0 +2012-06-10,13579,6.0,5.0,0.0 +2012-06-11,19795,0.0,5.0,0.0 +2012-06-12,20766,1.0,5.0,0.0 +2012-06-13,20493,2.0,5.0,0.0 +2012-06-14,20337,3.0,5.0,0.0 +2012-06-15,18872,4.0,5.0,0.0 +2012-06-16,12563,5.0,5.0,0.0 +2012-06-17,12595,6.0,5.0,0.0 +2012-06-18,19942,0.0,5.0,0.0 +2012-06-19,20901,1.0,5.0,0.0 +2012-06-20,20460,2.0,5.0,0.0 +2012-06-21,20208,3.0,5.0,0.0 +2012-06-22,18334,4.0,5.0,0.0 +2012-06-23,12188,5.0,5.0,0.0 +2012-06-24,12974,6.0,5.0,0.0 +2012-06-25,19997,0.0,5.0,0.0 +2012-06-26,21259,1.0,5.0,0.0 +2012-06-27,20474,2.0,5.0,0.0 +2012-06-28,19885,3.0,5.0,0.0 +2012-06-29,18686,4.0,5.0,0.0 +2012-06-30,12240,5.0,5.0,0.0 +2012-07-01,12825,6.0,6.0,0.0 +2012-07-02,19514,0.0,6.0,0.0 +2012-07-03,20326,1.0,6.0,0.0 +2012-07-04,18182,2.0,6.0,1.0 +2012-07-05,19268,3.0,6.0,0.0 +2012-07-06,19182,4.0,6.0,0.0 +2012-07-07,12835,5.0,6.0,0.0 +2012-07-08,13365,6.0,6.0,0.0 +2012-07-09,20486,0.0,6.0,0.0 +2012-07-10,21706,1.0,6.0,0.0 +2012-07-11,21626,2.0,6.0,0.0 +2012-07-12,21252,3.0,6.0,0.0 +2012-07-13,20151,4.0,6.0,0.0 +2012-07-14,12797,5.0,6.0,0.0 +2012-07-15,13483,6.0,6.0,0.0 +2012-07-16,20626,0.0,6.0,0.0 +2012-07-17,21534,1.0,6.0,0.0 +2012-07-18,21272,2.0,6.0,0.0 +2012-07-19,20996,3.0,6.0,0.0 +2012-07-20,19689,4.0,6.0,0.0 +2012-07-21,12728,5.0,6.0,0.0 +2012-07-22,13196,6.0,6.0,0.0 +2012-07-23,20682,0.0,6.0,0.0 +2012-07-24,21436,1.0,6.0,0.0 +2012-07-25,20928,2.0,6.0,0.0 +2012-07-26,20682,3.0,6.0,0.0 +2012-07-27,19471,4.0,6.0,0.0 +2012-07-28,12348,5.0,6.0,0.0 +2012-07-29,13181,6.0,6.0,0.0 +2012-07-30,20472,0.0,6.0,0.0 +2012-07-31,20755,1.0,6.0,0.0 +2012-08-01,20981,2.0,7.0,0.0 +2012-08-02,20754,3.0,7.0,0.0 +2012-08-03,19474,4.0,7.0,0.0 +2012-08-04,12608,5.0,7.0,0.0 +2012-08-05,13300,6.0,7.0,0.0 +2012-08-06,20171,0.0,7.0,0.0 +2012-08-07,21381,1.0,7.0,0.0 +2012-08-08,21414,2.0,7.0,0.0 +2012-08-09,21189,3.0,7.0,0.0 +2012-08-10,20258,4.0,7.0,0.0 +2012-08-11,13126,5.0,7.0,0.0 +2012-08-12,13542,6.0,7.0,0.0 +2012-08-13,21095,0.0,7.0,0.0 +2012-08-14,21820,1.0,7.0,0.0 +2012-08-15,20412,2.0,7.0,0.0 +2012-08-16,20654,3.0,7.0,0.0 +2012-08-17,19865,4.0,7.0,0.0 +2012-08-18,13124,5.0,7.0,0.0 +2012-08-19,13500,6.0,7.0,0.0 +2012-08-20,21156,0.0,7.0,0.0 +2012-08-21,22188,1.0,7.0,0.0 +2012-08-22,22133,2.0,7.0,0.0 +2012-08-23,21972,3.0,7.0,0.0 +2012-08-24,20575,4.0,7.0,0.0 +2012-08-25,13606,5.0,7.0,0.0 +2012-08-26,14147,6.0,7.0,0.0 +2012-08-27,21513,0.0,7.0,0.0 +2012-08-28,22396,1.0,7.0,0.0 +2012-08-29,22023,2.0,7.0,0.0 +2012-08-30,22032,3.0,7.0,0.0 +2012-08-31,20667,4.0,7.0,0.0 +2012-09-01,13193,5.0,8.0,0.0 +2012-09-02,14236,6.0,8.0,0.0 +2012-09-03,19533,0.0,8.0,1.0 +2012-09-04,22529,1.0,8.0,0.0 +2012-09-05,23006,2.0,8.0,0.0 +2012-09-06,22463,3.0,8.0,0.0 +2012-09-07,21547,4.0,8.0,0.0 +2012-09-08,14061,5.0,8.0,0.0 +2012-09-09,15149,6.0,8.0,0.0 +2012-09-10,22730,0.0,8.0,0.0 +2012-09-11,23336,1.0,8.0,0.0 +2012-09-12,23521,2.0,8.0,0.0 +2012-09-13,23435,3.0,8.0,0.0 +2012-09-14,21632,4.0,8.0,0.0 +2012-09-15,14370,5.0,8.0,0.0 +2012-09-16,15122,6.0,8.0,0.0 +2012-09-17,23351,0.0,8.0,0.0 +2012-09-18,24066,1.0,8.0,0.0 +2012-09-19,23742,2.0,8.0,0.0 +2012-09-20,23585,3.0,8.0,0.0 +2012-09-21,22157,4.0,8.0,0.0 +2012-09-22,14539,5.0,8.0,0.0 +2012-09-23,15735,6.0,8.0,0.0 +2012-09-24,23613,0.0,8.0,0.0 +2012-09-25,24315,1.0,8.0,0.0 +2012-09-26,24513,2.0,8.0,0.0 +2012-09-27,23950,3.0,8.0,0.0 +2012-09-28,22489,4.0,8.0,0.0 +2012-09-29,15130,5.0,8.0,0.0 +2012-09-30,15516,6.0,8.0,0.0 +2012-10-01,22938,0.0,9.0,0.0 +2012-10-02,23758,1.0,9.0,0.0 +2012-10-03,24048,2.0,9.0,0.0 +2012-10-04,23651,3.0,9.0,0.0 +2012-10-05,22488,4.0,9.0,0.0 +2012-10-06,15261,5.0,9.0,0.0 +2012-10-07,16074,6.0,9.0,0.0 +2012-10-08,24300,0.0,9.0,1.0 +2012-10-09,26112,1.0,9.0,0.0 +2012-10-10,26118,2.0,9.0,0.0 +2012-10-11,25481,3.0,9.0,0.0 +2012-10-12,23749,4.0,9.0,0.0 +2012-10-13,16161,5.0,9.0,0.0 +2012-10-14,17196,6.0,9.0,0.0 +2012-10-15,25711,0.0,9.0,0.0 +2012-10-16,26368,1.0,9.0,0.0 +2012-10-17,26436,2.0,9.0,0.0 +2012-10-18,25588,3.0,9.0,0.0 +2012-10-19,24120,4.0,9.0,0.0 +2012-10-20,16546,5.0,9.0,0.0 +2012-10-21,17939,6.0,9.0,0.0 +2012-10-22,26790,0.0,9.0,0.0 +2012-10-23,26904,1.0,9.0,0.0 +2012-10-24,27135,2.0,9.0,0.0 +2012-10-25,26631,3.0,9.0,0.0 +2012-10-26,24735,4.0,9.0,0.0 +2012-10-27,16414,5.0,9.0,0.0 +2012-10-28,17832,6.0,9.0,0.0 +2012-10-29,26382,0.0,9.0,0.0 +2012-10-30,27051,1.0,9.0,0.0 +2012-10-31,26630,2.0,9.0,0.0 +2012-11-01,25001,3.0,10.0,0.0 +2012-11-02,24505,4.0,10.0,0.0 +2012-11-03,17411,5.0,10.0,0.0 +2012-11-04,18421,6.0,10.0,0.0 +2012-11-05,27468,0.0,10.0,0.0 +2012-11-06,28425,1.0,10.0,0.0 +2012-11-07,27405,2.0,10.0,0.0 +2012-11-08,28017,3.0,10.0,0.0 +2012-11-09,26332,4.0,10.0,0.0 +2012-11-10,18246,5.0,10.0,0.0 +2012-11-11,19133,6.0,10.0,0.0 +2012-11-12,27814,0.0,10.0,1.0 +2012-11-13,28922,1.0,10.0,0.0 +2012-11-14,28695,2.0,10.0,0.0 +2012-11-15,28078,3.0,10.0,0.0 +2012-11-16,26404,4.0,10.0,0.0 +2012-11-17,18254,5.0,10.0,0.0 +2012-11-18,19573,6.0,10.0,0.0 +2012-11-19,28486,0.0,10.0,0.0 +2012-11-20,28976,1.0,10.0,0.0 +2012-11-21,28161,2.0,10.0,0.0 +2012-11-22,24228,3.0,10.0,1.0 +2012-11-23,22550,4.0,10.0,0.0 +2012-11-24,17484,5.0,10.0,0.0 +2012-11-25,19188,6.0,10.0,0.0 +2012-11-26,28974,0.0,10.0,0.0 +2012-11-27,29963,1.0,10.0,0.0 +2012-11-28,30244,2.0,10.0,0.0 +2012-11-29,29538,3.0,10.0,0.0 +2012-11-30,26786,4.0,10.0,0.0 +2012-12-01,19253,5.0,11.0,0.0 +2012-12-02,20778,6.0,11.0,0.0 +2012-12-03,30026,0.0,11.0,0.0 +2012-12-04,30295,1.0,11.0,0.0 +2012-12-05,30105,2.0,11.0,0.0 +2012-12-06,29559,3.0,11.0,0.0 +2012-12-07,26613,4.0,11.0,0.0 +2012-12-08,18467,5.0,11.0,0.0 +2012-12-09,20055,6.0,11.0,0.0 +2012-12-10,28579,0.0,11.0,0.0 +2012-12-11,29642,1.0,11.0,0.0 +2012-12-12,29168,2.0,11.0,0.0 +2012-12-13,28652,3.0,11.0,0.0 +2012-12-14,26568,4.0,11.0,0.0 +2012-12-15,17788,5.0,11.0,0.0 +2012-12-16,18785,6.0,11.0,0.0 +2012-12-17,27496,0.0,11.0,0.0 +2012-12-18,27723,1.0,11.0,0.0 +2012-12-19,27055,2.0,11.0,0.0 +2012-12-20,26013,3.0,11.0,0.0 +2012-12-21,23140,4.0,11.0,0.0 +2012-12-22,15245,5.0,11.0,0.0 +2012-12-23,14097,6.0,11.0,0.0 +2012-12-24,16373,0.0,11.0,0.0 +2012-12-25,13596,1.0,11.0,1.0 +2012-12-26,17465,2.0,11.0,0.0 +2012-12-27,20445,3.0,11.0,0.0 +2012-12-28,20120,4.0,11.0,0.0 +2012-12-29,16407,5.0,11.0,0.0 +2012-12-30,15777,6.0,11.0,0.0 +2012-12-31,6200,0.0,11.0,0.0 +2013-01-01,11208,1.0,0.0,1.0 +2013-01-02,22522,2.0,0.0,0.0 +2013-01-03,24859,3.0,0.0,0.0 +2013-01-04,25302,4.0,0.0,0.0 +2013-01-05,19114,5.0,0.0,0.0 +2013-01-06,19650,6.0,0.0,0.0 +2013-01-07,27504,0.0,0.0,0.0 +2013-01-08,29375,1.0,0.0,0.0 +2013-01-09,29679,2.0,0.0,0.0 +2013-01-10,29661,3.0,0.0,0.0 +2013-01-11,28997,4.0,0.0,0.0 +2013-01-12,19920,5.0,0.0,0.0 +2013-01-13,21301,6.0,0.0,0.0 +2013-01-14,30089,0.0,0.0,0.0 +2013-01-15,30936,1.0,0.0,0.0 +2013-01-16,31416,2.0,0.0,0.0 +2013-01-17,30992,3.0,0.0,0.0 +2013-01-18,29420,4.0,0.0,0.0 +2013-01-19,20790,5.0,0.0,0.0 +2013-01-20,21897,6.0,0.0,0.0 +2013-01-21,29606,0.0,0.0,1.0 +2013-01-22,31573,1.0,0.0,0.0 +2013-01-23,32344,2.0,0.0,0.0 +2013-01-24,32485,3.0,0.0,0.0 +2013-01-25,30793,4.0,0.0,0.0 +2013-01-26,21917,5.0,0.0,0.0 +2013-01-27,23032,6.0,0.0,0.0 +2013-01-28,31946,0.0,0.0,0.0 +2013-01-29,33487,1.0,0.0,0.0 +2013-01-30,33192,2.0,0.0,0.0 +2013-01-31,32722,3.0,0.0,0.0 +2013-02-01,30716,4.0,1.0,0.0 +2013-02-02,21484,5.0,1.0,0.0 +2013-02-03,22962,6.0,1.0,0.0 +2013-02-04,31284,0.0,1.0,0.0 +2013-02-05,33106,1.0,1.0,0.0 +2013-02-06,32976,2.0,1.0,0.0 +2013-02-07,32429,3.0,1.0,0.0 +2013-02-08,30524,4.0,1.0,0.0 +2013-02-09,21085,5.0,1.0,0.0 +2013-02-10,22281,6.0,1.0,0.0 +2013-02-11,30989,0.0,1.0,0.0 +2013-02-12,32543,1.0,1.0,0.0 +2013-02-13,31854,2.0,1.0,0.0 +2013-02-14,30875,3.0,1.0,0.0 +2013-02-15,29531,4.0,1.0,0.0 +2013-02-16,22299,5.0,1.0,0.0 +2013-02-17,23941,6.0,1.0,0.0 +2013-02-18,33106,0.0,1.0,1.0 +2013-02-19,35274,1.0,1.0,0.0 +2013-02-20,35265,2.0,1.0,0.0 +2013-02-21,34535,3.0,1.0,0.0 +2013-02-22,33009,4.0,1.0,0.0 +2013-02-23,23466,5.0,1.0,0.0 +2013-02-24,24903,6.0,1.0,0.0 +2013-02-25,35081,0.0,1.0,0.0 +2013-02-26,36143,1.0,1.0,0.0 +2013-02-27,35992,2.0,1.0,0.0 +2013-02-28,35284,3.0,1.0,0.0 +2013-03-01,33063,4.0,2.0,0.0 +2013-03-02,23944,5.0,2.0,0.0 +2013-03-03,25119,6.0,2.0,0.0 +2013-03-04,35777,0.0,2.0,0.0 +2013-03-05,36559,1.0,2.0,0.0 +2013-03-06,35998,2.0,2.0,0.0 +2013-03-07,35682,3.0,2.0,0.0 +2013-03-08,33619,4.0,2.0,0.0 +2013-03-09,23860,5.0,2.0,0.0 +2013-03-10,25293,6.0,2.0,0.0 +2013-03-11,36253,0.0,2.0,0.0 +2013-03-12,37391,1.0,2.0,0.0 +2013-03-13,37132,2.0,2.0,0.0 +2013-03-14,36044,3.0,2.0,0.0 +2013-03-15,34297,4.0,2.0,0.0 +2013-03-16,24005,5.0,2.0,0.0 +2013-03-17,25836,6.0,2.0,0.0 +2013-03-18,36614,0.0,2.0,0.0 +2013-03-19,38229,1.0,2.0,0.0 +2013-03-20,38085,2.0,2.0,0.0 +2013-03-21,37290,3.0,2.0,0.0 +2013-03-22,35173,4.0,2.0,0.0 +2013-03-23,23732,5.0,2.0,0.0 +2013-03-24,26573,6.0,2.0,0.0 +2013-03-25,38095,0.0,2.0,0.0 +2013-03-26,38959,1.0,2.0,0.0 +2013-03-27,36841,2.0,2.0,0.0 +2013-03-28,35861,3.0,2.0,0.0 +2013-03-29,31458,4.0,2.0,0.0 +2013-03-30,23375,5.0,2.0,0.0 +2013-03-31,23229,6.0,2.0,0.0 +2013-04-01,32188,0.0,3.0,0.0 +2013-04-02,37574,1.0,3.0,0.0 +2013-04-03,37688,2.0,3.0,0.0 +2013-04-04,36662,3.0,3.0,0.0 +2013-04-05,35247,4.0,3.0,0.0 +2013-04-06,25579,5.0,3.0,0.0 +2013-04-07,28152,6.0,3.0,0.0 +2013-04-08,38770,0.0,3.0,0.0 +2013-04-09,39537,1.0,3.0,0.0 +2013-04-10,39099,2.0,3.0,0.0 +2013-04-11,38970,3.0,3.0,0.0 +2013-04-12,37006,4.0,3.0,0.0 +2013-04-13,25241,5.0,3.0,0.0 +2013-04-14,26604,6.0,3.0,0.0 +2013-04-15,38046,0.0,3.0,0.0 +2013-04-16,39572,1.0,3.0,0.0 +2013-04-17,39873,2.0,3.0,0.0 +2013-04-18,39338,3.0,3.0,0.0 +2013-04-19,36343,4.0,3.0,0.0 +2013-04-20,25210,5.0,3.0,0.0 +2013-04-21,26877,6.0,3.0,0.0 +2013-04-22,39663,0.0,3.0,0.0 +2013-04-23,40706,1.0,3.0,0.0 +2013-04-24,39844,2.0,3.0,0.0 +2013-04-25,38703,3.0,3.0,0.0 +2013-04-26,35427,4.0,3.0,0.0 +2013-04-27,26071,5.0,3.0,0.0 +2013-04-28,27388,6.0,3.0,0.0 +2013-04-29,37487,0.0,3.0,0.0 +2013-04-30,36940,1.0,3.0,0.0 +2013-05-01,33606,2.0,4.0,0.0 +2013-05-02,37390,3.0,4.0,0.0 +2013-05-03,35633,4.0,4.0,0.0 +2013-05-04,24228,5.0,4.0,0.0 +2013-05-05,24997,6.0,4.0,0.0 +2013-05-06,36749,0.0,4.0,0.0 +2013-05-07,37704,1.0,4.0,0.0 +2013-05-08,37857,2.0,4.0,0.0 +2013-05-09,35833,3.0,4.0,0.0 +2013-05-10,34646,4.0,4.0,0.0 +2013-05-11,24376,5.0,4.0,0.0 +2013-05-12,25378,6.0,4.0,0.0 +2013-05-13,38290,0.0,4.0,0.0 +2013-05-14,39639,1.0,4.0,0.0 +2013-05-15,38600,2.0,4.0,0.0 +2013-05-16,38360,3.0,4.0,0.0 +2013-05-17,35699,4.0,4.0,0.0 +2013-05-18,23617,5.0,4.0,0.0 +2013-05-19,24777,6.0,4.0,0.0 +2013-05-20,36164,0.0,4.0,0.0 +2013-05-21,38868,1.0,4.0,0.0 +2013-05-22,39343,2.0,4.0,0.0 +2013-05-23,38808,3.0,4.0,0.0 +2013-05-24,35952,4.0,4.0,0.0 +2013-05-25,23631,5.0,4.0,0.0 +2013-05-26,24617,6.0,4.0,0.0 +2013-05-27,33553,0.0,4.0,1.0 +2013-05-28,38933,1.0,4.0,0.0 +2013-05-29,39393,2.0,4.0,0.0 +2013-05-30,37654,3.0,4.0,0.0 +2013-05-31,36341,4.0,4.0,0.0 +2013-06-01,23781,5.0,5.0,0.0 +2013-06-02,25611,6.0,5.0,0.0 +2013-06-03,38377,0.0,5.0,0.0 +2013-06-04,39508,1.0,5.0,0.0 +2013-06-05,38949,2.0,5.0,0.0 +2013-06-06,38397,3.0,5.0,0.0 +2013-06-07,36512,4.0,5.0,0.0 +2013-06-08,24453,5.0,5.0,0.0 +2013-06-09,25513,6.0,5.0,0.0 +2013-06-10,35931,0.0,5.0,0.0 +2013-06-11,36456,1.0,5.0,0.0 +2013-06-12,36649,2.0,5.0,0.0 +2013-06-13,37838,3.0,5.0,0.0 +2013-06-14,35372,4.0,5.0,0.0 +2013-06-15,22633,5.0,5.0,0.0 +2013-06-16,23632,6.0,5.0,0.0 +2013-06-17,36996,0.0,5.0,0.0 +2013-06-18,38905,1.0,5.0,0.0 +2013-06-19,38128,2.0,5.0,0.0 +2013-06-20,37205,3.0,5.0,0.0 +2013-06-21,34488,4.0,5.0,0.0 +2013-06-22,22328,5.0,5.0,0.0 +2013-06-23,24116,6.0,5.0,0.0 +2013-06-24,37051,0.0,5.0,0.0 +2013-06-25,38924,1.0,5.0,0.0 +2013-06-26,38481,2.0,5.0,0.0 +2013-06-27,37527,3.0,5.0,0.0 +2013-06-28,35081,4.0,5.0,0.0 +2013-06-29,22609,5.0,5.0,0.0 +2013-06-30,23535,6.0,5.0,0.0 +2013-07-01,35825,0.0,6.0,0.0 +2013-07-02,37818,1.0,6.0,0.0 +2013-07-03,37797,2.0,6.0,0.0 +2013-07-04,33322,3.0,6.0,1.0 +2013-07-05,32777,4.0,6.0,0.0 +2013-07-06,22675,5.0,6.0,0.0 +2013-07-07,24558,6.0,6.0,0.0 +2013-07-08,38713,0.0,6.0,0.0 +2013-07-09,40620,1.0,6.0,0.0 +2013-07-10,42070,2.0,6.0,0.0 +2013-07-11,41020,3.0,6.0,0.0 +2013-07-12,37346,4.0,6.0,0.0 +2013-07-13,23190,5.0,6.0,0.0 +2013-07-14,24518,6.0,6.0,0.0 +2013-07-15,38390,0.0,6.0,0.0 +2013-07-16,40149,1.0,6.0,0.0 +2013-07-17,40568,2.0,6.0,0.0 +2013-07-18,40213,3.0,6.0,0.0 +2013-07-19,38293,4.0,6.0,0.0 +2013-07-20,24090,5.0,6.0,0.0 +2013-07-21,24762,6.0,6.0,0.0 +2013-07-22,39038,0.0,6.0,0.0 +2013-07-23,40878,1.0,6.0,0.0 +2013-07-24,39600,2.0,6.0,0.0 +2013-07-25,38883,3.0,6.0,0.0 +2013-07-26,36596,4.0,6.0,0.0 +2013-07-27,23424,5.0,6.0,0.0 +2013-07-28,24364,6.0,6.0,0.0 +2013-07-29,37997,0.0,6.0,0.0 +2013-07-30,39569,1.0,6.0,0.0 +2013-07-31,39220,2.0,6.0,0.0 +2013-08-01,38151,3.0,7.0,0.0 +2013-08-02,35991,4.0,7.0,0.0 +2013-08-03,23359,5.0,7.0,0.0 +2013-08-04,24392,6.0,7.0,0.0 +2013-08-05,37880,0.0,7.0,0.0 +2013-08-06,39787,1.0,7.0,0.0 +2013-08-07,40562,2.0,7.0,0.0 +2013-08-08,39204,3.0,7.0,0.0 +2013-08-09,36126,4.0,7.0,0.0 +2013-08-10,23322,5.0,7.0,0.0 +2013-08-11,24528,6.0,7.0,0.0 +2013-08-12,37294,0.0,7.0,0.0 +2013-08-13,38848,1.0,7.0,0.0 +2013-08-14,38772,2.0,7.0,0.0 +2013-08-15,34626,3.0,7.0,0.0 +2013-08-16,34857,4.0,7.0,0.0 +2013-08-17,23932,5.0,7.0,0.0 +2013-08-18,24779,6.0,7.0,0.0 +2013-08-19,37843,0.0,7.0,0.0 +2013-08-20,38890,1.0,7.0,0.0 +2013-08-21,39298,2.0,7.0,0.0 +2013-08-22,38649,3.0,7.0,0.0 +2013-08-23,36410,4.0,7.0,0.0 +2013-08-24,23893,5.0,7.0,0.0 +2013-08-25,25183,6.0,7.0,0.0 +2013-08-26,37745,0.0,7.0,0.0 +2013-08-27,40279,1.0,7.0,0.0 +2013-08-28,40041,2.0,7.0,0.0 +2013-08-29,39814,3.0,7.0,0.0 +2013-08-30,36737,4.0,7.0,0.0 +2013-08-31,23496,5.0,7.0,0.0 +2013-09-01,24887,6.0,8.0,0.0 +2013-09-02,34734,0.0,8.0,1.0 +2013-09-03,40062,1.0,8.0,0.0 +2013-09-04,40547,2.0,8.0,0.0 +2013-09-05,39817,3.0,8.0,0.0 +2013-09-06,36795,4.0,8.0,0.0 +2013-09-07,25041,5.0,8.0,0.0 +2013-09-08,26867,6.0,8.0,0.0 +2013-09-09,40162,0.0,8.0,0.0 +2013-09-10,41282,1.0,8.0,0.0 +2013-09-11,41776,2.0,8.0,0.0 +2013-09-12,40797,3.0,8.0,0.0 +2013-09-13,39038,4.0,8.0,0.0 +2013-09-14,25547,5.0,8.0,0.0 +2013-09-15,27248,6.0,8.0,0.0 +2013-09-16,41174,0.0,8.0,0.0 +2013-09-17,41800,1.0,8.0,0.0 +2013-09-18,40673,2.0,8.0,0.0 +2013-09-19,35777,3.0,8.0,0.0 +2013-09-20,37267,4.0,8.0,0.0 +2013-09-21,25963,5.0,8.0,0.0 +2013-09-22,28105,6.0,8.0,0.0 +2013-09-23,40921,0.0,8.0,0.0 +2013-09-24,42979,1.0,8.0,0.0 +2013-09-25,42683,2.0,8.0,0.0 +2013-09-26,42336,3.0,8.0,0.0 +2013-09-27,39720,4.0,8.0,0.0 +2013-09-28,26060,5.0,8.0,0.0 +2013-09-29,29404,6.0,8.0,0.0 +2013-09-30,41805,0.0,8.0,0.0 +2013-10-01,41029,1.0,9.0,0.0 +2013-10-02,41378,2.0,9.0,0.0 +2013-10-03,40288,3.0,9.0,0.0 +2013-10-04,38966,4.0,9.0,0.0 +2013-10-05,26606,5.0,9.0,0.0 +2013-10-06,28694,6.0,9.0,0.0 +2013-10-07,42983,0.0,9.0,0.0 +2013-10-08,45969,1.0,9.0,0.0 +2013-10-09,45673,2.0,9.0,0.0 +2013-10-10,44823,3.0,9.0,0.0 +2013-10-11,42240,4.0,9.0,0.0 +2013-10-12,28719,5.0,9.0,0.0 +2013-10-13,29129,6.0,9.0,0.0 +2013-10-14,42706,0.0,9.0,1.0 +2013-10-15,45380,1.0,9.0,0.0 +2013-10-16,46301,2.0,9.0,0.0 +2013-10-17,45649,3.0,9.0,0.0 +2013-10-18,42778,4.0,9.0,0.0 +2013-10-19,28774,5.0,9.0,0.0 +2013-10-20,31296,6.0,9.0,0.0 +2013-10-21,45838,0.0,9.0,0.0 +2013-10-22,46948,1.0,9.0,0.0 +2013-10-23,46510,2.0,9.0,0.0 +2013-10-24,44514,3.0,9.0,0.0 +2013-10-25,44395,4.0,9.0,0.0 +2013-10-26,29485,5.0,9.0,0.0 +2013-10-27,31661,6.0,9.0,0.0 +2013-10-28,46946,0.0,9.0,0.0 +2013-10-29,48500,1.0,9.0,0.0 +2013-10-30,48321,2.0,9.0,0.0 +2013-10-31,46159,3.0,9.0,0.0 +2013-11-01,41112,4.0,10.0,0.0 +2013-11-02,29827,5.0,10.0,0.0 +2013-11-03,31521,6.0,10.0,0.0 +2013-11-04,47735,0.0,10.0,0.0 +2013-11-05,49358,1.0,10.0,0.0 +2013-11-06,49622,2.0,10.0,0.0 +2013-11-07,48864,3.0,10.0,0.0 +2013-11-08,46153,4.0,10.0,0.0 +2013-11-09,31598,5.0,10.0,0.0 +2013-11-10,33505,6.0,10.0,0.0 +2013-11-11,47101,0.0,10.0,1.0 +2013-11-12,50609,1.0,10.0,0.0 +2013-11-13,48306,2.0,10.0,0.0 +2013-11-14,49673,3.0,10.0,0.0 +2013-11-15,46797,4.0,10.0,0.0 +2013-11-16,32098,5.0,10.0,0.0 +2013-11-17,34542,6.0,10.0,0.0 +2013-11-18,50981,0.0,10.0,0.0 +2013-11-19,51901,1.0,10.0,0.0 +2013-11-20,51862,2.0,10.0,0.0 +2013-11-21,51330,3.0,10.0,0.0 +2013-11-22,48100,4.0,10.0,0.0 +2013-11-23,32590,5.0,10.0,0.0 +2013-11-24,34863,6.0,10.0,0.0 +2013-11-25,49346,0.0,10.0,0.0 +2013-11-26,51549,1.0,10.0,0.0 +2013-11-27,49231,2.0,10.0,0.0 +2013-11-28,42985,3.0,10.0,1.0 +2013-11-29,39014,4.0,10.0,0.0 +2013-11-30,29927,5.0,10.0,0.0 +2013-12-01,32875,6.0,11.0,0.0 +2013-12-02,50342,0.0,11.0,0.0 +2013-12-03,52500,1.0,11.0,0.0 +2013-12-04,52398,2.0,11.0,0.0 +2013-12-05,51352,3.0,11.0,0.0 +2013-12-06,47337,4.0,11.0,0.0 +2013-12-07,32551,5.0,11.0,0.0 +2013-12-08,34756,6.0,11.0,0.0 +2013-12-09,50839,0.0,11.0,0.0 +2013-12-10,51506,1.0,11.0,0.0 +2013-12-11,50204,2.0,11.0,0.0 +2013-12-12,48640,3.0,11.0,0.0 +2013-12-13,45504,4.0,11.0,0.0 +2013-12-14,30350,5.0,11.0,0.0 +2013-12-15,32192,6.0,11.0,0.0 +2013-12-16,47571,0.0,11.0,0.0 +2013-12-17,48189,1.0,11.0,0.0 +2013-12-18,46983,2.0,11.0,0.0 +2013-12-19,44986,3.0,11.0,0.0 +2013-12-20,41717,4.0,11.0,0.0 +2013-12-21,26649,5.0,11.0,0.0 +2013-12-22,26917,6.0,11.0,0.0 +2013-12-23,36144,0.0,11.0,0.0 +2013-12-24,30015,1.0,11.0,0.0 +2013-12-25,23280,2.0,11.0,1.0 +2013-12-26,29732,3.0,11.0,0.0 +2013-12-27,32334,4.0,11.0,0.0 +2013-12-28,26369,5.0,11.0,0.0 +2013-12-29,27110,6.0,11.0,0.0 +2013-12-30,35237,0.0,11.0,0.0 +2013-12-31,12471,1.0,11.0,0.0 +2014-01-01,19103,2.0,0.0,1.0 +2014-01-02,38454,3.0,0.0,0.0 +2014-01-03,38788,4.0,0.0,0.0 +2014-01-04,31132,5.0,0.0,0.0 +2014-01-05,32334,6.0,0.0,0.0 +2014-01-06,44539,0.0,0.0,0.0 +2014-01-07,47256,1.0,0.0,0.0 +2014-01-08,47472,2.0,0.0,0.0 +2014-01-09,48662,3.0,0.0,0.0 +2014-01-10,46462,4.0,0.0,0.0 +2014-01-11,32376,5.0,0.0,0.0 +2014-01-12,34043,6.0,0.0,0.0 +2014-01-13,49000,0.0,0.0,0.0 +2014-01-14,50766,1.0,0.0,0.0 +2014-01-15,51247,2.0,0.0,0.0 +2014-01-16,51321,3.0,0.0,0.0 +2014-01-17,48280,4.0,0.0,0.0 +2014-01-18,33741,5.0,0.0,0.0 +2014-01-19,35398,6.0,0.0,0.0 +2014-01-20,48750,0.0,0.0,1.0 +2014-01-21,52079,1.0,0.0,0.0 +2014-01-22,52542,2.0,0.0,0.0 +2014-01-23,52376,3.0,0.0,0.0 +2014-01-24,48155,4.0,0.0,0.0 +2014-01-25,36337,5.0,0.0,0.0 +2014-01-26,38223,6.0,0.0,0.0 +2014-01-27,51032,0.0,0.0,0.0 +2014-01-28,52414,1.0,0.0,0.0 +2014-01-29,51673,2.0,0.0,0.0 +2014-01-30,50439,3.0,0.0,0.0 +2014-01-31,47161,4.0,0.0,0.0 +2014-02-01,33166,5.0,1.0,0.0 +2014-02-02,34890,6.0,1.0,0.0 +2014-02-03,47975,0.0,1.0,0.0 +2014-02-04,51265,1.0,1.0,0.0 +2014-02-05,51264,2.0,1.0,0.0 +2014-02-06,52288,3.0,1.0,0.0 +2014-02-07,50247,4.0,1.0,0.0 +2014-02-08,36698,5.0,1.0,0.0 +2014-02-09,38503,6.0,1.0,0.0 +2014-02-10,54226,0.0,1.0,0.0 +2014-02-11,56115,1.0,1.0,0.0 +2014-02-12,56224,2.0,1.0,0.0 +2014-02-13,55362,3.0,1.0,0.0 +2014-02-14,50776,4.0,1.0,0.0 +2014-02-15,35096,5.0,1.0,0.0 +2014-02-16,38108,6.0,1.0,0.0 +2014-02-17,53408,0.0,1.0,1.0 +2014-02-18,57332,1.0,1.0,0.0 +2014-02-19,56375,2.0,1.0,0.0 +2014-02-20,53624,3.0,1.0,0.0 +2014-02-21,53840,4.0,1.0,0.0 +2014-02-22,37965,5.0,1.0,0.0 +2014-02-23,38901,6.0,1.0,0.0 +2014-02-24,57056,0.0,1.0,0.0 +2014-02-25,58723,1.0,1.0,0.0 +2014-02-26,58317,2.0,1.0,0.0 +2014-02-27,58104,3.0,1.0,0.0 +2014-02-28,54417,4.0,1.0,0.0 +2014-03-01,37253,5.0,2.0,0.0 +2014-03-02,39545,6.0,2.0,0.0 +2014-03-03,56281,0.0,2.0,0.0 +2014-03-04,58275,1.0,2.0,0.0 +2014-03-05,58531,2.0,2.0,0.0 +2014-03-06,58230,3.0,2.0,0.0 +2014-03-07,54381,4.0,2.0,0.0 +2014-03-08,36908,5.0,2.0,0.0 +2014-03-09,38903,6.0,2.0,0.0 +2014-03-10,57466,0.0,2.0,0.0 +2014-03-11,58201,1.0,2.0,0.0 +2014-03-12,59508,2.0,2.0,0.0 +2014-03-13,58819,3.0,2.0,0.0 +2014-03-14,54631,4.0,2.0,0.0 +2014-03-15,37045,5.0,2.0,0.0 +2014-03-16,40071,6.0,2.0,0.0 +2014-03-17,58119,0.0,2.0,0.0 +2014-03-18,60296,1.0,2.0,0.0 +2014-03-19,60348,2.0,2.0,0.0 +2014-03-20,59653,3.0,2.0,0.0 +2014-03-21,54723,4.0,2.0,0.0 +2014-03-22,38438,5.0,2.0,0.0 +2014-03-23,41116,6.0,2.0,0.0 +2014-03-24,59413,0.0,2.0,0.0 +2014-03-25,58491,1.0,2.0,0.0 +2014-03-26,57706,2.0,2.0,0.0 +2014-03-27,59629,3.0,2.0,0.0 +2014-03-28,55961,4.0,2.0,0.0 +2014-03-29,37785,5.0,2.0,0.0 +2014-03-30,40405,6.0,2.0,0.0 +2014-03-31,59279,0.0,2.0,0.0 +2014-04-01,59904,1.0,3.0,0.0 +2014-04-02,61078,2.0,3.0,0.0 +2014-04-03,60665,3.0,3.0,0.0 +2014-04-04,56880,4.0,3.0,0.0 +2014-04-05,38088,5.0,3.0,0.0 +2014-04-06,40745,6.0,3.0,0.0 +2014-04-07,59201,0.0,3.0,0.0 +2014-04-08,62373,1.0,3.0,0.0 +2014-04-09,61447,2.0,3.0,0.0 +2014-04-10,60721,3.0,3.0,0.0 +2014-04-11,57113,4.0,3.0,0.0 +2014-04-12,38778,5.0,3.0,0.0 +2014-04-13,41754,6.0,3.0,0.0 +2014-04-14,60584,0.0,3.0,0.0 +2014-04-15,62573,1.0,3.0,0.0 +2014-04-16,61692,2.0,3.0,0.0 +2014-04-17,58954,3.0,3.0,0.0 +2014-04-18,50828,4.0,3.0,0.0 +2014-04-19,37908,5.0,3.0,0.0 +2014-04-20,37161,6.0,3.0,0.0 +2014-04-21,53971,0.0,3.0,0.0 +2014-04-22,63154,1.0,3.0,0.0 +2014-04-23,64034,2.0,3.0,0.0 +2014-04-24,63013,3.0,3.0,0.0 +2014-04-25,58866,4.0,3.0,0.0 +2014-04-26,41588,5.0,3.0,0.0 +2014-04-27,46281,6.0,3.0,0.0 +2014-04-28,62917,0.0,3.0,0.0 +2014-04-29,58880,1.0,3.0,0.0 +2014-04-30,59201,2.0,3.0,0.0 +2014-05-01,51873,3.0,4.0,0.0 +2014-05-02,53187,4.0,4.0,0.0 +2014-05-03,38524,5.0,4.0,0.0 +2014-05-04,42442,6.0,4.0,0.0 +2014-05-05,59938,0.0,4.0,0.0 +2014-05-06,63226,1.0,4.0,0.0 +2014-05-07,64056,2.0,4.0,0.0 +2014-05-08,62352,3.0,4.0,0.0 +2014-05-09,58471,4.0,4.0,0.0 +2014-05-10,40697,5.0,4.0,0.0 +2014-05-11,42929,6.0,4.0,0.0 +2014-05-12,62494,0.0,4.0,0.0 +2014-05-13,63889,1.0,4.0,0.0 +2014-05-14,63489,2.0,4.0,0.0 +2014-05-15,62615,3.0,4.0,0.0 +2014-05-16,57477,4.0,4.0,0.0 +2014-05-17,38612,5.0,4.0,0.0 +2014-05-18,41834,6.0,4.0,0.0 +2014-05-19,61093,0.0,4.0,0.0 +2014-05-20,63539,1.0,4.0,0.0 +2014-05-21,63520,2.0,4.0,0.0 +2014-05-22,62947,3.0,4.0,0.0 +2014-05-23,58949,4.0,4.0,0.0 +2014-05-24,38860,5.0,4.0,0.0 +2014-05-25,41500,6.0,4.0,0.0 +2014-05-26,54191,0.0,4.0,1.0 +2014-05-27,62110,1.0,4.0,0.0 +2014-05-28,62119,2.0,4.0,0.0 +2014-05-29,57778,3.0,4.0,0.0 +2014-05-30,55360,4.0,4.0,0.0 +2014-05-31,36956,5.0,4.0,0.0 +2014-06-01,38803,6.0,5.0,0.0 +2014-06-02,57279,0.0,5.0,0.0 +2014-06-03,61545,1.0,5.0,0.0 +2014-06-04,62143,2.0,5.0,0.0 +2014-06-05,61565,3.0,5.0,0.0 +2014-06-06,56682,4.0,5.0,0.0 +2014-06-07,37230,5.0,5.0,0.0 +2014-06-08,39439,6.0,5.0,0.0 +2014-06-09,56406,0.0,5.0,0.0 +2014-06-10,60934,1.0,5.0,0.0 +2014-06-11,61030,2.0,5.0,0.0 +2014-06-12,58531,3.0,5.0,0.0 +2014-06-13,54801,4.0,5.0,0.0 +2014-06-14,36688,5.0,5.0,0.0 +2014-06-15,38911,6.0,5.0,0.0 +2014-06-16,57628,0.0,5.0,0.0 +2014-06-17,60437,1.0,5.0,0.0 +2014-06-18,59666,2.0,5.0,0.0 +2014-06-19,58550,3.0,5.0,0.0 +2014-06-20,54734,4.0,5.0,0.0 +2014-06-21,36936,5.0,5.0,0.0 +2014-06-22,40998,6.0,5.0,0.0 +2014-06-23,57817,0.0,5.0,0.0 +2014-06-24,59898,1.0,5.0,0.0 +2014-06-25,59275,2.0,5.0,0.0 +2014-06-26,58194,3.0,5.0,0.0 +2014-06-27,54687,4.0,5.0,0.0 +2014-06-28,34376,5.0,5.0,0.0 +2014-06-29,36039,6.0,5.0,0.0 +2014-06-30,56288,0.0,5.0,0.0 +2014-07-01,57564,1.0,6.0,0.0 +2014-07-02,58226,2.0,6.0,0.0 +2014-07-03,57447,3.0,6.0,0.0 +2014-07-04,46868,4.0,6.0,1.0 +2014-07-05,31976,5.0,6.0,0.0 +2014-07-06,35625,6.0,6.0,0.0 +2014-07-07,57648,0.0,6.0,0.0 +2014-07-08,59817,1.0,6.0,0.0 +2014-07-09,58684,2.0,6.0,0.0 +2014-07-10,59610,3.0,6.0,0.0 +2014-07-11,56361,4.0,6.0,0.0 +2014-07-12,36405,5.0,6.0,0.0 +2014-07-13,37367,6.0,6.0,0.0 +2014-07-14,57220,0.0,6.0,0.0 +2014-07-15,60954,1.0,6.0,0.0 +2014-07-16,60772,2.0,6.0,0.0 +2014-07-17,58139,3.0,6.0,0.0 +2014-07-18,55605,4.0,6.0,0.0 +2014-07-19,35444,5.0,6.0,0.0 +2014-07-20,37516,6.0,6.0,0.0 +2014-07-21,58789,0.0,6.0,0.0 +2014-07-22,61115,1.0,6.0,0.0 +2014-07-23,61183,2.0,6.0,0.0 +2014-07-24,60482,3.0,6.0,0.0 +2014-07-25,56642,4.0,6.0,0.0 +2014-07-26,37052,5.0,6.0,0.0 +2014-07-27,40482,6.0,6.0,0.0 +2014-07-28,58625,0.0,6.0,0.0 +2014-07-29,60214,1.0,6.0,0.0 +2014-07-30,60244,2.0,6.0,0.0 +2014-07-31,59555,3.0,6.0,0.0 +2014-08-01,54851,4.0,7.0,0.0 +2014-08-02,34918,5.0,7.0,0.0 +2014-08-03,36852,6.0,7.0,0.0 +2014-08-04,57355,0.0,7.0,0.0 +2014-08-05,60536,1.0,7.0,0.0 +2014-08-06,60691,2.0,7.0,0.0 +2014-08-07,59387,3.0,7.0,0.0 +2014-08-08,55824,4.0,7.0,0.0 +2014-08-09,35770,5.0,7.0,0.0 +2014-08-10,38102,6.0,7.0,0.0 +2014-08-11,59054,0.0,7.0,0.0 +2014-08-12,60590,1.0,7.0,0.0 +2014-08-13,60448,2.0,7.0,0.0 +2014-08-14,58944,3.0,7.0,0.0 +2014-08-15,53160,4.0,7.0,0.0 +2014-08-16,35988,5.0,7.0,0.0 +2014-08-17,39009,6.0,7.0,0.0 +2014-08-18,59031,0.0,7.0,0.0 +2014-08-19,61664,1.0,7.0,0.0 +2014-08-20,61490,2.0,7.0,0.0 +2014-08-21,61343,3.0,7.0,0.0 +2014-08-22,58054,4.0,7.0,0.0 +2014-08-23,38573,5.0,7.0,0.0 +2014-08-24,41813,6.0,7.0,0.0 +2014-08-25,58804,0.0,7.0,0.0 +2014-08-26,61870,1.0,7.0,0.0 +2014-08-27,61716,2.0,7.0,0.0 +2014-08-28,60539,3.0,7.0,0.0 +2014-08-29,56147,4.0,7.0,0.0 +2014-08-30,36483,5.0,7.0,0.0 +2014-08-31,38402,6.0,7.0,0.0 +2014-09-01,53643,0.0,8.0,1.0 +2014-09-02,62318,1.0,8.0,0.0 +2014-09-03,63877,2.0,8.0,0.0 +2014-09-04,63233,3.0,8.0,0.0 +2014-09-05,59368,4.0,8.0,0.0 +2014-09-06,39023,5.0,8.0,0.0 +2014-09-07,40969,6.0,8.0,0.0 +2014-09-08,59558,0.0,8.0,0.0 +2014-09-09,63536,1.0,8.0,0.0 +2014-09-10,64457,2.0,8.0,0.0 +2014-09-11,64373,3.0,8.0,0.0 +2014-09-12,60704,4.0,8.0,0.0 +2014-09-13,40285,5.0,8.0,0.0 +2014-09-14,42980,6.0,8.0,0.0 +2014-09-15,63854,0.0,8.0,0.0 +2014-09-16,66603,1.0,8.0,0.0 +2014-09-17,66943,2.0,8.0,0.0 +2014-09-18,65374,3.0,8.0,0.0 +2014-09-19,61976,4.0,8.0,0.0 +2014-09-20,41540,5.0,8.0,0.0 +2014-09-21,45895,6.0,8.0,0.0 +2014-09-22,65680,0.0,8.0,0.0 +2014-09-23,65894,1.0,8.0,0.0 +2014-09-24,67516,2.0,8.0,0.0 +2014-09-25,66172,3.0,8.0,0.0 +2014-09-26,62052,4.0,8.0,0.0 +2014-09-27,40681,5.0,8.0,0.0 +2014-09-28,44507,6.0,8.0,0.0 +2014-09-29,66009,0.0,8.0,0.0 +2014-09-30,65377,1.0,8.0,0.0 +2014-10-01,64361,2.0,9.0,0.0 +2014-10-02,63192,3.0,9.0,0.0 +2014-10-03,58623,4.0,9.0,0.0 +2014-10-04,40046,5.0,9.0,0.0 +2014-10-05,42635,6.0,9.0,0.0 +2014-10-06,64289,0.0,9.0,0.0 +2014-10-07,67728,1.0,9.0,0.0 +2014-10-08,70580,2.0,9.0,0.0 +2014-10-09,68939,3.0,9.0,0.0 +2014-10-10,65565,4.0,9.0,0.0 +2014-10-11,45396,5.0,9.0,0.0 +2014-10-12,46315,6.0,9.0,0.0 +2014-10-13,68081,0.0,9.0,1.0 +2014-10-14,70462,1.0,9.0,0.0 +2014-10-15,71679,2.0,9.0,0.0 +2014-10-16,71133,3.0,9.0,0.0 +2014-10-17,66584,4.0,9.0,0.0 +2014-10-18,45259,5.0,9.0,0.0 +2014-10-19,46726,6.0,9.0,0.0 +2014-10-20,71061,0.0,9.0,0.0 +2014-10-21,74351,1.0,9.0,0.0 +2014-10-22,71496,2.0,9.0,0.0 +2014-10-23,72852,3.0,9.0,0.0 +2014-10-24,68836,4.0,9.0,0.0 +2014-10-25,46343,5.0,9.0,0.0 +2014-10-26,51704,6.0,9.0,0.0 +2014-10-27,72386,0.0,9.0,0.0 +2014-10-28,73319,1.0,9.0,0.0 +2014-10-29,71694,2.0,9.0,0.0 +2014-10-30,73188,3.0,9.0,0.0 +2014-10-31,66606,4.0,9.0,0.0 +2014-11-01,43864,5.0,10.0,0.0 +2014-11-02,48725,6.0,10.0,0.0 +2014-11-03,72901,0.0,10.0,0.0 +2014-11-04,75637,1.0,10.0,0.0 +2014-11-05,76423,2.0,10.0,0.0 +2014-11-06,74164,3.0,10.0,0.0 +2014-11-07,71186,4.0,10.0,0.0 +2014-11-08,49033,5.0,10.0,0.0 +2014-11-09,52480,6.0,10.0,0.0 +2014-11-10,74984,0.0,10.0,0.0 +2014-11-11,76113,1.0,10.0,1.0 +2014-11-12,77768,2.0,10.0,0.0 +2014-11-13,77072,3.0,10.0,0.0 +2014-11-14,72203,4.0,10.0,0.0 +2014-11-15,50149,5.0,10.0,0.0 +2014-11-16,53584,6.0,10.0,0.0 +2014-11-17,77223,0.0,10.0,0.0 +2014-11-18,79371,1.0,10.0,0.0 +2014-11-19,79472,2.0,10.0,0.0 +2014-11-20,78357,3.0,10.0,0.0 +2014-11-21,73355,4.0,10.0,0.0 +2014-11-22,50881,5.0,10.0,0.0 +2014-11-23,55522,6.0,10.0,0.0 +2014-11-24,78109,0.0,10.0,0.0 +2014-11-25,79387,1.0,10.0,0.0 +2014-11-26,76170,2.0,10.0,0.0 +2014-11-27,67321,3.0,10.0,1.0 +2014-11-28,61368,4.0,10.0,0.0 +2014-11-29,46144,5.0,10.0,0.0 +2014-11-30,51623,6.0,10.0,0.0 +2014-12-01,77869,0.0,11.0,0.0 +2014-12-02,80799,1.0,11.0,0.0 +2014-12-03,80672,2.0,11.0,0.0 +2014-12-04,78755,3.0,11.0,0.0 +2014-12-05,74423,4.0,11.0,0.0 +2014-12-06,51209,5.0,11.0,0.0 +2014-12-07,54238,6.0,11.0,0.0 +2014-12-08,77058,0.0,11.0,0.0 +2014-12-09,79147,1.0,11.0,0.0 +2014-12-10,77471,2.0,11.0,0.0 +2014-12-11,76005,3.0,11.0,0.0 +2014-12-12,70489,4.0,11.0,0.0 +2014-12-13,47806,5.0,11.0,0.0 +2014-12-14,50486,6.0,11.0,0.0 +2014-12-15,73353,0.0,11.0,0.0 +2014-12-16,74217,1.0,11.0,0.0 +2014-12-17,72849,2.0,11.0,0.0 +2014-12-18,70140,3.0,11.0,0.0 +2014-12-19,64016,4.0,11.0,0.0 +2014-12-20,42131,5.0,11.0,0.0 +2014-12-21,45466,6.0,11.0,0.0 +2014-12-22,59804,0.0,11.0,0.0 +2014-12-23,57678,1.0,11.0,0.0 +2014-12-24,45609,2.0,11.0,0.0 +2014-12-25,34924,3.0,11.0,1.0 +2014-12-26,40747,4.0,11.0,0.0 +2014-12-27,37359,5.0,11.0,0.0 +2014-12-28,39682,6.0,11.0,0.0 +2014-12-29,53699,0.0,11.0,0.0 +2014-12-30,54029,1.0,11.0,0.0 +2014-12-31,18574,2.0,11.0,0.0 +2015-01-01,33211,3.0,0.0,1.0 +2015-01-02,48077,4.0,0.0,0.0 +2015-01-03,44563,5.0,0.0,0.0 +2015-01-04,49137,6.0,0.0,0.0 +2015-01-05,66676,0.0,0.0,0.0 +2015-01-06,66039,1.0,0.0,0.0 +2015-01-07,70055,2.0,0.0,0.0 +2015-01-08,71505,3.0,0.0,0.0 +2015-01-09,66446,4.0,0.0,0.0 +2015-01-10,49634,5.0,0.0,0.0 +2015-01-11,52346,6.0,0.0,0.0 +2015-01-12,76021,0.0,0.0,0.0 +2015-01-13,77374,1.0,0.0,0.0 +2015-01-14,78209,2.0,0.0,0.0 +2015-01-15,77896,3.0,0.0,0.0 +2015-01-16,73533,4.0,0.0,0.0 +2015-01-17,51229,5.0,0.0,0.0 +2015-01-18,54212,6.0,0.0,0.0 +2015-01-19,75243,0.0,0.0,1.0 +2015-01-20,80898,1.0,0.0,0.0 +2015-01-21,81397,2.0,0.0,0.0 +2015-01-22,80848,3.0,0.0,0.0 +2015-01-23,77202,4.0,0.0,0.0 +2015-01-24,55935,5.0,0.0,0.0 +2015-01-25,61597,6.0,0.0,0.0 +2015-01-26,79962,0.0,0.0,0.0 +2015-01-27,82207,1.0,0.0,0.0 +2015-01-28,82554,2.0,0.0,0.0 +2015-01-29,81467,3.0,0.0,0.0 +2015-01-30,76405,4.0,0.0,0.0 +2015-01-31,53052,5.0,0.0,0.0 +2015-02-01,55516,6.0,1.0,0.0 +2015-02-02,78483,0.0,1.0,0.0 +2015-02-03,80571,1.0,1.0,0.0 +2015-02-04,83041,2.0,1.0,0.0 +2015-02-05,82992,3.0,1.0,0.0 +2015-02-06,79509,4.0,1.0,0.0 +2015-02-07,54980,5.0,1.0,0.0 +2015-02-08,59201,6.0,1.0,0.0 +2015-02-09,84344,0.0,1.0,0.0 +2015-02-10,85600,1.0,1.0,0.0 +2015-02-11,84990,2.0,1.0,0.0 +2015-02-12,84056,3.0,1.0,0.0 +2015-02-13,78771,4.0,1.0,0.0 +2015-02-14,50473,5.0,1.0,0.0 +2015-02-15,55681,6.0,1.0,0.0 +2015-02-16,76934,0.0,1.0,1.0 +2015-02-17,80882,1.0,1.0,0.0 +2015-02-18,80672,2.0,1.0,0.0 +2015-02-19,79879,3.0,1.0,0.0 +2015-02-20,77309,4.0,1.0,0.0 +2015-02-21,56256,5.0,1.0,0.0 +2015-02-22,62005,6.0,1.0,0.0 +2015-02-23,81400,0.0,1.0,0.0 +2015-02-24,84252,1.0,1.0,0.0 +2015-02-25,85804,2.0,1.0,0.0 +2015-02-26,86417,3.0,1.0,0.0 +2015-02-27,81035,4.0,1.0,0.0 +2015-02-28,57647,5.0,1.0,0.0 +2015-03-01,59286,6.0,2.0,0.0 +2015-03-02,87020,0.0,2.0,0.0 +2015-03-03,89520,1.0,2.0,0.0 +2015-03-04,90519,2.0,2.0,0.0 +2015-03-05,88078,3.0,2.0,0.0 +2015-03-06,83016,4.0,2.0,0.0 +2015-03-07,57201,5.0,2.0,0.0 +2015-03-08,60121,6.0,2.0,0.0 +2015-03-09,88330,0.0,2.0,0.0 +2015-03-10,91456,1.0,2.0,0.0 +2015-03-11,91102,2.0,2.0,0.0 +2015-03-12,90934,3.0,2.0,0.0 +2015-03-13,86003,4.0,2.0,0.0 +2015-03-14,58089,5.0,2.0,0.0 +2015-03-15,62177,6.0,2.0,0.0 +2015-03-16,90924,0.0,2.0,0.0 +2015-03-17,93210,1.0,2.0,0.0 +2015-03-18,92153,2.0,2.0,0.0 +2015-03-19,91674,3.0,2.0,0.0 +2015-03-20,86065,4.0,2.0,0.0 +2015-03-21,59532,5.0,2.0,0.0 +2015-03-22,65999,6.0,2.0,0.0 +2015-03-23,91418,0.0,2.0,0.0 +2015-03-24,94159,1.0,2.0,0.0 +2015-03-25,93458,2.0,2.0,0.0 +2015-03-26,92072,3.0,2.0,0.0 +2015-03-27,83128,4.0,2.0,0.0 +2015-03-28,57894,5.0,2.0,0.0 +2015-03-29,60676,6.0,2.0,0.0 +2015-03-30,91212,0.0,2.0,0.0 +2015-03-31,93079,1.0,2.0,0.0 +2015-04-01,90691,2.0,3.0,0.0 +2015-04-02,86589,3.0,3.0,0.0 +2015-04-03,74443,4.0,3.0,0.0 +2015-04-04,55184,5.0,3.0,0.0 +2015-04-05,54861,6.0,3.0,0.0 +2015-04-06,78892,0.0,3.0,0.0 +2015-04-07,93458,1.0,3.0,0.0 +2015-04-08,95291,2.0,3.0,0.0 +2015-04-09,93141,3.0,3.0,0.0 +2015-04-10,86853,4.0,3.0,0.0 +2015-04-11,59522,5.0,3.0,0.0 +2015-04-12,63432,6.0,3.0,0.0 +2015-04-13,91817,0.0,3.0,0.0 +2015-04-14,94974,1.0,3.0,0.0 +2015-04-15,94061,2.0,3.0,0.0 +2015-04-16,94221,3.0,3.0,0.0 +2015-04-17,88699,4.0,3.0,0.0 +2015-04-18,59654,5.0,3.0,0.0 +2015-04-19,65146,6.0,3.0,0.0 +2015-04-20,94916,0.0,3.0,0.0 +2015-04-21,97299,1.0,3.0,0.0 +2015-04-22,97751,2.0,3.0,0.0 +2015-04-23,95638,3.0,3.0,0.0 +2015-04-24,89613,4.0,3.0,0.0 +2015-04-25,61119,5.0,3.0,0.0 +2015-04-26,68408,6.0,3.0,0.0 +2015-04-27,94300,0.0,3.0,0.0 +2015-04-28,97417,1.0,3.0,0.0 +2015-04-29,95247,2.0,3.0,0.0 +2015-04-30,89512,3.0,3.0,0.0 +2015-05-01,71100,4.0,4.0,0.0 +2015-05-02,55068,5.0,4.0,0.0 +2015-05-03,59245,6.0,4.0,0.0 +2015-05-04,89677,0.0,4.0,0.0 +2015-05-05,94643,1.0,4.0,0.0 +2015-05-06,94869,2.0,4.0,0.0 +2015-05-07,93583,3.0,4.0,0.0 +2015-05-08,85836,4.0,4.0,0.0 +2015-05-09,57774,5.0,4.0,0.0 +2015-05-10,61098,6.0,4.0,0.0 +2015-05-11,92261,0.0,4.0,0.0 +2015-05-12,96912,1.0,4.0,0.0 +2015-05-13,94490,2.0,4.0,0.0 +2015-05-14,88189,3.0,4.0,0.0 +2015-05-15,84151,4.0,4.0,0.0 +2015-05-16,57518,5.0,4.0,0.0 +2015-05-17,62282,6.0,4.0,0.0 +2015-05-18,92330,0.0,4.0,0.0 +2015-05-19,96248,1.0,4.0,0.0 +2015-05-20,96061,2.0,4.0,0.0 +2015-05-21,94121,3.0,4.0,0.0 +2015-05-22,87344,4.0,4.0,0.0 +2015-05-23,56965,5.0,4.0,0.0 +2015-05-24,60744,6.0,4.0,0.0 +2015-05-25,77609,0.0,4.0,1.0 +2015-05-26,93876,1.0,4.0,0.0 +2015-05-27,95475,2.0,4.0,0.0 +2015-05-28,92911,3.0,4.0,0.0 +2015-05-29,86540,4.0,4.0,0.0 +2015-05-30,56399,5.0,4.0,0.0 +2015-05-31,59770,6.0,4.0,0.0 +2015-06-01,89681,0.0,5.0,0.0 +2015-06-02,94065,1.0,5.0,0.0 +2015-06-03,93262,2.0,5.0,0.0 +2015-06-04,89150,3.0,5.0,0.0 +2015-06-05,84240,4.0,5.0,0.0 +2015-06-06,55264,5.0,5.0,0.0 +2015-06-07,59114,6.0,5.0,0.0 +2015-06-08,89414,0.0,5.0,0.0 +2015-06-09,94342,1.0,5.0,0.0 +2015-06-10,92730,2.0,5.0,0.0 +2015-06-11,90337,3.0,5.0,0.0 +2015-06-12,82629,4.0,5.0,0.0 +2015-06-13,54393,5.0,5.0,0.0 +2015-06-14,58454,6.0,5.0,0.0 +2015-06-15,88580,0.0,5.0,0.0 +2015-06-16,91424,1.0,5.0,0.0 +2015-06-17,91408,2.0,5.0,0.0 +2015-06-18,89458,3.0,5.0,0.0 +2015-06-19,82843,4.0,5.0,0.0 +2015-06-20,52691,5.0,5.0,0.0 +2015-06-21,57034,6.0,5.0,0.0 +2015-06-22,84455,0.0,5.0,0.0 +2015-06-23,90430,1.0,5.0,0.0 +2015-06-24,89483,2.0,5.0,0.0 +2015-06-25,88234,3.0,5.0,0.0 +2015-06-26,81883,4.0,5.0,0.0 +2015-06-27,52129,5.0,5.0,0.0 +2015-06-28,54858,6.0,5.0,0.0 +2015-06-29,86080,0.0,5.0,0.0 +2015-06-30,88498,1.0,5.0,0.0 +2015-07-01,86019,2.0,6.0,0.0 +2015-07-02,84921,3.0,6.0,0.0 +2015-07-03,72626,4.0,6.0,1.0 +2015-07-04,47682,5.0,6.0,0.0 +2015-07-05,51161,6.0,6.0,0.0 +2015-07-06,84781,0.0,6.0,0.0 +2015-07-07,89887,1.0,6.0,0.0 +2015-07-08,89657,2.0,6.0,0.0 +2015-07-09,88592,3.0,6.0,0.0 +2015-07-10,82408,4.0,6.0,0.0 +2015-07-11,52448,5.0,6.0,0.0 +2015-07-12,56396,6.0,6.0,0.0 +2015-07-13,87354,0.0,6.0,0.0 +2015-07-14,88965,1.0,6.0,0.0 +2015-07-15,88859,2.0,6.0,0.0 +2015-07-16,86788,3.0,6.0,0.0 +2015-07-17,80759,4.0,6.0,0.0 +2015-07-18,51601,5.0,6.0,0.0 +2015-07-19,55215,6.0,6.0,0.0 +2015-07-20,85913,0.0,6.0,0.0 +2015-07-21,89034,1.0,6.0,0.0 +2015-07-22,89449,2.0,6.0,0.0 +2015-07-23,89039,3.0,6.0,0.0 +2015-07-24,82762,4.0,6.0,0.0 +2015-07-25,53435,5.0,6.0,0.0 +2015-07-26,57851,6.0,6.0,0.0 +2015-07-27,87111,0.0,6.0,0.0 +2015-07-28,89813,1.0,6.0,0.0 +2015-07-29,89080,2.0,6.0,0.0 +2015-07-30,86852,3.0,6.0,0.0 +2015-07-31,80715,4.0,6.0,0.0 +2015-08-01,49693,5.0,7.0,0.0 +2015-08-02,51980,6.0,7.0,0.0 +2015-08-03,83065,0.0,7.0,0.0 +2015-08-04,87753,1.0,7.0,0.0 +2015-08-05,87047,2.0,7.0,0.0 +2015-08-06,85675,3.0,7.0,0.0 +2015-08-07,79329,4.0,7.0,0.0 +2015-08-08,50372,5.0,7.0,0.0 +2015-08-09,53900,6.0,7.0,0.0 +2015-08-10,84498,0.0,7.0,0.0 +2015-08-11,88065,1.0,7.0,0.0 +2015-08-12,88003,2.0,7.0,0.0 +2015-08-13,86159,3.0,7.0,0.0 +2015-08-14,80407,4.0,7.0,0.0 +2015-08-15,52148,5.0,7.0,0.0 +2015-08-16,55563,6.0,7.0,0.0 +2015-08-17,85716,0.0,7.0,0.0 +2015-08-18,90098,1.0,7.0,0.0 +2015-08-19,90311,2.0,7.0,0.0 +2015-08-20,89112,3.0,7.0,0.0 +2015-08-21,83607,4.0,7.0,0.0 +2015-08-22,54685,5.0,7.0,0.0 +2015-08-23,59679,6.0,7.0,0.0 +2015-08-24,87916,0.0,7.0,0.0 +2015-08-25,89785,1.0,7.0,0.0 +2015-08-26,90842,2.0,7.0,0.0 +2015-08-27,89589,3.0,7.0,0.0 +2015-08-28,84012,4.0,7.0,0.0 +2015-08-29,52998,5.0,7.0,0.0 +2015-08-30,55886,6.0,7.0,0.0 +2015-08-31,86983,0.0,7.0,0.0 +2015-09-01,91295,1.0,8.0,0.0 +2015-09-02,91046,2.0,8.0,0.0 +2015-09-03,87017,3.0,8.0,0.0 +2015-09-04,80813,4.0,8.0,0.0 +2015-09-05,54463,5.0,8.0,0.0 +2015-09-06,59864,6.0,8.0,0.0 +2015-09-07,80617,0.0,8.0,1.0 +2015-09-08,93446,1.0,8.0,0.0 +2015-09-09,94640,2.0,8.0,0.0 +2015-09-10,94089,3.0,8.0,0.0 +2015-09-11,88287,4.0,8.0,0.0 +2015-09-12,57236,5.0,8.0,0.0 +2015-09-13,61339,6.0,8.0,0.0 +2015-09-14,94100,0.0,8.0,0.0 +2015-09-15,97210,1.0,8.0,0.0 +2015-09-16,97520,2.0,8.0,0.0 +2015-09-17,95561,3.0,8.0,0.0 +2015-09-18,90210,4.0,8.0,0.0 +2015-09-19,58521,5.0,8.0,0.0 +2015-09-20,62414,6.0,8.0,0.0 +2015-09-21,96432,0.0,8.0,0.0 +2015-09-22,99956,1.0,8.0,0.0 +2015-09-23,99207,2.0,8.0,0.0 +2015-09-24,97696,3.0,8.0,0.0 +2015-09-25,90619,4.0,8.0,0.0 +2015-09-26,59733,5.0,8.0,0.0 +2015-09-27,64337,6.0,8.0,0.0 +2015-09-28,95277,0.0,8.0,0.0 +2015-09-29,99909,1.0,8.0,0.0 +2015-09-30,98496,2.0,8.0,0.0 +2015-10-01,93111,3.0,9.0,0.0 +2015-10-02,86753,4.0,9.0,0.0 +2015-10-03,58268,5.0,9.0,0.0 +2015-10-04,62592,6.0,9.0,0.0 +2015-10-05,95603,0.0,9.0,0.0 +2015-10-06,99837,1.0,9.0,0.0 +2015-10-07,100860,2.0,9.0,0.0 +2015-10-08,102409,3.0,9.0,0.0 +2015-10-09,95631,4.0,9.0,0.0 +2015-10-10,66043,5.0,9.0,0.0 +2015-10-11,66601,6.0,9.0,0.0 +2015-10-12,98066,0.0,9.0,1.0 +2015-10-13,106570,1.0,9.0,0.0 +2015-10-14,105415,2.0,9.0,0.0 +2015-10-15,104366,3.0,9.0,0.0 +2015-10-16,97556,4.0,9.0,0.0 +2015-10-17,64064,5.0,9.0,0.0 +2015-10-18,69221,6.0,9.0,0.0 +2015-10-19,105710,0.0,9.0,0.0 +2015-10-20,108226,1.0,9.0,0.0 +2015-10-21,107216,2.0,9.0,0.0 +2015-10-22,106180,3.0,9.0,0.0 +2015-10-23,99348,4.0,9.0,0.0 +2015-10-24,67090,5.0,9.0,0.0 +2015-10-25,73283,6.0,9.0,0.0 +2015-10-26,104805,0.0,9.0,0.0 +2015-10-27,111076,1.0,9.0,0.0 +2015-10-28,110991,2.0,9.0,0.0 +2015-10-29,109068,3.0,9.0,0.0 +2015-10-30,100655,4.0,9.0,0.0 +2015-10-31,63910,5.0,9.0,0.0 +2015-11-01,67454,6.0,10.0,0.0 +2015-11-02,106405,0.0,10.0,0.0 +2015-11-03,113189,1.0,10.0,0.0 +2015-11-04,112399,2.0,10.0,0.0 +2015-11-05,112257,3.0,10.0,0.0 +2015-11-06,105629,4.0,10.0,0.0 +2015-11-07,70570,5.0,10.0,0.0 +2015-11-08,75161,6.0,10.0,0.0 +2015-11-09,110784,0.0,10.0,0.0 +2015-11-10,112978,1.0,10.0,0.0 +2015-11-11,107347,2.0,10.0,1.0 +2015-11-12,111293,3.0,10.0,0.0 +2015-11-13,104493,4.0,10.0,0.0 +2015-11-14,68039,5.0,10.0,0.0 +2015-11-15,73945,6.0,10.0,0.0 +2015-11-16,111285,0.0,10.0,0.0 +2015-11-17,115457,1.0,10.0,0.0 +2015-11-18,115393,2.0,10.0,0.0 +2015-11-19,115387,3.0,10.0,0.0 +2015-11-20,107008,4.0,10.0,0.0 +2015-11-21,71677,5.0,10.0,0.0 +2015-11-22,77702,6.0,10.0,0.0 +2015-11-23,113226,0.0,10.0,0.0 +2015-11-24,114841,1.0,10.0,0.0 +2015-11-25,109386,2.0,10.0,0.0 +2015-11-26,96620,3.0,10.0,1.0 +2015-11-27,88369,4.0,10.0,0.0 +2015-11-28,66696,5.0,10.0,0.0 +2015-11-29,74591,6.0,10.0,0.0 +2015-11-30,114424,0.0,10.0,0.0 +2015-12-01,117806,1.0,11.0,0.0 +2015-12-02,118201,2.0,11.0,0.0 +2015-12-03,117780,3.0,11.0,0.0 +2015-12-04,108975,4.0,11.0,0.0 +2015-12-05,72662,5.0,11.0,0.0 +2015-12-06,76360,6.0,11.0,0.0 +2015-12-07,113903,0.0,11.0,0.0 +2015-12-08,115911,1.0,11.0,0.0 +2015-12-09,115324,2.0,11.0,0.0 +2015-12-10,113844,3.0,11.0,0.0 +2015-12-11,105420,4.0,11.0,0.0 +2015-12-12,70442,5.0,11.0,0.0 +2015-12-13,74537,6.0,11.0,0.0 +2015-12-14,110352,0.0,11.0,0.0 +2015-12-15,111033,1.0,11.0,0.0 +2015-12-16,107508,2.0,11.0,0.0 +2015-12-17,103108,3.0,11.0,0.0 +2015-12-18,93664,4.0,11.0,0.0 +2015-12-19,60441,5.0,11.0,0.0 +2015-12-20,62608,6.0,11.0,0.0 +2015-12-21,91916,0.0,11.0,0.0 +2015-12-22,91125,1.0,11.0,0.0 +2015-12-23,84466,2.0,11.0,0.0 +2015-12-24,66672,3.0,11.0,0.0 +2015-12-25,50812,4.0,11.0,1.0 +2015-12-26,49720,5.0,11.0,0.0 +2015-12-27,57018,6.0,11.0,0.0 +2015-12-28,76983,0.0,11.0,0.0 +2015-12-29,80256,1.0,11.0,0.0 +2015-12-30,78067,2.0,11.0,0.0 +2016-01-01,46109,4.0,0.0,1.0 +2016-01-02,56771,5.0,0.0,0.0 +2016-01-03,63608,6.0,0.0,0.0 +2016-01-04,96670,0.0,0.0,0.0 +2016-01-05,102054,1.0,0.0,0.0 +2016-01-06,101968,2.0,0.0,0.0 +2016-01-07,103695,3.0,0.0,0.0 +2016-01-08,99226,4.0,0.0,0.0 +2016-01-09,68617,5.0,0.0,0.0 +2016-01-10,73313,6.0,0.0,0.0 +2016-01-11,107882,0.0,0.0,0.0 +2016-01-12,111240,1.0,0.0,0.0 +2016-01-13,111346,2.0,0.0,0.0 +2016-01-14,110350,3.0,0.0,0.0 +2016-01-15,103836,4.0,0.0,0.0 +2016-01-16,69762,5.0,0.0,0.0 +2016-01-17,73548,6.0,0.0,0.0 +2016-01-18,106252,0.0,0.0,1.0 +2016-01-19,114235,1.0,0.0,0.0 +2016-01-20,114520,2.0,0.0,0.0 +2016-01-21,113333,3.0,0.0,0.0 +2016-01-22,106865,4.0,0.0,0.0 +2016-01-23,74103,5.0,0.0,0.0 +2016-01-24,78655,6.0,0.0,0.0 +2016-01-25,114045,0.0,0.0,0.0 +2016-01-26,116293,1.0,0.0,0.0 +2016-01-27,117360,2.0,0.0,0.0 +2016-01-28,112890,3.0,0.0,0.0 +2016-01-29,110408,4.0,0.0,0.0 +2016-01-30,77881,5.0,0.0,0.0 +2016-01-31,81804,6.0,0.0,0.0 +2016-02-01,115705,0.0,1.0,0.0 +2016-02-02,117639,1.0,1.0,0.0 +2016-02-03,118168,2.0,1.0,0.0 +2016-02-04,115485,3.0,1.0,0.0 +2016-02-05,106779,4.0,1.0,0.0 +2016-02-06,72602,5.0,1.0,0.0 +2016-02-07,73299,6.0,1.0,0.0 +2016-02-08,103308,0.0,1.0,0.0 +2016-02-09,110246,1.0,1.0,0.0 +2016-02-10,111835,2.0,1.0,0.0 +2016-02-11,112118,3.0,1.0,0.0 +2016-02-12,105677,4.0,1.0,0.0 +2016-02-13,74145,5.0,1.0,0.0 +2016-02-14,76379,6.0,1.0,0.0 +2016-02-15,111654,0.0,1.0,1.0 +2016-02-16,121528,1.0,1.0,0.0 +2016-02-17,122884,2.0,1.0,0.0 +2016-02-18,123112,3.0,1.0,0.0 +2016-02-19,117492,4.0,1.0,0.0 +2016-02-20,81509,5.0,1.0,0.0 +2016-02-21,86026,6.0,1.0,0.0 +2016-02-22,124960,0.0,1.0,0.0 +2016-02-23,128025,1.0,1.0,0.0 +2016-02-24,128860,2.0,1.0,0.0 +2016-02-25,126574,3.0,1.0,0.0 +2016-02-26,119158,4.0,1.0,0.0 +2016-02-27,81761,5.0,1.0,0.0 +2016-02-28,86421,6.0,1.0,0.0 +2016-02-29,125898,0.0,1.0,0.0 +2016-03-01,128020,1.0,2.0,0.0 +2016-03-02,130518,2.0,2.0,0.0 +2016-03-03,129859,3.0,2.0,0.0 +2016-03-04,121636,4.0,2.0,0.0 +2016-03-05,83814,5.0,2.0,0.0 +2016-03-06,86859,6.0,2.0,0.0 +2016-03-07,127229,0.0,2.0,0.0 +2016-03-08,129281,1.0,2.0,0.0 +2016-03-09,131505,2.0,2.0,0.0 +2016-03-10,126847,3.0,2.0,0.0 +2016-03-11,121670,4.0,2.0,0.0 +2016-03-12,82209,5.0,2.0,0.0 +2016-03-13,87358,6.0,2.0,0.0 +2016-03-14,129607,0.0,2.0,0.0 +2016-03-15,132397,1.0,2.0,0.0 +2016-03-16,132666,2.0,2.0,0.0 +2016-03-17,129579,3.0,2.0,0.0 +2016-03-18,120239,4.0,2.0,0.0 +2016-03-19,81427,5.0,2.0,0.0 +2016-03-20,86878,6.0,2.0,0.0 +2016-03-21,128245,0.0,2.0,0.0 +2016-03-22,130351,1.0,2.0,0.0 +2016-03-23,128611,2.0,2.0,0.0 +2016-03-24,122141,3.0,2.0,0.0 +2016-03-25,105815,4.0,2.0,0.0 +2016-03-26,78197,5.0,2.0,0.0 +2016-03-27,78675,6.0,2.0,0.0 +2016-03-28,116328,0.0,2.0,0.0 +2016-03-29,131001,1.0,2.0,0.0 +2016-03-30,133101,2.0,2.0,0.0 +2016-03-31,130283,3.0,2.0,0.0 +2016-04-01,119257,4.0,3.0,0.0 +2016-04-02,81281,5.0,3.0,0.0 +2016-04-03,87360,6.0,3.0,0.0 +2016-04-04,126389,0.0,3.0,0.0 +2016-04-05,133803,1.0,3.0,0.0 +2016-04-06,135934,2.0,3.0,0.0 +2016-04-07,134653,3.0,3.0,0.0 +2016-04-08,125221,4.0,3.0,0.0 +2016-04-09,85645,5.0,3.0,0.0 +2016-04-10,91857,6.0,3.0,0.0 +2016-04-11,136700,0.0,3.0,0.0 +2016-04-12,138801,1.0,3.0,0.0 +2016-04-13,137409,2.0,3.0,0.0 +2016-04-14,134651,3.0,3.0,0.0 +2016-04-15,125713,4.0,3.0,0.0 +2016-04-16,84789,5.0,3.0,0.0 +2016-04-17,90514,6.0,3.0,0.0 +2016-04-18,135770,0.0,3.0,0.0 +2016-04-19,140338,1.0,3.0,0.0 +2016-04-20,138994,2.0,3.0,0.0 +2016-04-21,134338,3.0,3.0,0.0 +2016-04-22,125713,4.0,3.0,0.0 +2016-04-23,85348,5.0,3.0,0.0 +2016-04-24,91963,6.0,3.0,0.0 +2016-04-25,135422,0.0,3.0,0.0 +2016-04-26,141059,1.0,3.0,0.0 +2016-04-27,138390,2.0,3.0,0.0 +2016-04-28,134493,3.0,3.0,0.0 +2016-04-29,123089,4.0,3.0,0.0 +2016-04-30,78081,5.0,3.0,0.0 +2016-05-01,80160,6.0,4.0,0.0 +2016-05-02,118508,0.0,4.0,0.0 +2016-05-03,131204,1.0,4.0,0.0 +2016-05-04,132146,2.0,4.0,0.0 +2016-05-05,123214,3.0,4.0,0.0 +2016-05-06,117566,4.0,4.0,0.0 +2016-05-07,78005,5.0,4.0,0.0 +2016-05-08,81871,6.0,4.0,0.0 +2016-05-09,127489,0.0,4.0,0.0 +2016-05-10,136121,1.0,4.0,0.0 +2016-05-11,135402,2.0,4.0,0.0 +2016-05-12,132926,3.0,4.0,0.0 +2016-05-13,123555,4.0,4.0,0.0 +2016-05-14,80533,5.0,4.0,0.0 +2016-05-15,84697,6.0,4.0,0.0 +2016-05-16,125306,0.0,4.0,0.0 +2016-05-17,135812,1.0,4.0,0.0 +2016-05-18,135197,2.0,4.0,0.0 +2016-05-19,131924,3.0,4.0,0.0 +2016-05-20,122504,4.0,4.0,0.0 +2016-05-21,79192,5.0,4.0,0.0 +2016-05-22,84851,6.0,4.0,0.0 +2016-05-23,127438,0.0,4.0,0.0 +2016-05-24,133972,1.0,4.0,0.0 +2016-05-25,131697,2.0,4.0,0.0 +2016-05-26,126174,3.0,4.0,0.0 +2016-05-27,117773,4.0,4.0,0.0 +2016-05-28,74793,5.0,4.0,0.0 +2016-05-29,79262,6.0,4.0,0.0 +2016-05-30,113390,0.0,4.0,1.0 +2016-05-31,129636,1.0,4.0,0.0 +2016-06-01,129838,2.0,5.0,0.0 +2016-06-02,127650,3.0,5.0,0.0 +2016-06-03,119107,4.0,5.0,0.0 +2016-06-04,76582,5.0,5.0,0.0 +2016-06-05,80829,6.0,5.0,0.0 +2016-06-06,123175,0.0,5.0,0.0 +2016-06-07,128655,1.0,5.0,0.0 +2016-06-08,126728,2.0,5.0,0.0 +2016-06-09,116963,3.0,5.0,0.0 +2016-06-10,108602,4.0,5.0,0.0 +2016-06-11,73541,5.0,5.0,0.0 +2016-06-12,82245,6.0,5.0,0.0 +2016-06-13,119977,0.0,5.0,0.0 +2016-06-14,125678,1.0,5.0,0.0 +2016-06-15,125977,2.0,5.0,0.0 +2016-06-16,122900,3.0,5.0,0.0 +2016-06-17,113905,4.0,5.0,0.0 +2016-06-18,71738,5.0,5.0,0.0 +2016-06-19,74376,6.0,5.0,0.0 +2016-06-20,93499,0.0,5.0,0.0 +2016-06-21,124257,1.0,5.0,0.0 +2016-06-22,122793,2.0,5.0,0.0 +2016-06-23,120902,3.0,5.0,0.0 +2016-06-24,108118,4.0,5.0,0.0 +2016-06-25,69170,5.0,5.0,0.0 +2016-06-26,72480,6.0,5.0,0.0 +2016-06-27,115501,0.0,5.0,0.0 +2016-06-28,121523,1.0,5.0,0.0 +2016-06-29,121456,2.0,5.0,0.0 +2016-06-30,119093,3.0,5.0,0.0 +2016-07-01,107813,4.0,6.0,0.0 +2016-07-02,66427,5.0,6.0,0.0 +2016-07-03,68168,6.0,6.0,0.0 +2016-07-04,101448,0.0,6.0,1.0 +2016-07-05,114130,1.0,6.0,0.0 +2016-07-06,118196,2.0,6.0,0.0 +2016-07-07,116360,3.0,6.0,0.0 +2016-07-08,109588,4.0,6.0,0.0 +2016-07-09,68949,5.0,6.0,0.0 +2016-07-10,71387,6.0,6.0,0.0 +2016-07-11,116802,0.0,6.0,0.0 +2016-07-12,119864,1.0,6.0,0.0 +2016-07-13,120468,2.0,6.0,0.0 +2016-07-14,117523,3.0,6.0,0.0 +2016-07-15,108681,4.0,6.0,0.0 +2016-07-16,67189,5.0,6.0,0.0 +2016-07-17,71085,6.0,6.0,0.0 +2016-07-18,116616,0.0,6.0,0.0 +2016-07-19,121000,1.0,6.0,0.0 +2016-07-20,119165,2.0,6.0,0.0 +2016-07-21,117941,3.0,6.0,0.0 +2016-07-22,110570,4.0,6.0,0.0 +2016-07-23,68398,5.0,6.0,0.0 +2016-07-24,71980,6.0,6.0,0.0 +2016-07-25,116361,0.0,6.0,0.0 +2016-07-26,120986,1.0,6.0,0.0 +2016-07-27,120932,2.0,6.0,0.0 +2016-07-28,118101,3.0,6.0,0.0 +2016-07-29,110240,4.0,6.0,0.0 +2016-07-30,69022,5.0,6.0,0.0 +2016-07-31,71959,6.0,6.0,0.0 +2016-08-01,114920,0.0,7.0,0.0 +2016-08-02,120783,1.0,7.0,0.0 +2016-08-03,119825,2.0,7.0,0.0 +2016-08-04,117712,3.0,7.0,0.0 +2016-08-05,109966,4.0,7.0,0.0 +2016-08-06,67755,5.0,7.0,0.0 +2016-08-07,70693,6.0,7.0,0.0 +2016-08-08,115440,0.0,7.0,0.0 +2016-08-09,118682,1.0,7.0,0.0 +2016-08-10,119555,2.0,7.0,0.0 +2016-08-11,117924,3.0,7.0,0.0 +2016-08-12,110083,4.0,7.0,0.0 +2016-08-13,68028,5.0,7.0,0.0 +2016-08-14,69705,6.0,7.0,0.0 +2016-08-15,109543,0.0,7.0,0.0 +2016-08-16,120896,1.0,7.0,0.0 +2016-08-17,121107,2.0,7.0,0.0 +2016-08-18,119516,3.0,7.0,0.0 +2016-08-19,112999,4.0,7.0,0.0 +2016-08-20,71603,5.0,7.0,0.0 +2016-08-21,74724,6.0,7.0,0.0 +2016-08-22,120374,0.0,7.0,0.0 +2016-08-23,125253,1.0,7.0,0.0 +2016-08-24,124546,2.0,7.0,0.0 +2016-08-25,123134,3.0,7.0,0.0 +2016-08-26,115443,4.0,7.0,0.0 +2016-08-27,73510,5.0,7.0,0.0 +2016-08-28,77456,6.0,7.0,0.0 +2016-08-29,122370,0.0,7.0,0.0 +2016-08-30,128081,1.0,7.0,0.0 +2016-08-31,127520,2.0,7.0,0.0 +2016-09-01,124829,3.0,8.0,0.0 +2016-09-02,115659,4.0,8.0,0.0 +2016-09-03,71772,5.0,8.0,0.0 +2016-09-04,76164,6.0,8.0,0.0 +2016-09-05,109751,0.0,8.0,1.0 +2016-09-06,127745,1.0,8.0,0.0 +2016-09-07,128145,2.0,8.0,0.0 +2016-09-08,127996,3.0,8.0,0.0 +2016-09-09,120314,4.0,8.0,0.0 +2016-09-10,77719,5.0,8.0,0.0 +2016-09-11,81649,6.0,8.0,0.0 +2016-09-12,127325,0.0,8.0,0.0 +2016-09-13,131451,1.0,8.0,0.0 +2016-09-14,128826,2.0,8.0,0.0 +2016-09-15,120041,3.0,8.0,0.0 +2016-09-16,113989,4.0,8.0,0.0 +2016-09-17,80862,5.0,8.0,0.0 +2016-09-18,91832,6.0,8.0,0.0 +2016-09-19,131871,0.0,8.0,0.0 +2016-09-20,138590,1.0,8.0,0.0 +2016-09-21,138146,2.0,8.0,0.0 +2016-09-22,136479,3.0,8.0,0.0 +2016-09-23,127803,4.0,8.0,0.0 +2016-09-24,81861,5.0,8.0,0.0 +2016-09-25,86861,6.0,8.0,0.0 +2016-09-26,137176,0.0,8.0,0.0 +2016-09-27,139433,1.0,8.0,0.0 +2016-09-28,140373,2.0,8.0,0.0 +2016-09-29,138011,3.0,8.0,0.0 +2016-09-30,127044,4.0,8.0,0.0 +2016-10-01,78726,5.0,9.0,0.0 +2016-10-02,82758,6.0,9.0,0.0 +2016-10-03,125866,0.0,9.0,0.0 +2016-10-04,132182,1.0,9.0,0.0 +2016-10-05,131995,2.0,9.0,0.0 +2016-10-06,132759,3.0,9.0,0.0 +2016-10-07,124588,4.0,9.0,0.0 +2016-10-08,90358,5.0,9.0,0.0 +2016-10-09,96542,6.0,9.0,0.0 +2016-10-10,135850,0.0,9.0,1.0 +2016-10-11,144073,1.0,9.0,0.0 +2016-10-12,143248,2.0,9.0,0.0 +2016-10-13,144176,3.0,9.0,0.0 +2016-10-14,134423,4.0,9.0,0.0 +2016-10-15,88312,5.0,9.0,0.0 +2016-10-16,94694,6.0,9.0,0.0 +2016-10-17,140981,0.0,9.0,0.0 +2016-10-18,150758,1.0,9.0,0.0 +2016-10-19,148760,2.0,9.0,0.0 +2016-10-20,145021,3.0,9.0,0.0 +2016-10-21,123991,4.0,9.0,0.0 +2016-10-22,90117,5.0,9.0,0.0 +2016-10-23,95498,6.0,9.0,0.0 +2016-10-24,146136,0.0,9.0,0.0 +2016-10-25,150283,1.0,9.0,0.0 +2016-10-26,149086,2.0,9.0,0.0 +2016-10-27,146600,3.0,9.0,0.0 +2016-10-28,134101,4.0,9.0,0.0 +2016-10-29,85873,5.0,9.0,0.0 +2016-10-30,91905,6.0,9.0,0.0 +2016-10-31,141022,0.0,9.0,0.0 +2016-11-01,142467,1.0,10.0,0.0 +2016-11-02,148404,2.0,10.0,0.0 +2016-11-03,149540,3.0,10.0,0.0 +2016-11-04,138040,4.0,10.0,0.0 +2016-11-05,93128,5.0,10.0,0.0 +2016-11-06,99820,6.0,10.0,0.0 +2016-11-07,150788,0.0,10.0,0.0 +2016-11-08,150053,1.0,10.0,0.0 +2016-11-09,140674,2.0,10.0,0.0 +2016-11-10,146301,3.0,10.0,0.0 +2016-11-11,132609,4.0,10.0,1.0 +2016-11-12,93843,5.0,10.0,0.0 +2016-11-13,100633,6.0,10.0,0.0 +2016-11-14,150935,0.0,10.0,0.0 +2016-11-15,156066,1.0,10.0,0.0 +2016-11-16,156273,2.0,10.0,0.0 +2016-11-17,154473,3.0,10.0,0.0 +2016-11-18,144040,4.0,10.0,0.0 +2016-11-19,95853,5.0,10.0,0.0 +2016-11-20,103220,6.0,10.0,0.0 +2016-11-21,154232,0.0,10.0,0.0 +2016-11-22,156131,1.0,10.0,0.0 +2016-11-23,149146,2.0,10.0,0.0 +2016-11-24,133080,3.0,10.0,1.0 +2016-11-25,120535,4.0,10.0,0.0 +2016-11-26,90022,5.0,10.0,0.0 +2016-11-27,100373,6.0,10.0,0.0 +2016-11-28,154971,0.0,10.0,0.0 +2016-11-29,161691,1.0,10.0,0.0 +2016-11-30,159450,2.0,10.0,0.0 +2016-12-01,157196,3.0,11.0,0.0 +2016-12-02,147743,4.0,11.0,0.0 +2016-12-03,98102,5.0,11.0,0.0 +2016-12-04,104400,6.0,11.0,0.0 +2016-12-05,156268,0.0,11.0,0.0 +2016-12-06,158169,1.0,11.0,0.0 +2016-12-07,158758,2.0,11.0,0.0 +2016-12-08,152258,3.0,11.0,0.0 +2016-12-09,142222,4.0,11.0,0.0 +2016-12-10,95665,5.0,11.0,0.0 +2016-12-11,100707,6.0,11.0,0.0 +2016-12-12,148783,0.0,11.0,0.0 +2016-12-13,152591,1.0,11.0,0.0 +2016-12-14,149908,2.0,11.0,0.0 +2016-12-15,145085,3.0,11.0,0.0 +2016-12-16,131580,4.0,11.0,0.0 +2016-12-17,84443,5.0,11.0,0.0 +2016-12-18,88845,6.0,11.0,0.0 +2016-12-19,134794,0.0,11.0,0.0 +2016-12-20,136427,1.0,11.0,0.0 +2016-12-21,131770,2.0,11.0,0.0 +2016-12-22,124751,3.0,11.0,0.0 +2016-12-23,105776,4.0,11.0,0.0 +2016-12-24,66740,5.0,11.0,0.0 +2016-12-25,60535,6.0,11.0,0.0 +2016-12-26,86775,0.0,11.0,1.0 +2016-12-27,102574,1.0,11.0,0.0 +2016-12-28,106393,2.0,11.0,0.0 +2016-12-29,105158,3.0,11.0,0.0 +2016-12-30,98098,4.0,11.0,0.0 +2016-12-31,64696,5.0,11.0,0.0 +2017-01-01,59005,6.0,0.0,0.0 +2017-01-02,95818,0.0,0.0,1.0 +2017-01-03,127728,1.0,0.0,0.0 +2017-01-04,133210,2.0,0.0,0.0 +2017-01-05,128376,3.0,0.0,0.0 +2017-01-06,125230,4.0,0.0,0.0 +2017-01-07,71521,5.0,0.0,0.0 +2017-01-08,94736,6.0,0.0,0.0 +2017-01-09,140861,0.0,0.0,0.0 +2017-01-10,145521,1.0,0.0,0.0 +2017-01-11,145604,2.0,0.0,0.0 +2017-01-12,144985,3.0,0.0,0.0 +2017-01-13,135657,4.0,0.0,0.0 +2017-01-14,91791,5.0,0.0,0.0 +2017-01-15,97570,6.0,0.0,0.0 +2017-01-16,140046,0.0,0.0,1.0 +2017-01-17,151455,1.0,0.0,0.0 +2017-01-18,151122,2.0,0.0,0.0 +2017-01-19,149733,3.0,0.0,0.0 +2017-01-20,140506,4.0,0.0,0.0 +2017-01-21,97774,5.0,0.0,0.0 +2017-01-22,106965,6.0,0.0,0.0 +2017-01-23,147843,0.0,0.0,0.0 +2017-01-24,149039,1.0,0.0,0.0 +2017-01-25,144802,2.0,0.0,0.0 +2017-01-26,138288,3.0,0.0,0.0 +2017-01-27,127738,4.0,0.0,0.0 +2017-01-28,88164,5.0,0.0,0.0 +2017-01-29,92052,6.0,0.0,0.0 +2017-01-30,137919,0.0,0.0,0.0 +2017-01-31,143069,1.0,0.0,0.0 +2017-02-01,143529,2.0,1.0,0.0 +2017-02-02,145011,3.0,1.0,0.0 +2017-02-03,139875,4.0,1.0,0.0 +2017-02-04,101218,5.0,1.0,0.0 +2017-02-05,104585,6.0,1.0,0.0 +2017-02-06,152808,0.0,1.0,0.0 +2017-02-07,161273,1.0,1.0,0.0 +2017-02-08,162144,2.0,1.0,0.0 +2017-02-09,159440,3.0,1.0,0.0 +2017-02-10,149755,4.0,1.0,0.0 +2017-02-11,100746,5.0,1.0,0.0 +2017-02-12,106434,6.0,1.0,0.0 +2017-02-13,160474,0.0,1.0,0.0 +2017-02-14,159982,1.0,1.0,0.0 +2017-02-15,161897,2.0,1.0,0.0 +2017-02-16,164364,3.0,1.0,0.0 +2017-02-17,153956,4.0,1.0,0.0 +2017-02-18,104661,5.0,1.0,0.0 +2017-02-19,109589,6.0,1.0,0.0 +2017-02-20,158043,0.0,1.0,1.0 +2017-02-21,170265,1.0,1.0,0.0 +2017-02-22,170559,2.0,1.0,0.0 +2017-02-23,163711,3.0,1.0,0.0 +2017-02-24,154537,4.0,1.0,0.0 +2017-02-25,106039,5.0,1.0,0.0 +2017-02-26,111816,6.0,1.0,0.0 +2017-02-27,163119,0.0,1.0,0.0 +2017-02-28,165643,1.0,1.0,0.0 +2017-03-01,167480,2.0,2.0,0.0 +2017-03-02,168730,3.0,2.0,0.0 +2017-03-03,158171,4.0,2.0,0.0 +2017-03-04,106739,5.0,2.0,0.0 +2017-03-05,114464,6.0,2.0,0.0 +2017-03-06,169538,0.0,2.0,0.0 +2017-03-07,173736,1.0,2.0,0.0 +2017-03-08,168734,2.0,2.0,0.0 +2017-03-09,171452,3.0,2.0,0.0 +2017-03-10,159470,4.0,2.0,0.0 +2017-03-11,107371,5.0,2.0,0.0 +2017-03-12,114907,6.0,2.0,0.0 +2017-03-13,170043,0.0,2.0,0.0 +2017-03-14,174748,1.0,2.0,0.0 +2017-03-15,171274,2.0,2.0,0.0 +2017-03-16,172067,3.0,2.0,0.0 +2017-03-17,159312,4.0,2.0,0.0 +2017-03-18,107141,5.0,2.0,0.0 +2017-03-19,116705,6.0,2.0,0.0 +2017-03-20,173053,0.0,2.0,0.0 +2017-03-21,179270,1.0,2.0,0.0 +2017-03-22,178776,2.0,2.0,0.0 +2017-03-23,175353,3.0,2.0,0.0 +2017-03-24,155802,4.0,2.0,0.0 +2017-03-25,107862,5.0,2.0,0.0 +2017-03-26,114867,6.0,2.0,0.0 +2017-03-27,174989,0.0,2.0,0.0 +2017-03-28,177936,1.0,2.0,0.0 +2017-03-29,177053,2.0,2.0,0.0 +2017-03-30,174951,3.0,2.0,0.0 +2017-03-31,161692,4.0,2.0,0.0 +2017-04-01,111982,5.0,3.0,0.0 +2017-04-02,109185,6.0,3.0,0.0 +2017-04-03,159117,0.0,3.0,0.0 +2017-04-04,162855,1.0,3.0,0.0 +2017-04-05,176611,2.0,3.0,0.0 +2017-04-06,174519,3.0,3.0,0.0 +2017-04-07,161085,4.0,3.0,0.0 +2017-04-08,106383,5.0,3.0,0.0 +2017-04-09,112315,6.0,3.0,0.0 +2017-04-10,169584,0.0,3.0,0.0 +2017-04-11,171826,1.0,3.0,0.0 +2017-04-12,168847,2.0,3.0,0.0 +2017-04-13,160786,3.0,3.0,0.0 +2017-04-14,137040,4.0,3.0,0.0 +2017-04-15,100190,5.0,3.0,0.0 +2017-04-16,100898,6.0,3.0,0.0 +2017-04-17,152066,0.0,3.0,0.0 +2017-04-18,174171,1.0,3.0,0.0 +2017-04-19,175620,2.0,3.0,0.0 +2017-04-20,173856,3.0,3.0,0.0 +2017-04-21,160574,4.0,3.0,0.0 +2017-04-22,110084,5.0,3.0,0.0 +2017-04-23,117159,6.0,3.0,0.0 +2017-04-24,174875,0.0,3.0,0.0 +2017-04-25,179750,1.0,3.0,0.0 +2017-04-26,179115,2.0,3.0,0.0 +2017-04-27,172230,3.0,3.0,0.0 +2017-04-28,157630,4.0,3.0,0.0 +2017-04-29,99513,5.0,3.0,0.0 +2017-04-30,100849,6.0,3.0,0.0 +2017-05-01,137413,0.0,4.0,0.0 +2017-05-02,169970,1.0,4.0,0.0 +2017-05-03,173007,2.0,4.0,0.0 +2017-05-04,171814,3.0,4.0,0.0 +2017-05-05,158556,4.0,4.0,0.0 +2017-05-06,104891,5.0,4.0,0.0 +2017-05-07,111184,6.0,4.0,0.0 +2017-05-08,167207,0.0,4.0,0.0 +2017-05-09,174139,1.0,4.0,0.0 +2017-05-10,173376,2.0,4.0,0.0 +2017-05-11,170399,3.0,4.0,0.0 +2017-05-12,159003,4.0,4.0,0.0 +2017-05-13,104441,5.0,4.0,0.0 +2017-05-14,108658,6.0,4.0,0.0 +2017-05-15,169555,0.0,4.0,0.0 +2017-05-16,174468,1.0,4.0,0.0 +2017-05-17,172630,2.0,4.0,0.0 +2017-05-18,168885,3.0,4.0,0.0 +2017-05-19,158328,4.0,4.0,0.0 +2017-05-20,101883,5.0,4.0,0.0 +2017-05-21,108279,6.0,4.0,0.0 +2017-05-22,167274,0.0,4.0,0.0 +2017-05-23,173357,1.0,4.0,0.0 +2017-05-24,170350,2.0,4.0,0.0 +2017-05-25,157737,3.0,4.0,0.0 +2017-05-26,150028,4.0,4.0,0.0 +2017-05-27,103856,5.0,4.0,0.0 +2017-05-28,99612,6.0,4.0,0.0 +2017-05-29,138303,0.0,4.0,1.0 +2017-05-30,159403,1.0,4.0,0.0 +2017-05-31,167107,2.0,4.0,0.0 +2017-06-01,165586,3.0,5.0,0.0 +2017-06-02,154671,4.0,5.0,0.0 +2017-06-03,99082,5.0,5.0,0.0 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/helper.py b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/helper.py new file mode 100644 index 000000000..5b78e0ba4 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/helper.py @@ -0,0 +1,183 @@ +import pandas as pd +from azureml.core import Environment +from azureml.core.conda_dependencies import CondaDependencies +from azureml.train.estimator import Estimator +from azureml.core.run import Run +from azureml.automl.core.shared import constants + + +def split_fraction_by_grain(df, fraction, time_column_name, grain_column_names=None): + if not grain_column_names: + df["tmp_grain_column"] = "grain" + grain_column_names = ["tmp_grain_column"] + + """Group df by grain and split on last n rows for each group.""" + df_grouped = df.sort_values(time_column_name).groupby( + grain_column_names, group_keys=False + ) + + df_head = df_grouped.apply( + lambda dfg: dfg.iloc[: -int(len(dfg) * fraction)] if fraction > 0 else dfg + ) + + df_tail = df_grouped.apply( + lambda dfg: dfg.iloc[-int(len(dfg) * fraction) :] if fraction > 0 else dfg[:0] + ) + + if "tmp_grain_column" in grain_column_names: + for df2 in (df, df_head, df_tail): + df2.drop("tmp_grain_column", axis=1, inplace=True) + + grain_column_names.remove("tmp_grain_column") + + return df_head, df_tail + + +def split_full_for_forecasting( + df, time_column_name, grain_column_names=None, test_split=0.2 +): + index_name = df.index.name + + # Assumes that there isn't already a column called tmpindex + + df["tmpindex"] = df.index + + train_df, test_df = split_fraction_by_grain( + df, test_split, time_column_name, grain_column_names + ) + + train_df = train_df.set_index("tmpindex") + train_df.index.name = index_name + + test_df = test_df.set_index("tmpindex") + test_df.index.name = index_name + + df.drop("tmpindex", axis=1, inplace=True) + + return train_df, test_df + + +def get_result_df(remote_run): + children = list(remote_run.get_children(recursive=True)) + summary_df = pd.DataFrame( + index=["run_id", "run_algorithm", "primary_metric", "Score"] + ) + goal_minimize = False + for run in children: + if ( + run.get_status().lower() == constants.RunState.COMPLETE_RUN + and "run_algorithm" in run.properties + and "score" in run.properties + ): + # We only count in the completed child runs. + summary_df[run.id] = [ + run.id, + run.properties["run_algorithm"], + run.properties["primary_metric"], + float(run.properties["score"]), + ] + if "goal" in run.properties: + goal_minimize = run.properties["goal"].split("_")[-1] == "min" + + summary_df = summary_df.T.sort_values( + "Score", ascending=goal_minimize + ).drop_duplicates(["run_algorithm"]) + summary_df = summary_df.set_index("run_algorithm") + return summary_df + + +def run_inference( + test_experiment, + compute_target, + script_folder, + train_run, + test_dataset, + lookback_dataset, + max_horizon, + target_column_name, + time_column_name, + freq, +): + model_base_name = "model.pkl" + if "model_data_location" in train_run.properties: + model_location = train_run.properties["model_data_location"] + _, model_base_name = model_location.rsplit("/", 1) + train_run.download_file( + "outputs/{}".format(model_base_name), "inference/{}".format(model_base_name) + ) + train_run.download_file("outputs/conda_env_v_1_0_0.yml", "inference/condafile.yml") + + inference_env = Environment("myenv") + inference_env.docker.enabled = True + inference_env.python.conda_dependencies = CondaDependencies( + conda_dependencies_file_path="inference/condafile.yml" + ) + + est = Estimator( + source_directory=script_folder, + entry_script="infer.py", + script_params={ + "--max_horizon": max_horizon, + "--target_column_name": target_column_name, + "--time_column_name": time_column_name, + "--frequency": freq, + "--model_path": model_base_name, + }, + inputs=[ + test_dataset.as_named_input("test_data"), + lookback_dataset.as_named_input("lookback_data"), + ], + compute_target=compute_target, + environment_definition=inference_env, + ) + + run = test_experiment.submit( + est, + tags={ + "training_run_id": train_run.id, + "run_algorithm": train_run.properties["run_algorithm"], + "valid_score": train_run.properties["score"], + "primary_metric": train_run.properties["primary_metric"], + }, + ) + + run.log("run_algorithm", run.tags["run_algorithm"]) + return run + + +def run_multiple_inferences( + summary_df, + train_experiment, + test_experiment, + compute_target, + script_folder, + test_dataset, + lookback_dataset, + max_horizon, + target_column_name, + time_column_name, + freq, +): + for run_name, run_summary in summary_df.iterrows(): + print(run_name) + print(run_summary) + run_id = run_summary.run_id + train_run = Run(train_experiment, run_id) + + test_run = run_inference( + test_experiment, + compute_target, + script_folder, + train_run, + test_dataset, + lookback_dataset, + max_horizon, + target_column_name, + time_column_name, + freq, + ) + + print(test_run) + summary_df.loc[summary_df.run_id == run_id, "test_run_id"] = test_run.id + + return summary_df diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/infer.py b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/infer.py new file mode 100644 index 000000000..7b2f1eee4 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-github-dau/infer.py @@ -0,0 +1,386 @@ +import argparse +import os + +import numpy as np +import pandas as pd + +from pandas.tseries.frequencies import to_offset +from sklearn.externals import joblib +from sklearn.metrics import mean_absolute_error, mean_squared_error + +from azureml.automl.runtime.shared.score import scoring, constants +from azureml.core import Run + +try: + import torch + + _torch_present = True +except ImportError: + _torch_present = False + + +def align_outputs( + y_predicted, + X_trans, + X_test, + y_test, + predicted_column_name="predicted", + horizon_colname="horizon_origin", +): + """ + Demonstrates how to get the output aligned to the inputs + using pandas indexes. Helps understand what happened if + the output's shape differs from the input shape, or if + the data got re-sorted by time and grain during forecasting. + + Typical causes of misalignment are: + * we predicted some periods that were missing in actuals -> drop from eval + * model was asked to predict past max_horizon -> increase max horizon + * data at start of X_test was needed for lags -> provide previous periods + """ + if horizon_colname in X_trans: + df_fcst = pd.DataFrame( + { + predicted_column_name: y_predicted, + horizon_colname: X_trans[horizon_colname], + } + ) + else: + df_fcst = pd.DataFrame({predicted_column_name: y_predicted}) + + # y and X outputs are aligned by forecast() function contract + df_fcst.index = X_trans.index + + # align original X_test to y_test + X_test_full = X_test.copy() + X_test_full[target_column_name] = y_test + + # X_test_full's index does not include origin, so reset for merge + df_fcst.reset_index(inplace=True) + X_test_full = X_test_full.reset_index().drop(columns="index") + together = df_fcst.merge(X_test_full, how="right") + + # drop rows where prediction or actuals are nan + # happens because of missing actuals + # or at edges of time due to lags/rolling windows + clean = together[ + together[[target_column_name, predicted_column_name]].notnull().all(axis=1) + ] + return clean + + +def do_rolling_forecast_with_lookback( + fitted_model, X_test, y_test, max_horizon, X_lookback, y_lookback, freq="D" +): + """ + Produce forecasts on a rolling origin over the given test set. + + Each iteration makes a forecast for the next 'max_horizon' periods + with respect to the current origin, then advances the origin by the + horizon time duration. The prediction context for each forecast is set so + that the forecaster uses the actual target values prior to the current + origin time for constructing lag features. + + This function returns a concatenated DataFrame of rolling forecasts. + """ + print("Using lookback of size: ", y_lookback.size) + df_list = [] + origin_time = X_test[time_column_name].min() + X = X_lookback.append(X_test) + y = np.concatenate((y_lookback, y_test), axis=0) + while origin_time <= X_test[time_column_name].max(): + # Set the horizon time - end date of the forecast + horizon_time = origin_time + max_horizon * to_offset(freq) + + # Extract test data from an expanding window up-to the horizon + expand_wind = X[time_column_name] < horizon_time + X_test_expand = X[expand_wind] + y_query_expand = np.zeros(len(X_test_expand)).astype(np.float) + y_query_expand.fill(np.NaN) + + if origin_time != X[time_column_name].min(): + # Set the context by including actuals up-to the origin time + test_context_expand_wind = X[time_column_name] < origin_time + context_expand_wind = X_test_expand[time_column_name] < origin_time + y_query_expand[context_expand_wind] = y[test_context_expand_wind] + + # Print some debug info + print( + "Horizon_time:", + horizon_time, + " origin_time: ", + origin_time, + " max_horizon: ", + max_horizon, + " freq: ", + freq, + ) + print("expand_wind: ", expand_wind) + print("y_query_expand") + print(y_query_expand) + print("X_test") + print(X) + print("X_test_expand") + print(X_test_expand) + print("Type of X_test_expand: ", type(X_test_expand)) + print("Type of y_query_expand: ", type(y_query_expand)) + + print("y_query_expand") + print(y_query_expand) + + # Make a forecast out to the maximum horizon + # y_fcst, X_trans = y_query_expand, X_test_expand + y_fcst, X_trans = fitted_model.forecast(X_test_expand, y_query_expand) + + print("y_fcst") + print(y_fcst) + + # Align forecast with test set for dates within + # the current rolling window + trans_tindex = X_trans.index.get_level_values(time_column_name) + trans_roll_wind = (trans_tindex >= origin_time) & (trans_tindex < horizon_time) + test_roll_wind = expand_wind & (X[time_column_name] >= origin_time) + df_list.append( + align_outputs( + y_fcst[trans_roll_wind], + X_trans[trans_roll_wind], + X[test_roll_wind], + y[test_roll_wind], + ) + ) + + # Advance the origin time + origin_time = horizon_time + + return pd.concat(df_list, ignore_index=True) + + +def do_rolling_forecast(fitted_model, X_test, y_test, max_horizon, freq="D"): + """ + Produce forecasts on a rolling origin over the given test set. + + Each iteration makes a forecast for the next 'max_horizon' periods + with respect to the current origin, then advances the origin by the + horizon time duration. The prediction context for each forecast is set so + that the forecaster uses the actual target values prior to the current + origin time for constructing lag features. + + This function returns a concatenated DataFrame of rolling forecasts. + """ + df_list = [] + origin_time = X_test[time_column_name].min() + while origin_time <= X_test[time_column_name].max(): + # Set the horizon time - end date of the forecast + horizon_time = origin_time + max_horizon * to_offset(freq) + + # Extract test data from an expanding window up-to the horizon + expand_wind = X_test[time_column_name] < horizon_time + X_test_expand = X_test[expand_wind] + y_query_expand = np.zeros(len(X_test_expand)).astype(np.float) + y_query_expand.fill(np.NaN) + + if origin_time != X_test[time_column_name].min(): + # Set the context by including actuals up-to the origin time + test_context_expand_wind = X_test[time_column_name] < origin_time + context_expand_wind = X_test_expand[time_column_name] < origin_time + y_query_expand[context_expand_wind] = y_test[test_context_expand_wind] + + # Print some debug info + print( + "Horizon_time:", + horizon_time, + " origin_time: ", + origin_time, + " max_horizon: ", + max_horizon, + " freq: ", + freq, + ) + print("expand_wind: ", expand_wind) + print("y_query_expand") + print(y_query_expand) + print("X_test") + print(X_test) + print("X_test_expand") + print(X_test_expand) + print("Type of X_test_expand: ", type(X_test_expand)) + print("Type of y_query_expand: ", type(y_query_expand)) + print("y_query_expand") + print(y_query_expand) + + # Make a forecast out to the maximum horizon + y_fcst, X_trans = fitted_model.forecast(X_test_expand, y_query_expand) + + print("y_fcst") + print(y_fcst) + + # Align forecast with test set for dates within the + # current rolling window + trans_tindex = X_trans.index.get_level_values(time_column_name) + trans_roll_wind = (trans_tindex >= origin_time) & (trans_tindex < horizon_time) + test_roll_wind = expand_wind & (X_test[time_column_name] >= origin_time) + df_list.append( + align_outputs( + y_fcst[trans_roll_wind], + X_trans[trans_roll_wind], + X_test[test_roll_wind], + y_test[test_roll_wind], + ) + ) + + # Advance the origin time + origin_time = horizon_time + + return pd.concat(df_list, ignore_index=True) + + +def APE(actual, pred): + """ + Calculate absolute percentage error. + Returns a vector of APE values with same length as actual/pred. + """ + return 100 * np.abs((actual - pred) / actual) + + +def MAPE(actual, pred): + """ + Calculate mean absolute percentage error. + Remove NA and values where actual is close to zero + """ + not_na = ~(np.isnan(actual) | np.isnan(pred)) + not_zero = ~np.isclose(actual, 0.0) + actual_safe = actual[not_na & not_zero] + pred_safe = pred[not_na & not_zero] + return np.mean(APE(actual_safe, pred_safe)) + + +def map_location_cuda(storage, loc): + return storage.cuda() + + +parser = argparse.ArgumentParser() +parser.add_argument( + "--max_horizon", + type=int, + dest="max_horizon", + default=10, + help="Max Horizon for forecasting", +) +parser.add_argument( + "--target_column_name", + type=str, + dest="target_column_name", + help="Target Column Name", +) +parser.add_argument( + "--time_column_name", type=str, dest="time_column_name", help="Time Column Name" +) +parser.add_argument( + "--frequency", type=str, dest="freq", help="Frequency of prediction" +) +parser.add_argument( + "--model_path", + type=str, + dest="model_path", + default="model.pkl", + help="Filename of model to be loaded", +) + +args = parser.parse_args() +max_horizon = args.max_horizon +target_column_name = args.target_column_name +time_column_name = args.time_column_name +freq = args.freq +model_path = args.model_path + +print("args passed are: ") +print(max_horizon) +print(target_column_name) +print(time_column_name) +print(freq) +print(model_path) + +run = Run.get_context() +# get input dataset by name +test_dataset = run.input_datasets["test_data"] +lookback_dataset = run.input_datasets["lookback_data"] + +grain_column_names = [] + +df = test_dataset.to_pandas_dataframe() + +print("Read df") +print(df) + +X_test_df = test_dataset.drop_columns(columns=[target_column_name]) +y_test_df = test_dataset.with_timestamp_columns(None).keep_columns( + columns=[target_column_name] +) + +X_lookback_df = lookback_dataset.drop_columns(columns=[target_column_name]) +y_lookback_df = lookback_dataset.with_timestamp_columns(None).keep_columns( + columns=[target_column_name] +) + +_, ext = os.path.splitext(model_path) +if ext == ".pt": + # Load the fc-tcn torch model. + assert _torch_present + if torch.cuda.is_available(): + map_location = map_location_cuda + else: + map_location = "cpu" + with open(model_path, "rb") as fh: + fitted_model = torch.load(fh, map_location=map_location) +else: + # Load the sklearn pipeline. + fitted_model = joblib.load(model_path) + +if hasattr(fitted_model, "get_lookback"): + lookback = fitted_model.get_lookback() + df_all = do_rolling_forecast_with_lookback( + fitted_model, + X_test_df.to_pandas_dataframe(), + y_test_df.to_pandas_dataframe().values.T[0], + max_horizon, + X_lookback_df.to_pandas_dataframe()[-lookback:], + y_lookback_df.to_pandas_dataframe().values.T[0][-lookback:], + freq, + ) +else: + df_all = do_rolling_forecast( + fitted_model, + X_test_df.to_pandas_dataframe(), + y_test_df.to_pandas_dataframe().values.T[0], + max_horizon, + freq, + ) + +print(df_all) + +print("target values:::") +print(df_all[target_column_name]) +print("predicted values:::") +print(df_all["predicted"]) + +# Use the AutoML scoring module +regression_metrics = list(constants.REGRESSION_SCALAR_SET) +y_test = np.array(df_all[target_column_name]) +y_pred = np.array(df_all["predicted"]) +scores = scoring.score_regression(y_test, y_pred, regression_metrics) + +print("scores:") +print(scores) + +for key, value in scores.items(): + run.log(key, value) + +print("Simple forecasting model") +rmse = np.sqrt(mean_squared_error(df_all[target_column_name], df_all["predicted"])) +print("[Test Data] \nRoot Mean squared error: %.2f" % rmse) +mae = mean_absolute_error(df_all[target_column_name], df_all["predicted"]) +print("mean_absolute_error score: %.2f" % mae) +print("MAPE: %.2f" % MAPE(df_all[target_column_name], df_all["predicted"])) + +run.log("rmse", rmse) +run.log("mae", mae) diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/README.md b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/README.md new file mode 100644 index 000000000..735e348d4 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/README.md @@ -0,0 +1,94 @@ +--- +page_type: sample +languages: +- python +products: +- azure-machine-learning +description: Tutorial showing how to solve a complex machine learning time series forecasting problems at scale by using Azure Automated ML and Hierarchical time series accelerator. +--- + +## Microsoft Solution Accelerator: Hierachical Time Series Forecasting + +In most applications, customers have a need to understand their forecasts at a macro and micro level of the business. Whether that be predicting sales of products at different geographic locations, or understanding the expected workforce demand for different organizations at a company, the ability to train a machine learning model to intelligently forecast on hierarchy data is essential. + +This business pattern is common across a wide variety of industries and applicable to many real world use cases. Below are some examples of where the hierarchical time series pattern is useful. + +| Industry | Scenario | +|----------------|--------------------------------------------| +| *Restaurant Chain* | Building demand forecasting models across thousands of restaurants and several countries. | +| *Retail Organization* | Building workforce optimization models for thousands of stores. | +| *Retail Organization*| Price optimization models for hundreds of thousands of products available. | + + +### Technical Summary + +A hierarchical time series is a structure in which each of the unique series are arranged into a hierarchy based on dimensions such as geography, or product type. The table below shows an example of data whose unique attributes form a hierarchy. Our hierarchy is defined by the `product type` such as headphones or tablets, the `product category` which splits product types into accessories and devices, and the `region` the products are sold in. The table below demonstrates the first input of each unique series in the hierarchy. + +![data-table](./media/data-table.png) + +To further visualize this, the leaf levels of the hierarchy contain all the time series with unique combinations of attribute values. Each higher level in the hierarchy will consider one less dimension for defining the time series and will aggregate each set of `child nodes` from the lower level into a `parent node`. + +![hierachy-sample](./media/hierarchy-sample-ms.PNG) + +> **Note:** If no unique root level exists in the data, Automated Machine Learning will create a node `automl_top_level` for users to train or forecasts totals. + +## Prerequisites + +To use this solution accelerator, all you need is access to an [Azure subscription](https://azure.microsoft.com/free/) and an [Azure Machine Learning Workspace](https://docs.microsoft.com/azure/machine-learning/how-to-manage-workspace) that you'll create below. + +A basic understanding of Azure Machine Learning and hierarchical time series concepts will be helpful for understanding the solution. The following resources can help introduce you to these concepts: + +1. [Azure Machine Learning Overview](https://azure.microsoft.com/services/machine-learning/) +2. [Azure Machine Learning Tutorials](https://docs.microsoft.com/azure/machine-learning/tutorial-1st-experiment-sdk-setup) +3. [Azure Machine Learning Sample Notebooks on Github](https://github.com/Azure/azureml-examples/) +4. [Forecasting: Principles and Practice, Hierarchical time series](https://otexts.com/fpp2/hts.html) + +## Getting started + +### 1. Set up the Compute Instance +Please create a [Compute Instance](https://docs.microsoft.com/en-us/azure/machine-learning/concept-compute-instance#create) and clone the git repo to your workspace. + +### 2. Run the Notebook + +Once your environment is set up, go to JupyterLab and run the notebook auto-ml-hierarchical-timeseries.ipynb on Compute Instance you created. It would run through the steps outlined sequentially. By the end, you'll know how to train, score, and make predictions using the hierarchical time series model pattern on Azure Machine Learning. + +| Notebook | Description | +|----------------|--------------------------------------------| +| `auto-ml-forecasting-hierarchical-timeseries.ipynb`|Creates a pipeline to train machine learning models for the defined hierarchy and forecast at the desired hierarchy level using Automated ML. | + + +![Work Flow](./media/workflow.PNG) + +## Key Concepts + +### Automated Machine Learning + +[Automated Machine Learning](https://docs.microsoft.com/azure/machine-learning/concept-automated-ml) also referred to as automated ML or AutoML, is the process of automating the time consuming, iterative tasks of machine learning model development. It allows data scientists, analysts, and developers to build ML models with high scale, efficiency, and productivity all while sustaining model quality. + +### Pipelines + +[Pipelines](https://docs.microsoft.com/azure/machine-learning/concept-ml-pipelines) allow you to create workflows in your machine learning projects. These workflows have a number of benefits including speed, simplicity, repeatability, and modularity. + +### ParallelRunStep + +[ParallelRunStep](https://docs.microsoft.com/en-us/python/api/azureml-pipeline-steps/azureml.pipeline.steps.parallel_run_step.parallelrunstep?view=azure-ml-py) enables the parallel training of models and is commonly used for batch inferencing. This [document](https://docs.microsoft.com/azure/machine-learning/how-to-use-parallel-run-step) walks through some of the key concepts around ParallelRunStep. + +### Other Concepts + +In additional to ParallelRunStep, Pipelines and Automated Machine Learning, you'll also be working with the following concepts including [workspace](https://docs.microsoft.com/azure/machine-learning/concept-workspace), [datasets](https://docs.microsoft.com/azure/machine-learning/concept-data#datasets), [compute targets](https://docs.microsoft.com/azure/machine-learning/concept-compute-target#train), [python script steps](https://docs.microsoft.com/python/api/azureml-pipeline-steps/azureml.pipeline.steps.python_script_step.pythonscriptstep?view=azure-ml-py), and [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). + +## Contributing + +This project welcomes contributions and suggestions. To learn more visit the [contributing](CONTRIBUTING.md) section. + +Most contributions require you to agree to a Contributor License Agreement (CLA) +declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.ipynb index e2ab133f9..ebbf8b04b 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.ipynb @@ -1,639 +1,639 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Hierarchical Time Series - Automated ML\n", - "**_Generate hierarchical time series forecasts with Automated Machine Learning_**\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this notebook we are using a synthetic dataset portraying sales data to predict the the quantity of a vartiety of product skus across several states, stores, and product categories.\n", - "\n", - "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prerequisites\n", - "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1.0 Set up workspace, datastore, experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003526897 - } - }, - "outputs": [], - "source": [ - "import azureml.core\n", - "from azureml.core import Workspace, Datastore\n", - "import pandas as pd\n", - "\n", - "# Set up your workspace\n", - "ws = Workspace.from_config()\n", - "ws.get_details()\n", - "\n", - "# Set up your datastores\n", - "dstore = ws.get_default_datastore()\n", - "\n", - "output = {}\n", - "output[\"SDK version\"] = azureml.core.VERSION\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Default datastore name\"] = dstore.name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose an experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003540729 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Experiment\n", - "\n", - "experiment = Experiment(ws, \"automl-hts\")\n", - "\n", - "print(\"Experiment name: \" + experiment.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.0 Data\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "nteract": { - "transient": { - "deleting": false - } - } - }, - "source": [ - "### Upload local csv files to datastore\n", - "You can upload your train and inference csv files to the default datastore in your workspace. \n", - "\n", - "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", - "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore.datastore?view=azure-ml-py) documentation on how to access data from Datastore." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "datastore_path = \"hts-sample\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "datastore = ws.get_default_datastore()\n", - "datastore" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create the TabularDatasets \n", - "\n", - "Datasets in Azure Machine Learning are references to specific data in a Datastore. The data can be retrieved as a [TabularDatasets](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py). We will read in the data as a pandas DataFrame, upload to the data store and register them to your Workspace using ```register_pandas_dataframe``` so they can be called as an input into the training pipeline. We will use the inference dataset as part of the forecasting pipeline. The step need only be completed once." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007017296 - } - }, - "outputs": [], - "source": [ - "from azureml.data.dataset_factory import TabularDatasetFactory\n", - "\n", - "registered_train = TabularDatasetFactory.register_pandas_dataframe(\n", - " pd.read_csv(\"Data/hts-sample-train.csv\"),\n", - " target=(datastore, \"hts-sample\"),\n", - " name=\"hts-sales-train\",\n", - ")\n", - "registered_inference = TabularDatasetFactory.register_pandas_dataframe(\n", - " pd.read_csv(\"Data/hts-sample-test.csv\"),\n", - " target=(datastore, \"hts-sample\"),\n", - " name=\"hts-sales-test\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3.0 Build the training pipeline\n", - "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose a compute target\n", - "\n", - "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", - "\n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007037308 - } - }, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "\n", - "# Name your cluster\n", - "compute_name = \"hts-compute\"\n", - "\n", - "\n", - "if compute_name in ws.compute_targets:\n", - " compute_target = ws.compute_targets[compute_name]\n", - " if compute_target and type(compute_target) is AmlCompute:\n", - " print(\"Found compute target: \" + compute_name)\n", - "else:\n", - " print(\"Creating a new compute target...\")\n", - " provisioning_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_D16S_V3\", max_nodes=20\n", - " )\n", - " # Create the compute target\n", - " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", - "\n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min node count is provided it will use the scale settings for the cluster\n", - " compute_target.wait_for_completion(\n", - " show_output=True, min_node_count=None, timeout_in_minutes=20\n", - " )\n", - "\n", - " # For a more detailed view of current cluster status, use the 'status' property\n", - " print(compute_target.status.serialize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up training parameters\n", - "\n", - "This dictionary defines the AutoML and hierarchy settings. For this forecasting task we need to define several settings inncluding the name of the time column, the maximum forecast horizon, the hierarchy definition, and the level of the hierarchy at which to train.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **task** | forecasting |\n", - "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error |\n", - "| **blocked_models** | Blocked models won't be used by AutoML. |\n", - "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", - "| **label_column_name** | The name of the label column. |\n", - "| **forecast_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", - "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", - "| **enable_early_stopping** | Flag to enable early termination if the score is not improving in the short term. |\n", - "| **time_column_name** | The name of your time column. |\n", - "| **hierarchy_column_names** | The names of columns that define the hierarchical structure of the data from highest level to most granular. |\n", - "| **training_level** | The level of the hierarchy to be used for training models. |\n", - "| **enable_engineered_explanations** | Engineered feature explanations will be downloaded if enable_engineered_explanations flag is set to True. By default it is set to False to save storage space. |\n", - "| **time_series_id_column_name** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", - "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", - "| **pipeline_fetch_max_batch_size** | Determines how many pipelines (training algorithms) to fetch at a time for training, this helps reduce throttling when training at large scale. |\n", - "| **model_explainability** | Flag to disable explaining the best automated ML model at the end of all training iterations. The default is True and will block non-explainable models which may impact the forecast accuracy. For more information, see [Interpretability: model explanations in automated machine learning](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-machine-learning-interpretability-automl). |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007061544 - } - }, - "outputs": [], - "source": [ - "from azureml.train.automl.runtime._hts.hts_parameters import HTSTrainParameters\n", - "\n", - "model_explainability = True\n", - "\n", - "engineered_explanations = False\n", - "# Define your hierarchy. Adjust the settings below based on your dataset.\n", - "hierarchy = [\"state\", \"store_id\", \"product_category\", \"SKU\"]\n", - "training_level = \"SKU\"\n", - "\n", - "# Set your forecast parameters. Adjust the settings below based on your dataset.\n", - "time_column_name = \"date\"\n", - "label_column_name = \"quantity\"\n", - "forecast_horizon = 7\n", - "\n", - "\n", - "automl_settings = {\n", - " \"task\": \"forecasting\",\n", - " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", - " \"label_column_name\": label_column_name,\n", - " \"time_column_name\": time_column_name,\n", - " \"forecast_horizon\": forecast_horizon,\n", - " \"hierarchy_column_names\": hierarchy,\n", - " \"hierarchy_training_level\": training_level,\n", - " \"track_child_runs\": False,\n", - " \"pipeline_fetch_max_batch_size\": 15,\n", - " \"model_explainability\": model_explainability,\n", - " # The following settings are specific to this sample and should be adjusted according to your own needs.\n", - " \"iteration_timeout_minutes\": 10,\n", - " \"iterations\": 10,\n", - " \"n_cross_validations\": 2,\n", - "}\n", - "\n", - "hts_parameters = HTSTrainParameters(\n", - " automl_settings=automl_settings,\n", - " hierarchy_column_names=hierarchy,\n", - " training_level=training_level,\n", - " enable_engineered_explanations=engineered_explanations,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up hierarchy training pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Parallel run step is leveraged to train the hierarchy. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The `process_count_per_node` is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", - "\n", - "* **experiment:** The experiment used for training.\n", - "* **train_data:** The tabular dataset to be used as input to the training run.\n", - "* **node_count:** The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long.\n", - "* **process_count_per_node:** Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance.\n", - "* **train_pipeline_parameters:** The set of configuration parameters defined in the previous section. \n", - "\n", - "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", - "\n", - "\n", - "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", - " experiment=experiment,\n", - " train_data=registered_train,\n", - " compute_target=compute_target,\n", - " node_count=2,\n", - " process_count_per_node=8,\n", - " train_pipeline_parameters=hts_parameters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit the pipeline to run\n", - "Next we submit our pipeline to run. The whole training pipeline takes about 1h 11m using a Standard_D12_V2 VM with our current ParallelRunConfig setting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run = experiment.submit(training_pipeline)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the run status, if training_run is in completed state, continue to forecasting. If training_run is in another state, check the portal for failures." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### [Optional] Get the explanations\n", - "First we need to download the explanations to the local disk." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "if model_explainability:\n", - " expl_output = training_run.get_pipeline_output(\"explanations\")\n", - " expl_output.download(\"training_explanations\")\n", - "else:\n", - " print(\n", - " \"Model explanations are available only if model_explainability is set to True.\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The explanations are downloaded to the \"training_explanations/azureml\" directory." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "if model_explainability:\n", - " explanations_dirrectory = os.listdir(\n", - " os.path.join(\"training_explanations\", \"azureml\")\n", - " )\n", - " if len(explanations_dirrectory) > 1:\n", - " print(\n", - " \"Warning! The directory contains multiple explanations, only the first one will be displayed.\"\n", - " )\n", - " print(\"The explanations are located at {}.\".format(explanations_dirrectory[0]))\n", - " # Now we will list all the explanations.\n", - " explanation_path = os.path.join(\n", - " \"training_explanations\",\n", - " \"azureml\",\n", - " explanations_dirrectory[0],\n", - " \"training_explanations\",\n", - " )\n", - " print(\"Available explanations\")\n", - " print(\"==============================\")\n", - " print(\"\\n\".join(os.listdir(explanation_path)))\n", - "else:\n", - " print(\n", - " \"Model explanations are available only if model_explainability is set to True.\"\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "View the explanations on \"state\" level." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import display\n", - "\n", - "explanation_type = \"raw\"\n", - "level = \"state\"\n", - "\n", - "if model_explainability:\n", - " display(\n", - " pd.read_csv(\n", - " os.path.join(explanation_path, \"{}_explanations_{}.csv\").format(\n", - " explanation_type, level\n", - " )\n", - " )\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5.0 Forecasting\n", - "For hierarchical forecasting we need to provide the HTSInferenceParameters object.\n", - "#### HTSInferenceParameters arguments\n", - "* **hierarchy_forecast_level:** The default level of the hierarchy to produce prediction/forecast on.\n", - "* **allocation_method:** \\[Optional] The disaggregation method to use if the hierarchy forecast level specified is below the define hierarchy training level.
(average historical proportions) 'average_historical_proportions'
(proportions of the historical averages) 'proportions_of_historical_average'\n", - "\n", - "#### get_many_models_batch_inference_steps arguments\n", - "* **experiment:** The experiment used for inference run.\n", - "* **inference_data:** The data to use for inferencing. It should be the same schema as used for training.\n", - "* **compute_target:** The compute target that runs the inference pipeline.\n", - "* **node_count:** The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku).\n", - "* **process_count_per_node:** The number of processes per node.\n", - "* **train_run_id:** \\[Optional] The run id of the hierarchy training, by default it is the latest successful training hts run in the experiment.\n", - "* **train_experiment_name:** \\[Optional] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline.\n", - "* **process_count_per_node:** \\[Optional] The number of processes per node, by default it's 4." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.train.automl.runtime._hts.hts_parameters import HTSInferenceParameters\n", - "\n", - "inference_parameters = HTSInferenceParameters(\n", - " hierarchy_forecast_level=\"store_id\", # The setting is specific to this dataset and should be changed based on your dataset.\n", - " allocation_method=\"proportions_of_historical_average\",\n", - ")\n", - "\n", - "steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", - " experiment=experiment,\n", - " inference_data=registered_inference,\n", - " compute_target=compute_target,\n", - " inference_pipeline_parameters=inference_parameters,\n", - " node_count=2,\n", - " process_count_per_node=8,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "inference_pipeline = Pipeline(ws, steps=steps)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inference_run = experiment.submit(inference_pipeline)\n", - "inference_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve results\n", - "\n", - "Forecast results can be retrieved through the following code. The prediction results summary and the actual predictions are downloaded the \"forecast_results\" folder" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "forecasts = inference_run.get_pipeline_output(\"forecasts\")\n", - "forecasts.download(\"forecast_results\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resbumit the Pipeline\n", - "\n", - "The inference pipeline can be submitted with different configurations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inference_run = experiment.submit(\n", - " inference_pipeline, pipeline_parameters={\"hierarchy_forecast_level\": \"state\"}\n", - ")\n", - "inference_run.wait_for_completion(show_output=False)" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hierarchical Time Series - Automated ML\n", + "**_Generate hierarchical time series forecasts with Automated Machine Learning_**\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this notebook we are using a synthetic dataset portraying sales data to predict the the quantity of a vartiety of product skus across several states, stores, and product categories.\n", + "\n", + "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prerequisites\n", + "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.0 Set up workspace, datastore, experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003526897 } - ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "categories": [ - "how-to-use-azureml", - "automated-machine-learning" - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" + }, + "outputs": [], + "source": [ + "import azureml.core\n", + "from azureml.core import Workspace, Datastore\n", + "import pandas as pd\n", + "\n", + "# Set up your workspace\n", + "ws = Workspace.from_config()\n", + "ws.get_details()\n", + "\n", + "# Set up your datastores\n", + "dstore = ws.get_default_datastore()\n", + "\n", + "output = {}\n", + "output[\"SDK version\"] = azureml.core.VERSION\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Default datastore name\"] = dstore.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose an experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003540729 + } + }, + "outputs": [], + "source": [ + "from azureml.core import Experiment\n", + "\n", + "experiment = Experiment(ws, \"automl-hts\")\n", + "\n", + "print(\"Experiment name: \" + experiment.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.0 Data\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "### Upload local csv files to datastore\n", + "You can upload your train and inference csv files to the default datastore in your workspace. \n", + "\n", + "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", + "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore.datastore?view=azure-ml-py) documentation on how to access data from Datastore." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore_path = \"hts-sample\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "datastore = ws.get_default_datastore()\n", + "datastore" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create the TabularDatasets \n", + "\n", + "Datasets in Azure Machine Learning are references to specific data in a Datastore. The data can be retrieved as a [TabularDatasets](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset?view=azure-ml-py). We will read in the data as a pandas DataFrame, upload to the data store and register them to your Workspace using ```register_pandas_dataframe``` so they can be called as an input into the training pipeline. We will use the inference dataset as part of the forecasting pipeline. The step need only be completed once." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007017296 + } + }, + "outputs": [], + "source": [ + "from azureml.data.dataset_factory import TabularDatasetFactory\n", + "\n", + "registered_train = TabularDatasetFactory.register_pandas_dataframe(\n", + " pd.read_csv(\"Data/hts-sample-train.csv\"),\n", + " target=(datastore, \"hts-sample\"),\n", + " name=\"hts-sales-train\",\n", + ")\n", + "registered_inference = TabularDatasetFactory.register_pandas_dataframe(\n", + " pd.read_csv(\"Data/hts-sample-test.csv\"),\n", + " target=(datastore, \"hts-sample\"),\n", + " name=\"hts-sales-test\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3.0 Build the training pipeline\n", + "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose a compute target\n", + "\n", + "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", + "\n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007037308 + } + }, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "\n", + "# Name your cluster\n", + "compute_name = \"hts-compute\"\n", + "\n", + "\n", + "if compute_name in ws.compute_targets:\n", + " compute_target = ws.compute_targets[compute_name]\n", + " if compute_target and type(compute_target) is AmlCompute:\n", + " print(\"Found compute target: \" + compute_name)\n", + "else:\n", + " print(\"Creating a new compute target...\")\n", + " provisioning_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_D16S_V3\", max_nodes=20\n", + " )\n", + " # Create the compute target\n", + " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", + "\n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min node count is provided it will use the scale settings for the cluster\n", + " compute_target.wait_for_completion(\n", + " show_output=True, min_node_count=None, timeout_in_minutes=20\n", + " )\n", + "\n", + " # For a more detailed view of current cluster status, use the 'status' property\n", + " print(compute_target.status.serialize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up training parameters\n", + "\n", + "This dictionary defines the AutoML and hierarchy settings. For this forecasting task we need to define several settings inncluding the name of the time column, the maximum forecast horizon, the hierarchy definition, and the level of the hierarchy at which to train.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **task** | forecasting |\n", + "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error |\n", + "| **blocked_models** | Blocked models won't be used by AutoML. |\n", + "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", + "| **label_column_name** | The name of the label column. |\n", + "| **forecast_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", + "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", + "| **enable_early_stopping** | Flag to enable early termination if the score is not improving in the short term. |\n", + "| **time_column_name** | The name of your time column. |\n", + "| **hierarchy_column_names** | The names of columns that define the hierarchical structure of the data from highest level to most granular. |\n", + "| **training_level** | The level of the hierarchy to be used for training models. |\n", + "| **enable_engineered_explanations** | Engineered feature explanations will be downloaded if enable_engineered_explanations flag is set to True. By default it is set to False to save storage space. |\n", + "| **time_series_id_column_name** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", + "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", + "| **pipeline_fetch_max_batch_size** | Determines how many pipelines (training algorithms) to fetch at a time for training, this helps reduce throttling when training at large scale. |\n", + "| **model_explainability** | Flag to disable explaining the best automated ML model at the end of all training iterations. The default is True and will block non-explainable models which may impact the forecast accuracy. For more information, see [Interpretability: model explanations in automated machine learning](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-machine-learning-interpretability-automl). |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007061544 } + }, + "outputs": [], + "source": [ + "from azureml.train.automl.runtime._hts.hts_parameters import HTSTrainParameters\n", + "\n", + "model_explainability = True\n", + "\n", + "engineered_explanations = False\n", + "# Define your hierarchy. Adjust the settings below based on your dataset.\n", + "hierarchy = [\"state\", \"store_id\", \"product_category\", \"SKU\"]\n", + "training_level = \"SKU\"\n", + "\n", + "# Set your forecast parameters. Adjust the settings below based on your dataset.\n", + "time_column_name = \"date\"\n", + "label_column_name = \"quantity\"\n", + "forecast_horizon = 7\n", + "\n", + "\n", + "automl_settings = {\n", + " \"task\": \"forecasting\",\n", + " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", + " \"label_column_name\": label_column_name,\n", + " \"time_column_name\": time_column_name,\n", + " \"forecast_horizon\": forecast_horizon,\n", + " \"hierarchy_column_names\": hierarchy,\n", + " \"hierarchy_training_level\": training_level,\n", + " \"track_child_runs\": False,\n", + " \"pipeline_fetch_max_batch_size\": 15,\n", + " \"model_explainability\": model_explainability,\n", + " # The following settings are specific to this sample and should be adjusted according to your own needs.\n", + " \"iteration_timeout_minutes\": 10,\n", + " \"iterations\": 10,\n", + " \"n_cross_validations\": 2,\n", + "}\n", + "\n", + "hts_parameters = HTSTrainParameters(\n", + " automl_settings=automl_settings,\n", + " hierarchy_column_names=hierarchy,\n", + " training_level=training_level,\n", + " enable_engineered_explanations=engineered_explanations,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up hierarchy training pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parallel run step is leveraged to train the hierarchy. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The `process_count_per_node` is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", + "\n", + "* **experiment:** The experiment used for training.\n", + "* **train_data:** The tabular dataset to be used as input to the training run.\n", + "* **node_count:** The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long.\n", + "* **process_count_per_node:** Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance.\n", + "* **train_pipeline_parameters:** The set of configuration parameters defined in the previous section. \n", + "\n", + "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", + "\n", + "\n", + "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", + " experiment=experiment,\n", + " train_data=registered_train,\n", + " compute_target=compute_target,\n", + " node_count=2,\n", + " process_count_per_node=8,\n", + " train_pipeline_parameters=hts_parameters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit the pipeline to run\n", + "Next we submit our pipeline to run. The whole training pipeline takes about 1h using a Standard_D16_V3 VM with our current ParallelRunConfig setting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run = experiment.submit(training_pipeline)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the run status, if training_run is in completed state, continue to forecasting. If training_run is in another state, check the portal for failures." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [Optional] Get the explanations\n", + "First we need to download the explanations to the local disk." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if model_explainability:\n", + " expl_output = training_run.get_pipeline_output(\"explanations\")\n", + " expl_output.download(\"training_explanations\")\n", + "else:\n", + " print(\n", + " \"Model explanations are available only if model_explainability is set to True.\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The explanations are downloaded to the \"training_explanations/azureml\" directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "if model_explainability:\n", + " explanations_dirrectory = os.listdir(\n", + " os.path.join(\"training_explanations\", \"azureml\")\n", + " )\n", + " if len(explanations_dirrectory) > 1:\n", + " print(\n", + " \"Warning! The directory contains multiple explanations, only the first one will be displayed.\"\n", + " )\n", + " print(\"The explanations are located at {}.\".format(explanations_dirrectory[0]))\n", + " # Now we will list all the explanations.\n", + " explanation_path = os.path.join(\n", + " \"training_explanations\",\n", + " \"azureml\",\n", + " explanations_dirrectory[0],\n", + " \"training_explanations\",\n", + " )\n", + " print(\"Available explanations\")\n", + " print(\"==============================\")\n", + " print(\"\\n\".join(os.listdir(explanation_path)))\n", + "else:\n", + " print(\n", + " \"Model explanations are available only if model_explainability is set to True.\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "View the explanations on \"state\" level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "\n", + "explanation_type = \"raw\"\n", + "level = \"state\"\n", + "\n", + "if model_explainability:\n", + " display(\n", + " pd.read_csv(\n", + " os.path.join(explanation_path, \"{}_explanations_{}.csv\").format(\n", + " explanation_type, level\n", + " )\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5.0 Forecasting\n", + "For hierarchical forecasting we need to provide the HTSInferenceParameters object.\n", + "#### HTSInferenceParameters arguments\n", + "* **hierarchy_forecast_level:** The default level of the hierarchy to produce prediction/forecast on.\n", + "* **allocation_method:** \\[Optional] The disaggregation method to use if the hierarchy forecast level specified is below the define hierarchy training level.
(average historical proportions) 'average_historical_proportions'
(proportions of the historical averages) 'proportions_of_historical_average'\n", + "\n", + "#### get_many_models_batch_inference_steps arguments\n", + "* **experiment:** The experiment used for inference run.\n", + "* **inference_data:** The data to use for inferencing. It should be the same schema as used for training.\n", + "* **compute_target:** The compute target that runs the inference pipeline.\n", + "* **node_count:** The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku).\n", + "* **process_count_per_node:** The number of processes per node.\n", + "* **train_run_id:** \\[Optional] The run id of the hierarchy training, by default it is the latest successful training hts run in the experiment.\n", + "* **train_experiment_name:** \\[Optional] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline.\n", + "* **process_count_per_node:** \\[Optional] The number of processes per node, by default it's 4." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.train.automl.runtime._hts.hts_parameters import HTSInferenceParameters\n", + "\n", + "inference_parameters = HTSInferenceParameters(\n", + " hierarchy_forecast_level=\"store_id\", # The setting is specific to this dataset and should be changed based on your dataset.\n", + " allocation_method=\"proportions_of_historical_average\",\n", + ")\n", + "\n", + "steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", + " experiment=experiment,\n", + " inference_data=registered_inference,\n", + " compute_target=compute_target,\n", + " inference_pipeline_parameters=inference_parameters,\n", + " node_count=2,\n", + " process_count_per_node=8,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "inference_pipeline = Pipeline(ws, steps=steps)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inference_run = experiment.submit(inference_pipeline)\n", + "inference_run.wait_for_completion(show_output=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve results\n", + "\n", + "Forecast results can be retrieved through the following code. The prediction results summary and the actual predictions are downloaded in forecast_results folder" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "forecasts = inference_run.get_pipeline_output(\"forecasts\")\n", + "forecasts.download(\"forecast_results\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Resbumit the Pipeline\n", + "\n", + "The inference pipeline can be submitted with different configurations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inference_run = experiment.submit(\n", + " inference_pipeline, pipeline_parameters={\"hierarchy_forecast_level\": \"state\"}\n", + ")\n", + "inference_run.wait_for_completion(show_output=False)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "categories": [ + "how-to-use-azureml", + "automated-machine-learning" + ], + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/data-table.png b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/data-table.png new file mode 100644 index 0000000000000000000000000000000000000000..193d9c06b8542eeaacee1724bcccae956dcb4cf9 GIT binary patch literal 67000 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 IQ!-Eg2Pl3tRsaA1 literal 0 HcmV?d00001 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/hierarchy-sample-ms.PNG b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/hierarchy-sample-ms.PNG new file mode 100644 index 0000000000000000000000000000000000000000..82bb14f7f18aef7e07164de5831f4d7760c6ef8a GIT binary patch literal 168634 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 EKU{y?R{#J2 literal 0 HcmV?d00001 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org-2.PNG b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org-2.PNG new file mode 100644 index 0000000000000000000000000000000000000000..70f48b0532f66764e780a727ac6d92ae542592af GIT binary patch literal 1535 zcmb7^`%@EF6o7ZLBrCy(^j3=}m?1gcmjfeJFb zq=MEMm_UXU_pNDR(+bG#v_p}=S*h6r*)zJG)O zf@oQ*3odv+a~gt(MFBqEp+81Vz6s9PG1vQQqr@pM1IH^a4p(&g)M_>bmOt6?QmN{M z8iuxv=hFXT?J4y+MaF#|vG$)@`@aw=TUuVel$MtEb=k4-W@NeXFOP?2Ru&f*UEXc| zcr6g;(shO#v=akmTC)n0Jhvbo->q41hG2!e0F1KO1tz^cG-^peeE3t=eg*IieBNSR zX6y(h?@o*zg7vDmi@zM=&0C8j{PBTg`K@N3&Lr%j!!~MO3lHCUfAbo@Em~Z!$sH(< zbJrCat`qV?&uJ@fC^%=AQ;8cGSY=9`HeBsykC@Coaivxutt9CPd0Y970a>uMz6idW zPZqKBewM*mQ5#7dLr=Vg&pTi}17$T`@jdeFm=mop#hO4V?vPbAUb!&eQu_J7EA@rWp(Rra= z+s* z99Hc3)mgt{&ZEqaNwtjybB*N`=3XQ(X1OIoI)O^sMVYSan1iCsTO0oaSbayui#cR5qAHvvp6CLP391x(VxHJl{-=1p*;#uxS5No-{AWm{I z5U9WKbNl>VUD^Z9JeO=^HFGPK+D^iub&* z0#6GV)2mwT0`&b^vJH)@G;U;EU6S}QM>QV?i>s9GJ=3iZ1G3nOP1%e?{E+hCNL|sGGMDG%hZVVk#~3}ce3w6^*#-9$m$=<4S6gaYHGp_1_1W;{38M*S(O zmyEXvO-{OiE2Lkve1!)g`GbiIJ&CFrM%!-m>C(~l=u&%q{lu#{-T6g;k%WRN88 zvj%L@BUAn(wYe!#YyzL@@k|evx|{K*N{%{_@y!Ia*X&xhX!wL`boX@hH#`pm$(|gD zEs`HmH8EiKx>Y%jEYKP)?E>4jkh{vwJiK z^$ZIYxV6MFP(CY)Ev)N+52i{ZDiwU_q!FHT`U+wSQaq+|t6i$6WxKG@OGB6F6K2+s o!|PD0)k*1AXVQOT?($c3(&b=!yE_~Ue%ergZ;(&jzUcIS0kad6tN;K2 literal 0 HcmV?d00001 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org.PNG b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/media/retail-org.PNG new file mode 100644 index 0000000000000000000000000000000000000000..0b4f84b6f2e86523a4b1def9a4f428ff34560b4b GIT binary patch literal 1852 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 z

O#h53-Kqq~RGnopPcn2Ys~QWtYiJlXTnp$-Z+@zweXPH=zZ_{;%uGz zepjfohnztahGi@MK8QuDwu}FT{&yKh<5AYo(ak6t+7?C*ZERa|$hdj+ohKuDig}#Q zw)~fY>9qX#Z0F{#N%-5ttHx!d?{W}8CxKO-o2>vn%IDdcU}vXbMq*kYP$OV`^uBJr zWgDBJu3LNXoLQMD{ZzBFfH&K?;8Os!iD`r}n#3iu-C82jUu27~92{5|B zI?p}Xcfjb+m?!6AP*)5EzN5NgRPmp52XvP4?k?j_)d>MFi+|^mPJ0Wz&5Ai>LlOYs z0BX%pTX|n;ME}WPtS>E7Mhe~-4m<$-bBlloix|o@l5Q_C7{&*yQ3_bAt6Mt}CfxW5 z#kqlInUbEsmWW%Z+Ud{o3FwLs`7S4*NC^v5Z8xU&498wDz3@Y2p0&a>!pgB?Nqq6( zkEJ5py5(hi5QW1MZdnVzr(+F)tmpYQbtq3Bi#M|v2 zy5?4rH@~2be){ccq>M+B_-&a|T+eAWBM|ta0f&-|#f?q?IcH+y6ZF^yvPc&|g7Y37$d%yZtX)vo!L>B{RXX3=vkuI);tlaS}VjZ*36}B-jb)3#3 zLcQiiAflRMarPr{ocDjmWjAQC!f&RTHu-N-J4Nyu7vMsF8HQ8JI*Mt8T0{$uhP>Ub z@O|o?%BydN<%c#Rf=!eg8zcnsVQEBCkWhcfu+nzXh|Perj^b|@W3;BSbqAn_tv+Ld z5~*On2Il!9A1Msy=#a31&{}hStbcY99QcIpm^#NHQHXK)&8>p<d zcK}>ePfXgVnDvEK_WWcszja!`^=Lk48kK#enO(#VqDVcJndqfBT4JSgO7UePLIiY? z5-KULTBGM>e)OkT-YC5AfI&zk4X`2IGz_6?L|FJk~75xG%U zwgKHprJ~gX=LVUh2G`~C{$1cYFf!?lAK0j@0JBBb=n-F%<({WZ)vEm|%J?wbB=isD zmgDk$E26ThM<$f5X)=j}w5F^Ky$Og@VIik7Flc&4Jbh=olZ`~(z}e{x!A&f9kze(y z5bOz_T|C{A)A;Y^GW zv78PEktVoV*FA$=JPOpuI_Gqiu;o zDwM4!TIyoGjq083fqFjwh8_O0Ik%-UVm{%AVx6`Cx!jn~-LmMFvk1$0lsr1v5^qpFXmCX;f%m*g^!5WnDQC&C3jM1wuJ79VI42(ln7$I{0NX?8Vot zQRY6Vp!fx}F#d0s)&@S6DzwteO`R+RX&dT*C74Z|WvVnqAu@T++Hv-n1Y9LulPKpT)cgU0L5_8!h3jY}~l4;}rE8DnEz zoHhq`X`tR%n8ZeYrq1?8L*VNiJCAWbmNI6ro$ks+35KwTM(4K>A?Lhl93_htk2mJB zS_W}7VtwwJSnq7PbX69fL5{O?khk=MJH!L)ZD!}2-Ozhk8|$Msev*aDbB5#C2yG`k zYaXIa87$ik2`wmrb=5AMGVRiiA7C(g9gBtJ67ki_b{{$n;`2(+Y{6t{0deF{b9`;l(`xVvc;2_wEc+kakXnI*8s zhq?_%VFVOgn=KMkb>-kAM=C$&4uug%8d+&2m_`Orib+m1w?E!EqE{G@di8*#&^+jC*iop9?$H>QO5CFGtcK8n-Cdd#Zw9Xqj=Vgv%#Zj_gA|2s=ahQQBl|G z%L|ih=?wM6xxouU2pU;#8D?~@vgRMPt}lLjI^Js8>e!C?l#hLG?h9>kK1up67|rp> zZ7N97xML_)Zxl<}L`l6}vAu^KD=32X96;o%_TrND;%>HyL`tT}hc^M!lbqw5>p7nv zCVTb94083LMAvtmRk?!Lq*!fP+TWk_mHeEswNMQG6WW<|H0XTV$(~|?f1oX)3*pP_ zw-Cvw3%=bFKXn-^G!>|rD+oz3=s%*IOXs;a!&kA_Ej3Q696t&~I5 z{*##3PwC-!>6ShE`Qxliv>K&B0$Ab&QFUh64S&BW*9M5VjzZYclg|;GIEJ=vyuMM? zUMRSS(6K>KaBh%vQVx6eDK2bjj!-=g3}K8<1bdc$E#X3r7*<&(8op|}G@7Uj_%BN4 z;e1oIfg~&!?chn`U@j=hPBy7bgldT&p51Z>A>XmSSGxYyj#dK8F3L)Kd`A(i6Ej53 zQ5?-bFoD)-y_-V_xQKMe#pR>mX!GY>)$NfH z_oLKF>W^oXAAi1MuYfVE?gr>oWF3?WzTCKy1E-hhr@l?0(N0cYxA!WF}{0C$v6>Iw}|A5}mIGNg?Vw%K~ z4&(@>(9PjV+uzMEX3645y(+inb7~&m{-rw+Vc1%C%MzIasuFKEJ7Fo}JYiio8-scH znenH1zPrXcap1wn?|I*$Z(Qlr8#u|nq6a@i(>`~Ekv17pvUFxlb~R5kI|skN8b?xX zcfh~HgeXP}*ju{p!|gn1`f80S+27L-YAnumivGVmq9h2D7$M|t;EGYdCkpG#?8=C~ zRWz3(0D{rj(CO%?Xif4M?6*sL$lz69km_%;#3MxU;{fxXI}}AXL}d%!D{q$dn;u0N zS-f$6Y`IS4i!J_o)P{nw2xoxAV`<+4>R$z@Gp*eDqu(Q5)x*O8VfxDv)#A{UW}N4Q^k%+}j4iWY&gjVry)`G*^wO4~8`_zrJ9uiV*l|kd`$wJy3xY1#X z63;5M%^wNc)|Q68qho6T>Q2V;S8r{+-nsE|saQ3#`3@6hwX|-Wts&2LSVtm9YE@7bUPua4PaJVXVjxxJGSr>O7nzQrK&1}sA>X-0z|sUaHbE1tRU>6sb+3^f zePnhxu(3#cwG#%&O-L!prcZ}uUB%%gx$54?@>hC%qAnm7I>C4ZeRq{MmG95iN}7C4 z-@7xr@)JS-!X?w#ViXq?u`k))02`R*+ezTn3=-_I;429%c2;thUM~FIZp-bQs;f6f z!Ql?gst{{*s#^)|eW?V-cT&s+`nr6}?Fle9ur(;eUteCkpK+1C(Yx$&{3Tl*=S?bq zZ%?AKQ_)*5?js6-MW>=DCHh<5V6nbfa!9eWzsqrPlrpChm-m(yQQgWy)F-})Wzct# zIK>WOi(9v1e7~P`rr37$5I4?BG(*3hh`$&P&g?F;OHlDJ=9)Qqv#W(RnnSGDq%y>Xk6lZ2i%sp#6O}8~%_2$g>sz zFA6E7)`X}s*8ipAx|O={!G;h)B%&kuPQj*%J#B4$$IFuT-un!?JNX7(zEIyKuPo{e zjw&F-P6n_+=>7BSwt>FD0?vPjT z+YSMjCaKNYx`MRnT%de1?`UHz>(4r)kx~UrU*%R0tX~W)uN|H$91Utit~S7goDCnu zXP8iD!Df@o>ogwf6orTE?eOnNFj#D0%jlx~F2e8p>H}>BSzkXf+#D@DoKs)N!zHFh z$X)`@LM1CBH1wi z8Ka$ep%tNWW@?1czJ{3J6A>yI5MPsjJH&_!4nHnN82~vV#l@#Q4EQ$H*$Upy_8b3N zK?0rVo^;z2*I7p(N52ZU1mA6ylMn?7Rvs(Nn_>Uqgo2ef4}l5dAFQN<&E85vz?xUB0m5=mZl>e04kUu9#kvZ7^T|BW z(}>Z^?a{GhZf03P#Zc~l!QusfJ9LIzTn zuR&T~&@#D*W&R;G;{D#`-~1_5n(gwkzNz!0hEdBO_d#oyMsV%C`9>j5NhhcqC1IvX zN<84`Kma2XEHYG!OGF&&JE#wNgS`bVcU` z>r$6``RXga7ZMjX9suOuCelv7fb@)3?l+pl9Hc{*Q6(x-L9rJc!y%v;U(^iM;g;#R z*Er&uK~jphSwku-Zm1s|-Upw)ADuZ$p@ScG!Wm#q4U~=QsFJZxPR&ldonPpcG?WF7 zzG=u0SORwfhx0au^me90&4(T)Rj zmH;$ZBy@&u5C=UzH??z->(2^wAhUog^??Nw$af05+-LFNMzy0}7h6bn*B`-GUdH_e zA&`yC(TNAQr;s*=|KmLH9q#g*$wc^&4Xd6SAtMaOSYnaRk=#=)GK}m-)oP9((>t1W zEz1U`^)Owydgj8w`hX&E==(FTRQnZ3A0;~^ywI%fi_84*oouoZx0DwlaK&~$3~ znr;p~A*w^tg7t|{P|l>Loy*tv1@n8wr(1_X^p_)?sO*S~Y4B#WHWMKhC1X>f;o*@t zwj-UI?lH>fN^D%3OTfQzi2~2e+3MOH#)@@%?M|Ykst7S!nj^JGak{K2%MM2Wm!KVT zao$^5_|)C{$6>{3&#+H^l$ndi9JvtwZtqF=hi77`?$^)9QjdVWf+|Pa`cIqC*Gs)2 zLp80_8PiDP6Cqb(l$v{2kba-bcG)u^G&E4D!zr4r!_sBrE9F(((}J%5>XPpc>RpKN zalz@D)g)=GpBP!wC|37heFc5}wJ*Xsi1VfCl(RQ}i+5f%%2r=p?GAsp5B>v!Y34Cu za_H;so|NzheJ?ns=jg(56lkOn&|uPQqD@EZW5L^W*C^Oi=li(8DMUiRyMs8Zb5#jL zr|B}`&mLwxLLSZWTIOfK^1Gu)g8698b91ksrTylGOBKD(I|yZT9niLhz_@GBetTAu?5GH zvm+AmiWauXhJLPg&>v-8EHG}yGwAyLwwQ%~yxkROu0Z^8A;;K^cuEQD^Gk41&IZKf zvis?*)c|d5L7ZXb-1iH7UEX^i?{b~GRU;CNoY|`W=y#^Q$|yK}y+EYzh2PyD9IY!K z2v(Xq@OJq*4(Oyu0y}t+uB|(gK!-de%Xy)X;X+UVQXmGG;U4V@BD5ls$sXa48l6-gASw^J*c}9Z?+ykX`xG3p?j8<#RVVOCT^4%a zoOk!~KmP>2cQz}JJj%R%tKZEL+7$+XHFgo1+{;mPPUlj+OPSUQzU&O$@(xE_H^}YE z0MWY^mOCm6Vc1M39uF38CJLlGl}&d@Y0c(%2X2%N|9qA538D7lgh7%7`9N@OH5<$P z>z{lr0|~6&&?8BnY7SyI%q0+gs$`}=*FvE%@ez%!(_LI%0-rX1+)4E>O z8*H%xVygeXRMI8ExkurdLxI;qvi=?|T0MHXDIM-Fjo7o?+&WgGz37Xxv9w$etS&@| z!h*N;I*_1G4b4?EJ~y{@AJ=>1#AyGqWLbH6WvM9OqZLitSoIW)S6_LX2{$?}=6EcH zgucrqcorQX5@FLIujd5Y9z7hjn<_WM#POg!1~n>4w|**?B8*T4OhI}}!Xv0}mCtgF z9`F!6O;_9IlKyoFv8``#rC!v@h9{?LlM1=LxDKD4mk^^)LVEKxMz8~K$6Z5A!v9<7 z@M4QQfs8B1F*3qWErT?YZsBX(JK;q6-lh64QsqA{QV#&JmPYv>5EE1G+|tDeWwlAV zV!fH_ilwtO=B1U$qM#w6psHsOHL{i>`m(tRD7mr8ZHc5=-hs}>`uCBPBZ=5B6WJ1E zorG#;0h_NfK>3K?OcDHn^^i;E!RAh`-iG0#?+10B%f5NEwY;2r_hrPi9z-WB0~+5G zEp?Btg6>lZ!QOIqnkx<sH4!}<)eGHEeuuK7kY?tL{S4!=?(Vw3oHI@&zmwUP4u{%6GF*c-Py_$oY(>b$pm zJCQIpxx`E8Q5n(RTO&&tbhX2Oj!6-5JEyCg7?TOL;)E!Ng^z+Bb`sR|Xto``O*cCS zN})$?{uK%KLKi8xo1O>00e+<#x#CiW+LIqWTyeJlX$`|$3mr~E#uO>m81H9rxlU!Q zwKa(4*@Aqe%#^RKkM%~c(u--dWQ3I=-IFb2ots+N09Ng{dii>Lyxln2=-&^% z(G88A*+Z1pnH{_YA>q{F#7|I!^I~CN=>Y)xMxveBpoT}Ib5?dOrA{27uZ!aLCv`9k zz8SfB+!IH1-MY0qaahlZ!9cYLf6?c0)^zzwI=B%I+uUMIE8*LQNedGCItxHUD|nN* zu7JsUj-i@rj(2^pJD9#7gYs(AwddsT1^n~61rg$P3)xjG*yYP$8~2>jirlwF_!U9h zf?N99m+0v5e9cVtAGmK1RqIn8tMMyF9qzttxk<#m0ME%iNDg)fb}qDYfm3XIYg-@k zxZl2|o1$resY1!s&#b*W4he&ne8_s@bmkR{jkRQdY8mdJC$uB ze5~c8MVg_l>>%+*oS#@FldklV&NM*^{D)9L}KW)ALQ+nj3MV#p4Ur|7;=% zY&VlNPo%D%YZ!X2Y@vAtY(R5j<#kAF)G2?bHtX~<@JsHtz%TFgdHbx~#OLFj3CDWOWJB9)08$uhXKn+&;mA;OawNk0pE6@6`+I_weFR zGCpmQo#r{vzSk>CBk%`^QEXpZ*_Kwp8vqAi3>UUq#+6ar-ig3kD`S_7c_yot_1Bp^ zIwqPmBYWx*0V`Pwzp+6Fy)}>8u$snLuY>*bUich5NAFsFT~dks0GwvaWnC~=pO73D z9L5J$iXyDN>5uLBKY?mP_EPS0zqPE~SP+Vr^tt_T^*&7YBUMkxwvnIGCd<$TfQLn0&aCjEr?7IB@gPvlJ}e z;!awlN5(A#dlLP-N>^IN2unO}AX}Snh=C4HV8erm_Yx$5JQ8z>yl#cQiYAW8g<`0G z8574Quq>};vn)hQeJ7+bf?khF58lILRkuP#Fo9*%4T2W?xJuAM)hU^q_5E%pvrW=| z996ws(U7Z4;2EfivQn|*oVjkqrFSc%*)u^I)=DzCR$uwu%c_%LL+#);r2l~x(6k-T zIK-T>38QST>2{q1*-A;_n14U%9uWbKTwl5<%RiSTYh-unSeQ_xw{Y$Od zzmKSLNK>Es61bPU0miuQe3V|_=I9}a44b|ADc*WBTZw*~EFmLn8grIN0r~&un~f9d zpj%k|H{b`Gmu5d|*b!G4aqWmM#kXW2?<4cWM-Mr}Ge`$u%Z&YtW^)xkm7O6_)r2O~ zA}>2Smh>t*EW5^TlNh8f8b_1BHVO2zG9{-1oSR1q)Bn|`ow+qzkvCaPbUEi)=T;(L zv8W4|HzupDgTdd0u_6|NJ6q*YIKwiLK~4p`b~|rVKO4zU%>Bri1nKu|rXO>!RGT%y z#R%~)S_NQ%d6};GGoh_-Bh2La&l0(rPw_QqJ!*eybzwF|sdL+*8jo{v%l)&&DS{YK%Y#aF+B;nKHCz0Ln@tB8a& zS$<~VYv^C+2CG7keqpbV?B#4{t^07m7;Vi7tH*FgAMlYaP9@1rL&h z@o*mrk?eEoq?E8`mBHpNaPJ+RLTSNJIngz1ykxg{+bUXV`aqA?iPW-lq4f>*{>o!M zK{}kUJXx2&&m!|(^|?yjIdw+(Kfg159=_Iv#@IVA7YU71Wor#HZsBSa;HORR5&rU%%E}GjsOP`=D ze>ZRO*=Kh<3?HZ`0JpJk+Wx)~#Wj|YQpDX6!h~S=xKA2hUWBJ_WecXIQh_m{Ug+6U z;?22m$+snp)hFH2STh~?-BRj1O5d{OxkdD5>V&GYKwcRYo+S z2TQ3Aj;yr}{7Z~NmwPAH>txd4S!C)*L#)-j+_hd_h>-UKN0(peP`$rA5Ti3KdIV)7t1aqh*vx!BKV^S>-mfjj4h?x~v?? zNJB!Cuc0S7s!;C))~mlW&&k=s-(#Lk;U~}?E4ygxHuq` zrXK7oJxEYAr|BnI->PpO3%DX31J`_Z=X^&(VZ=(76~Lw{Y6o#kOW2$n!k$^fmUN`) zuB{*jcM<61TC*mJ!}+|b2*E_7dgpllEBYL}qL^^a(cufORkWE&`l|s0>Ov;CcTNRO ztGh|lG(uU~4xSdvd4dgPC!-vJF2aMfej>xXpiI&;DcbJh{%DGRM z0wOa49dv_{f zB-d41c975Y6u;)69|upccxGp{#5)?!(xo7T`yxl#*;bKi5v3{mwMZYO2x-__hdc8V zmMc0XzF5j=;~}M{7g^L8rB`ohzC)2AD;kTBJawV)xy8)yJQYnHm4Y6o_EkJGrJvj{ zX`i1tabWqtDK{OS3>};DQLpHza2O%Cx)rH|^IyT+gIhHDIza|(;z>-UqIMqgidwNG zA*a?jFS60&l7b!j9x{h>))X{32Lnt)$gwgZ-C7)eFzBB1h?pBqG*+$)kRvSH3 zZXLE^&O&@8I#I6EOkMp*%B@HMX~x3;vblG=>WtDZj;53d~Asi|Pgig}W!3-ya%6XHv! z@YqnTUNK1{si#Vh6H}WV9Z;x4GLlZ#kx9s`ponH`81!!?74@xYqD7o*Aj0j?Sr4<|zdS;Q$qjl(E z;Mz7cq8;0D$MoeZj)7M4NyP=BuPp(;MUD?D;vnO| zXyN><3mIZbcclt0@tSjK(7EBEBz;9=tav--P&Lx&xq!?YK)Ultdunc{u|~}D$|{z7 zP;^0&nmIZ}8EOyHj?#xylC!d_?1jgMur!k>U&pN%YCo4CEbA(VW&AQJ+6vI!2@Sy0 zLqh<3G{c2ph)8HW@RB2bn5Zbym7oD_kvpd3<}GH*puJC!WMjVyeK2Z>FI@g-6?Q0H zM`|!}UUpo})D^%V%IUI4_2Pw*{LoFcgLRbsIr7%>ePHdm!SFQwEMQXa3}++uCtmWj zoMeduOP|If`#`UZ##LjvFA}G3_l2eDorsgtJ$dI6>nT#9z+ks|e~u)v?|#1m5di;) zFY1KQmRYJ{WS%MpkV&VaWA>57jy47sBi+@%4e#7ws=qxX7BEfe0_}*5&#|m#r1*61 z&2xiJ43L71n_~OK!8qiZb|%pwpi|pvOoW`z;_bJR3ON#L72b|4PZUCT%JO0ahDm>1 zB;0%y1dyrb>4Eo;C0hrol^tP?*hb3!gxuD$EsyJy%?1XjDeP#DDea0vR*A@6QFuGH zA)ZW3l5P{x6b2L+OF71kQRq>F$k3d~C)6f|X@>|@v3&Rzvrt2iCg!{M2%ekFN(5V7 zctyLmJq!upFNGl+(}8pG;wgkPU?(hm*vvT8v1ENRo2B8{7vwC;CJo$DgLI;uBVd2)G+l0zOw-gW zM(TNE=w1(sPAoJ_mD|{0^Px^jOICBf&O1wO7x_~yBUT}T2AjP3an_zRaFnv2G=sRC;a;|EzbyQt_FSJ&}i$_3@*K$1IIugn%G^o4lCcDx2%{^isLIUKD zMgl~~cuC$QMK?H}pP?9+33AGtA}zIhmlDEi#3-B67Kr^f+un&UQU}*j5Q)uaGv(Y7 zl%PYNG3ISq;S1BIt2H#xN-p9NtC^NQauL3g#6s4nK+@{8N=4&xr8sEeh>C7`qA|Fn zis~pTRAI@Ps0|7>mhxcPK+h~Rw|93XRwD~{cSZ$F@i zGXvV=$puJ8hsJcWVPseLbmJx42?SdwMQt74{9UY$(VAnu`*jCM01`lV&jIZ*hf7T0@=7$=x5(UdbN*>$*Ve5TJv@jYE5#Tsd(Pbn;lZG}$ zZWNvK8XEP?JsI%l8TsC1)0FSE_5{byO8u?ACf~p~#~|NEExw9hX)c#8^z*t~!8@5G z);G6|XP^Wjq(k`pb{P^Cb93B&WF<+u8C3 z*jE*0I>^8JFY{;lNy{y#f&*t0>IsxuoLo`VptnLCKT<5%iTU||nzb=Yo) zp3h=xCxbK6dR!^OO z2QQSpnuWZ+YXKG z#z5$Kg!++kdtymuUDIGon%;^*uw~8eD?77PnfV$0{B67WZq0&yqJ`-wU?U4R1%J{F z-Xc)O>wHb@OhJI@$D;UdBiz`b@e+*_yVmbv%zg2;GU+#49f4WI(_huA)MDDkkE9WU z$O7KpbZb-va$AwEyKow!DJ~8osorPJ&v5A&;$-m zh<6Km6&nxjVf3|zRUM1YAKB@+OpsRh# z4M>n3n+Q<{_7BlZb`Z-F+kQfrbw|-qFH*%Jn^j3wB}yQGvZQNJX@c=c8&Std@vubC zbdE^A`k@Ed3+NqBdw}DF2cVAkCCZ_q{_R>8ITPMQFZ!IU=mvO0&(dxY8`0$vudzR_ zKwsEF_<*-{h2k>SKpGP?%GgOX>P2j-dRQm~e6>y3p~3;+nMY@@T*XpdfOT1kLjBF* z+}NI@u6gVOM#-Om>s;5@y?#VEf3p0oEmS=>MlE8pcIbIZ&)X6GH6YNRGK=5yCj@bet z{1*in*>@9bdRPd4&4w=z6;=5aUnH4@9nSOtO#+39b~pOt%kavn1cyBXX*zmf8XCc; z=K%@bCqF=tcZ#?HWm*xk$FN_nO)3VFO74pk74m_sIYnXMO;42YIW!?i_oIOL(^ihy z6(||zJ~-*0Ri5Nhqt*W3@H>;e3@tQZ!LOJ}6NQ+to#lQgczlvGCAkB5;_nQWz zCUkonPA(2#@1{`vJ?^UxV~(j{sx zB|=*MDuro#-5cAAw@0&=GlUCO_w?_`ew{F6bfw$V)}giV{3t*-x5An^zt~^thYs>f zHp#NOu=uG|Y*I8--*%(VTfwg4DvF4B`c-d%kz@ck@zeXxTX%4ITs_yc5T$oU&@p*q zOeYDQVvEWujuLtz)4(oz79Tz=y0U#XLp_>;=yu$1%CD$M^2DSW zxT+R-p*G%A0lx8J5%D$3(_!%$_JnS^bVg$7j#f+CxSk6X`9QW@*vy9a0&+j;en9HI zJw47EcPO=7B0rEI%HL8WvY`kay6m1uaH(ZWIAU!g)CZ&%owTj`MriJxms;nFLC@h! z=#?bCW4vp|!llD!f@d zH=Zn=MMRmT(w+AU$O&S+Q{x5vtGUrDalFq9Q#kH;jiVY=)sLc{eVY9jR%v`-TWxP4 zjxXInG7c24>6y=O%?o!R3|stN#$8ER^X ztU1i2BvCV#;dTKOY(~2tAy035bUCK6wK^{l8{mL4Zivk&NOhsb@&O$#7)0_(G!6A= zsw+j_uB#fjwQSTHRNQFs`4Sc9ttpIw{yIZvSGyS0!34&gf&@+2cr&rd*eo#T#7h*$ zolUy91wcBzPI#dv6)2vNt{3o0BynTRhadx)nFmZ&NoBSnk{Wv8?onEy&Js&!&H*~u z`QBgZED9WnZeSB=3F9NnG2DH~tPz}^%->Fk^)yIm!^VohG|-vY#Yx`_YQBnVYG_$j zWs|{XcLL8fE>iC1z=Hz%gwiX@TBg7mz2l^wvW^-_QIy2M{<{bd6BTagYG|zl(i<$Z zo$(_~88=yVZU}OvaF8Gr%XQTxiYb;qM^hB)AtNojDHoK|L4h9(9*D!kb2pHCqI}ct z$;Fe{R68^#uKt!f@r&}e>9bRRFTT6_i2tn&CTNC(fR_N-pnnl}u0L0Pl&eId6_NBJ zSP&=L(qElG9f zF->FN(7B=9^4Em;!l<#WzI`^;04RPuk_?YUnYP)>L5P7CK2_zZg84_xm}nOOha}EQ z^8$|imrR2E1ardb89Q7x#SY8nmUiXV zee5#z6puSH!R!YO>fWiy58~RZCVw@dr+4Hyr=}U5nx^ccx68w+C2$nqIlN=^{KrA= z-H@~Xb_)qH`4oALV!3eMnPSK8oMY3(@5)$j^>Ai(*`blyOEXkbHeZ`k4CM;1 z+9ze%jEoS{)Z{MH0>tILk5u?XNG)`s9W{xn=z`IyKcRO=hDIW9JjpAC_%^`ZOwrG_ z%!n6pUY$zQn17sPun3tQZ)O|NTh}OzeAAe*$XgZM3v#AKUGxcb`4F8+JOwDPU$fVw z6~$2(8zH4Xj6x=x{8eTTi>eVqHvV}~>ve0ykXt?) z!)G*iQN&g#Q4vD!EhL8Mpr7jykTHFLbl3Nu(})`(ZAZ=iriQshBr{Lg@4)){w2icK zPy}%)Tft9?B+U0J2?-&sAJ?8?RKP+wpZT#N*{N0)Rq2P*uAnzpn1_ z=7m?^^>O^8fD%qtLF}Pu6_5)k`$2g(4$Rlp6*Y$3sgXvF6 zvf?`1?H?^K_SLtMQvl@DMxL?pOC^zXCNkA@79OwR`K21nBeM6o<>QSc>{ELQqEmkY zW&`zdG%*}63F~TyuUfhSC8)nn<>cJ4joX8ljIYPAU;XSB;Hw9xC{k|TDw>|;D@-U;{2@XwNDyufUC)&rpu(DL;U_n2b$gbZnjh+No^c|P_YPk#VPF5S$b90?%}}j~6!;!_>IK9B3iI^KBXz2M zzEZ_LnvqMM(tYHcjw0g9tS?rONP%g3Q8j2(I|qX|L!@vA@LKIl+Hle@=!zvxhuvOd z&2zY|-41e%1TJ-FC2glJ>A|$o&mP{$p_lQp)Ajw!(v)HNk`Sat`A4yEEy`W+lNKgf z6w7A}l8TkvOumSpA29AH3{SeEsugf|eshirfhu4gWQnzrg(3KUcBT~jvkX1rYe<8v!hJzQDPX~v1d zftZ2L19YYHGsHB%c7Nd1XRq&){rZuV$f~0sllz8wt@G774}+knKXY@-ZKX~B(`_3M z6S7ZMY7 z;P#m(6Xi#?R&Ulm7KnKH-XG>16|P{Jd07zUxlL@=hduWbrrv77(a`{M2lKx8uuq9x z|Hv_XW}CnDK32>G!{^UG@XCi@u*;PVTQQPnq~ljk)kBB9axhj7#>&BXNaJ4#u`6lC zbJ_3zyHsl0_4=-7?szphcHe&Hj#m$5_E_E1f02p@R%bkO>nktzxkUojl|@$eWW`SY j&(2OqFi;qDCKq*_%-@=u3~rEKmHI)(`|{s>_V51 literal 0 HcmV?d00001 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/update_env.yml b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/update_env.yml new file mode 100644 index 000000000..d0b193dab --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/update_env.yml @@ -0,0 +1,3 @@ +dependencies: +- pip: + - azureml-contrib-automl-pipeline-steps diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-many-models/README.md b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/README.md new file mode 100644 index 000000000..681528e33 --- /dev/null +++ b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/README.md @@ -0,0 +1,122 @@ +--- +page_type: sample +languages: +- python +products: +- azure-machine-learning +description: Tutorial showing how to solve a complex machine learning time series forecasting problems at scale by using Azure Automated ML and Many Models solution accelerator. +--- + +![Many Models Solution Accelerator Banner](images/mmsa.png) +# Many Models Solution Accelerator + + + +In the real world, many problems can be too complex to be solved by a single machine learning model. Whether that be predicting sales for each individual store, building a predictive maintanence model for hundreds of oil wells, or tailoring an experience to individual users, building a model for each instance can lead to improved results on many machine learning problems. + +This Pattern is very common across a wide variety of industries and applicable to many real world use cases. Below are some examples we have seen where this pattern is being used. + +- Energy and utility companies building predictive maintenance models for thousands of oil wells, hundreds of wind turbines or hundreds of smart meters + +- Retail organizations building workforce optimization models for thousands of stores, campaign promotion propensity models, Price optimization models for hundreds of thousands of products they sell + +- Restaurant chains building demand forecasting models across thousands of restaurants  + +- Banks and financial institutes building models for cash replenishment for ATM Machine and for several ATMs or building personalized models for individuals + +- Enterprises building revenue forecasting models at each division level + +- Document management companies building text analytics and legal document search models per each state + +Azure Machine Learning (AML) makes it easy to train, operate, and manage hundreds or even thousands of models. This repo will walk you through the end to end process of creating a many models solution from training to scoring to monitoring. + +## Prerequisites + +To use this solution accelerator, all you need is access to an [Azure subscription](https://azure.microsoft.com/free/) and an [Azure Machine Learning Workspace](https://docs.microsoft.com/azure/machine-learning/how-to-manage-workspace) that you'll create below. + +While it's not required, a basic understanding of Azure Machine Learning will be helpful for understanding the solution. The following resources can help introduce you to AML: + +1. [Azure Machine Learning Overview](https://azure.microsoft.com/services/machine-learning/) +2. [Azure Machine Learning Tutorials](https://docs.microsoft.com/azure/machine-learning/tutorial-1st-experiment-sdk-setup) +3. [Azure Machine Learning Sample Notebooks on Github](https://github.com/Azure/azureml-examples) + +## Getting started + +### 1. Deploy Resources + +Start by deploying the resources to Azure. The button below will deploy Azure Machine Learning and its related resources: + + + + + +### 2. Configure Development Environment + +Next you'll need to configure your [development environment](https://docs.microsoft.com/azure/machine-learning/how-to-configure-environment) for Azure Machine Learning. We recommend using a [Compute Instance](https://docs.microsoft.com/azure/machine-learning/how-to-configure-environment#compute-instance) as it's the fastest way to get up and running. + +### 3. Run Notebooks + +Once your development environment is set up, run through the Jupyter Notebooks sequentially following the steps outlined. By the end, you'll know how to train, score, and make predictions using the many models pattern on Azure Machine Learning. + +![Sequence of Notebooks](./images/mmsa-overview.png) + + +## Contents + +In this repo, you'll train and score a forecasting model for each orange juice brand and for each store at a (simulated) grocery chain. By the end, you'll have forecasted sales by using up to 11,973 models to predict sales for the next few weeks. + +The data used in this sample is simulated based on the [Dominick's Orange Juice Dataset](http://www.cs.unitn.it/~taufer/QMMA/L10-OJ-Data.html#(1)), sales data from a Chicago area grocery store. + + + +### Using Automated ML to train the models: + +The [`auto-ml-forecasting-many-models.ipynb`](./auto-ml-forecasting-many-models.ipynb) noteboook is a guided solution accelerator that demonstrates steps from data preparation, to model training, and forecasting on train models as well as operationalizing the solution. + +## How-to-videos + +Watch these how-to-videos for a step by step walk-through of the many model solution accelerator to learn how to setup your models using Automated ML. + +### Automated ML + +[![Watch the video](https://media.giphy.com/media/dWUKfameudyNGRnp1t/giphy.gif)](https://channel9.msdn.com/Shows/Docs-AI/Building-Large-Scale-Machine-Learning-Forecasting-Models-using-Azure-Machine-Learnings-Automated-ML) + +## Key concepts + +### ParallelRunStep + +[ParallelRunStep](https://docs.microsoft.com/en-us/python/api/azureml-pipeline-steps/azureml.pipeline.steps.parallel_run_step.parallelrunstep?view=azure-ml-py) enables the parallel training of models and is commonly used for batch inferencing. This [document](https://docs.microsoft.com/azure/machine-learning/how-to-use-parallel-run-step) walks through some of the key concepts around ParallelRunStep. + +### Pipelines + +[Pipelines](https://docs.microsoft.com/azure/machine-learning/concept-ml-pipelines) allow you to create workflows in your machine learning projects. These workflows have a number of benefits including speed, simplicity, repeatability, and modularity. + +### Automated Machine Learning + +[Automated Machine Learning](https://docs.microsoft.com/azure/machine-learning/concept-automated-ml) also referred to as automated ML or AutoML, is the process of automating the time consuming, iterative tasks of machine learning model development. It allows data scientists, analysts, and developers to build ML models with high scale, efficiency, and productivity all while sustaining model quality. + +### Other Concepts + +In additional to ParallelRunStep, Pipelines and Automated Machine Learning, you'll also be working with the following concepts including [workspace](https://docs.microsoft.com/azure/machine-learning/concept-workspace), [datasets](https://docs.microsoft.com/azure/machine-learning/concept-data#datasets), [compute targets](https://docs.microsoft.com/azure/machine-learning/concept-compute-target#train), [python script steps](https://docs.microsoft.com/python/api/azureml-pipeline-steps/azureml.pipeline.steps.python_script_step.pythonscriptstep?view=azure-ml-py), and [Azure Open Datasets](https://azure.microsoft.com/services/open-datasets/). + +## Contributing + +This project welcomes contributions and suggestions. To learn more visit the [contributing](../../../CONTRIBUTING.md) section. + +Most contributions require you to agree to a Contributor License Agreement (CLA) +declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-many-models/auto-ml-forecasting-many-models.ipynb b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/auto-ml-forecasting-many-models.ipynb index 75caf8596..686b8aeb2 100644 --- a/how-to-use-azureml/automated-machine-learning/forecasting-many-models/auto-ml-forecasting-many-models.ipynb +++ b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/auto-ml-forecasting-many-models.ipynb @@ -1,746 +1,746 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright (c) Microsoft Corporation. All rights reserved.\n", - "\n", - "Licensed under the MIT License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Many Models - Automated ML\n", - "**_Generate many models time series forecasts with Automated Machine Learning_**\n", - "\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For this notebook we are using a synthetic dataset portraying sales data to predict the quantity of a vartiety of product SKUs across several states, stores, and product categories.\n", - "\n", - "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prerequisites\n", - "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1.0 Set up workspace, datastore, experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003526897 - } - }, - "outputs": [], - "source": [ - "import azureml.core\n", - "from azureml.core import Workspace, Datastore\n", - "import pandas as pd\n", - "\n", - "# Set up your workspace\n", - "ws = Workspace.from_config()\n", - "ws.get_details()\n", - "\n", - "# Set up your datastores\n", - "dstore = ws.get_default_datastore()\n", - "\n", - "output = {}\n", - "output[\"SDK version\"] = azureml.core.VERSION\n", - "output[\"Subscription ID\"] = ws.subscription_id\n", - "output[\"Workspace\"] = ws.name\n", - "output[\"Resource Group\"] = ws.resource_group\n", - "output[\"Location\"] = ws.location\n", - "output[\"Default datastore name\"] = dstore.name\n", - "pd.set_option(\"display.max_colwidth\", -1)\n", - "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose an experiment" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613003540729 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Experiment\n", - "\n", - "experiment = Experiment(ws, \"automl-many-models\")\n", - "\n", - "print(\"Experiment name: \" + experiment.name)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2.0 Data\n", - "\n", - "This notebook uses simulated orange juice sales data to walk you through the process of training many models on Azure Machine Learning using Automated ML. \n", - "\n", - "The time series data used in this example was simulated based on the University of Chicago's Dominick's Finer Foods dataset which featured two years of sales of 3 different orange juice brands for individual stores. The full simulated dataset includes 3,991 stores with 3 orange juice brands each thus allowing 11,973 models to be trained to showcase the power of the many models pattern.\n", - "\n", - " \n", - "In this notebook, two datasets will be created: one with all 11,973 files and one with only 10 files that can be used to quickly test and debug. For each dataset, you'll be walked through the process of:\n", - "\n", - "1. Registering the blob container as a Datastore to the Workspace\n", - "2. Registering a tabular dataset to the Workspace" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "nteract": { - "transient": { - "deleting": false - } - } - }, - "source": [ - "### 2.1 Data Preparation\n", - "The OJ data is available in the public blob container. The data is split to be used for training and for inferencing. For the current dataset, the data was split on time column ('WeekStarting') before and after '1992-5-28' .\n", - "\n", - "The container has\n", - "

    \n", - "
  1. 'oj-data-tabular' and 'oj-inference-tabular' folders that contains training and inference data respectively for the 11,973 models.
  2. \n", - "
  3. It also has 'oj-data-small-tabular' and 'oj-inference-small-tabular' folders that has training and inference data for 10 models.
  4. \n", - "
\n", - "\n", - "To create the [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py) needed for the ParallelRunStep, you first need to register the blob container to the workspace." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "nteract": { - "transient": { - "deleting": false - } - } - }, - "source": [ - " To use your own data, put your own data in a blobstore folder. As shown it can be one file or multiple files. We can then register datastore using that blob as shown below.\n", - " \n", - "

How sample data in blob store looks like

\n", - "\n", - "['oj-data-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)
\n", - "![image-4.png](mm-1.png)\n", - "\n", - "['oj-inference-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", - "![image-3.png](mm-2.png)\n", - "\n", - "['oj-data-small-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", - "\n", - "![image-5.png](mm-3.png)\n", - "\n", - "['oj-inference-small-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", - "![image-6.png](mm-4.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 2.2 Register the blob container as DataStore\n", - "\n", - "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", - "\n", - "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore(class)?view=azure-ml-py) documentation on how to access data from Datastore.\n", - "\n", - "In this next step, we will be registering blob storage as datastore to the Workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.core import Datastore\n", - "\n", - "# Please change the following to point to your own blob container and pass in account_key\n", - "blob_datastore_name = \"automl_many_models\"\n", - "container_name = \"automl-sample-notebook-data\"\n", - "account_name = \"automlsamplenotebookdata\"\n", - "\n", - "oj_datastore = Datastore.register_azure_blob_container(\n", - " workspace=ws,\n", - " datastore_name=blob_datastore_name,\n", - " container_name=container_name,\n", - " account_name=account_name,\n", - " create_if_not_exists=True,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 2.3 Using tabular datasets \n", - "\n", - "Now that the datastore is available from the Workspace, [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py) can be created. Datasets in Azure Machine Learning are references to specific data in a Datastore. We are using TabularDataset, so that users who have their data which can be in one or many files (*.parquet or *.csv) and have not split up data according to group columns needed for training, can do so using out of box support for 'partiion_by' feature of TabularDataset shown in section 5.0 below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007017296 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Dataset\n", - "\n", - "ds_name_small = \"oj-data-small-tabular\"\n", - "input_ds_small = Dataset.Tabular.from_delimited_files(\n", - " path=oj_datastore.path(ds_name_small + \"/\"), validate=False\n", - ")\n", - "\n", - "inference_name_small = \"oj-inference-small-tabular\"\n", - "inference_ds_small = Dataset.Tabular.from_delimited_files(\n", - " path=oj_datastore.path(inference_name_small + \"/\"), validate=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3.0 Build the training pipeline\n", - "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", - "\n", - "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose a compute target\n", - "\n", - "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", - "\n", - "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", - "\n", - "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007037308 - } - }, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeTarget, AmlCompute\n", - "\n", - "# Name your cluster\n", - "compute_name = \"mm-compute\"\n", - "\n", - "\n", - "if compute_name in ws.compute_targets:\n", - " compute_target = ws.compute_targets[compute_name]\n", - " if compute_target and type(compute_target) is AmlCompute:\n", - " print(\"Found compute target: \" + compute_name)\n", - "else:\n", - " print(\"Creating a new compute target...\")\n", - " provisioning_config = AmlCompute.provisioning_configuration(\n", - " vm_size=\"STANDARD_D16S_V3\", max_nodes=20\n", - " )\n", - " # Create the compute target\n", - " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", - "\n", - " # Can poll for a minimum number of nodes and for a specific timeout.\n", - " # If no min node count is provided it will use the scale settings for the cluster\n", - " compute_target.wait_for_completion(\n", - " show_output=True, min_node_count=None, timeout_in_minutes=20\n", - " )\n", - "\n", - " # For a more detailed view of current cluster status, use the 'status' property\n", - " print(compute_target.status.serialize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up training parameters\n", - "\n", - "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings including the name of the time column, the maximum forecast horizon, and the partition column name definition.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **task** | forecasting |\n", - "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error |\n", - "| **blocked_models** | Blocked models won't be used by AutoML. |\n", - "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", - "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", - "| **label_column_name** | The name of the label column. |\n", - "| **forecast_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", - "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", - "| **enable_early_stopping** | Flag to enable early termination if the score is not improving in the short term. |\n", - "| **time_column_name** | The name of your time column. |\n", - "| **enable_engineered_explanations** | Engineered feature explanations will be downloaded if enable_engineered_explanations flag is set to True. By default it is set to False to save storage space. |\n", - "| **time_series_id_column_name** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", - "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", - "| **pipeline_fetch_max_batch_size** | Determines how many pipelines (training algorithms) to fetch at a time for training, this helps reduce throttling when training at large scale. |\n", - "| **partition_column_names** | The names of columns used to group your models. For timeseries, the groups must not split up individual time-series. That is, each group must contain one or more whole time-series. |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1613007061544 - } - }, - "outputs": [], - "source": [ - "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", - " ManyModelsTrainParameters,\n", - ")\n", - "\n", - "partition_column_names = [\"Store\", \"Brand\"]\n", - "automl_settings = {\n", - " \"task\": \"forecasting\",\n", - " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", - " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", - " \"iterations\": 15,\n", - " \"experiment_timeout_hours\": 0.25,\n", - " \"label_column_name\": \"Quantity\",\n", - " \"n_cross_validations\": 3,\n", - " \"time_column_name\": \"WeekStarting\",\n", - " \"drop_column_names\": \"Revenue\",\n", - " \"max_horizon\": 6,\n", - " \"grain_column_names\": partition_column_names,\n", - " \"track_child_runs\": False,\n", - "}\n", - "\n", - "mm_paramters = ManyModelsTrainParameters(\n", - " automl_settings=automl_settings, partition_column_names=partition_column_names\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up many models pipeline" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Parallel run step is leveraged to train multiple models at once. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The process_count_per_node is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", - "\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **experiment** | The experiment used for training. |\n", - "| **train_data** | The file dataset to be used as input to the training run. |\n", - "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long. |\n", - "| **process_count_per_node** | Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance. |\n", - "| **train_pipeline_parameters** | The set of configuration parameters defined in the previous section. |\n", - "\n", - "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", - "\n", - "\n", - "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", - " experiment=experiment,\n", - " train_data=input_ds_small,\n", - " compute_target=compute_target,\n", - " node_count=2,\n", - " process_count_per_node=8,\n", - " run_invocation_timeout=920,\n", - " train_pipeline_parameters=mm_paramters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submit the pipeline to run\n", - "Next we submit our pipeline to run. The whole training pipeline takes about 40m using a STANDARD_D16S_V3 VM with our current ParallelRunConfig setting." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run = experiment.submit(training_pipeline)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "training_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check the run status, if training_run is in completed state, continue to forecasting. If training_run is in another state, check the portal for failures." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5.0 Publish and schedule the train pipeline (Optional)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.1 Publish the pipeline\n", - "\n", - "Once you have a pipeline you're happy with, you can publish a pipeline so you can call it programmatically later on. See this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-create-your-first-pipeline#publish-a-pipeline) for additional information on publishing and calling pipelines." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# published_pipeline = training_pipeline.publish(name = 'automl_train_many_models',\n", - "# description = 'train many models',\n", - "# version = '1',\n", - "# continue_on_step_failure = False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 7.2 Schedule the pipeline\n", - "You can also [schedule the pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-schedule-pipelines) to run on a time-based or change-based schedule. This could be used to automatically retrain models every month or based on another trigger such as data drift." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from azureml.pipeline.core import Schedule, ScheduleRecurrence\n", - "\n", - "# training_pipeline_id = published_pipeline.id\n", - "\n", - "# recurrence = ScheduleRecurrence(frequency=\"Month\", interval=1, start_time=\"2020-01-01T09:00:00\")\n", - "# recurring_schedule = Schedule.create(ws, name=\"automl_training_recurring_schedule\",\n", - "# description=\"Schedule Training Pipeline to run on the first day of every month\",\n", - "# pipeline_id=training_pipeline_id,\n", - "# experiment_name=experiment.name,\n", - "# recurrence=recurrence)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 6.0 Forecasting" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Set up output dataset for inference data\n", - "Output of inference can be represented as [OutputFileDatasetConfig](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.output_dataset_config.outputdatasetconfig?view=azure-ml-py) object and OutputFileDatasetConfig can be registered as a dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.data import OutputFileDatasetConfig\n", - "\n", - "output_inference_data_ds = OutputFileDatasetConfig(\n", - " name=\"many_models_inference_output\", destination=(dstore, \"oj/inference_data/\")\n", - ").register_on_complete(name=\"oj_inference_data_ds\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For many models we need to provide the ManyModelsInferenceParameters object.\n", - "\n", - "#### ManyModelsInferenceParameters arguments\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **partition_column_names** | List of column names that identifies groups. |\n", - "| **target_column_name** | \\[Optional] Column name only if the inference dataset has the target. |\n", - "| **time_column_name** | \\[Optional] Column name only if it is timeseries. |\n", - "| **many_models_run_id** | \\[Optional] Many models run id where models were trained. |\n", - "\n", - "#### get_many_models_batch_inference_steps arguments\n", - "| Property | Description|\n", - "| :--------------- | :------------------- |\n", - "| **experiment** | The experiment used for inference run. |\n", - "| **inference_data** | The data to use for inferencing. It should be the same schema as used for training.\n", - "| **compute_target** | The compute target that runs the inference pipeline.|\n", - "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku). |\n", - "| **process_count_per_node** | The number of processes per node.\n", - "| **train_run_id** | \\[Optional\\] The run id of the hierarchy training, by default it is the latest successful training many model run in the experiment. |\n", - "| **train_experiment_name** | \\[Optional\\] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline. |\n", - "| **process_count_per_node** | \\[Optional\\] The number of processes per node, by default it's 4. |" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", - "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", - " ManyModelsInferenceParameters,\n", - ")\n", - "\n", - "mm_parameters = ManyModelsInferenceParameters(\n", - " partition_column_names=[\"Store\", \"Brand\"],\n", - " time_column_name=\"WeekStarting\",\n", - " target_column_name=\"Quantity\",\n", - ")\n", - "\n", - "inference_steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", - " experiment=experiment,\n", - " inference_data=inference_ds_small,\n", - " node_count=2,\n", - " process_count_per_node=8,\n", - " compute_target=compute_target,\n", - " run_invocation_timeout=300,\n", - " output_datastore=output_inference_data_ds,\n", - " train_run_id=training_run.id,\n", - " train_experiment_name=training_run.experiment.name,\n", - " inference_pipeline_parameters=mm_parameters,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.pipeline.core import Pipeline\n", - "\n", - "inference_pipeline = Pipeline(ws, steps=inference_steps)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inference_run = experiment.submit(inference_pipeline)\n", - "inference_run.wait_for_completion(show_output=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve results\n", - "\n", - "The forecasting pipeline forecasts the orange juice quantity for a Store by Brand. The pipeline returns one file with the predictions for each store and outputs the result to the forecasting_output Blob container. The details of the blob container is listed in 'forecasting_output.txt' under Outputs+logs. \n", - "\n", - "The following code snippet:\n", - "1. Downloads the contents of the output folder that is passed in the parallel run step \n", - "2. Reads the parallel_run_step.txt file that has the predictions as pandas dataframe and \n", - "3. Displays the top 10 rows of the predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.contrib.automl.pipeline.steps.utilities import get_output_from_mm_pipeline\n", - "\n", - "forecasting_results_name = \"forecasting_results\"\n", - "forecasting_output_name = \"many_models_inference_output\"\n", - "forecast_file = get_output_from_mm_pipeline(\n", - " inference_run, forecasting_results_name, forecasting_output_name\n", - ")\n", - "df = pd.read_csv(forecast_file, delimiter=\" \", header=None)\n", - "df.columns = [\n", - " \"Week Starting\",\n", - " \"Store\",\n", - " \"Brand\",\n", - " \"Quantity\",\n", - " \"Advert\",\n", - " \"Price\",\n", - " \"Revenue\",\n", - " \"Predicted\",\n", - "]\n", - "print(\n", - " \"Prediction has \", df.shape[0], \" rows. Here the first 10 rows are being displayed.\"\n", - ")\n", - "df.head(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 7.0 Publish and schedule the inference pipeline (Optional)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 7.1 Publish the pipeline\n", - "\n", - "Once you have a pipeline you're happy with, you can publish a pipeline so you can call it programmatically later on. See this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-create-your-first-pipeline#publish-a-pipeline) for additional information on publishing and calling pipelines." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# published_pipeline_inf = inference_pipeline.publish(name = 'automl_forecast_many_models',\n", - "# description = 'forecast many models',\n", - "# version = '1',\n", - "# continue_on_step_failure = False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 7.2 Schedule the pipeline\n", - "You can also [schedule the pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-schedule-pipelines) to run on a time-based or change-based schedule. This could be used to automatically retrain or forecast models every month or based on another trigger such as data drift." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from azureml.pipeline.core import Schedule, ScheduleRecurrence\n", - "\n", - "# forecasting_pipeline_id = published_pipeline.id\n", - "\n", - "# recurrence = ScheduleRecurrence(frequency=\"Month\", interval=1, start_time=\"2020-01-01T09:00:00\")\n", - "# recurring_schedule = Schedule.create(ws, name=\"automl_forecasting_recurring_schedule\",\n", - "# description=\"Schedule Forecasting Pipeline to run on the first day of every week\",\n", - "# pipeline_id=forecasting_pipeline_id,\n", - "# experiment_name=experiment.name,\n", - "# recurrence=recurrence)" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) Microsoft Corporation. All rights reserved.\n", + "\n", + "Licensed under the MIT License." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Impressions](https://PixelServer20190423114238.azurewebsites.net/api/impressions/MachineLearningNotebooks/how-to-use-azureml/automated-machine-learning/forecasting-hierarchical-timeseries/auto-ml-forecasting-hierarchical-timeseries.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Many Models - Automated ML\n", + "**_Generate many models time series forecasts with Automated Machine Learning_**\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this notebook we are using a synthetic dataset portraying sales data to predict the the quantity of a vartiety of product skus across several states, stores, and product categories.\n", + "\n", + "**NOTE: There are limits on how many runs we can do in parallel per workspace, and we currently recommend to set the parallelism to maximum of 320 runs per experiment per workspace. If users want to have more parallelism and increase this limit they might encounter Too Many Requests errors (HTTP 429).**" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Prerequisites\n", + "You'll need to create a compute Instance by following the instructions in the [EnvironmentSetup.md](../Setup_Resources/EnvironmentSetup.md)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1.0 Set up workspace, datastore, experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003526897 } - ], - "metadata": { - "authors": [ - { - "name": "jialiu" - } - ], - "categories": [ - "how-to-use-azureml", - "automated-machine-learning" - ], - "kernelspec": { - "display_name": "Python 3.6", - "language": "python", - "name": "python36" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" + }, + "outputs": [], + "source": [ + "import azureml.core\n", + "from azureml.core import Workspace, Datastore\n", + "import pandas as pd\n", + "\n", + "# Set up your workspace\n", + "ws = Workspace.from_config()\n", + "ws.get_details()\n", + "\n", + "# Set up your datastores\n", + "dstore = ws.get_default_datastore()\n", + "\n", + "output = {}\n", + "output[\"SDK version\"] = azureml.core.VERSION\n", + "output[\"Subscription ID\"] = ws.subscription_id\n", + "output[\"Workspace\"] = ws.name\n", + "output[\"Resource Group\"] = ws.resource_group\n", + "output[\"Location\"] = ws.location\n", + "output[\"Default datastore name\"] = dstore.name\n", + "pd.set_option(\"display.max_colwidth\", -1)\n", + "outputDf = pd.DataFrame(data=output, index=[\"\"])\n", + "outputDf.T" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose an experiment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613003540729 + } + }, + "outputs": [], + "source": [ + "from azureml.core import Experiment\n", + "\n", + "experiment = Experiment(ws, \"automl-many-models\")\n", + "\n", + "print(\"Experiment name: \" + experiment.name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.0 Data\n", + "\n", + "This notebook uses simulated orange juice sales data to walk you through the process of training many models on Azure Machine Learning using Automated ML. \n", + "\n", + "The time series data used in this example was simulated based on the University of Chicago's Dominick's Finer Foods dataset which featured two years of sales of 3 different orange juice brands for individual stores. The full simulated dataset includes 3,991 stores with 3 orange juice brands each thus allowing 11,973 models to be trained to showcase the power of the many models pattern.\n", + "\n", + " \n", + "In this notebook, two datasets will be created: one with all 11,973 files and one with only 10 files that can be used to quickly test and debug. For each dataset, you'll be walked through the process of:\n", + "\n", + "1. Registering the blob container as a Datastore to the Workspace\n", + "2. Registering a tabular dataset to the Workspace" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } + } + }, + "source": [ + "### 2.1 Data Preparation\n", + "The OJ data is available in the public blob container. The data is split to be used for training and for inferencing. For the current dataset, the data was split on time column ('WeekStarting') before and after '1992-5-28' .\n", + "\n", + "The container has\n", + "
    \n", + "
  1. 'oj-data-tabular' and 'oj-inference-tabular' folders that contains training and inference data respectively for the 11,973 models.
  2. \n", + "
  3. It also has 'oj-data-small-tabular' and 'oj-inference-small-tabular' folders that has training and inference data for 10 models.
  4. \n", + "
\n", + "\n", + "To create the [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py) needed for the ParallelRunStep, you first need to register the blob container to the workspace." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "nteract": { + "transient": { + "deleting": false + } } + }, + "source": [ + " To use your own data, put your own data in a blobstore folder. As shown it can be one file or multiple files. We can then register datastore using that blob as shown below.\n", + " \n", + "

How sample data in blob store looks like

\n", + "\n", + "['oj-data-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)
\n", + "![image-4.png](mm-1.png)\n", + "\n", + "['oj-inference-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", + "![image-3.png](mm-2.png)\n", + "\n", + "['oj-data-small-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", + "\n", + "![image-5.png](mm-3.png)\n", + "\n", + "['oj-inference-small-tabular'](https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2F102a16c3-37d3-48a8-9237-4c9b1e8e80e0%2FresourceGroups%2FAutoMLSampleNotebooksData%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fautomlsamplenotebookdata/path/automl-sample-notebook-data/etag/%220x8D84EAA65DE50B7%22/defaultEncryptionScope/%24account-encryption-key/denyEncryptionScopeOverride//defaultId//publicAccessVal/Container)\n", + "![image-6.png](mm-4.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.2 Register the blob container as DataStore\n", + "\n", + "A Datastore is a place where data can be stored that is then made accessible to a compute either by means of mounting or copying the data to the compute target.\n", + "\n", + "Please refer to [Datastore](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.datastore(class)?view=azure-ml-py) documentation on how to access data from Datastore.\n", + "\n", + "In this next step, we will be registering blob storage as datastore to the Workspace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.core import Datastore\n", + "\n", + "# Please change the following to point to your own blob container and pass in account_key\n", + "blob_datastore_name = \"automl_many_models\"\n", + "container_name = \"automl-sample-notebook-data\"\n", + "account_name = \"automlsamplenotebookdata\"\n", + "\n", + "oj_datastore = Datastore.register_azure_blob_container(\n", + " workspace=ws,\n", + " datastore_name=blob_datastore_name,\n", + " container_name=container_name,\n", + " account_name=account_name,\n", + " create_if_not_exists=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.3 Using tabular datasets \n", + "\n", + "Now that the datastore is available from the Workspace, [TabularDataset](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabular_dataset.tabulardataset?view=azure-ml-py) can be created. Datasets in Azure Machine Learning are references to specific data in a Datastore. We are using TabularDataset, so that users who have their data which can be in one or many files (*.parquet or *.csv) and have not split up data according to group columns needed for training, can do so using out of box support for 'partiion_by' feature of TabularDataset shown in section 5.0 below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007017296 + } + }, + "outputs": [], + "source": [ + "from azureml.core import Dataset\n", + "\n", + "ds_name_small = \"oj-data-small-tabular\"\n", + "input_ds_small = Dataset.Tabular.from_delimited_files(\n", + " path=oj_datastore.path(ds_name_small + \"/\"), validate=False\n", + ")\n", + "\n", + "inference_name_small = \"oj-inference-small-tabular\"\n", + "inference_ds_small = Dataset.Tabular.from_delimited_files(\n", + " path=oj_datastore.path(inference_name_small + \"/\"), validate=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3.0 Build the training pipeline\n", + "Now that the dataset, WorkSpace, and datastore are set up, we can put together a pipeline for training.\n", + "\n", + "> Note that if you have an AzureML Data Scientist role, you will not have permission to create compute resources. Talk to your workspace or IT admin to create the compute targets described in this section, if they do not already exist." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Choose a compute target\n", + "\n", + "You will need to create a [compute target](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-set-up-training-targets#amlcompute) for your AutoML run. In this tutorial, you create AmlCompute as your training compute resource.\n", + "\n", + "\\*\\*Creation of AmlCompute takes approximately 5 minutes.**\n", + "\n", + "If the AmlCompute with that name is already in your workspace this code will skip the creation process. As with other Azure services, there are limits on certain resources (e.g. AmlCompute) associated with the Azure Machine Learning service. Please read this [article](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-manage-quotas) on the default limits and how to request more quota." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007037308 + } + }, + "outputs": [], + "source": [ + "from azureml.core.compute import ComputeTarget, AmlCompute\n", + "\n", + "# Name your cluster\n", + "compute_name = \"mm-compute\"\n", + "\n", + "\n", + "if compute_name in ws.compute_targets:\n", + " compute_target = ws.compute_targets[compute_name]\n", + " if compute_target and type(compute_target) is AmlCompute:\n", + " print(\"Found compute target: \" + compute_name)\n", + "else:\n", + " print(\"Creating a new compute target...\")\n", + " provisioning_config = AmlCompute.provisioning_configuration(\n", + " vm_size=\"STANDARD_D16S_V3\", max_nodes=20\n", + " )\n", + " # Create the compute target\n", + " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", + "\n", + " # Can poll for a minimum number of nodes and for a specific timeout.\n", + " # If no min node count is provided it will use the scale settings for the cluster\n", + " compute_target.wait_for_completion(\n", + " show_output=True, min_node_count=None, timeout_in_minutes=20\n", + " )\n", + "\n", + " # For a more detailed view of current cluster status, use the 'status' property\n", + " print(compute_target.status.serialize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up training parameters\n", + "\n", + "This dictionary defines the AutoML and many models settings. For this forecasting task we need to define several settings inncluding the name of the time column, the maximum forecast horizon, and the partition column name definition.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **task** | forecasting |\n", + "| **primary_metric** | This is the metric that you want to optimize.
Forecasting supports the following primary metrics
spearman_correlation
normalized_root_mean_squared_error
r2_score
normalized_mean_absolute_error |\n", + "| **blocked_models** | Blocked models won't be used by AutoML. |\n", + "| **iteration_timeout_minutes** | Maximum amount of time in minutes that the model can train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **iterations** | Number of models to train. This is optional but provides customers with greater control on exit criteria. |\n", + "| **experiment_timeout_hours** | Maximum amount of time in hours that the experiment can take before it terminates. This is optional but provides customers with greater control on exit criteria. |\n", + "| **label_column_name** | The name of the label column. |\n", + "| **forecast_horizon** | The forecast horizon is how many periods forward you would like to forecast. This integer horizon is in units of the timeseries frequency (e.g. daily, weekly). Periods are inferred from your data. |\n", + "| **n_cross_validations** | Number of cross validation splits. Rolling Origin Validation is used to split time-series in a temporally consistent way. |\n", + "| **enable_early_stopping** | Flag to enable early termination if the score is not improving in the short term. |\n", + "| **time_column_name** | The name of your time column. |\n", + "| **enable_engineered_explanations** | Engineered feature explanations will be downloaded if enable_engineered_explanations flag is set to True. By default it is set to False to save storage space. |\n", + "| **time_series_id_column_name** | The column names used to uniquely identify timeseries in data that has multiple rows with the same timestamp. |\n", + "| **track_child_runs** | Flag to disable tracking of child runs. Only best run is tracked if the flag is set to False (this includes the model and metrics of the run). |\n", + "| **pipeline_fetch_max_batch_size** | Determines how many pipelines (training algorithms) to fetch at a time for training, this helps reduce throttling when training at large scale. |\n", + "| **partition_column_names** | The names of columns used to group your models. For timeseries, the groups must not split up individual time-series. That is, each group must contain one or more whole time-series. |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "gather": { + "logged": 1613007061544 + } + }, + "outputs": [], + "source": [ + "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", + " ManyModelsTrainParameters,\n", + ")\n", + "\n", + "partition_column_names = [\"Store\", \"Brand\"]\n", + "automl_settings = {\n", + " \"task\": \"forecasting\",\n", + " \"primary_metric\": \"normalized_root_mean_squared_error\",\n", + " \"iteration_timeout_minutes\": 10, # This needs to be changed based on the dataset. We ask customer to explore how long training is taking before settings this value\n", + " \"iterations\": 15,\n", + " \"experiment_timeout_hours\": 0.25,\n", + " \"label_column_name\": \"Quantity\",\n", + " \"n_cross_validations\": 3,\n", + " \"time_column_name\": \"WeekStarting\",\n", + " \"drop_column_names\": \"Revenue\",\n", + " \"max_horizon\": 6,\n", + " \"grain_column_names\": partition_column_names,\n", + " \"track_child_runs\": False,\n", + "}\n", + "\n", + "mm_paramters = ManyModelsTrainParameters(\n", + " automl_settings=automl_settings, partition_column_names=partition_column_names\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up many models pipeline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parallel run step is leveraged to train multiple models at once. To configure the ParallelRunConfig you will need to determine the appropriate number of workers and nodes for your use case. The process_count_per_node is based off the number of cores of the compute VM. The node_count will determine the number of master nodes to use, increasing the node count will speed up the training process.\n", + "\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **experiment** | The experiment used for training. |\n", + "| **train_data** | The file dataset to be used as input to the training run. |\n", + "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with 3 and increase the node_count if the training time is taking too long. |\n", + "| **process_count_per_node** | Process count per node, we recommend 2:1 ratio for number of cores: number of processes per node. eg. If node has 16 cores then configure 8 or less process count per node or optimal performance. |\n", + "| **train_pipeline_parameters** | The set of configuration parameters defined in the previous section. |\n", + "\n", + "Calling this method will create a new aggregated dataset which is generated dynamically on pipeline execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", + "\n", + "\n", + "training_pipeline_steps = AutoMLPipelineBuilder.get_many_models_train_steps(\n", + " experiment=experiment,\n", + " train_data=input_ds_small,\n", + " compute_target=compute_target,\n", + " node_count=2,\n", + " process_count_per_node=8,\n", + " run_invocation_timeout=920,\n", + " train_pipeline_parameters=mm_paramters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "training_pipeline = Pipeline(ws, steps=training_pipeline_steps)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Submit the pipeline to run\n", + "Next we submit our pipeline to run. The whole training pipeline takes about 40m using a STANDARD_D16S_V3 VM with our current ParallelRunConfig setting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run = experiment.submit(training_pipeline)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "training_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check the run status, if training_run is in completed state, continue to forecasting. If training_run is in another state, check the portal for failures." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5.0 Publish and schedule the train pipeline (Optional)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 5.1 Publish the pipeline\n", + "\n", + "Once you have a pipeline you're happy with, you can publish a pipeline so you can call it programmatically later on. See this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-create-your-first-pipeline#publish-a-pipeline) for additional information on publishing and calling pipelines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# published_pipeline = training_pipeline.publish(name = 'automl_train_many_models',\n", + "# description = 'train many models',\n", + "# version = '1',\n", + "# continue_on_step_failure = False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.2 Schedule the pipeline\n", + "You can also [schedule the pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-schedule-pipelines) to run on a time-based or change-based schedule. This could be used to automatically retrain models every month or based on another trigger such as data drift." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from azureml.pipeline.core import Schedule, ScheduleRecurrence\n", + "\n", + "# training_pipeline_id = published_pipeline.id\n", + "\n", + "# recurrence = ScheduleRecurrence(frequency=\"Month\", interval=1, start_time=\"2020-01-01T09:00:00\")\n", + "# recurring_schedule = Schedule.create(ws, name=\"automl_training_recurring_schedule\",\n", + "# description=\"Schedule Training Pipeline to run on the first day of every month\",\n", + "# pipeline_id=training_pipeline_id,\n", + "# experiment_name=experiment.name,\n", + "# recurrence=recurrence)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6.0 Forecasting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Set up output dataset for inference data\n", + "Output of inference can be represented as [OutputFileDatasetConfig](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.output_dataset_config.outputdatasetconfig?view=azure-ml-py) object and OutputFileDatasetConfig can be registered as a dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.data import OutputFileDatasetConfig\n", + "\n", + "output_inference_data_ds = OutputFileDatasetConfig(\n", + " name=\"many_models_inference_output\", destination=(dstore, \"oj/inference_data/\")\n", + ").register_on_complete(name=\"oj_inference_data_ds\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For many models we need to provide the ManyModelsInferenceParameters object.\n", + "\n", + "#### ManyModelsInferenceParameters arguments\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **partition_column_names** | List of column names that identifies groups. |\n", + "| **target_column_name** | \\[Optional] Column name only if the inference dataset has the target. |\n", + "| **time_column_name** | \\[Optional] Column name only if it is timeseries. |\n", + "| **many_models_run_id** | \\[Optional] Many models run id where models were trained. |\n", + "\n", + "#### get_many_models_batch_inference_steps arguments\n", + "| Property | Description|\n", + "| :--------------- | :------------------- |\n", + "| **experiment** | The experiment used for inference run. |\n", + "| **inference_data** | The data to use for inferencing. It should be the same schema as used for training.\n", + "| **compute_target** The compute target that runs the inference pipeline.|\n", + "| **node_count** | The number of compute nodes to be used for running the user script. We recommend to start with the number of cores per node (varies by compute sku). |\n", + "| **process_count_per_node** The number of processes per node.\n", + "| **train_run_id** | \\[Optional] The run id of the hierarchy training, by default it is the latest successful training many model run in the experiment. |\n", + "| **train_experiment_name** | \\[Optional] The train experiment that contains the train pipeline. This one is only needed when the train pipeline is not in the same experiement as the inference pipeline. |\n", + "| **process_count_per_node** | \\[Optional] The number of processes per node, by default it's 4. |" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps import AutoMLPipelineBuilder\n", + "from azureml.train.automl.runtime._many_models.many_models_parameters import (\n", + " ManyModelsInferenceParameters,\n", + ")\n", + "\n", + "mm_parameters = ManyModelsInferenceParameters(\n", + " partition_column_names=[\"Store\", \"Brand\"],\n", + " time_column_name=\"WeekStarting\",\n", + " target_column_name=\"Quantity\",\n", + ")\n", + "\n", + "inference_steps = AutoMLPipelineBuilder.get_many_models_batch_inference_steps(\n", + " experiment=experiment,\n", + " inference_data=inference_ds_small,\n", + " node_count=2,\n", + " process_count_per_node=8,\n", + " compute_target=compute_target,\n", + " run_invocation_timeout=300,\n", + " output_datastore=output_inference_data_ds,\n", + " train_run_id=training_run.id,\n", + " train_experiment_name=training_run.experiment.name,\n", + " inference_pipeline_parameters=mm_parameters,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.pipeline.core import Pipeline\n", + "\n", + "inference_pipeline = Pipeline(ws, steps=inference_steps)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inference_run = experiment.submit(inference_pipeline)\n", + "inference_run.wait_for_completion(show_output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Retrieve results\n", + "\n", + "The forecasting pipeline forecasts the orange juice quantity for a Store by Brand. The pipeline returns one file with the predictions for each store and outputs the result to the forecasting_output Blob container. The details of the blob container is listed in 'forecasting_output.txt' under Outputs+logs. \n", + "\n", + "The following code snippet:\n", + "1. Downloads the contents of the output folder that is passed in the parallel run step \n", + "2. Reads the parallel_run_step.txt file that has the predictions as pandas dataframe and \n", + "3. Displays the top 10 rows of the predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azureml.contrib.automl.pipeline.steps.utilities import get_output_from_mm_pipeline\n", + "\n", + "forecasting_results_name = \"forecasting_results\"\n", + "forecasting_output_name = \"many_models_inference_output\"\n", + "forecast_file = get_output_from_mm_pipeline(\n", + " inference_run, forecasting_results_name, forecasting_output_name\n", + ")\n", + "df = pd.read_csv(forecast_file, delimiter=\" \", header=None)\n", + "df.columns = [\n", + " \"Week Starting\",\n", + " \"Store\",\n", + " \"Brand\",\n", + " \"Quantity\",\n", + " \"Advert\",\n", + " \"Price\",\n", + " \"Revenue\",\n", + " \"Predicted\",\n", + "]\n", + "print(\n", + " \"Prediction has \", df.shape[0], \" rows. Here the first 10 rows are being displayed.\"\n", + ")\n", + "df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7.0 Publish and schedule the inference pipeline (Optional)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.1 Publish the pipeline\n", + "\n", + "Once you have a pipeline you're happy with, you can publish a pipeline so you can call it programmatically later on. See this [tutorial](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-create-your-first-pipeline#publish-a-pipeline) for additional information on publishing and calling pipelines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# published_pipeline_inf = inference_pipeline.publish(name = 'automl_forecast_many_models',\n", + "# description = 'forecast many models',\n", + "# version = '1',\n", + "# continue_on_step_failure = False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 7.2 Schedule the pipeline\n", + "You can also [schedule the pipeline](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-schedule-pipelines) to run on a time-based or change-based schedule. This could be used to automatically retrain or forecast models every month or based on another trigger such as data drift." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from azureml.pipeline.core import Schedule, ScheduleRecurrence\n", + "\n", + "# forecasting_pipeline_id = published_pipeline.id\n", + "\n", + "# recurrence = ScheduleRecurrence(frequency=\"Month\", interval=1, start_time=\"2020-01-01T09:00:00\")\n", + "# recurring_schedule = Schedule.create(ws, name=\"automl_forecasting_recurring_schedule\",\n", + "# description=\"Schedule Forecasting Pipeline to run on the first day of every week\",\n", + "# pipeline_id=forecasting_pipeline_id,\n", + "# experiment_name=experiment.name,\n", + "# recurrence=recurrence)" + ] + } + ], + "metadata": { + "authors": [ + { + "name": "jialiu" + } + ], + "categories": [ + "how-to-use-azureml", + "automated-machine-learning" + ], + "kernelspec": { + "display_name": "Python 3.6 - AzureML", + "language": "python", + "name": "python3-azureml" }, - "nbformat": 4, - "nbformat_minor": 4 -} \ No newline at end of file + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/01_userfilesupdate.PNG b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/01_userfilesupdate.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6b46a9c02159fe869c40c6dd2418c430ca61524f GIT binary patch literal 33143 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 YsjRM4uFR;PcYeOTL0r@=n#R+ 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 literal 0 HcmV?d00001 diff --git a/how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/ai show.gif b/how-to-use-azureml/automated-machine-learning/forecasting-many-models/images/ai show.gif new file mode 100644 index 0000000000000000000000000000000000000000..98d280ae05fa8b8a0978cb383a16947b49039b97 GIT binary patch literal 2760226 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

uhki3X?fu?>9?^u9BsWSN~lZS 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

g9G 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 z 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