Okay, so I kind of live in Azure ML Workspace these days... leading me to want to make a small notebook utilizing it here. It's been changing pretty rapidly every ~6 months, so I'm going to include the versions I work on.

If your company is going down the the Azure road for public cloud, Azure ML Workspace (or AWS SageMaker) is probably the best solution to scale easy access to compute, datasets, experiments, etc. to different data science teams accross a large organization.

Notebook Setup

%load_ext autoreload
%autoreload 2
%matplotlib inline

Libraries

import os 

import datetime as dt

from pathlib import Path
from dotenv import load_dotenv
import pandas as pd
import numpy as np
# import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt


print(f"pandas version {pd.__version__}")
print(f"numpy version {np.__version__}")
# print(f"tensorflow version {tf.__version__}")
pandas version 1.2.0
numpy version 1.18.5
import azureml.core as aml

from azureml.core import Workspace, ScriptRunConfig, Environment, Experiment, Run
from azureml.core import Datastore, Dataset
from azureml.core.compute import ComputeTarget, AmlCompute

from azureml.core.runconfig import RunConfiguration
from azureml.core.conda_dependencies import CondaDependencies

from azureml.core import Model
from azureml.core.resource_configuration import ResourceConfiguration


from azureml.pipeline.core import Pipeline, PipelineParameter
from azureml.pipeline.steps import PythonScriptStep


from azureml.widgets import RunDetails


print(f"azureml version {aml.__version__}")
azureml version 1.25.0
import twelvedata
from twelvedata import TDClient

print(f"twelvedata version {twelvedata.__version__}")
twelvedata version 1.1.7

Project Environment Variables

This is a personal preference of mine to make a .env file per project to encapsulate tokens/secrets/etc outside of notebooks.

In this case I created a file named .env with a single variable apikey=(api key) in the same directory as my experiment.

env_path = Path(".env")
assert env_path.exists()
_ = load_dotenv(env_path)

Matplotlib

It's useful to set a few global plotting defaults to save from doing them for every plot in a notebook

mpl.rcParams['figure.figsize'] = (12, 8)
mpl.rcParams['axes.grid'] = False

Azure ML Workspace

To setup an Azure ML Workspace you will need an azure account (with credit card). To spin it up simply go to https://portal.azure.com/ and type machine learning in the search bar and create a workspace.

Once you have a workspace you will need to download the config.json prior to going to https://ml.azure.com/ to access your workspace

workspace_config_path = Path("config.json")
assert workspace_config_path.exists()
ws = Workspace.from_config(path=workspace_config_path)

Twelve Data Client

I setup an account at https://twelvedata.com/ to get a free api key to try it out. I had not heard of it before, but it was the first thing that came up in my google search for free market data...

apikey = os.environ.get("apikey")
td = TDClient(apikey=apikey)

ML Workspace Compute

Get existing compute cluster or create one

compute_name = "aml-compute"
vm_size = "Standard_NC6"
# vm_size = "Standard_NC6s_v3"

if compute_name in ws.compute_targets:
    compute_target = ws.compute_targets[compute_name]
    if compute_target and type(compute_target) is AmlCompute:
        print('Found compute target: ' + compute_name)
else:
    print('Creating a new compute target...')
    provisioning_config = AmlCompute.provisioning_configuration(vm_size=vm_size,  # STANDARD_NC6 is GPU-enabled
                                                                min_nodes=0,
                                                                max_nodes=4)
    # create the compute target
    compute_target = ComputeTarget.create(
        ws, compute_name, provisioning_config)

    # Can poll for a minimum number of nodes and for a specific timeout.
    # If no min node count is provided it will use the scale settings for the cluster
    compute_target.wait_for_completion(
        show_output=True, min_node_count=None, timeout_in_minutes=20)

    # For a more detailed view of current cluster status, use the 'status' property
    print(compute_target.status.serialize())
Creating a new compute target...
Creating...
SucceededProvisioning operation finished, operation "Succeeded"
Succeeded
AmlCompute wait for completion finished

Minimum number of nodes requested have been provisioned
{'currentNodeCount': 0, 'targetNodeCount': 0, 'nodeStateCounts': {'preparingNodeCount': 0, 'runningNodeCount': 0, 'idleNodeCount': 0, 'unusableNodeCount': 0, 'leavingNodeCount': 0, 'preemptedNodeCount': 0}, 'allocationState': 'Steady', 'allocationStateTransitionTime': '2021-03-31T18:41:03.129000+00:00', 'errors': None, 'creationTime': '2021-03-31T18:41:00.552530+00:00', 'modifiedTime': '2021-03-31T18:41:16.029047+00:00', 'provisioningState': 'Succeeded', 'provisioningStateTransitionTime': None, 'scaleSettings': {'minNodeCount': 0, 'maxNodeCount': 4, 'nodeIdleTimeBeforeScaleDown': 'PT120S'}, 'vmPriority': 'Dedicated', 'vmSize': 'STANDARD_NC6'}

ML Workspace Data

TwelveData

List ETFs Available

etf_data = td.get_etf_list()
etf_list = etf_data.as_json()
etf_df = pd.DataFrame(etf_list)
etf_df.head()
symbol name currency exchange
0 8PSG Invesco Physical Gold ETC EUR XETR
1 AAA BetaShares Australian High Interest Cash ETF AUD ASX
2 AAAU Perth Mint Physical Gold ETF USD NYSE
3 AADR AdvisorShares Dorsey Wright ADR ETF USD NYSE
4 AASF Airlie Australian Share Fund -- ETF Feeder AUD ASX

Get ETF Time Series

end_date = pd.Timestamp(dt.datetime.today())
start_date = end_date - pd.tseries.offsets.BDay(252)

start_date.to_pydatetime().date(), end_date.to_pydatetime().date()
(datetime.date(2020, 4, 13), datetime.date(2021, 3, 31))
ticker = "VOO"
ts = td.time_series(
    symbol=ticker, 
    interval="1day",
    start_date=start_date,
    end_date=end_date,
    outputsize=300
)

df = ts.with_ema().as_pandas()
df.describe()
open high low close volume ema
count 241.000000 241.000000 241.000000 241.000000 2.410000e+02 241.000000
mean 315.827105 318.008939 313.696467 316.061946 3.388346e+06 314.099308
std 30.935291 30.777465 31.051232 30.962168 1.384690e+06 31.300765
min 250.960010 255.490010 250.000000 250.960010 7.530980e+05 247.567540
25% 294.420010 296.386990 293.149990 294.780000 2.359274e+06 289.318520
50% 314.355010 316.260010 312.989990 314.810000 3.065109e+06 313.609280
75% 342.239990 344.370000 340.179990 341.989990 4.069956e+06 340.674540
max 365.079990 366.049990 363.250000 365.410000 8.397805e+06 362.513420
df.head().reset_index()
datetime open high low close volume ema
0 2021-03-31 362.85999 365.82001 362.85999 365.41000 2687756 362.51342
1 2021-03-30 363.79001 363.79001 361.28500 363.00000 3637520 361.78927
2 2021-03-29 362.66000 364.67001 361.10971 363.79001 3062900 361.48659
3 2021-03-26 359.42999 364.35001 358.75000 363.95999 3212525 360.91074
4 2021-03-25 357.42001 360.23999 354.14001 359.47000 5361270 360.14842

Azure

Azure Workspace Datastore

data_store = ws.get_default_datastore()

Upload ETF Dataset

def get_or_upload_df(ws, data_store, df, ticker):
    
    dataset_name = f'{ticker.lower()}_ds'
    try: 
        ds = Dataset.get_by_name(workspace=ws, name=dataset_name)
        df = ds.to_pandas_dataframe()
    except:
        Dataset.Tabular.register_pandas_dataframe(df, data_store, dataset_name)
        ds = Dataset.get_by_name(workspace=ws, name=dataset_name)
        df = ds.to_pandas_dataframe()
    
    return df
    

aml_df = get_or_upload_df(ws, data_store, df.reset_index(), ticker)
aml_df.head()
datetime open high low close volume ema
0 2021-03-26 04:00:00 359.42999 364.35001 358.75000 363.95999 3212525 360.91074
1 2021-03-25 04:00:00 357.42001 360.23999 354.14001 359.47000 5361270 360.14842
2 2021-03-24 04:00:00 360.70999 362.26999 357.44000 357.57999 3989728 360.31803
3 2021-03-23 04:00:00 359.79501 362.51001 359.79501 362.32001 1208455 361.00254
4 2021-03-22 04:00:00 359.88000 363.50000 359.76999 362.10999 3320390 360.67317

Training

Create Training Script

src_dir = 'aml-exp'
aml_exp = Path(src_dir)
if not aml_exp.exists(): aml_path.mkdir()
%%writefile aml-exp/train.py

# Standard Libraries
import argparse
import json
import os

import datetime as dt

# 3rd Party Libraries
import numpy as np
import pandas as pd
import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt

from azureml.core import Run
from azureml.core import Dataset
from azureml.core import Model

from azureml.pipeline.core import Pipeline, PipelineParameter
from azureml.pipeline.steps import PythonScriptStep

from azureml.interpret import ExplanationClient

from sklearn.metrics import confusion_matrix

# Classes 
class WindowGenerator():
    def __init__(self, input_width, label_width, shift,
               train_df, val_df, test_df,
               label_columns=None):
        # Store the raw data.
        self.train_df = train_df
        self.val_df = val_df
        self.test_df = test_df

        # Work out the label column indices.
        self.label_columns = label_columns
        if label_columns is not None:
            self.label_columns_indices = {name: i for i, name in
                                        enumerate(label_columns)}
        self.column_indices = {name: i for i, name in
                               enumerate(train_df.columns)}

        # Work out the window parameters.
        self.input_width = input_width
        self.label_width = label_width
        self.shift = shift

        self.total_window_size = input_width + shift

        self.input_slice = slice(0, input_width)
        self.input_indices = np.arange(self.total_window_size)[self.input_slice]

        self.label_start = self.total_window_size - self.label_width
        self.labels_slice = slice(self.label_start, None)
        self.label_indices = np.arange(self.total_window_size)[self.labels_slice]

    def __repr__(self):
        return '\n'.join([
            f'Total window size: {self.total_window_size}',
            f'Input indices: {self.input_indices}',
            f'Label indices: {self.label_indices}',
            f'Label column name(s): {self.label_columns}'])
    
    @property
    def train(self):
        return self.make_dataset(self.train_df)

    @property
    def val(self):
        return self.make_dataset(self.val_df)

    @property
    def test(self):
        return self.make_dataset(self.test_df)

    @property
    def example(self):
        """Get and cache an example batch of `inputs, labels` for plotting."""
        result = getattr(self, '_example', None)
        if result is None:
            # No example batch was found, so get one from the `.train` dataset
            result = next(iter(self.train))
            # And cache it for next time
            self._example = result
        return result
    
    def split_window(self, features):
        inputs = features[:, self.input_slice, :]
        labels = features[:, self.labels_slice, :]
        if self.label_columns is not None:
            labels = tf.stack(
                [labels[:, :, self.column_indices[name]] for name in self.label_columns],
                axis=-1)

        # Slicing doesn't preserve static shape information, so set the shapes
        # manually. This way the `tf.data.Datasets` are easier to inspect.
        inputs.set_shape([None, self.input_width, None])
        labels.set_shape([None, self.label_width, None])

        return inputs, labels
    
    def plot(self, plot_col, model=None, max_subplots=3):
        plt.figure(figsize=(12, 8))
        plot_col_index = self.column_indices[plot_col]
        inputs, labels = self.example
        max_n = min(max_subplots, len(inputs))
        for n in range(max_n):
            plt.subplot(max_n, 1, n+1)
            plt.ylabel(f'{plot_col} [normed]')
            plt.plot(self.input_indices, inputs[n, :, plot_col_index],
                     label='Inputs', marker='.', zorder=-10)

            if self.label_columns:
                label_col_index = self.label_columns_indices.get(plot_col, None)
            else:
                label_col_index = plot_col_index

            if label_col_index is None:
                continue

            plt.scatter(self.label_indices, labels[n, :, label_col_index],
                edgecolors='k', label='Labels', c='#2ca02c', s=64)
            if model is not None:
                predictions = model(inputs)
                plt.scatter(self.label_indices, predictions[n, :, label_col_index],
                          marker='X', edgecolors='k', label='Predictions',
                          c='#ff7f0e', s=64)

            if n == 0:
                plt.legend()

        plt.xlabel('Time')
        
        
    def make_dataset(self, data):
        data = np.array(data, dtype=np.float32)
        ds = tf.keras.preprocessing.timeseries_dataset_from_array(
          data=data,
          targets=None,
          sequence_length=self.total_window_size,
          sequence_stride=1,
          shuffle=True,
          batch_size=32,)

        ds = ds.map(self.split_window)

        return ds

class Baseline(tf.keras.Model):
    def __init__(self, label_index=None):
        super().__init__()
        self.label_index = label_index

    def call(self, inputs):
        if self.label_index is None:
            return inputs
        result = inputs[:, :, self.label_index]
        return result[:, :, tf.newaxis]
    
# Global Variables
MAX_EPOCHS = 20
CONV_WIDTH = 3

# Read in Args
parser = argparse.ArgumentParser(description='Train')
parser.add_argument('--dataset_name', type=str, dest='dataset_name')

args = parser.parse_args()


# Paths
os.makedirs('./outputs', exist_ok=True)
os.makedirs('./outputs/model', exist_ok=True)


# ML Run
run = Run.get_context()
workspace = run.experiment.workspace


# ML Dataset
ds = Dataset.get_by_name(workspace=workspace, name=args.dataset_name)
df = ds.to_pandas_dataframe()


# Date Feature Prep
day = 24*60*60
year = (365.2425)*day

date_time = pd.to_datetime(df.datetime)
timestamp_s = date_time.map(dt.datetime.timestamp)

df['day_sin'] = np.sin(timestamp_s * (2 * np.pi / day))
df['day_cos'] = np.cos(timestamp_s * (2 * np.pi / day))
df['year_sin'] = np.sin(timestamp_s * (2 * np.pi / year))
df['year_cos'] = np.cos(timestamp_s * (2 * np.pi / year))


# Data Filter
features = ['day_sin', 'day_cos', 'ema']
target = 'close'
columns = features + [target]
df = df[columns]

# Data Splitting
n = len(df)
train_df = df[0:int(n*0.7)]
val_df = df[int(n*0.7):int(n*0.9)]
test_df = df[int(n*0.9):]


# Data Normalization
# TODO - normalize step based on train_df

# Data Windows
single_step_window = WindowGenerator(
    input_width=1, label_width=1, shift=1,
    train_df=train_df, val_df=val_df, test_df=test_df,
    label_columns=[target])
wide_window = WindowGenerator(
    input_width=24, label_width=24, shift=1,
    train_df=train_df, val_df=val_df, test_df=test_df,
    label_columns=[target])
conv_window = WindowGenerator(
    input_width=CONV_WIDTH,
    label_width=1, shift=1,
    train_df=train_df, val_df=val_df, test_df=test_df,
    label_columns=[target])


# Train Baseline
baseline = Baseline(label_index=single_step_window.column_indices.get(target))
baseline.compile(loss=tf.losses.MeanSquaredError(),
                 metrics=[tf.metrics.MeanAbsoluteError()])

val_performance, tst_performance = {}, {}
val_performance['baseline'] = baseline.evaluate(single_step_window.val)
tst_performance['baseline'] = baseline.evaluate(single_step_window.test, verbose=0)

wide_window.plot(target, baseline)
run.log_image('baseline_pred', plot=plt)

# Train Models
def compile_and_fit(model, window, patience=4):
    early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
                                                    patience=patience,
                                                    mode='min')

    model.compile(loss=tf.losses.MeanSquaredError(),
                optimizer=tf.optimizers.Adam(),
                metrics=[tf.metrics.MeanAbsoluteError()])

    history = model.fit(window.train, epochs=MAX_EPOCHS,
                      validation_data=window.val,
                      callbacks=[early_stopping])
    return history

# Train Linear Model
linear = tf.keras.Sequential([
    tf.keras.layers.Dense(units=1)
])
history = compile_and_fit(linear, single_step_window)

val_performance['linear'] = linear.evaluate(single_step_window.val)
tst_performance['linear'] = linear.evaluate(single_step_window.test, verbose=0)

tf.saved_model.save(linear, './outputs/model/linear')

fig1 = plt.figure()
ax = fig1.add_subplot(111)
ax.bar(x = range(len(train_df.columns)),
        height=linear.layers[0].kernel[:,0].numpy())
ax.set_xticks(range(len(train_df.columns)))
_ = ax.set_xticklabels(train_df.columns, rotation=90)
run.log_image('linear_coef', plot=plt)

wide_window.plot(target, linear)
run.log_image('linear_pred', plot=plt)

# Train Single Step Dense Model
single_step_dense = tf.keras.Sequential([
    tf.keras.layers.Dense(units=64, activation='relu'),
    tf.keras.layers.Dense(units=64, activation='relu'),
    tf.keras.layers.Dense(units=1)
])
history = compile_and_fit(single_step_dense, single_step_window)

val_performance['single_step_dense'] = single_step_dense.evaluate(single_step_window.val)
tst_performance['single_step_dense'] = single_step_dense.evaluate(single_step_window.test, verbose=0)

tf.saved_model.save(single_step_dense, './outputs/model/single_step_dense')

wide_window.plot(target, single_step_dense)
run.log_image('single_step_dense_pred', plot=plt)

# Train Multi Step Dense Model
multi_step_dense = tf.keras.Sequential([
    # Shape: (time, features) => (time*features)
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(units=32, activation='relu'),
    tf.keras.layers.Dense(units=32, activation='relu'),
    tf.keras.layers.Dense(units=1),
    # Add back the time dimension.
    # Shape: (outputs) => (1, outputs)
    tf.keras.layers.Reshape([1, -1]),
])
history = compile_and_fit(multi_step_dense, conv_window)

val_performance['multi_step_dense'] = multi_step_dense.evaluate(conv_window.val)
tst_performance['multi_step_dense'] = multi_step_dense.evaluate(conv_window.test, verbose=0)

tf.saved_model.save(multi_step_dense, './outputs/model/multi_step_dense')

# wide_window.plot(target, multi_step_dense)
# run.log_image('multi_step_dense_pred', plot=plt)

# Train Conv Model
conv = tf.keras.Sequential([
    tf.keras.layers.Conv1D(filters=32,
                           kernel_size=(CONV_WIDTH,),
                           activation='relu'),
    tf.keras.layers.Dense(units=32, activation='relu'),
    tf.keras.layers.Dense(units=1),
])
history = compile_and_fit(conv, conv_window)

val_performance['conv'] = conv.evaluate(conv_window.val)
tst_performance['conv'] = conv.evaluate(conv_window.test, verbose=0)

tf.saved_model.save(conv, './outputs/model/conv')

# Train LSTM Model
lstm = tf.keras.models.Sequential([
    # Shape [batch, time, features] => [batch, time, lstm_units]
    tf.keras.layers.LSTM(10, return_sequences=True),
    # Shape => [batch, time, features]
    tf.keras.layers.Dense(units=1)
])
history = compile_and_fit(lstm, wide_window)

val_performance['lstm'] = lstm.evaluate(wide_window.val)
tst_performance['lstm'] = lstm.evaluate(wide_window.test, verbose=0)

tf.saved_model.save(lstm, './outputs/model/lstm')

# Performance
x = np.arange(len(val_performance))
width = 0.3

metric_name = 'mean_absolute_error'
metric_index = lstm.metrics_names.index('mean_absolute_error')
val_mae = [v[metric_index] for v in val_performance.values()]
test_mae = [v[metric_index] for v in tst_performance.values()]

fig2 = plt.figure()
ax = fig2.add_subplot(111)
b1 = ax.bar(x - 0.2, val_mae, width, label='validation')
# b2 = ax.bar(x + 0.2, test_mae, width, label='test')
ax.set_xticks(range(len(val_mae)))
_ = ax.set_xticklabels(val_performance.keys(), rotation=90)
run.log_image('performance_mae', plot=plt)


# Log Results & Select Best Model
best_model, best_score = None, None
if run is not None:
    
    for k, v in val_performance.items():
        run.log_list(f'val_{k}', v)
        
    for k, v in tst_performance.items():
        run.log_list(f'tst_{k}', v)
        try:
            mae = float(v[1])    
            if best_score is None and best_model is None: 
                best_model = k
                best_score = mae
            elif best_score > mae:
                best_model = k
                best_score = mae   
        except:
            continue

    run.log('best_model', best_model)
    run.log('best_score', best_score)

if best_model != "baseline": model = run.register_model(model_name=best_model, model_path=f'outputs/model/{best_model}')

#### Setup Training Environment 
Overwriting aml-exp/train.py
aml_run_config = RunConfiguration()
aml_run_config.target = compute_target

aml_run_config.environment.python.user_managed_dependencies = False

# Add some packages relied on by data prep step
deps = CondaDependencies.create(
    conda_packages=['pandas','scikit-learn', 'matplotlib'], 
    pip_packages=['azureml-sdk', 'azureml-dataprep[fuse,pandas]', 'azureml-pipeline', 'azureml-interpret'], 
    python_version='3.6.2',
    pin_sdk_version=True)
deps.add_tensorflow_pip_package(core_type='gpu', version='2.3.1')
aml_run_config.environment.python.conda_dependencies = deps


src = ScriptRunConfig(source_directory=src_dir,
                      script='train.py',
                      arguments=['--dataset_name', f'{ticker.lower()}_ds'],
                      run_config=aml_run_config)

Run Experiment

%%capture
experiment = Experiment(ws, 'aml_exp')
script_run = experiment.submit(src)
script_run.wait_for_completion(show_output=False)
RunDetails(script_run).show()

Review

I find it best to simply go to the experiment portal url to review from the gui. It contains all the runs from your experiment and makes it easy to review changes from a central location.

script_run.get_portal_url()
'https://ml.azure.com/runs/aml_exp_1617233359_a7595e84?wsid=/subscriptions/f3b5840b-706e-44ba-8aa1-6fd3fc8aaab0/resourcegroups/ds-workspace/workspaces/minion-lab&tid=e6777dcd-6f87-4dd0-92e5-e98312157dac'

However, you can choose to do the model review inside the notebook too.

The first place to look when doing this is the experiments metrics. In this example I'm logging the mse and mae for validation & test datasets for each model

metrics = script_run.get_metrics()
metrics
{'baseline_pred': 'aml://artifactId/ExperimentRun/dcid.aml_exp_1617233359_a7595e84/baseline_pred_1617233672.png',
 'linear_coef': 'aml://artifactId/ExperimentRun/dcid.aml_exp_1617233359_a7595e84/linear_coef_1617233676.png',
 'linear_pred': 'aml://artifactId/ExperimentRun/dcid.aml_exp_1617233359_a7595e84/linear_pred_1617233676.png',
 'single_step_dense_pred': 'aml://artifactId/ExperimentRun/dcid.aml_exp_1617233359_a7595e84/single_step_dense_pred_1617233681.png',
 'performance_mae': 'aml://artifactId/ExperimentRun/dcid.aml_exp_1617233359_a7595e84/performance_mae_1617233696.png',
 'val_baseline': [18.686260223388672, 3.2065956592559814],
 'val_linear': [605690.9375, 777.9266357421875],
 'val_single_step_dense': [12.129790306091309, 2.596614122390747],
 'val_multi_step_dense': [23.9085693359375, 3.903738021850586],
 'val_conv': [17.16225242614746, 2.9845776557922363],
 'val_lstm': [81859.9296875, 286.0354919433594],
 'tst_baseline': [20.958070755004883, 3.920870542526245],
 'tst_linear': [502930.53125, 709.0858154296875],
 'tst_single_step_dense': [14.61068344116211, 3.2360215187072754],
 'tst_multi_step_dense': [20.170698165893555, 4.205769062042236],
 'tst_conv': [17.6562557220459, 3.5512776374816895],
 'best_model': 'single_step_dense',
 'best_score': 3.2360215187072754}
mae_metrics = []
for name, value in metrics.items():
    if isinstance(value, list) and len(value) >= 2:
        splits = name.split("_")
        grp, model = splits[0], "_".join(splits[1:])
        mae_metrics.append((model, grp, value[1]))
        
for model, grp, mae in sorted(mae_metrics, key=lambda o: o[0]):
    name = f'{model}_{grp}'
    print(f'{name:25s}: {mae:0.4f}')
baseline_val             : 3.2066
baseline_tst             : 3.9209
conv_val                 : 2.9846
conv_tst                 : 3.5513
linear_val               : 777.9266
linear_tst               : 709.0858
lstm_val                 : 286.0355
multi_step_dense_val     : 3.9037
multi_step_dense_tst     : 4.2058
single_step_dense_val    : 2.5966
single_step_dense_tst    : 3.2360

It can also be useful to review the log files to figure out wtf is going wrong constantly...

files = script_run.get_file_names()
files
['azureml-logs/55_azureml-execution-tvmps_0c357d5ce4f8eda465b3f47f7b95b579e656516823eef14997ff742d99f7565e_d.txt',
 'azureml-logs/65_job_prep-tvmps_0c357d5ce4f8eda465b3f47f7b95b579e656516823eef14997ff742d99f7565e_d.txt',
 'azureml-logs/70_driver_log.txt',
 'azureml-logs/75_job_post-tvmps_0c357d5ce4f8eda465b3f47f7b95b579e656516823eef14997ff742d99f7565e_d.txt',
 'azureml-logs/process_info.json',
 'azureml-logs/process_status.json',
 'baseline_pred_1617233672.png',
 'linear_coef_1617233676.png',
 'linear_pred_1617233676.png',
 'logs/azureml/106_azureml.log',
 'logs/azureml/dataprep/backgroundProcess.log',
 'logs/azureml/dataprep/backgroundProcess_Telemetry.log',
 'logs/azureml/job_prep_azureml.log',
 'logs/azureml/job_release_azureml.log',
 'outputs/model/conv/saved_model.pb',
 'outputs/model/conv/variables/variables.data-00000-of-00001',
 'outputs/model/conv/variables/variables.index',
 'outputs/model/linear/saved_model.pb',
 'outputs/model/linear/variables/variables.data-00000-of-00001',
 'outputs/model/linear/variables/variables.index',
 'outputs/model/lstm/saved_model.pb',
 'outputs/model/lstm/variables/variables.data-00000-of-00001',
 'outputs/model/lstm/variables/variables.index',
 'outputs/model/multi_step_dense/saved_model.pb',
 'outputs/model/multi_step_dense/variables/variables.data-00000-of-00001',
 'outputs/model/multi_step_dense/variables/variables.index',
 'outputs/model/single_step_dense/saved_model.pb',
 'outputs/model/single_step_dense/variables/variables.data-00000-of-00001',
 'outputs/model/single_step_dense/variables/variables.index',
 'performance_mae_1617233696.png',
 'single_step_dense_pred_1617233681.png']

Clean up

This is an important step if you don't want to end up having a big bill at the end of the month 😉

print("starting compute cleanup")

for name, compute in ws.compute_targets.items():
    print(f"deleting {name} instance")
    compute.delete()
    
while len(ws.compute_targets.items()) != 0:
    continue

print("compute cleanup complete")
starting compute cleanup
deleting aml-compute instance
deleting ks-nb instance
Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

Current provisioning state of AmlCompute is "Deleting"

compute cleanup complete