├── .gitignore ├── LICENSE ├── README.md ├── demo ├── __init__.py ├── credit_card_demo │ ├── __init__.py │ ├── conv1d_autoencoder.py │ ├── data │ │ ├── .gitignore │ │ └── creditcardfraud.zip │ ├── feed_forward_autoencoder.py │ ├── models │ │ ├── con1d-auto-encoder-architecture.json │ │ ├── con1d-auto-encoder-config.npy │ │ ├── con1d-auto-encoder-history.npy │ │ ├── con1d-auto-encoder-weights.h5 │ │ ├── feedforward-encoder-architecture.json │ │ ├── feedforward-encoder-config.npy │ │ ├── feedforward-encoder-history.npy │ │ └── feedforward-encoder-weights.h5 │ └── unzip_utils.py └── ecg_demo │ ├── __init__.py │ ├── bidirectional_lstm_autoencoder.py │ ├── cnn_lstm_autoencoder.py │ ├── conv1d_autoencoder.py │ ├── data │ └── ecg_discord_test.csv │ ├── feed_forward_autoencoder.py │ ├── h2o_ecg_pulse_detection.py │ ├── lstm_autoencoder.py │ └── models │ ├── bidirectional-lstm-auto-encoder-architecture.json │ ├── bidirectional-lstm-auto-encoder-config.npy │ ├── bidirectional-lstm-auto-encoder-weights.h5 │ ├── cnn-lstm-auto-encoder-architecture.json │ ├── cnn-lstm-auto-encoder-config.npy │ ├── cnn-lstm-auto-encoder-weights.h5 │ ├── con1d-auto-encoder-architecture.json │ ├── con1d-auto-encoder-config.npy │ ├── con1d-auto-encoder-weights.h5 │ ├── feedforward-encoder-architecture.json │ ├── feedforward-encoder-config.npy │ ├── feedforward-encoder-weights.h5 │ ├── lstm-auto-encoder-architecture.json │ ├── lstm-auto-encoder-config.npy │ └── lstm-auto-encoder-weights.h5 ├── keras_anomaly_detection ├── __init__.py └── library │ ├── __init__.py │ ├── convolutional.py │ ├── evaluation_utils.py │ ├── feedforward.py │ ├── plot_utils.py │ └── recurrent.py ├── notebooks └── .gitignore ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | .idea/ 10 | *.iml 11 | 12 | 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # dotenv 88 | .env 89 | 90 | # virtualenv 91 | .venv 92 | venv/ 93 | ENV/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Xianshun Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # keras-anomaly-detection 2 | 3 | Anomaly detection implemented in Keras 4 | 5 | The source codes of the recurrent, convolutional and feedforward networks auto-encoders for anomaly detection can be found in 6 | [keras_anomaly_detection/library/convolutional.py](keras_anomaly_detection/library/convolutional.py) and 7 | [keras_anomaly_detection/library/recurrent.py](keras_anomaly_detection/library/recurrent.py) and 8 | [keras_anomaly_detection/library/feedforward.py](keras_anomaly_detection/library/feedforward.py) 9 | 10 | The the anomaly detection is implemented using auto-encoder with convolutional, feedforward, and recurrent networks and can be applied 11 | to: 12 | 13 | * timeseries data to detect timeseries time windows that have anomaly pattern 14 | * LstmAutoEncoder in [keras_anomaly_detection/library/recurrent.py](keras_anomaly_detection/library/recurrent.py) 15 | * Conv1DAutoEncoder in [keras_anomaly_detection/library/convolutional.py](keras_anomaly_detection/library/convolutional.py) 16 | * CnnLstmAutoEncoder in [keras_anomaly_detection/library/recurrent.py](keras_anomaly_detection/library/recurrent.py) 17 | * BidirectionalLstmAutoEncoder in [keras_anomaly_detection/library/recurrent.py](keras_anomaly_detection/library/recurrent.py) 18 | * structured data (i.e., tabular data) to detect anomaly in data records 19 | * Conv1DAutoEncoder in [keras_anomaly_detection/library/convolutional.py](keras_anomaly_detection/library/convolutional.py) 20 | * FeedforwardAutoEncoder in [keras_anomaly_detection/library/feedforward.py](keras_anomaly_detection/library/feedforward.py) 21 | 22 | # Usage 23 | 24 | ### Detect Anomaly within the ECG Data 25 | 26 | The sample codes can be found in the [demo/ecg_demo](demo/ecg_demo). 27 | 28 | The following sample codes show how to fit and detect anomaly using Conv1DAutoEncoder: 29 | 30 | ```python 31 | import pandas as pd 32 | from sklearn.preprocessing import MinMaxScaler 33 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 34 | from keras_anomaly_detection.library.convolutional import Conv1DAutoEncoder 35 | 36 | 37 | def main(): 38 | data_dir_path = './data' 39 | model_dir_path = './models' 40 | 41 | # ecg data in which each row is a temporal sequence data of continuous values 42 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 43 | print(ecg_data.head()) 44 | ecg_np_data = ecg_data.as_matrix() 45 | scaler = MinMaxScaler() 46 | ecg_np_data = scaler.fit_transform(ecg_np_data) 47 | 48 | print(ecg_np_data.shape) 49 | 50 | ae = Conv1DAutoEncoder() 51 | 52 | # fit the data and save model into model_dir_path 53 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 54 | 55 | # load back the model saved in model_dir_path detect anomaly 56 | ae.load_model(model_dir_path) 57 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 58 | reconstruction_error = [] 59 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 60 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 61 | reconstruction_error.append(dist) 62 | 63 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 64 | 65 | 66 | if __name__ == '__main__': 67 | main() 68 | ``` 69 | 70 | The following sample codes show how to fit and detect anomaly using LstmAutoEncoder: 71 | 72 | ```python 73 | import pandas as pd 74 | from sklearn.preprocessing import MinMaxScaler 75 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 76 | from keras_anomaly_detection.library.recurrent import LstmAutoEncoder 77 | 78 | 79 | def main(): 80 | data_dir_path = './data' 81 | model_dir_path = './models' 82 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 83 | print(ecg_data.head()) 84 | ecg_np_data = ecg_data.as_matrix() 85 | scaler = MinMaxScaler() 86 | ecg_np_data = scaler.fit_transform(ecg_np_data) 87 | print(ecg_np_data.shape) 88 | 89 | ae = LstmAutoEncoder() 90 | 91 | # fit the data and save model into model_dir_path 92 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 93 | 94 | # load back the model saved in model_dir_path detect anomaly 95 | ae.load_model(model_dir_path) 96 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 97 | reconstruction_error = [] 98 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 99 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 100 | reconstruction_error.append(dist) 101 | 102 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 103 | 104 | 105 | if __name__ == '__main__': 106 | main() 107 | ``` 108 | 109 | The following sample codes show how to fit and detect anomaly using CnnLstmAutoEncoder: 110 | 111 | ```python 112 | import pandas as pd 113 | from sklearn.preprocessing import MinMaxScaler 114 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 115 | from keras_anomaly_detection.library.recurrent import CnnLstmAutoEncoder 116 | 117 | 118 | def main(): 119 | data_dir_path = './data' 120 | model_dir_path = './models' 121 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 122 | print(ecg_data.head()) 123 | ecg_np_data = ecg_data.as_matrix() 124 | scaler = MinMaxScaler() 125 | ecg_np_data = scaler.fit_transform(ecg_np_data) 126 | print(ecg_np_data.shape) 127 | 128 | ae = CnnLstmAutoEncoder() 129 | 130 | # fit the data and save model into model_dir_path 131 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 132 | 133 | # load back the model saved in model_dir_path detect anomaly 134 | ae.load_model(model_dir_path) 135 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 136 | reconstruction_error = [] 137 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 138 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 139 | reconstruction_error.append(dist) 140 | 141 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 142 | 143 | 144 | if __name__ == '__main__': 145 | main() 146 | ``` 147 | 148 | The following sample codes show how to fit and detect anomaly using BidirectionalLstmAutoEncoder: 149 | 150 | ```python 151 | import pandas as pd 152 | from sklearn.preprocessing import MinMaxScaler 153 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 154 | from keras_anomaly_detection.library.recurrent import BidirectionalLstmAutoEncoder 155 | 156 | 157 | def main(): 158 | data_dir_path = './data' 159 | model_dir_path = './models' 160 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 161 | print(ecg_data.head()) 162 | ecg_np_data = ecg_data.as_matrix() 163 | scaler = MinMaxScaler() 164 | ecg_np_data = scaler.fit_transform(ecg_np_data) 165 | print(ecg_np_data.shape) 166 | 167 | ae = BidirectionalLstmAutoEncoder() 168 | 169 | # fit the data and save model into model_dir_path 170 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 171 | 172 | # load back the model saved in model_dir_path detect anomaly 173 | ae.load_model(model_dir_path) 174 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 175 | reconstruction_error = [] 176 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 177 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 178 | reconstruction_error.append(dist) 179 | 180 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 181 | 182 | 183 | if __name__ == '__main__': 184 | main() 185 | ``` 186 | 187 | The following sample codes show how to fit and detect anomaly using FeedForwardAutoEncoder: 188 | 189 | ```python 190 | import pandas as pd 191 | from sklearn.preprocessing import MinMaxScaler 192 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 193 | from keras_anomaly_detection.library.feedforward import FeedForwardAutoEncoder 194 | 195 | 196 | def main(): 197 | data_dir_path = './data' 198 | model_dir_path = './models' 199 | 200 | # ecg data in which each row is a temporal sequence data of continuous values 201 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 202 | print(ecg_data.head()) 203 | ecg_np_data = ecg_data.as_matrix() 204 | scaler = MinMaxScaler() 205 | ecg_np_data = scaler.fit_transform(ecg_np_data) 206 | 207 | print(ecg_np_data.shape) 208 | 209 | ae = FeedForwardAutoEncoder() 210 | 211 | # fit the data and save model into model_dir_path 212 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 213 | 214 | # load back the model saved in model_dir_path detect anomaly 215 | ae.load_model(model_dir_path) 216 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 217 | reconstruction_error = [] 218 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 219 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 220 | reconstruction_error.append(dist) 221 | 222 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 223 | 224 | 225 | if __name__ == '__main__': 226 | main() 227 | ``` 228 | 229 | # Detect Fraud in Credit Card Transaction 230 | 231 | The sample codes can be found in the [demo/credit_card_demo](demo/credit_card_demo). 232 | 233 | The credit card sample data is from [this repo](https://github.com/curiousily/Credit-Card-Fraud-Detection-using-Autoencoders-in-Keras/blob/master/fraud_detection.ipynb) 234 | 235 | Below is the sample code using FeedforwardAutoEncoder: 236 | 237 | ```python 238 | import pandas as pd 239 | from sklearn.model_selection import train_test_split 240 | from sklearn.preprocessing import StandardScaler 241 | 242 | from keras_anomaly_detection.library.feedforward import FeedForwardAutoEncoder 243 | from keras_anomaly_detection.demo.credit_card_demo.unzip_utils import unzip 244 | from keras_anomaly_detection.library.plot_utils import plot_confusion_matrix, plot_training_history, visualize_anomaly 245 | from keras_anomaly_detection.library.evaluation_utils import report_evaluation_metrics 246 | import numpy as np 247 | 248 | DO_TRAINING = False 249 | 250 | 251 | def preprocess_data(csv_data): 252 | credit_card_data = csv_data.drop(labels=['Class', 'Time'], axis=1) 253 | credit_card_data['Amount'] = StandardScaler().fit_transform(credit_card_data['Amount'].values.reshape(-1, 1)) 254 | # print(credit_card_data.head()) 255 | credit_card_np_data = credit_card_data.as_matrix() 256 | y_true = csv_data['Class'].as_matrix() 257 | return credit_card_np_data, y_true 258 | 259 | 260 | def main(): 261 | seed = 42 262 | np.random.seed(seed) 263 | 264 | data_dir_path = './data' 265 | model_dir_path = './models' 266 | 267 | unzip(data_dir_path + '/creditcardfraud.zip', data_dir_path) 268 | csv_data = pd.read_csv(data_dir_path + '/creditcard.csv') 269 | estimated_negative_sample_ratio = 1 - csv_data['Class'].sum() / csv_data['Class'].count() 270 | print(estimated_negative_sample_ratio) 271 | X, Y = preprocess_data(csv_data) 272 | print(X.shape) 273 | 274 | ae = FeedForwardAutoEncoder() 275 | 276 | training_history_file_path = model_dir_path + '/' + FeedForwardAutoEncoder.model_name + '-history.npy' 277 | # fit the data and save model into model_dir_path 278 | epochs = 100 279 | history = None 280 | if DO_TRAINING: 281 | history = ae.fit(X, model_dir_path=model_dir_path, 282 | estimated_negative_sample_ratio=estimated_negative_sample_ratio, 283 | nb_epoch=epochs, 284 | random_state=seed) 285 | np.save(training_history_file_path, history) 286 | else: 287 | history = np.load(training_history_file_path).item() 288 | 289 | # load back the model saved in model_dir_path 290 | ae.load_model(model_dir_path) 291 | # detect anomaly for the test data 292 | Ypred = [] 293 | _, Xtest, _, Ytest = train_test_split(X, Y, test_size=0.2, random_state=seed) 294 | reconstruction_error = [] 295 | adjusted_threshold = 14 296 | anomaly_information = ae.anomaly(Xtest, adjusted_threshold) 297 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 298 | predicted_label = 1 if is_anomaly else 0 299 | Ypred.append(predicted_label) 300 | reconstruction_error.append(dist) 301 | 302 | report_evaluation_metrics(Ytest, Ypred) 303 | plot_training_history(history) 304 | visualize_anomaly(Ytest, reconstruction_error, adjusted_threshold) 305 | plot_confusion_matrix(Ytest, Ypred) 306 | 307 | 308 | if __name__ == '__main__': 309 | main() 310 | ``` 311 | 312 | The sample code below uses Conv1DAutoEncoder: 313 | 314 | ```python 315 | import pandas as pd 316 | from sklearn.model_selection import train_test_split 317 | from sklearn.preprocessing import StandardScaler 318 | 319 | from keras_anomaly_detection.library.convolutional import Conv1DAutoEncoder 320 | from keras_anomaly_detection.demo.credit_card_demo.unzip_utils import unzip 321 | from keras_anomaly_detection.library.plot_utils import plot_confusion_matrix, plot_training_history, visualize_anomaly 322 | from keras_anomaly_detection.library.evaluation_utils import report_evaluation_metrics 323 | import numpy as np 324 | import os 325 | 326 | DO_TRAINING = False 327 | 328 | 329 | def preprocess_data(csv_data): 330 | credit_card_data = csv_data.drop(labels=['Class', 'Time'], axis=1) 331 | credit_card_data['Amount'] = StandardScaler().fit_transform(credit_card_data['Amount'].values.reshape(-1, 1)) 332 | # print(credit_card_data.head()) 333 | credit_card_np_data = credit_card_data.as_matrix() 334 | y_true = csv_data['Class'].as_matrix() 335 | return credit_card_np_data, y_true 336 | 337 | 338 | def main(): 339 | seed = 42 340 | np.random.seed(seed) 341 | 342 | data_dir_path = './data' 343 | model_dir_path = './models' 344 | 345 | unzip(data_dir_path + '/creditcardfraud.zip', data_dir_path) 346 | csv_data = pd.read_csv(data_dir_path + '/creditcard.csv') 347 | estimated_negative_sample_ratio = 1 - csv_data['Class'].sum() / csv_data['Class'].count() 348 | print(estimated_negative_sample_ratio) 349 | X, Y = preprocess_data(csv_data) 350 | print(X.shape) 351 | 352 | ae = Conv1DAutoEncoder() 353 | 354 | training_history_file_path = model_dir_path + '/' + Conv1DAutoEncoder.model_name + '-history.npy' 355 | # fit the data and save model into model_dir_path 356 | epochs = 10 357 | history = None 358 | if DO_TRAINING: 359 | history = ae.fit(X, model_dir_path=model_dir_path, 360 | estimated_negative_sample_ratio=estimated_negative_sample_ratio, 361 | epochs=epochs) 362 | np.save(training_history_file_path, history) 363 | elif os.path.exists(training_history_file_path): 364 | history = np.load(training_history_file_path).item() 365 | 366 | # load back the model saved in model_dir_path 367 | ae.load_model(model_dir_path) 368 | # detect anomaly for the test data 369 | Ypred = [] 370 | _, Xtest, _, Ytest = train_test_split(X, Y, test_size=0.2, random_state=seed) 371 | reconstruction_error = [] 372 | adjusted_threshold = 10 373 | anomaly_information = ae.anomaly(Xtest, adjusted_threshold) 374 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 375 | predicted_label = 1 if is_anomaly else 0 376 | Ypred.append(predicted_label) 377 | reconstruction_error.append(dist) 378 | 379 | report_evaluation_metrics(Ytest, Ypred) 380 | plot_training_history(history) 381 | visualize_anomaly(Ytest, reconstruction_error, adjusted_threshold) 382 | plot_confusion_matrix(Ytest, Ypred) 383 | 384 | 385 | if __name__ == '__main__': 386 | main() 387 | 388 | ``` 389 | 390 | 391 | # Note 392 | 393 | There is also an autoencoder from H2O for timeseries anomaly detection in 394 | [demo/h2o_ecg_pulse_detection.py](demo/ecg_demo/h2o_ecg_pulse_detection.py) 395 | 396 | ### Configure to run on GPU on Windows 397 | 398 | * Step 1: Change tensorflow to tensorflow-gpu in requirements.txt and install tensorflow-gpu 399 | * Step 2: Download and install the [CUDA® Toolkit 9.0](https://developer.nvidia.com/cuda-90-download-archive) (Please note that 400 | currently CUDA® Toolkit 9.1 is not yet supported by tensorflow, therefore you should download CUDA® Toolkit 9.0) 401 | * Step 3: Download and unzip the [cuDNN 7.0.4 for CUDA@ Toolkit 9.0](https://developer.nvidia.com/cudnn) and add the 402 | bin folder of the unzipped directory to the $PATH of your Windows environment 403 | 404 | 405 | 406 | 407 | -------------------------------------------------------------------------------- /demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/__init__.py -------------------------------------------------------------------------------- /demo/credit_card_demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/__init__.py -------------------------------------------------------------------------------- /demo/credit_card_demo/conv1d_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.model_selection import train_test_split 3 | from sklearn.preprocessing import StandardScaler 4 | 5 | from keras_anomaly_detection.library.convolutional import Conv1DAutoEncoder 6 | from keras_anomaly_detection.demo.credit_card_demo.unzip_utils import unzip 7 | from keras_anomaly_detection.library.plot_utils import plot_confusion_matrix, plot_training_history, visualize_anomaly 8 | from keras_anomaly_detection.library.evaluation_utils import report_evaluation_metrics 9 | import numpy as np 10 | import os 11 | 12 | DO_TRAINING = False 13 | 14 | 15 | def preprocess_data(csv_data): 16 | credit_card_data = csv_data.drop(labels=['Class', 'Time'], axis=1) 17 | credit_card_data['Amount'] = StandardScaler().fit_transform(credit_card_data['Amount'].values.reshape(-1, 1)) 18 | # print(credit_card_data.head()) 19 | credit_card_np_data = credit_card_data.as_matrix() 20 | y_true = csv_data['Class'].as_matrix() 21 | return credit_card_np_data, y_true 22 | 23 | 24 | def main(): 25 | seed = 42 26 | np.random.seed(seed) 27 | 28 | data_dir_path = './data' 29 | model_dir_path = './models' 30 | 31 | unzip(data_dir_path + '/creditcardfraud.zip', data_dir_path) 32 | csv_data = pd.read_csv(data_dir_path + '/creditcard.csv') 33 | estimated_negative_sample_ratio = 1 - csv_data['Class'].sum() / csv_data['Class'].count() 34 | print(estimated_negative_sample_ratio) 35 | X, Y = preprocess_data(csv_data) 36 | print(X.shape) 37 | 38 | ae = Conv1DAutoEncoder() 39 | 40 | training_history_file_path = model_dir_path + '/' + Conv1DAutoEncoder.model_name + '-history.npy' 41 | # fit the data and save model into model_dir_path 42 | epochs = 10 43 | history = None 44 | if DO_TRAINING: 45 | history = ae.fit(X, model_dir_path=model_dir_path, 46 | estimated_negative_sample_ratio=estimated_negative_sample_ratio, 47 | epochs=epochs) 48 | np.save(training_history_file_path, history) 49 | elif os.path.exists(training_history_file_path): 50 | history = np.load(training_history_file_path).item() 51 | 52 | # load back the model saved in model_dir_path 53 | ae.load_model(model_dir_path) 54 | # detect anomaly for the test data 55 | Ypred = [] 56 | _, Xtest, _, Ytest = train_test_split(X, Y, test_size=0.2, random_state=seed) 57 | reconstruction_error = [] 58 | adjusted_threshold = 10 59 | anomaly_information = ae.anomaly(Xtest, adjusted_threshold) 60 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 61 | predicted_label = 1 if is_anomaly else 0 62 | Ypred.append(predicted_label) 63 | reconstruction_error.append(dist) 64 | 65 | report_evaluation_metrics(Ytest, Ypred) 66 | plot_training_history(history) 67 | visualize_anomaly(Ytest, reconstruction_error, adjusted_threshold) 68 | plot_confusion_matrix(Ytest, Ypred) 69 | 70 | 71 | if __name__ == '__main__': 72 | main() 73 | -------------------------------------------------------------------------------- /demo/credit_card_demo/data/.gitignore: -------------------------------------------------------------------------------- 1 | creditcard.csv -------------------------------------------------------------------------------- /demo/credit_card_demo/data/creditcardfraud.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/data/creditcardfraud.zip -------------------------------------------------------------------------------- /demo/credit_card_demo/feed_forward_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.model_selection import train_test_split 3 | from sklearn.preprocessing import StandardScaler 4 | 5 | from keras_anomaly_detection.library.feedforward import FeedForwardAutoEncoder 6 | from keras_anomaly_detection.demo.credit_card_demo.unzip_utils import unzip 7 | from keras_anomaly_detection.library.plot_utils import plot_confusion_matrix, plot_training_history, visualize_anomaly 8 | from keras_anomaly_detection.library.evaluation_utils import report_evaluation_metrics 9 | import numpy as np 10 | 11 | DO_TRAINING = False 12 | 13 | 14 | def preprocess_data(csv_data): 15 | credit_card_data = csv_data.drop(labels=['Class', 'Time'], axis=1) 16 | credit_card_data['Amount'] = StandardScaler().fit_transform(credit_card_data['Amount'].values.reshape(-1, 1)) 17 | # print(credit_card_data.head()) 18 | credit_card_np_data = credit_card_data.as_matrix() 19 | y_true = csv_data['Class'].as_matrix() 20 | return credit_card_np_data, y_true 21 | 22 | 23 | def main(): 24 | seed = 42 25 | np.random.seed(seed) 26 | 27 | data_dir_path = './data' 28 | model_dir_path = './models' 29 | 30 | unzip(data_dir_path + '/creditcardfraud.zip', data_dir_path) 31 | csv_data = pd.read_csv(data_dir_path + '/creditcard.csv') 32 | estimated_negative_sample_ratio = 1 - csv_data['Class'].sum() / csv_data['Class'].count() 33 | print(estimated_negative_sample_ratio) 34 | X, Y = preprocess_data(csv_data) 35 | print(X.shape) 36 | 37 | ae = FeedForwardAutoEncoder() 38 | 39 | training_history_file_path = model_dir_path + '/' + FeedForwardAutoEncoder.model_name + '-history.npy' 40 | # fit the data and save model into model_dir_path 41 | epochs = 100 42 | history = None 43 | if DO_TRAINING: 44 | history = ae.fit(X, model_dir_path=model_dir_path, 45 | estimated_negative_sample_ratio=estimated_negative_sample_ratio, 46 | nb_epoch=epochs, 47 | random_state=seed) 48 | np.save(training_history_file_path, history) 49 | else: 50 | history = np.load(training_history_file_path).item() 51 | 52 | # load back the model saved in model_dir_path 53 | ae.load_model(model_dir_path) 54 | # detect anomaly for the test data 55 | Ypred = [] 56 | _, Xtest, _, Ytest = train_test_split(X, Y, test_size=0.2, random_state=seed) 57 | reconstruction_error = [] 58 | adjusted_threshold = 14 59 | anomaly_information = ae.anomaly(Xtest, adjusted_threshold) 60 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 61 | predicted_label = 1 if is_anomaly else 0 62 | Ypred.append(predicted_label) 63 | reconstruction_error.append(dist) 64 | 65 | report_evaluation_metrics(Ytest, Ypred) 66 | plot_training_history(history) 67 | visualize_anomaly(Ytest, reconstruction_error, adjusted_threshold) 68 | plot_confusion_matrix(Ytest, Ypred) 69 | 70 | 71 | if __name__ == '__main__': 72 | main() 73 | -------------------------------------------------------------------------------- /demo/credit_card_demo/models/con1d-auto-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Conv1D", "config": {"name": "conv1d_1", "trainable": true, "batch_input_shape": [null, 29, 1], "dtype": "float32", "filters": 256, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "GlobalMaxPooling1D", "config": {"name": "global_max_pooling1d_1", "trainable": true}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 29, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/credit_card_demo/models/con1d-auto-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/con1d-auto-encoder-config.npy -------------------------------------------------------------------------------- /demo/credit_card_demo/models/con1d-auto-encoder-history.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/con1d-auto-encoder-history.npy -------------------------------------------------------------------------------- /demo/credit_card_demo/models/con1d-auto-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/con1d-auto-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/credit_card_demo/models/feedforward-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Model", "config": {"name": "model_1", "layers": [{"name": "input_1", "class_name": "InputLayer", "config": {"batch_input_shape": [null, 29], "dtype": "float32", "sparse": false, "name": "input_1"}, "inbound_nodes": []}, {"name": "dense_1", "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 14, "activation": "tanh", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": {"class_name": "L1L2", "config": {"l1": 9.999999747378752e-05, "l2": 0.0}}, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"name": "dense_2", "class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "units": 7, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_1", 0, 0, {}]]]}, {"name": "dense_3", "class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "units": 7, "activation": "tanh", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_2", 0, 0, {}]]]}, {"name": "dense_4", "class_name": "Dense", "config": {"name": "dense_4", "trainable": true, "units": 29, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_3", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense_4", 0, 0]]}, "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/credit_card_demo/models/feedforward-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/feedforward-encoder-config.npy -------------------------------------------------------------------------------- /demo/credit_card_demo/models/feedforward-encoder-history.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/feedforward-encoder-history.npy -------------------------------------------------------------------------------- /demo/credit_card_demo/models/feedforward-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/credit_card_demo/models/feedforward-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/credit_card_demo/unzip_utils.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | 3 | 4 | def unzip(path_to_zip_file, directory_to_extract_to): 5 | zip_ref = zipfile.ZipFile(path_to_zip_file, 'r') 6 | zip_ref.extractall(directory_to_extract_to) 7 | zip_ref.close() 8 | -------------------------------------------------------------------------------- /demo/ecg_demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/__init__.py -------------------------------------------------------------------------------- /demo/ecg_demo/bidirectional_lstm_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.preprocessing import MinMaxScaler 3 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 4 | from keras_anomaly_detection.library.recurrent import BidirectionalLstmAutoEncoder 5 | 6 | DO_TRAINING = False 7 | 8 | 9 | def main(): 10 | data_dir_path = './data' 11 | model_dir_path = './models' 12 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 13 | print(ecg_data.head()) 14 | ecg_np_data = ecg_data.as_matrix() 15 | scaler = MinMaxScaler() 16 | ecg_np_data = scaler.fit_transform(ecg_np_data) 17 | print(ecg_np_data.shape) 18 | 19 | ae = BidirectionalLstmAutoEncoder() 20 | 21 | # fit the data and save model into model_dir_path 22 | if DO_TRAINING: 23 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 24 | 25 | # load back the model saved in model_dir_path detect anomaly 26 | ae.load_model(model_dir_path) 27 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 28 | reconstruction_error = [] 29 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 30 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 31 | reconstruction_error.append(dist) 32 | 33 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /demo/ecg_demo/cnn_lstm_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.preprocessing import MinMaxScaler 3 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 4 | from keras_anomaly_detection.library.recurrent import CnnLstmAutoEncoder 5 | 6 | DO_TRAINING = False 7 | 8 | 9 | def main(): 10 | data_dir_path = './data' 11 | model_dir_path = './models' 12 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 13 | print(ecg_data.head()) 14 | ecg_np_data = ecg_data.as_matrix() 15 | scaler = MinMaxScaler() 16 | ecg_np_data = scaler.fit_transform(ecg_np_data) 17 | print(ecg_np_data.shape) 18 | 19 | ae = CnnLstmAutoEncoder() 20 | 21 | # fit the data and save model into model_dir_path 22 | if DO_TRAINING: 23 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 24 | 25 | # load back the model saved in model_dir_path detect anomaly 26 | ae.load_model(model_dir_path) 27 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 28 | reconstruction_error = [] 29 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 30 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 31 | reconstruction_error.append(dist) 32 | 33 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /demo/ecg_demo/conv1d_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.preprocessing import MinMaxScaler 3 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 4 | from keras_anomaly_detection.library.convolutional import Conv1DAutoEncoder 5 | 6 | DO_TRAINING = False 7 | 8 | 9 | def main(): 10 | data_dir_path = './data' 11 | model_dir_path = './models' 12 | 13 | # ecg data in which each row is a temporal sequence data of continuous values 14 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 15 | print(ecg_data.head()) 16 | ecg_np_data = ecg_data.as_matrix() 17 | scaler = MinMaxScaler() 18 | ecg_np_data = scaler.fit_transform(ecg_np_data) 19 | 20 | print(ecg_np_data.shape) 21 | 22 | ae = Conv1DAutoEncoder() 23 | 24 | # fit the data and save model into model_dir_path 25 | if DO_TRAINING: 26 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 27 | 28 | # load back the model saved in model_dir_path detect anomaly 29 | ae.load_model(model_dir_path) 30 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 31 | reconstruction_error = [] 32 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 33 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 34 | reconstruction_error.append(dist) 35 | 36 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /demo/ecg_demo/data/ecg_discord_test.csv: -------------------------------------------------------------------------------- 1 | 2.10,2.13,2.19,2.28,2.44,2.62,2.80,3.04,3.36,3.69,3.97,4.24,4.53,4.80,5.02,5.21,5.40,5.57,5.71,5.79,5.86,5.92,5.98,6.02,6.06,6.08,6.14,6.18,6.22,6.27,6.32,6.35,6.38,6.45,6.49,6.53,6.57,6.64,6.70,6.73,6.78,6.83,6.88,6.92,6.94,6.98,7.01,7.03,7.05,7.06,7.07,7.08,7.06,7.04,7.03,6.99,6.94,6.88,6.83,6.77,6.69,6.60,6.53,6.45,6.36,6.27,6.19,6.11,6.03,5.94,5.88,5.81,5.75,5.68,5.62,5.61,5.54,5.49,5.45,5.42,5.38,5.34,5.31,5.30,5.29,5.26,5.23,5.23,5.22,5.20,5.19,5.18,5.19,5.17,5.15,5.14,5.17,5.16,5.15,5.15,5.15,5.14,5.14,5.14,5.15,5.14,5.14,5.13,5.15,5.15,5.15,5.14,5.16,5.15,5.15,5.14,5.14,5.15,5.15,5.14,5.13,5.14,5.14,5.11,5.12,5.12,5.12,5.09,5.09,5.09,5.10,5.08,5.08,5.08,5.08,5.06,5.05,5.06,5.07,5.05,5.03,5.03,5.04,5.03,5.01,5.01,5.02,5.01,5.01,5.00,5.00,5.02,5.01,4.98,5.00,5.00,5.00,4.99,5.00,5.01,5.02,5.01,5.03,5.03,5.02,5.02,5.04,5.04,5.04,5.02,5.02,5.01,4.99,4.98,4.96,4.96,4.96,4.94,4.93,4.93,4.93,4.93,4.93,5.02,5.27,5.80,5.94,5.58,5.39,5.32,5.25,5.21,5.13,4.97,4.71,4.39,4.05,3.69,3.32,3.05,2.99,2.74,2.61,2.47,2.35,2.26,2.20,2.15,2.10,2.08 2 | 2.06,2.05,2.06,2.07,2.08,2.13,2.22,2.37,2.53,2.71,2.97,3.31,3.65,3.96,4.27,4.37,4.82,5.04,5.24,5.43,5.59,5.70,5.76,5.83,5.90,5.94,5.97,6.04,6.08,6.12,6.16,6.22,6.26,6.31,6.33,6.38,6.45,6.49,6.51,6.56,6.58,6.68,6.71,6.75,6.81,6.85,6.89,6.91,6.96,6.99,7.01,7.01,7.03,7.04,7.05,7.01,6.99,6.97,6.92,6.86,6.80,6.75,6.67,6.57,6.50,6.47,6.33,6.25,6.15,6.09,6.01,5.93,5.84,5.78,5.72,5.65,5.58,5.53,5.50,5.44,5.38,5.36,5.33,5.30,5.25,5.24,5.23,5.21,5.18,5.17,5.17,5.15,5.14,5.12,5.12,5.12,5.11,5.09,5.11,5.11,5.09,5.08,5.09,5.09,5.08,5.08,5.09,5.09,5.10,5.08,5.09,5.11,5.10,5.09,5.09,5.10,5.10,5.08,5.09,5.11,5.09,5.08,5.08,5.08,5.09,5.08,5.07,5.07,5.07,5.08,5.05,5.05,5.06,5.05,5.04,5.03,5.04,5.04,5.02,5.01,5.00,5.01,5.00,4.99,4.99,4.99,4.98,4.96,4.98,4.98,4.98,4.96,4.97,4.97,4.96,4.95,4.95,4.97,4.96,4.94,4.94,4.96,4.96,4.94,4.94,4.94,4.94,4.93,4.92,4.93,4.94,4.93,4.92,4.94,4.94,4.93,4.93,4.94,4.95,4.96,4.96,4.98,4.98,4.97,4.96,4.95,5.00,5.18,5.62,5.85,5.81,5.33,5.25,5.16,5.08,4.93,4.73,4.49,4.20,3.89,3.62,3.38,3.17,2.92,2.64,2.45,2.33,2.23,2.13,2.08 3 | 2.05,2.05,2.03,2.02,2.03,2.04,2.08,2.14,2.28,2.44,2.65,2.89,3.19,3.50,3.82,4.13,4.41,4.68,4.92,5.14,5.34,5.51,5.63,5.70,5.75,5.81,5.87,5.91,5.94,5.98,6.00,6.05,6.09,6.11,6.16,6.20,6.23,6.26,6.32,6.35,6.38,6.42,6.47,6.52,6.55,6.58,6.62,6.67,6.69,6.71,6.74,6.77,6.79,6.80,6.80,6.81,6.82,6.79,6.77,6.75,6.71,6.66,6.60,6.56,6.50,6.41,6.33,6.26,6.18,6.09,6.00,5.93,5.85,5.77,5.69,5.63,5.58,5.52,5.45,5.41,5.41,5.35,5.30,5.27,5.26,5.24,5.20,5.18,5.18,5.17,5.15,5.14,5.14,5.14,5.14,5.12,5.11,5.13,5.12,5.10,5.10,5.12,5.12,5.12,5.12,5.13,5.13,5.11,5.12,5.13,5.13,5.12,5.10,5.12,5.12,5.12,5.11,5.12,5.13,5.11,5.10,5.10,5.12,5.11,5.08,5.08,5.09,5.08,5.07,5.07,5.07,5.07,5.06,5.05,5.06,5.05,5.05,5.03,5.04,5.04,5.02,5.01,5.02,5.02,5.01,5.00,5.00,5.00,5.01,4.98,4.99,4.99,4.99,4.98,4.97,4.97,4.98,4.98,4.97,4.99,4.97,4.96,4.96,4.97,4.97,4.96,4.95,4.95,4.97,4.97,4.95,4.96,4.96,4.96,4.95,4.95,4.96,4.96,4.94,4.94,4.94,4.96,4.96,4.95,4.98,4.98,5.05,5.33,5.82,5.81,5.48,5.36,5.33,5.28,5.24,5.13,4.98,4.73,4.40,4.07,3.71,3.33,3.01,2.74,2.55,2.51,2.30,2.17,2.10,2.08 4 | 2.07,2.04,2.03,2.05,2.05,2.04,2.05,2.09,2.14,2.22,2.35,2.53,2.76,3.04,3.36,3.69,4.00,4.32,4.60,4.86,4.94,5.31,5.51,5.65,5.75,5.81,5.85,5.90,5.95,5.99,6.02,6.06,6.09,6.13,6.16,6.18,6.23,6.25,6.28,6.29,6.33,6.38,6.41,6.43,6.47,6.48,6.55,6.57,6.62,6.67,6.72,6.74,6.77,6.81,6.84,6.86,6.89,6.91,6.93,6.93,6.92,6.92,6.91,6.87,6.83,6.77,6.73,6.66,6.57,6.49,6.46,6.32,6.22,6.14,6.06,5.98,5.89,5.81,5.75,5.69,5.61,5.54,5.51,5.47,5.43,5.39,5.35,5.35,5.32,5.28,5.27,5.27,5.25,5.24,5.24,5.24,5.24,5.22,5.22,5.23,5.21,5.22,5.22,5.22,5.23,5.22,5.21,5.22,5.23,5.23,5.21,5.22,5.22,5.22,5.20,5.20,5.21,5.22,5.20,5.19,5.19,5.19,5.17,5.18,5.17,5.17,5.15,5.14,5.14,5.15,5.14,5.12,5.13,5.13,5.12,5.10,5.10,5.10,5.08,5.08,5.07,5.08,5.07,5.06,5.05,5.06,5.06,5.05,5.03,5.03,5.04,5.03,5.02,5.03,5.03,5.02,5.00,5.01,5.02,5.00,5.00,5.01,5.01,5.01,4.99,4.99,5.00,5.00,4.99,4.98,4.98,4.99,4.97,4.98,4.99,4.99,4.97,4.97,4.98,4.99,4.97,4.96,4.98,4.99,5.00,5.03,5.20,5.73,5.90,5.53,5.36,5.30,5.22,5.09,4.91,4.84,4.39,4.06,3.75,3.51,3.27,3.02,2.74,2.54,2.39,2.27,2.17,2.13,2.11,2.07 5 | 2.06,2.07,2.07,2.08,2.08,2.12,2.18,2.28,2.43,2.66,2.73,3.28,3.64,4.01,4.36,4.66,4.93,5.17,5.42,5.63,5.76,5.83,5.91,5.96,5.98,6.02,6.07,6.10,6.11,6.13,6.15,6.19,6.20,6.22,6.25,6.27,6.34,6.37,6.41,6.46,6.49,6.51,6.55,6.60,6.63,6.66,6.69,6.73,6.76,6.78,6.80,6.83,6.86,6.86,6.88,6.87,6.89,6.87,6.85,6.82,6.81,6.74,6.67,6.61,6.55,6.48,6.39,6.31,6.23,6.14,6.04,5.95,5.88,5.81,5.74,5.66,5.61,5.57,5.50,5.45,5.41,5.38,5.35,5.32,5.29,5.28,5.28,5.25,5.25,5.25,5.24,5.22,5.22,5.22,5.23,5.22,5.20,5.21,5.23,5.20,5.21,5.21,5.22,5.21,5.20,5.20,5.21,5.21,5.20,5.20,5.20,5.18,5.18,5.18,5.19,5.17,5.17,5.15,5.17,5.17,5.15,5.13,5.13,5.12,5.12,5.10,5.11,5.11,5.10,5.08,5.08,5.08,5.07,5.06,5.05,5.05,5.05,5.03,5.04,5.04,5.04,5.02,5.02,5.02,5.02,5.01,5.00,5.02,5.01,4.99,4.99,4.99,5.00,4.99,4.99,4.98,5.00,4.99,4.98,4.98,4.98,4.99,4.98,4.97,4.98,4.98,4.96,4.96,4.97,4.97,4.97,4.96,4.97,4.97,4.97,4.95,4.96,4.97,4.97,4.97,5.03,5.27,5.77,5.90,5.55,5.46,5.30,5.21,5.09,4.92,4.69,4.41,4.08,3.80,3.57,3.33,3.07,2.79,2.58,2.43,2.31,2.22,2.16,2.12,2.08,2.08,2.08,2.07,2.06,2.09 6 | 2.10,2.18,2.28,2.46,2.69,2.98,3.30,3.64,3.98,4.28,4.55,4.81,5.06,5.29,5.49,5.63,5.72,5.79,5.85,5.88,5.93,5.98,6.02,6.04,6.07,6.09,6.16,6.19,6.22,6.26,6.29,6.32,6.35,6.41,6.44,6.48,6.50,6.55,6.59,6.61,6.63,6.67,6.69,6.70,6.72,6.74,6.77,6.78,6.78,6.80,6.82,6.84,6.81,6.78,6.77,6.72,6.66,6.60,6.56,6.48,6.40,6.32,6.26,6.18,6.09,6.00,5.91,5.85,5.77,5.69,5.63,5.58,5.51,5.46,5.41,5.41,5.34,5.31,5.29,5.28,5.26,5.23,5.21,5.21,5.21,5.19,5.19,5.20,5.21,5.20,5.18,5.19,5.21,5.20,5.19,5.18,5.20,5.19,5.19,5.19,5.20,5.20,5.20,5.19,5.20,5.19,5.18,5.17,5.19,5.19,5.17,5.16,5.16,5.16,5.15,5.15,5.15,5.16,5.14,5.11,5.12,5.13,5.11,5.09,5.09,5.10,5.10,5.08,5.07,5.08,5.07,5.05,5.05,5.05,5.06,5.05,5.04,5.04,5.05,5.04,5.03,5.03,5.03,5.02,5.00,5.01,5.01,5.02,5.01,5.00,5.00,5.01,5.00,5.00,5.01,5.00,5.00,4.98,5.00,5.00,4.99,4.98,4.98,4.99,4.98,4.97,4.98,4.98,4.99,4.98,4.99,5.03,5.08,5.33,5.83,5.97,5.55,5.39,5.36,5.32,5.27,5.15,5.00,4.73,4.38,4.00,3.60,3.26,3.00,2.76,2.60,2.46,2.31,2.21,2.15,2.12,2.12,2.09,2.08,2.08,2.08,2.11,2.13,2.21,2.35,2.53,2.75,3.03,3.40,3.76 7 | 4.07,4.38,4.69,4.93,5.13,5.34,5.55,5.70,5.78,5.84,5.90,5.95,5.99,6.02,6.07,6.09,6.14,6.17,6.20,6.25,6.28,6.30,6.34,6.40,6.43,6.47,6.51,6.56,6.61,6.66,6.68,6.74,6.78,6.81,6.84,6.89,6.94,6.97,6.99,7.00,7.01,7.01,6.98,6.95,6.93,6.88,6.82,6.74,6.68,6.58,6.49,6.40,6.33,6.24,6.17,6.08,6.00,5.92,5.83,5.75,5.67,5.62,5.57,5.51,5.46,5.46,5.40,5.36,5.33,5.32,5.31,5.28,5.25,5.26,5.26,5.24,5.23,5.24,5.24,5.23,5.22,5.23,5.24,5.24,5.21,5.22,5.23,5.22,5.23,5.23,5.23,5.23,5.23,5.22,5.24,5.23,5.22,5.21,5.23,5.22,5.20,5.19,5.20,5.20,5.19,5.18,5.18,5.18,5.16,5.15,5.15,5.16,5.14,5.13,5.13,5.13,5.12,5.11,5.09,5.11,5.10,5.10,5.09,5.09,5.10,5.07,5.06,5.07,5.08,5.06,5.05,5.05,5.06,5.06,5.05,5.05,5.06,5.05,5.04,5.04,5.03,5.04,5.03,5.02,5.03,5.04,5.03,5.02,5.03,5.03,5.02,5.02,5.02,5.02,5.01,5.01,5.02,5.02,5.02,5.02,5.04,5.10,5.27,5.76,6.02,6.00,5.47,5.39,5.31,5.23,5.09,4.86,4.58,4.29,3.98,3.69,3.42,3.21,2.94,2.65,2.46,2.34,2.25,2.15,2.11,2.11,2.11,2.10,2.09,2.10,2.10,2.15,2.20,2.33,2.52,2.76,3.06,3.42,3.81,4.17,4.48,4.76,5.06,5.31,5.52,5.67,5.78,5.84,5.90,5.94 8 | 6.00,6.04,6.08,6.10,6.14,6.16,6.21,6.24,6.28,6.33,6.37,6.39,6.43,6.47,6.51,6.55,6.59,6.63,6.68,6.71,6.75,6.80,6.84,6.87,6.89,6.92,6.95,6.95,6.95,6.94,6.93,6.91,6.87,6.83,6.80,6.73,6.67,6.59,6.52,6.43,6.32,6.22,6.15,6.05,5.96,5.88,5.80,5.74,5.66,5.59,5.55,5.50,5.45,5.39,5.36,5.37,5.31,5.28,5.27,5.27,5.25,5.23,5.23,5.23,5.22,5.21,5.20,5.20,5.21,5.21,5.20,5.21,5.24,5.25,5.24,5.25,5.27,5.26,5.24,5.24,5.25,5.25,5.24,5.24,5.25,5.24,5.21,5.18,5.17,5.16,5.14,5.12,5.11,5.11,5.09,5.09,5.11,5.12,5.11,5.09,5.09,5.10,5.08,5.08,5.07,5.09,5.07,5.05,5.04,5.05,5.05,5.04,5.03,5.03,5.04,5.02,5.01,5.00,5.00,5.00,5.00,5.00,5.01,4.99,4.99,4.99,5.01,4.99,4.97,4.97,4.97,4.99,4.98,4.97,4.99,4.99,4.98,4.97,4.99,4.99,4.98,4.97,4.98,4.98,4.97,4.96,4.97,4.98,4.97,4.96,4.97,4.99,4.98,4.97,4.98,5.00,5.01,5.09,5.48,5.92,5.78,5.48,5.39,5.34,5.28,5.21,5.11,4.97,4.74,4.42,4.07,3.73,3.38,3.04,2.82,2.66,2.55,2.48,2.39,2.28,2.25,2.16,2.14,2.10,2.10,2.09,2.08,2.08,2.11,2.14,2.18,2.26,2.40,2.58,2.78,3.01,3.29,3.60,3.89,4.15,4.42,4.69,4.92,5.12,5.31,5.37,5.64,5.73,5.80,5.87 9 | 5.93,5.97,6.00,6.06,6.10,6.15,6.18,6.24,6.28,6.33,6.36,6.43,6.47,6.51,6.55,6.62,6.66,6.71,6.74,6.79,6.81,6.87,6.91,6.95,6.99,7.01,7.03,7.05,7.08,7.10,7.09,7.08,7.09,7.07,7.03,6.98,6.95,6.91,6.82,6.76,6.70,6.64,6.56,6.46,6.39,6.37,6.24,6.15,6.07,6.02,5.92,5.85,5.78,5.73,5.68,5.62,5.56,5.53,5.49,5.45,5.40,5.38,5.35,5.33,5.30,5.28,5.27,5.24,5.21,5.20,5.21,5.19,5.17,5.16,5.17,5.15,5.15,5.13,5.15,5.15,5.14,5.13,5.14,5.14,5.13,5.12,5.13,5.14,5.13,5.12,5.14,5.15,5.13,5.11,5.13,5.13,5.14,5.12,5.12,5.12,5.12,5.11,5.10,5.12,5.12,5.10,5.09,5.09,5.09,5.09,5.08,5.07,5.08,5.07,5.05,5.05,5.06,5.05,5.04,5.04,5.05,5.04,5.02,5.01,5.03,5.03,5.01,5.00,5.02,5.02,5.00,4.99,5.01,5.01,5.01,5.01,5.04,5.04,5.04,5.03,5.03,5.04,5.04,5.02,5.03,5.03,5.02,4.99,4.98,4.97,4.95,4.94,4.93,4.93,4.93,4.91,4.91,4.94,4.95,4.94,4.93,4.95,4.96,4.98,5.06,5.39,5.84,5.74,5.44,5.35,5.33,5.29,5.22,5.14,5.01,4.79,4.51,4.19,3.92,3.60,3.33,3.15,3.03,2.92,2.77,2.58,2.42,2.33,2.28,2.20,2.16,2.13,2.10,2.09,2.09,2.09,2.12,2.18,2.29,2.44,2.62,2.83,3.11,3.45,3.79,4.09,4.41,4.69,4.95,5.17 10 | 5.36,5.53,5.65,5.74,5.80,5.86,5.93,5.97,6.00,6.05,6.07,6.14,6.17,6.21,6.27,6.30,6.34,6.39,6.44,6.48,6.52,6.56,6.62,6.67,6.70,6.75,6.80,6.85,6.87,6.91,6.95,6.99,7.01,7.00,7.03,7.04,7.03,6.99,6.98,6.97,6.92,6.85,6.80,6.75,6.69,6.60,6.52,6.45,6.37,6.28,6.19,6.11,6.04,5.95,5.89,5.82,5.76,5.68,5.60,5.56,5.54,5.47,5.41,5.35,5.35,5.32,5.28,5.25,5.23,5.22,5.19,5.17,5.16,5.15,5.14,5.11,5.12,5.12,5.10,5.09,5.10,5.10,5.09,5.08,5.09,5.09,5.10,5.08,5.08,5.10,5.10,5.10,5.08,5.10,5.11,5.09,5.09,5.09,5.10,5.10,5.08,5.09,5.10,5.10,5.09,5.09,5.10,5.08,5.08,5.08,5.07,5.07,5.05,5.04,5.05,5.04,5.03,5.02,5.03,5.03,5.01,5.00,5.02,5.01,5.00,4.98,4.98,4.99,4.97,4.97,4.97,4.99,4.97,4.95,4.96,4.96,4.96,4.93,4.94,4.95,4.96,4.94,4.93,4.93,4.94,4.92,4.92,4.93,4.93,4.92,4.92,4.92,4.92,4.92,4.90,4.91,4.93,4.92,4.89,4.90,4.91,4.91,4.90,4.90,4.92,4.95,5.04,5.40,5.82,5.73,5.42,5.31,5.26,5.22,5.16,5.06,4.89,4.59,4.23,3.87,3.48,3.12,2.82,2.58,2.45,2.42,2.19,2.09,2.04,2.03,2.01,1.99,1.99,2.00,2.00,2.00,2.02,2.07,2.17,2.30,2.51,2.77,3.12,3.48,3.78,4.11,4.45,4.72,4.95,5.19 11 | 5.26,5.57,5.66,5.74,5.80,5.84,5.87,5.91,5.96,6.00,6.01,6.04,6.09,6.12,6.13,6.17,6.22,6.26,6.29,6.32,6.37,6.41,6.45,6.49,6.53,6.55,6.62,6.64,6.68,6.73,6.76,6.79,6.82,6.85,6.87,6.88,6.87,6.89,6.89,6.86,6.82,6.81,6.77,6.72,6.64,6.57,6.51,6.41,6.33,6.24,6.21,6.05,5.95,5.88,5.81,5.73,5.64,5.59,5.54,5.48,5.41,5.37,5.34,5.31,5.26,5.23,5.22,5.20,5.17,5.15,5.15,5.14,5.14,5.12,5.12,5.12,5.14,5.12,5.11,5.13,5.12,5.11,5.12,5.12,5.12,5.11,5.11,5.11,5.12,5.12,5.10,5.11,5.12,5.11,5.09,5.11,5.10,5.10,5.08,5.08,5.09,5.08,5.06,5.05,5.06,5.06,5.04,5.04,5.05,5.04,5.01,5.00,5.01,5.01,4.99,4.98,4.99,4.99,4.98,4.95,4.96,4.97,4.96,4.94,4.95,4.96,4.95,4.93,4.94,4.93,4.94,4.93,4.92,4.93,4.93,4.91,4.90,4.92,4.92,4.91,4.90,4.90,4.92,4.90,4.88,4.89,4.91,4.90,4.89,4.88,4.88,4.89,4.88,4.88,4.89,4.88,4.88,4.88,4.89,4.89,4.88,4.88,4.92,4.97,5.20,5.70,5.80,5.46,5.29,5.22,5.18,5.12,5.02,4.82,4.54,4.45,3.84,3.45,3.11,2.85,2.64,2.47,2.36,2.23,2.13,2.06,2.03,2.02,2.00,1.99,1.99,2.00,2.01,2.02,2.04,2.12,2.23,2.39,2.56,2.84,2.94,3.50,3.81,4.15,4.46,4.74,4.97,5.19,5.40,5.58 12 | 5.70,5.77,5.84,5.88,5.91,5.94,5.98,6.02,6.04,6.05,6.08,6.11,6.13,6.13,6.19,6.21,6.28,6.31,6.34,6.39,6.41,6.44,6.48,6.53,6.57,6.59,6.63,6.67,6.71,6.73,6.75,6.78,6.80,6.81,6.81,6.82,6.83,6.80,6.77,6.75,6.74,6.66,6.59,6.54,6.47,6.40,6.30,6.22,6.14,6.05,5.95,5.86,5.79,5.72,5.65,5.57,5.52,5.46,5.40,5.34,5.33,5.30,5.25,5.21,5.19,5.19,5.16,5.13,5.12,5.14,5.13,5.11,5.11,5.11,5.11,5.10,5.10,5.11,5.11,5.10,5.10,5.10,5.11,5.11,5.10,5.10,5.12,5.11,5.09,5.09,5.10,5.10,5.08,5.08,5.09,5.08,5.07,5.08,5.08,5.07,5.05,5.05,5.05,5.05,5.03,5.02,5.03,5.03,5.01,4.98,5.00,5.00,4.98,4.97,4.97,4.97,4.96,4.95,4.95,4.95,4.95,4.93,4.94,4.94,4.94,4.93,4.92,4.92,4.93,4.92,4.91,4.92,4.93,4.91,4.90,4.92,4.93,4.91,4.90,4.91,4.92,4.91,4.90,4.91,4.91,4.90,4.88,4.90,4.90,4.91,4.89,4.89,4.91,4.91,4.93,4.96,5.11,5.62,5.96,5.64,5.37,5.31,5.25,5.20,5.14,5.11,4.76,4.41,4.05,3.69,3.31,3.00,2.76,2.60,2.46,2.30,2.15,2.10,2.07,2.02,2.00,2.02,2.00,2.00,2.00,2.03,2.09,2.17,2.30,2.51,2.57,3.05,3.37,3.72,4.04,4.36,4.66,4.93,5.16,5.36,5.52,5.64,5.74,5.80,5.84,5.89,5.94,5.98,6.00,6.03 13 | 6.09,6.11,6.14,6.17,6.20,6.21,6.28,6.31,6.37,6.42,6.47,6.50,6.55,6.61,6.66,6.68,6.73,6.80,6.84,6.85,6.88,6.92,6.93,6.93,6.92,6.92,6.92,6.89,6.85,6.82,6.81,6.74,6.68,6.64,6.57,6.48,6.39,6.30,6.22,6.14,6.03,5.95,5.88,5.80,5.72,5.64,5.57,5.53,5.45,5.40,5.36,5.32,5.28,5.23,5.22,5.21,5.18,5.15,5.15,5.14,5.13,5.12,5.12,5.13,5.13,5.10,5.11,5.11,5.12,5.11,5.11,5.12,5.13,5.12,5.11,5.13,5.12,5.12,5.11,5.12,5.12,5.12,5.11,5.11,5.11,5.12,5.10,5.09,5.09,5.09,5.07,5.06,5.06,5.05,5.06,5.04,5.05,5.04,5.02,5.00,5.00,5.02,5.00,4.99,4.99,5.00,4.99,4.98,4.97,4.98,4.98,4.96,4.95,4.96,4.96,4.94,4.93,4.96,4.95,4.94,4.93,4.94,4.94,4.93,4.91,4.93,4.93,4.92,4.90,4.92,4.92,4.92,4.91,4.90,4.91,4.90,4.90,4.89,4.90,4.91,4.90,4.89,4.91,4.91,4.90,4.89,4.92,4.94,4.99,5.30,5.83,5.84,5.47,5.28,5.20,5.18,5.01,4.82,4.61,4.37,4.06,3.76,3.54,3.36,3.17,2.87,2.60,2.44,2.31,2.19,2.09,2.06,2.04,2.02,1.99,2.01,2.01,2.02,2.01,2.08,2.10,2.31,2.49,2.75,3.06,3.38,3.72,4.08,4.37,4.65,4.89,5.11,5.34,5.51,5.62,5.69,5.76,5.81,5.85,5.89,5.95,5.99,6.00,6.04,6.08,6.09,6.14,6.16,6.20,6.24 14 | 6.27,6.29,6.34,6.38,6.42,6.45,6.49,6.55,6.58,6.60,6.64,6.68,6.72,6.75,6.77,6.80,6.84,6.86,6.85,6.86,6.87,6.86,6.83,6.81,6.78,6.73,6.65,6.60,6.54,6.48,6.38,6.30,6.23,6.15,6.05,5.95,5.90,5.82,5.73,5.65,5.59,5.55,5.48,5.41,5.36,5.35,5.28,5.22,5.20,5.18,5.15,5.11,5.09,5.10,5.11,5.11,5.10,5.10,5.11,5.09,5.08,5.09,5.10,5.09,5.08,5.09,5.09,5.09,5.08,5.10,5.11,5.10,5.09,5.09,5.10,5.10,5.09,5.07,5.08,5.08,5.07,5.06,5.06,5.06,5.05,5.02,5.04,5.03,5.02,5.00,5.00,5.01,5.00,4.98,4.98,4.99,4.99,4.97,4.96,4.98,4.96,4.95,4.95,4.96,4.96,4.95,4.94,4.96,4.96,4.94,4.94,4.94,4.95,4.95,4.93,4.95,4.95,4.95,4.92,4.93,4.94,4.93,4.92,4.92,4.93,4.94,4.93,4.92,4.93,4.92,4.91,4.91,4.92,4.92,4.91,4.90,4.91,4.92,4.92,4.91,4.93,4.99,5.22,5.73,5.88,5.82,5.34,5.28,5.22,5.18,5.06,4.86,4.59,4.25,3.88,3.47,3.09,2.84,2.66,2.50,2.40,2.30,2.19,2.11,2.06,2.04,2.03,2.02,2.00,2.01,2.02,2.04,2.05,2.13,2.25,2.41,2.61,2.87,3.20,3.54,3.83,4.16,4.49,4.76,4.99,5.18,5.38,5.55,5.67,5.73,5.81,5.85,5.90,5.93,5.98,6.00,6.07,6.09,6.13,6.18,6.21,6.24,6.27,6.33,6.37,6.41,6.45,6.51,6.56,6.59 15 | 6.63,6.68,6.73,6.76,6.80,6.85,6.88,6.91,6.93,6.96,6.97,6.98,6.97,6.96,6.95,6.91,6.85,6.81,6.75,6.69,6.61,6.52,6.46,6.38,6.28,6.18,6.11,6.03,5.94,5.85,5.80,5.74,5.67,5.60,5.56,5.55,5.47,5.41,5.36,5.32,5.29,5.25,5.21,5.19,5.16,5.12,5.10,5.09,5.09,5.09,5.09,5.11,5.10,5.09,5.08,5.08,5.09,5.08,5.07,5.08,5.09,5.09,5.08,5.08,5.10,5.11,5.09,5.08,5.09,5.09,5.09,5.08,5.08,5.09,5.07,5.06,5.06,5.08,5.06,5.05,5.04,5.05,5.05,5.02,5.02,5.03,5.02,5.00,4.99,5.01,5.00,5.00,4.98,5.00,4.99,4.97,4.97,4.98,4.97,4.96,4.96,4.97,4.97,4.96,4.95,4.95,4.97,4.96,4.94,4.95,4.95,4.95,4.93,4.94,4.94,4.94,4.93,4.94,4.94,4.94,4.93,4.91,4.93,4.93,4.93,4.92,4.94,4.94,4.93,4.92,4.94,4.94,4.96,5.01,5.30,5.44,5.91,5.54,5.38,5.34,5.27,5.19,5.09,4.92,4.67,4.31,3.95,3.59,3.26,2.98,2.76,2.62,2.51,2.37,2.24,2.15,2.12,2.09,2.06,2.05,2.04,2.04,2.04,2.05,2.08,2.14,2.23,2.39,2.58,2.79,3.05,3.36,3.70,4.01,4.30,4.59,4.87,5.09,5.28,5.45,5.61,5.71,5.76,5.80,5.85,5.87,5.95,5.97,6.00,6.05,6.08,6.10,6.12,6.17,6.21,6.24,6.27,6.32,6.36,6.40,6.43,6.49,6.53,6.55,6.58,6.63,6.69,6.72,6.76,6.81 16 | 6.83,6.88,6.88,6.91,6.94,6.93,6.92,6.91,6.89,6.86,6.80,6.74,6.70,6.63,6.54,6.45,6.38,6.30,6.22,6.11,6.05,5.97,5.89,5.80,5.75,5.73,5.65,5.59,5.54,5.51,5.47,5.43,5.39,5.39,5.33,5.29,5.26,5.25,5.23,5.20,5.17,5.16,5.16,5.13,5.11,5.11,5.12,5.13,5.12,5.13,5.13,5.13,5.12,5.13,5.13,5.15,5.13,5.13,5.14,5.14,5.13,5.13,5.14,5.15,5.15,5.13,5.14,5.14,5.12,5.12,5.12,5.12,5.12,5.09,5.10,5.10,5.10,5.08,5.08,5.09,5.09,5.06,5.06,5.07,5.06,5.05,5.03,5.04,5.04,5.03,5.02,5.02,5.04,5.03,5.00,5.02,5.02,5.01,4.99,5.02,5.02,5.01,4.99,5.00,5.01,5.01,4.99,4.99,5.00,5.00,4.99,4.97,5.00,5.00,4.98,4.98,4.98,4.99,4.99,4.98,4.98,4.99,4.98,4.96,4.97,4.97,4.98,4.96,4.97,4.98,4.98,4.97,4.96,4.98,4.99,5.00,5.04,5.30,5.79,5.95,5.61,5.43,5.39,5.33,5.28,5.24,5.13,4.96,4.72,4.43,4.33,3.80,3.47,3.17,2.97,2.79,2.63,2.53,2.44,2.32,2.22,2.16,2.15,2.12,2.09,2.08,2.08,2.08,2.09,2.09,2.13,2.19,2.26,2.37,2.55,2.61,2.97,3.25,3.57,3.88,4.17,4.46,4.74,4.99,5.20,5.37,5.54,5.70,5.79,5.85,5.91,5.96,6.01,6.04,6.06,6.11,6.15,6.17,6.20,6.24,6.26,6.32,6.34,6.38,6.42,6.46,6.48,6.52,6.58,6.62 17 | 6.66,6.69,6.74,6.80,6.81,6.86,6.91,6.95,6.97,7.00,7.05,7.05,7.07,7.06,7.07,7.07,7.05,7.03,7.00,6.98,6.94,6.88,6.83,6.78,6.72,6.63,6.55,6.48,6.40,6.30,6.21,6.14,6.07,5.98,5.89,5.83,5.76,5.69,5.63,5.60,5.59,5.50,5.45,5.41,5.40,5.37,5.32,5.30,5.29,5.28,5.25,5.23,5.22,5.22,5.20,5.19,5.21,5.20,5.19,5.17,5.18,5.19,5.18,5.17,5.17,5.17,5.17,5.16,5.17,5.17,5.18,5.16,5.16,5.17,5.17,5.16,5.16,5.17,5.18,5.18,5.17,5.19,5.20,5.20,5.19,5.19,5.19,5.18,5.16,5.17,5.17,5.14,5.12,5.10,5.09,5.07,5.06,5.03,5.04,5.03,5.01,5.01,5.02,5.04,5.01,5.01,5.01,5.02,5.02,5.00,5.00,5.01,5.00,4.99,5.00,5.01,5.00,4.98,4.97,4.99,4.99,4.97,4.97,4.97,4.98,4.97,4.96,4.97,4.96,4.96,4.95,4.97,4.96,4.95,4.94,4.96,4.97,4.96,4.94,4.95,4.96,4.96,4.96,4.96,4.98,5.00,5.06,5.39,5.88,5.82,5.50,5.38,5.34,5.29,5.23,5.15,5.02,4.81,4.50,4.15,3.81,3.44,3.08,2.82,2.65,2.61,2.45,2.33,2.23,2.17,2.13,2.09,2.07,2.07,2.06,2.04,2.04,2.07,2.10,2.16,2.24,2.39,2.56,2.75,2.98,3.28,3.60,3.90,4.17,4.45,4.54,4.94,5.13,5.32,5.51,5.66,5.75,5.82,5.90,5.94,5.98,6.03,6.08,6.12,6.15,6.19,6.25,6.30,6.33,6.37 18 | 6.42,6.48,6.51,6.56,6.61,6.63,6.71,6.75,6.80,6.85,6.89,6.92,6.96,7.00,7.03,7.05,7.06,7.08,7.09,7.08,7.05,7.04,7.01,6.96,6.91,6.86,6.81,6.73,6.63,6.57,6.55,6.42,6.33,6.25,6.19,6.10,6.00,5.92,5.87,5.80,5.71,5.66,5.61,5.56,5.50,5.46,5.43,5.40,5.35,5.31,5.29,5.28,5.24,5.21,5.20,5.21,5.18,5.15,5.15,5.14,5.14,5.12,5.12,5.13,5.12,5.10,5.09,5.11,5.10,5.08,5.09,5.09,5.10,5.10,5.09,5.10,5.10,5.09,5.08,5.09,5.10,5.10,5.09,5.08,5.10,5.09,5.08,5.08,5.08,5.08,5.06,5.06,5.07,5.07,5.06,5.04,5.04,5.04,5.04,5.02,5.02,5.03,5.02,5.01,5.00,5.01,5.01,4.99,4.98,4.99,4.99,4.98,4.97,4.98,4.99,4.97,4.98,4.99,5.00,4.99,5.00,5.02,5.01,5.02,5.00,5.01,5.00,4.98,4.95,4.95,4.96,4.93,4.92,4.90,4.90,4.90,4.87,4.88,4.90,4.91,4.91,4.90,4.91,4.92,4.92,4.96,5.07,5.50,5.95,5.75,5.44,5.36,5.31,5.25,5.20,5.18,4.94,4.66,4.37,4.08,3.78,3.48,3.27,3.12,2.99,2.83,2.67,2.51,2.37,2.29,2.23,2.18,2.14,2.10,2.07,2.07,2.07,2.10,2.16,2.31,2.35,2.66,2.86,3.16,3.49,3.81,4.11,4.41,4.71,4.96,5.13,5.33,5.51,5.63,5.70,5.76,5.83,5.88,5.93,5.96,6.02,6.06,6.09,6.13,6.17,6.19,6.27,6.30,6.36,6.41 19 | 6.46,6.49,6.55,6.61,6.65,6.68,6.72,6.78,6.82,6.86,6.88,6.93,6.96,6.96,6.97,6.98,6.98,6.95,6.92,6.89,6.88,6.78,6.70,6.65,6.59,6.51,6.42,6.34,6.27,6.19,6.09,6.00,5.93,5.86,5.77,5.70,5.64,5.59,5.53,5.47,5.45,5.41,5.36,5.32,5.30,5.29,5.25,5.21,5.20,5.20,5.18,5.15,5.14,5.15,5.15,5.13,5.12,5.12,5.12,5.10,5.10,5.12,5.11,5.11,5.09,5.10,5.12,5.12,5.10,5.11,5.12,5.12,5.11,5.11,5.12,5.12,5.11,5.11,5.12,5.12,5.10,5.10,5.10,5.11,5.10,5.08,5.09,5.09,5.08,5.06,5.07,5.08,5.06,5.03,5.04,5.04,5.03,5.01,5.01,5.01,5.02,5.00,5.00,5.00,5.00,5.00,4.99,4.99,4.99,4.97,4.97,4.98,4.97,4.97,4.96,4.97,4.98,4.97,4.94,4.95,4.96,4.95,4.95,4.95,4.95,4.95,4.94,4.94,4.96,4.96,4.94,4.93,4.95,4.95,4.94,4.94,4.97,4.99,5.02,5.22,5.70,6.01,5.74,5.46,5.41,5.40,5.30,5.24,5.13,4.96,4.68,4.32,3.97,3.62,3.28,2.98,2.74,2.57,2.45,2.31,2.18,2.13,2.08,2.05,2.03,2.02,2.02,2.03,2.02,2.04,2.05,2.14,2.24,2.43,2.64,2.88,3.15,3.47,3.79,4.10,4.40,4.68,4.93,5.14,5.33,5.50,5.65,5.72,5.78,5.82,5.88,5.92,5.96,5.99,6.04,6.06,6.13,6.14,6.20,6.25,6.28,6.31,6.36,6.41,6.46,6.49,6.53,6.60,6.65,6.67 20 | 6.71,6.77,6.82,6.84,6.87,6.91,6.94,6.96,6.95,6.96,6.97,6.95,6.92,6.90,6.86,6.81,6.73,6.67,6.61,6.53,6.43,6.33,6.25,6.17,6.07,5.98,5.91,5.83,5.75,5.67,5.60,5.55,5.48,5.42,5.39,5.38,5.32,5.27,5.26,5.25,5.23,5.20,5.19,5.19,5.20,5.18,5.16,5.17,5.17,5.17,5.16,5.17,5.17,5.16,5.15,5.16,5.17,5.15,5.16,5.15,5.16,5.16,5.14,5.14,5.16,5.15,5.14,5.15,5.14,5.14,5.11,5.12,5.12,5.13,5.11,5.09,5.10,5.09,5.08,5.06,5.06,5.07,5.06,5.03,5.04,5.05,5.04,5.03,5.02,5.03,5.03,5.01,5.01,5.01,5.02,4.99,4.99,5.00,5.01,4.99,4.97,4.99,4.99,4.98,4.96,4.98,4.98,4.97,4.96,4.97,4.97,4.97,4.95,4.95,4.96,4.97,4.95,4.96,4.97,4.96,4.95,4.95,4.96,4.96,4.95,4.94,4.96,4.97,4.99,5.13,5.57,5.89,5.62,5.38,5.34,5.32,5.23,5.14,5.02,4.80,4.47,4.10,3.73,3.36,3.03,2.78,2.58,2.47,2.37,2.23,2.15,2.12,2.10,2.07,2.04,2.06,2.05,2.06,2.07,2.10,2.12,2.27,2.43,2.65,2.91,3.22,3.54,3.88,4.20,4.52,4.79,5.03,5.25,5.46,5.62,5.71,5.79,5.85,5.88,5.92,5.97,6.02,6.04,6.06,6.10,6.12,6.16,6.17,6.22,6.25,6.29,6.30,6.34,6.39,6.44,6.46,6.51,6.56,6.60,6.63,6.67,6.71,6.75,6.77,6.80,6.84,6.86,6.89,6.89,6.90 21 | 6.90,6.89,6.86,6.82,6.78,6.73,6.64,6.57,6.50,6.41,6.31,6.22,6.13,6.04,5.93,5.85,5.77,5.72,5.65,5.57,5.53,5.48,5.42,5.38,5.35,5.34,5.30,5.27,5.25,5.26,5.24,5.21,5.22,5.22,5.22,5.20,5.19,5.20,5.20,5.18,5.19,5.19,5.18,5.15,5.13,5.10,5.07,5.03,4.99,5.00,5.01,5.06,5.14,5.31,5.52,5.72,5.88,6.09,6.36,6.63,6.86,7.10,7.34,7.53,7.63,7.64,7.60,7.38,6.87,6.06,5.34,5.03,4.95,4.84,4.69,4.65,4.54,4.49,4.46,4.43,4.38,4.33,4.31,4.28,4.26,4.21,4.19,4.18,4.15,4.12,4.09,4.08,4.07,4.03,4.01,4.00,3.97,3.94,3.90,3.90,3.89,3.85,3.81,3.81,3.79,3.77,3.74,3.72,3.71,3.70,3.67,3.66,3.68,3.67,3.66,3.67,3.69,3.71,3.72,3.75,3.80,3.85,3.89,3.95,4.03,4.06,4.18,4.25,4.36,4.45,4.54,4.60,4.68,4.76,4.83,4.86,4.91,4.95,4.97,4.98,5.00,5.04,5.04,5.05,5.03,5.06,5.07,5.06,5.05,5.06,5.07,5.07,5.06,5.06,5.07,5.07,5.06,5.07,5.07,5.08,5.06,5.06,5.08,5.09,5.09,5.10,5.11,5.11,5.10,5.10,5.11,5.12,5.10,5.06,5.07,5.06,5.05,5.02,5.02,5.02,5.01,4.99,4.98,5.00,5.00,5.00,5.02,5.03,5.03,5.01,5.01,5.03,5.04,5.02,5.01,5.02,5.04,5.02,5.02,5.03,5.04,5.03,5.03,5.02,5.04,5.04,5.03,5.03,5.05,5.04 22 | 5.04,5.04,5.04,5.05,5.05,5.05,5.04,5.05,5.05,5.04,5.05,5.07,5.06,5.04,5.06,5.06,5.07,5.06,5.07,5.08,5.08,5.06,5.07,5.08,5.09,5.07,5.08,5.08,5.09,5.08,5.07,5.08,5.09,5.09,5.07,5.08,5.10,5.09,5.08,5.09,5.10,5.10,5.08,5.10,5.09,5.09,5.07,5.09,5.10,5.10,5.08,5.08,5.10,5.10,5.08,5.09,5.10,5.14,5.20,5.52,6.07,6.10,5.73,5.54,5.50,5.49,5.38,5.27,5.11,4.87,4.54,4.18,3.84,3.52,3.23,3.02,2.88,2.79,2.69,2.55,2.43,2.38,2.35,2.29,2.25,2.25,2.24,2.25,2.25,2.32,2.34,2.54,2.70,2.90,3.12,3.39,3.69,4.01,4.31,4.62,4.88,5.12,5.32,5.50,5.66,5.81,5.92,6.00,6.05,6.10,6.17,6.22,6.27,6.32,6.38,6.40,6.49,6.53,6.59,6.66,6.71,6.76,6.82,6.89,6.94,6.99,7.04,7.10,7.15,7.19,7.23,7.29,7.32,7.34,7.37,7.39,7.41,7.41,7.39,7.40,7.40,7.36,7.31,7.28,7.24,7.19,7.11,7.05,6.99,6.92,6.82,6.73,6.66,6.57,6.48,6.40,6.33,6.24,6.16,6.07,6.02,5.94,5.87,5.81,5.75,5.73,5.65,5.58,5.54,5.51,5.47,5.43,5.40,5.39,5.36,5.32,5.30,5.30,5.29,5.26,5.25,5.25,5.25,5.23,5.22,5.23,5.24,5.23,5.22,5.24,5.25,5.25,5.25,5.26,5.28,5.27,5.27,5.27,5.29,5.29,5.27,5.27,5.27,5.26,5.23,5.22,5.21,5.20,5.18,5.15 23 | 5.16,5.15,5.14,5.14,5.16,5.17,5.15,5.14,5.13,5.13,5.11,5.10,5.10,5.10,5.11,5.09,5.08,5.09,5.08,5.07,5.06,5.06,5.05,5.04,5.03,5.04,5.04,5.03,5.02,5.02,5.03,5.02,5.01,5.01,5.02,5.01,5.01,5.01,5.02,5.02,5.01,5.00,5.02,5.01,5.00,5.00,5.01,5.01,5.01,5.00,5.01,5.03,5.03,5.04,5.19,5.27,5.91,5.62,5.41,5.37,5.32,5.25,5.16,5.03,4.80,4.46,4.08,3.72,3.33,2.96,2.72,2.58,2.47,2.37,2.26,2.18,2.14,2.11,2.08,2.07,2.08,2.07,2.06,2.07,2.12,2.19,2.28,2.44,2.65,2.87,3.15,3.51,3.85,4.14,4.43,4.72,4.98,5.18,5.35,5.52,5.69,5.78,5.83,5.88,5.94,5.96,6.03,6.06,6.10,6.16,6.19,6.23,6.26,6.32,6.37,6.40,6.44,6.50,6.54,6.58,6.63,6.70,6.73,6.76,6.81,6.86,6.91,6.93,6.95,6.98,6.99,7.01,7.02,7.03,7.02,7.00,6.96,6.92,6.88,6.82,6.74,6.67,6.60,6.52,6.42,6.34,6.27,6.16,6.08,6.00,5.92,5.87,5.78,5.70,5.65,5.63,5.53,5.47,5.43,5.40,5.35,5.32,5.29,5.27,5.24,5.20,5.19,5.19,5.18,5.15,5.13,5.13,5.13,5.11,5.10,5.10,5.11,5.10,5.08,5.10,5.10,5.10,5.07,5.08,5.09,5.09,5.08,5.08,5.09,5.09,5.10,5.10,5.10,5.10,5.08,5.07,5.09,5.08,5.08,5.07,5.07,5.08,5.07,5.06,5.05,5.05,5.06,5.04,5.03,5.03 24 | -------------------------------------------------------------------------------- /demo/ecg_demo/feed_forward_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.preprocessing import MinMaxScaler 3 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 4 | from keras_anomaly_detection.library.feedforward import FeedForwardAutoEncoder 5 | 6 | DO_TRAINING = False 7 | 8 | 9 | def main(): 10 | data_dir_path = './data' 11 | model_dir_path = './models' 12 | 13 | # ecg data in which each row is a temporal sequence data of continuous values 14 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 15 | print(ecg_data.head()) 16 | ecg_np_data = ecg_data.as_matrix() 17 | scaler = MinMaxScaler() 18 | ecg_np_data = scaler.fit_transform(ecg_np_data) 19 | 20 | print(ecg_np_data.shape) 21 | 22 | ae = FeedForwardAutoEncoder() 23 | 24 | # fit the data and save model into model_dir_path 25 | if DO_TRAINING: 26 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 27 | 28 | # load back the model saved in model_dir_path detect anomaly 29 | ae.load_model(model_dir_path) 30 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 31 | reconstruction_error = [] 32 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 33 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 34 | reconstruction_error.append(dist) 35 | 36 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /demo/ecg_demo/h2o_ecg_pulse_detection.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib 4 | from matplotlib import cm 5 | import matplotlib.pyplot as plt 6 | import h2o 7 | from h2o.estimators.deeplearning import H2OAutoEncoderEstimator 8 | 9 | # Start H2O on your local machine 10 | h2o.init() 11 | ecg_data = h2o.import_file("http://h2o-public-test-data.s3.amazonaws.com/smalldata/anomaly/ecg_discord_test.csv") 12 | print(ecg_data.shape) 13 | print(ecg_data.types) 14 | print(ecg_data.head()) 15 | 16 | train_ecg = ecg_data[:20:, :] 17 | test_ecg = ecg_data[:23, :] 18 | 19 | 20 | def plot_stacked_time_series(df, title): 21 | stacked = df.stack() 22 | stacked = stacked.reset_index() 23 | total = [data[0].values for name, data in stacked.groupby('level_0')] 24 | # pd.DataFrame({idx: pos for idx, pos in enumerate(total)}, index=stacked['level_1']).plot(title=title) 25 | pd.DataFrame({idx: pos for idx, pos in enumerate(total)}).plot(title=title) 26 | plt.legend(bbox_to_anchor=(1.05, 1)) 27 | plt.show() 28 | 29 | 30 | plot_stacked_time_series(ecg_data.as_data_frame(), "ECG data set") 31 | 32 | 33 | def plot_bidimensional(model, test, recon_error, layer, title): 34 | bidimensional_data = model.deepfeatures(test, layer).cbind(recon_error).as_data_frame() 35 | 36 | cmap = cm.get_cmap('Spectral') 37 | 38 | fig, ax = plt.subplots() 39 | bidimensional_data.plot(kind='scatter', 40 | x='DF.L{}.C1'.format(layer + 1), 41 | y='DF.L{}.C2'.format(layer + 1), 42 | s=500, 43 | c='Reconstruction.MSE', 44 | title=title, 45 | ax=ax, 46 | colormap=cmap) 47 | layer_column = 'DF.L{}.C'.format(layer + 1) 48 | columns = [layer_column + '1', layer_column + '2'] 49 | for k, v in bidimensional_data[columns].iterrows(): 50 | ax.annotate(k, v, size=20, verticalalignment='bottom', horizontalalignment='left') 51 | fig.canvas.draw() 52 | plt.show() 53 | 54 | 55 | seed = 13 56 | anomaly_model = H2OAutoEncoderEstimator( 57 | activation="Tanh", 58 | hidden=[50, 20, 2, 20, 50], 59 | epochs=100, 60 | # sparse=True, 61 | # l1=1e-5, 62 | seed=seed, 63 | reproducible=True) 64 | 65 | anomaly_model.train( 66 | x=train_ecg.names, 67 | training_frame=train_ecg 68 | ) 69 | 70 | recon_error = anomaly_model.anomaly(test_ecg) 71 | plot_bidimensional(anomaly_model, test_ecg, recon_error, 2, "2D representation of data points seed {}".format(seed)) 72 | 73 | # plot_stacked_time_series(anomaly_model.predict(ecg_data).as_data_frame(), "Reconstructed test set") 74 | 75 | print(anomaly_model) 76 | 77 | plt.figure() 78 | df = recon_error.as_data_frame(True) 79 | df["sample_index"] = df.index 80 | df.plot(kind="scatter", x="sample_index", y="Reconstruction.MSE", 81 | title="reconstruction error", s=500) 82 | 83 | len(recon_error) 84 | 85 | anomaly_model.deepfeatures(train_ecg, 1).as_data_frame() # .plot(kind='scatter', x='DF.L2.C1', y='DF.L2.C2') 86 | 87 | for seed in range(1, 6): 88 | model = H2OAutoEncoderEstimator( 89 | activation="Tanh", 90 | hidden=[50, 20, 2, 20, 50], 91 | epochs=100, 92 | # sparse=True, 93 | # l1=1e-5, 94 | seed=seed, 95 | reproducible=True) 96 | model.train( 97 | x=train_ecg.names, 98 | training_frame=train_ecg) 99 | 100 | recon_error = model.anomaly(test_ecg) 101 | plot_bidimensional(model, test_ecg, recon_error, 2, "2D representation of data points seed {}".format(seed)) 102 | # compute average and variance of the 2 dimensions 103 | 104 | model = H2OAutoEncoderEstimator( 105 | activation="Tanh", 106 | hidden=[50, 20, 2, 20, 50], 107 | epochs=100, 108 | # sparse=True, 109 | # l1=1e-5, 110 | seed=1, 111 | reproducible=True) 112 | model.train( 113 | x=train_ecg.names, 114 | training_frame=train_ecg 115 | ) 116 | 117 | recon_error = model.anomaly(test_ecg) 118 | bidimensional_data = model.deepfeatures(test_ecg, 2).cbind(recon_error).as_data_frame() 119 | print(bidimensional_data) 120 | -------------------------------------------------------------------------------- /demo/ecg_demo/lstm_autoencoder.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.preprocessing import MinMaxScaler 3 | from keras_anomaly_detection.library.plot_utils import visualize_reconstruction_error 4 | from keras_anomaly_detection.library.recurrent import LstmAutoEncoder 5 | 6 | DO_TRAINING = False 7 | 8 | 9 | def main(): 10 | data_dir_path = './data' 11 | model_dir_path = './models' 12 | ecg_data = pd.read_csv(data_dir_path + '/ecg_discord_test.csv', header=None) 13 | print(ecg_data.head()) 14 | ecg_np_data = ecg_data.as_matrix() 15 | scaler = MinMaxScaler() 16 | ecg_np_data = scaler.fit_transform(ecg_np_data) 17 | print(ecg_np_data.shape) 18 | 19 | ae = LstmAutoEncoder() 20 | 21 | # fit the data and save model into model_dir_path 22 | if DO_TRAINING: 23 | ae.fit(ecg_np_data[:23, :], model_dir_path=model_dir_path, estimated_negative_sample_ratio=0.9) 24 | 25 | # load back the model saved in model_dir_path detect anomaly 26 | ae.load_model(model_dir_path) 27 | anomaly_information = ae.anomaly(ecg_np_data[:23, :]) 28 | reconstruction_error = [] 29 | for idx, (is_anomaly, dist) in enumerate(anomaly_information): 30 | print('# ' + str(idx) + ' is ' + ('abnormal' if is_anomaly else 'normal') + ' (dist: ' + str(dist) + ')') 31 | reconstruction_error.append(dist) 32 | 33 | visualize_reconstruction_error(reconstruction_error, ae.threshold) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /demo/ecg_demo/models/bidirectional-lstm-auto-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Bidirectional", "config": {"name": "bidirectional_1", "trainable": true, "batch_input_shape": [null, 210, 1], "dtype": "float32", "layer": {"class_name": "LSTM", "config": {"name": "lstm_1", "trainable": true, "return_sequences": false, "return_state": false, "go_backwards": false, "stateful": false, "unroll": false, "units": 64, "activation": "tanh", "recurrent_activation": "hard_sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "recurrent_initializer": {"class_name": "Orthogonal", "config": {"gain": 1.0, "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "unit_forget_bias": true, "kernel_regularizer": null, "recurrent_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "recurrent_constraint": null, "bias_constraint": null, "dropout": 0.2, "recurrent_dropout": 0.2, "implementation": 1}}, "merge_mode": "concat"}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 210, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/ecg_demo/models/bidirectional-lstm-auto-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/bidirectional-lstm-auto-encoder-config.npy -------------------------------------------------------------------------------- /demo/ecg_demo/models/bidirectional-lstm-auto-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/bidirectional-lstm-auto-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/ecg_demo/models/cnn-lstm-auto-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Conv1D", "config": {"name": "conv1d_1", "trainable": true, "batch_input_shape": [null, 210, 1], "dtype": "float32", "filters": 256, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling1D", "config": {"name": "max_pooling1d_1", "trainable": true, "strides": [4], "pool_size": [4], "padding": "valid"}}, {"class_name": "LSTM", "config": {"name": "lstm_1", "trainable": true, "return_sequences": false, "return_state": false, "go_backwards": false, "stateful": false, "unroll": false, "units": 64, "activation": "tanh", "recurrent_activation": "hard_sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "recurrent_initializer": {"class_name": "Orthogonal", "config": {"gain": 1.0, "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "unit_forget_bias": true, "kernel_regularizer": null, "recurrent_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "recurrent_constraint": null, "bias_constraint": null, "dropout": 0.0, "recurrent_dropout": 0.0, "implementation": 1}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 210, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/ecg_demo/models/cnn-lstm-auto-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/cnn-lstm-auto-encoder-config.npy -------------------------------------------------------------------------------- /demo/ecg_demo/models/cnn-lstm-auto-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/cnn-lstm-auto-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/ecg_demo/models/con1d-auto-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "Conv1D", "config": {"name": "conv1d_1", "trainable": true, "batch_input_shape": [null, 210, 1], "dtype": "float32", "filters": 256, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "GlobalMaxPooling1D", "config": {"name": "global_max_pooling1d_1", "trainable": true}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 210, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/ecg_demo/models/con1d-auto-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/con1d-auto-encoder-config.npy -------------------------------------------------------------------------------- /demo/ecg_demo/models/con1d-auto-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/con1d-auto-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/ecg_demo/models/feedforward-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Model", "config": {"name": "model_1", "layers": [{"name": "input_1", "class_name": "InputLayer", "config": {"batch_input_shape": [null, 210], "dtype": "float32", "sparse": false, "name": "input_1"}, "inbound_nodes": []}, {"name": "dense_1", "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 14, "activation": "tanh", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": {"class_name": "L1L2", "config": {"l1": 9.999999747378752e-05, "l2": 0.0}}, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["input_1", 0, 0, {}]]]}, {"name": "dense_2", "class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "units": 7, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_1", 0, 0, {}]]]}, {"name": "dense_3", "class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "units": 7, "activation": "tanh", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_2", 0, 0, {}]]]}, {"name": "dense_4", "class_name": "Dense", "config": {"name": "dense_4", "trainable": true, "units": 210, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "inbound_nodes": [[["dense_3", 0, 0, {}]]]}], "input_layers": [["input_1", 0, 0]], "output_layers": [["dense_4", 0, 0]]}, "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/ecg_demo/models/feedforward-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/feedforward-encoder-config.npy -------------------------------------------------------------------------------- /demo/ecg_demo/models/feedforward-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/feedforward-encoder-weights.h5 -------------------------------------------------------------------------------- /demo/ecg_demo/models/lstm-auto-encoder-architecture.json: -------------------------------------------------------------------------------- 1 | {"class_name": "Sequential", "config": [{"class_name": "LSTM", "config": {"name": "lstm_1", "trainable": true, "batch_input_shape": [null, 210, 1], "dtype": "float32", "return_sequences": false, "return_state": false, "go_backwards": false, "stateful": false, "unroll": false, "units": 128, "activation": "tanh", "recurrent_activation": "hard_sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "recurrent_initializer": {"class_name": "Orthogonal", "config": {"gain": 1.0, "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "unit_forget_bias": true, "kernel_regularizer": null, "recurrent_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "recurrent_constraint": null, "bias_constraint": null, "dropout": 0.0, "recurrent_dropout": 0.0, "implementation": 1}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "units": 210, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}], "keras_version": "2.1.2", "backend": "tensorflow"} -------------------------------------------------------------------------------- /demo/ecg_demo/models/lstm-auto-encoder-config.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/lstm-auto-encoder-config.npy -------------------------------------------------------------------------------- /demo/ecg_demo/models/lstm-auto-encoder-weights.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/demo/ecg_demo/models/lstm-auto-encoder-weights.h5 -------------------------------------------------------------------------------- /keras_anomaly_detection/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/keras_anomaly_detection/__init__.py -------------------------------------------------------------------------------- /keras_anomaly_detection/library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/keras-anomaly-detection/4025365096493464c64ffb0227efcb7ff8fe4229/keras_anomaly_detection/library/__init__.py -------------------------------------------------------------------------------- /keras_anomaly_detection/library/convolutional.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Based on work by Xianshun Chen 3 | https://github.com/chen0040/keras-anomaly-detection 4 | ''' 5 | 6 | from keras.layers import Conv1D, GlobalMaxPool1D, Dense, Flatten 7 | from keras.models import Sequential 8 | from keras.callbacks import ModelCheckpoint 9 | import numpy as np 10 | 11 | 12 | class Conv1DAutoEncoder(object): 13 | model_name = 'con1d-auto-encoder' 14 | VERBOSE = 1 15 | 16 | def __init__(self): 17 | self.model = None 18 | self.time_window_size = None 19 | self.metric = None 20 | self.threshold = 5.0 21 | self.config = None 22 | 23 | @staticmethod 24 | def create_model(time_window_size, metric): 25 | model = Sequential() 26 | model.add(Conv1D(filters=256, kernel_size=5, padding='same', activation='relu', 27 | input_shape=(time_window_size, 1))) 28 | model.add(GlobalMaxPool1D()) 29 | 30 | model.add(Dense(units=time_window_size, activation='linear')) 31 | 32 | model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 33 | print(model.summary()) 34 | return model 35 | 36 | @staticmethod 37 | def get_config_file(model_dir_path): 38 | return model_dir_path + '/' + Conv1DAutoEncoder.model_name + '-config.npy' 39 | 40 | @staticmethod 41 | def get_weight_file(model_dir_path): 42 | return model_dir_path + '/' + Conv1DAutoEncoder.model_name + '-weights.h5' 43 | 44 | @staticmethod 45 | def get_architecture_file(model_dir_path): 46 | return model_dir_path + '/' + Conv1DAutoEncoder.model_name + '-architecture.json' 47 | 48 | def load_model(self, model_dir_path): 49 | config_file_path = self.get_config_file(model_dir_path) 50 | self.config = np.load(config_file_path).item() 51 | self.metric = self.config['metric'] 52 | self.time_window_size = self.config['time_window_size'] 53 | self.threshold = self.config['threshold'] 54 | self.model = self.create_model(self.time_window_size, self.metric) 55 | weight_file_path = self.get_weight_file(model_dir_path) 56 | self.model.load_weights(weight_file_path) 57 | 58 | def fit(self, dataset, model_dir_path, batch_size=8, epochs=100, validation_split=0.1, metric='mean_absolute_error', 59 | estimated_negative_sample_ratio=0.9): 60 | 61 | self.time_window_size = dataset.shape[1] 62 | self.metric = metric 63 | 64 | input_timeseries_dataset = np.expand_dims(dataset, axis=2) 65 | 66 | weight_file_path = self.get_weight_file(model_dir_path=model_dir_path) 67 | architecture_file_path = self.get_architecture_file(model_dir_path) 68 | checkpoint = ModelCheckpoint(weight_file_path) 69 | self.model = self.create_model(self.time_window_size, metric=self.metric) 70 | open(architecture_file_path, 'w').write(self.model.to_json()) 71 | history = self.model.fit(x=input_timeseries_dataset, y=dataset, 72 | batch_size=batch_size, epochs=epochs, 73 | verbose=self.VERBOSE, validation_split=validation_split, 74 | callbacks=[checkpoint]).history 75 | self.model.save_weights(weight_file_path) 76 | 77 | scores = self.predict(dataset) 78 | scores.sort() 79 | cut_point = int(estimated_negative_sample_ratio * len(scores)) 80 | self.threshold = scores[cut_point] 81 | 82 | print('estimated threshold is ' + str(self.threshold)) 83 | 84 | self.config = dict() 85 | self.config['time_window_size'] = self.time_window_size 86 | self.config['metric'] = self.metric 87 | self.config['threshold'] = self.threshold 88 | config_file_path = self.get_config_file(model_dir_path=model_dir_path) 89 | np.save(config_file_path, self.config) 90 | 91 | return history 92 | 93 | def predict(self, timeseries_dataset): 94 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 95 | target_timeseries_dataset = self.model.predict(x=input_timeseries_dataset) 96 | dist = np.linalg.norm(timeseries_dataset - target_timeseries_dataset, axis=-1) 97 | return dist 98 | 99 | def anomaly(self, timeseries_dataset, threshold=None): 100 | if threshold is not None: 101 | self.threshold = threshold 102 | 103 | dist = self.predict(timeseries_dataset) 104 | return zip(dist >= self.threshold, dist) 105 | -------------------------------------------------------------------------------- /keras_anomaly_detection/library/evaluation_utils.py: -------------------------------------------------------------------------------- 1 | from sklearn.metrics import average_precision_score, recall_score, precision_score, f1_score 2 | 3 | 4 | def report_evaluation_metrics(y_true, y_pred): 5 | average_precision = average_precision_score(y_true, y_pred) 6 | precision = precision_score(y_true, y_pred, labels=[0, 1], pos_label=1) 7 | recall = recall_score(y_true, y_pred, labels=[0, 1], pos_label=1) 8 | f1 = f1_score(y_true, y_pred, labels=[0, 1], pos_label=1) 9 | 10 | print('Average precision-recall score: {0:0.2f}'.format(average_precision)) 11 | print('Precision: {0:0.2f}'.format(precision)) 12 | print('Recall: {0:0.2f}'.format(recall)) 13 | print('F1: {0:0.2f}'.format(f1)) 14 | -------------------------------------------------------------------------------- /keras_anomaly_detection/library/feedforward.py: -------------------------------------------------------------------------------- 1 | from keras.models import Model, model_from_json 2 | from keras.layers import Input, Dense 3 | from keras.callbacks import ModelCheckpoint 4 | from keras import regularizers 5 | from sklearn.model_selection import train_test_split 6 | import os 7 | import numpy as np 8 | 9 | 10 | class FeedForwardAutoEncoder(object): 11 | model_name = 'feedforward-encoder' 12 | 13 | def __init__(self): 14 | self.model = None 15 | self.input_dim = None 16 | self.threshold = None 17 | self.config = None 18 | 19 | def load_model(self, model_dir_path): 20 | config_file_path = FeedForwardAutoEncoder.get_config_file_path(model_dir_path) 21 | self.config = np.load(config_file_path).item() 22 | self.input_dim = self.config['input_dim'] 23 | self.threshold = self.config['threshold'] 24 | 25 | architecture_file_path = FeedForwardAutoEncoder.get_architecture_file_path(model_dir_path) 26 | self.model = model_from_json(open(architecture_file_path, 'r').read()) 27 | weight_file_path = FeedForwardAutoEncoder.get_weight_file_path(model_dir_path) 28 | self.model.load_weights(weight_file_path) 29 | 30 | def create_model(self, input_dim): 31 | encoding_dim = 14 32 | input_layer = Input(shape=(input_dim,)) 33 | 34 | encoder = Dense(encoding_dim, activation="tanh", 35 | activity_regularizer=regularizers.l1(10e-5))(input_layer) 36 | encoder = Dense(encoding_dim // 2, activation="relu")(encoder) 37 | 38 | decoder = Dense(encoding_dim // 2, activation='tanh')(encoder) 39 | decoder = Dense(input_dim, activation='relu')(decoder) 40 | 41 | model = Model(inputs=input_layer, outputs=decoder) 42 | model.compile(optimizer='adam', 43 | loss='mean_squared_error', 44 | metrics=['accuracy']) 45 | 46 | return model 47 | 48 | @staticmethod 49 | def get_architecture_file_path(model_dir_path): 50 | return os.path.join(model_dir_path, FeedForwardAutoEncoder.model_name + '-architecture.json') 51 | 52 | @staticmethod 53 | def get_weight_file_path(model_dir_path): 54 | return os.path.join(model_dir_path, FeedForwardAutoEncoder.model_name + '-weights.h5') 55 | 56 | @staticmethod 57 | def get_config_file_path(model_dir_path): 58 | return os.path.join(model_dir_path, FeedForwardAutoEncoder.model_name + '-config.npy') 59 | 60 | def fit(self, data, model_dir_path, nb_epoch=None, batch_size=None, test_size=None, random_state=None, 61 | estimated_negative_sample_ratio=None): 62 | if test_size is None: 63 | test_size = 0.2 64 | if random_state is None: 65 | random_state = 42 66 | if nb_epoch is None: 67 | nb_epoch = 100 68 | if batch_size is None: 69 | batch_size = 32 70 | if estimated_negative_sample_ratio is None: 71 | estimated_negative_sample_ratio = 0.9 72 | 73 | weight_file_path = FeedForwardAutoEncoder.get_weight_file_path(model_dir_path) 74 | architecture_file_path = FeedForwardAutoEncoder.get_architecture_file_path(model_dir_path) 75 | 76 | X_train, X_test = train_test_split(data, test_size=test_size, random_state=random_state) 77 | checkpointer = ModelCheckpoint(filepath=weight_file_path, 78 | verbose=0, 79 | save_best_only=True) 80 | 81 | self.input_dim = X_train.shape[1] 82 | self.model = self.create_model(self.input_dim) 83 | open(architecture_file_path, 'w').write(self.model.to_json()) 84 | history = self.model.fit(X_train, X_train, 85 | epochs=nb_epoch, 86 | batch_size=batch_size, 87 | shuffle=True, 88 | validation_data=(X_test, X_test), 89 | verbose=1, 90 | callbacks=[checkpointer]).history 91 | 92 | self.model.save_weights(weight_file_path) 93 | 94 | scores = self.predict(data) 95 | scores.sort() 96 | cut_point = int(estimated_negative_sample_ratio * len(scores)) 97 | self.threshold = scores[cut_point] 98 | 99 | print('estimated threshold is ' + str(self.threshold)) 100 | 101 | self.config = dict() 102 | self.config['input_dim'] = self.input_dim 103 | self.config['threshold'] = self.threshold 104 | config_file_path = FeedForwardAutoEncoder.get_config_file_path(model_dir_path=model_dir_path) 105 | np.save(config_file_path, self.config) 106 | 107 | return history 108 | 109 | def predict(self, data): 110 | target_data = self.model.predict(x=data) 111 | dist = np.linalg.norm(data - target_data, axis=-1) 112 | return dist 113 | 114 | def anomaly(self, data, threshold=None): 115 | if threshold is not None: 116 | self.threshold = threshold 117 | 118 | dist = self.predict(data) 119 | return zip(dist >= self.threshold, dist) -------------------------------------------------------------------------------- /keras_anomaly_detection/library/plot_utils.py: -------------------------------------------------------------------------------- 1 | from matplotlib import pyplot as plt 2 | import seaborn as sns 3 | from sklearn.metrics import confusion_matrix 4 | import pandas as pd 5 | 6 | LABELS = ["Normal", "Fraud"] 7 | 8 | 9 | def plot_confusion_matrix(y_true, y_pred): 10 | conf_matrix = confusion_matrix(y_true, y_pred) 11 | 12 | plt.figure(figsize=(12, 12)) 13 | sns.heatmap(conf_matrix, xticklabels=LABELS, yticklabels=LABELS, annot=True, fmt="d") 14 | plt.title("Confusion matrix") 15 | plt.ylabel('True class') 16 | plt.xlabel('Predicted class') 17 | plt.show() 18 | 19 | 20 | def plot_training_history(history): 21 | if history is None: 22 | return 23 | plt.plot(history['loss']) 24 | plt.plot(history['val_loss']) 25 | plt.title('model loss') 26 | plt.ylabel('loss') 27 | plt.xlabel('epoch') 28 | plt.legend(['train', 'test'], loc='upper right') 29 | plt.show() 30 | 31 | 32 | def visualize_anomaly(y_true, reconstruction_error, threshold): 33 | error_df = pd.DataFrame({'reconstruction_error': reconstruction_error, 34 | 'true_class': y_true}) 35 | print(error_df.describe()) 36 | 37 | groups = error_df.groupby('true_class') 38 | fig, ax = plt.subplots() 39 | 40 | for name, group in groups: 41 | ax.plot(group.index, group.reconstruction_error, marker='o', ms=3.5, linestyle='', 42 | label="Fraud" if name == 1 else "Normal") 43 | 44 | ax.hlines(threshold, ax.get_xlim()[0], ax.get_xlim()[1], colors="r", zorder=100, label='Threshold') 45 | ax.legend() 46 | plt.title("Reconstruction error for different classes") 47 | plt.ylabel("Reconstruction error") 48 | plt.xlabel("Data point index") 49 | plt.show() 50 | 51 | 52 | def visualize_reconstruction_error(reconstruction_error, threshold): 53 | plt.plot(reconstruction_error, marker='o', ms=3.5, linestyle='', 54 | label='Point') 55 | 56 | plt.hlines(threshold, xmin=0, xmax=len(reconstruction_error)-1, colors="r", zorder=100, label='Threshold') 57 | plt.legend() 58 | plt.title("Reconstruction error") 59 | plt.ylabel("Reconstruction error") 60 | plt.xlabel("Data point index") 61 | plt.show() 62 | -------------------------------------------------------------------------------- /keras_anomaly_detection/library/recurrent.py: -------------------------------------------------------------------------------- 1 | from keras.layers import Conv1D, GlobalMaxPool1D, Dense, Flatten, LSTM, Bidirectional, RepeatVector, MaxPooling1D 2 | from keras.models import Sequential 3 | from keras.callbacks import ModelCheckpoint 4 | import numpy as np 5 | 6 | 7 | class LstmAutoEncoder(object): 8 | model_name = 'lstm-auto-encoder' 9 | VERBOSE = 1 10 | 11 | def __init__(self): 12 | self.model = None 13 | self.time_window_size = None 14 | self.config = None 15 | self.metric = None 16 | self.threshold = None 17 | 18 | @staticmethod 19 | def create_model(time_window_size, metric): 20 | model = Sequential() 21 | model.add(LSTM(units=128, input_shape=(time_window_size, 1), return_sequences=False)) 22 | 23 | model.add(Dense(units=time_window_size, activation='linear')) 24 | 25 | model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 26 | print(model.summary()) 27 | return model 28 | 29 | def load_model(self, model_dir_path): 30 | config_file_path = LstmAutoEncoder.get_config_file(model_dir_path) 31 | self.config = np.load(config_file_path).item() 32 | self.metric = self.config['metric'] 33 | self.time_window_size = self.config['time_window_size'] 34 | self.threshold = self.config['threshold'] 35 | self.model = LstmAutoEncoder.create_model(self.time_window_size, self.metric) 36 | weight_file_path = LstmAutoEncoder.get_weight_file(model_dir_path) 37 | self.model.load_weights(weight_file_path) 38 | 39 | @staticmethod 40 | def get_config_file(model_dir_path): 41 | return model_dir_path + '/' + LstmAutoEncoder.model_name + '-config.npy' 42 | 43 | @staticmethod 44 | def get_weight_file(model_dir_path): 45 | return model_dir_path + '/' + LstmAutoEncoder.model_name + '-weights.h5' 46 | 47 | @staticmethod 48 | def get_architecture_file(model_dir_path): 49 | return model_dir_path + '/' + LstmAutoEncoder.model_name + '-architecture.json' 50 | 51 | def fit(self, timeseries_dataset, model_dir_path, batch_size=None, epochs=None, validation_split=None, metric=None, 52 | estimated_negative_sample_ratio=None): 53 | if batch_size is None: 54 | batch_size = 8 55 | if epochs is None: 56 | epochs = 20 57 | if validation_split is None: 58 | validation_split = 0.2 59 | if metric is None: 60 | metric = 'mean_absolute_error' 61 | if estimated_negative_sample_ratio is None: 62 | estimated_negative_sample_ratio = 0.9 63 | 64 | self.metric = metric 65 | self.time_window_size = timeseries_dataset.shape[1] 66 | 67 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 68 | 69 | weight_file_path = LstmAutoEncoder.get_weight_file(model_dir_path=model_dir_path) 70 | architecture_file_path = LstmAutoEncoder.get_architecture_file(model_dir_path) 71 | checkpoint = ModelCheckpoint(weight_file_path) 72 | self.model = LstmAutoEncoder.create_model(self.time_window_size, metric=self.metric) 73 | open(architecture_file_path, 'w').write(self.model.to_json()) 74 | self.model.fit(x=input_timeseries_dataset, y=timeseries_dataset, 75 | batch_size=batch_size, epochs=epochs, 76 | verbose=LstmAutoEncoder.VERBOSE, validation_split=validation_split, 77 | callbacks=[checkpoint]) 78 | self.model.save_weights(weight_file_path) 79 | 80 | scores = self.predict(timeseries_dataset) 81 | scores.sort() 82 | cut_point = int(estimated_negative_sample_ratio * len(scores)) 83 | self.threshold = scores[cut_point] 84 | 85 | print('estimated threshold is ' + str(self.threshold)) 86 | 87 | self.config = dict() 88 | self.config['time_window_size'] = self.time_window_size 89 | self.config['metric'] = self.metric 90 | self.config['threshold'] = self.threshold 91 | config_file_path = LstmAutoEncoder.get_config_file(model_dir_path=model_dir_path) 92 | np.save(config_file_path, self.config) 93 | 94 | def predict(self, timeseries_dataset): 95 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 96 | target_timeseries_dataset = self.model.predict(x=input_timeseries_dataset) 97 | dist = np.linalg.norm(timeseries_dataset - target_timeseries_dataset, axis=-1) 98 | return dist 99 | 100 | def anomaly(self, timeseries_dataset, threshold=None): 101 | if threshold is not None: 102 | self.threshold = threshold 103 | 104 | dist = self.predict(timeseries_dataset) 105 | return zip(dist >= self.threshold, dist) 106 | 107 | 108 | class CnnLstmAutoEncoder(object): 109 | model_name = 'cnn-lstm-auto-encoder' 110 | VERBOSE = 1 111 | 112 | def __init__(self): 113 | self.model = None 114 | self.time_window_size = None 115 | self.config = None 116 | self.metric = None 117 | self.threshold = None 118 | 119 | @staticmethod 120 | def create_model(time_window_size, metric): 121 | model = Sequential() 122 | 123 | model.add(Conv1D(filters=256, kernel_size=5, padding='same', activation='relu', 124 | input_shape=(time_window_size, 1))) 125 | model.add(MaxPooling1D(pool_size=4)) 126 | 127 | model.add(LSTM(64)) 128 | 129 | model.add(Dense(units=time_window_size, activation='linear')) 130 | 131 | model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 132 | 133 | # model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 134 | # model.compile(optimizer="sgd", loss="mse", metrics=[metric]) 135 | 136 | print(model.summary()) 137 | return model 138 | 139 | def load_model(self, model_dir_path): 140 | config_file_path = CnnLstmAutoEncoder.get_config_file(model_dir_path) 141 | self.config = np.load(config_file_path).item() 142 | self.metric = self.config['metric'] 143 | self.time_window_size = self.config['time_window_size'] 144 | self.threshold = self.config['threshold'] 145 | self.model = CnnLstmAutoEncoder.create_model(self.time_window_size, self.metric) 146 | weight_file_path = CnnLstmAutoEncoder.get_weight_file(model_dir_path) 147 | self.model.load_weights(weight_file_path) 148 | 149 | @staticmethod 150 | def get_config_file(model_dir_path): 151 | return model_dir_path + '/' + CnnLstmAutoEncoder.model_name + '-config.npy' 152 | 153 | @staticmethod 154 | def get_weight_file(model_dir_path): 155 | return model_dir_path + '/' + CnnLstmAutoEncoder.model_name + '-weights.h5' 156 | 157 | @staticmethod 158 | def get_architecture_file(model_dir_path): 159 | return model_dir_path + '/' + CnnLstmAutoEncoder.model_name + '-architecture.json' 160 | 161 | def fit(self, timeseries_dataset, model_dir_path, batch_size=None, epochs=None, validation_split=None, metric=None, 162 | estimated_negative_sample_ratio=None): 163 | if batch_size is None: 164 | batch_size = 8 165 | if epochs is None: 166 | epochs = 20 167 | if validation_split is None: 168 | validation_split = 0.2 169 | if metric is None: 170 | metric = 'mean_absolute_error' 171 | if estimated_negative_sample_ratio is None: 172 | estimated_negative_sample_ratio = 0.9 173 | 174 | self.metric = metric 175 | self.time_window_size = timeseries_dataset.shape[1] 176 | 177 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 178 | 179 | weight_file_path = CnnLstmAutoEncoder.get_weight_file(model_dir_path=model_dir_path) 180 | architecture_file_path = CnnLstmAutoEncoder.get_architecture_file(model_dir_path) 181 | checkpoint = ModelCheckpoint(weight_file_path) 182 | self.model = CnnLstmAutoEncoder.create_model(self.time_window_size, metric=self.metric) 183 | open(architecture_file_path, 'w').write(self.model.to_json()) 184 | self.model.fit(x=input_timeseries_dataset, y=timeseries_dataset, 185 | batch_size=batch_size, epochs=epochs, 186 | verbose=CnnLstmAutoEncoder.VERBOSE, validation_split=validation_split, 187 | callbacks=[checkpoint]) 188 | self.model.save_weights(weight_file_path) 189 | 190 | scores = self.predict(timeseries_dataset) 191 | scores.sort() 192 | cut_point = int(estimated_negative_sample_ratio * len(scores)) 193 | self.threshold = scores[cut_point] 194 | 195 | print('estimated threshold is ' + str(self.threshold)) 196 | 197 | self.config = dict() 198 | self.config['time_window_size'] = self.time_window_size 199 | self.config['metric'] = self.metric 200 | self.config['threshold'] = self.threshold 201 | config_file_path = CnnLstmAutoEncoder.get_config_file(model_dir_path=model_dir_path) 202 | np.save(config_file_path, self.config) 203 | 204 | def predict(self, timeseries_dataset): 205 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 206 | target_timeseries_dataset = self.model.predict(x=input_timeseries_dataset) 207 | dist = np.linalg.norm(timeseries_dataset - target_timeseries_dataset, axis=-1) 208 | return dist 209 | 210 | def anomaly(self, timeseries_dataset, threshold=None): 211 | if threshold is not None: 212 | self.threshold = threshold 213 | 214 | dist = self.predict(timeseries_dataset) 215 | return zip(dist >= self.threshold, dist) 216 | 217 | 218 | class BidirectionalLstmAutoEncoder(object): 219 | model_name = 'bidirectional-lstm-auto-encoder' 220 | VERBOSE = 1 221 | 222 | def __init__(self): 223 | self.model = None 224 | self.time_window_size = None 225 | self.config = None 226 | self.metric = None 227 | self.threshold = None 228 | 229 | @staticmethod 230 | def create_model(time_window_size, metric): 231 | model = Sequential() 232 | 233 | model.add(Bidirectional(LSTM(units=64, dropout=0.2, recurrent_dropout=0.2), input_shape=(time_window_size, 1))) 234 | 235 | model.add(Dense(units=time_window_size, activation='linear')) 236 | 237 | model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 238 | 239 | # model.compile(optimizer='adam', loss='mean_squared_error', metrics=[metric]) 240 | # model.compile(optimizer="sgd", loss="mse", metrics=[metric]) 241 | 242 | print(model.summary()) 243 | return model 244 | 245 | def load_model(self, model_dir_path): 246 | config_file_path = BidirectionalLstmAutoEncoder.get_config_file(model_dir_path) 247 | self.config = np.load(config_file_path).item() 248 | self.metric = self.config['metric'] 249 | self.time_window_size = self.config['time_window_size'] 250 | self.threshold = self.config['threshold'] 251 | self.model = BidirectionalLstmAutoEncoder.create_model(self.time_window_size, self.metric) 252 | weight_file_path = BidirectionalLstmAutoEncoder.get_weight_file(model_dir_path) 253 | self.model.load_weights(weight_file_path) 254 | 255 | @staticmethod 256 | def get_config_file(model_dir_path): 257 | return model_dir_path + '/' + BidirectionalLstmAutoEncoder.model_name + '-config.npy' 258 | 259 | @staticmethod 260 | def get_weight_file(model_dir_path): 261 | return model_dir_path + '/' + BidirectionalLstmAutoEncoder.model_name + '-weights.h5' 262 | 263 | @staticmethod 264 | def get_architecture_file(model_dir_path): 265 | return model_dir_path + '/' + BidirectionalLstmAutoEncoder.model_name + '-architecture.json' 266 | 267 | def fit(self, timeseries_dataset, model_dir_path, batch_size=None, epochs=None, validation_split=None, metric=None, 268 | estimated_negative_sample_ratio=None): 269 | if batch_size is None: 270 | batch_size = 8 271 | if epochs is None: 272 | epochs = 20 273 | if validation_split is None: 274 | validation_split = 0.2 275 | if metric is None: 276 | metric = 'mean_absolute_error' 277 | if estimated_negative_sample_ratio is None: 278 | estimated_negative_sample_ratio = 0.9 279 | 280 | self.metric = metric 281 | self.time_window_size = timeseries_dataset.shape[1] 282 | 283 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 284 | 285 | weight_file_path = BidirectionalLstmAutoEncoder.get_weight_file(model_dir_path=model_dir_path) 286 | architecture_file_path = BidirectionalLstmAutoEncoder.get_architecture_file(model_dir_path) 287 | checkpoint = ModelCheckpoint(weight_file_path) 288 | self.model = BidirectionalLstmAutoEncoder.create_model(self.time_window_size, metric=self.metric) 289 | open(architecture_file_path, 'w').write(self.model.to_json()) 290 | self.model.fit(x=input_timeseries_dataset, y=timeseries_dataset, 291 | batch_size=batch_size, epochs=epochs, 292 | verbose=BidirectionalLstmAutoEncoder.VERBOSE, validation_split=validation_split, 293 | callbacks=[checkpoint]) 294 | self.model.save_weights(weight_file_path) 295 | 296 | scores = self.predict(timeseries_dataset) 297 | scores.sort() 298 | cut_point = int(estimated_negative_sample_ratio * len(scores)) 299 | self.threshold = scores[cut_point] 300 | 301 | print('estimated threshold is ' + str(self.threshold)) 302 | 303 | self.config = dict() 304 | self.config['time_window_size'] = self.time_window_size 305 | self.config['metric'] = self.metric 306 | self.config['threshold'] = self.threshold 307 | config_file_path = BidirectionalLstmAutoEncoder.get_config_file(model_dir_path=model_dir_path) 308 | np.save(config_file_path, self.config) 309 | 310 | def predict(self, timeseries_dataset): 311 | input_timeseries_dataset = np.expand_dims(timeseries_dataset, axis=2) 312 | target_timeseries_dataset = self.model.predict(x=input_timeseries_dataset) 313 | dist = np.linalg.norm(timeseries_dataset - target_timeseries_dataset, axis=-1) 314 | return dist 315 | 316 | def anomaly(self, timeseries_dataset, threshold=None): 317 | if threshold is not None: 318 | self.threshold = threshold 319 | 320 | dist = self.predict(timeseries_dataset) 321 | return zip(dist >= self.threshold, dist) -------------------------------------------------------------------------------- /notebooks/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | numpy 3 | seaborn 4 | matplotlib 5 | h2o 6 | keras 7 | tensorflow 8 | h5py 9 | scikit-learn -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages 2 | from setuptools import setup 3 | 4 | 5 | setup(name='keras_anomaly_detection', 6 | version='0.0.1', 7 | description='Anomaly Detector', 8 | author='Xianshun Chen', 9 | author_email='xs0040@gmail.com', 10 | url='https://github.com/chen0040/keras-anomaly-detection', 11 | download_url='https://github.com/chen0040/keras-anomaly-detection/tarball/0.0.1', 12 | license='MIT', 13 | install_requires=['Keras==2.1.2'], 14 | packages=find_packages()) 15 | --------------------------------------------------------------------------------