Skip to content

Commit eceb936

Browse files
tarockeyeedorenko
authored andcommitted
Updated repo to use the SDK more consistently (#67)
* Updated code to use SDK instead of JSON files * include location in environment setup * docs updates * Update docker-image-pipeline.yml for Azure Pipelines * Update docker-image-pipeline.yml for Azure Pipelines
1 parent f1d85cc commit eceb936

File tree

13 files changed

+180
-170
lines changed

13 files changed

+180
-170
lines changed

.env.example

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,43 @@
11
# Azure Subscription Variables
2-
WORKSPACE_NAME = ''
3-
RESOURCE_GROUP = ''
42
SUBSCRIPTION_ID = ''
53
LOCATION = ''
64
TENANT_ID = ''
5+
BASE_NAME = ''
6+
SP_APP_ID = ''
7+
SP_APP_SECRET = ''
8+
9+
# Mock build/release ID for local testing - update ReleaseID each "release"
10+
BUILD_BUILDID = '001'
11+
RELEASE_RELEASEID = '001'
712

813
# Azure ML Workspace Variables
914
EXPERIMENT_NAME = ''
1015
SCRIPT_FOLDER = './'
11-
BLOB_STORE_NAME = ''
12-
# Remote VM Config
13-
REMOTE_VM_NAME = ''
14-
REMOTE_VM_USERNAME = ''
15-
REMOTE_VM_PASSWORD = ''
16-
REMOTE_VM_IP = ''
16+
1717
# AML Compute Cluster Config
18-
AML_CLUSTER_NAME = ''
19-
AML_CLUSTER_VM_SIZE = ''
18+
AML_COMPUTE_CLUSTER_NAME = ''
19+
AML_COMPUTE_CLUSTER_CPU_SKU = ''
2020
AML_CLUSTER_MAX_NODES = ''
2121
AML_CLUSTER_MIN_NODES = ''
2222
AML_CLUSTER_PRIORITY = 'lowpriority'
2323
# Training Config
24-
MODEL_NAME = ''
25-
MODEL_VERSION = ''
24+
MODEL_NAME = 'sklearn_regression_model.pkl'
25+
MODEL_VERSION = '1'
26+
TRAIN_SCRIPT_PATH = 'training/train.py'
2627
# AML Pipeline Config
2728
TRAINING_PIPELINE_NAME = ''
2829
PIPELINE_CONDA_PATH = 'aml_config/conda_dependencies.yml'
2930
MODEL_PATH = ''
31+
EVALUATE_SCRIPT_PATH = 'evaluate/evaluate_model.py'
32+
REGISTER_SCRIPT_PATH = 'register/register_model.py'
33+
SOURCES_DIR_TRAIN = 'code'
34+
35+
# These are not mandatory for the core workflow
36+
# Remote VM Config
37+
REMOTE_VM_NAME = ''
38+
REMOTE_VM_USERNAME = ''
39+
REMOTE_VM_PASSWORD = ''
40+
REMOTE_VM_IP = ''
3041
# Image config
3142
IMAGE_NAME = ''
3243
IMAGE_DESCRIPTION = ''

.pipelines/azdo-ci-build-train.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ steps:
2323
failOnStderr: 'false'
2424
env:
2525
SP_APP_SECRET: '$(SP_APP_SECRET)'
26-
displayName: 'Train model using AML with Remote Compute'
26+
displayName: 'Publish Azure Machine Learning Pipeline'
2727
enabled: 'true'
2828

2929
- task: CopyFiles@2
@@ -32,7 +32,7 @@ steps:
3232
SourceFolder: '$(Build.SourcesDirectory)'
3333
TargetFolder: '$(Build.ArtifactStagingDirectory)'
3434
Contents: |
35-
ml_service/pipelines/?(run_train_pipeline.py|*.json)
35+
ml_service/pipelines/?(run_train_pipeline.py|*.json)
3636
code/scoring/**
3737
3838

code/evaluate/evaluate_model.py

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,53 +24,45 @@
2424
POSSIBILITY OF SUCH DAMAGE.
2525
"""
2626
import os
27-
import json
28-
from azureml.core.model import Model
29-
from azureml.core import Run
27+
from azureml.core import Model, Run
3028
import argparse
3129

3230

3331
# Get workspace
34-
# ws = Workspace.from_config()
3532
run = Run.get_context()
3633
exp = run.experiment
3734
ws = run.experiment.workspace
3835

3936

4037
parser = argparse.ArgumentParser("evaluate")
4138
parser.add_argument(
42-
"--config_suffix", type=str, help="Datetime suffix for json config files"
39+
"--release_id",
40+
type=str,
41+
help="The ID of the release triggering this pipeline run",
4342
)
4443
parser.add_argument(
45-
"--json_config",
44+
"--model_name",
4645
type=str,
47-
help="Directory to write all the intermediate json configs",
46+
help="Name of the Model",
47+
default="sklearn_regression_model.pkl",
4848
)
4949
args = parser.parse_args()
5050

51-
print("Argument 1: %s" % args.config_suffix)
52-
print("Argument 2: %s" % args.json_config)
51+
print("Argument 1: %s" % args.release_id)
52+
print("Argument 2: %s" % args.model_name)
53+
model_name = args.model_name
54+
release_id = args.release_id
5355

54-
if not (args.json_config is None):
55-
os.makedirs(args.json_config, exist_ok=True)
56-
print("%s created" % args.json_config)
5756
# Paramaterize the matrics on which the models should be compared
5857
# Add golden data set on which all the model performance can be evaluated
5958

60-
# Get the latest run_id
61-
# with open("aml_config/run_id.json") as f:
62-
# config = json.load(f)
63-
64-
train_run_id_json = "run_id_{}.json".format(args.config_suffix)
65-
train_output_path = os.path.join(args.json_config, train_run_id_json)
66-
with open(train_output_path) as f:
67-
config = json.load(f)
68-
69-
70-
new_model_run_id = config["run_id"] # args.train_run_id
71-
experiment_name = config["experiment_name"]
72-
# exp = Experiment(workspace=ws, name=experiment_name)
73-
59+
all_runs = exp.get_runs(
60+
properties={"release_id": release_id, "run_type": "train"},
61+
include_children=True
62+
)
63+
new_model_run = next(all_runs)
64+
new_model_run_id = new_model_run.id
65+
print(f'New Run found with Run ID of: {new_model_run_id}')
7466

7567
try:
7668
# Get most recently registered model, we assume that
@@ -110,16 +102,12 @@
110102
print("This is the first model to be trained, \
111103
thus nothing to evaluate for now")
112104

113-
run_id = {}
114-
run_id["run_id"] = ""
105+
115106
# Writing the run id to /aml_config/run_id.json
116107
if promote_new_model:
117-
run_id["run_id"] = new_model_run_id
118-
# register new model
119-
# new_model_run.register_model(model_name='',model_path='outputs/sklearn_regression_model.pkl')
120-
121-
run_id["experiment_name"] = experiment_name
122-
filename = "run_id_{}.json".format(args.config_suffix)
123-
output_path = os.path.join(args.json_config, filename)
124-
with open(output_path, "w") as outfile:
125-
json.dump(run_id, outfile)
108+
model_path = os.path.join('outputs', model_name)
109+
new_model_run.register_model(
110+
model_name=model_name,
111+
model_path=model_path,
112+
properties={"release_id": release_id})
113+
print("Registered new model!")

code/training/train.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,13 @@
3232
from sklearn.model_selection import train_test_split
3333
from sklearn.externals import joblib
3434
import numpy as np
35-
import json
3635

3736

3837
parser = argparse.ArgumentParser("train")
3938
parser.add_argument(
40-
"--config_suffix", type=str, help="Datetime suffix for json config files"
41-
)
42-
parser.add_argument(
43-
"--json_config",
39+
"--release_id",
4440
type=str,
45-
help="Directory to write all the intermediate json configs",
41+
help="The ID of the release triggering this pipeline run",
4642
)
4743
parser.add_argument(
4844
"--model_name",
@@ -53,14 +49,11 @@
5349

5450
args = parser.parse_args()
5551

56-
print("Argument 1: %s" % args.config_suffix)
57-
print("Argument 2: %s" % args.json_config)
52+
print("Argument 1: %s" % args.release_id)
53+
print("Argument 2: %s" % args.model_name)
5854

5955
model_name = args.model_name
60-
61-
if not (args.json_config is None):
62-
os.makedirs(args.json_config, exist_ok=True)
63-
print("%s created" % args.json_config)
56+
release_id = args.release_id
6457

6558
run = Run.get_context()
6659
exp = run.experiment
@@ -102,12 +95,8 @@
10295
print("Following files are uploaded ")
10396
print(run.get_file_names())
10497

105-
run_id = {}
106-
run_id["run_id"] = run.id
107-
run_id["experiment_name"] = run.experiment.name
108-
filename = "run_id_{}.json".format(args.config_suffix)
109-
output_path = os.path.join(args.json_config, filename)
110-
with open(output_path, "w") as outfile:
111-
json.dump(run_id, outfile)
98+
# Add properties to identify this specific training run
99+
run.add_properties({"release_id": release_id, "run_type": "train"})
100+
print(f"added properties: {run.properties}")
112101

113102
run.complete()

docs/code_description.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
### Code
2828

2929
- `code/training/train.py` : a training step of an ML training pipeline.
30-
- `code/evaluate/evaluate_model.py` : an evaluating step of an ML training pipeline.
31-
- `code/evaluate/register_model.py` : registers a new trained model if evaluation shows the new model is more performant than the previous one.
30+
- `code/evaluate/evaluate_model.py` : an evaluating step of an ML training pipeline which registers a new trained model if evaluation shows the new model is more performant than the previous one.
31+
- `code/evaluate/register_model.py` : (LEGACY) registers a new trained model if evaluation shows the new model is more performant than the previous one.
3232

3333
### Scoring
3434
- code/scoring/score.py : a scoring script which is about to be packed into a Docker Image along with a model while being deployed to QA/Prod environment.

docs/getting_started.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ The variable group should contain the following variables:
4949
| SUBSCRIPTION_ID | |
5050
| TENANT_ID | |
5151
| TRAIN_SCRIPT_PATH | training/train.py |
52+
| TRAINING_PIPELINE_NAME | training-pipeline |
5253

5354
Mark **SP_APP_SECRET** variable as a secret one.
5455

@@ -88,6 +89,7 @@ Check out created resources in the [Azure Portal](portal.azure.com):
8889

8990
Alternatively, you can also use a [cleaning pipeline](../environment_setup/iac-remove-environment.yml) that removes resources created for this project or you can just delete a resource group in the [Azure Portal](portal.azure.com).
9091

92+
Once this resource group is created, be sure that the Service Principal you have created has access to this resource group.
9193

9294
### 6. Set up Build Pipeline
9395

@@ -127,9 +129,11 @@ Rename the default "Stage 1" to **Invoke Training Pipeline** and make sure that
127129
Add a **Command Line Script** step, rename it to **Run Training Pipeline** with the following script:
128130

129131
```bash
130-
docker run -v $(System.DefaultWorkingDirectory)/_ci-build/mlops-pipelines/ml_service/pipelines:/pipelines \
131-
-w=/pipelines -e MODEL_NAME=$MODEL_NAME -e EXPERIMENT_NAME=$EXPERIMENT_NAME \
132-
-e TENANT_ID=$TENANT_ID -e SP_APP_ID=$SP_APP_ID -e SP_APP_SECRET=$(SP_APP_SECRET) \
132+
docker run -v $(System.DefaultWorkingDirectory)/_ci-build/mlops-pipelines/ml_service/pipelines:/pipelines \
133+
-w=/pipelines -e MODEL_NAME=$MODEL_NAME -e EXPERIMENT_NAME=$EXPERIMENT_NAME \
134+
-e TENANT_ID=$TENANT_ID -e SP_APP_ID=$SP_APP_ID -e SP_APP_SECRET=$(SP_APP_SECRET) \
135+
-e SUBSCRIPTION_ID=$SUBSCRIPTION_ID -e RELEASE_RELEASEID=$RELEASE_RELEASEID \
136+
-e BUILD_BUILDID=$BUILD_BUILDID -e BASE_NAME=$BASE_NAME \
133137
mcr.microsoft.com/mlops/python:latest python run_train_pipeline.py
134138
```
135139

environment_setup/arm-templates/cloud-environment.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"southeastasia",
2121
"westcentralus",
2222
"westeurope",
23-
"westus2"
23+
"westus2",
24+
"centralus"
2425
],
2526
"metadata": {
2627
"description": "Specifies the location for all resources."

environment_setup/iac-create-environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ steps:
2222
location: $(LOCATION)
2323
templateLocation: 'Linked artifact'
2424
csmFile: '$(Build.SourcesDirectory)/environment_setup/arm-templates/cloud-environment.json'
25-
overrideParameters: '-baseName $(BASE_NAME)'
25+
overrideParameters: '-baseName $(BASE_NAME) -location $(LOCATION)'
2626
deploymentMode: 'Incremental'
2727
displayName: 'Deploy MLOps resources to Azure'
2828

environment_setup/requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
pytest==4.3.0
22
requests>=2.22
3-
azureml>=0.2
43
azureml-sdk>=1.0
54
python-dotenv>=0.10.3
65
flake8

ml_service/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)