This notebook is the same as the previous notebook. The goals is simply to update it to make a few enhancements that became apparent during the process of making the first notebook.

Enhancements

  • Start using the pipeline api
  • Adding plots for all models

Bug Fixes

  • Normalizing input data
  • Increasing the test data set for lstm testing & performance plot

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 tensorboard

from azureml.tensorboard import Tensorboard

print(f"tensorboard version {tensorboard.__version__}")
tensorboard version 2.4.1
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-04-02T20:04:47.531000+00:00', 'errors': None, 'creationTime': '2021-04-02T20:04:42.205768+00:00', 'modifiedTime': '2021-04-02T20:04:59.941680+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, 15), datetime.date(2021, 4, 2))
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 240.000000 240.000000 240.000000 240.000000 2.400000e+02 240.000000
mean 316.532337 318.708476 314.401410 316.757663 3.385244e+06 314.852005
std 30.684477 30.533911 30.831302 30.738751 1.376637e+06 30.938463
min 250.960010 255.490010 250.000000 250.960010 7.530980e+05 250.599230
25% 294.715005 296.435857 293.557575 295.067500 2.356972e+06 291.208500
50% 314.860000 317.341200 313.375000 315.255005 3.077382e+06 313.968755
75% 342.612498 345.372500 340.907497 342.372490 4.080284e+06 340.965635
max 366.205990 368.290010 366.030000 368.160000 8.397805e+06 363.463540
df.head().reset_index()
datetime open high low close volume ema
0 2021-04-01 366.20599 368.29001 366.03000 368.16000 4591212 363.46354
1 2021-03-31 362.85999 365.82001 362.85999 364.29001 4870674 362.28942
2 2021-03-30 363.79001 363.79001 361.28500 363.00000 3637520 361.78927
3 2021-03-29 362.66000 364.67001 361.10971 363.79001 3062900 361.48659
4 2021-03-26 359.42999 364.35001 358.75000 363.95999 3212525 360.91074

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_exp.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 seaborn as sns
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.tensorboard.export import export_to_tensorboard
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)
os.makedirs('./outputs/log', 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.6)]
val_df = df[int(n*0.6):int(n*0.8)]
test_df = df[int(n*0.8):]


# Data Normalization
train_mean = train_df.mean()
train_std = train_df.std()

train_df = (train_df - train_mean) / train_std
val_df = (val_df - train_mean) / train_std
test_df = (test_df - train_mean) / train_std


# Data Distribution Check
df_std = (df - train_mean) / train_std
df_std = df_std.melt(var_name='Column', value_name='Normalized')
plt.figure(figsize=(12, 6))
ax = sns.violinplot(x='Column', y='Normalized', data=df_std)
_ = ax.set_xticklabels(df.keys(), rotation=45)
run.log_image('feature_distribution_check', plot=plt)


# 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])
wide_conv_window = WindowGenerator(
    input_width=24 + CONV_WIDTH - 1,
    label_width=24, 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=45)
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')

conv_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)
# TODO - log training epoch history to tesnorboard

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')

wide_conv_window.plot(target, conv)
run.log_image('conv_pred', plot=plt)

# 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')

wide_window.plot(target, lstm)
run.log_image('lstm_pred', plot=plt)

# 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=45)
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}')
Overwriting aml-exp/train.py
 

Setup Training Environment

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', 'seaborn'], 
    pip_packages=['azureml-sdk', 'azureml-dataprep[fuse,pandas]', 
                  'azureml-pipeline', 'azureml.tensorboard', '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

Build Train Step

step1 = PythonScriptStep(name="train_step",
                         source_directory=src_dir,
                         script_name="train.py", 
                         arguments=['--dataset_name', f'{ticker.lower()}_ds'],
                         runconfig=aml_run_config, 
                         allow_reuse=True)

Build Pipeline

steps = [step1]
pipeline = Pipeline(workspace=ws, steps=steps)

Run Experiment

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

Review

Azure Portal

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/0e922ade-bbce-44c9-94c1-33ebdb17e44f?wsid=/subscriptions/f3b5840b-706e-44ba-8aa1-6fd3fc8aaab0/resourcegroups/ds-workspace/workspaces/minion-lab&tid=e6777dcd-6f87-4dd0-92e5-e98312157dac'

Metrics

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

step_name = 'train_step'
step = script_run.find_step_run(step_name)[0]
metrics_dict = step.get_metrics()

print(f"{metrics_dict.get('best_model')}: {metrics_dict.get('best_score'):0.4f}")
print()
print('\033[1m' + f"Model                        Val      Tst" + '\033[0m')
metrics_list = list(filter(lambda v: isinstance(v[1], list), metrics_dict.items()))

mae_metrics = []

for name, values in metrics_list:
    splits = name.split("_")
    grp, model = splits[0], "_".join(splits[1:])
    mae_metrics.append((model, grp, values[1]))
    
mae_metrics = list(sorted(mae_metrics, key=lambda o: o[0]))

for cur, nxt in zip(mae_metrics[0::2], mae_metrics[1::2]):
    name_1, _, value_1 = cur
    name_2, _, value_2 = nxt
    assert name_1 == name_2
    print(f'{name_1:25s}: {value_1:0.4f} | {value_2:0.4f}')
single_step_dense: 0.1610

Model                        Val      Tst
baseline                 : 0.1176 | 0.1872
conv                     : 0.0875 | 0.1773
linear                   : 3.0643 | 4.7024
lstm                     : 0.9358 | 2.6143
multi_step_dense         : 0.1977 | 0.6740
single_step_dense        : 0.1043 | 0.1610

All Stored Files

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

files = step.get_file_names()
files
['azureml-logs/20_image_build_log.txt',
 'azureml-logs/55_azureml-execution-tvmps_1d261b42abd5181b8b59daf381ea85917514878d6568f63fa19a6608f53ca32c_d.txt',
 'azureml-logs/65_job_prep-tvmps_1d261b42abd5181b8b59daf381ea85917514878d6568f63fa19a6608f53ca32c_d.txt',
 'azureml-logs/70_driver_log.txt',
 'azureml-logs/75_job_post-tvmps_1d261b42abd5181b8b59daf381ea85917514878d6568f63fa19a6608f53ca32c_d.txt',
 'azureml-logs/process_info.json',
 'azureml-logs/process_status.json',
 'baseline_pred_1617395547.png',
 'conv_pred_1617395562.png',
 'feature_distribution_check_1617395545.png',
 'linear_coef_1617395551.png',
 'linear_pred_1617395551.png',
 'logs/azureml/106_azureml.log',
 'logs/azureml/dataprep/backgroundProcess.log',
 'logs/azureml/dataprep/backgroundProcess_Telemetry.log',
 'logs/azureml/executionlogs.txt',
 'logs/azureml/job_prep_azureml.log',
 'logs/azureml/job_release_azureml.log',
 'logs/azureml/stderrlogs.txt',
 'logs/azureml/stdoutlogs.txt',
 'lstm_pred_1617395572.png',
 'multi_step_dense_pred_1617395558.png',
 '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_1617395572.png',
 'single_step_dense_pred_1617395555.png']

Clean up

Delete Compute Cluster

This is an important step if you don't want save some money 😉

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
compute cleanup complete