From aae88e87eae563d023f9d98d041369a07a63db37 Mon Sep 17 00:00:00 2001 From: amlrelsa-ms Date: Mon, 5 Aug 2024 21:57:46 +0000 Subject: [PATCH] update samples from Release-240 as a part of 1.57.0 SDK stable release --- configuration.ipynb | 2 +- .../fairlearn-azureml-mitigation.ipynb | 2 +- .../fairness/fairlearn-azureml-mitigation.yml | 2 +- .../fairness/upload-fairness-dashboard.ipynb | 2 +- .../fairness/upload-fairness-dashboard.yml | 2 +- .../automated-machine-learning/automl_env.yml | 10 +- .../automl_env_linux.yml | 12 +- .../automl_env_mac.yml | 12 +- .../codegen-for-autofeaturization.ipynb | 2 +- ...-training-from-autofeaturization-run.ipynb | 2 +- ...tion-credit-card-fraud-local-managed.ipynb | 420 -------- .../auto-ml-regression-model-proxy.ipynb | 2 +- .../auto-ml-forecasting-many-models.ipynb | 2 +- .../explain-model-on-amlcompute.ipynb | 8 +- .../remote-explanation/train_explain.py | 14 +- .../aml-pipelines-data-transfer.ipynb | 128 +-- ...nes-parameter-tuning-with-hyperdrive.ipynb | 2 +- .../intro-to-pipelines/tf_mnist.py | 2 +- ...erparameter-tune-deploy-with-sklearn.ipynb | 2 +- .../reinforcement-learning/README.md | 2 - .../cartpole_ci.ipynb | 768 --------------- .../files/cartpole-ppo.yaml | 23 - .../files/cartpole_rollout.py | 108 --- .../files/cartpole_training.py | 34 - .../files/docker/Dockerfile | 27 - .../files/utils/callbacks.py | 22 - .../files/utils/misc.py | 13 - .../images/cartpole.png | Bin 1346 -> 0 bytes .../cartpole_sc.ipynb | 917 ------------------ .../files/cartpole-ppo.yaml | 24 - .../files/cartpole_rollout.py | 108 --- .../files/cartpole_training.py | 34 - .../files/docker/Dockerfile | 35 - .../files/utils/callbacks.py | 22 - .../files/utils/misc.py | 13 - .../images/cartpole.png | Bin 1346 -> 0 bytes .../logging-api/logging-api.ipynb | 2 +- .../train-remote/train-remote.ipynb | 3 +- index.md | 3 - setup-environment/configuration.ipynb | 2 +- .../quickstart-azureml-in-10mins.ipynb | 2 +- .../quickstart-azureml-python-sdk.ipynb | 2 +- .../create-first-ml-experiment/imgs/flow2.png | Bin 0 -> 106278 bytes .../img-classification-part1-training.ipynb | 4 +- ...lassification-part3-deploy-encrypted.ipynb | 2 +- 45 files changed, 113 insertions(+), 2685 deletions(-) delete mode 100644 how-to-use-azureml/automated-machine-learning/experimental/classification-credit-card-fraud-local-managed/auto-ml-classification-credit-card-fraud-local-managed.ipynb delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/cartpole_ci.ipynb delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole-ppo.yaml delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_rollout.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_training.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/docker/Dockerfile delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/callbacks.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/misc.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/images/cartpole.png delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/cartpole_sc.ipynb delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole-ppo.yaml delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_rollout.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_training.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/docker/Dockerfile delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/callbacks.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/misc.py delete mode 100644 how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/images/cartpole.png create mode 100644 tutorials/create-first-ml-experiment/imgs/flow2.png diff --git a/configuration.ipynb b/configuration.ipynb index 93aca3a2..a1595493 100644 --- a/configuration.ipynb +++ b/configuration.ipynb @@ -103,7 +103,7 @@ "source": [ "import azureml.core\n", "\n", - "print(\"This notebook was created using version 1.56.0 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version 1.57.0 of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, diff --git a/contrib/fairness/fairlearn-azureml-mitigation.ipynb b/contrib/fairness/fairlearn-azureml-mitigation.ipynb index 83cf336c..ec5cdf72 100644 --- a/contrib/fairness/fairlearn-azureml-mitigation.ipynb +++ b/contrib/fairness/fairlearn-azureml-mitigation.ipynb @@ -194,7 +194,7 @@ "categorical_transformer = Pipeline(\n", " [\n", " (\"impute\", SimpleImputer(strategy=\"most_frequent\")),\n", - " (\"ohe\", OneHotEncoder(handle_unknown=\"ignore\", sparse=False)),\n", + " (\"ohe\", OneHotEncoder(handle_unknown=\"ignore\", sparse_output=False)),\n", " ]\n", ")\n", "\n", diff --git a/contrib/fairness/fairlearn-azureml-mitigation.yml b/contrib/fairness/fairlearn-azureml-mitigation.yml index b222c62e..b3641506 100644 --- a/contrib/fairness/fairlearn-azureml-mitigation.yml +++ b/contrib/fairness/fairlearn-azureml-mitigation.yml @@ -6,7 +6,7 @@ dependencies: - fairlearn>=0.6.2,<=0.7.0 - joblib - liac-arff - - raiwidgets~=0.33.0 + - raiwidgets~=0.36.0 - itsdangerous==2.0.1 - markupsafe<2.1.0 - protobuf==3.20.0 diff --git a/contrib/fairness/upload-fairness-dashboard.ipynb b/contrib/fairness/upload-fairness-dashboard.ipynb index 5f204d21..312d5893 100644 --- a/contrib/fairness/upload-fairness-dashboard.ipynb +++ b/contrib/fairness/upload-fairness-dashboard.ipynb @@ -209,7 +209,7 @@ "categorical_transformer = Pipeline(\n", " [\n", " (\"impute\", SimpleImputer(strategy=\"most_frequent\")),\n", - " (\"ohe\", OneHotEncoder(handle_unknown=\"ignore\", sparse=False)),\n", + " (\"ohe\", OneHotEncoder(handle_unknown=\"ignore\", sparse_output=False)),\n", " ]\n", ")\n", "\n", diff --git a/contrib/fairness/upload-fairness-dashboard.yml b/contrib/fairness/upload-fairness-dashboard.yml index 89be9978..d52dbd8f 100644 --- a/contrib/fairness/upload-fairness-dashboard.yml +++ b/contrib/fairness/upload-fairness-dashboard.yml @@ -6,7 +6,7 @@ dependencies: - fairlearn>=0.6.2,<=0.7.0 - joblib - liac-arff - - raiwidgets~=0.33.0 + - raiwidgets~=0.36.0 - itsdangerous==2.0.1 - markupsafe<2.1.0 - protobuf==3.20.0 diff --git a/how-to-use-azureml/automated-machine-learning/automl_env.yml b/how-to-use-azureml/automated-machine-learning/automl_env.yml index 9ffb33b3..a01247aa 100644 --- a/how-to-use-azureml/automated-machine-learning/automl_env.yml +++ b/how-to-use-azureml/automated-machine-learning/automl_env.yml @@ -14,14 +14,14 @@ dependencies: - pip: # Required packages for AzureML execution, history, and data preparation. - - azureml-widgets~=1.56.0 - - azureml-defaults~=1.56.0 - - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.56.0/validated_win32_requirements.txt [--no-deps] + - azureml-widgets~=1.57.0 + - azureml-defaults~=1.57.0 + - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.57.0/validated_win32_requirements.txt [--no-deps] - matplotlib==3.7.1 - xgboost==1.5.2 - prophet==1.1.4 - pandas==1.3.5 - cmdstanpy==1.1.0 - setuptools-git==1.2 - - spacy==3.4.4 - - https://aka.ms/automl-resources/packages/en_core_web_sm-3.4.1.tar.gz + - spacy==3.7.4 + - https://aka.ms/automl-resources/packages/en_core_web_sm-3.7.1.tar.gz diff --git a/how-to-use-azureml/automated-machine-learning/automl_env_linux.yml b/how-to-use-azureml/automated-machine-learning/automl_env_linux.yml index b7a0b10a..a7d1facf 100644 --- a/how-to-use-azureml/automated-machine-learning/automl_env_linux.yml +++ b/how-to-use-azureml/automated-machine-learning/automl_env_linux.yml @@ -12,7 +12,7 @@ dependencies: - numpy>=1.21.6,<=1.23.5 - urllib3==1.26.7 - scipy==1.10.1 -- scikit-learn==1.1.3 +- scikit-learn==1.5.1 - holidays==0.29 - pytorch::pytorch=1.11.0 - cudatoolkit=10.1.243 @@ -20,11 +20,11 @@ dependencies: - pip: # Required packages for AzureML execution, history, and data preparation. - - azureml-widgets~=1.56.0 - - azureml-defaults~=1.56.0 + - azureml-widgets~=1.57.0 + - azureml-defaults~=1.57.0 - pytorch-transformers==1.0.0 - - spacy==3.4.4 + - spacy==3.7.4 - xgboost==1.5.2 - prophet==1.1.4 - - https://aka.ms/automl-resources/packages/en_core_web_sm-3.4.1.tar.gz - - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.56.0/validated_linux_requirements.txt [--no-deps] + - https://aka.ms/automl-resources/packages/en_core_web_sm-3.7.1.tar.gz + - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.57.0/validated_linux_requirements.txt [--no-deps] diff --git a/how-to-use-azureml/automated-machine-learning/automl_env_mac.yml b/how-to-use-azureml/automated-machine-learning/automl_env_mac.yml index bac39513..3372598d 100644 --- a/how-to-use-azureml/automated-machine-learning/automl_env_mac.yml +++ b/how-to-use-azureml/automated-machine-learning/automl_env_mac.yml @@ -10,17 +10,17 @@ dependencies: - python>=3.10,<3.11 - numpy>=1.21.6,<=1.23.5 - scipy==1.10.1 -- scikit-learn==1.1.3 +- scikit-learn==1.5.1 - holidays==0.29 - pip: # Required packages for AzureML execution, history, and data preparation. - - azureml-widgets~=1.56.0 - - azureml-defaults~=1.56.0 + - azureml-widgets~=1.57.0 + - azureml-defaults~=1.57.0 - pytorch-transformers==1.0.0 - prophet==1.1.4 - xgboost==1.5.2 - - spacy==3.4.4 + - spacy==3.7.4 - matplotlib==3.7.1 - - https://aka.ms/automl-resources/packages/en_core_web_sm-3.4.1.tar.gz - - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.56.0/validated_darwin_requirements.txt [--no-deps] + - https://aka.ms/automl-resources/packages/en_core_web_sm-3.7.1.tar.gz + - -r https://automlsdkdataresources.blob.core.windows.net/validated-requirements/1.57.0/validated_darwin_requirements.txt [--no-deps] diff --git a/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-codegen/codegen-for-autofeaturization.ipynb b/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-codegen/codegen-for-autofeaturization.ipynb index 6af7da1e..2156eb36 100644 --- a/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-codegen/codegen-for-autofeaturization.ipynb +++ b/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-codegen/codegen-for-autofeaturization.ipynb @@ -97,7 +97,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"This notebook was created using version 1.56.0 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version 1.57.0 of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, diff --git a/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-custom-model-training/custom-model-training-from-autofeaturization-run.ipynb b/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-custom-model-training/custom-model-training-from-autofeaturization-run.ipynb index c4e13504..1bc059bc 100644 --- a/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-custom-model-training/custom-model-training-from-autofeaturization-run.ipynb +++ b/how-to-use-azureml/automated-machine-learning/experimental/autofeaturization-custom-model-training/custom-model-training-from-autofeaturization-run.ipynb @@ -97,7 +97,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"This notebook was created using version 1.56.0 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version 1.57.0 of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, diff --git a/how-to-use-azureml/automated-machine-learning/experimental/classification-credit-card-fraud-local-managed/auto-ml-classification-credit-card-fraud-local-managed.ipynb b/how-to-use-azureml/automated-machine-learning/experimental/classification-credit-card-fraud-local-managed/auto-ml-classification-credit-card-fraud-local-managed.ipynb deleted file mode 100644 index 1c2e67ff..00000000 --- a/how-to-use-azureml/automated-machine-learning/experimental/classification-credit-card-fraud-local-managed/auto-ml-classification-credit-card-fraud-local-managed.ipynb +++ /dev/null @@ -1,420 +0,0 @@ -{ - "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/experimental/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 local managed 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 local managed 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 local managed 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", - "import pandas as pd\n", - "\n", - "import azureml.core\n", - "from azureml.core.compute_target import LocalTarget\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.56.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-local-managed'\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', None)\n", - "outputDf = pd.DataFrame(data = output, index = [''])\n", - "outputDf.T" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Determine if local docker is configured for Linux images\n", - "\n", - "Local managed runs will leverage a Linux docker container to submit the run to. Due to this, the docker needs to be configured to use Linux containers." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check if Docker is installed and Linux containers are enabled\n", - "import subprocess\n", - "from subprocess import CalledProcessError\n", - "try:\n", - " assert subprocess.run(\"docker -v\", shell=True).returncode == 0, 'Local Managed runs require docker to be installed.'\n", - " out = subprocess.check_output(\"docker system info\", shell=True).decode('ascii')\n", - " assert \"OSType: linux\" in out, 'Docker engine needs to be configured to use Linux containers.' \\\n", - " 'https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers'\n", - "except CalledProcessError as ex:\n", - " raise Exception('Local Managed runs require docker to be installed.') from ex" - ] - }, - { - "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", - "|**enable_local_managed**|Enable the experimental local-managed scenario.|\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", - " \"experiment_timeout_hours\": 0.3, #for real scenarios we recommend a timeout of at least one hour \n", - " \"verbosity\": logging.INFO,\n", - "}\n", - "\n", - "automl_config = AutoMLConfig(task = 'classification',\n", - " debug_log = 'automl_errors.log',\n", - " compute_target = LocalTarget(),\n", - " enable_local_managed = True,\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": [ - "parent_run = experiment.submit(automl_config, show_output = True)" - ] - }, - { - "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", - "#parent_run = AutoMLRun(experiment = experiment, run_id = '')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "parent_run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Results" - ] - }, - { - "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 Child Run\n", - "\n", - "Below we select the best pipeline from our iterations. The `get_best_child` method returns the best run. Overloads on `get_best_child` allow you to retrieve the best run for *any* logged metric." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "best_run = parent_run.get_best_child()\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": [ - "X_test_df = validation_data.drop_columns(columns=[label_column_name])\n", - "y_test_df = validation_data.keep_columns(columns=[label_column_name], validate=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Creating ModelProxy for submitting prediction runs to the training environment.\n", - "We will create a ModelProxy for the best child run, which will allow us to submit a run that does the prediction in the training environment. Unlike the local client, which can have different versions of some libraries, the training environment will have all the compatible libraries for the model already." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azureml.train.automl.model_proxy import ModelProxy\n", - "best_model_proxy = ModelProxy(best_run)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# call the predict functions on the model proxy\n", - "y_pred = best_model_proxy.predict(X_test_df).to_pandas_dataframe()\n", - "y_pred" - ] - }, - { - "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", - "\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\u0192\u00c2\u00a9 Libre de Bruxelles) on big data mining and fraud detection. More details on current and past projects on related topics are available on https://www.researchgate.net and the page of the DefeatFraud project\n", - "Please cite the following works: \n", - "\u00c3\u00a2\u00e2\u201a\u00ac\u00c2\u00a2\tAndrea 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", - "\u00c3\u00a2\u00e2\u201a\u00ac\u00c2\u00a2\tDal 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", - "\u00c3\u00a2\u00e2\u201a\u00ac\u00c2\u00a2\tDal 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", - "o\tDal Pozzolo, Andrea Adaptive Machine learning for credit card fraud detection ULB MLG PhD thesis (supervised by G. Bontempi)\n", - "\u00c3\u00a2\u00e2\u201a\u00ac\u00c2\u00a2\tCarcillo, Fabrizio; Dal Pozzolo, Andrea; Le Borgne, Yann-A\u00c3\u0192\u00c2\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", - "\u00c3\u00a2\u00e2\u201a\u00ac\u00c2\u00a2\tCarcillo, Fabrizio; Le Borgne, Yann-A\u00c3\u0192\u00c2\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" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "sekrupa" - } - ], - "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.8 - AzureML", - "language": "python", - "name": "python38-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": [ - "AutomatedML" - ], - "task": "Classification", - "version": "3.6.7" - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file diff --git a/how-to-use-azureml/automated-machine-learning/experimental/regression-model-proxy/auto-ml-regression-model-proxy.ipynb b/how-to-use-azureml/automated-machine-learning/experimental/regression-model-proxy/auto-ml-regression-model-proxy.ipynb index 624f078c..830f38d6 100644 --- a/how-to-use-azureml/automated-machine-learning/experimental/regression-model-proxy/auto-ml-regression-model-proxy.ipynb +++ b/how-to-use-azureml/automated-machine-learning/experimental/regression-model-proxy/auto-ml-regression-model-proxy.ipynb @@ -91,7 +91,7 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"This notebook was created using version 1.56.0 of the Azure ML SDK\")\n", + "print(\"This notebook was created using version 1.57.0 of the Azure ML SDK\")\n", "print(\"You are currently using version\", azureml.core.VERSION, \"of the Azure ML SDK\")" ] }, 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 660add29..480bfbed 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 @@ -366,7 +366,7 @@ "USE_CURATED_ENV = True\n", "if USE_CURATED_ENV:\n", " curated_environment = Environment.get(\n", - " workspace=ws, name=\"AzureML-sklearn-0.24-ubuntu18.04-py37-cpu\"\n", + " workspace=ws, name=\"AzureML-sklearn-1.5\"\n", " )\n", " aml_run_config.environment = curated_environment\n", "else:\n", diff --git a/how-to-use-azureml/explain-model/azure-integration/remote-explanation/explain-model-on-amlcompute.ipynb b/how-to-use-azureml/explain-model/azure-integration/remote-explanation/explain-model-on-amlcompute.ipynb index 1189953b..2e8d8d37 100644 --- a/how-to-use-azureml/explain-model/azure-integration/remote-explanation/explain-model-on-amlcompute.ipynb +++ b/how-to-use-azureml/explain-model/azure-integration/remote-explanation/explain-model-on-amlcompute.ipynb @@ -53,7 +53,7 @@ "\n", "We will showcase one of the tabular data explainers: TabularExplainer (SHAP).\n", "\n", - "Problem: Boston Housing Price Prediction with scikit-learn (train a model and run an explainer remotely via AMLCompute, and download and visualize the remotely-calculated explanations.)\n", + "Problem: Housing Price Prediction with scikit-learn (train a model and run an explainer remotely via AMLCompute, and download and visualize the remotely-calculated explanations.)\n", "\n", "| ![explanations-run-history](./img/explanations-run-history.png) |\n", "|:--:|\n" @@ -429,8 +429,8 @@ "outputs": [], "source": [ "# Retrieve x_test for visualization\n", - "x_test_path = './x_test_boston_housing.pkl'\n", - "run.download_file('x_test_boston_housing.pkl', output_file_path=x_test_path)" + "x_test_path = './x_test_california_housing.pkl'\n", + "run.download_file('x_test_california_housing.pkl', output_file_path=x_test_path)" ] }, { @@ -439,7 +439,7 @@ "metadata": {}, "outputs": [], "source": [ - "x_test = joblib.load('x_test_boston_housing.pkl')" + "x_test = joblib.load('x_test_california_housing.pkl')" ] }, { diff --git a/how-to-use-azureml/explain-model/azure-integration/remote-explanation/train_explain.py b/how-to-use-azureml/explain-model/azure-integration/remote-explanation/train_explain.py index 4b2879c0..8afb5144 100644 --- a/how-to-use-azureml/explain-model/azure-integration/remote-explanation/train_explain.py +++ b/how-to-use-azureml/explain-model/azure-integration/remote-explanation/train_explain.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft. All rights reserved. # Licensed under the MIT license. -from sklearn import datasets +from sklearn.datasets import fetch_california_housing from sklearn.linear_model import Ridge from interpret.ext.blackbox import TabularExplainer from azureml.interpret import ExplanationClient @@ -14,20 +14,20 @@ import numpy as np OUTPUT_DIR = './outputs/' os.makedirs(OUTPUT_DIR, exist_ok=True) -boston_data = datasets.load_boston() +california_data = fetch_california_housing() run = Run.get_context() client = ExplanationClient.from_run(run) -X_train, X_test, y_train, y_test = train_test_split(boston_data.data, - boston_data.target, +X_train, X_test, y_train, y_test = train_test_split(california_data.data, + california_data.target, test_size=0.2, random_state=0) # write x_test out as a pickle file for later visualization x_test_pkl = 'x_test.pkl' with open(x_test_pkl, 'wb') as file: joblib.dump(value=X_test, filename=os.path.join(OUTPUT_DIR, x_test_pkl)) -run.upload_file('x_test_boston_housing.pkl', os.path.join(OUTPUT_DIR, x_test_pkl)) +run.upload_file('x_test_california_housing.pkl', os.path.join(OUTPUT_DIR, x_test_pkl)) alpha = 0.5 @@ -50,7 +50,7 @@ original_model = run.register_model(model_name='model_explain_model_on_amlcomp', model_path='original_model.pkl') # Explain predictions on your local machine -tabular_explainer = TabularExplainer(model, X_train, features=boston_data.feature_names) +tabular_explainer = TabularExplainer(model, X_train, features=california_data.feature_names) # Explain overall model predictions (global explanation) # Passing in test dataset for evaluation examples - note it must be a representative sample of the original data @@ -60,5 +60,5 @@ global_explanation = tabular_explainer.explain_global(X_test) # Uploading model explanation data for storage or visualization in webUX # The explanation can then be downloaded on any compute -comment = 'Global explanation on regression model trained on boston dataset' +comment = 'Global explanation on regression model trained on california dataset' client.upload_model_explanation(global_explanation, comment=comment, model_id=original_model.id) diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.ipynb b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.ipynb index 0d624c5c..59bc931b 100644 --- a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.ipynb +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-data-transfer.ipynb @@ -125,29 +125,29 @@ }, "outputs": [], "source": [ - "from azureml.exceptions import UserErrorException\n", - "\n", - "blob_datastore_name='MyBlobDatastore'\n", - "account_name=os.getenv(\"BLOB_ACCOUNTNAME_62\", \"\") # Storage account name\n", - "container_name=os.getenv(\"BLOB_CONTAINER_62\", \"\") # Name of Azure blob container\n", - "account_key=os.getenv(\"BLOB_ACCOUNT_KEY_62\", \"\") # Storage account key\n", - "\n", - "try:\n", - " blob_datastore = Datastore.get(ws, blob_datastore_name)\n", - " print(\"Found Blob Datastore with name: %s\" % blob_datastore_name)\n", - "except UserErrorException:\n", - " blob_datastore = Datastore.register_azure_blob_container(\n", - " workspace=ws,\n", - " datastore_name=blob_datastore_name,\n", - " account_name=account_name, # Storage account name\n", - " container_name=container_name, # Name of Azure blob container\n", - " account_key=account_key) # Storage account key\n", - " print(\"Registered blob datastore with name: %s\" % blob_datastore_name)\n", - "\n", - "blob_data_ref = DataReference(\n", - " datastore=blob_datastore,\n", - " data_reference_name=\"blob_test_data\",\n", - " path_on_datastore=\"testdata\")" + "# from azureml.exceptions import UserErrorException\n", + "#\n", + "# blob_datastore_name='MyBlobDatastore'\n", + "# account_name=os.getenv(\"BLOB_ACCOUNTNAME_62\", \"\") # Storage account name\n", + "# container_name=os.getenv(\"BLOB_CONTAINER_62\", \"\") # Name of Azure blob container\n", + "# account_key=os.getenv(\"BLOB_ACCOUNT_KEY_62\", \"\") # Storage account key\n", + "#\n", + "# try:\n", + "# blob_datastore = Datastore.get(ws, blob_datastore_name)\n", + "# print(\"Found Blob Datastore with name: %s\" % blob_datastore_name)\n", + "# except UserErrorException:\n", + "# blob_datastore = Datastore.register_azure_blob_container(\n", + "# workspace=ws,\n", + "# datastore_name=blob_datastore_name,\n", + "# account_name=account_name, # Storage account name\n", + "# container_name=container_name, # Name of Azure blob container\n", + "# account_key=account_key) # Storage account key\n", + "# print(\"Registered blob datastore with name: %s\" % blob_datastore_name)\n", + "#\n", + "# blob_data_ref = DataReference(\n", + "# datastore=blob_datastore,\n", + "# data_reference_name=\"blob_test_data\",\n", + "# path_on_datastore=\"testdata\")" ] }, { @@ -341,24 +341,24 @@ "metadata": {}, "outputs": [], "source": [ - "data_factory_name = 'adftest'\n", - "\n", - "def get_or_create_data_factory(workspace, factory_name):\n", - " try:\n", - " return DataFactoryCompute(workspace, factory_name)\n", - " except ComputeTargetException as e:\n", - " if 'ComputeTargetNotFound' in e.message:\n", - " print('Data factory not found, creating...')\n", - " provisioning_config = DataFactoryCompute.provisioning_configuration()\n", - " data_factory = ComputeTarget.create(workspace, factory_name, provisioning_config)\n", - " data_factory.wait_for_completion()\n", - " return data_factory\n", - " else:\n", - " raise e\n", - " \n", - "data_factory_compute = get_or_create_data_factory(ws, data_factory_name)\n", - "\n", - "print(\"Setup Azure Data Factory account complete\")" + "# data_factory_name = 'adftest'\n", + "#\n", + "# def get_or_create_data_factory(workspace, factory_name):\n", + "# try:\n", + "# return DataFactoryCompute(workspace, factory_name)\n", + "# except ComputeTargetException as e:\n", + "# if 'ComputeTargetNotFound' in e.message:\n", + "# print('Data factory not found, creating...')\n", + "# provisioning_config = DataFactoryCompute.provisioning_configuration()\n", + "# data_factory = ComputeTarget.create(workspace, factory_name, provisioning_config)\n", + "# data_factory.wait_for_completion()\n", + "# return data_factory\n", + "# else:\n", + "# raise e\n", + "#\n", + "# data_factory_compute = get_or_create_data_factory(ws, data_factory_name)\n", + "#\n", + "# print(\"Setup Azure Data Factory account complete\")" ] }, { @@ -392,19 +392,21 @@ "metadata": {}, "outputs": [], "source": [ - "# TODO: 3012801 - Use ADLS Gen2 datastore.\n", - "blob_data_ref2 = DataReference(\n", - " datastore=blob_datastore,\n", - " data_reference_name=\"blob_test_data2\",\n", - " path_on_datastore=\"testdata2\")\n", - "\n", - "transfer_adls_to_blob = DataTransferStep(\n", - " name=\"transfer_adls_to_blob\",\n", - " source_data_reference=blob_data_ref,\n", - " destination_data_reference=blob_data_ref2,\n", - " compute_target=data_factory_compute)\n", - "\n", - "print(\"Data transfer step created\")" + "# # TODO: 3012801 - Use ADLS Gen2 datastore.\n", + "# blob_data_ref2 = DataReference(\n", + "# datastore=blob_datastore,\n", + "# data_reference_name=\"blob_test_data2\",\n", + "# path_on_datastore=\"testdata2\")\n", + "#\n", + "# transfer_adls_to_blob = DataTransferStep(\n", + "# name=\"transfer_adls_to_blob\",\n", + "# source_data_reference=blob_data_ref,\n", + "# destination_data_reference=blob_data_ref2,\n", + "# compute_target=data_factory_compute,\n", + "# source_reference_type='file',\n", + "# destination_reference_type=\"file\")\n", + "#\n", + "# print(\"Data transfer step created\")" ] }, { @@ -455,13 +457,13 @@ "metadata": {}, "outputs": [], "source": [ - "pipeline_01 = Pipeline(\n", - " description=\"data_transfer_01\",\n", - " workspace=ws,\n", - " steps=[transfer_adls_to_blob])\n", - "\n", - "pipeline_run_01 = Experiment(ws, \"Data_Transfer_example_01\").submit(pipeline_01)\n", - "pipeline_run_01.wait_for_completion()" + "# pipeline_01 = Pipeline(\n", + "# description=\"data_transfer_01\",\n", + "# workspace=ws,\n", + "# steps=[transfer_adls_to_blob])\n", + "#\n", + "# pipeline_run_01 = Experiment(ws, \"Data_Transfer_example_01\").submit(pipeline_01)\n", + "# pipeline_run_01.wait_for_completion()" ] }, { @@ -492,8 +494,8 @@ "metadata": {}, "outputs": [], "source": [ - "from azureml.widgets import RunDetails\n", - "RunDetails(pipeline_run_01).show()" + "# from azureml.widgets import RunDetails\n", + "# RunDetails(pipeline_run_01).show()" ] }, { diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-parameter-tuning-with-hyperdrive.ipynb b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-parameter-tuning-with-hyperdrive.ipynb index 2cdf4113..3d9b36da 100644 --- a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-parameter-tuning-with-hyperdrive.ipynb +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/aml-pipelines-parameter-tuning-with-hyperdrive.ipynb @@ -292,7 +292,7 @@ "metadata": {}, "outputs": [], "source": [ - "tf_env = Environment.get(ws, name='AzureML-tensorflow-2.12-cuda11')" + "tf_env = Environment.get(ws, name='AzureML-tensorflow-2.16-cuda11')" ] }, { diff --git a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/tf_mnist.py b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/tf_mnist.py index 77c376d1..6e20f464 100644 --- a/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/tf_mnist.py +++ b/how-to-use-azureml/machine-learning-pipelines/intro-to-pipelines/tf_mnist.py @@ -178,7 +178,7 @@ os.makedirs('./outputs/model', exist_ok=True) # files saved in the "./outputs" folder are automatically uploaded into run history # this is workaround for https://github.com/tensorflow/tensorflow/issues/33913 and will be fixed once we move to >tf2.1 -neural_net._set_inputs(X_train) +# neural_net._set_inputs(X_train) tf.saved_model.save(neural_net, './outputs/model/') stop_time = time.perf_counter() diff --git a/how-to-use-azureml/ml-frameworks/scikit-learn/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.ipynb b/how-to-use-azureml/ml-frameworks/scikit-learn/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.ipynb index 37c93105..15530489 100644 --- a/how-to-use-azureml/ml-frameworks/scikit-learn/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.ipynb +++ b/how-to-use-azureml/ml-frameworks/scikit-learn/train-hyperparameter-tune-deploy-with-sklearn/train-hyperparameter-tune-deploy-with-sklearn.ipynb @@ -322,7 +322,7 @@ "source": [ "from azureml.core import Environment\n", "\n", - "sklearn_env = Environment.get(ws, name='azureml-sklearn-1.0')" + "sklearn_env = Environment.get(ws, name='azureml-sklearn-1.5')" ] }, { diff --git a/how-to-use-azureml/reinforcement-learning/README.md b/how-to-use-azureml/reinforcement-learning/README.md index 49803b0c..dccd22f0 100644 --- a/how-to-use-azureml/reinforcement-learning/README.md +++ b/how-to-use-azureml/reinforcement-learning/README.md @@ -33,8 +33,6 @@ Using these samples, you will learn how to do the following. | File/folder | Description | |-------------------|--------------------------------------------| -| [cartpole_ci.ipynb](cartpole-on-compute-instance/cartpole_ci.ipynb) | Notebook to train a Cartpole playing agent on an Azure Machine Learning Compute Instance | -| [cartpole_sc.ipynb](cartpole-on-single-compute/cartpole_sc.ipynb) | Notebook to train a Cartpole playing agent on an Azure Machine Learning Compute Cluster (single node) | | [pong_rllib.ipynb](atari-on-distributed-compute/pong_rllib.ipynb) | Notebook for distributed training of Pong agent using RLlib on multiple compute targets | ## Prerequisites diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/cartpole_ci.ipynb b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/cartpole_ci.ipynb deleted file mode 100644 index 9790e0ba..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/cartpole_ci.ipynb +++ /dev/null @@ -1,768 +0,0 @@ -{ - "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/reinforcement-learning/cartpole-on-compute-instance/cartpole_ci.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Reinforcement Learning in Azure Machine Learning - Cartpole Problem on Compute Instance\n", - "\n", - "Reinforcement Learning in Azure Machine Learning is a managed service for running reinforcement learning training and simulation. With Reinforcement Learning in Azure Machine Learning, data scientists can start developing reinforcement learning systems on one machine, and scale to compute targets with 100s of nodes if needed.\n", - "\n", - "This example shows how to use Reinforcement Learning in Azure Machine Learning to train a Cartpole playing agent on a compute instance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Cartpole problem\n", - "\n", - "Cartpole, also known as [Inverted Pendulum](https://en.wikipedia.org/wiki/Inverted_pendulum), is a pendulum with a center of mass above its pivot point. This formation is essentially unstable and will easily fall over but can be kept balanced by applying appropriate horizontal forces to the pivot point.\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - " \"Cartpole \n", - "

Fig 1. Cartpole problem schematic description (from towardsdatascience.com).

\n", - "\n", - "The goal here is to train an agent to keep the cartpole balanced by applying appropriate forces to the pivot point.\n", - "\n", - "See [this video](https://www.youtube.com/watch?v=XiigTGKZfks) for a real-world demonstration of cartpole problem." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prerequisite\n", - "The user should have completed the Azure Machine Learning Tutorial: [Get started creating your first ML experiment with the Python SDK](https://docs.microsoft.com/en-us/azure/machine-learning/tutorial-1st-experiment-sdk-setup). You will need to make sure that you have a valid subscription ID, a resource group, and an Azure Machine Learning workspace. All datastores and datasets you use should be associated with your workspace." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up Development Environment\n", - "The following subsections show typical steps to setup your development environment. Setup includes:\n", - "\n", - "* Connecting to a workspace to enable communication between your local machine and remote resources\n", - "* Creating an experiment to track all your runs\n", - "* Using a Compute Instance as compute target" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Azure Machine Learning SDK \n", - "Display the Azure Machine Learning SDK version." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062935076 - } - }, - "outputs": [], - "source": [ - "import azureml.core\n", - "print(\"Azure Machine Learning SDK version:\", azureml.core.VERSION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Azure Machine Learning workspace\n", - "Get a reference to an existing Azure Machine Learning workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062936280 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace.from_config()\n", - "print(ws.name, ws.location, ws.resource_group, sep = ' | ')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Use Compute Instance as compute target\n", - "\n", - "A compute target is a designated compute resource where you run your training and simulation scripts. This location may be your local machine or a cloud-based compute resource. For more information see [What are compute targets in Azure Machine Learning?](https://docs.microsoft.com/en-us/azure/machine-learning/concept-compute-target)\n", - "\n", - "The code below shows how to use current compute instance as a compute target. First some helper functions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062936485 - } - }, - "outputs": [], - "source": [ - "import os.path\n", - "\n", - "# Get information about the currently running compute instance (notebook VM), like its name and prefix.\n", - "def load_nbvm():\n", - " if not os.path.isfile(\"/mnt/azmnt/.nbvm\"):\n", - " return None\n", - " with open(\"/mnt/azmnt/.nbvm\", 'r') as nbvm_file:\n", - " return { key:value for (key, value) in [ line.strip().split('=') for line in nbvm_file if '=' in line ] }\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we use these helper functions to get a handle to current compute instance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062937126 - } - }, - "outputs": [], - "source": [ - "from azureml.core.compute import ComputeInstance\n", - "from azureml.core.compute_target import ComputeTargetException\n", - "\n", - "import random\n", - "import string\n", - "\n", - "# Load current compute instance info\n", - "current_compute_instance = load_nbvm()\n", - "\n", - "# For this demo, let's use the current compute instance as the compute target, if available\n", - "if current_compute_instance:\n", - " print(\"Current compute instance:\", current_compute_instance)\n", - " instance_name = current_compute_instance['instance']\n", - "else:\n", - " # Compute instance name needs to be unique across all existing compute instances within an Azure region\n", - " instance_name = \"cartpole-ci-\" + \"\".join(random.choice(string.ascii_lowercase) for _ in range(5))\n", - " try:\n", - " instance = ComputeInstance(workspace=ws, name=instance_name)\n", - " print('Found existing instance, use it.')\n", - " except ComputeTargetException:\n", - " print(\"Creating new compute instance...\")\n", - " compute_config = ComputeInstance.provisioning_configuration(\n", - " vm_size='STANDARD_D2_V2'\n", - " )\n", - " instance = ComputeInstance.create(ws, instance_name, compute_config)\n", - " instance.wait_for_completion(show_output=True)\n", - " print(\"Instance name:\", instance_name)\n", - "\n", - "compute_target = ws.compute_targets[instance_name]\n", - "\n", - "print(\"Compute target status:\")\n", - "print(compute_target.get_status().serialize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Azure Machine Learning experiment\n", - "Create an experiment to track the runs in your workspace. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062937499 - } - }, - "outputs": [], - "source": [ - "from azureml.core.experiment import Experiment\n", - "\n", - "experiment_name = 'CartPole-v1-CI'\n", - "experiment = Experiment(workspace=ws, name=experiment_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064044718 - }, - "jupyter": { - "outputs_hidden": false, - "source_hidden": false - }, - "nteract": { - "transient": { - "deleting": false - } - } - }, - "outputs": [], - "source": [ - "from azureml.core import Environment\n", - "import os\n", - "import time\n", - "\n", - "ray_environment_name = 'cartpole-ray-ci'\n", - "ray_environment_dockerfile_path = os.path.join(os.getcwd(), 'files', 'docker', 'Dockerfile')\n", - "\n", - "# Build environment image\n", - "ray_environment = Environment. \\\n", - " from_dockerfile(name=ray_environment_name, dockerfile=ray_environment_dockerfile_path). \\\n", - " register(workspace=ws)\n", - "ray_env_build_details = ray_environment.build(workspace=ws)\n", - "\n", - "ray_env_build_details.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train Cartpole Agent\n", - "In this section, we show how to use Azure Machine Learning jobs and Ray/RLlib framework to train a cartpole playing agent. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create reinforcement learning training run\n", - "\n", - "The code below submits the training run using a `ScriptRunConfig`. By providing the\n", - "command to run the training, and a `RunConfig` object configured with your\n", - "compute target, number of nodes, and environment image to use." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064046594 - }, - "jupyter": { - "outputs_hidden": false, - "source_hidden": false - }, - "nteract": { - "transient": { - "deleting": false - } - } - }, - "outputs": [], - "source": [ - "from azureml.core import Environment\n", - "from azureml.core import RunConfiguration, ScriptRunConfig, Experiment\n", - "from azureml.core.runconfig import DockerConfiguration, RunConfiguration\n", - "\n", - "config_name = 'cartpole-ppo.yaml'\n", - "script_name = 'cartpole_training.py'\n", - "script_arguments = [\n", - " '--config', config_name\n", - "]\n", - "\n", - "aml_run_config_ml = RunConfiguration(communicator='OpenMpi')\n", - "aml_run_config_ml.target = compute_target\n", - "aml_run_config_ml.node_count = 1\n", - "aml_run_config_ml.environment = ray_environment\n", - "\n", - "training_config = ScriptRunConfig(source_directory='./files',\n", - " script=script_name,\n", - " arguments=script_arguments,\n", - " run_config = aml_run_config_ml\n", - " )\n", - "training_run = experiment.submit(training_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training configuration\n", - "\n", - "This is the training configuration (in yaml) that we use to train an agent to solve the CartPole problem using\n", - "the PPO algorithm.\n", - "\n", - "```yaml\n", - "cartpole-ppo:\n", - " env: CartPole-v1\n", - " run: PPO\n", - " stop:\n", - " episode_reward_mean: 475\n", - " time_total_s: 300\n", - " checkpoint_config:\n", - " checkpoint_frequency: 2\n", - " checkpoint_at_end: true\n", - " config:\n", - " # Works for both torch and tf.\n", - " framework: torch\n", - " gamma: 0.99\n", - " lr: 0.0003\n", - " num_workers: 1\n", - " observation_filter: MeanStdFilter\n", - " num_sgd_iter: 6\n", - " vf_loss_coeff: 0.01\n", - " model:\n", - " fcnet_hiddens: [32]\n", - " fcnet_activation: linear\n", - " vf_share_layers: true\n", - " enable_connectors: true\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Monitor experiment\n", - "Azure Machine Learning provides a Jupyter widget to show the status of an experiment run. You could use this widget to monitor the status of the runs.\n", - "\n", - "You can click on the link under **Status** to see the details of a child run. It will also show the metrics being logged." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064049813 - } - }, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "\n", - "RunDetails(training_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stop the run\n", - "\n", - "To stop the run, call `training_run.cancel()`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064050024 - } - }, - "outputs": [], - "source": [ - "# Uncomment line below to cancel the run\n", - "# training_run.cancel()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Wait for completion\n", - "Wait for the run to complete before proceeding.\n", - "\n", - "**Note: The run may take a few minutes to complete.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064304728 - } - }, - "outputs": [], - "source": [ - "training_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate Trained Agent and See Results\n", - "\n", - "We can evaluate a previously trained policy using the `cartpole_rollout.py` helper script provided by RLlib (see [Evaluating Trained Policies](https://ray.readthedocs.io/en/latest/rllib-training.html#evaluating-trained-policies) for more details). Here we use an adaptation of this script to reconstruct a policy from a checkpoint taken and saved during training. We took these checkpoints by setting `checkpoint-freq` and `checkpoint-at-end` parameters above.\n", - "\n", - "In this section we show how to get access to these checkpoints data, and then how to use them to evaluate the trained policy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a dataset of training artifacts\n", - "To evaluate a trained policy (a checkpoint) we need to make the checkpoint accessible to the rollout script.\n", - "We can use the Run API to download policy training artifacts (saved model and checkpoints) to local compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305251 - } - }, - "outputs": [], - "source": [ - "from os import path\n", - "from distutils import dir_util\n", - "\n", - "training_artifacts_path = path.join(\"logs\", \"cartpole-ppo\")\n", - "print(\"Training artifacts path:\", training_artifacts_path)\n", - "\n", - "if path.exists(training_artifacts_path):\n", - " dir_util.remove_tree(training_artifacts_path)\n", - "\n", - "# Download run artifacts to local compute\n", - "training_run.download_files(training_artifacts_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's find the checkpoints and the last checkpoint number." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305283 - } - }, - "outputs": [], - "source": [ - "# A helper function to find all of the checkpoint directories located within a larger directory tree\n", - "def find_checkpoints(file_path):\n", - " print(\"Looking in path:\", file_path)\n", - " checkpoints = []\n", - " for root, dirs, files in os.walk(file_path):\n", - " trimmed_root = root[len(file_path)+1:]\n", - " for name in dirs:\n", - " if name.startswith('checkpoint_'):\n", - " checkpoints.append(path.join(trimmed_root, name))\n", - " return checkpoints" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305305 - } - }, - "outputs": [], - "source": [ - "# Find checkpoints and last checkpoint number\n", - "checkpoint_files = find_checkpoints(training_artifacts_path)\n", - "\n", - "last_checkpoint_path = None\n", - "last_checkpoint_number = -1\n", - "for checkpoint_file in checkpoint_files:\n", - " checkpoint_number = int(os.path.basename(checkpoint_file).split('_')[1])\n", - " if checkpoint_number > last_checkpoint_number:\n", - " last_checkpoint_path = checkpoint_file\n", - " last_checkpoint_number = checkpoint_number\n", - "\n", - "print(\"Last checkpoint number:\", last_checkpoint_number)\n", - "print(\"Last checkpoint path:\", last_checkpoint_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we upload checkpoints to default datastore and create a file dataset. This dataset will be used to pass in the checkpoints to the rollout script." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305331 - } - }, - "outputs": [], - "source": [ - "# Upload the checkpoint files and create a DataSet\n", - "from azureml.data.dataset_factory import FileDatasetFactory\n", - "\n", - "datastore = ws.get_default_datastore()\n", - "checkpoint_ds = FileDatasetFactory.upload_directory(training_artifacts_path, (datastore, 'cartpole_checkpoints_' + training_run.id), overwrite=False, show_progress=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To verify, we can print out the number (and paths) of all the files in the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305353 - } - }, - "outputs": [], - "source": [ - "artifacts_paths = checkpoint_ds.to_path()\n", - "print(\"Number of files in dataset:\", len(artifacts_paths))\n", - "\n", - "# Uncomment line below to print all file paths\n", - "#print(\"Artifacts dataset file paths: \", artifacts_paths)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate Trained Agent and See Results\n", - "\n", - "We can evaluate a previously trained policy using the `cartpole_rollout.py` helper script provided by RLlib (see [Evaluating Trained Policies](https://ray.readthedocs.io/en/latest/rllib-training.html#evaluating-trained-policies) for more details). Here we use an adaptation of this script to reconstruct a policy from a checkpoint taken and saved during training. We took these checkpoints by setting `checkpoint-freq` and `checkpoint-at-end` parameters above.\n", - "In this section we show how to use these checkpoints to evaluate the trained policy." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305371 - }, - "jupyter": { - "outputs_hidden": false, - "source_hidden": false - }, - "nteract": { - "transient": { - "deleting": false - } - } - }, - "outputs": [], - "source": [ - "ray_environment_name = 'cartpole-ray-ci'\n", - "\n", - "experiment_name = 'CartPole-v1-CI'\n", - "\n", - "experiment = Experiment(workspace=ws, name=experiment_name)\n", - "ray_environment = Environment.get(workspace=ws, name=ray_environment_name)\n", - "\n", - "script_name = 'cartpole_rollout.py'\n", - "script_arguments = [\n", - " '--steps', '2000',\n", - " '--checkpoint', last_checkpoint_path,\n", - " '--algo', 'PPO',\n", - " '--render', 'false',\n", - " '--dataset_path', checkpoint_ds.as_named_input('dataset_path').as_mount()\n", - "]\n", - "\n", - "aml_run_config_ml = RunConfiguration(communicator='OpenMpi')\n", - "aml_run_config_ml.target = compute_target\n", - "aml_run_config_ml.node_count = 1\n", - "aml_run_config_ml.environment = ray_environment\n", - "aml_run_config_ml.data\n", - "\n", - "rollout_config = ScriptRunConfig(\n", - " source_directory='./files',\n", - " script=script_name,\n", - " arguments=script_arguments,\n", - " run_config = aml_run_config_ml\n", - " )\n", - " \n", - "rollout_run = experiment.submit(rollout_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And then, similar to the training section, we can monitor the real-time progress of the rollout run and its chid as follows. If you browse logs of the child run you can see the evaluation results recorded in std_log_process_0.txt file. Note that you may need to wait several minutes before these results become available." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305399 - } - }, - "outputs": [], - "source": [ - "RunDetails(rollout_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Wait for completion of the rollout run, or you may cancel the run." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305419 - } - }, - "outputs": [], - "source": [ - "# Uncomment line below to cancel the run\n", - "#rollout_run.cancel()\n", - "rollout_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cleaning up\n", - "For your convenience, below you can find code snippets to clean up any resources created as part of this tutorial that you don't wish to retain." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683064305437 - } - }, - "outputs": [], - "source": [ - "# To archive the created experiment:\n", - "#exp.archive()\n", - "\n", - "# To delete created compute instance\n", - "if not current_compute_instance:\n", - " compute_target.delete()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Next\n", - "This example was about running Reinforcement Learning in Azure Machine Learning (Ray/RLlib Framework) on a compute instance. Please see [Cartpole Problem on Single Compute](../cartpole-on-single-compute/cartpole_sc.ipynb)\n", - "example which uses Ray RLlib to train a Cartpole playing agent on a single node remote compute.\n" - ] - } - ], - "metadata": { - "authors": [ - { - "name": "adrosa" - }, - { - "name": "hoazari" - } - ], - "categories": [ - "how-to-use-azureml", - "reinforcement-learning" - ], - "kernel_info": { - "name": "python38-azureml" - }, - "kernelspec": { - "display_name": "Python 3.8 - AzureML", - "language": "python", - "name": "python38-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.8.5" - }, - "microsoft": { - "host": { - "AzureML": { - "notebookHasBeenCompleted": true - } - }, - "ms_spell_check": { - "ms_spell_check_language": "en" - } - }, - "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License.", - "nteract": { - "version": "nteract-front-end@1.0.0" - }, - "vscode": { - "interpreter": { - "hash": "00c28698cbad9eaca051e9759b1181630e646922505b47b4c6352eb5aa72ddfc" - } - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole-ppo.yaml b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole-ppo.yaml deleted file mode 100644 index 2c2d5cce..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole-ppo.yaml +++ /dev/null @@ -1,23 +0,0 @@ -cartpole-ppo: - env: CartPole-v1 - run: PPO - stop: - episode_reward_mean: 475 - time_total_s: 300 - checkpoint_config: - checkpoint_frequency: 2 - checkpoint_at_end: true - config: - # Works for both torch and tf. - framework: torch - gamma: 0.99 - lr: 0.0003 - num_workers: 1 - observation_filter: MeanStdFilter - num_sgd_iter: 6 - vf_loss_coeff: 0.01 - model: - fcnet_hiddens: [32] - fcnet_activation: linear - vf_share_layers: true - enable_connectors: true diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_rollout.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_rollout.py deleted file mode 100644 index b825d02f..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_rollout.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys -import argparse - -from ray.rllib.evaluate import RolloutSaver, rollout -from ray_on_aml.core import Ray_On_AML -import ray.cloudpickle as cloudpickle -from ray.tune.utils import merge_dicts -from ray.tune.registry import get_trainable_cls, _global_registry, ENV_CREATOR - -from azureml.core import Run -from utils import callbacks - -import collections -import copy -import gymnasium as gym -import json -from pathlib import Path - - -def run_rollout(checkpoint, algo, render, steps, episodes): - config_dir = os.path.dirname(checkpoint) - config_path = os.path.join(config_dir, "params.pkl") - config = None - - # Try parent directory. - if not os.path.exists(config_path): - config_path = os.path.join(config_dir, "../params.pkl") - - # Load the config from pickled. - if os.path.exists(config_path): - with open(config_path, "rb") as f: - config = cloudpickle.load(f) - # If no pkl file found, require command line `--config`. - else: - raise ValueError("Could not find params.pkl in either the checkpoint dir or its parent directory") - - # Make sure worker 0 has an Env. - config["create_env_on_driver"] = True - - # Merge with `evaluation_config` (first try from command line, then from - # pkl file). - evaluation_config = copy.deepcopy(config.get("evaluation_config", {})) - config = merge_dicts(config, evaluation_config) - env = config.get("env") - - # Make sure we have evaluation workers. - if not config.get("evaluation_num_workers"): - config["evaluation_num_workers"] = config.get("num_workers", 0) - if not config.get("evaluation_duration"): - config["evaluation_duration"] = 1 - - # Hard-override this as it raises a warning by Algorithm otherwise. - # Makes no sense anyways, to have it set to None as we don't call - # `Algorithm.train()` here. - config["evaluation_interval"] = 1 - - # Rendering settings. - config["render_env"] = render - - # Create the Algorithm from config. - cls = get_trainable_cls(algo) - algorithm = cls(env=env, config=config) - - # Load state from checkpoint, if provided. - if checkpoint: - algorithm.restore(checkpoint) - - # Do the actual rollout. - with RolloutSaver( - outfile=None, - use_shelve=False, - write_update_file=False, - target_steps=steps, - target_episodes=episodes, - save_info=False, - ) as saver: - rollout(algorithm, env, steps, episodes, saver, not render) - algorithm.stop() - - -if __name__ == "__main__": - # Start ray head (single node) - ray_on_aml = Ray_On_AML() - ray = ray_on_aml.getRay() - if ray: - parser = argparse.ArgumentParser() - parser.add_argument('--dataset_path', required=True, help='Path to artifacts dataset') - parser.add_argument('--checkpoint', required=True, help='Name of checkpoint file directory') - parser.add_argument('--algo', required=True, help='Name of RL algorithm') - parser.add_argument('--render', default=False, required=False, help='True to render') - parser.add_argument('--steps', required=False, type=int, help='Number of steps to run') - parser.add_argument('--episodes', required=False, type=int, help='Number of episodes to run') - args = parser.parse_args() - - # Get a handle to run - run = Run.get_context() - - # Get handles to the tarining artifacts dataset and mount path - dataset_path = run.input_datasets['dataset_path'] - - # Find checkpoint file to be evaluated - checkpoint = os.path.join(dataset_path, args.checkpoint) - print('Checkpoint:', checkpoint) - - # Start rollout - ray.init(address='auto') - run_rollout(checkpoint, args.algo, args.render, args.steps, args.episodes) diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_training.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_training.py deleted file mode 100644 index 2322e69a..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/cartpole_training.py +++ /dev/null @@ -1,34 +0,0 @@ -from ray_on_aml.core import Ray_On_AML -import yaml -from ray.tune.tune import run_experiments -from utils import callbacks -import argparse - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--config', help='Path to yaml configuration file') - args = parser.parse_args() - - ray_on_aml = Ray_On_AML() - ray = ray_on_aml.getRay() - if ray: # in the headnode - ray.init(address="auto") - print("Configuring run from file: ", args.config) - experiment_config = None - with open(args.config, "r") as file: - experiment_config = yaml.safe_load(file) - - # Set local_dir in each experiment configuration to ensure generated logs get picked up - # Also set monitor to ensure videos are captured - for experiment_name, experiment in experiment_config.items(): - experiment["storage_path"] = "./logs" - experiment['config']['monitor'] = True - print(f'Config: {experiment_config}') - - trials = run_experiments( - experiment_config, - callbacks=[callbacks.TrialCallback()], - verbose=2 - ) -else: - print("in worker node") diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/docker/Dockerfile b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/docker/Dockerfile deleted file mode 100644 index dd37010b..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/docker/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04 - -RUN pip install ray-on-aml==0.2.4 \ - ray==2.4.0 \ - ray[rllib]==2.4.0 \ - mlflow==2.3.1 \ - azureml-defaults==1.50.0 \ - azureml-dataset-runtime[fuse,pandas]==1.50.0 \ - azureml-contrib-reinforcementlearning==1.50.0 \ - gputil==1.4.0 \ - scipy==1.9.1 \ - pyglet==2.0.6 \ - cloudpickle==2.2.1 \ - tensorflow==2.11.0 \ - tensorflow-probability==0.19.0 \ - torch \ - tabulate==0.9.0 \ - dm_tree==0.1.8 \ - lz4==4.3.2 \ - psutil==5.9.4 \ - setproctitle==1.3.2 \ - pygame==2.1.0 \ - gymnasium[classic_control]==0.26.3 \ - gym[classic_control]==0.26.2 - -# Display the exact versions we have installed -RUN pip freeze diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/callbacks.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/callbacks.py deleted file mode 100644 index f296f577..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/callbacks.py +++ /dev/null @@ -1,22 +0,0 @@ -'''RLlib callbacks module: - Common callback methods to be passed to RLlib trainer. -''' - -from azureml.core import Run -from ray import tune -from ray.tune import Callback -from ray.air import session - - -class TrialCallback(Callback): - - def on_trial_result(self, iteration, trials, trial, result, **info): - '''Callback on train result to record metrics returned by trainer. - ''' - run = Run.get_context() - run.log( - name='episode_reward_mean', - value=result["episode_reward_mean"]) - run.log( - name='episodes_total', - value=result["episodes_total"]) diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/misc.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/misc.py deleted file mode 100644 index f123324e..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/files/utils/misc.py +++ /dev/null @@ -1,13 +0,0 @@ -'''Misc module: - Miscellaneous helper functions and utilities. -''' - -import os -import glob - - -# Helper function to find a file or folder path -def find_path(name, path_prefix): - for root, _, _ in os.walk(path_prefix): - if glob.glob(os.path.join(root, name)): - return root diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/images/cartpole.png b/how-to-use-azureml/reinforcement-learning/cartpole-on-compute-instance/images/cartpole.png deleted file mode 100644 index f37c084ed993f78687559ea621f21900d5b73e3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1346 zcmeAS@N?(olHy`uVBq!ia0y~yU<9$>aj*f&gPEQ$85meOJzX3_DsH{G>)SD#q!u7)$=mw$laTB|Z@P|ed2f*r;N+-0d#+6X$9R2C)@^dAX2Gq5Gf?G0t|iYCt%B(K(D^`{ z$O#Ng2T{r33kN1;i7(5XB^Cen#p~DI!s>n@d-85i`?_)chX-B9_k4c2b(P%Zhlkr2 z2WV6jWxZ_ipS0@S&p8*bwojZm@x{xRD=Y4vZdFf?f4Eh&f9139bGO(3uUoj`D?@Cs+I9N~5*02}YeqH|8^u=}t8qxd=3xP@mxEwTqj8_hN^Uv3d3HFEX zD!9Hbc3HUdv13{9Hwzr^lU@Dl{``XfPd@)_^t!$zvr0KLH}`Io*3@fZyLN2fUVh+4 z_VJa$%O9PXsBHRmMxVUBTx@LYyg5>rZr=PEvNU_s%^X8>E32x-v+Or4UVQjUwnu$^ z{aM>(+qRjlpL}cgr#&%et-gaC0Cx$9(Xjguf4H~C^yf(rp6#?SuQ2k9f7AKx&*P8p zX2(nU$V$DklMoiyJNC>u-^Kg;yo`5u*|E>f3Fvs-geGS zZ@b#EboIqH;a%I#Mn8VMxH(R@G~+|(+`v6%GM;+kGnbVH>Ya1>d@eot^6T}-+SZ9D zeN}$@we-;Mxc*Zj>B}EIe|`Vx%*OMJzn=;>G&Gg-oYeVpW@l^sd`(gDt0skRS9fj+ z>DpfK3)-#u53+DQ z_g~NAI9*Ydw?uKhhLoAe=Wr4GSZmpI{rZhhM2{v#o7qdr%kF)ov-0uU<9m(H`U*b% zwR_QquPZLh3Voe)eA~NL`OkIxOO4OP|E+o`cl5vq{)uPTb-lc~vb9>|%IA5H-`y|w z)RUgMY+3!C#Tk`Pk4Aj`;GFg(?b#)<{jUo*y+8aS\n", - " \n", - " \n", - " \"Cartpole \n", - " \n", - " \n", - " \n", - "

Fig 1. Cartpole problem schematic description (from towardsdatascience.com).

\n", - " \n", - "\n", - "\n", - "The goal here is to train an agent to keep the cartpole balanced by applying appropriate forces to the pivot point.\n", - "\n", - "See [this video](https://www.youtube.com/watch?v=XiigTGKZfks) for a real-world demonstration of cartpole problem." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Prerequisite\n", - "The user should have completed the Azure Machine Learning Tutorial: [Get started creating your first ML experiment with the Python SDK](https://docs.microsoft.com/en-us/azure/machine-learning/tutorial-1st-experiment-sdk-setup). You will need to make sure that you have a valid subscription ID, a resource group, and an Azure Machine Learning workspace. All datastores and datasets you use should be associated with your workspace." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Set up Development Environment\n", - "The following subsections show typical steps to setup your development environment. Setup includes:\n", - "\n", - "* Connecting to a workspace to enable communication between your local machine and remote resources\n", - "* Creating an experiment to track all your runs\n", - "* Creating a remote compute target to use for training" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Azure Machine Learning SDK \n", - "Display the Azure Machine Learning SDK version." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683056824182 - } - }, - "outputs": [], - "source": [ - "import azureml.core\n", - "\n", - "print(\"Azure Machine Learning SDK version:\", azureml.core.VERSION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get Azure Machine Learning workspace\n", - "Get a reference to an existing Azure Machine Learning workspace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683056825821 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Workspace\n", - "\n", - "ws = Workspace.from_config()\n", - "print(ws.name, ws.location, ws.resource_group, sep = ' | ')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create a new compute resource or attach an existing one\n", - "\n", - "A compute target is a designated compute resource where you run your training and simulation scripts. This location may be your local machine or a cloud-based compute resource. The code below shows how to create a cloud-based compute target. For more information see [What are compute targets in Azure Machine Learning?](https://docs.microsoft.com/en-us/azure/machine-learning/concept-compute-target)\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", - "**Note: Creation of a compute resource can take several minutes**. Please make sure to change `STANDARD_D2_V2` to a [size available in your region](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=virtual-machines)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683056826903 - } - }, - "outputs": [], - "source": [ - "from azureml.core.compute import AmlCompute, ComputeTarget\n", - "import os\n", - "\n", - "# Choose a name and maximum size for your cluster\n", - "compute_name = \"cpu-cluster-d2\"\n", - "compute_min_nodes = 0\n", - "compute_max_nodes = 4\n", - "vm_size = \"STANDARD_D2_V2\"\n", - "\n", - "if compute_name in ws.compute_targets:\n", - " print(\"Found an existing compute target of name: \" + compute_name)\n", - " compute_target = ws.compute_targets[compute_name]\n", - " # Note: you may want to make sure compute_target is of type AmlCompute \n", - "else:\n", - " print(\"Creating new compute target...\")\n", - " provisioning_config = AmlCompute.provisioning_configuration(\n", - " vm_size=vm_size,\n", - " min_nodes=compute_min_nodes, \n", - " max_nodes=compute_max_nodes)\n", - " \n", - " # Create the cluster\n", - " compute_target = ComputeTarget.create(ws, compute_name, provisioning_config)\n", - " compute_target.wait_for_completion(show_output=True, min_node_count=None, timeout_in_minutes=20)\n", - "\n", - "print(compute_target.get_status().serialize())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create Azure Machine Learning experiment\n", - "Create an experiment to track the runs in your workspace. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683056827252 - } - }, - "outputs": [], - "source": [ - "from azureml.core.experiment import Experiment\n", - "\n", - "experiment_name = 'CartPole-v1-SC'\n", - "experiment = Experiment(workspace=ws, name=experiment_name)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1646417962898 - }, - "jupyter": { - "outputs_hidden": false, - "source_hidden": false - }, - "nteract": { - "transient": { - "deleting": false - } - } - }, - "outputs": [], - "source": [ - "from azureml.core import Environment\n", - "import os\n", - "\n", - "ray_environment_name = 'cartpole-ray-sc'\n", - "ray_environment_dockerfile_path = os.path.join(os.getcwd(), 'files', 'docker', 'Dockerfile')\n", - "\n", - "# Build environment image\n", - "ray_environment = Environment. \\\n", - " from_dockerfile(name=ray_environment_name, dockerfile=ray_environment_dockerfile_path). \\\n", - " register(workspace=ws)\n", - "ray_env_build_details = ray_environment.build(workspace=ws)\n", - "\n", - "ray_env_build_details.wait_for_completion(show_output=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Train Cartpole Agent\n", - "In this section, we show how to use Azure Machine Learning jobs and Ray/RLlib framework to train a cartpole playing agent. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create reinforcement learning training run\n", - "\n", - "The code below submits the training run using a `ScriptRunConfig`. By providing the\n", - "command to run the training, and a `RunConfig` object configured with your\n", - "compute target, number of nodes, and environment image to use." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683059658819 - }, - "jupyter": { - "outputs_hidden": false, - "source_hidden": false - }, - "nteract": { - "transient": { - "deleting": false - } - } - }, - "outputs": [], - "source": [ - "from azureml.core import Environment\n", - "from azureml.core import RunConfiguration, ScriptRunConfig, Experiment\n", - "from azureml.core.runconfig import DockerConfiguration, RunConfiguration\n", - "\n", - "config_name = 'cartpole-ppo.yaml'\n", - "script_name = 'cartpole_training.py'\n", - "video_capture = True\n", - "script_arguments = [\n", - " '--config', config_name\n", - "]\n", - "command=[\"python\", script_name, *script_arguments]\n", - "\n", - "aml_run_config_ml = RunConfiguration(communicator='OpenMpi')\n", - "aml_run_config_ml.target = compute_target\n", - "aml_run_config_ml.node_count = 1\n", - "aml_run_config_ml.environment = ray_environment\n", - "\n", - "if video_capture:\n", - " command = [\"xvfb-run -s '-screen 0 640x480x16 -ac +extension GLX +render' \"] + command\n", - " aml_run_config_ml.environment_variables[\"SDL_VIDEODRIVER\"] = \"dummy\"\n", - "\n", - "training_config = ScriptRunConfig(source_directory='./files',\n", - " command=command,\n", - " run_config = aml_run_config_ml\n", - " )\n", - "training_run = experiment.submit(training_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Training configuration\n", - "\n", - "This is the training configuration (in yaml) that we use to train an agent to solve the CartPole problem using\n", - "the PPO algorithm.\n", - "\n", - "```yaml\n", - "cartpole-ppo:\n", - " env: CartPole-v1\n", - " run: PPO\n", - " stop:\n", - " episode_reward_mean: 475\n", - " time_total_s: 300\n", - " checkpoint_config:\n", - " checkpoint_frequency: 2\n", - " checkpoint_at_end: true\n", - " config:\n", - " # Works for both torch and tf.\n", - " framework: torch\n", - " gamma: 0.99\n", - " lr: 0.0003\n", - " num_workers: 1\n", - " observation_filter: MeanStdFilter\n", - " num_sgd_iter: 6\n", - " vf_loss_coeff: 0.01\n", - " model:\n", - " fcnet_hiddens: [32]\n", - " fcnet_activation: linear\n", - " vf_share_layers: true\n", - " enable_connectors: true\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Monitor experiment\n", - "\n", - "Azure Machine Learning provides a Jupyter widget to show the status of an experiment run. You could use this widget to monitor the status of the runs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060289002 - } - }, - "outputs": [], - "source": [ - "from azureml.widgets import RunDetails\n", - "\n", - "RunDetails(training_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stop the run\n", - "To stop the run, call `training_run.cancel()`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment line below to cancel the run\n", - "# training_run.cancel()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Wait for completion\n", - "Wait for the run to complete before proceeding.\n", - "\n", - "**Note: The length of the run depends on the provisioning time of the compute target and it may take several minutes to complete.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060297005 - } - }, - "outputs": [], - "source": [ - "training_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Get access to training artifacts\n", - "We can simply use run id to get a handle to an in-progress or a previously concluded run." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060517858 - } - }, - "outputs": [], - "source": [ - "from azureml.core import Run\n", - "\n", - "run_id = training_run.id # Or set to run id of a completed run (e.g. 'rl-cartpole-v0_1587572312_06e04ace_head')\n", - "run = Run(experiment, run_id=run_id)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can use the Run API to download policy training artifacts (saved model and checkpoints) to local compute." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060521847 - } - }, - "outputs": [], - "source": [ - "from os import path\n", - "from distutils import dir_util\n", - "\n", - "training_artifacts_path = path.join(\"logs\", \"cartpole-ppo\")\n", - "print(\"Training artifacts path:\", training_artifacts_path)\n", - "\n", - "if path.exists(training_artifacts_path):\n", - " dir_util.remove_tree(training_artifacts_path)\n", - "\n", - "# Download run artifacts to local compute\n", - "training_run.download_files(training_artifacts_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Display movies of selected training episodes\n", - "\n", - "Ray creates video output of selected training episodes in mp4 format. Here we will display two of these, i.e. the first and the last recorded videos, so you could see the improvement of the agent after training.\n", - "\n", - "First we introduce a few helper functions: a function to download the movies from our dataset, another one to find mp4 movies in a local directory, and one more to display a downloaded movie." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060867182 - } - }, - "outputs": [], - "source": [ - "import shutil\n", - "\n", - "# A helper function to find movies in a directory\n", - "def find_movies(movie_path):\n", - " print(\"Looking in path:\", movie_path)\n", - " mp4_movies = []\n", - " for root, _, files in os.walk(movie_path):\n", - " for name in files:\n", - " if name.endswith('.mp4'):\n", - " mp4_movies.append(path.join(root, name))\n", - " print('Found {} movies'.format(len(mp4_movies)))\n", - "\n", - " return mp4_movies\n", - "\n", - "\n", - "# A helper function to display a movie\n", - "from IPython.core.display import Video\n", - "from IPython.display import display\n", - "def display_movie(movie_file):\n", - " display(Video(movie_file, embed=True, html_attributes='controls'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Look for the downloaded movies in the local directory and sort them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060871682 - } - }, - "outputs": [], - "source": [ - "mp4_files = find_movies(training_artifacts_path)\n", - "mp4_files.sort()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display a movie of the first training episode. This is how the agent performs with no training." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060900828 - } - }, - "outputs": [], - "source": [ - "first_movie = mp4_files[0] if len(mp4_files) > 0 else None\n", - "print(\"First movie:\", first_movie)\n", - "\n", - "if first_movie:\n", - " display_movie(first_movie)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display a movie of the last training episode. This is how a fully-trained agent performs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683060914790 - } - }, - "outputs": [], - "source": [ - "last_movie = mp4_files[-1] if len(mp4_files) > 0 else None\n", - "print(\"Last movie:\", last_movie)\n", - "\n", - "if last_movie:\n", - " display_movie(last_movie)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate Trained Agent and See Results\n", - "\n", - "We can evaluate a previously trained policy using the `rollout.py` helper script provided by RLlib (see [Evaluating Trained Policies](https://ray.readthedocs.io/en/latest/rllib-training.html#evaluating-trained-policies) for more details). Here we use an adaptation of this script to reconstruct a policy from a checkpoint taken and saved during training. We took these checkpoints by setting `checkpoint-freq` and `checkpoint-at-end` parameters above.\n", - "In this section we show how to use these checkpoints to evaluate the trained policy." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Evaluate a trained policy\n", - "In this section, we submit another job, to evalute a trained policy. The entrypoint for this job is\n", - "`cartpole-rollout.py` script, and we we pass the checkpoints dataset to this script as a dataset refrence.\n", - "\n", - "We are using script parameters to pass in the same algorithm and the same environment used during training. We also specify the checkpoint number of the checkpoint we wish to evaluate, `checkpoint-number`, and number of the steps we shall run the rollout, `steps`.\n", - "\n", - "The training artifacts dataset will be accessible to the rollout script as a mounted folder. The mounted folder and the checkpoint number, passed in via `checkpoint-number`, will be used to create a path to the checkpoint we are going to evaluate. The created checkpoint path then will be passed into RLlib rollout script for evaluation.\n", - "\n", - "Let's find the checkpoints and the last checkpoint number first." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683061167899 - } - }, - "outputs": [], - "source": [ - "# A helper function to find all of the checkpoint directories located within a larger directory tree\n", - "def find_checkpoints(file_path):\n", - " print(\"Looking in path:\", file_path)\n", - " checkpoints = []\n", - " for root, dirs, files in os.walk(file_path):\n", - " trimmed_root = root[len(file_path)+1:]\n", - " for name in dirs:\n", - " if name.startswith('checkpoint_'):\n", - " checkpoints.append(path.join(trimmed_root, name))\n", - " return checkpoints" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683061170184 - } - }, - "outputs": [], - "source": [ - "# Find checkpoints and last checkpoint number\n", - "checkpoint_files = find_checkpoints(training_artifacts_path)\n", - "\n", - "last_checkpoint_path = None\n", - "last_checkpoint_number = -1\n", - "for checkpoint_file in checkpoint_files:\n", - " checkpoint_number = int(os.path.basename(checkpoint_file).split('_')[1])\n", - " if checkpoint_number > last_checkpoint_number:\n", - " last_checkpoint_path = checkpoint_file\n", - " last_checkpoint_number = checkpoint_number\n", - "\n", - "print(\"Last checkpoint number:\", last_checkpoint_number)\n", - "print(\"Last checkpoint path:\", last_checkpoint_path)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683061176740 - } - }, - "outputs": [], - "source": [ - "# Upload the checkpoint files and create a DataSet\n", - "from azureml.data.dataset_factory import FileDatasetFactory\n", - "\n", - "datastore = ws.get_default_datastore()\n", - "checkpoint_ds = FileDatasetFactory.upload_directory(training_artifacts_path, (datastore, 'cartpole_checkpoints_' + training_run.id), overwrite=False, show_progress=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can submit the training run using a `ScriptRunConfig`. By providing the\n", - "command to run the training, and a `RunConfig` object configured w" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062377151 - } - }, - "outputs": [], - "source": [ - "ray_environment_name = 'cartpole-ray-sc'\n", - "\n", - "experiment_name = 'CartPole-v1-SC'\n", - "training_algorithm = 'PPO'\n", - "rl_environment = 'CartPole-v1'\n", - "\n", - "experiment = Experiment(workspace=ws, name=experiment_name)\n", - "ray_environment = Environment.get(workspace=ws, name=ray_environment_name)\n", - "\n", - "script_name = 'cartpole_rollout.py'\n", - "script_arguments = [\n", - " '--steps', '2000',\n", - " '--checkpoint', last_checkpoint_path,\n", - " '--algo', 'PPO',\n", - " '--render', 'true',\n", - " '--dataset_path', checkpoint_ds.as_named_input('dataset_path').as_mount()\n", - "]\n", - "\n", - "aml_run_config_ml = RunConfiguration(communicator='OpenMpi')\n", - "aml_run_config_ml.target = compute_target\n", - "aml_run_config_ml.node_count = 1\n", - "aml_run_config_ml.environment = ray_environment\n", - "aml_run_config_ml.data\n", - "\n", - "rollout_config = ScriptRunConfig(\n", - " source_directory='./files',\n", - " script=script_name,\n", - " arguments=script_arguments,\n", - " run_config = aml_run_config_ml\n", - " )\n", - " \n", - "rollout_run = experiment.submit(rollout_config)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And then, similar to the training section, we can monitor the real-time progress of the rollout run and its chid as follows. If you browse logs of the child run you can see the evaluation results recorded in driver_log.txt file. Note that you may need to wait several minutes before these results become available." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062379999 - } - }, - "outputs": [], - "source": [ - "RunDetails(rollout_run).show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Wait for completion of the rollout run before moving to the next section, or you may cancel the run." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062451723 - } - }, - "outputs": [], - "source": [ - "# Uncomment line below to cancel the run\n", - "#rollout_run.cancel()\n", - "rollout_run.wait_for_completion()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Display movies of selected rollout episodes\n", - "\n", - "To display recorded movies first we download recorded videos to local machine. Here again we create a dataset of rollout artifacts and use the helper functions introduced above to download and displays rollout videos." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062747822 - } - }, - "outputs": [], - "source": [ - "# Download rollout artifacts\n", - "rollout_artifacts_path = path.join(\"logs\", \"rollout\")\n", - "print(\"Rollout artifacts path:\", rollout_artifacts_path)\n", - "\n", - "if path.exists(rollout_artifacts_path):\n", - " dir_util.remove_tree(rollout_artifacts_path)\n", - "\n", - "# Download videos to local compute\n", - "rollout_run.download_files(\"logs/video\", output_directory = rollout_artifacts_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, similar to the training section, we look for the last video." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062752847 - } - }, - "outputs": [], - "source": [ - "# Look for the downloaded movie in local directory\n", - "mp4_files = find_movies(rollout_artifacts_path)\n", - "mp4_files.sort()\n", - "last_movie = mp4_files[-1] if len(mp4_files) > 1 else None\n", - "print(\"Last movie:\", last_movie)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display last video recorded during the rollout." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "gather": { - "logged": 1683062763275 - } - }, - "outputs": [], - "source": [ - "last_movie = mp4_files[-1] if len(mp4_files) > 0 else None\n", - "print(\"Last movie:\", last_movie)\n", - "\n", - "if last_movie:\n", - " display_movie(last_movie)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Cleaning up\n", - "For your convenience, below you can find code snippets to clean up any resources created as part of this tutorial that you don't wish to retain." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# To archive the created experiment:\n", - "#exp.archive()\n", - "\n", - "# To delete the compute target:\n", - "#compute_target.delete()\n", - "\n", - "# To delete downloaded training artifacts\n", - "#if os.path.exists(training_artifacts_path):\n", - "# dir_util.remove_tree(training_artifacts_path)\n", - "\n", - "# To delete downloaded rollout videos\n", - "#if path.exists(rollout_artifacts_path):\n", - "# dir_util.remove_tree(rollout_artifacts_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Next\n", - "This example was about running Reinforcement Learning in Azure Machine Learning (Ray/RLlib Framework) on a single compute. Please see [Pong Problem](../atari-on-distributed-compute/pong_rllib.ipynb)\n", - "example which uses Ray RLlib to train a Pong playing agent on a multi-node cluster." - ] - } - ], - "metadata": { - "authors": [ - { - "name": "hoazari" - }, - { - "name": "dasommer" - } - ], - "categories": [ - "how-to-use-azureml", - "reinforcement-learning" - ], - "kernel_info": { - "name": "python38-azureml" - }, - "kernelspec": { - "display_name": "Python 3.8 - AzureML", - "language": "python", - "name": "python38-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.8.5" - }, - "microsoft": { - "host": { - "AzureML": { - "notebookHasBeenCompleted": true - } - }, - "ms_spell_check": { - "ms_spell_check_language": "en" - } - }, - "notice": "Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License.", - "nteract": { - "version": "nteract-front-end@1.0.0" - }, - "vscode": { - "interpreter": { - "hash": "00c28698cbad9eaca051e9759b1181630e646922505b47b4c6352eb5aa72ddfc" - } - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} \ No newline at end of file diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole-ppo.yaml b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole-ppo.yaml deleted file mode 100644 index 4d6b041e..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole-ppo.yaml +++ /dev/null @@ -1,24 +0,0 @@ -cartpole-ppo: - env: CartPole-v1 - run: PPO - stop: - episode_reward_mean: 475 - time_total_s: 300 - checkpoint_config: - checkpoint_frequency: 2 - checkpoint_at_end: true - config: - # Works for both torch and tf. - framework: torch - gamma: 0.99 - lr: 0.0003 - num_workers: 1 - observation_filter: MeanStdFilter - num_sgd_iter: 6 - vf_loss_coeff: 0.01 - model: - fcnet_hiddens: [32] - fcnet_activation: linear - vf_share_layers: true - enable_connectors: true - render_env: true diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_rollout.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_rollout.py deleted file mode 100644 index b825d02f..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_rollout.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -import sys -import argparse - -from ray.rllib.evaluate import RolloutSaver, rollout -from ray_on_aml.core import Ray_On_AML -import ray.cloudpickle as cloudpickle -from ray.tune.utils import merge_dicts -from ray.tune.registry import get_trainable_cls, _global_registry, ENV_CREATOR - -from azureml.core import Run -from utils import callbacks - -import collections -import copy -import gymnasium as gym -import json -from pathlib import Path - - -def run_rollout(checkpoint, algo, render, steps, episodes): - config_dir = os.path.dirname(checkpoint) - config_path = os.path.join(config_dir, "params.pkl") - config = None - - # Try parent directory. - if not os.path.exists(config_path): - config_path = os.path.join(config_dir, "../params.pkl") - - # Load the config from pickled. - if os.path.exists(config_path): - with open(config_path, "rb") as f: - config = cloudpickle.load(f) - # If no pkl file found, require command line `--config`. - else: - raise ValueError("Could not find params.pkl in either the checkpoint dir or its parent directory") - - # Make sure worker 0 has an Env. - config["create_env_on_driver"] = True - - # Merge with `evaluation_config` (first try from command line, then from - # pkl file). - evaluation_config = copy.deepcopy(config.get("evaluation_config", {})) - config = merge_dicts(config, evaluation_config) - env = config.get("env") - - # Make sure we have evaluation workers. - if not config.get("evaluation_num_workers"): - config["evaluation_num_workers"] = config.get("num_workers", 0) - if not config.get("evaluation_duration"): - config["evaluation_duration"] = 1 - - # Hard-override this as it raises a warning by Algorithm otherwise. - # Makes no sense anyways, to have it set to None as we don't call - # `Algorithm.train()` here. - config["evaluation_interval"] = 1 - - # Rendering settings. - config["render_env"] = render - - # Create the Algorithm from config. - cls = get_trainable_cls(algo) - algorithm = cls(env=env, config=config) - - # Load state from checkpoint, if provided. - if checkpoint: - algorithm.restore(checkpoint) - - # Do the actual rollout. - with RolloutSaver( - outfile=None, - use_shelve=False, - write_update_file=False, - target_steps=steps, - target_episodes=episodes, - save_info=False, - ) as saver: - rollout(algorithm, env, steps, episodes, saver, not render) - algorithm.stop() - - -if __name__ == "__main__": - # Start ray head (single node) - ray_on_aml = Ray_On_AML() - ray = ray_on_aml.getRay() - if ray: - parser = argparse.ArgumentParser() - parser.add_argument('--dataset_path', required=True, help='Path to artifacts dataset') - parser.add_argument('--checkpoint', required=True, help='Name of checkpoint file directory') - parser.add_argument('--algo', required=True, help='Name of RL algorithm') - parser.add_argument('--render', default=False, required=False, help='True to render') - parser.add_argument('--steps', required=False, type=int, help='Number of steps to run') - parser.add_argument('--episodes', required=False, type=int, help='Number of episodes to run') - args = parser.parse_args() - - # Get a handle to run - run = Run.get_context() - - # Get handles to the tarining artifacts dataset and mount path - dataset_path = run.input_datasets['dataset_path'] - - # Find checkpoint file to be evaluated - checkpoint = os.path.join(dataset_path, args.checkpoint) - print('Checkpoint:', checkpoint) - - # Start rollout - ray.init(address='auto') - run_rollout(checkpoint, args.algo, args.render, args.steps, args.episodes) diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_training.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_training.py deleted file mode 100644 index 2322e69a..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/cartpole_training.py +++ /dev/null @@ -1,34 +0,0 @@ -from ray_on_aml.core import Ray_On_AML -import yaml -from ray.tune.tune import run_experiments -from utils import callbacks -import argparse - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--config', help='Path to yaml configuration file') - args = parser.parse_args() - - ray_on_aml = Ray_On_AML() - ray = ray_on_aml.getRay() - if ray: # in the headnode - ray.init(address="auto") - print("Configuring run from file: ", args.config) - experiment_config = None - with open(args.config, "r") as file: - experiment_config = yaml.safe_load(file) - - # Set local_dir in each experiment configuration to ensure generated logs get picked up - # Also set monitor to ensure videos are captured - for experiment_name, experiment in experiment_config.items(): - experiment["storage_path"] = "./logs" - experiment['config']['monitor'] = True - print(f'Config: {experiment_config}') - - trials = run_experiments( - experiment_config, - callbacks=[callbacks.TrialCallback()], - verbose=2 - ) -else: - print("in worker node") diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/docker/Dockerfile b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/docker/Dockerfile deleted file mode 100644 index af5d455c..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/docker/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04 - -RUN apt-get update && apt-get install -y --no-install-recommends \ - python-opengl \ - rsync \ - xvfb && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* && \ - rm -rf /usr/share/man/* - -RUN pip install ray-on-aml==0.2.4 \ - ray==2.4.0 \ - ray[rllib]==2.4.0 \ - mlflow==2.3.1 \ - azureml-defaults==1.50.0 \ - azureml-dataset-runtime[fuse,pandas]==1.50.0 \ - azureml-contrib-reinforcementlearning==1.50.0 \ - gputil==1.4.0 \ - scipy==1.9.1 \ - pyglet==2.0.6 \ - cloudpickle==2.2.1 \ - tensorflow==2.11.0 \ - tensorflow-probability==0.19.0 \ - torch \ - tabulate==0.9.0 \ - dm_tree==0.1.8 \ - lz4==4.3.2 \ - psutil==5.9.4 \ - setproctitle==1.3.2 \ - pygame==2.1.0 \ - gymnasium[classic_control]==0.26.3 \ - gym[classic_control]==0.26.2 - -# Display the exact versions we have installed -RUN pip freeze diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/callbacks.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/callbacks.py deleted file mode 100644 index f296f577..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/callbacks.py +++ /dev/null @@ -1,22 +0,0 @@ -'''RLlib callbacks module: - Common callback methods to be passed to RLlib trainer. -''' - -from azureml.core import Run -from ray import tune -from ray.tune import Callback -from ray.air import session - - -class TrialCallback(Callback): - - def on_trial_result(self, iteration, trials, trial, result, **info): - '''Callback on train result to record metrics returned by trainer. - ''' - run = Run.get_context() - run.log( - name='episode_reward_mean', - value=result["episode_reward_mean"]) - run.log( - name='episodes_total', - value=result["episodes_total"]) diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/misc.py b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/misc.py deleted file mode 100644 index f123324e..00000000 --- a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/files/utils/misc.py +++ /dev/null @@ -1,13 +0,0 @@ -'''Misc module: - Miscellaneous helper functions and utilities. -''' - -import os -import glob - - -# Helper function to find a file or folder path -def find_path(name, path_prefix): - for root, _, _ in os.walk(path_prefix): - if glob.glob(os.path.join(root, name)): - return root diff --git a/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/images/cartpole.png b/how-to-use-azureml/reinforcement-learning/cartpole-on-single-compute/images/cartpole.png deleted file mode 100644 index f37c084ed993f78687559ea621f21900d5b73e3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1346 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

0o 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 literal 0 HcmV?d00001 diff --git a/tutorials/image-classification-mnist-data/img-classification-part1-training.ipynb b/tutorials/image-classification-mnist-data/img-classification-part1-training.ipynb index 0dff9660..c9c52d71 100644 --- a/tutorials/image-classification-mnist-data/img-classification-part1-training.ipynb +++ b/tutorials/image-classification-mnist-data/img-classification-part1-training.ipynb @@ -32,7 +32,7 @@ "\n", "See prerequisites in the [Azure Machine Learning documentation](https://docs.microsoft.com/azure/machine-learning/service/tutorial-train-models-with-aml#prerequisites).\n", "\n", - "On the computer running this notebook, conda install matplotlib, numpy, scikit-learn=0.22.1" + "On the computer running this notebook, conda install matplotlib, numpy, scikit-learn=1.5.1" ] }, { @@ -431,7 +431,7 @@ "\n", "# to install required packages\n", "env = Environment('tutorial-env')\n", - "cd = CondaDependencies.create(pip_packages=['azureml-dataset-runtime[pandas,fuse]', 'azureml-defaults'], conda_packages = ['scikit-learn==0.22.1', 'numpy==1.23'])\n", + "cd = CondaDependencies.create(pip_packages=['azureml-dataset-runtime[pandas,fuse]', 'azureml-defaults'], conda_packages = ['scikit-learn==1.5.1', 'numpy==1.23.5'])\n", "\n", "env.python.conda_dependencies = cd\n", "\n", diff --git a/tutorials/image-classification-mnist-data/img-classification-part3-deploy-encrypted.ipynb b/tutorials/image-classification-mnist-data/img-classification-part3-deploy-encrypted.ipynb index a77f7a35..85e0f415 100644 --- a/tutorials/image-classification-mnist-data/img-classification-part3-deploy-encrypted.ipynb +++ b/tutorials/image-classification-mnist-data/img-classification-part3-deploy-encrypted.ipynb @@ -82,7 +82,7 @@ "\n", "# to install required packages\n", "env = Environment('tutorial-encryption-env')\n", - "cd = CondaDependencies.create(pip_packages=['azureml-dataset-runtime[pandas,fuse]', 'azureml-defaults', 'azure-storage-blob', 'encrypted-inference==0.9'], conda_packages = ['scikit-learn==0.22.1', 'numpy==1.23'])\n", + "cd = CondaDependencies.create(pip_packages=['azureml-dataset-runtime[pandas,fuse]', 'azureml-defaults', 'azure-storage-blob', 'encrypted-inference==0.9'], conda_packages = ['scikit-learn==1.5.1', 'numpy==1.23.5'])\n", "\n", "env.python.conda_dependencies = cd\n", "\n",