├── .gitignore
├── figures
├── ambient_temp_ours.pdf
├── ambient_temp_ours.png
├── detailed_architecture.pdf
└── detailed_architecture.png
├── codes
├── __pycache__
│ ├── base.cpython-36.pyc
│ ├── utils.cpython-36.pyc
│ ├── models.cpython-36.pyc
│ ├── trainers.cpython-36.pyc
│ └── data_loader.cpython-36.pyc
├── .ipynb_checkpoints
│ └── NAB-anomaly-detection-checkpoint.ipynb
├── NAB_config.json
├── train.py
├── utils.py
├── data_loader.py
├── trainers.py
├── base.py
└── models.py
├── datasets
├── NAB-known-anomaly
│ ├── nyc_taxi.npz
│ ├── ambient_temp.npz
│ ├── ec2_request.npz
│ ├── machine_temp.npz
│ └── cpu_utilization.npz
└── NAB-dataset-preprocessing.ipynb
├── requirements.txt
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | experiments/
2 | anomaly-env/
3 | codes/__*
4 | .idea/
5 | codes/.ipynb_checkpoints/
6 | datasets/.ipynb_checkpoints/
7 |
--------------------------------------------------------------------------------
/figures/ambient_temp_ours.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/figures/ambient_temp_ours.pdf
--------------------------------------------------------------------------------
/figures/ambient_temp_ours.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/figures/ambient_temp_ours.png
--------------------------------------------------------------------------------
/figures/detailed_architecture.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/figures/detailed_architecture.pdf
--------------------------------------------------------------------------------
/figures/detailed_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/figures/detailed_architecture.png
--------------------------------------------------------------------------------
/codes/__pycache__/base.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/codes/__pycache__/base.cpython-36.pyc
--------------------------------------------------------------------------------
/codes/__pycache__/utils.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/codes/__pycache__/utils.cpython-36.pyc
--------------------------------------------------------------------------------
/codes/__pycache__/models.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/codes/__pycache__/models.cpython-36.pyc
--------------------------------------------------------------------------------
/codes/__pycache__/trainers.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/codes/__pycache__/trainers.cpython-36.pyc
--------------------------------------------------------------------------------
/datasets/NAB-known-anomaly/nyc_taxi.npz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/datasets/NAB-known-anomaly/nyc_taxi.npz
--------------------------------------------------------------------------------
/codes/.ipynb_checkpoints/NAB-anomaly-detection-checkpoint.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [],
3 | "metadata": {},
4 | "nbformat": 4,
5 | "nbformat_minor": 2
6 | }
7 |
--------------------------------------------------------------------------------
/datasets/NAB-known-anomaly/ambient_temp.npz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/datasets/NAB-known-anomaly/ambient_temp.npz
--------------------------------------------------------------------------------
/datasets/NAB-known-anomaly/ec2_request.npz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/datasets/NAB-known-anomaly/ec2_request.npz
--------------------------------------------------------------------------------
/datasets/NAB-known-anomaly/machine_temp.npz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/datasets/NAB-known-anomaly/machine_temp.npz
--------------------------------------------------------------------------------
/codes/__pycache__/data_loader.cpython-36.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/codes/__pycache__/data_loader.cpython-36.pyc
--------------------------------------------------------------------------------
/datasets/NAB-known-anomaly/cpu_utilization.npz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/HEAD/datasets/NAB-known-anomaly/cpu_utilization.npz
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 |
2 | ###### Requirements without Version Specifiers ######
3 | matplotlib
4 | sklearn
5 | scipy
6 | tqdm
7 |
8 | ###### Requirements with Version Specifiers ######
9 | # See https://www.python.org/dev/peps/pep-0440/#version-specifiers
10 | numpy == 1.16.0
11 | pandas == 0.25.3
12 | GPy == 1.9.9
13 | munch == 2.3.2
14 | opencv-contrib-python==4.1.0.25
15 | tensorflow-gpu == 1.15.2 # Version Matching. Must be version 0.6.1
16 | tensorflow_probability == 0.7.0 # Minimum version 0.8.0
17 |
18 |
19 |
--------------------------------------------------------------------------------
/codes/NAB_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "exp_name": "NAB",
3 | "dataset": "machine_temp",
4 | "y_scale": 5,
5 | "one_image": 0,
6 | "l_seq": 12,
7 | "l_win": 48,
8 | "n_channel": 1,
9 | "TRAIN_VAE": 1,
10 | "TRAIN_LSTM": 1,
11 | "TRAIN_sigma": 0,
12 | "batch_size": 32,
13 | "batch_size_lstm": 32,
14 | "load_model": 1,
15 | "load_dir": "default",
16 | "num_epochs_vae": 0,
17 | "num_epochs_lstm": 20,
18 | "learning_rate_vae": 0.0004,
19 | "learning_rate_lstm": 0.0002,
20 | "code_size": 6,
21 | "sigma": 0.1,
22 | "sigma2_offset": 0.01,
23 | "num_hidden_units": 512,
24 | "num_hidden_units_lstm": 64
25 | }
--------------------------------------------------------------------------------
/codes/train.py:
--------------------------------------------------------------------------------
1 | import os
2 | import tensorflow as tf
3 | from data_loader import DataGenerator
4 | from models import VAEmodel, lstmKerasModel
5 | from trainers import vaeTrainer
6 | from utils import process_config, create_dirs, get_args, save_config
7 | os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" # see issue #152
8 | os.environ["CUDA_VISIBLE_DEVICES"] = "0"
9 |
10 |
11 | def main():
12 | # capture the config path from the run arguments
13 | # then process the json configuration file
14 | try:
15 | args = get_args()
16 | config = process_config(args.config)
17 | except:
18 | print("missing or invalid arguments")
19 | exit(0)
20 |
21 | # create the experiments dirs
22 | create_dirs([config['result_dir'], config['checkpoint_dir'], config['checkpoint_dir_lstm']])
23 | # save the config in a txt file
24 | save_config(config)
25 | # create tensorflow session
26 | sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
27 | # create your data generator
28 | data = DataGenerator(config)
29 | # create a CNN model
30 | model_vae = VAEmodel(config)
31 | # create a trainer for VAE model
32 | trainer_vae = vaeTrainer(sess, model_vae, data, config)
33 | model_vae.load(sess)
34 | # here you train your model
35 | if config['TRAIN_VAE']:
36 | if config['num_epochs_vae'] > 0:
37 | trainer_vae.train()
38 |
39 | if config['TRAIN_LSTM']:
40 | # create a lstm model class instance
41 | lstm_model = lstmKerasModel(data)
42 |
43 | # produce the embedding of all sequences for training of lstm model
44 | # process the windows in sequence to get their VAE embeddings
45 | lstm_model.produce_embeddings(config, model_vae, data, sess)
46 |
47 | # Create a basic model instance
48 | lstm_nn_model = lstm_model.create_lstm_model(config)
49 | lstm_nn_model.summary() # Display the model's architecture
50 | # checkpoint path
51 | checkpoint_path = config['checkpoint_dir_lstm']\
52 | + "cp.ckpt"
53 | # Create a callback that saves the model's weights
54 | cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
55 | save_weights_only=True,
56 | verbose=1)
57 | # load weights if possible
58 | lstm_model.load_model(lstm_nn_model, config, checkpoint_path)
59 |
60 | # start training
61 | if config['num_epochs_lstm'] > 0:
62 | lstm_model.train(config, lstm_nn_model, cp_callback)
63 |
64 | # make a prediction on the test set using the trained model
65 | lstm_embedding = lstm_nn_model.predict(lstm_model.x_test, batch_size=config['batch_size_lstm'])
66 | print(lstm_embedding.shape)
67 |
68 | # visualise the first 10 test sequences
69 | for i in range(10):
70 | lstm_model.plot_lstm_embedding_prediction(i, config, model_vae, sess, data, lstm_embedding)
71 |
72 |
73 | if __name__ == '__main__':
74 | main()
75 |
--------------------------------------------------------------------------------
/codes/utils.py:
--------------------------------------------------------------------------------
1 | """ util function.py """
2 |
3 | import json
4 | import os
5 | import argparse
6 | import tensorflow as tf
7 | from datetime import datetime
8 |
9 |
10 | def get_config_from_json(json_file):
11 | """
12 | Get the config from a json file
13 | :param json_file:
14 | :return: config(dictionary)
15 | """
16 | # parse the configurations from the config json file provided
17 | with open(json_file, 'r') as config_file:
18 | config_dict = json.load(config_file)
19 |
20 | return config_dict
21 |
22 |
23 | def save_config(config):
24 | dateTimeObj = datetime.now()
25 | timestampStr = dateTimeObj.strftime("%d-%b-%Y-%H-%M")
26 | filename = config['result_dir'] + 'training_config_{}.txt'.format(timestampStr)
27 | config_to_save = json.dumps(config)
28 | f = open(filename, "w")
29 | f.write(config_to_save)
30 | f.close()
31 |
32 |
33 | def process_config(json_file):
34 | config = get_config_from_json(json_file)
35 |
36 | # create directories to save experiment results and trained models
37 | if config['load_dir'] == "default":
38 | save_dir = "../experiments/local-results/{}/{}/batch-{}".format(
39 | config['exp_name'], config['dataset'], config['batch_size'])
40 | else:
41 | save_dir = config['load_dir']
42 | # specify the saving folder name for this experiment
43 | if config['TRAIN_sigma'] == 1:
44 | save_name = '{}-{}-{}-{}-{}-trainSigma'.format(config['exp_name'],
45 | config['dataset'],
46 | config['l_win'],
47 | config['l_seq'],
48 | config['code_size'])
49 | else:
50 | save_name = '{}-{}-{}-{}-{}-fixedSigma-{}'.format(config['exp_name'],
51 | config['dataset'],
52 | config['l_win'],
53 | config['l_seq'],
54 | config['code_size'],
55 | config['sigma'])
56 | config['summary_dir'] = os.path.join(save_dir, save_name, "summary/")
57 | config['result_dir'] = os.path.join(save_dir, save_name, "result/")
58 | config['checkpoint_dir'] = os.path.join(save_dir, save_name, "checkpoint/")
59 | config['checkpoint_dir_lstm'] = os.path.join(save_dir, save_name, "checkpoint/lstm/")
60 |
61 | return config
62 |
63 |
64 | def create_dirs(dirs):
65 | """
66 | dirs - a list of directories to create if these directories are not found
67 | :param dirs:
68 | :return exit_code: 0:success -1:failed
69 | """
70 | try:
71 | for dir_ in dirs:
72 | if not os.path.exists(dir_):
73 | os.makedirs(dir_)
74 | return 0
75 | except Exception as err:
76 | print("Creating directories error: {0}".format(err))
77 | exit(-1)
78 |
79 |
80 | def count_trainable_variables(scope_name):
81 | total_parameters = 0
82 | for variable in tf.trainable_variables(scope_name):
83 | # shape is an array of tf.Dimension
84 | shape = variable.get_shape()
85 | variable_parameters = 1
86 | for dim in shape:
87 | variable_parameters *= dim.value
88 | total_parameters += variable_parameters
89 | print(
90 | 'The total number of trainable parameters in the {} model is: {}'.format(scope_name, total_parameters))
91 | return total_parameters
92 |
93 |
94 | def get_args():
95 | argparser = argparse.ArgumentParser(description=__doc__)
96 | argparser.add_argument(
97 | '-c', '--config',
98 | metavar='C',
99 | default='None',
100 | help='The Configuration file')
101 | args = argparser.parse_args()
102 | return args
103 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VAE-LSTM for anomaly detection (ICASSP'20)
2 |
3 | This Github repository hosts our code and pre-processed data to train a VAE-LSTM hybrid model for anomaly detection, as proposed in our paper:
4 | [Anomaly Detection for Time Series Using VAE-LSTM Hybrid Model](https://ieeexplore.ieee.org/document/9053558).
5 |
6 | [Shuyu Lin1](https://shuyulin.co.uk/), [Ronald Clark2](http://www.ronnieclark.co.uk), Robert Birke3, Sandro Schönborn3, Niki Trigoni1, [Stephen Roberts1](https://www.robots.ox.ac.uk/~sjrob/)
7 |
8 | 1University of Oxford, 2Imperial College London, 3ABB Corporate Research
9 |
10 | In short, our anomaly detection model contains:
11 | * a VAE unit which summarizes the local information of a short window into a low-dimensional embedding,
12 | * a LSTM model, which acts on the low- dimensional embeddings produced by the VAE model, to manage the sequential patterns over longer term.
13 |
14 | An overview of our model is shown below:
15 |
16 |
17 |
18 |
19 | An example of anomaly detection on a time series of office temperature, which is provided by Numenta anomaly benchmark (NAB) datasets in their known anomaly subgroup [link](https://github.com/numenta/NAB/tree/master/data/realKnownCause):
20 |
21 |
22 |
23 |
24 |
25 |
26 | To run our code, please follow the instructions shown below.
27 |
28 | ## Environment
29 | Our code is written in Python3 with tensorflow 1.5 library.
30 | Please install the python libraries listed in the requirements.txt. We suggest to build a virtual environment using virtualenv package. To install and set up virtualenv, please follwo the procedures [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/).
31 |
32 | ## Data pre-processing
33 | We pre-processed the NAB data, which consists of 5 sequences, by creating a training set that does not contain any anomalies while keeping the rest of sequence as test set for evaluation. In addition, we removed the mean and standardized the time series. Pre-processed data are included in the `datasets/` subfolder with this repository for convenience of running our code. A demo ipython notebook has also been provided in `datasets/` to show how the pre-processing is done. Explanation of the different features saved in the .npz files for the pre-processed datasets are given in the [discussion](https://github.com/lin-shuyu/VAE-LSTM-for-anomaly-detection/issues/3).
34 |
35 | If you want to use the data for your own project, please cite and refer to the [NAB project](https://numenta.com/machine-intelligence-technology/numenta-anomaly-benchmark/) and access the raw data if needed.
36 |
37 | ## Training
38 | Our VAE-LSTM model is defined in several files in the `codes/` subfolder, including train.py, base.py, utils.py, data_loader.py, models.py, trainers.py. To train our model, simply run
39 | `python3 train.py --config NAB_config.json`,
40 | where NAB_config.json defines all the hyper-parameters of our model and you can experiment by using different values.
41 |
42 | ## Anomaly detection using the trained model
43 | After the model has been trained, we also prepare an iPython-notebook in NAB-anomaly-detection.ipynb for you to detect some anomalies detection on the test set. All you need to do is to run the code, make sure the NAB_config.json is prepared so that the right trained model will be loaded. The only thing that you need to specify in order to achieve reasonable anomaly detection result is to set a threshold on the anomaly detection metric. We suggest to observe the histogram of the anomaly detection metric and set the threshold accordingly.
44 |
45 | Hope you enjoy playing with our code and find it helpful for your projects! Happy anomaly detection!
46 |
47 | If you find our codes/project relevant to your work, please cite us in your work:
48 |
49 | ```
50 | @INPROCEEDINGS{VAE-LSTM-AD,
51 | author={S. {Lin} and R. {Clark} and R. {Birke} and S. {Schönborn} and N. {Trigoni} and S. {Roberts}},
52 | booktitle={ICASSP 2020 - 2020 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)},
53 | title={Anomaly Detection for Time Series Using VAE-LSTM Hybrid Model},
54 | year={2020}}
55 | ```
56 |
--------------------------------------------------------------------------------
/codes/data_loader.py:
--------------------------------------------------------------------------------
1 | from base import BaseDataGenerator
2 | import numpy as np
3 | import matplotlib.pylab as plt
4 | from matplotlib.pyplot import savefig
5 |
6 |
7 | class DataGenerator(BaseDataGenerator):
8 | def __init__(self, config):
9 | super(DataGenerator, self).__init__(config)
10 | # load data here: generate 3 state variables: train_set, val_set and test_set
11 | self.load_NAB_dataset(self.config['dataset'], self.config['y_scale'])
12 |
13 | def load_NAB_dataset(self, dataset, y_scale=6):
14 | data_dir = '../datasets/NAB-known-anomaly/'
15 | data = np.load(data_dir + dataset + '.npz')
16 |
17 | # normalise the dataset by training set mean and std
18 | train_m = data['train_m']
19 | train_std = data['train_std']
20 | readings_normalised = (data['readings'] - train_m) / train_std
21 |
22 | # plot normalised data
23 | fig, axs = plt.subplots(1, 1, figsize=(18, 4), edgecolor='k')
24 | fig.subplots_adjust(hspace=.4, wspace=.4)
25 | axs.plot(data['t'], readings_normalised)
26 | if data['idx_split'][0] == 0:
27 | axs.plot(data['idx_split'][1] * np.ones(20), np.linspace(-y_scale, y_scale, 20), 'b-')
28 | else:
29 | for i in range(2):
30 | axs.plot(data['idx_split'][i] * np.ones(20), np.linspace(-y_scale, y_scale, 20), 'b-')
31 | axs.plot(*np.ones(20), np.linspace(-y_scale, y_scale, 20), 'b--')
32 | for j in range(len(data['idx_anomaly'])):
33 | axs.plot(data['idx_anomaly'][j] * np.ones(20), np.linspace(-y_scale, 0.75 * y_scale, 20), 'r--')
34 | axs.grid(True)
35 | axs.set_xlim(0, len(data['t']))
36 | axs.set_ylim(-y_scale, y_scale)
37 | axs.set_xlabel("timestamp (every {})".format(data['t_unit']))
38 | axs.set_ylabel("readings")
39 | axs.set_title("{} dataset\n(normalised by train mean {:.4f} and std {:.4f})".format(dataset, train_m, train_std))
40 | axs.legend(('data', 'train test set split', 'anomalies'))
41 | savefig(self.config['result_dir'] + '/raw_data_normalised.pdf')
42 |
43 | # slice training set into rolling windows
44 | n_train_sample = len(data['training'])
45 | n_train_vae = n_train_sample - self.config['l_win'] + 1
46 | rolling_windows = np.zeros((n_train_vae, self.config['l_win']))
47 | for i in range(n_train_sample - self.config['l_win'] + 1):
48 | rolling_windows[i] = data['training'][i:i + self.config['l_win']]
49 |
50 | # create VAE training and validation set
51 | idx_train, idx_val, self.n_train_vae, self.n_val_vae = self.separate_train_and_val_set(n_train_vae)
52 | self.train_set_vae = dict(data=np.expand_dims(rolling_windows[idx_train], -1))
53 | self.val_set_vae = dict(data=np.expand_dims(rolling_windows[idx_val], -1))
54 | self.test_set_vae = dict(data=np.expand_dims(rolling_windows[idx_val[:self.config['batch_size']]], -1))
55 |
56 | # create LSTM training and validation set
57 | for k in range(self.config['l_win']):
58 | n_not_overlap_wins = (n_train_sample - k) // self.config['l_win']
59 | n_train_lstm = n_not_overlap_wins - self.config['l_seq'] + 1
60 | cur_lstm_seq = np.zeros((n_train_lstm, self.config['l_seq'], self.config['l_win']))
61 | for i in range(n_train_lstm):
62 | cur_seq = np.zeros((self.config['l_seq'], self.config['l_win']))
63 | for j in range(self.config['l_seq']):
64 | # print(k,i,j)
65 | cur_seq[j] = data['training'][k + self.config['l_win'] * (j + i): k + self.config['l_win'] * (j + i + 1)]
66 | cur_lstm_seq[i] = cur_seq
67 | if k == 0:
68 | lstm_seq = cur_lstm_seq
69 | else:
70 | lstm_seq = np.concatenate((lstm_seq, cur_lstm_seq), axis=0)
71 |
72 | n_train_lstm = lstm_seq.shape[0]
73 | idx_train, idx_val, self.n_train_lstm, self.n_val_lstm = self.separate_train_and_val_set(n_train_lstm)
74 | self.train_set_lstm = dict(data=np.expand_dims(lstm_seq[idx_train], -1))
75 | self.val_set_lstm = dict(data=np.expand_dims(lstm_seq[idx_val], -1))
76 |
77 | def plot_time_series(self, data, time, data_list):
78 | fig, axs = plt.subplots(1, 4, figsize=(18, 2.5), edgecolor='k')
79 | fig.subplots_adjust(hspace=.8, wspace=.4)
80 | axs = axs.ravel()
81 | for i in range(4):
82 | axs[i].plot(time / 60., data[:, i])
83 | axs[i].set_title(data_list[i])
84 | axs[i].set_xlabel('time (h)')
85 | axs[i].set_xlim((np.amin(time) / 60., np.amax(time) / 60.))
86 | savefig(self.config['result_dir'] + '/raw_training_set_normalised.pdf')
87 |
--------------------------------------------------------------------------------
/codes/trainers.py:
--------------------------------------------------------------------------------
1 | from base import BaseTrain
2 | import numpy as np
3 | import matplotlib.pylab as plt
4 | from matplotlib.pyplot import savefig
5 | from scipy.stats import multivariate_normal
6 |
7 |
8 | class vaeTrainer(BaseTrain):
9 | def __init__(self, sess, model, data, config):
10 | super(vaeTrainer, self).__init__(sess, model, data, config)
11 |
12 | def train_epoch(self):
13 | self.cur_epoch = self.model.cur_epoch_tensor.eval(self.sess)
14 |
15 | # training
16 | self.sess.run(self.model.iterator.initializer,
17 | feed_dict={self.model.original_signal: self.data.train_set_vae['data'],
18 | self.model.seed: self.cur_epoch})
19 | self.n_train_iter = self.data.n_train_vae // self.config['batch_size']
20 | idx_check_point = (self.n_train_iter - 1)
21 | train_loss_cur_epoch = 0.0
22 | for i in range(self.n_train_iter):
23 | loss = self.train_step()
24 | self.sess.run(self.model.increment_global_step_tensor)
25 | self.train_loss.append(np.squeeze(loss))
26 | train_loss_cur_epoch = train_loss_cur_epoch + loss
27 | if i == idx_check_point:
28 | test_loss, test_recons_loss_weighted, test_kl, test_sigma_regularisor, test_code_std_norm, test_cur_sigma2, test_recons_loss_ls = self.test_step()
29 | self.train_loss_ave_epoch.append(train_loss_cur_epoch / self.n_train_iter)
30 |
31 | # validation
32 | self.iter_epochs_list.append(self.n_train_iter * (self.cur_epoch + 1))
33 | self.sess.run(self.model.iterator.initializer,
34 | feed_dict={self.model.original_signal: self.data.val_set_vae['data'],
35 | self.model.seed: self.cur_epoch})
36 | self.n_val_iter = self.data.n_val_vae // self.config['batch_size']
37 | val_loss_cur_epoch = 0.0
38 | for i in range(self.n_val_iter):
39 | val_loss = self.val_step()
40 | val_loss_cur_epoch = val_loss_cur_epoch + val_loss
41 | self.val_loss_ave_epoch.append(val_loss_cur_epoch / self.n_val_iter)
42 |
43 | # save the model parameters at the end of this epoch
44 | self.model.save(self.sess)
45 |
46 | print(
47 | "{}/{}, test loss: -elbo: {:.4f}, recons_loss_weighted: {:.4f}, recons_loss_ls: {:.4f}, KL_loss: {:.4f}, sigma_regularisor: {:.4f}, code_std_dev: {}".format(
48 | self.cur_epoch,
49 | self.config['num_epochs_vae'] - 1,
50 | test_loss,
51 | test_recons_loss_weighted,
52 | np.squeeze(np.mean(test_recons_loss_ls)),
53 | test_kl,
54 | test_sigma_regularisor,
55 | np.squeeze(test_code_std_norm)))
56 | print("Loss on training and val sets:\ntrain: {:.4f}, val: {:.4f}".format(
57 | self.train_loss_ave_epoch[self.cur_epoch],
58 | self.val_loss_ave_epoch[self.cur_epoch]))
59 | print("Current sigma2: {:.7f}".format(test_cur_sigma2))
60 |
61 | # save the current variables
62 | self.save_variables_VAE()
63 |
64 | # reconstruction plot
65 | self.plot_reconstructed_signal()
66 |
67 | # generate samples from prior
68 | self.generate_samples_from_prior()
69 |
70 | # plot the training and validation loss over iterations/epochs
71 | self.plot_train_and_val_loss()
72 |
73 | def train_step(self):
74 | batch_image = self.sess.run(self.model.input_image)
75 | feed_dict = {self.model.original_signal: batch_image,
76 | self.model.is_code_input: False,
77 | self.model.code_input: np.zeros((1, self.config['code_size'])),
78 | self.model.lr: self.config['learning_rate_vae'] * (0.98 ** self.cur_epoch)}
79 | train_loss, _ = self.sess.run([self.model.elbo_loss, self.model.train_step_gradient],
80 | feed_dict=feed_dict)
81 | return train_loss
82 |
83 | def val_step(self):
84 | input_image_val = self.sess.run(self.model.input_image)
85 | val_cost, recon_loss_val, kl_loss_val, std_dev_loss_val = self.sess.run([self.model.elbo_loss,
86 | self.model.ls_reconstruction_error,
87 | self.model.KL_loss,
88 | self.model.std_dev_norm],
89 | feed_dict={
90 | self.model.original_signal: input_image_val,
91 | self.model.is_code_input: False,
92 | self.model.code_input: np.zeros(
93 | (1, self.config['code_size']))})
94 | self.val_loss.append(np.squeeze(val_cost))
95 | self.recons_loss_val.append(np.squeeze(np.mean(recon_loss_val)))
96 | self.KL_loss_val.append(kl_loss_val)
97 | return val_cost
98 |
99 | def test_step(self):
100 | feed_dict = {self.model.original_signal: self.data.test_set_vae['data'],
101 | self.model.is_code_input: False,
102 | self.model.code_input: np.zeros((1, self.config['code_size']))}
103 | self.output_test, test_loss, test_recons_loss_weighted, test_kl, test_sigma_regularisor, test_code_std_norm, test_cur_sigma2, test_recons_loss_ls = self.sess.run(
104 | [self.model.decoded,
105 | self.model.elbo_loss,
106 | self.model.weighted_reconstruction_error_dataset,
107 | self.model.KL_loss,
108 | self.model.sigma_regularisor_dataset,
109 | self.model.std_dev_norm,
110 | self.model.sigma2,
111 | self.model.ls_reconstruction_error],
112 | feed_dict=feed_dict)
113 | self.test_sigma2.append(np.squeeze(test_cur_sigma2))
114 | return test_loss, test_recons_loss_weighted, test_kl, test_sigma_regularisor, test_code_std_norm, np.squeeze(
115 | test_cur_sigma2), test_recons_loss_ls
116 |
117 | def plot_reconstructed_signal(self):
118 | input_images = np.squeeze(self.data.test_set_vae['data'])
119 | decoded_images = np.squeeze(self.output_test)
120 | n_images = 20
121 | # plot the reconstructed image for a shape
122 | for j in range(self.config['n_channel']):
123 | fig, axs = plt.subplots(4, 5, figsize=(18, 10), edgecolor='k')
124 | fig.subplots_adjust(hspace=.4, wspace=.4)
125 | axs = axs.ravel()
126 | for i in range(n_images):
127 | if self.config['n_channel'] == 1:
128 | axs[i].plot(input_images[i])
129 | axs[i].plot(decoded_images[i])
130 | else:
131 | axs[i].plot(input_images[i, :, j])
132 | axs[i].plot(decoded_images[i, :, j])
133 | axs[i].grid(True)
134 | axs[i].set_xlim(0, self.config['l_win'])
135 | axs[i].set_ylim(-5, 5)
136 | if i == 19:
137 | axs[i].legend(('original', 'reconstructed'))
138 | plt.suptitle('Channel {}'.format(j))
139 | savefig(self.config['result_dir'] + 'test_reconstructed_{}_{}.pdf'.format(self.cur_epoch, j))
140 | fig.clf()
141 | plt.close()
142 |
143 | def generate_samples_from_prior(self):
144 | rv = multivariate_normal(np.zeros(self.config['code_size']), np.diag(np.ones(self.config['code_size'])))
145 | # Generate a batch size of samples from the prior samples
146 | n_images = 20
147 | samples_code_prior = rv.rvs(n_images)
148 | sampled_images = self.sess.run(self.model.decoded,
149 | feed_dict={self.model.original_signal: np.zeros(
150 | (n_images, self.config['l_win'], self.config['n_channel'])),
151 | self.model.is_code_input: True,
152 | self.model.code_input: samples_code_prior})
153 | sampled_images = np.squeeze(sampled_images)
154 | for j in range(self.config['n_channel']):
155 | fig, axs = plt.subplots(4, 5, figsize=(18, 10), edgecolor='k')
156 | fig.subplots_adjust(hspace=.4, wspace=.4)
157 | axs = axs.ravel()
158 | for i in range(n_images):
159 | if self.config['n_channel'] == 1:
160 | axs[i].plot(sampled_images[i])
161 | else:
162 | axs[i].plot(sampled_images[i, :, j])
163 | axs[i].grid(True)
164 | axs[i].set_xlim(0, self.config['l_win'])
165 | axs[i].set_ylim(-5, 5)
166 | plt.suptitle('Channel {}'.format(j))
167 | savefig(self.config['result_dir'] + 'generated_samples_{}_{}.pdf'.format(self.cur_epoch, j))
168 | fig.clf()
169 | plt.close()
170 |
--------------------------------------------------------------------------------
/codes/base.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import tensorflow_probability as tfp
3 | import random
4 | import numpy as np
5 | import time
6 | import matplotlib.pylab as plt
7 | from matplotlib.pyplot import plot, savefig, figure
8 | from utils import count_trainable_variables
9 | tfd = tfp.distributions
10 |
11 |
12 | class BaseDataGenerator:
13 | def __init__(self, config):
14 | self.config = config
15 |
16 | # separate training and val sets
17 | def separate_train_and_val_set(self, n_win):
18 | n_train = int(np.floor((n_win * 0.9)))
19 | n_val = n_win - n_train
20 | idx_train = random.sample(range(n_win), n_train)
21 | idx_val = list(set(idx_train) ^ set(range(n_win)))
22 | return idx_train, idx_val, n_train, n_val
23 |
24 |
25 | class BaseModel:
26 | def __init__(self, config):
27 | self.config = config
28 | # init the global step
29 | self.init_global_step()
30 | # init the epoch counter
31 | self.init_cur_epoch()
32 | self.two_pi = tf.constant(2 * np.pi)
33 |
34 | # save function that saves the checkpoint in the path defined in the config file
35 | def save(self, sess):
36 | print("Saving model...")
37 | self.saver.save(sess, self.config['checkpoint_dir'],
38 | self.global_step_tensor)
39 | print("Model saved.")
40 |
41 | # load latest checkpoint from the experiment path defined in the config file
42 | def load(self, sess):
43 | print("checkpoint_dir at loading: {}".format(self.config['checkpoint_dir']))
44 | latest_checkpoint = tf.train.latest_checkpoint(self.config['checkpoint_dir'])
45 |
46 | if latest_checkpoint:
47 | print("Loading model checkpoint {} ...\n".format(latest_checkpoint))
48 | self.saver.restore(sess, latest_checkpoint)
49 | print("Model loaded.")
50 | else:
51 | print("No model loaded.")
52 |
53 | # initialize a tensorflow variable to use it as epoch counter
54 | def init_cur_epoch(self):
55 | with tf.variable_scope('cur_epoch'):
56 | self.cur_epoch_tensor = tf.Variable(0, trainable=False, name='cur_epoch')
57 | self.increment_cur_epoch_tensor = tf.assign(self.cur_epoch_tensor, self.cur_epoch_tensor + 1)
58 |
59 | # just initialize a tensorflow variable to use it as global step counter
60 | def init_global_step(self):
61 | # DON'T forget to add the global step tensor to the tensorflow trainer
62 | with tf.variable_scope('global_step'):
63 | self.global_step_tensor = tf.Variable(0, trainable=False, name='global_step')
64 | self.increment_global_step_tensor = tf.assign(
65 | self.global_step_tensor, self.global_step_tensor + 1)
66 |
67 | def define_loss(self):
68 | with tf.name_scope("loss"):
69 | # KL divergence loss - analytical result
70 | KL_loss = 0.5 * (tf.reduce_sum(tf.square(self.code_mean), 1)
71 | + tf.reduce_sum(tf.square(self.code_std_dev), 1)
72 | - tf.reduce_sum(tf.log(tf.square(self.code_std_dev)), 1)
73 | - self.config['code_size'])
74 | self.KL_loss = tf.reduce_mean(KL_loss)
75 |
76 | # norm 1 of standard deviation of the sample-wise encoder prediction
77 | self.std_dev_norm = tf.reduce_mean(self.code_std_dev, axis=0)
78 |
79 | weighted_reconstruction_error_dataset = tf.reduce_sum(
80 | tf.square(self.original_signal - self.decoded), [1, 2])
81 | weighted_reconstruction_error_dataset = tf.reduce_mean(weighted_reconstruction_error_dataset)
82 | self.weighted_reconstruction_error_dataset = weighted_reconstruction_error_dataset / (2 * self.sigma2)
83 |
84 | # least squared reconstruction error
85 | ls_reconstruction_error = tf.reduce_sum(
86 | tf.square(self.original_signal - self.decoded), [1, 2])
87 | self.ls_reconstruction_error = tf.reduce_mean(ls_reconstruction_error)
88 |
89 | # sigma regularisor - input elbo
90 | self.sigma_regularisor_dataset = self.input_dims / 2 * tf.log(self.sigma2)
91 | two_pi = self.input_dims / 2 * tf.constant(2 * np.pi)
92 |
93 | self.elbo_loss = two_pi + self.sigma_regularisor_dataset + \
94 | 0.5 * self.weighted_reconstruction_error_dataset + self.KL_loss
95 |
96 | def training_variables(self):
97 | encoder_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "encoder")
98 | decoder_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "decoder")
99 | sigma_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, "sigma2_dataset")
100 | self.train_vars_VAE = encoder_vars + decoder_vars + sigma_vars
101 |
102 | num_encoder = count_trainable_variables('encoder')
103 | num_decoder = count_trainable_variables('decoder')
104 | num_sigma2 = count_trainable_variables('sigma2_dataset')
105 | self.num_vars_total = num_decoder + num_encoder + num_sigma2
106 | print("Total number of trainable parameters in the VAE network is: {}".format(self.num_vars_total))
107 |
108 | def compute_gradients(self):
109 | self.lr = tf.placeholder(tf.float32, [])
110 | opt = tf.train.AdamOptimizer(learning_rate=self.lr, beta1=0.9, beta2=0.95)
111 | update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
112 | gvs_dataset = opt.compute_gradients(self.elbo_loss, var_list=self.train_vars_VAE)
113 | print('gvs for dataset: {}'.format(gvs_dataset))
114 | capped_gvs = [(self.ClipIfNotNone(grad), var) for grad, var in gvs_dataset]
115 |
116 | with tf.control_dependencies(update_ops):
117 | self.train_step_gradient = opt.apply_gradients(capped_gvs)
118 | print("Reach the definition of loss for VAE")
119 |
120 | def ClipIfNotNone(self, grad):
121 | if grad is None:
122 | return grad
123 | return tf.clip_by_value(grad, -1, 1)
124 |
125 | def init_saver(self):
126 | self.saver = tf.train.Saver(max_to_keep=1, var_list=self.train_vars_VAE)
127 |
128 |
129 | class BaseTrain:
130 | def __init__(self, sess, model, data, config):
131 | self.model = model
132 | self.config = config
133 | self.sess = sess
134 | self.data = data
135 | self.init = tf.group(tf.global_variables_initializer(),
136 | tf.local_variables_initializer())
137 | self.sess.run(self.init)
138 |
139 | # keep a record of the training result
140 | self.train_loss = []
141 | self.val_loss = []
142 | self.train_loss_ave_epoch = []
143 | self.val_loss_ave_epoch = []
144 | self.recons_loss_train = []
145 | self.recons_loss_val = []
146 | self.KL_loss_train = []
147 | self.KL_loss_val = []
148 | self.sample_std_dev_train = []
149 | self.sample_std_dev_val = []
150 | self.iter_epochs_list = []
151 | self.test_sigma2 = []
152 |
153 | def train(self):
154 | self.start_time = time.time()
155 | for cur_epoch in range(0, self.config['num_epochs_vae'], 1):
156 | self.train_epoch()
157 |
158 | # compute current execution time
159 | self.current_time = time.time()
160 | elapsed_time = (self.current_time - self.start_time) / 60
161 | est_remaining_time = (
162 | self.current_time - self.start_time) / (cur_epoch + 1) * (
163 | self.config['num_epochs_vae'] - cur_epoch - 1)
164 | est_remaining_time = est_remaining_time / 60
165 | print("Already trained for {} min; Remaining {} min.".format(elapsed_time, est_remaining_time))
166 | self.sess.run(self.model.increment_cur_epoch_tensor)
167 |
168 | def save_variables_VAE(self):
169 | # save some variables for later inspection
170 | file_name = "{}{}-batch-{}-epoch-{}-code-{}-lr-{}.npz".format(self.config['result_dir'],
171 | self.config['exp_name'],
172 | self.config['batch_size'],
173 | self.config['num_epochs_vae'],
174 | self.config['code_size'],
175 | self.config['learning_rate_vae'])
176 | np.savez(file_name,
177 | iter_list_val=self.iter_epochs_list,
178 | train_loss=self.train_loss,
179 | val_loss=self.val_loss,
180 | n_train_iter=self.n_train_iter,
181 | n_val_iter=self.n_val_iter,
182 | recons_loss_train=self.recons_loss_train,
183 | recons_loss_val=self.recons_loss_val,
184 | KL_loss_train=self.KL_loss_train,
185 | KL_loss_val=self.KL_loss_val,
186 | num_para_all=self.model.num_vars_total,
187 | sigma2=self.test_sigma2)
188 |
189 | def plot_train_and_val_loss(self):
190 | # plot the training and validation loss over epochs
191 | plt.clf()
192 | figure(num=1, figsize=(8, 6))
193 | plot(self.train_loss, 'b-')
194 | plot(self.iter_epochs_list, self.val_loss_ave_epoch, 'r-')
195 | plt.legend(('training loss (total)', 'validation loss'))
196 | plt.title('training loss over iterations (val @ epochs)')
197 | plt.ylabel('total loss')
198 | plt.xlabel('iterations')
199 | plt.grid(True)
200 | savefig(self.config['result_dir'] + '/loss.png')
201 |
202 | # plot individual components of validation loss over epochs
203 | plt.clf()
204 | figure(num=1, figsize=(8, 6))
205 | plot(self.recons_loss_val, 'b-')
206 | plot(self.KL_loss_val, 'r-')
207 | plt.legend(('Reconstruction loss', 'KL loss'))
208 | plt.title('validation loss breakdown')
209 | plt.ylabel('loss')
210 | plt.xlabel('num of batch')
211 | plt.grid(True)
212 | savefig(self.config['result_dir'] + '/val-loss.png')
213 |
214 | # plot individual components of validation loss over epochs
215 | plt.clf()
216 | figure(num=1, figsize=(8, 6))
217 | plot(self.test_sigma2, 'b-')
218 | plt.title('sigma2 over training')
219 | plt.ylabel('sigma2')
220 | plt.xlabel('iter')
221 | plt.grid(True)
222 | savefig(self.config['result_dir'] + '/sigma2.png')
223 |
--------------------------------------------------------------------------------
/codes/models.py:
--------------------------------------------------------------------------------
1 | from base import BaseModel
2 | import os
3 | import numpy as np
4 | import matplotlib.pylab as plt
5 | from matplotlib.pyplot import savefig
6 | import tensorflow as tf
7 | import tensorflow_probability as tfp
8 |
9 | tfd = tfp.distributions
10 |
11 |
12 | class VAEmodel(BaseModel):
13 | def __init__(self, config):
14 | super(VAEmodel, self).__init__(config)
15 | self.input_dims = self.config['l_win'] * self.config['n_channel']
16 |
17 | self.define_iterator()
18 | self.build_model()
19 | self.define_loss()
20 | self.training_variables()
21 | self.compute_gradients()
22 | self.init_saver()
23 |
24 | def define_iterator(self):
25 | self.original_signal = tf.placeholder(tf.float32, [None, self.config['l_win'], self.config['n_channel']])
26 | self.seed = tf.placeholder(tf.int64, shape=())
27 | self.dataset = tf.data.Dataset.from_tensor_slices(self.original_signal)
28 | self.dataset = self.dataset.shuffle(buffer_size=60000, seed=self.seed)
29 | self.dataset = self.dataset.repeat(8000)
30 | self.dataset = self.dataset.batch(self.config['batch_size'], drop_remainder=True)
31 | self.iterator = self.dataset.make_initializable_iterator()
32 | self.input_image = self.iterator.get_next()
33 | self.code_input = tf.placeholder(tf.float32, [None, self.config['code_size']])
34 | self.is_code_input = tf.placeholder(tf.bool)
35 | self.sigma2_offset = tf.constant(self.config['sigma2_offset'])
36 |
37 | def build_model(self):
38 | init = tf.contrib.layers.xavier_initializer()
39 | with tf.variable_scope('encoder'):
40 | input_tensor = tf.expand_dims(self.original_signal, -1)
41 | if self.config['l_win'] == 24:
42 | conv_1 = tf.layers.conv2d(inputs=tf.pad(input_tensor, [[0, 0], [4, 4], [0, 0], [0, 0]], "SYMMETRIC"),
43 | filters=self.config['num_hidden_units'] / 16,
44 | kernel_size=(3, self.config['n_channel']),
45 | strides=(2, 1),
46 | padding='same',
47 | activation=tf.nn.leaky_relu,
48 | kernel_initializer=init)
49 | print("conv_1: {}".format(conv_1))
50 | conv_2 = tf.layers.conv2d(inputs=conv_1,
51 | filters=self.config['num_hidden_units'] / 8,
52 | kernel_size=(3, self.config['n_channel']),
53 | strides=(2, 1),
54 | padding='same',
55 | activation=tf.nn.leaky_relu,
56 | kernel_initializer=init)
57 | print("conv_2: {}".format(conv_2))
58 | conv_3 = tf.layers.conv2d(inputs=conv_2,
59 | filters=self.config['num_hidden_units'] / 4,
60 | kernel_size=(3, self.config['n_channel']),
61 | strides=(2, 1),
62 | padding='same',
63 | activation=tf.nn.leaky_relu,
64 | kernel_initializer=init)
65 | print("conv_3: {}".format(conv_3))
66 | conv_4 = tf.layers.conv2d(inputs=conv_3,
67 | filters=self.config['num_hidden_units'],
68 | kernel_size=(4, self.config['n_channel']),
69 | strides=1,
70 | padding='valid',
71 | activation=tf.nn.leaky_relu,
72 | kernel_initializer=init)
73 | print("conv_4: {}".format(conv_4))
74 | elif self.config['l_win'] == 48:
75 | conv_1 = tf.layers.conv2d(input_tensor,
76 | filters=self.config['num_hidden_units'] / 16,
77 | kernel_size=(3, self.config['n_channel']),
78 | strides=(2, 1),
79 | padding='same',
80 | activation=tf.nn.leaky_relu,
81 | kernel_initializer=init)
82 | print("conv_1: {}".format(conv_1))
83 | conv_2 = tf.layers.conv2d(inputs=conv_1,
84 | filters=self.config['num_hidden_units'] / 8,
85 | kernel_size=(3, self.config['n_channel']),
86 | strides=(2, 1),
87 | padding='same',
88 | activation=tf.nn.leaky_relu,
89 | kernel_initializer=init)
90 | print("conv_2: {}".format(conv_2))
91 | conv_3 = tf.layers.conv2d(inputs=conv_2,
92 | filters=self.config['num_hidden_units'] / 4,
93 | kernel_size=(3, self.config['n_channel']),
94 | strides=(2, 1),
95 | padding='same',
96 | activation=tf.nn.leaky_relu,
97 | kernel_initializer=init)
98 | print("conv_3: {}".format(conv_3))
99 | conv_4 = tf.layers.conv2d(inputs=conv_3,
100 | filters=self.config['num_hidden_units'],
101 | kernel_size=(6, self.config['n_channel']),
102 | strides=1,
103 | padding='valid',
104 | activation=tf.nn.leaky_relu,
105 | kernel_initializer=init)
106 | print("conv_4: {}".format(conv_4))
107 | elif self.config['l_win'] == 144:
108 | conv_1 = tf.layers.conv2d(inputs=input_tensor,
109 | filters=self.config['num_hidden_units'] / 16,
110 | kernel_size=(3, self.config['n_channel']),
111 | strides=(4, 1),
112 | padding='same',
113 | activation=tf.nn.leaky_relu,
114 | kernel_initializer=init)
115 | print("conv_1: {}".format(conv_1))
116 | conv_2 = tf.layers.conv2d(inputs=conv_1,
117 | filters=self.config['num_hidden_units'] / 8,
118 | kernel_size=(3, self.config['n_channel']),
119 | strides=(4, 1),
120 | padding='same',
121 | activation=tf.nn.leaky_relu,
122 | kernel_initializer=init)
123 | print("conv_2: {}".format(conv_2))
124 | conv_3 = tf.layers.conv2d(inputs=conv_2,
125 | filters=self.config['num_hidden_units'] / 4,
126 | kernel_size=(3, self.config['n_channel']),
127 | strides=(3, 1),
128 | padding='same',
129 | activation=tf.nn.leaky_relu,
130 | kernel_initializer=init)
131 | print("conv_3: {}".format(conv_3))
132 | conv_4 = tf.layers.conv2d(inputs=conv_3,
133 | filters=self.config['num_hidden_units'],
134 | kernel_size=(3, self.config['n_channel']),
135 | strides=1,
136 | padding='valid',
137 | activation=tf.nn.leaky_relu,
138 | kernel_initializer=init)
139 | print("conv_4: {}".format(conv_4))
140 |
141 | encoded_signal = tf.layers.flatten(conv_4)
142 | encoded_signal = tf.layers.dense(encoded_signal,
143 | units=self.config['code_size'] * 4,
144 | activation=tf.nn.leaky_relu,
145 | kernel_initializer=init)
146 | self.code_mean = tf.layers.dense(encoded_signal,
147 | units=self.config['code_size'],
148 | activation=None,
149 | kernel_initializer=init,
150 | name='code_mean')
151 | self.code_std_dev = tf.layers.dense(encoded_signal,
152 | units=self.config['code_size'],
153 | activation=tf.nn.relu,
154 | kernel_initializer=init,
155 | name='code_std_dev')
156 | self.code_std_dev = self.code_std_dev + 1e-2
157 | mvn = tfp.distributions.MultivariateNormalDiag(loc=self.code_mean, scale_diag=self.code_std_dev)
158 | self.code_sample = mvn.sample()
159 | print("finish encoder: \n{}".format(self.code_sample))
160 | print("\n")
161 |
162 | with tf.variable_scope('decoder'):
163 | encoded = tf.cond(self.is_code_input, lambda: self.code_input, lambda: self.code_sample)
164 | decoded_1 = tf.layers.dense(encoded,
165 | units=self.config['num_hidden_units'],
166 | activation=tf.nn.leaky_relu,
167 | kernel_initializer=init)
168 | decoded_1 = tf.reshape(decoded_1, [-1, 1, 1, self.config['num_hidden_units']])
169 | if self.config['l_win'] == 24:
170 | decoded_2 = tf.layers.conv2d(decoded_1,
171 | filters=self.config['num_hidden_units'],
172 | kernel_size=1,
173 | padding='same',
174 | activation=tf.nn.leaky_relu)
175 | decoded_2 = tf.reshape(decoded_2, [-1, 4, 1, self.config['num_hidden_units'] // 4])
176 | print("decoded_2 is: {}".format(decoded_2))
177 | decoded_3 = tf.layers.conv2d(decoded_2,
178 | filters=self.config['num_hidden_units'] // 4,
179 | kernel_size=(3, 1),
180 | strides=1,
181 | padding='same',
182 | activation=tf.nn.leaky_relu,
183 | kernel_initializer=init)
184 | decoded_3 = tf.nn.depth_to_space(input=decoded_3,
185 | block_size=2)
186 | decoded_3 = tf.reshape(decoded_3, [-1, 8, 1, self.config['num_hidden_units'] // 8])
187 | print("decoded_3 is: {}".format(decoded_3))
188 | decoded_4 = tf.layers.conv2d(decoded_3,
189 | filters=self.config['num_hidden_units'] // 8,
190 | kernel_size=(3, 1),
191 | strides=1,
192 | padding='same',
193 | activation=tf.nn.leaky_relu,
194 | kernel_initializer=init)
195 | decoded_4 = tf.nn.depth_to_space(input=decoded_4,
196 | block_size=2)
197 | decoded_4 = tf.reshape(decoded_4, [-1, 16, 1, self.config['num_hidden_units'] // 16])
198 | print("decoded_4 is: {}".format(decoded_4))
199 | decoded_5 = tf.layers.conv2d(decoded_4,
200 | filters=self.config['num_hidden_units'] // 16,
201 | kernel_size=(3, 1),
202 | strides=1,
203 | padding='same',
204 | activation=tf.nn.leaky_relu,
205 | kernel_initializer=init)
206 | decoded_5 = tf.nn.depth_to_space(input=decoded_5,
207 | block_size=2)
208 | decoded_5 = tf.reshape(decoded_5, [-1, self.config['num_hidden_units'] // 16, 1, 16])
209 | print("decoded_5 is: {}".format(decoded_5))
210 | decoded = tf.layers.conv2d(inputs=decoded_5,
211 | filters=self.config['n_channel'],
212 | kernel_size=(9, 1),
213 | strides=1,
214 | padding='valid',
215 | activation=None,
216 | kernel_initializer=init)
217 | print("decoded_6 is: {}".format(decoded))
218 | self.decoded = tf.reshape(decoded, [-1, self.config['l_win'], self.config['n_channel']])
219 | elif self.config['l_win'] == 48:
220 | decoded_2 = tf.layers.conv2d(decoded_1,
221 | filters=256 * 3,
222 | kernel_size=1,
223 | padding='same',
224 | activation=tf.nn.leaky_relu)
225 | decoded_2 = tf.reshape(decoded_2, [-1, 3, 1, 256])
226 | print("decoded_2 is: {}".format(decoded_2))
227 | decoded_3 = tf.layers.conv2d(decoded_2,
228 | filters=256,
229 | kernel_size=(3, 1),
230 | strides=1,
231 | padding='same',
232 | activation=tf.nn.leaky_relu,
233 | kernel_initializer=init)
234 | decoded_3 = tf.nn.depth_to_space(input=decoded_3,
235 | block_size=2)
236 | decoded_3 = tf.reshape(decoded_3, [-1, 6, 1, 128])
237 | print("decoded_3 is: {}".format(decoded_3))
238 | decoded_4 = tf.layers.conv2d(decoded_3,
239 | filters=128,
240 | kernel_size=(3, 1),
241 | strides=1,
242 | padding='same',
243 | activation=tf.nn.leaky_relu,
244 | kernel_initializer=init)
245 | decoded_4 = tf.nn.depth_to_space(input=decoded_4,
246 | block_size=2)
247 | decoded_4 = tf.reshape(decoded_4, [-1, 24, 1, 32])
248 | print("decoded_4 is: {}".format(decoded_4))
249 | decoded_5 = tf.layers.conv2d(decoded_4,
250 | filters=32,
251 | kernel_size=(3, 1),
252 | strides=1,
253 | padding='same',
254 | activation=tf.nn.leaky_relu,
255 | kernel_initializer=init)
256 | decoded_5 = tf.nn.depth_to_space(input=decoded_5,
257 | block_size=2)
258 | decoded_5 = tf.reshape(decoded_5, [-1, 48, 1, 16])
259 | print("decoded_5 is: {}".format(decoded_5))
260 | decoded = tf.layers.conv2d(inputs=decoded_5,
261 | filters=1,
262 | kernel_size=(5, self.config['n_channel']),
263 | strides=1,
264 | padding='same',
265 | activation=None,
266 | kernel_initializer=init)
267 | print("decoded_6 is: {}".format(decoded))
268 | self.decoded = tf.reshape(decoded, [-1, self.config['l_win'], self.config['n_channel']])
269 | elif self.config['l_win'] == 144:
270 | decoded_2 = tf.layers.conv2d(decoded_1,
271 | filters=32 * 27,
272 | kernel_size=1,
273 | strides=1,
274 | padding='same',
275 | activation=tf.nn.leaky_relu)
276 | decoded_2 = tf.reshape(decoded_2, [-1, 3, 1, 32 * 9])
277 | print("decoded_2 is: {}".format(decoded_2))
278 | decoded_3 = tf.layers.conv2d(decoded_2,
279 | filters=32 * 9,
280 | kernel_size=(3, 1),
281 | strides=1,
282 | padding='same',
283 | activation=tf.nn.leaky_relu,
284 | kernel_initializer=init)
285 | decoded_3 = tf.nn.depth_to_space(input=decoded_3,
286 | block_size=3)
287 | decoded_3 = tf.reshape(decoded_3, [-1, 9, 1, 32 * 3])
288 | print("decoded_3 is: {}".format(decoded_3))
289 | decoded_4 = tf.layers.conv2d(decoded_3,
290 | filters=32 * 3,
291 | kernel_size=(3, 1),
292 | strides=1,
293 | padding='same',
294 | activation=tf.nn.leaky_relu,
295 | kernel_initializer=init)
296 | decoded_4 = tf.nn.depth_to_space(input=decoded_4,
297 | block_size=2)
298 | decoded_4 = tf.reshape(decoded_4, [-1, 36, 1, 24])
299 | print("decoded_4 is: {}".format(decoded_4))
300 | decoded_5 = tf.layers.conv2d(decoded_4,
301 | filters=24,
302 | kernel_size=(3, 1),
303 | strides=1,
304 | padding='same',
305 | activation=tf.nn.leaky_relu,
306 | kernel_initializer=init)
307 | decoded_5 = tf.nn.depth_to_space(input=decoded_5,
308 | block_size=2)
309 | decoded_5 = tf.reshape(decoded_5, [-1, 144, 1, 6])
310 | print("decoded_5 is: {}".format(decoded_5))
311 | decoded = tf.layers.conv2d(inputs=decoded_5,
312 | filters=1,
313 | kernel_size=(9, self.config['n_channel']),
314 | strides=1,
315 | padding='same',
316 | activation=None,
317 | kernel_initializer=init)
318 | print("decoded_6 is: {}".format(decoded))
319 | self.decoded = tf.reshape(decoded, [-1, self.config['l_win'], self.config['n_channel']])
320 | print("finish decoder: \n{}".format(self.decoded))
321 | print('\n')
322 |
323 | # define sigma2 parameter to be trained to optimise ELBO
324 | with tf.variable_scope('sigma2_dataset'):
325 | if self.config['TRAIN_sigma'] == 1:
326 | sigma = tf.Variable(tf.cast(self.config['sigma'], tf.float32),
327 | dtype=tf.float32, trainable=True)
328 | else:
329 | sigma = tf.cast(self.config['sigma'], tf.float32)
330 | self.sigma2 = tf.square(sigma)
331 | if self.config['TRAIN_sigma'] == 1:
332 | self.sigma2 = self.sigma2 + self.sigma2_offset
333 |
334 | print("sigma2: \n{}\n".format(self.sigma2))
335 |
336 |
337 | class lstmKerasModel:
338 | def __init__(self, data):
339 | pass
340 |
341 | def create_lstm_model(self, config):
342 | lstm_input = tf.keras.layers.Input(shape=(config['l_seq'] - 1, config['code_size']))
343 | LSTM1 = tf.keras.layers.LSTM(config['num_hidden_units_lstm'], return_sequences=True)(lstm_input)
344 | LSTM2 = tf.keras.layers.LSTM(config['num_hidden_units_lstm'], return_sequences=True)(LSTM1)
345 | lstm_output = tf.keras.layers.LSTM(config['code_size'], return_sequences=True, activation=None)(LSTM2)
346 | lstm_model = tf.keras.Model(lstm_input, lstm_output)
347 | lstm_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=config['learning_rate_lstm']),
348 | loss='mse',
349 | metrics=['mse'])
350 | return lstm_model
351 |
352 | def produce_embeddings(self, config, model_vae, data, sess):
353 | self.embedding_lstm_train = np.zeros((data.n_train_lstm, config['l_seq'], config['code_size']))
354 | for i in range(data.n_train_lstm):
355 | feed_dict = {model_vae.original_signal: data.train_set_lstm['data'][i],
356 | model_vae.is_code_input: False,
357 | model_vae.code_input: np.zeros((1, config['code_size']))}
358 | self.embedding_lstm_train[i] = sess.run(model_vae.code_mean, feed_dict=feed_dict)
359 | print("Finish processing the embeddings of the entire dataset.")
360 | print("The first a few embeddings are\n{}".format(self.embedding_lstm_train[0, 0:5]))
361 | self.x_train = self.embedding_lstm_train[:, :config['l_seq'] - 1]
362 | self.y_train = self.embedding_lstm_train[:, 1:]
363 |
364 | self.embedding_lstm_test = np.zeros((data.n_val_lstm, config['l_seq'], config['code_size']))
365 | for i in range(data.n_val_lstm):
366 | feed_dict = {model_vae.original_signal: data.val_set_lstm['data'][i],
367 | model_vae.is_code_input: False,
368 | model_vae.code_input: np.zeros((1, config['code_size']))}
369 | self.embedding_lstm_test[i] = sess.run(model_vae.code_mean, feed_dict=feed_dict)
370 | self.x_test = self.embedding_lstm_test[:, :config['l_seq'] - 1]
371 | self.y_test = self.embedding_lstm_test[:, 1:]
372 |
373 | def load_model(self, lstm_model, config, checkpoint_path):
374 | print(config['checkpoint_dir_lstm'] + 'checkpoint')
375 | if os.path.isfile(config['checkpoint_dir_lstm'] + 'checkpoint'):
376 | lstm_model.load_weights(checkpoint_path)
377 | print("LSTM model loaded.")
378 | else:
379 | print("No LSTM model loaded.")
380 |
381 | def train(self, config, lstm_model, cp_callback):
382 | lstm_model.fit(self.x_train, self.y_train,
383 | validation_data=(self.x_test, self.y_test),
384 | batch_size=config['batch_size_lstm'],
385 | epochs=config['num_epochs_lstm'],
386 | callbacks=[cp_callback])
387 |
388 | def plot_reconstructed_lt_seq(self, idx_test, config, model_vae, sess, data, lstm_embedding_test):
389 | feed_dict_vae = {model_vae.original_signal: np.zeros((config['l_seq'], config['l_win'], config['n_channel'])),
390 | model_vae.is_code_input: True,
391 | model_vae.code_input: self.embedding_lstm_test[idx_test]}
392 | decoded_seq_vae = np.squeeze(sess.run(model_vae.decoded, feed_dict=feed_dict_vae))
393 | print("Decoded seq from VAE: {}".format(decoded_seq_vae.shape))
394 |
395 | feed_dict_lstm = {model_vae.original_signal: np.zeros((config['l_seq'] - 1, config['l_win'], config['n_channel'])),
396 | model_vae.is_code_input: True,
397 | model_vae.code_input: lstm_embedding_test[idx_test]}
398 | decoded_seq_lstm = np.squeeze(sess.run(model_vae.decoded, feed_dict=feed_dict_lstm))
399 | print("Decoded seq from lstm: {}".format(decoded_seq_lstm.shape))
400 |
401 | fig, axs = plt.subplots(config['n_channel'], 2, figsize=(15, 4.5 * config['n_channel']), edgecolor='k')
402 | fig.subplots_adjust(hspace=.4, wspace=.4)
403 | axs = axs.ravel()
404 | for j in range(config['n_channel']):
405 | for i in range(2):
406 | axs[i + j * 2].plot(np.arange(0, config['l_seq'] * config['l_win']),
407 | np.reshape(data.val_set_lstm['data'][idx_test, :, :, j],
408 | (config['l_seq'] * config['l_win'])))
409 | axs[i + j * 2].grid(True)
410 | axs[i + j * 2].set_xlim(0, config['l_seq'] * config['l_win'])
411 | axs[i + j * 2].set_xlabel('samples')
412 | if config['n_channel'] == 1:
413 | axs[0 + j * 2].plot(np.arange(0, config['l_seq'] * config['l_win']),
414 | np.reshape(decoded_seq_vae, (config['l_seq'] * config['l_win'])), 'r--')
415 | axs[1 + j * 2].plot(np.arange(config['l_win'], config['l_seq'] * config['l_win']),
416 | np.reshape(decoded_seq_lstm, ((config['l_seq'] - 1) * config['l_win'])), 'g--')
417 | else:
418 | axs[0 + j * 2].plot(np.arange(0, config['l_seq'] * config['l_win']),
419 | np.reshape(decoded_seq_vae[:, :, j], (config['l_seq'] * config['l_win'])), 'r--')
420 | axs[1 + j * 2].plot(np.arange(config['l_win'], config['l_seq'] * config['l_win']),
421 | np.reshape(decoded_seq_lstm[:, :, j], ((config['l_seq'] - 1) * config['l_win'])), 'g--')
422 | axs[0 + j * 2].set_title('VAE reconstruction - channel {}'.format(j))
423 | axs[1 + j * 2].set_title('LSTM reconstruction - channel {}'.format(j))
424 | for i in range(2):
425 | axs[i + j * 2].legend(('ground truth', 'reconstruction'))
426 | savefig(config['result_dir'] + "lstm_long_seq_recons_{}.pdf".format(idx_test))
427 | fig.clf()
428 | plt.close()
429 |
430 | def plot_lstm_embedding_prediction(self, idx_test, config, model_vae, sess, data, lstm_embedding_test):
431 | self.plot_reconstructed_lt_seq(idx_test, config, model_vae, sess, data, lstm_embedding_test)
432 |
433 | fig, axs = plt.subplots(2, config['code_size'] // 2, figsize=(15, 5.5), edgecolor='k')
434 | fig.subplots_adjust(hspace=.4, wspace=.4)
435 | axs = axs.ravel()
436 | for i in range(config['code_size']):
437 | axs[i].plot(np.arange(1, config['l_seq']), np.squeeze(self.embedding_lstm_test[idx_test, 1:, i]))
438 | axs[i].plot(np.arange(1, config['l_seq']), np.squeeze(lstm_embedding_test[idx_test, :, i]))
439 | axs[i].set_xlim(1, config['l_seq'] - 1)
440 | axs[i].set_ylim(-2.5, 2.5)
441 | axs[i].grid(True)
442 | axs[i].set_title('Embedding dim {}'.format(i))
443 | axs[i].set_xlabel('windows')
444 | if i == config['code_size'] - 1:
445 | axs[i].legend(('VAE\nembedding', 'LSTM\nembedding'))
446 | savefig(config['result_dir'] + "lstm_seq_embedding_{}.pdf".format(idx_test))
447 | fig.clf()
448 | plt.close()
449 |
--------------------------------------------------------------------------------
/datasets/NAB-dataset-preprocessing.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import os\n",
10 | "import csv\n",
11 | "import numpy as np\n",
12 | "import matplotlib.pylab as plt\n",
13 | "from matplotlib.pyplot import plot, ion, show, savefig, cla, figure"
14 | ]
15 | },
16 | {
17 | "cell_type": "markdown",
18 | "metadata": {},
19 | "source": [
20 | "## Helper functions to load and process original csv files"
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": 2,
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# this function load one .cvs (a sequence)\n",
30 | "def load_data(dataset, csv_folder='./NAB-known-anomaly/csv-files/'):\n",
31 | " if dataset == 'ambient_temp':\n",
32 | " data_file = os.path.join(csv_folder, 'ambient_temperature_system_failure.csv')\n",
33 | " anomalies = ['2013-12-22 20:00:00', '2014-04-13 09:00:00']\n",
34 | " t_unit = 'hour'\n",
35 | " elif dataset == 'cpu_utilization':\n",
36 | " data_file = os.path.join(csv_folder, 'cpu_utilization_asg_misconfiguration.csv')\n",
37 | " anomalies = ['2014-07-12 02:04:00', '2014-07-14 21:44:00']\n",
38 | " t_unit = '5 min'\n",
39 | " elif dataset == 'ec2_request':\n",
40 | " data_file = os.path.join(csv_folder, 'ec2_request_latency_system_failure.csv')\n",
41 | " anomalies = ['2014-03-14 09:06:00', '2014-03-18 22:41:00', '2014-03-21 03:01:00']\n",
42 | " t_unit = '5 min'\n",
43 | " elif dataset == 'machine_temp':\n",
44 | " data_file = os.path.join(csv_folder, 'machine_temperature_system_failure.csv')\n",
45 | " anomalies = ['2013-12-11 06:00:00', '2013-12-16 17:25:00', '2014-01-28 13:55:00', '2014-02-08 14:30:00']\n",
46 | " t_unit = '5 min'\n",
47 | " elif dataset == 'rogue_agent_key_hold':\n",
48 | " data_file = os.path.join(csv_folder, 'rogue_agent_key_hold.csv')\n",
49 | " anomalies = ['2014-07-15 08:30:00', '2014-07-17 09:50:00']\n",
50 | " t_unit = '5 min'\n",
51 | " elif dataset == 'rogue_agent_key_updown':\n",
52 | " data_file = os.path.join(csv_folder, 'rogue_agent_key_updown.csv')\n",
53 | " anomalies = ['2014-07-15 04:00:00', '2014-07-17 08:50:00']\n",
54 | " t_unit = '5 min'\n",
55 | " elif dataset == 'nyc_taxi':\n",
56 | " data_file = os.path.join(csv_folder, 'nyc_taxi.csv')\n",
57 | " anomalies = ['2014-11-01 19:00:00', '2014-11-27 15:30:00', '2014-12-25 15:00:00', '2015-01-01 01:00:00', \n",
58 | " '2015-01-27 00:00:00']\n",
59 | " t_unit = '30 min'\n",
60 | " \n",
61 | " t = []\n",
62 | " readings = []\n",
63 | " idx_anomaly = []\n",
64 | " i = 0\n",
65 | " with open(data_file) as csvfile:\n",
66 | " readCSV = csv.reader(csvfile, delimiter=',')\n",
67 | " print(\"\\n--> Anomalies occur at:\")\n",
68 | " for row in readCSV:\n",
69 | " if i > 0:\n",
70 | " t.append(i)\n",
71 | " readings.append(float(row[1]))\n",
72 | " for j in range(len(anomalies)):\n",
73 | " if row[0] == anomalies[j]:\n",
74 | " idx_anomaly.append(i)\n",
75 | " print(\" timestamp #{}: {}\".format(j, row[0]))\n",
76 | " i = i + 1\n",
77 | " t = np.asarray(t)\n",
78 | " readings = np.asarray(readings)\n",
79 | " print(\"\\nOriginal csv file contains {} timestamps.\".format(t.shape))\n",
80 | " print(\"Processed time series contain {} readings.\".format(readings.shape))\n",
81 | " print(\"Anomaly indices are {}\".format(idx_anomaly))\n",
82 | " \n",
83 | " return t, t_unit, readings, idx_anomaly"
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": 3,
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "# This function plots a dataset with the train/test split and known anomalies\n",
93 | "# Relies on helper function load_data()\n",
94 | "\n",
95 | "def process_and_save_specified_dataset(dataset, idx_split, y_scale=5, save_file=False):\n",
96 | " t, t_unit, readings, idx_anomaly = load_data(dataset)\n",
97 | " \n",
98 | " # split into training and test sets\n",
99 | " training = readings[idx_split[0]:idx_split[1]]\n",
100 | " t_train = t[idx_split[0]:idx_split[1]]\n",
101 | " \n",
102 | " # normalise by training mean and std \n",
103 | " train_m = np.mean(training)\n",
104 | " train_std = np.std(training)\n",
105 | " print(\"\\nTraining set mean is {}\".format(train_m))\n",
106 | " print(\"Training set std is {}\".format(train_std))\n",
107 | " readings_normalised = (readings - train_m) / train_std\n",
108 | " \n",
109 | " training = readings_normalised[idx_split[0]:idx_split[1]]\n",
110 | " if idx_split[0] == 0:\n",
111 | " test = readings_normalised[idx_split[1]:]\n",
112 | " t_test = t[idx_split[1]:] - idx_split[1]\n",
113 | " idx_anomaly_test = np.asarray(idx_anomaly) - idx_split[1]\n",
114 | " else:\n",
115 | " test = [readings_normalised[:idx_split[0]], readings_normalised[idx_split[1]:]]\n",
116 | " t_test = [t[:idx_split[0]], t[idx_split[1]:] - idx_split[1]]\n",
117 | " idx_anomaly_split = np.squeeze(np.argwhere(np.asarray(idx_anomaly)>idx_split[0]))\n",
118 | " idx_anomaly_test = [np.asarray(idx_anomaly[:idx_anomaly_split[0]]), \n",
119 | " np.asarray(idx_anomaly[idx_anomaly_split[0]:]) - idx_split[1]]\n",
120 | " print(\"Anomaly indices in the test set are {}\".format(idx_anomaly_test))\n",
121 | " \n",
122 | " if save_file:\n",
123 | " save_dir = './datasets/NAB-known-anomaly/'\n",
124 | " np.savez(save_dir+dataset+'.npz', t=t, t_unit=t_unit, readings=readings, idx_anomaly=idx_anomaly,\n",
125 | " idx_split=idx_split, training=training, test=test, train_m=train_m, train_std=train_std,\n",
126 | " t_train=t_train, t_test=t_test, idx_anomaly_test=idx_anomaly_test)\n",
127 | " print(\"\\nProcessed time series are saved at {}\".format(save_dir+dataset+'.npz'))\n",
128 | " else:\n",
129 | " print(\"\\nProcessed time series are not saved.\")\n",
130 | " \n",
131 | " # plot the whole normalised sequence\n",
132 | " fig, axs = plt.subplots(1, 1, figsize=(18, 4), edgecolor='k')\n",
133 | " fig.subplots_adjust(hspace=.4, wspace=.4)\n",
134 | " # axs = axs.ravel()\n",
135 | " # for i in range(4):\n",
136 | " axs.plot(t, readings_normalised)\n",
137 | " if idx_split[0] == 0:\n",
138 | " axs.plot(idx_split[1]*np.ones(20), np.linspace(-y_scale,y_scale,20), 'b--')\n",
139 | " else:\n",
140 | " for i in range(2):\n",
141 | " axs.plot(idx_split[i]*np.ones(20), np.linspace(-y_scale,y_scale,20), 'b--')\n",
142 | " for j in range(len(idx_anomaly)):\n",
143 | " axs.plot(idx_anomaly[j]*np.ones(20), np.linspace(-y_scale,y_scale,20), 'r--')\n",
144 | " # axs.plot(data[:,1])\n",
145 | " axs.grid(True)\n",
146 | " axs.set_xlim(0, len(t))\n",
147 | " axs.set_ylim(-y_scale, y_scale)\n",
148 | " axs.set_xlabel(\"timestamp (every {})\".format(t_unit))\n",
149 | " axs.set_ylabel(\"normalised readings\")\n",
150 | " axs.set_title(\"{} dataset\\n(normalised by train mean {:.2f} and std {:.2f})\".format(dataset, train_m, train_std))\n",
151 | " axs.legend(('data', 'train test set split', 'anomalies'))\n",
152 | " \n",
153 | " return t, readings_normalised"
154 | ]
155 | },
156 | {
157 | "cell_type": "markdown",
158 | "metadata": {},
159 | "source": [
160 | "## Example on ambient temperature series"
161 | ]
162 | },
163 | {
164 | "cell_type": "code",
165 | "execution_count": 4,
166 | "metadata": {},
167 | "outputs": [
168 | {
169 | "name": "stdout",
170 | "output_type": "stream",
171 | "text": [
172 | "\n",
173 | "--> Anomalies occur at:\n",
174 | " timestamp #0: 2013-12-22 20:00:00\n",
175 | " timestamp #1: 2014-04-13 09:00:00\n",
176 | "\n",
177 | "Original csv file contains (7267,) timestamps.\n",
178 | "Processed time series contain (7267,) readings.\n",
179 | "Anomaly indices are [3722, 6181]\n",
180 | "\n",
181 | "Training set mean is 72.04114695024849\n",
182 | "Training set std is 3.323831562717656\n",
183 | "Anomaly indices in the test set are [ 422 2881]\n",
184 | "\n",
185 | "Processed time series are not saved.\n"
186 | ]
187 | },
188 | {
189 | "data": {
190 | "image/png": "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\n",
191 | "text/plain": [
192 | ""
193 | ]
194 | },
195 | "metadata": {
196 | "needs_background": "light"
197 | },
198 | "output_type": "display_data"
199 | }
200 | ],
201 | "source": [
202 | "dataset = 'ambient_temp'\n",
203 | "idx_split = [0,3300]\n",
204 | "\n",
205 | "t, readings_normalised = process_and_save_specified_dataset(dataset, idx_split)"
206 | ]
207 | }
208 | ],
209 | "metadata": {
210 | "kernelspec": {
211 | "display_name": "anomaly-env",
212 | "language": "python",
213 | "name": "anomaly-env"
214 | },
215 | "language_info": {
216 | "codemirror_mode": {
217 | "name": "ipython",
218 | "version": 3
219 | },
220 | "file_extension": ".py",
221 | "mimetype": "text/x-python",
222 | "name": "python",
223 | "nbconvert_exporter": "python",
224 | "pygments_lexer": "ipython3",
225 | "version": "3.6.9"
226 | }
227 | },
228 | "nbformat": 4,
229 | "nbformat_minor": 2
230 | }
231 |
--------------------------------------------------------------------------------