From 914bb020d09a17dc61830ec10e676c2bbba09f88 Mon Sep 17 00:00:00 2001 From: Jan Kowalczyk Date: Fri, 28 Jun 2024 07:42:12 +0200 Subject: [PATCH] added deepsad base code --- Deep-SAD-PyTorch/LICENSE | 21 ++ Deep-SAD-PyTorch/README.md | 139 ++++++++++ Deep-SAD-PyTorch/data/.gitkeep | 0 Deep-SAD-PyTorch/imgs/.gitkeep | 0 Deep-SAD-PyTorch/imgs/fig1.png | Bin 0 -> 319030 bytes Deep-SAD-PyTorch/log/.gitkeep | 0 Deep-SAD-PyTorch/requirements.txt | 18 ++ Deep-SAD-PyTorch/src/DeepSAD.py | 161 ++++++++++++ Deep-SAD-PyTorch/src/__init__.py | 0 Deep-SAD-PyTorch/src/base/__init__.py | 5 + Deep-SAD-PyTorch/src/base/base_dataset.py | 26 ++ Deep-SAD-PyTorch/src/base/base_net.py | 26 ++ Deep-SAD-PyTorch/src/base/base_trainer.py | 34 +++ Deep-SAD-PyTorch/src/base/odds_dataset.py | 110 ++++++++ .../src/base/torchvision_dataset.py | 17 ++ Deep-SAD-PyTorch/src/baseline_SemiDGM.py | 240 +++++++++++++++++ Deep-SAD-PyTorch/src/baseline_isoforest.py | 183 +++++++++++++ Deep-SAD-PyTorch/src/baseline_kde.py | 180 +++++++++++++ Deep-SAD-PyTorch/src/baseline_ocsvm.py | 174 +++++++++++++ Deep-SAD-PyTorch/src/baseline_ssad.py | 176 +++++++++++++ Deep-SAD-PyTorch/src/baselines/SemiDGM.py | 128 +++++++++ Deep-SAD-PyTorch/src/baselines/__init__.py | 6 + Deep-SAD-PyTorch/src/baselines/isoforest.py | 147 +++++++++++ Deep-SAD-PyTorch/src/baselines/kde.py | 164 ++++++++++++ Deep-SAD-PyTorch/src/baselines/ocsvm.py | 221 ++++++++++++++++ .../src/baselines/shallow_ssad/__init__.py | 1 + .../src/baselines/shallow_ssad/ssad_convex.py | 186 +++++++++++++ Deep-SAD-PyTorch/src/baselines/ssad.py | 244 ++++++++++++++++++ Deep-SAD-PyTorch/src/datasets/__init__.py | 6 + Deep-SAD-PyTorch/src/datasets/cifar10.py | 86 ++++++ Deep-SAD-PyTorch/src/datasets/fmnist.py | 85 ++++++ Deep-SAD-PyTorch/src/datasets/main.py | 54 ++++ Deep-SAD-PyTorch/src/datasets/mnist.py | 85 ++++++ Deep-SAD-PyTorch/src/datasets/odds.py | 47 ++++ .../src/datasets/preprocessing.py | 66 +++++ Deep-SAD-PyTorch/src/main.py | 239 +++++++++++++++++ Deep-SAD-PyTorch/src/networks/__init__.py | 10 + .../src/networks/cifar10_LeNet.py | 82 ++++++ Deep-SAD-PyTorch/src/networks/dgm.py | 123 +++++++++ Deep-SAD-PyTorch/src/networks/fmnist_LeNet.py | 76 ++++++ .../src/networks/inference/distributions.py | 41 +++ .../src/networks/layers/standard.py | 52 ++++ .../src/networks/layers/stochastic.py | 53 ++++ Deep-SAD-PyTorch/src/networks/main.py | 138 ++++++++++ Deep-SAD-PyTorch/src/networks/mlp.py | 76 ++++++ Deep-SAD-PyTorch/src/networks/mnist_LeNet.py | 71 +++++ Deep-SAD-PyTorch/src/networks/vae.py | 145 +++++++++++ Deep-SAD-PyTorch/src/optim/DeepSAD_trainer.py | 173 +++++++++++++ Deep-SAD-PyTorch/src/optim/SemiDGM_trainer.py | 188 ++++++++++++++ Deep-SAD-PyTorch/src/optim/__init__.py | 5 + Deep-SAD-PyTorch/src/optim/ae_trainer.py | 136 ++++++++++ Deep-SAD-PyTorch/src/optim/vae_trainer.py | 139 ++++++++++ Deep-SAD-PyTorch/src/optim/variational.py | 93 +++++++ Deep-SAD-PyTorch/src/utils/__init__.py | 3 + Deep-SAD-PyTorch/src/utils/config.py | 23 ++ Deep-SAD-PyTorch/src/utils/misc.py | 46 ++++ .../utils/visualization/plot_images_grid.py | 26 ++ 57 files changed, 4974 insertions(+) create mode 100644 Deep-SAD-PyTorch/LICENSE create mode 100644 Deep-SAD-PyTorch/README.md create mode 100644 Deep-SAD-PyTorch/data/.gitkeep create mode 100644 Deep-SAD-PyTorch/imgs/.gitkeep create mode 100644 Deep-SAD-PyTorch/imgs/fig1.png create mode 100644 Deep-SAD-PyTorch/log/.gitkeep create mode 100644 Deep-SAD-PyTorch/requirements.txt create mode 100644 Deep-SAD-PyTorch/src/DeepSAD.py create mode 100644 Deep-SAD-PyTorch/src/__init__.py create mode 100644 Deep-SAD-PyTorch/src/base/__init__.py create mode 100644 Deep-SAD-PyTorch/src/base/base_dataset.py create mode 100644 Deep-SAD-PyTorch/src/base/base_net.py create mode 100644 Deep-SAD-PyTorch/src/base/base_trainer.py create mode 100644 Deep-SAD-PyTorch/src/base/odds_dataset.py create mode 100644 Deep-SAD-PyTorch/src/base/torchvision_dataset.py create mode 100644 Deep-SAD-PyTorch/src/baseline_SemiDGM.py create mode 100644 Deep-SAD-PyTorch/src/baseline_isoforest.py create mode 100644 Deep-SAD-PyTorch/src/baseline_kde.py create mode 100644 Deep-SAD-PyTorch/src/baseline_ocsvm.py create mode 100644 Deep-SAD-PyTorch/src/baseline_ssad.py create mode 100644 Deep-SAD-PyTorch/src/baselines/SemiDGM.py create mode 100644 Deep-SAD-PyTorch/src/baselines/__init__.py create mode 100644 Deep-SAD-PyTorch/src/baselines/isoforest.py create mode 100644 Deep-SAD-PyTorch/src/baselines/kde.py create mode 100644 Deep-SAD-PyTorch/src/baselines/ocsvm.py create mode 100644 Deep-SAD-PyTorch/src/baselines/shallow_ssad/__init__.py create mode 100644 Deep-SAD-PyTorch/src/baselines/shallow_ssad/ssad_convex.py create mode 100644 Deep-SAD-PyTorch/src/baselines/ssad.py create mode 100644 Deep-SAD-PyTorch/src/datasets/__init__.py create mode 100644 Deep-SAD-PyTorch/src/datasets/cifar10.py create mode 100644 Deep-SAD-PyTorch/src/datasets/fmnist.py create mode 100644 Deep-SAD-PyTorch/src/datasets/main.py create mode 100644 Deep-SAD-PyTorch/src/datasets/mnist.py create mode 100644 Deep-SAD-PyTorch/src/datasets/odds.py create mode 100644 Deep-SAD-PyTorch/src/datasets/preprocessing.py create mode 100644 Deep-SAD-PyTorch/src/main.py create mode 100644 Deep-SAD-PyTorch/src/networks/__init__.py create mode 100644 Deep-SAD-PyTorch/src/networks/cifar10_LeNet.py create mode 100644 Deep-SAD-PyTorch/src/networks/dgm.py create mode 100644 Deep-SAD-PyTorch/src/networks/fmnist_LeNet.py create mode 100644 Deep-SAD-PyTorch/src/networks/inference/distributions.py create mode 100644 Deep-SAD-PyTorch/src/networks/layers/standard.py create mode 100644 Deep-SAD-PyTorch/src/networks/layers/stochastic.py create mode 100644 Deep-SAD-PyTorch/src/networks/main.py create mode 100644 Deep-SAD-PyTorch/src/networks/mlp.py create mode 100644 Deep-SAD-PyTorch/src/networks/mnist_LeNet.py create mode 100644 Deep-SAD-PyTorch/src/networks/vae.py create mode 100644 Deep-SAD-PyTorch/src/optim/DeepSAD_trainer.py create mode 100644 Deep-SAD-PyTorch/src/optim/SemiDGM_trainer.py create mode 100644 Deep-SAD-PyTorch/src/optim/__init__.py create mode 100644 Deep-SAD-PyTorch/src/optim/ae_trainer.py create mode 100644 Deep-SAD-PyTorch/src/optim/vae_trainer.py create mode 100644 Deep-SAD-PyTorch/src/optim/variational.py create mode 100644 Deep-SAD-PyTorch/src/utils/__init__.py create mode 100644 Deep-SAD-PyTorch/src/utils/config.py create mode 100644 Deep-SAD-PyTorch/src/utils/misc.py create mode 100644 Deep-SAD-PyTorch/src/utils/visualization/plot_images_grid.py diff --git a/Deep-SAD-PyTorch/LICENSE b/Deep-SAD-PyTorch/LICENSE new file mode 100644 index 0000000..15bd142 --- /dev/null +++ b/Deep-SAD-PyTorch/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 lukasruff + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Deep-SAD-PyTorch/README.md b/Deep-SAD-PyTorch/README.md new file mode 100644 index 0000000..705bd13 --- /dev/null +++ b/Deep-SAD-PyTorch/README.md @@ -0,0 +1,139 @@ +# Deep SAD: A Method for Deep Semi-Supervised Anomaly Detection +This repository provides a [PyTorch](https://pytorch.org/) implementation of the *Deep SAD* method presented in our ICLR 2020 paper ”Deep Semi-Supervised Anomaly Detection”. + + +## Citation and Contact +You find a PDF of the Deep Semi-Supervised Anomaly Detection ICLR 2020 paper on arXiv +[https://arxiv.org/abs/1906.02694](https://arxiv.org/abs/1906.02694). + +If you find our work useful, please also cite the paper: +``` +@InProceedings{ruff2020deep, + title = {Deep Semi-Supervised Anomaly Detection}, + author = {Ruff, Lukas and Vandermeulen, Robert A. and G{\"o}rnitz, Nico and Binder, Alexander and M{\"u}ller, Emmanuel and M{\"u}ller, Klaus-Robert and Kloft, Marius}, + booktitle = {International Conference on Learning Representations}, + year = {2020}, + url = {https://openreview.net/forum?id=HkgH0TEYwH} +} +``` + +If you would like get in touch, just drop us an email to [contact@lukasruff.com](mailto:contact@lukasruff.com). + + +## Abstract +> > Deep approaches to anomaly detection have recently shown promising results over shallow methods on large and complex datasets. Typically anomaly detection is treated as an unsupervised learning problem. In practice however, one may have---in addition to a large set of unlabeled samples---access to a small pool of labeled samples, e.g. a subset verified by some domain expert as being normal or anomalous. Semi-supervised approaches to anomaly detection aim to utilize such labeled samples, but most proposed methods are limited to merely including labeled normal samples. Only a few methods take advantage of labeled anomalies, with existing deep approaches being domain-specific. In this work we present Deep SAD, an end-to-end deep methodology for general semi-supervised anomaly detection. We further introduce an information-theoretic framework for deep anomaly detection based on the idea that the entropy of the latent distribution for normal data should be lower than the entropy of the anomalous distribution, which can serve as a theoretical interpretation for our method. In extensive experiments on MNIST, Fashion-MNIST, and CIFAR-10, along with other anomaly detection benchmark datasets, we demonstrate that our method is on par or outperforms shallow, hybrid, and deep competitors, yielding appreciable performance improvements even when provided with only little labeled data. + +## The need for semi-supervised anomaly detection + +![fig1](imgs/fig1.png?raw=true "fig1") + + +## Installation +This code is written in `Python 3.7` and requires the packages listed in `requirements.txt`. + +Clone the repository to your machine and directory of choice: +``` +git clone https://github.com/lukasruff/Deep-SAD-PyTorch.git +``` + +To run the code, we recommend setting up a virtual environment, e.g. using `virtualenv` or `conda`: + +### `virtualenv` +``` +# pip install virtualenv +cd +virtualenv myenv +source myenv/bin/activate +pip install -r requirements.txt +``` + +### `conda` +``` +cd +conda create --name myenv +source activate myenv +while read requirement; do conda install -n myenv --yes $requirement; done < requirements.txt +``` + + +## Running experiments +We have implemented the [`MNIST`](http://yann.lecun.com/exdb/mnist/), +[`Fashion-MNIST`](https://research.zalando.com/welcome/mission/research-projects/fashion-mnist/), and +[`CIFAR-10`](https://www.cs.toronto.edu/~kriz/cifar.html) datasets as well as the classic anomaly detection +benchmark datasets `arrhythmia`, `cardio`, `satellite`, `satimage-2`, `shuttle`, and `thyroid` from the +Outlier Detection DataSets (ODDS) repository ([http://odds.cs.stonybrook.edu/](http://odds.cs.stonybrook.edu/)) +as reported in the paper. + +The implemented network architectures are as reported in the appendix of the paper. + +### Deep SAD +You can run Deep SAD experiments using the `main.py` script. + +Here's an example on `MNIST` with `0` considered to be the normal class and having 1% labeled (known) training samples +from anomaly class `1` with a pollution ratio of 10% of the unlabeled training data (with unknown anomalies from all +anomaly classes `1`-`9`): +``` +cd + +# activate virtual environment +source myenv/bin/activate # or 'source activate myenv' for conda + +# create folders for experimental output +mkdir log/DeepSAD +mkdir log/DeepSAD/mnist_test + +# change to source directory +cd src + +# run experiment +python main.py mnist mnist_LeNet ../log/DeepSAD/mnist_test ../data --ratio_known_outlier 0.01 --ratio_pollution 0.1 --lr 0.0001 --n_epochs 150 --lr_milestone 50 --batch_size 128 --weight_decay 0.5e-6 --pretrain True --ae_lr 0.0001 --ae_n_epochs 150 --ae_batch_size 128 --ae_weight_decay 0.5e-3 --normal_class 0 --known_outlier_class 1 --n_known_outlier_classes 1; +``` +Have a look into `main.py` for all possible arguments and options. + +### Baselines +We also provide an implementation of the following baselines via the respective `baseline_.py` scripts: +OC-SVM (`ocsvm`), Isolation Forest (`isoforest`), Kernel Density Estimation (`kde`), kernel Semi-Supervised Anomaly +Detection (`ssad`), and Semi-Supervised Deep Generative Model (`SemiDGM`). + +Here's how to run SSAD for example on the same experimental setup as above: +``` +cd + +# activate virtual environment +source myenv/bin/activate # or 'source activate myenv' for conda + +# create folder for experimental output +mkdir log/ssad +mkdir log/ssad/mnist_test + +# change to source directory +cd src + +# run experiment +python baseline_ssad.py mnist ../log/ssad/mnist_test ../data --ratio_known_outlier 0.01 --ratio_pollution 0.1 --kernel rbf --kappa 1.0 --normal_class 0 --known_outlier_class 1 --n_known_outlier_classes 1; +``` + +The autoencoder is provided through Deep SAD pre-training using `--pretrain True` with `main.py`. +To then run a hybrid approach using one of the classic methods on top of autoencoder features, simply point to the saved +autoencoder model using `--load_ae ../log/DeepSAD/mnist_test/model.tar` and set `--hybrid True`. + +To run hybrid SSAD for example on the same experimental setup as above: +``` +cd + +# activate virtual environment +source myenv/bin/activate # or 'source activate myenv' for conda + +# create folder for experimental output +mkdir log/hybrid_ssad +mkdir log/hybrid_ssad/mnist_test + +# change to source directory +cd src + +# run experiment +python baseline_ssad.py mnist ../log/hybrid_ssad/mnist_test ../data --ratio_known_outlier 0.01 --ratio_pollution 0.1 --kernel rbf --kappa 1.0 --hybrid True --load_ae ../log/DeepSAD/mnist_test/model.tar --normal_class 0 --known_outlier_class 1 --n_known_outlier_classes 1; +``` + +## License +MIT diff --git a/Deep-SAD-PyTorch/data/.gitkeep b/Deep-SAD-PyTorch/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Deep-SAD-PyTorch/imgs/.gitkeep b/Deep-SAD-PyTorch/imgs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Deep-SAD-PyTorch/imgs/fig1.png b/Deep-SAD-PyTorch/imgs/fig1.png new file mode 100644 index 0000000000000000000000000000000000000000..2035aad1865a285638e4770bb0abcb33c401d366 GIT binary patch literal 319030 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%0D-GG{?X`Vd`(z39}oVH!2Iv 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

v%D5cw4%cgD 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�YbzjnjQBjZL5}h76(50bb~`HdZCc9@MsJ=y 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�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 ( + DataLoader, DataLoader): + """Implement data loaders of type torch.utils.data.DataLoader for train_set and test_set.""" + pass + + def __repr__(self): + return self.__class__.__name__ diff --git a/Deep-SAD-PyTorch/src/base/base_net.py b/Deep-SAD-PyTorch/src/base/base_net.py new file mode 100644 index 0000000..af90a9a --- /dev/null +++ b/Deep-SAD-PyTorch/src/base/base_net.py @@ -0,0 +1,26 @@ +import logging +import torch.nn as nn +import numpy as np + + +class BaseNet(nn.Module): + """Base class for all neural networks.""" + + def __init__(self): + super().__init__() + self.logger = logging.getLogger(self.__class__.__name__) + self.rep_dim = None # representation dimensionality, i.e. dim of the code layer or last layer + + def forward(self, *input): + """ + Forward pass logic + :return: Network output + """ + raise NotImplementedError + + def summary(self): + """Network summary.""" + net_parameters = filter(lambda p: p.requires_grad, self.parameters()) + params = sum([np.prod(p.size()) for p in net_parameters]) + self.logger.info('Trainable parameters: {}'.format(params)) + self.logger.info(self) diff --git a/Deep-SAD-PyTorch/src/base/base_trainer.py b/Deep-SAD-PyTorch/src/base/base_trainer.py new file mode 100644 index 0000000..530eee5 --- /dev/null +++ b/Deep-SAD-PyTorch/src/base/base_trainer.py @@ -0,0 +1,34 @@ +from abc import ABC, abstractmethod +from .base_dataset import BaseADDataset +from .base_net import BaseNet + + +class BaseTrainer(ABC): + """Trainer base class.""" + + def __init__(self, optimizer_name: str, lr: float, n_epochs: int, lr_milestones: tuple, batch_size: int, + weight_decay: float, device: str, n_jobs_dataloader: int): + super().__init__() + self.optimizer_name = optimizer_name + self.lr = lr + self.n_epochs = n_epochs + self.lr_milestones = lr_milestones + self.batch_size = batch_size + self.weight_decay = weight_decay + self.device = device + self.n_jobs_dataloader = n_jobs_dataloader + + @abstractmethod + def train(self, dataset: BaseADDataset, net: BaseNet) -> BaseNet: + """ + Implement train method that trains the given network using the train_set of dataset. + :return: Trained net + """ + pass + + @abstractmethod + def test(self, dataset: BaseADDataset, net: BaseNet): + """ + Implement test method that evaluates the test_set of dataset on the given network. + """ + pass diff --git a/Deep-SAD-PyTorch/src/base/odds_dataset.py b/Deep-SAD-PyTorch/src/base/odds_dataset.py new file mode 100644 index 0000000..a8c8340 --- /dev/null +++ b/Deep-SAD-PyTorch/src/base/odds_dataset.py @@ -0,0 +1,110 @@ +from pathlib import Path +from torch.utils.data import Dataset +from scipy.io import loadmat +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler, MinMaxScaler +from torchvision.datasets.utils import download_url + +import os +import torch +import numpy as np + + +class ODDSDataset(Dataset): + """ + ODDSDataset class for datasets from Outlier Detection DataSets (ODDS): http://odds.cs.stonybrook.edu/ + + Dataset class with additional targets for the semi-supervised setting and modification of __getitem__ method + to also return the semi-supervised target as well as the index of a data sample. + """ + + urls = { + 'arrhythmia': 'https://www.dropbox.com/s/lmlwuspn1sey48r/arrhythmia.mat?dl=1', + 'cardio': 'https://www.dropbox.com/s/galg3ihvxklf0qi/cardio.mat?dl=1', + 'satellite': 'https://www.dropbox.com/s/dpzxp8jyr9h93k5/satellite.mat?dl=1', + 'satimage-2': 'https://www.dropbox.com/s/hckgvu9m6fs441p/satimage-2.mat?dl=1', + 'shuttle': 'https://www.dropbox.com/s/mk8ozgisimfn3dw/shuttle.mat?dl=1', + 'thyroid': 'https://www.dropbox.com/s/bih0e15a0fukftb/thyroid.mat?dl=1' + } + + def __init__(self, root: str, dataset_name: str, train=True, random_state=None, download=False): + super(Dataset, self).__init__() + + self.classes = [0, 1] + + if isinstance(root, torch._six.string_classes): + root = os.path.expanduser(root) + self.root = Path(root) + self.dataset_name = dataset_name + self.train = train # training set or test set + self.file_name = self.dataset_name + '.mat' + self.data_file = self.root / self.file_name + + if download: + self.download() + + mat = loadmat(self.data_file) + X = mat['X'] + y = mat['y'].ravel() + idx_norm = y == 0 + idx_out = y == 1 + + # 60% data for training and 40% for testing; keep outlier ratio + X_train_norm, X_test_norm, y_train_norm, y_test_norm = train_test_split(X[idx_norm], y[idx_norm], + test_size=0.4, + random_state=random_state) + X_train_out, X_test_out, y_train_out, y_test_out = train_test_split(X[idx_out], y[idx_out], + test_size=0.4, + random_state=random_state) + X_train = np.concatenate((X_train_norm, X_train_out)) + X_test = np.concatenate((X_test_norm, X_test_out)) + y_train = np.concatenate((y_train_norm, y_train_out)) + y_test = np.concatenate((y_test_norm, y_test_out)) + + # Standardize data (per feature Z-normalization, i.e. zero-mean and unit variance) + scaler = StandardScaler().fit(X_train) + X_train_stand = scaler.transform(X_train) + X_test_stand = scaler.transform(X_test) + + # Scale to range [0,1] + minmax_scaler = MinMaxScaler().fit(X_train_stand) + X_train_scaled = minmax_scaler.transform(X_train_stand) + X_test_scaled = minmax_scaler.transform(X_test_stand) + + if self.train: + self.data = torch.tensor(X_train_scaled, dtype=torch.float32) + self.targets = torch.tensor(y_train, dtype=torch.int64) + else: + self.data = torch.tensor(X_test_scaled, dtype=torch.float32) + self.targets = torch.tensor(y_test, dtype=torch.int64) + + self.semi_targets = torch.zeros_like(self.targets) + + def __getitem__(self, index): + """ + Args: + index (int): Index + + Returns: + tuple: (sample, target, semi_target, index) + """ + sample, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[index]) + + return sample, target, semi_target, index + + def __len__(self): + return len(self.data) + + def _check_exists(self): + return os.path.exists(self.data_file) + + def download(self): + """Download the ODDS dataset if it doesn't exist in root already.""" + + if self._check_exists(): + return + + # download file + download_url(self.urls[self.dataset_name], self.root, self.file_name) + + print('Done!') diff --git a/Deep-SAD-PyTorch/src/base/torchvision_dataset.py b/Deep-SAD-PyTorch/src/base/torchvision_dataset.py new file mode 100644 index 0000000..82d468b --- /dev/null +++ b/Deep-SAD-PyTorch/src/base/torchvision_dataset.py @@ -0,0 +1,17 @@ +from .base_dataset import BaseADDataset +from torch.utils.data import DataLoader + + +class TorchvisionDataset(BaseADDataset): + """TorchvisionDataset class for datasets already implemented in torchvision.datasets.""" + + def __init__(self, root: str): + super().__init__(root) + + def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> ( + DataLoader, DataLoader): + train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train, + num_workers=num_workers, drop_last=True) + test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test, + num_workers=num_workers, drop_last=False) + return train_loader, test_loader diff --git a/Deep-SAD-PyTorch/src/baseline_SemiDGM.py b/Deep-SAD-PyTorch/src/baseline_SemiDGM.py new file mode 100644 index 0000000..554fbff --- /dev/null +++ b/Deep-SAD-PyTorch/src/baseline_SemiDGM.py @@ -0,0 +1,240 @@ +import click +import torch +import logging +import random +import numpy as np + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from baselines.SemiDGM import SemiDeepGenerativeModel +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('net_name', type=click.Choice(['mnist_DGM_M2', 'mnist_DGM_M1M2', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2', + 'cifar10_DGM_M2', 'cifar10_DGM_M1M2', + 'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2', + 'satimage-2_DGM_M2', 'shuttle_DGM_M2', 'thyroid_DGM_M2'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam', + help='Name of the optimizer to use for training the Semi-Supervised Deep Generative model.') +@click.option('--lr', type=float, default=0.001, + help='Initial learning rate for training. Default=0.001') +@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.') +@click.option('--lr_milestone', type=int, default=0, multiple=True, + help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') +@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.') +@click.option('--weight_decay', type=float, default=1e-6, + help='Weight decay (L2 penalty) hyperparameter.') +@click.option('--pretrain', type=bool, default=False, help='Pretrain a variational autoencoder.') +@click.option('--vae_optimizer_name', type=click.Choice(['adam']), default='adam', + help='Name of the optimizer to use for variational autoencoder pretraining.') +@click.option('--vae_lr', type=float, default=0.001, + help='Initial learning rate for pretraining. Default=0.001') +@click.option('--vae_n_epochs', type=int, default=100, help='Number of epochs to train the variational autoencoder.') +@click.option('--vae_lr_milestone', type=int, default=0, multiple=True, + help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') +@click.option('--vae_batch_size', type=int, default=128, help='Batch size for variational autoencoder training.') +@click.option('--vae_weight_decay', type=float, default=1e-6, + help='Weight decay (L2 penalty) hyperparameter for variational autoencoder.') +@click.option('--num_threads', type=int, default=0, + help='Number of threads used for parallelizing CPU operations. 0 means that all resources are used.') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, + ratio_pollution, device, seed, optimizer_name, lr, n_epochs, lr_milestone, batch_size, weight_decay, pretrain, + vae_optimizer_name, vae_lr, vae_n_epochs, vae_lr_milestone, vae_batch_size, vae_weight_decay, + num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): + """ + Semi-Supervised Deep Generative model (M1+M2 model) from Kingma et al. (2014) + + :arg DATASET_NAME: Name of the dataset to load. + :arg NET_NAME: Name of the neural network to use. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s' % log_file) + logger.info('Data path is %s' % data_path) + logger.info('Export path is %s' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + logger.info('Network: %s' % net_name) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Default device to 'cpu' if cuda is not available + if not torch.cuda.is_available(): + device = 'cpu' + # Set the number of threads used for parallelizing CPU operations + if num_threads > 0: + torch.set_num_threads(num_threads) + logger.info('Computation device: %s' % device) + logger.info('Number of threads: %d' % num_threads) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize semiDGM model and set neural network phi + alpha = 0.1 * (1 - ratio_known_normal - ratio_known_outlier) / (ratio_known_normal + ratio_known_outlier) + semiDGM = SemiDeepGenerativeModel(alpha=alpha) + + # If specified, load model + if load_model: + # Initialize networks + semiDGM.set_vae(net_name) + semiDGM.set_network(net_name) + # Load model + semiDGM.load_model(model_path=load_model) + logger.info('Loading model from %s.' % load_model) + + logger.info('Pretraining: %s' % pretrain) + if pretrain: + # Log pretraining details + logger.info('Pretraining optimizer: %s' % cfg.settings['vae_optimizer_name']) + logger.info('Pretraining learning rate: %g' % cfg.settings['vae_lr']) + logger.info('Pretraining epochs: %d' % cfg.settings['vae_n_epochs']) + logger.info('Pretraining learning rate scheduler milestones: %s' % (cfg.settings['vae_lr_milestone'],)) + logger.info('Pretraining batch size: %d' % cfg.settings['vae_batch_size']) + logger.info('Pretraining weight decay: %g' % cfg.settings['vae_weight_decay']) + + # Pretrain model on dataset (via variational autoencoder) + semiDGM.set_vae(net_name) + semiDGM.pretrain(dataset, + optimizer_name=cfg.settings['vae_optimizer_name'], + lr=cfg.settings['vae_lr'], + n_epochs=cfg.settings['vae_n_epochs'], + lr_milestones=cfg.settings['vae_lr_milestone'], + batch_size=cfg.settings['vae_batch_size'], + weight_decay=cfg.settings['vae_weight_decay'], + device=device, + n_jobs_dataloader=n_jobs_dataloader) + + # Save pretraining results + semiDGM.save_vae_results(export_json=xp_path + '/vae_results.json') + + # Log training details + logger.info('Training optimizer: %s' % cfg.settings['optimizer_name']) + logger.info('Training learning rate: %g' % cfg.settings['lr']) + logger.info('Training epochs: %d' % cfg.settings['n_epochs']) + logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],)) + logger.info('Training batch size: %d' % cfg.settings['batch_size']) + logger.info('Training weight decay: %g' % cfg.settings['weight_decay']) + + # Train model on dataset + semiDGM.set_network(net_name) + semiDGM.train(dataset, + optimizer_name=cfg.settings['optimizer_name'], + lr=cfg.settings['lr'], + n_epochs=cfg.settings['n_epochs'], + lr_milestones=cfg.settings['lr_milestone'], + batch_size=cfg.settings['batch_size'], + weight_decay=cfg.settings['weight_decay'], + device=device, + n_jobs_dataloader=n_jobs_dataloader) + + # Test model + semiDGM.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results, model, and configuration + semiDGM.save_results(export_json=xp_path + '/results.json') + semiDGM.save_model(export_model=xp_path + '/model.tar') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*semiDGM.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0,3,1,2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2))) + X_normal_high = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0,3,1,2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/baseline_isoforest.py b/Deep-SAD-PyTorch/src/baseline_isoforest.py new file mode 100644 index 0000000..1b25d6e --- /dev/null +++ b/Deep-SAD-PyTorch/src/baseline_isoforest.py @@ -0,0 +1,183 @@ +import click +import torch +import logging +import random +import numpy as np + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from baselines.isoforest import IsoForest +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--n_estimators', type=int, default=100, + help='Set the number of base estimators in the ensemble (default: 100).') +@click.option('--max_samples', type=int, default=256, + help='Set the number of samples drawn to train each base estimator (default: 256).') +@click.option('--contamination', type=float, default=0.1, + help='Expected fraction of anomalies in the training set. (default: 0.1).') +@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.') +@click.option('--hybrid', type=bool, default=False, + help='Train model on features extracted from an autoencoder. If True, load_ae must be specified.') +@click.option('--load_ae', type=click.Path(exists=True), default=None, + help='Model file path to load autoencoder weights (default: None).') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, + ratio_pollution, seed, n_estimators, max_samples, contamination, n_jobs_model, hybrid, load_ae, + n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): + """ + (Hybrid) Isolation Forest model for anomaly detection. + + :arg DATASET_NAME: Name of the dataset to load. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s.' % log_file) + logger.info('Data path is %s.' % data_path) + logger.info('Export path is %s.' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Print Isolation Forest configuration + logger.info('Number of base estimators in the ensemble: %d' % cfg.settings['n_estimators']) + logger.info('Number of samples for training each base estimator: %d' % cfg.settings['max_samples']) + logger.info('Contamination parameter: %.2f' % cfg.settings['contamination']) + logger.info('Number of jobs for model training: %d' % n_jobs_model) + logger.info('Hybrid model: %s' % cfg.settings['hybrid']) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Use 'cpu' as device for Isolation Forest + device = 'cpu' + torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu + logger.info('Computation device: %s' % device) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize Isolation Forest model + Isoforest = IsoForest(hybrid=cfg.settings['hybrid'], n_estimators=cfg.settings['n_estimators'], + max_samples=cfg.settings['max_samples'], contamination=cfg.settings['contamination'], + n_jobs=n_jobs_model, seed=cfg.settings['seed']) + + # If specified, load model parameters from already trained model + if load_model: + Isoforest.load_model(import_path=load_model, device=device) + logger.info('Loading model from %s.' % load_model) + + # If specified, load model autoencoder weights for a hybrid approach + if hybrid and load_ae is not None: + Isoforest.load_ae(dataset_name, model_path=load_ae) + logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) + + # Train model on dataset + Isoforest.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Test model + Isoforest.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results and configuration + Isoforest.save_results(export_json=xp_path + '/results.json') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*Isoforest.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) + X_normal_high = torch.tensor( + np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/baseline_kde.py b/Deep-SAD-PyTorch/src/baseline_kde.py new file mode 100644 index 0000000..f89e633 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baseline_kde.py @@ -0,0 +1,180 @@ +import click +import torch +import logging +import random +import numpy as np + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from baselines.kde import KDE +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--kernel', type=click.Choice(['gaussian', 'tophat', 'epanechnikov', 'exponential', 'linear', 'cosine']), + default='gaussian', help='Kernel for the KDE') +@click.option('--grid_search_cv', type=bool, default=True, + help='Use sklearn GridSearchCV to determine optimal bandwidth') +@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.') +@click.option('--hybrid', type=bool, default=False, + help='Train KDE on features extracted from an autoencoder. If True, load_ae must be specified.') +@click.option('--load_ae', type=click.Path(exists=True), default=None, + help='Model file path to load autoencoder weights (default: None).') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, + ratio_pollution, seed, kernel, grid_search_cv, n_jobs_model, hybrid, load_ae, n_jobs_dataloader, normal_class, + known_outlier_class, n_known_outlier_classes): + """ + (Hybrid) KDE for anomaly detection. + + :arg DATASET_NAME: Name of the dataset to load. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s.' % log_file) + logger.info('Data path is %s.' % data_path) + logger.info('Export path is %s.' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Print KDE configuration + logger.info('KDE kernel: %s' % cfg.settings['kernel']) + logger.info('Use GridSearchCV for bandwidth selection: %s' % cfg.settings['grid_search_cv']) + logger.info('Number of jobs for model training: %d' % n_jobs_model) + logger.info('Hybrid model: %s' % cfg.settings['hybrid']) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Use 'cpu' as device for KDE + device = 'cpu' + torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu + logger.info('Computation device: %s' % device) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize KDE model + kde = KDE(hybrid=cfg.settings['hybrid'], kernel=cfg.settings['kernel'], n_jobs=n_jobs_model, + seed=cfg.settings['seed']) + + # If specified, load model parameters from already trained model + if load_model: + kde.load_model(import_path=load_model, device=device) + logger.info('Loading model from %s.' % load_model) + + # If specified, load model autoencoder weights for a hybrid approach + if hybrid and load_ae is not None: + kde.load_ae(dataset_name, model_path=load_ae) + logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) + + # Train model on dataset + kde.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader, + bandwidth_GridSearchCV=cfg.settings['grid_search_cv']) + + # Test model + kde.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results and configuration + kde.save_results(export_json=xp_path + '/results.json') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*kde.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) + X_normal_high = torch.tensor( + np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/baseline_ocsvm.py b/Deep-SAD-PyTorch/src/baseline_ocsvm.py new file mode 100644 index 0000000..4110366 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baseline_ocsvm.py @@ -0,0 +1,174 @@ +import click +import torch +import logging +import random +import numpy as np + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from baselines.ocsvm import OCSVM +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--kernel', type=click.Choice(['rbf', 'linear', 'poly']), default='rbf', help='Kernel for the OC-SVM') +@click.option('--nu', type=float, default=0.1, help='OC-SVM hyperparameter nu (must be 0 < nu <= 1).') +@click.option('--hybrid', type=bool, default=False, + help='Train OC-SVM on features extracted from an autoencoder. If True, load_ae must be specified.') +@click.option('--load_ae', type=click.Path(exists=True), default=None, + help='Model file path to load autoencoder weights (default: None).') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, + ratio_pollution, seed, kernel, nu, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class, + n_known_outlier_classes): + """ + (Hybrid) One-Class SVM for anomaly detection. + + :arg DATASET_NAME: Name of the dataset to load. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s.' % log_file) + logger.info('Data path is %s.' % data_path) + logger.info('Export path is %s.' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Print OC-SVM configuration + logger.info('OC-SVM kernel: %s' % cfg.settings['kernel']) + logger.info('Nu-paramerter: %.2f' % cfg.settings['nu']) + logger.info('Hybrid model: %s' % cfg.settings['hybrid']) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Use 'cpu' as device for OC-SVM + device = 'cpu' + torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu + logger.info('Computation device: %s' % device) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize OC-SVM model + ocsvm = OCSVM(cfg.settings['kernel'], cfg.settings['nu'], cfg.settings['hybrid']) + + # If specified, load model parameters from already trained model + if load_model: + ocsvm.load_model(import_path=load_model, device=device) + logger.info('Loading model from %s.' % load_model) + + # If specified, load model autoencoder weights for a hybrid approach + if hybrid and load_ae is not None: + ocsvm.load_ae(dataset_name, model_path=load_ae) + logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) + + # Train model on dataset + ocsvm.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Test model + ocsvm.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results and configuration + ocsvm.save_results(export_json=xp_path + '/results.json') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*ocsvm.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) + X_normal_high = torch.tensor( + np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/baseline_ssad.py b/Deep-SAD-PyTorch/src/baseline_ssad.py new file mode 100644 index 0000000..3c16eb3 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baseline_ssad.py @@ -0,0 +1,176 @@ +import click +import torch +import logging +import random +import numpy as np +import cvxopt as co + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from baselines.ssad import SSAD +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--kernel', type=click.Choice(['rbf']), default='rbf', help='Kernel for SSAD') +@click.option('--kappa', type=float, default=1.0, help='SSAD hyperparameter kappa.') +@click.option('--hybrid', type=bool, default=False, + help='Train SSAD on features extracted from an autoencoder. If True, load_ae must be specified') +@click.option('--load_ae', type=click.Path(exists=True), default=None, + help='Model file path to load autoencoder weights (default: None).') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, + ratio_pollution, seed, kernel, kappa, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class, + n_known_outlier_classes): + """ + (Hybrid) SSAD for anomaly detection as in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013. + + :arg DATASET_NAME: Name of the dataset to load. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s.' % log_file) + logger.info('Data path is %s.' % data_path) + logger.info('Export path is %s.' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Print SSAD configuration + logger.info('SSAD kernel: %s' % cfg.settings['kernel']) + logger.info('Kappa-paramerter: %.2f' % cfg.settings['kappa']) + logger.info('Hybrid model: %s' % cfg.settings['hybrid']) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + co.setseed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Use 'cpu' as device for SSAD + device = 'cpu' + torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu + logger.info('Computation device: %s' % device) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize SSAD model + ssad = SSAD(kernel=cfg.settings['kernel'], kappa=cfg.settings['kappa'], hybrid=cfg.settings['hybrid']) + + # If specified, load model parameters from already trained model + if load_model: + ssad.load_model(import_path=load_model, device=device) + logger.info('Loading model from %s.' % load_model) + + # If specified, load model autoencoder weights for a hybrid approach + if hybrid and load_ae is not None: + ssad.load_ae(dataset_name, model_path=load_ae) + logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) + + # Train model on dataset + ssad.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Test model + ssad.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results and configuration + ssad.save_results(export_json=xp_path + '/results.json') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*ssad.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) + X_normal_high = torch.tensor( + np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/baselines/SemiDGM.py b/Deep-SAD-PyTorch/src/baselines/SemiDGM.py new file mode 100644 index 0000000..a05a4ef --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/SemiDGM.py @@ -0,0 +1,128 @@ +import json +import torch + +from base.base_dataset import BaseADDataset +from networks.main import build_network, build_autoencoder +from optim import SemiDeepGenerativeTrainer, VAETrainer + + +class SemiDeepGenerativeModel(object): + """A class for the Semi-Supervised Deep Generative model (M1+M2 model). + + Paper: Kingma et al. (2014). Semi-supervised learning with deep generative models. In NIPS (pp. 3581-3589). + Link: https://papers.nips.cc/paper/5352-semi-supervised-learning-with-deep-generative-models.pdf + + Attributes: + net_name: A string indicating the name of the neural network to use. + net: The neural network. + trainer: SemiDeepGenerativeTrainer to train a Semi-Supervised Deep Generative model. + optimizer_name: A string indicating the optimizer to use for training. + results: A dictionary to save the results. + """ + + def __init__(self, alpha: float = 0.1): + """Inits SemiDeepGenerativeModel.""" + + self.alpha = alpha + + self.net_name = None + self.net = None + + self.trainer = None + self.optimizer_name = None + + self.vae_net = None # variational autoencoder network for pretraining + self.vae_trainer = None + self.vae_optimizer_name = None + + self.results = { + 'train_time': None, + 'test_auc': None, + 'test_time': None, + 'test_scores': None, + } + + self.vae_results = { + 'train_time': None, + 'test_auc': None, + 'test_time': None + } + + def set_vae(self, net_name): + """Builds the variational autoencoder network for pretraining.""" + self.net_name = net_name + self.vae_net = build_autoencoder(self.net_name) # VAE for pretraining + + def set_network(self, net_name): + """Builds the neural network.""" + self.net_name = net_name + self.net = build_network(net_name, ae_net=self.vae_net) # full M1+M2 model + + def train(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 50, + lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', + n_jobs_dataloader: int = 0): + """Trains the Semi-Supervised Deep Generative model on the training data.""" + + self.optimizer_name = optimizer_name + + self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, optimizer_name=optimizer_name, lr=lr, + n_epochs=n_epochs, lr_milestones=lr_milestones, batch_size=batch_size, + weight_decay=weight_decay, device=device, + n_jobs_dataloader=n_jobs_dataloader) + self.net = self.trainer.train(dataset, self.net) + self.results['train_time'] = self.trainer.train_time + + def test(self, dataset: BaseADDataset, device: str = 'cuda', n_jobs_dataloader: int = 0): + """Tests the Semi-Supervised Deep Generative model on the test data.""" + + if self.trainer is None: + self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, device=device, + n_jobs_dataloader=n_jobs_dataloader) + + self.trainer.test(dataset, self.net) + # Get results + self.results['test_auc'] = self.trainer.test_auc + self.results['test_time'] = self.trainer.test_time + self.results['test_scores'] = self.trainer.test_scores + + def pretrain(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 100, + lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', + n_jobs_dataloader: int = 0): + """Pretrains a variational autoencoder (M1) for the Semi-Supervised Deep Generative model.""" + + # Train + self.vae_optimizer_name = optimizer_name + self.vae_trainer = VAETrainer(optimizer_name=optimizer_name, lr=lr, n_epochs=n_epochs, + lr_milestones=lr_milestones, batch_size=batch_size, weight_decay=weight_decay, + device=device, n_jobs_dataloader=n_jobs_dataloader) + self.vae_net = self.vae_trainer.train(dataset, self.vae_net) + # Get train results + self.vae_results['train_time'] = self.vae_trainer.train_time + + # Test + self.vae_trainer.test(dataset, self.vae_net) + # Get test results + self.vae_results['test_auc'] = self.vae_trainer.test_auc + self.vae_results['test_time'] = self.vae_trainer.test_time + + def save_model(self, export_model): + """Save a Semi-Supervised Deep Generative model to export_model.""" + + net_dict = self.net.state_dict() + torch.save({'net_dict': net_dict}, export_model) + + def load_model(self, model_path): + """Load a Semi-Supervised Deep Generative model from model_path.""" + + model_dict = torch.load(model_path) + self.net.load_state_dict(model_dict['net_dict']) + + def save_results(self, export_json): + """Save results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.results, fp) + + def save_vae_results(self, export_json): + """Save variational autoencoder results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.vae_results, fp) diff --git a/Deep-SAD-PyTorch/src/baselines/__init__.py b/Deep-SAD-PyTorch/src/baselines/__init__.py new file mode 100644 index 0000000..34383e2 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/__init__.py @@ -0,0 +1,6 @@ +from .SemiDGM import SemiDeepGenerativeModel +from .ocsvm import OCSVM +from .kde import KDE +from .isoforest import IsoForest +from .ssad import SSAD +from .shallow_ssad.ssad_convex import ConvexSSAD diff --git a/Deep-SAD-PyTorch/src/baselines/isoforest.py b/Deep-SAD-PyTorch/src/baselines/isoforest.py new file mode 100644 index 0000000..cca71e5 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/isoforest.py @@ -0,0 +1,147 @@ +import json +import logging +import time +import torch +import numpy as np + +from torch.utils.data import DataLoader +from sklearn.ensemble import IsolationForest +from sklearn.metrics import roc_auc_score +from base.base_dataset import BaseADDataset +from networks.main import build_autoencoder + + +class IsoForest(object): + """A class for Isolation Forest models.""" + + def __init__(self, hybrid=False, n_estimators=100, max_samples='auto', contamination=0.1, n_jobs=-1, seed=None, + **kwargs): + """Init Isolation Forest instance.""" + self.n_estimators = n_estimators + self.max_samples = max_samples + self.contamination = contamination + self.n_jobs = n_jobs + self.seed = seed + + self.model = IsolationForest(n_estimators=n_estimators, max_samples=max_samples, contamination=contamination, + n_jobs=n_jobs, random_state=seed, **kwargs) + + self.hybrid = hybrid + self.ae_net = None # autoencoder network for the case of a hybrid model + + self.results = { + 'train_time': None, + 'test_time': None, + 'test_auc': None, + 'test_scores': None + } + + def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Trains the Isolation Forest model on the training data.""" + logger = logging.getLogger() + + # do not drop last batch for non-SGD optimization shallow_ssad + train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, + num_workers=n_jobs_dataloader, drop_last=False) + + # Get data from loader + X = () + for data in train_loader: + inputs, _, _, _ = data + inputs = inputs.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + X = np.concatenate(X) + + # Training + logger.info('Starting training...') + start_time = time.time() + self.model.fit(X) + train_time = time.time() - start_time + self.results['train_time'] = train_time + + logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) + logger.info('Finished training.') + + def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Tests the Isolation Forest model on the test data.""" + logger = logging.getLogger() + + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + # Get data from loader + idx_label_score = [] + X = () + idxs = [] + labels = [] + for data in test_loader: + inputs, label_batch, _, idx = data + inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + idxs += idx.cpu().data.numpy().astype(np.int64).tolist() + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X = np.concatenate(X) + + # Testing + logger.info('Starting testing...') + start_time = time.time() + scores = (-1.0) * self.model.decision_function(X) + self.results['test_time'] = time.time() - start_time + scores = scores.flatten() + + # Save triples of (idx, label, score) in a list + idx_label_score += list(zip(idxs, labels, scores.tolist())) + self.results['test_scores'] = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.results['test_auc'] = roc_auc_score(labels, scores) + + # Log results + logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) + logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) + logger.info('Finished testing.') + + def load_ae(self, dataset_name, model_path): + """Load pretrained autoencoder from model_path for feature extraction in a hybrid Isolation Forest model.""" + + model_dict = torch.load(model_path, map_location='cpu') + ae_net_dict = model_dict['ae_net_dict'] + if dataset_name in ['mnist', 'fmnist', 'cifar10']: + net_name = dataset_name + '_LeNet' + else: + net_name = dataset_name + '_mlp' + + if self.ae_net is None: + self.ae_net = build_autoencoder(net_name) + + # update keys (since there was a change in network definition) + ae_keys = list(self.ae_net.state_dict().keys()) + for i in range(len(ae_net_dict)): + k, v = ae_net_dict.popitem(False) + new_key = ae_keys[i] + ae_net_dict[new_key] = v + i += 1 + + self.ae_net.load_state_dict(ae_net_dict) + self.ae_net.eval() + + def save_model(self, export_path): + """Save Isolation Forest model to export_path.""" + pass + + def load_model(self, import_path, device: str = 'cpu'): + """Load Isolation Forest model from import_path.""" + pass + + def save_results(self, export_json): + """Save results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.results, fp) diff --git a/Deep-SAD-PyTorch/src/baselines/kde.py b/Deep-SAD-PyTorch/src/baselines/kde.py new file mode 100644 index 0000000..05a895a --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/kde.py @@ -0,0 +1,164 @@ +import json +import logging +import time +import torch +import numpy as np + +from torch.utils.data import DataLoader +from sklearn.neighbors import KernelDensity +from sklearn.metrics import roc_auc_score +from sklearn.metrics.pairwise import pairwise_distances +from sklearn.model_selection import GridSearchCV +from base.base_dataset import BaseADDataset +from networks.main import build_autoencoder + + +class KDE(object): + """A class for Kernel Density Estimation models.""" + + def __init__(self, hybrid=False, kernel='gaussian', n_jobs=-1, seed=None, **kwargs): + """Init Kernel Density Estimation instance.""" + self.kernel = kernel + self.n_jobs = n_jobs + self.seed = seed + + self.model = KernelDensity(kernel=kernel, **kwargs) + self.bandwidth = self.model.bandwidth + + self.hybrid = hybrid + self.ae_net = None # autoencoder network for the case of a hybrid model + + self.results = { + 'train_time': None, + 'test_time': None, + 'test_auc': None, + 'test_scores': None + } + + def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0, + bandwidth_GridSearchCV: bool = True): + """Trains the Kernel Density Estimation model on the training data.""" + logger = logging.getLogger() + + # do not drop last batch for non-SGD optimization shallow_ssad + train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, + num_workers=n_jobs_dataloader, drop_last=False) + + # Get data from loader + X = () + for data in train_loader: + inputs, _, _, _ = data + inputs = inputs.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + X = np.concatenate(X) + + # Training + logger.info('Starting training...') + start_time = time.time() + + if bandwidth_GridSearchCV: + # use grid search cross-validation to select bandwidth + logger.info('Using GridSearchCV for bandwidth selection...') + params = {'bandwidth': np.logspace(0.5, 5, num=10, base=2)} + hyper_kde = GridSearchCV(KernelDensity(kernel=self.kernel), params, n_jobs=self.n_jobs, cv=5, verbose=0) + hyper_kde.fit(X) + self.bandwidth = hyper_kde.best_estimator_.bandwidth + logger.info('Best bandwidth: {:.8f}'.format(self.bandwidth)) + self.model = hyper_kde.best_estimator_ + else: + # if exponential kernel, re-initialize kde with bandwidth minimizing the numerical error + if self.kernel == 'exponential': + self.bandwidth = np.max(pairwise_distances(X)) ** 2 + self.model = KernelDensity(kernel=self.kernel, bandwidth=self.bandwidth) + + self.model.fit(X) + + train_time = time.time() - start_time + self.results['train_time'] = train_time + + logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) + logger.info('Finished training.') + + def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Tests the Kernel Density Estimation model on the test data.""" + logger = logging.getLogger() + + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + # Get data from loader + idx_label_score = [] + X = () + idxs = [] + labels = [] + for data in test_loader: + inputs, label_batch, _, idx = data + inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + idxs += idx.cpu().data.numpy().astype(np.int64).tolist() + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X = np.concatenate(X) + + # Testing + logger.info('Starting testing...') + start_time = time.time() + scores = (-1.0) * self.model.score_samples(X) + self.results['test_time'] = time.time() - start_time + scores = scores.flatten() + + # Save triples of (idx, label, score) in a list + idx_label_score += list(zip(idxs, labels, scores.tolist())) + self.results['test_scores'] = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.results['test_auc'] = roc_auc_score(labels, scores) + + # Log results + logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) + logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) + logger.info('Finished testing.') + + def load_ae(self, dataset_name, model_path): + """Load pretrained autoencoder from model_path for feature extraction in a hybrid KDE model.""" + + model_dict = torch.load(model_path, map_location='cpu') + ae_net_dict = model_dict['ae_net_dict'] + if dataset_name in ['mnist', 'fmnist', 'cifar10']: + net_name = dataset_name + '_LeNet' + else: + net_name = dataset_name + '_mlp' + + if self.ae_net is None: + self.ae_net = build_autoencoder(net_name) + + # update keys (since there was a change in network definition) + ae_keys = list(self.ae_net.state_dict().keys()) + for i in range(len(ae_net_dict)): + k, v = ae_net_dict.popitem(False) + new_key = ae_keys[i] + ae_net_dict[new_key] = v + i += 1 + + self.ae_net.load_state_dict(ae_net_dict) + self.ae_net.eval() + + def save_model(self, export_path): + """Save KDE model to export_path.""" + pass + + def load_model(self, import_path, device: str = 'cpu'): + """Load KDE model from import_path.""" + pass + + def save_results(self, export_json): + """Save results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.results, fp) diff --git a/Deep-SAD-PyTorch/src/baselines/ocsvm.py b/Deep-SAD-PyTorch/src/baselines/ocsvm.py new file mode 100644 index 0000000..c8e5e2e --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/ocsvm.py @@ -0,0 +1,221 @@ +import json +import logging +import time +import torch +import numpy as np + +from torch.utils.data import DataLoader +from sklearn.svm import OneClassSVM +from sklearn.metrics import roc_auc_score +from base.base_dataset import BaseADDataset +from networks.main import build_autoencoder + + +class OCSVM(object): + """A class for One-Class SVM models.""" + + def __init__(self, kernel='rbf', nu=0.1, hybrid=False): + """Init OCSVM instance.""" + self.kernel = kernel + self.nu = nu + self.rho = None + self.gamma = None + + self.model = OneClassSVM(kernel=kernel, nu=nu) + + self.hybrid = hybrid + self.ae_net = None # autoencoder network for the case of a hybrid model + self.linear_model = None # also init a model with linear kernel if hybrid approach + + self.results = { + 'train_time': None, + 'test_time': None, + 'test_auc': None, + 'test_scores': None, + 'train_time_linear': None, + 'test_time_linear': None, + 'test_auc_linear': None + } + + def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Trains the OC-SVM model on the training data.""" + logger = logging.getLogger() + + # do not drop last batch for non-SGD optimization shallow_ssad + train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, + num_workers=n_jobs_dataloader, drop_last=False) + + # Get data from loader + X = () + for data in train_loader: + inputs, _, _, _ = data + inputs = inputs.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + X = np.concatenate(X) + + # Training + logger.info('Starting training...') + + # Select model via hold-out test set of 1000 samples + gammas = np.logspace(-7, 2, num=10, base=2) + best_auc = 0.0 + + # Sample hold-out set from test set + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + X_test = () + labels = [] + for data in test_loader: + inputs, label_batch, _, _ = data + inputs, label_batch = inputs.to(device), label_batch.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X_test += (X_batch.cpu().data.numpy(),) + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X_test, labels = np.concatenate(X_test), np.array(labels) + n_test, n_normal, n_outlier = len(X_test), np.sum(labels == 0), np.sum(labels == 1) + n_val = int(0.1 * n_test) + n_val_normal, n_val_outlier = int(n_val * (n_normal/n_test)), int(n_val * (n_outlier/n_test)) + perm = np.random.permutation(n_test) + X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal], + X_test[perm][labels[perm] == 1][:n_val_outlier])) + labels = np.array([0] * n_val_normal + [1] * n_val_outlier) + + i = 1 + for gamma in gammas: + + # Model candidate + model = OneClassSVM(kernel=self.kernel, nu=self.nu, gamma=gamma) + + # Train + start_time = time.time() + model.fit(X) + train_time = time.time() - start_time + + # Test on small hold-out set from test set + scores = (-1.0) * model.decision_function(X_val) + scores = scores.flatten() + + # Compute AUC + auc = roc_auc_score(labels, scores) + + logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s ' + f'| Val AUC: {100. * auc:.2f} |') + + if auc > best_auc: + best_auc = auc + self.model = model + self.gamma = gamma + self.results['train_time'] = train_time + + i += 1 + + # If hybrid, also train a model with linear kernel + if self.hybrid: + self.linear_model = OneClassSVM(kernel='linear', nu=self.nu) + start_time = time.time() + self.linear_model.fit(X) + train_time = time.time() - start_time + self.results['train_time_linear'] = train_time + + logger.info(f'Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}') + logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) + logger.info('Finished training.') + + def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Tests the OC-SVM model on the test data.""" + logger = logging.getLogger() + + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + # Get data from loader + idx_label_score = [] + X = () + idxs = [] + labels = [] + for data in test_loader: + inputs, label_batch, _, idx = data + inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + idxs += idx.cpu().data.numpy().astype(np.int64).tolist() + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X = np.concatenate(X) + + # Testing + logger.info('Starting testing...') + start_time = time.time() + + scores = (-1.0) * self.model.decision_function(X) + + self.results['test_time'] = time.time() - start_time + scores = scores.flatten() + self.rho = -self.model.intercept_[0] + + # Save triples of (idx, label, score) in a list + idx_label_score += list(zip(idxs, labels, scores.tolist())) + self.results['test_scores'] = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.results['test_auc'] = roc_auc_score(labels, scores) + + # If hybrid, also test model with linear kernel + if self.hybrid: + start_time = time.time() + scores_linear = (-1.0) * self.linear_model.decision_function(X) + self.results['test_time_linear'] = time.time() - start_time + scores_linear = scores_linear.flatten() + self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear) + logger.info('Test AUC linear model: {:.2f}%'.format(100. * self.results['test_auc_linear'])) + logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear'])) + + # Log results + logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) + logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) + logger.info('Finished testing.') + + def load_ae(self, dataset_name, model_path): + """Load pretrained autoencoder from model_path for feature extraction in a hybrid OC-SVM model.""" + + model_dict = torch.load(model_path, map_location='cpu') + ae_net_dict = model_dict['ae_net_dict'] + if dataset_name in ['mnist', 'fmnist', 'cifar10']: + net_name = dataset_name + '_LeNet' + else: + net_name = dataset_name + '_mlp' + + if self.ae_net is None: + self.ae_net = build_autoencoder(net_name) + + # update keys (since there was a change in network definition) + ae_keys = list(self.ae_net.state_dict().keys()) + for i in range(len(ae_net_dict)): + k, v = ae_net_dict.popitem(False) + new_key = ae_keys[i] + ae_net_dict[new_key] = v + i += 1 + + self.ae_net.load_state_dict(ae_net_dict) + self.ae_net.eval() + + def save_model(self, export_path): + """Save OC-SVM model to export_path.""" + pass + + def load_model(self, import_path, device: str = 'cpu'): + """Load OC-SVM model from import_path.""" + pass + + def save_results(self, export_json): + """Save results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.results, fp) diff --git a/Deep-SAD-PyTorch/src/baselines/shallow_ssad/__init__.py b/Deep-SAD-PyTorch/src/baselines/shallow_ssad/__init__.py new file mode 100644 index 0000000..64546db --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/shallow_ssad/__init__.py @@ -0,0 +1 @@ +from .ssad_convex import ConvexSSAD diff --git a/Deep-SAD-PyTorch/src/baselines/shallow_ssad/ssad_convex.py b/Deep-SAD-PyTorch/src/baselines/shallow_ssad/ssad_convex.py new file mode 100644 index 0000000..bc6aa31 --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/shallow_ssad/ssad_convex.py @@ -0,0 +1,186 @@ +######################################################################################################################## +# Acknowledgements: https://github.com/nicococo/tilitools +######################################################################################################################## +import numpy as np + +from cvxopt import matrix, spmatrix, sparse, spdiag +from cvxopt.solvers import qp + + +class ConvexSSAD: + """ Convex semi-supervised anomaly detection with hinge-loss and L2 regularizer + as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013 + + minimize 0.5 ||w||^2_2 - rho - kappa*gamma + eta_u sum_i xi_i + eta_l sum_j xi_j + {w,rho,gamma>=0,xi>=0} + subject to >= rho - xi_i + y_j >= y_j*rho + gamma - xi_j + + And the corresponding dual optimization problem: + + maximize -0.5 sum_(i,j) alpha_i alpha_j y_i y_j k(x_i,x_j) + {0<=alpha_i<=eta_i} + subject to kappa <= sum_j alpha_j (for all labeled examples) + 1 = sum_j y_i alpha_j (for all examples) + + We introduce labels y_i = +1 for all unlabeled examples which enables us to combine sums. + + Note: Only dual solution is supported. + + Written by: Nico Goernitz, TU Berlin, 2013/14 + """ + PRECISION = 1e-9 # important: effects the threshold, support vectors and speed! + + def __init__(self, kernel, y, kappa=1.0, Cp=1.0, Cu=1.0, Cn=1.0): + assert(len(y.shape) == 1) + self.kernel = kernel + self.y = y # (vector) corresponding labels (+1,-1 and 0 for unlabeled) + self.kappa = kappa # (scalar) regularizer for importance of the margin + self.Cp = Cp # (scalar) the regularization constant for positively labeled samples > 0 + self.Cu = Cu # (scalar) the regularization constant for unlabeled samples > 0 + self.Cn = Cn # (scalar) the regularization constant for outliers > 0 + self.samples = y.size + self.labeled = np.sum(np.abs(y)) + + # cy: (vector) converted label vector (+1 for pos and unlabeled, -1 for outliers) + self.cy = y.copy().reshape((y.size, 1)) + self.cy[y == 0] = 1 # cy=+1.0 (unlabeled,pos) & cy=-1.0 (neg) + + # cl: (vector) converted label vector (+1 for labeled examples, 0.0 for unlabeled) + self.cl = np.abs(y.copy()) # cl=+1.0 (labeled) cl=0.0 (unlabeled) + + # (vector) converted upper bound box constraint for each example + self.cC = np.zeros(y.size) # cC=Cu (unlabeled) cC=Cp (pos) cC=Cn (neg) + self.cC[y == 0] = Cu + self.cC[y == 1] = Cp + self.cC[y ==-1] = Cn + + self.alphas = None + self.svs = None # (vector) list of support vector (contains indices) + self.threshold = 0.0 # (scalar) the optimized threshold (rho) + + # if there are no labeled examples, then set kappa to 0.0 otherwise + # the dual constraint kappa <= sum_{i \in labeled} alpha_i = 0.0 will + # prohibit a solution + if self.labeled == 0: + print('There are no labeled examples hence, setting kappa=0.0') + self.kappa = 0.0 + print('Convex semi-supervised anomaly detection with {0} samples ({1} labeled).'.format(self.samples, self.labeled)) + + def set_train_kernel(self, kernel): + dim1, dim2 = kernel.shape + print([dim1, dim2]) + assert(dim1 == dim2 and dim1 == self.samples) + self.kernel = kernel + + def fit(self, check_psd_eigs=False): + # number of training examples + N = self.samples + + # generate the label kernel + Y = self.cy.dot(self.cy.T) + + # generate the final PDS kernel + P = matrix(self.kernel*Y) + + # check for PSD + if check_psd_eigs: + eigs = np.linalg.eigvalsh(np.array(P)) + if eigs[0] < 0.0: + print('Smallest eigenvalue is {0}'.format(eigs[0])) + P += spdiag([-eigs[0] for i in range(N)]) + + # there is no linear part of the objective + q = matrix(0.0, (N, 1)) + + # sum_i y_i alpha_i = A alpha = b = 1.0 + A = matrix(self.cy, (1, self.samples), 'd') + b = matrix(1.0, (1, 1)) + + # inequality constraints: G alpha <= h + # 1) alpha_i <= C_i + # 2) -alpha_i <= 0 + G12 = spmatrix(1.0, range(N), range(N)) + h1 = matrix(self.cC) + h2 = matrix(0.0, (N, 1)) + G = sparse([G12, -G12]) + h = matrix([h1, h2]) + if self.labeled > 0: + # 3) kappa <= \sum_i labeled_i alpha_i -> -cl' alpha <= -kappa + print('Labeled data found.') + G3 = -matrix(self.cl, (1, self.cl.size), 'd') + h3 = -matrix(self.kappa, (1, 1)) + G = sparse([G12, -G12, G3]) + h = matrix([h1, h2, h3]) + + # solve the quadratic programm + sol = qp(P, -q, G, h, A, b) + + # store solution + self.alphas = np.array(sol['x']) + + # 1. find all support vectors, i.e. 0 < alpha_i <= C + # 2. store all support vector with alpha_i < C in 'margins' + self.svs = np.where(self.alphas >= ConvexSSAD.PRECISION)[0] + + # these should sum to one + print('Validate solution:') + print('- found {0} support vectors'.format(len(self.svs))) + print('0 <= alpha_i : {0} of {1}'.format(np.sum(0. <= self.alphas), N)) + print('- sum_(i) alpha_i cy_i = {0} = 1.0'.format(np.sum(self.alphas*self.cy))) + print('- sum_(i in sv) alpha_i cy_i = {0} ~ 1.0 (approx error)'.format(np.sum(self.alphas[self.svs]*self.cy[self.svs]))) + print('- sum_(i in labeled) alpha_i = {0} >= {1} = kappa'.format(np.sum(self.alphas[self.cl == 1]), self.kappa)) + print('- sum_(i in unlabeled) alpha_i = {0}'.format(np.sum(self.alphas[self.y == 0]))) + print('- sum_(i in positives) alpha_i = {0}'.format(np.sum(self.alphas[self.y == 1]))) + print('- sum_(i in negatives) alpha_i = {0}'.format(np.sum(self.alphas[self.y ==-1]))) + + # infer threshold (rho) + psvs = np.where(self.y[self.svs] == 0)[0] + # case 1: unlabeled support vectors available + self.threshold = 0. + unl_threshold = -1e12 + lbl_threshold = -1e12 + if psvs.size > 0: + k = self.kernel[:, self.svs] + k = k[self.svs[psvs], :] + unl_threshold = np.max(self.apply(k)) + + if np.sum(self.cl) > 1e-12: + # case 2: only labeled examples available + k = self.kernel[:, self.svs] + k = k[self.svs, :] + thres = self.apply(k) + pinds = np.where(self.y[self.svs] == +1)[0] + ninds = np.where(self.y[self.svs] == -1)[0] + # only negatives is not possible + if ninds.size > 0 and pinds.size == 0: + print('ERROR: Check pre-defined PRECISION.') + lbl_threshold = np.max(thres[ninds]) + elif ninds.size == 0: + lbl_threshold = np.max(thres[pinds]) + else: + # smallest negative + largest positive + p = np.max(thres[pinds]) + n = np.min(thres[ninds]) + lbl_threshold = (n+p)/2. + self.threshold = np.max((unl_threshold, lbl_threshold)) + + def get_threshold(self): + return self.threshold + + def get_support_dual(self): + return self.svs + + def get_alphas(self): + return self.alphas + + def apply(self, kernel): + """ Application of dual trained ssad. + kernel = get_kernel(Y, X[:, cssad.svs], kernel_type, kernel_param) + """ + if kernel.shape[1] == self.samples: + # if kernel is not restricted to support vectors + ay = self.alphas * self.cy + else: + ay = self.alphas[self.svs] * self.cy[self.svs] + return ay.T.dot(kernel.T).T - self.threshold diff --git a/Deep-SAD-PyTorch/src/baselines/ssad.py b/Deep-SAD-PyTorch/src/baselines/ssad.py new file mode 100644 index 0000000..156ccfa --- /dev/null +++ b/Deep-SAD-PyTorch/src/baselines/ssad.py @@ -0,0 +1,244 @@ +import json +import logging +import time +import torch +import numpy as np + +from torch.utils.data import DataLoader +from .shallow_ssad.ssad_convex import ConvexSSAD +from sklearn.metrics import roc_auc_score +from sklearn.metrics.pairwise import pairwise_kernels +from base.base_dataset import BaseADDataset +from networks.main import build_autoencoder + + +class SSAD(object): + """ + A class for kernel SSAD models as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013. + """ + + def __init__(self, kernel='rbf', kappa=1.0, Cp=1.0, Cu=1.0, Cn=1.0, hybrid=False): + """Init SSAD instance.""" + self.kernel = kernel + self.kappa = kappa + self.Cp = Cp + self.Cu = Cu + self.Cn = Cn + self.rho = None + self.gamma = None + + self.model = None + self.X_svs = None + + self.hybrid = hybrid + self.ae_net = None # autoencoder network for the case of a hybrid model + self.linear_model = None # also init a model with linear kernel if hybrid approach + self.linear_X_svs = None + + self.results = { + 'train_time': None, + 'test_time': None, + 'test_auc': None, + 'test_scores': None, + 'train_time_linear': None, + 'test_time_linear': None, + 'test_auc_linear': None + } + + def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Trains the SSAD model on the training data.""" + logger = logging.getLogger() + + # do not drop last batch for non-SGD optimization shallow_ssad + train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, + num_workers=n_jobs_dataloader, drop_last=False) + + # Get data from loader + X = () + semi_targets = [] + for data in train_loader: + inputs, _, semi_targets_batch, _ = data + inputs, semi_targets_batch = inputs.to(device), semi_targets_batch.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + semi_targets += semi_targets_batch.cpu().data.numpy().astype(np.int).tolist() + X, semi_targets = np.concatenate(X), np.array(semi_targets) + + # Training + logger.info('Starting training...') + + # Select model via hold-out test set of 1000 samples + gammas = np.logspace(-7, 2, num=10, base=2) + best_auc = 0.0 + + # Sample hold-out set from test set + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + X_test = () + labels = [] + for data in test_loader: + inputs, label_batch, _, _ = data + inputs, label_batch = inputs.to(device), label_batch.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X_test += (X_batch.cpu().data.numpy(),) + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X_test, labels = np.concatenate(X_test), np.array(labels) + n_test, n_normal, n_outlier = len(X_test), np.sum(labels == 0), np.sum(labels == 1) + n_val = int(0.1 * n_test) + n_val_normal, n_val_outlier = int(n_val * (n_normal/n_test)), int(n_val * (n_outlier/n_test)) + perm = np.random.permutation(n_test) + X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal], + X_test[perm][labels[perm] == 1][:n_val_outlier])) + labels = np.array([0] * n_val_normal + [1] * n_val_outlier) + + i = 1 + for gamma in gammas: + + # Build the training kernel + kernel = pairwise_kernels(X, X, metric=self.kernel, gamma=gamma) + + # Model candidate + model = ConvexSSAD(kernel, semi_targets, Cp=self.Cp, Cu=self.Cu, Cn=self.Cn) + + # Train + start_time = time.time() + model.fit() + train_time = time.time() - start_time + + # Test on small hold-out set from test set + kernel_val = pairwise_kernels(X_val, X[model.svs, :], metric=self.kernel, gamma=gamma) + scores = (-1.0) * model.apply(kernel_val) + scores = scores.flatten() + + # Compute AUC + auc = roc_auc_score(labels, scores) + + logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s ' + f'| Val AUC: {100. * auc:.2f} |') + + if auc > best_auc: + best_auc = auc + self.model = model + self.gamma = gamma + self.results['train_time'] = train_time + + i += 1 + + # Get support vectors for testing + self.X_svs = X[self.model.svs, :] + + # If hybrid, also train a model with linear kernel + if self.hybrid: + linear_kernel = pairwise_kernels(X, X, metric='linear') + self.linear_model = ConvexSSAD(linear_kernel, semi_targets, Cp=self.Cp, Cu=self.Cu, Cn=self.Cn) + start_time = time.time() + self.linear_model.fit() + train_time = time.time() - start_time + self.results['train_time_linear'] = train_time + self.linear_X_svs = X[self.linear_model.svs, :] + + logger.info(f'Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}') + logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) + logger.info('Finished training.') + + def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): + """Tests the SSAD model on the test data.""" + logger = logging.getLogger() + + _, test_loader = dataset.loaders(batch_size=128, num_workers=n_jobs_dataloader) + + # Get data from loader + idx_label_score = [] + X = () + idxs = [] + labels = [] + for data in test_loader: + inputs, label_batch, _, idx = data + inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) + if self.hybrid: + inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features + X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) + X += (X_batch.cpu().data.numpy(),) + idxs += idx.cpu().data.numpy().astype(np.int64).tolist() + labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() + X = np.concatenate(X) + + # Testing + logger.info('Starting testing...') + start_time = time.time() + + # Build kernel + kernel = pairwise_kernels(X, self.X_svs, metric=self.kernel, gamma=self.gamma) + + scores = (-1.0) * self.model.apply(kernel) + + self.results['test_time'] = time.time() - start_time + scores = scores.flatten() + self.rho = -self.model.threshold + + # Save triples of (idx, label, score) in a list + idx_label_score += list(zip(idxs, labels, scores.tolist())) + self.results['test_scores'] = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.results['test_auc'] = roc_auc_score(labels, scores) + + # If hybrid, also test model with linear kernel + if self.hybrid: + start_time = time.time() + linear_kernel = pairwise_kernels(X, self.linear_X_svs, metric='linear') + scores_linear = (-1.0) * self.linear_model.apply(linear_kernel) + self.results['test_time_linear'] = time.time() - start_time + scores_linear = scores_linear.flatten() + self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear) + logger.info('Test AUC linear model: {:.2f}%'.format(100. * self.results['test_auc_linear'])) + logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear'])) + + # Log results + logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) + logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) + logger.info('Finished testing.') + + def load_ae(self, dataset_name, model_path): + """Load pretrained autoencoder from model_path for feature extraction in a hybrid SSAD model.""" + + model_dict = torch.load(model_path, map_location='cpu') + ae_net_dict = model_dict['ae_net_dict'] + if dataset_name in ['mnist', 'fmnist', 'cifar10']: + net_name = dataset_name + '_LeNet' + else: + net_name = dataset_name + '_mlp' + + if self.ae_net is None: + self.ae_net = build_autoencoder(net_name) + + # update keys (since there was a change in network definition) + ae_keys = list(self.ae_net.state_dict().keys()) + for i in range(len(ae_net_dict)): + k, v = ae_net_dict.popitem(False) + new_key = ae_keys[i] + ae_net_dict[new_key] = v + i += 1 + + self.ae_net.load_state_dict(ae_net_dict) + self.ae_net.eval() + + def save_model(self, export_path): + """Save SSAD model to export_path.""" + pass + + def load_model(self, import_path, device: str = 'cpu'): + """Load SSAD model from import_path.""" + pass + + def save_results(self, export_json): + """Save results dict to a JSON-file.""" + with open(export_json, 'w') as fp: + json.dump(self.results, fp) diff --git a/Deep-SAD-PyTorch/src/datasets/__init__.py b/Deep-SAD-PyTorch/src/datasets/__init__.py new file mode 100644 index 0000000..cd4f3f9 --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/__init__.py @@ -0,0 +1,6 @@ +from .main import load_dataset +from .mnist import MNIST_Dataset +from .fmnist import FashionMNIST_Dataset +from .cifar10 import CIFAR10_Dataset +from .odds import ODDSADDataset +from .preprocessing import * diff --git a/Deep-SAD-PyTorch/src/datasets/cifar10.py b/Deep-SAD-PyTorch/src/datasets/cifar10.py new file mode 100644 index 0000000..f3bfa1c --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/cifar10.py @@ -0,0 +1,86 @@ +from torch.utils.data import Subset +from PIL import Image +from torchvision.datasets import CIFAR10 +from base.torchvision_dataset import TorchvisionDataset +from .preprocessing import create_semisupervised_setting + +import torch +import torchvision.transforms as transforms +import random +import numpy as np + + +class CIFAR10_Dataset(TorchvisionDataset): + + def __init__(self, root: str, normal_class: int = 5, known_outlier_class: int = 3, n_known_outlier_classes: int = 0, + ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): + super().__init__(root) + + # Define normal and outlier classes + self.n_classes = 2 # 0: normal, 1: outlier + self.normal_classes = tuple([normal_class]) + self.outlier_classes = list(range(0, 10)) + self.outlier_classes.remove(normal_class) + self.outlier_classes = tuple(self.outlier_classes) + + if n_known_outlier_classes == 0: + self.known_outlier_classes = () + elif n_known_outlier_classes == 1: + self.known_outlier_classes = tuple([known_outlier_class]) + else: + self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) + + # CIFAR-10 preprocessing: feature scaling to [0, 1] + transform = transforms.ToTensor() + target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) + + # Get train set + train_set = MyCIFAR10(root=self.root, train=True, transform=transform, target_transform=target_transform, + download=True) + + # Create semi-supervised setting + idx, _, semi_targets = create_semisupervised_setting(np.array(train_set.targets), self.normal_classes, + self.outlier_classes, self.known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution) + train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels + + # Subset train_set to semi-supervised setup + self.train_set = Subset(train_set, idx) + + # Get test set + self.test_set = MyCIFAR10(root=self.root, train=False, transform=transform, target_transform=target_transform, + download=True) + + +class MyCIFAR10(CIFAR10): + """ + Torchvision CIFAR10 class with additional targets for the semi-supervised setting and patch of __getitem__ method + to also return the semi-supervised target as well as the index of a data sample. + """ + + def __init__(self, *args, **kwargs): + super(MyCIFAR10, self).__init__(*args, **kwargs) + + self.semi_targets = torch.zeros(len(self.targets), dtype=torch.int64) + + def __getitem__(self, index): + """Override the original method of the CIFAR10 class. + Args: + index (int): Index + + Returns: + tuple: (image, target, semi_target, index) + """ + img, target, semi_target = self.data[index], self.targets[index], int(self.semi_targets[index]) + + # doing this so that it is consistent with all other datasets + # to return a PIL Image + img = Image.fromarray(img) + + if self.transform is not None: + img = self.transform(img) + + if self.target_transform is not None: + target = self.target_transform(target) + + return img, target, semi_target, index diff --git a/Deep-SAD-PyTorch/src/datasets/fmnist.py b/Deep-SAD-PyTorch/src/datasets/fmnist.py new file mode 100644 index 0000000..2a6df2f --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/fmnist.py @@ -0,0 +1,85 @@ +from torch.utils.data import Subset +from PIL import Image +from torchvision.datasets import FashionMNIST +from base.torchvision_dataset import TorchvisionDataset +from .preprocessing import create_semisupervised_setting + +import torch +import torchvision.transforms as transforms +import random + + +class FashionMNIST_Dataset(TorchvisionDataset): + + def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0, + ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): + super().__init__(root) + + # Define normal and outlier classes + self.n_classes = 2 # 0: normal, 1: outlier + self.normal_classes = tuple([normal_class]) + self.outlier_classes = list(range(0, 10)) + self.outlier_classes.remove(normal_class) + self.outlier_classes = tuple(self.outlier_classes) + + if n_known_outlier_classes == 0: + self.known_outlier_classes = () + elif n_known_outlier_classes == 1: + self.known_outlier_classes = tuple([known_outlier_class]) + else: + self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) + + # FashionMNIST preprocessing: feature scaling to [0, 1] + transform = transforms.ToTensor() + target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) + + # Get train set + train_set = MyFashionMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform, + download=True) + + # Create semi-supervised setting + idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, + self.outlier_classes, self.known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution) + train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels + + # Subset train_set to semi-supervised setup + self.train_set = Subset(train_set, idx) + + # Get test set + self.test_set = MyFashionMNIST(root=self.root, train=False, transform=transform, + target_transform=target_transform, download=True) + + +class MyFashionMNIST(FashionMNIST): + """ + Torchvision FashionMNIST class with additional targets for the semi-supervised setting and patch of __getitem__ + method to also return the semi-supervised target as well as the index of a data sample. + """ + + def __init__(self, *args, **kwargs): + super(MyFashionMNIST, self).__init__(*args, **kwargs) + + self.semi_targets = torch.zeros_like(self.targets) + + def __getitem__(self, index): + """Override the original method of the MyFashionMNIST class. + Args: + index (int): Index + + Returns: + tuple: (image, target, semi_target, index) + """ + img, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[index]) + + # doing this so that it is consistent with all other datasets + # to return a PIL Image + img = Image.fromarray(img.numpy(), mode='L') + + if self.transform is not None: + img = self.transform(img) + + if self.target_transform is not None: + target = self.target_transform(target) + + return img, target, semi_target, index diff --git a/Deep-SAD-PyTorch/src/datasets/main.py b/Deep-SAD-PyTorch/src/datasets/main.py new file mode 100644 index 0000000..845b36a --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/main.py @@ -0,0 +1,54 @@ +from .mnist import MNIST_Dataset +from .fmnist import FashionMNIST_Dataset +from .cifar10 import CIFAR10_Dataset +from .odds import ODDSADDataset + + +def load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes: int = 0, + ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0, + random_state=None): + """Loads the dataset.""" + + implemented_datasets = ('mnist', 'fmnist', 'cifar10', + 'arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid') + assert dataset_name in implemented_datasets + + dataset = None + + if dataset_name == 'mnist': + dataset = MNIST_Dataset(root=data_path, + normal_class=normal_class, + known_outlier_class=known_outlier_class, + n_known_outlier_classes=n_known_outlier_classes, + ratio_known_normal=ratio_known_normal, + ratio_known_outlier=ratio_known_outlier, + ratio_pollution=ratio_pollution) + + if dataset_name == 'fmnist': + dataset = FashionMNIST_Dataset(root=data_path, + normal_class=normal_class, + known_outlier_class=known_outlier_class, + n_known_outlier_classes=n_known_outlier_classes, + ratio_known_normal=ratio_known_normal, + ratio_known_outlier=ratio_known_outlier, + ratio_pollution=ratio_pollution) + + if dataset_name == 'cifar10': + dataset = CIFAR10_Dataset(root=data_path, + normal_class=normal_class, + known_outlier_class=known_outlier_class, + n_known_outlier_classes=n_known_outlier_classes, + ratio_known_normal=ratio_known_normal, + ratio_known_outlier=ratio_known_outlier, + ratio_pollution=ratio_pollution) + + if dataset_name in ('arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid'): + dataset = ODDSADDataset(root=data_path, + dataset_name=dataset_name, + n_known_outlier_classes=n_known_outlier_classes, + ratio_known_normal=ratio_known_normal, + ratio_known_outlier=ratio_known_outlier, + ratio_pollution=ratio_pollution, + random_state=random_state) + + return dataset diff --git a/Deep-SAD-PyTorch/src/datasets/mnist.py b/Deep-SAD-PyTorch/src/datasets/mnist.py new file mode 100644 index 0000000..1999264 --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/mnist.py @@ -0,0 +1,85 @@ +from torch.utils.data import Subset +from PIL import Image +from torchvision.datasets import MNIST +from base.torchvision_dataset import TorchvisionDataset +from .preprocessing import create_semisupervised_setting + +import torch +import torchvision.transforms as transforms +import random + + +class MNIST_Dataset(TorchvisionDataset): + + def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0, + ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): + super().__init__(root) + + # Define normal and outlier classes + self.n_classes = 2 # 0: normal, 1: outlier + self.normal_classes = tuple([normal_class]) + self.outlier_classes = list(range(0, 10)) + self.outlier_classes.remove(normal_class) + self.outlier_classes = tuple(self.outlier_classes) + + if n_known_outlier_classes == 0: + self.known_outlier_classes = () + elif n_known_outlier_classes == 1: + self.known_outlier_classes = tuple([known_outlier_class]) + else: + self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) + + # MNIST preprocessing: feature scaling to [0, 1] + transform = transforms.ToTensor() + target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) + + # Get train set + train_set = MyMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform, + download=True) + + # Create semi-supervised setting + idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, + self.outlier_classes, self.known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution) + train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels + + # Subset train_set to semi-supervised setup + self.train_set = Subset(train_set, idx) + + # Get test set + self.test_set = MyMNIST(root=self.root, train=False, transform=transform, target_transform=target_transform, + download=True) + + +class MyMNIST(MNIST): + """ + Torchvision MNIST class with additional targets for the semi-supervised setting and patch of __getitem__ method + to also return the semi-supervised target as well as the index of a data sample. + """ + + def __init__(self, *args, **kwargs): + super(MyMNIST, self).__init__(*args, **kwargs) + + self.semi_targets = torch.zeros_like(self.targets) + + def __getitem__(self, index): + """Override the original method of the MNIST class. + Args: + index (int): Index + + Returns: + tuple: (image, target, semi_target, index) + """ + img, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[index]) + + # doing this so that it is consistent with all other datasets + # to return a PIL Image + img = Image.fromarray(img.numpy(), mode='L') + + if self.transform is not None: + img = self.transform(img) + + if self.target_transform is not None: + target = self.target_transform(target) + + return img, target, semi_target, index diff --git a/Deep-SAD-PyTorch/src/datasets/odds.py b/Deep-SAD-PyTorch/src/datasets/odds.py new file mode 100644 index 0000000..aec907b --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/odds.py @@ -0,0 +1,47 @@ +from torch.utils.data import DataLoader, Subset +from base.base_dataset import BaseADDataset +from base.odds_dataset import ODDSDataset +from .preprocessing import create_semisupervised_setting + +import torch + + +class ODDSADDataset(BaseADDataset): + + def __init__(self, root: str, dataset_name: str, n_known_outlier_classes: int = 0, ratio_known_normal: float = 0.0, + ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0, random_state=None): + super().__init__(root) + + # Define normal and outlier classes + self.n_classes = 2 # 0: normal, 1: outlier + self.normal_classes = (0,) + self.outlier_classes = (1,) + + if n_known_outlier_classes == 0: + self.known_outlier_classes = () + else: + self.known_outlier_classes = (1,) + + # Get train set + train_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=True, random_state=random_state, + download=True) + + # Create semi-supervised setting + idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, + self.outlier_classes, self.known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution) + train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels + + # Subset train_set to semi-supervised setup + self.train_set = Subset(train_set, idx) + + # Get test set + self.test_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=False, random_state=random_state) + + def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> ( + DataLoader, DataLoader): + train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train, + num_workers=num_workers, drop_last=True) + test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test, + num_workers=num_workers, drop_last=False) + return train_loader, test_loader diff --git a/Deep-SAD-PyTorch/src/datasets/preprocessing.py b/Deep-SAD-PyTorch/src/datasets/preprocessing.py new file mode 100644 index 0000000..5d3612d --- /dev/null +++ b/Deep-SAD-PyTorch/src/datasets/preprocessing.py @@ -0,0 +1,66 @@ +import torch +import numpy as np + + +def create_semisupervised_setting(labels, normal_classes, outlier_classes, known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution): + """ + Create a semi-supervised data setting. + :param labels: np.array with labels of all dataset samples + :param normal_classes: tuple with normal class labels + :param outlier_classes: tuple with anomaly class labels + :param known_outlier_classes: tuple with known (labeled) anomaly class labels + :param ratio_known_normal: the desired ratio of known (labeled) normal samples + :param ratio_known_outlier: the desired ratio of known (labeled) anomalous samples + :param ratio_pollution: the desired pollution ratio of the unlabeled data with unknown (unlabeled) anomalies. + :return: tuple with list of sample indices, list of original labels, and list of semi-supervised labels + """ + idx_normal = np.argwhere(np.isin(labels, normal_classes)).flatten() + idx_outlier = np.argwhere(np.isin(labels, outlier_classes)).flatten() + idx_known_outlier_candidates = np.argwhere(np.isin(labels, known_outlier_classes)).flatten() + + n_normal = len(idx_normal) + + # Solve system of linear equations to obtain respective number of samples + a = np.array([[1, 1, 0, 0], + [(1-ratio_known_normal), -ratio_known_normal, -ratio_known_normal, -ratio_known_normal], + [-ratio_known_outlier, -ratio_known_outlier, -ratio_known_outlier, (1-ratio_known_outlier)], + [0, -ratio_pollution, (1-ratio_pollution), 0]]) + b = np.array([n_normal, 0, 0, 0]) + x = np.linalg.solve(a, b) + + # Get number of samples + n_known_normal = int(x[0]) + n_unlabeled_normal = int(x[1]) + n_unlabeled_outlier = int(x[2]) + n_known_outlier = int(x[3]) + + # Sample indices + perm_normal = np.random.permutation(n_normal) + perm_outlier = np.random.permutation(len(idx_outlier)) + perm_known_outlier = np.random.permutation(len(idx_known_outlier_candidates)) + + idx_known_normal = idx_normal[perm_normal[:n_known_normal]].tolist() + idx_unlabeled_normal = idx_normal[perm_normal[n_known_normal:n_known_normal+n_unlabeled_normal]].tolist() + idx_unlabeled_outlier = idx_outlier[perm_outlier[:n_unlabeled_outlier]].tolist() + idx_known_outlier = idx_known_outlier_candidates[perm_known_outlier[:n_known_outlier]].tolist() + + # Get original class labels + labels_known_normal = labels[idx_known_normal].tolist() + labels_unlabeled_normal = labels[idx_unlabeled_normal].tolist() + labels_unlabeled_outlier = labels[idx_unlabeled_outlier].tolist() + labels_known_outlier = labels[idx_known_outlier].tolist() + + # Get semi-supervised setting labels + semi_labels_known_normal = np.ones(n_known_normal).astype(np.int32).tolist() + semi_labels_unlabeled_normal = np.zeros(n_unlabeled_normal).astype(np.int32).tolist() + semi_labels_unlabeled_outlier = np.zeros(n_unlabeled_outlier).astype(np.int32).tolist() + semi_labels_known_outlier = (-np.ones(n_known_outlier).astype(np.int32)).tolist() + + # Create final lists + list_idx = idx_known_normal + idx_unlabeled_normal + idx_unlabeled_outlier + idx_known_outlier + list_labels = labels_known_normal + labels_unlabeled_normal + labels_unlabeled_outlier + labels_known_outlier + list_semi_labels = (semi_labels_known_normal + semi_labels_unlabeled_normal + semi_labels_unlabeled_outlier + + semi_labels_known_outlier) + + return list_idx, list_labels, list_semi_labels diff --git a/Deep-SAD-PyTorch/src/main.py b/Deep-SAD-PyTorch/src/main.py new file mode 100644 index 0000000..1e3d78e --- /dev/null +++ b/Deep-SAD-PyTorch/src/main.py @@ -0,0 +1,239 @@ +import click +import torch +import logging +import random +import numpy as np + +from utils.config import Config +from utils.visualization.plot_images_grid import plot_images_grid +from DeepSAD import DeepSAD +from datasets.main import load_dataset + + +################################################################################ +# Settings +################################################################################ +@click.command() +@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', + 'satimage-2', 'shuttle', 'thyroid'])) +@click.argument('net_name', type=click.Choice(['mnist_LeNet', 'fmnist_LeNet', 'cifar10_LeNet', 'arrhythmia_mlp', + 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', + 'thyroid_mlp'])) +@click.argument('xp_path', type=click.Path(exists=True)) +@click.argument('data_path', type=click.Path(exists=True)) +@click.option('--load_config', type=click.Path(exists=True), default=None, + help='Config JSON-file path (default: None).') +@click.option('--load_model', type=click.Path(exists=True), default=None, + help='Model file path (default: None).') +@click.option('--eta', type=float, default=1.0, help='Deep SAD hyperparameter eta (must be 0 < eta).') +@click.option('--ratio_known_normal', type=float, default=0.0, + help='Ratio of known (labeled) normal training examples.') +@click.option('--ratio_known_outlier', type=float, default=0.0, + help='Ratio of known (labeled) anomalous training examples.') +@click.option('--ratio_pollution', type=float, default=0.0, + help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') +@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).') +@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') +@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam', + help='Name of the optimizer to use for Deep SAD network training.') +@click.option('--lr', type=float, default=0.001, + help='Initial learning rate for Deep SAD network training. Default=0.001') +@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.') +@click.option('--lr_milestone', type=int, default=0, multiple=True, + help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') +@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.') +@click.option('--weight_decay', type=float, default=1e-6, + help='Weight decay (L2 penalty) hyperparameter for Deep SAD objective.') +@click.option('--pretrain', type=bool, default=True, + help='Pretrain neural network parameters via autoencoder.') +@click.option('--ae_optimizer_name', type=click.Choice(['adam']), default='adam', + help='Name of the optimizer to use for autoencoder pretraining.') +@click.option('--ae_lr', type=float, default=0.001, + help='Initial learning rate for autoencoder pretraining. Default=0.001') +@click.option('--ae_n_epochs', type=int, default=100, help='Number of epochs to train autoencoder.') +@click.option('--ae_lr_milestone', type=int, default=0, multiple=True, + help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') +@click.option('--ae_batch_size', type=int, default=128, help='Batch size for mini-batch autoencoder training.') +@click.option('--ae_weight_decay', type=float, default=1e-6, + help='Weight decay (L2 penalty) hyperparameter for autoencoder objective.') +@click.option('--num_threads', type=int, default=0, + help='Number of threads used for parallelizing CPU operations. 0 means that all resources are used.') +@click.option('--n_jobs_dataloader', type=int, default=0, + help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') +@click.option('--normal_class', type=int, default=0, + help='Specify the normal class of the dataset (all other classes are considered anomalous).') +@click.option('--known_outlier_class', type=int, default=1, + help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') +@click.option('--n_known_outlier_classes', type=int, default=0, + help='Number of known outlier classes.' + 'If 0, no anomalies are known.' + 'If 1, outlier class as specified in --known_outlier_class option.' + 'If > 1, the specified number of outlier classes will be sampled at random.') +def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, eta, + ratio_known_normal, ratio_known_outlier, ratio_pollution, device, seed, + optimizer_name, lr, n_epochs, lr_milestone, batch_size, weight_decay, + pretrain, ae_optimizer_name, ae_lr, ae_n_epochs, ae_lr_milestone, ae_batch_size, ae_weight_decay, + num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): + """ + Deep SAD, a method for deep semi-supervised anomaly detection. + + :arg DATASET_NAME: Name of the dataset to load. + :arg NET_NAME: Name of the neural network to use. + :arg XP_PATH: Export path for logging the experiment. + :arg DATA_PATH: Root path of data. + """ + + # Get configuration + cfg = Config(locals().copy()) + + # Set up logging + logging.basicConfig(level=logging.INFO) + logger = logging.getLogger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_file = xp_path + '/log.txt' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + # Print paths + logger.info('Log file is %s' % log_file) + logger.info('Data path is %s' % data_path) + logger.info('Export path is %s' % xp_path) + + # Print experimental setup + logger.info('Dataset: %s' % dataset_name) + logger.info('Normal class: %d' % normal_class) + logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) + logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier) + logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) + if n_known_outlier_classes == 1: + logger.info('Known anomaly class: %d' % known_outlier_class) + else: + logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) + logger.info('Network: %s' % net_name) + + # If specified, load experiment config from JSON-file + if load_config: + cfg.load_config(import_json=load_config) + logger.info('Loaded configuration from %s.' % load_config) + + # Print model configuration + logger.info('Eta-parameter: %.2f' % cfg.settings['eta']) + + # Set seed + if cfg.settings['seed'] != -1: + random.seed(cfg.settings['seed']) + np.random.seed(cfg.settings['seed']) + torch.manual_seed(cfg.settings['seed']) + torch.cuda.manual_seed(cfg.settings['seed']) + torch.backends.cudnn.deterministic = True + logger.info('Set seed to %d.' % cfg.settings['seed']) + + # Default device to 'cpu' if cuda is not available + if not torch.cuda.is_available(): + device = 'cpu' + # Set the number of threads used for parallelizing CPU operations + if num_threads > 0: + torch.set_num_threads(num_threads) + logger.info('Computation device: %s' % device) + logger.info('Number of threads: %d' % num_threads) + logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) + + # Load data + dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, + ratio_known_normal, ratio_known_outlier, ratio_pollution, + random_state=np.random.RandomState(cfg.settings['seed'])) + # Log random sample of known anomaly classes if more than 1 class + if n_known_outlier_classes > 1: + logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) + + # Initialize DeepSAD model and set neural network phi + deepSAD = DeepSAD(cfg.settings['eta']) + deepSAD.set_network(net_name) + + # If specified, load Deep SAD model (center c, network weights, and possibly autoencoder weights) + if load_model: + deepSAD.load_model(model_path=load_model, load_ae=True, map_location=device) + logger.info('Loading model from %s.' % load_model) + + logger.info('Pretraining: %s' % pretrain) + if pretrain: + # Log pretraining details + logger.info('Pretraining optimizer: %s' % cfg.settings['ae_optimizer_name']) + logger.info('Pretraining learning rate: %g' % cfg.settings['ae_lr']) + logger.info('Pretraining epochs: %d' % cfg.settings['ae_n_epochs']) + logger.info('Pretraining learning rate scheduler milestones: %s' % (cfg.settings['ae_lr_milestone'],)) + logger.info('Pretraining batch size: %d' % cfg.settings['ae_batch_size']) + logger.info('Pretraining weight decay: %g' % cfg.settings['ae_weight_decay']) + + # Pretrain model on dataset (via autoencoder) + deepSAD.pretrain(dataset, + optimizer_name=cfg.settings['ae_optimizer_name'], + lr=cfg.settings['ae_lr'], + n_epochs=cfg.settings['ae_n_epochs'], + lr_milestones=cfg.settings['ae_lr_milestone'], + batch_size=cfg.settings['ae_batch_size'], + weight_decay=cfg.settings['ae_weight_decay'], + device=device, + n_jobs_dataloader=n_jobs_dataloader) + + # Save pretraining results + deepSAD.save_ae_results(export_json=xp_path + '/ae_results.json') + + # Log training details + logger.info('Training optimizer: %s' % cfg.settings['optimizer_name']) + logger.info('Training learning rate: %g' % cfg.settings['lr']) + logger.info('Training epochs: %d' % cfg.settings['n_epochs']) + logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],)) + logger.info('Training batch size: %d' % cfg.settings['batch_size']) + logger.info('Training weight decay: %g' % cfg.settings['weight_decay']) + + # Train model on dataset + deepSAD.train(dataset, + optimizer_name=cfg.settings['optimizer_name'], + lr=cfg.settings['lr'], + n_epochs=cfg.settings['n_epochs'], + lr_milestones=cfg.settings['lr_milestone'], + batch_size=cfg.settings['batch_size'], + weight_decay=cfg.settings['weight_decay'], + device=device, + n_jobs_dataloader=n_jobs_dataloader) + + # Test model + deepSAD.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) + + # Save results, model, and configuration + deepSAD.save_results(export_json=xp_path + '/results.json') + deepSAD.save_model(export_model=xp_path + '/model.tar') + cfg.save_config(export_json=xp_path + '/config.json') + + # Plot most anomalous and most normal test samples + indices, labels, scores = zip(*deepSAD.results['test_scores']) + indices, labels, scores = np.array(indices), np.array(labels), np.array(scores) + idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score + idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score + + if dataset_name in ('mnist', 'fmnist', 'cifar10'): + + if dataset_name in ('mnist', 'fmnist'): + X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) + X_all_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1) + X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1) + X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) + + if dataset_name == 'cifar10': + X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0,3,1,2))) + X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2))) + X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2))) + X_normal_high = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0,3,1,2))) + + plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2) + plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2) + plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2) + plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) + + +if __name__ == '__main__': + main() diff --git a/Deep-SAD-PyTorch/src/networks/__init__.py b/Deep-SAD-PyTorch/src/networks/__init__.py new file mode 100644 index 0000000..7c9d706 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/__init__.py @@ -0,0 +1,10 @@ +from .main import build_network, build_autoencoder +from .mnist_LeNet import MNIST_LeNet, MNIST_LeNet_Decoder, MNIST_LeNet_Autoencoder +from .fmnist_LeNet import FashionMNIST_LeNet, FashionMNIST_LeNet_Decoder, FashionMNIST_LeNet_Autoencoder +from .cifar10_LeNet import CIFAR10_LeNet, CIFAR10_LeNet_Decoder, CIFAR10_LeNet_Autoencoder +from .mlp import MLP, MLP_Decoder, MLP_Autoencoder +from .layers.stochastic import GaussianSample +from .layers.standard import Standardize +from .inference.distributions import log_standard_gaussian, log_gaussian, log_standard_categorical +from .vae import VariationalAutoencoder, Encoder, Decoder +from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel diff --git a/Deep-SAD-PyTorch/src/networks/cifar10_LeNet.py b/Deep-SAD-PyTorch/src/networks/cifar10_LeNet.py new file mode 100644 index 0000000..0bb6ac7 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/cifar10_LeNet.py @@ -0,0 +1,82 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from base.base_net import BaseNet + + +class CIFAR10_LeNet(BaseNet): + + def __init__(self, rep_dim=128): + super().__init__() + + self.rep_dim = rep_dim + self.pool = nn.MaxPool2d(2, 2) + + self.conv1 = nn.Conv2d(3, 32, 5, bias=False, padding=2) + self.bn2d1 = nn.BatchNorm2d(32, eps=1e-04, affine=False) + self.conv2 = nn.Conv2d(32, 64, 5, bias=False, padding=2) + self.bn2d2 = nn.BatchNorm2d(64, eps=1e-04, affine=False) + self.conv3 = nn.Conv2d(64, 128, 5, bias=False, padding=2) + self.bn2d3 = nn.BatchNorm2d(128, eps=1e-04, affine=False) + self.fc1 = nn.Linear(128 * 4 * 4, self.rep_dim, bias=False) + + def forward(self, x): + x = x.view(-1, 3, 32, 32) + x = self.conv1(x) + x = self.pool(F.leaky_relu(self.bn2d1(x))) + x = self.conv2(x) + x = self.pool(F.leaky_relu(self.bn2d2(x))) + x = self.conv3(x) + x = self.pool(F.leaky_relu(self.bn2d3(x))) + x = x.view(int(x.size(0)), -1) + x = self.fc1(x) + return x + + +class CIFAR10_LeNet_Decoder(BaseNet): + + def __init__(self, rep_dim=128): + super().__init__() + + self.rep_dim = rep_dim + + self.deconv1 = nn.ConvTranspose2d(int(self.rep_dim / (4 * 4)), 128, 5, bias=False, padding=2) + nn.init.xavier_uniform_(self.deconv1.weight, gain=nn.init.calculate_gain('leaky_relu')) + self.bn2d4 = nn.BatchNorm2d(128, eps=1e-04, affine=False) + self.deconv2 = nn.ConvTranspose2d(128, 64, 5, bias=False, padding=2) + nn.init.xavier_uniform_(self.deconv2.weight, gain=nn.init.calculate_gain('leaky_relu')) + self.bn2d5 = nn.BatchNorm2d(64, eps=1e-04, affine=False) + self.deconv3 = nn.ConvTranspose2d(64, 32, 5, bias=False, padding=2) + nn.init.xavier_uniform_(self.deconv3.weight, gain=nn.init.calculate_gain('leaky_relu')) + self.bn2d6 = nn.BatchNorm2d(32, eps=1e-04, affine=False) + self.deconv4 = nn.ConvTranspose2d(32, 3, 5, bias=False, padding=2) + nn.init.xavier_uniform_(self.deconv4.weight, gain=nn.init.calculate_gain('leaky_relu')) + + def forward(self, x): + x = x.view(int(x.size(0)), int(self.rep_dim / (4 * 4)), 4, 4) + x = F.leaky_relu(x) + x = self.deconv1(x) + x = F.interpolate(F.leaky_relu(self.bn2d4(x)), scale_factor=2) + x = self.deconv2(x) + x = F.interpolate(F.leaky_relu(self.bn2d5(x)), scale_factor=2) + x = self.deconv3(x) + x = F.interpolate(F.leaky_relu(self.bn2d6(x)), scale_factor=2) + x = self.deconv4(x) + x = torch.sigmoid(x) + return x + + +class CIFAR10_LeNet_Autoencoder(BaseNet): + + def __init__(self, rep_dim=128): + super().__init__() + + self.rep_dim = rep_dim + self.encoder = CIFAR10_LeNet(rep_dim=rep_dim) + self.decoder = CIFAR10_LeNet_Decoder(rep_dim=rep_dim) + + def forward(self, x): + x = self.encoder(x) + x = self.decoder(x) + return x diff --git a/Deep-SAD-PyTorch/src/networks/dgm.py b/Deep-SAD-PyTorch/src/networks/dgm.py new file mode 100644 index 0000000..d8be582 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/dgm.py @@ -0,0 +1,123 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from torch.nn import init +from .vae import VariationalAutoencoder, Encoder, Decoder + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +class Classifier(nn.Module): + """ + Classifier network, i.e. q(y|x), for two classes (0: normal, 1: outlier) + + :param net: neural network class to use (as parameter to use the same network over different shallow_ssad) + """ + + def __init__(self, net, dims=None): + super(Classifier, self).__init__() + self.dims = dims + if dims is None: + self.net = net() + self.logits = nn.Linear(self.net.rep_dim, 2) + else: + [x_dim, h_dim, y_dim] = dims + self.dense = nn.Linear(x_dim, h_dim) + self.logits = nn.Linear(h_dim, y_dim) + + def forward(self, x): + if self.dims is None: + x = self.net(x) + else: + x = F.relu(self.dense(x)) + x = F.softmax(self.logits(x), dim=-1) + return x + + +class DeepGenerativeModel(VariationalAutoencoder): + """ + M2 model from the paper 'Semi-Supervised Learning with Deep Generative Models' (Kingma et al., 2014). + + The 'Generative semi-supervised model' (M2) is a probabilistic model that incorporates label information in both + inference and generation. + + :param dims: dimensions of the model given by [input_dim, label_dim, latent_dim, [hidden_dims]]. + :param classifier_net: classifier network class to use. + """ + + def __init__(self, dims, classifier_net=None): + [x_dim, self.y_dim, z_dim, h_dim] = dims + super(DeepGenerativeModel, self).__init__([x_dim, z_dim, h_dim]) + + self.encoder = Encoder([x_dim + self.y_dim, h_dim, z_dim]) + self.decoder = Decoder([z_dim + self.y_dim, list(reversed(h_dim)), x_dim]) + if classifier_net is None: + self.classifier = Classifier(net=None, dims=[x_dim, h_dim[0], self.y_dim]) + else: + self.classifier = Classifier(classifier_net) + + # Init linear layers + for m in self.modules(): + if isinstance(m, nn.Linear): + init.xavier_normal_(m.weight.data) + if m.bias is not None: + m.bias.data.zero_() + + def forward(self, x, y): + z, q_mu, q_log_var = self.encoder(torch.cat((x, y), dim=1)) + self.kl_divergence = self._kld(z, (q_mu, q_log_var)) + rec = self.decoder(torch.cat((z, y), dim=1)) + + return rec + + def classify(self, x): + logits = self.classifier(x) + return logits + + def sample(self, z, y): + """ + Samples from the Decoder to generate an x. + + :param z: latent normal variable + :param y: label (one-hot encoded) + :return: x + """ + y = y.float() + x = self.decoder(torch.cat((z, y), dim=1)) + return x + + +class StackedDeepGenerativeModel(DeepGenerativeModel): + def __init__(self, dims, features): + """ + M1+M2 model as described in (Kingma et al., 2014). + + :param dims: dimensions of the model given by [input_dim, label_dim, latent_dim, [hidden_dims]]. + :param classifier_net: classifier network class to use. + :param features: a pre-trained M1 model of class 'VariationalAutoencoder' trained on the same dataset. + """ + [x_dim, y_dim, z_dim, h_dim] = dims + super(StackedDeepGenerativeModel, self).__init__([features.z_dim, y_dim, z_dim, h_dim]) + + # Be sure to reconstruct with the same dimensions + in_features = self.decoder.reconstruction.in_features + self.decoder.reconstruction = nn.Linear(in_features, x_dim) + + # Make vae feature model untrainable by freezing parameters + self.features = features + self.features.train(False) + + for param in self.features.parameters(): + param.requires_grad = False + + def forward(self, x, y): + # Sample a new latent x from the M1 model + x_sample, _, _ = self.features.encoder(x) + + # Use the sample as new input to M2 + return super(StackedDeepGenerativeModel, self).forward(x_sample, y) + + def classify(self, x): + _, x, _ = self.features.encoder(x) + logits = self.classifier(x) + return logits diff --git a/Deep-SAD-PyTorch/src/networks/fmnist_LeNet.py b/Deep-SAD-PyTorch/src/networks/fmnist_LeNet.py new file mode 100644 index 0000000..87bdb6d --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/fmnist_LeNet.py @@ -0,0 +1,76 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from base.base_net import BaseNet + + +class FashionMNIST_LeNet(BaseNet): + + def __init__(self, rep_dim=64): + super().__init__() + + self.rep_dim = rep_dim + self.pool = nn.MaxPool2d(2, 2) + + self.conv1 = nn.Conv2d(1, 16, 5, bias=False, padding=2) + self.bn2d1 = nn.BatchNorm2d(16, eps=1e-04, affine=False) + self.conv2 = nn.Conv2d(16, 32, 5, bias=False, padding=2) + self.bn2d2 = nn.BatchNorm2d(32, eps=1e-04, affine=False) + self.fc1 = nn.Linear(32 * 7 * 7, 128, bias=False) + self.bn1d1 = nn.BatchNorm1d(128, eps=1e-04, affine=False) + self.fc2 = nn.Linear(128, self.rep_dim, bias=False) + + def forward(self, x): + x = x.view(-1, 1, 28, 28) + x = self.conv1(x) + x = self.pool(F.leaky_relu(self.bn2d1(x))) + x = self.conv2(x) + x = self.pool(F.leaky_relu(self.bn2d2(x))) + x = x.view(int(x.size(0)), -1) + x = F.leaky_relu(self.bn1d1(self.fc1(x))) + x = self.fc2(x) + return x + + +class FashionMNIST_LeNet_Decoder(BaseNet): + + def __init__(self, rep_dim=64): + super().__init__() + + self.rep_dim = rep_dim + + self.fc3 = nn.Linear(self.rep_dim, 128, bias=False) + self.bn1d2 = nn.BatchNorm1d(128, eps=1e-04, affine=False) + self.deconv1 = nn.ConvTranspose2d(8, 32, 5, bias=False, padding=2) + self.bn2d3 = nn.BatchNorm2d(32, eps=1e-04, affine=False) + self.deconv2 = nn.ConvTranspose2d(32, 16, 5, bias=False, padding=3) + self.bn2d4 = nn.BatchNorm2d(16, eps=1e-04, affine=False) + self.deconv3 = nn.ConvTranspose2d(16, 1, 5, bias=False, padding=2) + + def forward(self, x): + x = self.bn1d2(self.fc3(x)) + x = x.view(int(x.size(0)), int(128 / 16), 4, 4) + x = F.interpolate(F.leaky_relu(x), scale_factor=2) + x = self.deconv1(x) + x = F.interpolate(F.leaky_relu(self.bn2d3(x)), scale_factor=2) + x = self.deconv2(x) + x = F.interpolate(F.leaky_relu(self.bn2d4(x)), scale_factor=2) + x = self.deconv3(x) + x = torch.sigmoid(x) + return x + + +class FashionMNIST_LeNet_Autoencoder(BaseNet): + + def __init__(self, rep_dim=64): + super().__init__() + + self.rep_dim = rep_dim + self.encoder = FashionMNIST_LeNet(rep_dim=rep_dim) + self.decoder = FashionMNIST_LeNet_Decoder(rep_dim=rep_dim) + + def forward(self, x): + x = self.encoder(x) + x = self.decoder(x) + return x diff --git a/Deep-SAD-PyTorch/src/networks/inference/distributions.py b/Deep-SAD-PyTorch/src/networks/inference/distributions.py new file mode 100644 index 0000000..f683048 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/inference/distributions.py @@ -0,0 +1,41 @@ +import math +import torch +import torch.nn.functional as F + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +def log_standard_gaussian(x): + """ + Evaluates the log pdf of a standard normal distribution at x. + + :param x: point to evaluate + :return: log N(x|0,I) + """ + return torch.sum(-0.5 * math.log(2 * math.pi) - x ** 2 / 2, dim=-1) + + +def log_gaussian(x, mu, log_var): + """ + Evaluates the log pdf of a normal distribution parametrized by mu and log_var at x. + + :param x: point to evaluate + :param mu: mean + :param log_var: log variance + :return: log N(x|µ,σI) + """ + log_pdf = -0.5 * math.log(2 * math.pi) - log_var / 2 - (x - mu)**2 / (2 * torch.exp(log_var)) + return torch.sum(log_pdf, dim=-1) + + +def log_standard_categorical(p): + """ + Computes the cross-entropy between a (one-hot) categorical vector and a standard (uniform) categorical distribution. + :param p: one-hot categorical distribution + :return: H(p,u) + """ + eps = 1e-8 + prior = F.softmax(torch.ones_like(p), dim=1) # Uniform prior over y + prior.requires_grad = False + cross_entropy = -torch.sum(p * torch.log(prior + eps), dim=1) + + return cross_entropy diff --git a/Deep-SAD-PyTorch/src/networks/layers/standard.py b/Deep-SAD-PyTorch/src/networks/layers/standard.py new file mode 100644 index 0000000..f7c6b3d --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/layers/standard.py @@ -0,0 +1,52 @@ +import torch + +from torch.nn import Module +from torch.nn import init +from torch.nn.parameter import Parameter + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +class Standardize(Module): + """ + Applies (element-wise) standardization with trainable translation parameter μ and scale parameter σ, i.e. computes + (x - μ) / σ where '/' is applied element-wise. + + Args: + in_features: size of each input sample + out_features: size of each output sample + bias: If set to False, the layer will not learn a translation parameter μ. + Default: ``True`` + + Attributes: + mu: the learnable translation parameter μ. + std: the learnable scale parameter σ. + """ + __constants__ = ['mu'] + + def __init__(self, in_features, bias=True, eps=1e-6): + super(Standardize, self).__init__() + self.in_features = in_features + self.out_features = in_features + self.eps = eps + self.std = Parameter(torch.Tensor(in_features)) + if bias: + self.mu = Parameter(torch.Tensor(in_features)) + else: + self.register_parameter('mu', None) + self.reset_parameters() + + def reset_parameters(self): + init.constant_(self.std, 1) + if self.mu is not None: + init.constant_(self.mu, 0) + + def forward(self, x): + if self.mu is not None: + x -= self.mu + x = torch.div(x, self.std + self.eps) + return x + + def extra_repr(self): + return 'in_features={}, out_features={}, bias={}'.format( + self.in_features, self.out_features, self.mu is not None + ) diff --git a/Deep-SAD-PyTorch/src/networks/layers/stochastic.py b/Deep-SAD-PyTorch/src/networks/layers/stochastic.py new file mode 100644 index 0000000..00db50e --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/layers/stochastic.py @@ -0,0 +1,53 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from torch.autograd import Variable + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +class Stochastic(nn.Module): + """ + Base stochastic layer that uses the reparametrization trick (Kingma and Welling, 2013) to draw a sample from a + distribution parametrized by mu and log_var. + """ + + def __init__(self): + super(Stochastic, self).__init__() + + def reparametrize(self, mu, log_var): + epsilon = Variable(torch.randn(mu.size()), requires_grad=False) + + if mu.is_cuda: + epsilon = epsilon.to(mu.device) + + # log_std = 0.5 * log_var + # std = exp(log_std) + std = log_var.mul(0.5).exp_() + + # z = std * epsilon + mu + z = mu.addcmul(std, epsilon) + + return z + + def forward(self, x): + raise NotImplementedError + + +class GaussianSample(Stochastic): + """ + Layer that represents a sample from a Gaussian distribution. + """ + + def __init__(self, in_features, out_features): + super(GaussianSample, self).__init__() + self.in_features = in_features + self.out_features = out_features + + self.mu = nn.Linear(in_features, out_features) + self.log_var = nn.Linear(in_features, out_features) + + def forward(self, x): + mu = self.mu(x) + log_var = F.softplus(self.log_var(x)) + return self.reparametrize(mu, log_var), mu, log_var diff --git a/Deep-SAD-PyTorch/src/networks/main.py b/Deep-SAD-PyTorch/src/networks/main.py new file mode 100644 index 0000000..5d6d4cf --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/main.py @@ -0,0 +1,138 @@ +from .mnist_LeNet import MNIST_LeNet, MNIST_LeNet_Autoencoder +from .fmnist_LeNet import FashionMNIST_LeNet, FashionMNIST_LeNet_Autoencoder +from .cifar10_LeNet import CIFAR10_LeNet, CIFAR10_LeNet_Autoencoder +from .mlp import MLP, MLP_Autoencoder +from .vae import VariationalAutoencoder +from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel + + +def build_network(net_name, ae_net=None): + """Builds the neural network.""" + + implemented_networks = ('mnist_LeNet', 'mnist_DGM_M2', 'mnist_DGM_M1M2', + 'fmnist_LeNet', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2', + 'cifar10_LeNet', 'cifar10_DGM_M2', 'cifar10_DGM_M1M2', + 'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', + 'thyroid_mlp', + 'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2', 'satimage-2_DGM_M2', + 'shuttle_DGM_M2', 'thyroid_DGM_M2') + assert net_name in implemented_networks + + net = None + + if net_name == 'mnist_LeNet': + net = MNIST_LeNet() + + if net_name == 'mnist_DGM_M2': + net = DeepGenerativeModel([1*28*28, 2, 32, [128, 64]], classifier_net=MNIST_LeNet) + + if net_name == 'mnist_DGM_M1M2': + net = StackedDeepGenerativeModel([1*28*28, 2, 32, [128, 64]], features=ae_net) + + if net_name == 'fmnist_LeNet': + net = FashionMNIST_LeNet() + + if net_name == 'fmnist_DGM_M2': + net = DeepGenerativeModel([1*28*28, 2, 64, [256, 128]], classifier_net=FashionMNIST_LeNet) + + if net_name == 'fmnist_DGM_M1M2': + net = StackedDeepGenerativeModel([1*28*28, 2, 64, [256, 128]], features=ae_net) + + if net_name == 'cifar10_LeNet': + net = CIFAR10_LeNet() + + if net_name == 'cifar10_DGM_M2': + net = DeepGenerativeModel([3*32*32, 2, 128, [512, 256]], classifier_net=CIFAR10_LeNet) + + if net_name == 'cifar10_DGM_M1M2': + net = StackedDeepGenerativeModel([3*32*32, 2, 128, [512, 256]], features=ae_net) + + if net_name == 'arrhythmia_mlp': + net = MLP(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False) + + if net_name == 'cardio_mlp': + net = MLP(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'satellite_mlp': + net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'satimage-2_mlp': + net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'shuttle_mlp': + net = MLP(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'thyroid_mlp': + net = MLP(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False) + + if net_name == 'arrhythmia_DGM_M2': + net = DeepGenerativeModel([274, 2, 32, [128, 64]]) + + if net_name == 'cardio_DGM_M2': + net = DeepGenerativeModel([21, 2, 8, [32, 16]]) + + if net_name == 'satellite_DGM_M2': + net = DeepGenerativeModel([36, 2, 8, [32, 16]]) + + if net_name == 'satimage-2_DGM_M2': + net = DeepGenerativeModel([36, 2, 8, [32, 16]]) + + if net_name == 'shuttle_DGM_M2': + net = DeepGenerativeModel([9, 2, 8, [32, 16]]) + + if net_name == 'thyroid_DGM_M2': + net = DeepGenerativeModel([6, 2, 4, [32, 16]]) + + return net + + +def build_autoencoder(net_name): + """Builds the corresponding autoencoder network.""" + + implemented_networks = ('mnist_LeNet', 'mnist_DGM_M1M2', + 'fmnist_LeNet', 'fmnist_DGM_M1M2', + 'cifar10_LeNet', 'cifar10_DGM_M1M2', + 'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', + 'thyroid_mlp') + + assert net_name in implemented_networks + + ae_net = None + + if net_name == 'mnist_LeNet': + ae_net = MNIST_LeNet_Autoencoder() + + if net_name == 'mnist_DGM_M1M2': + ae_net = VariationalAutoencoder([1*28*28, 32, [128, 64]]) + + if net_name == 'fmnist_LeNet': + ae_net = FashionMNIST_LeNet_Autoencoder() + + if net_name == 'fmnist_DGM_M1M2': + ae_net = VariationalAutoencoder([1*28*28, 64, [256, 128]]) + + if net_name == 'cifar10_LeNet': + ae_net = CIFAR10_LeNet_Autoencoder() + + if net_name == 'cifar10_DGM_M1M2': + ae_net = VariationalAutoencoder([3*32*32, 128, [512, 256]]) + + if net_name == 'arrhythmia_mlp': + ae_net = MLP_Autoencoder(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False) + + if net_name == 'cardio_mlp': + ae_net = MLP_Autoencoder(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'satellite_mlp': + ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'satimage-2_mlp': + ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'shuttle_mlp': + ae_net = MLP_Autoencoder(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False) + + if net_name == 'thyroid_mlp': + ae_net = MLP_Autoencoder(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False) + + return ae_net diff --git a/Deep-SAD-PyTorch/src/networks/mlp.py b/Deep-SAD-PyTorch/src/networks/mlp.py new file mode 100644 index 0000000..7754931 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/mlp.py @@ -0,0 +1,76 @@ +import torch.nn as nn +import torch.nn.functional as F + +from base.base_net import BaseNet + + +class MLP(BaseNet): + + def __init__(self, x_dim, h_dims=[128, 64], rep_dim=32, bias=False): + super().__init__() + + self.rep_dim = rep_dim + + neurons = [x_dim, *h_dims] + layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))] + + self.hidden = nn.ModuleList(layers) + self.code = nn.Linear(h_dims[-1], rep_dim, bias=bias) + + def forward(self, x): + x = x.view(int(x.size(0)), -1) + for layer in self.hidden: + x = layer(x) + return self.code(x) + + +class MLP_Decoder(BaseNet): + + def __init__(self, x_dim, h_dims=[64, 128], rep_dim=32, bias=False): + super().__init__() + + self.rep_dim = rep_dim + + neurons = [rep_dim, *h_dims] + layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))] + + self.hidden = nn.ModuleList(layers) + self.reconstruction = nn.Linear(h_dims[-1], x_dim, bias=bias) + self.output_activation = nn.Sigmoid() + + def forward(self, x): + x = x.view(int(x.size(0)), -1) + for layer in self.hidden: + x = layer(x) + x = self.reconstruction(x) + return self.output_activation(x) + + +class MLP_Autoencoder(BaseNet): + + def __init__(self, x_dim, h_dims=[128, 64], rep_dim=32, bias=False): + super().__init__() + + self.rep_dim = rep_dim + self.encoder = MLP(x_dim, h_dims, rep_dim, bias) + self.decoder = MLP_Decoder(x_dim, list(reversed(h_dims)), rep_dim, bias) + + def forward(self, x): + x = self.encoder(x) + x = self.decoder(x) + return x + + +class Linear_BN_leakyReLU(nn.Module): + """ + A nn.Module that consists of a Linear layer followed by BatchNorm1d and a leaky ReLu activation + """ + + def __init__(self, in_features, out_features, bias=False, eps=1e-04): + super(Linear_BN_leakyReLU, self).__init__() + + self.linear = nn.Linear(in_features, out_features, bias=bias) + self.bn = nn.BatchNorm1d(out_features, eps=eps, affine=bias) + + def forward(self, x): + return F.leaky_relu(self.bn(self.linear(x))) diff --git a/Deep-SAD-PyTorch/src/networks/mnist_LeNet.py b/Deep-SAD-PyTorch/src/networks/mnist_LeNet.py new file mode 100644 index 0000000..28e101d --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/mnist_LeNet.py @@ -0,0 +1,71 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +from base.base_net import BaseNet + + +class MNIST_LeNet(BaseNet): + + def __init__(self, rep_dim=32): + super().__init__() + + self.rep_dim = rep_dim + self.pool = nn.MaxPool2d(2, 2) + + self.conv1 = nn.Conv2d(1, 8, 5, bias=False, padding=2) + self.bn1 = nn.BatchNorm2d(8, eps=1e-04, affine=False) + self.conv2 = nn.Conv2d(8, 4, 5, bias=False, padding=2) + self.bn2 = nn.BatchNorm2d(4, eps=1e-04, affine=False) + self.fc1 = nn.Linear(4 * 7 * 7, self.rep_dim, bias=False) + + def forward(self, x): + x = x.view(-1, 1, 28, 28) + x = self.conv1(x) + x = self.pool(F.leaky_relu(self.bn1(x))) + x = self.conv2(x) + x = self.pool(F.leaky_relu(self.bn2(x))) + x = x.view(int(x.size(0)), -1) + x = self.fc1(x) + return x + + +class MNIST_LeNet_Decoder(BaseNet): + + def __init__(self, rep_dim=32): + super().__init__() + + self.rep_dim = rep_dim + + # Decoder network + self.deconv1 = nn.ConvTranspose2d(2, 4, 5, bias=False, padding=2) + self.bn3 = nn.BatchNorm2d(4, eps=1e-04, affine=False) + self.deconv2 = nn.ConvTranspose2d(4, 8, 5, bias=False, padding=3) + self.bn4 = nn.BatchNorm2d(8, eps=1e-04, affine=False) + self.deconv3 = nn.ConvTranspose2d(8, 1, 5, bias=False, padding=2) + + def forward(self, x): + x = x.view(int(x.size(0)), int(self.rep_dim / 16), 4, 4) + x = F.interpolate(F.leaky_relu(x), scale_factor=2) + x = self.deconv1(x) + x = F.interpolate(F.leaky_relu(self.bn3(x)), scale_factor=2) + x = self.deconv2(x) + x = F.interpolate(F.leaky_relu(self.bn4(x)), scale_factor=2) + x = self.deconv3(x) + x = torch.sigmoid(x) + return x + + +class MNIST_LeNet_Autoencoder(BaseNet): + + def __init__(self, rep_dim=32): + super().__init__() + + self.rep_dim = rep_dim + self.encoder = MNIST_LeNet(rep_dim=rep_dim) + self.decoder = MNIST_LeNet_Decoder(rep_dim=rep_dim) + + def forward(self, x): + x = self.encoder(x) + x = self.decoder(x) + return x diff --git a/Deep-SAD-PyTorch/src/networks/vae.py b/Deep-SAD-PyTorch/src/networks/vae.py new file mode 100644 index 0000000..08f9104 --- /dev/null +++ b/Deep-SAD-PyTorch/src/networks/vae.py @@ -0,0 +1,145 @@ +import torch.nn as nn +import torch.nn.functional as F +from torch.nn import init + +from .layers.stochastic import GaussianSample +from .inference.distributions import log_standard_gaussian, log_gaussian + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +class Encoder(nn.Module): + """ + Encoder, i.e. the inference network. + + Attempts to infer the latent probability distribution p(z|x) from the data x by fitting a + variational distribution q_φ(z|x). Returns the two parameters of the distribution (µ, log σ²). + + :param dims: dimensions of the network given by [input_dim, [hidden_dims], latent_dim]. + """ + + def __init__(self, dims, sample_layer=GaussianSample): + super(Encoder, self).__init__() + + [x_dim, h_dim, z_dim] = dims + neurons = [x_dim, *h_dim] + linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))] + + self.hidden = nn.ModuleList(linear_layers) + self.sample = sample_layer(h_dim[-1], z_dim) + + def forward(self, x): + for layer in self.hidden: + x = F.relu(layer(x)) + return self.sample(x) + + +class Decoder(nn.Module): + """ + Decoder, i.e. the generative network. + + Generates samples from an approximation p_θ(x|z) of the original distribution p(x) + by transforming a latent representation z. + + :param dims: dimensions of the network given by [latent_dim, [hidden_dims], input_dim]. + """ + + def __init__(self, dims): + super(Decoder, self).__init__() + + [z_dim, h_dim, x_dim] = dims + neurons = [z_dim, *h_dim] + linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))] + + self.hidden = nn.ModuleList(linear_layers) + self.reconstruction = nn.Linear(h_dim[-1], x_dim) + self.output_activation = nn.Sigmoid() + + def forward(self, x): + for layer in self.hidden: + x = F.relu(layer(x)) + return self.output_activation(self.reconstruction(x)) + + +class VariationalAutoencoder(nn.Module): + """ + Variational Autoencoder (VAE) (Kingma and Welling, 2013) model consisting of an encoder-decoder pair for which + a variational distribution is fitted to the encoder. + Also known as the M1 model in (Kingma et al., 2014) + + :param dims: dimensions of the networks given by [input_dim, latent_dim, [hidden_dims]]. Encoder and decoder + are build symmetrically. + """ + + def __init__(self, dims): + super(VariationalAutoencoder, self).__init__() + + [x_dim, z_dim, h_dim] = dims + self.z_dim = z_dim + self.flow = None + + self.encoder = Encoder([x_dim, h_dim, z_dim]) + self.decoder = Decoder([z_dim, list(reversed(h_dim)), x_dim]) + self.kl_divergence = 0 + + # Init linear layers + for m in self.modules(): + if isinstance(m, nn.Linear): + init.xavier_normal_(m.weight.data) + if m.bias is not None: + m.bias.data.zero_() + + def _kld(self, z, q_param, p_param=None): + """ + Computes the KL-divergence of some latent variable z. + + KL(q||p) = - ∫ q(z) log [ p(z) / q(z) ] = - E_q[ log p(z) - log q(z) ] + + :param z: sample from q-distribuion + :param q_param: (mu, log_var) of the q-distribution + :param p_param: (mu, log_var) of the p-distribution + :return: KL(q||p) + """ + (mu, log_var) = q_param + + if self.flow is not None: + f_z, log_det_z = self.flow(z) + qz = log_gaussian(z, mu, log_var) - sum(log_det_z) + z = f_z + else: + qz = log_gaussian(z, mu, log_var) + + if p_param is None: + pz = log_standard_gaussian(z) + else: + (mu, log_var) = p_param + pz = log_gaussian(z, mu, log_var) + + kl = qz - pz + + return kl + + def add_flow(self, flow): + self.flow = flow + + def forward(self, x, y=None): + """ + Runs a forward pass on a data point through the VAE model to provide its reconstruction and the parameters of + the variational approximate distribution q. + + :param x: input data + :return: reconstructed input + """ + z, q_mu, q_log_var = self.encoder(x) + self.kl_divergence = self._kld(z, (q_mu, q_log_var)) + rec = self.decoder(z) + + return rec + + def sample(self, z): + """ + Given z ~ N(0, I) generates a sample from the learned distribution based on p_θ(x|z). + + :param z: (torch.autograd.Variable) latent normal variable + :return: (torch.autograd.Variable) generated sample + """ + return self.decoder(z) diff --git a/Deep-SAD-PyTorch/src/optim/DeepSAD_trainer.py b/Deep-SAD-PyTorch/src/optim/DeepSAD_trainer.py new file mode 100644 index 0000000..44b1118 --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/DeepSAD_trainer.py @@ -0,0 +1,173 @@ +from base.base_trainer import BaseTrainer +from base.base_dataset import BaseADDataset +from base.base_net import BaseNet +from torch.utils.data.dataloader import DataLoader +from sklearn.metrics import roc_auc_score + +import logging +import time +import torch +import torch.optim as optim +import numpy as np + + +class DeepSADTrainer(BaseTrainer): + + def __init__(self, c, eta: float, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, + lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', + n_jobs_dataloader: int = 0): + super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, + n_jobs_dataloader) + + # Deep SAD parameters + self.c = torch.tensor(c, device=self.device) if c is not None else None + self.eta = eta + + # Optimization parameters + self.eps = 1e-6 + + # Results + self.train_time = None + self.test_auc = None + self.test_time = None + self.test_scores = None + + def train(self, dataset: BaseADDataset, net: BaseNet): + logger = logging.getLogger() + + # Get train data loader + train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device for network + net = net.to(self.device) + + # Set optimizer (Adam optimizer for now) + optimizer = optim.Adam(net.parameters(), lr=self.lr, weight_decay=self.weight_decay) + + # Set learning rate scheduler + scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) + + # Initialize hypersphere center c (if c not loaded) + if self.c is None: + logger.info('Initializing center c...') + self.c = self.init_center_c(train_loader, net) + logger.info('Center c initialized.') + + # Training + logger.info('Starting training...') + start_time = time.time() + net.train() + for epoch in range(self.n_epochs): + + scheduler.step() + if epoch in self.lr_milestones: + logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) + + epoch_loss = 0.0 + n_batches = 0 + epoch_start_time = time.time() + for data in train_loader: + inputs, _, semi_targets, _ = data + inputs, semi_targets = inputs.to(self.device), semi_targets.to(self.device) + + # Zero the network parameter gradients + optimizer.zero_grad() + + # Update network parameters via backpropagation: forward + backward + optimize + outputs = net(inputs) + dist = torch.sum((outputs - self.c) ** 2, dim=1) + losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float())) + loss = torch.mean(losses) + loss.backward() + optimizer.step() + + epoch_loss += loss.item() + n_batches += 1 + + # log epoch statistics + epoch_train_time = time.time() - epoch_start_time + logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s ' + f'| Train Loss: {epoch_loss / n_batches:.6f} |') + + self.train_time = time.time() - start_time + logger.info('Training Time: {:.3f}s'.format(self.train_time)) + logger.info('Finished training.') + + return net + + def test(self, dataset: BaseADDataset, net: BaseNet): + logger = logging.getLogger() + + # Get test data loader + _, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device for network + net = net.to(self.device) + + # Testing + logger.info('Starting testing...') + epoch_loss = 0.0 + n_batches = 0 + start_time = time.time() + idx_label_score = [] + net.eval() + with torch.no_grad(): + for data in test_loader: + inputs, labels, semi_targets, idx = data + + inputs = inputs.to(self.device) + labels = labels.to(self.device) + semi_targets = semi_targets.to(self.device) + idx = idx.to(self.device) + + outputs = net(inputs) + dist = torch.sum((outputs - self.c) ** 2, dim=1) + losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float())) + loss = torch.mean(losses) + scores = dist + + # Save triples of (idx, label, score) in a list + idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), + labels.cpu().data.numpy().tolist(), + scores.cpu().data.numpy().tolist())) + + epoch_loss += loss.item() + n_batches += 1 + + self.test_time = time.time() - start_time + self.test_scores = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.test_auc = roc_auc_score(labels, scores) + + # Log results + logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) + logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) + logger.info('Test Time: {:.3f}s'.format(self.test_time)) + logger.info('Finished testing.') + + def init_center_c(self, train_loader: DataLoader, net: BaseNet, eps=0.1): + """Initialize hypersphere center c as the mean from an initial forward pass on the data.""" + n_samples = 0 + c = torch.zeros(net.rep_dim, device=self.device) + + net.eval() + with torch.no_grad(): + for data in train_loader: + # get the inputs of the batch + inputs, _, _, _ = data + inputs = inputs.to(self.device) + outputs = net(inputs) + n_samples += outputs.shape[0] + c += torch.sum(outputs, dim=0) + + c /= n_samples + + # If c_i is too close to 0, set to +-eps. Reason: a zero unit can be trivially matched with zero weights. + c[(abs(c) < eps) & (c < 0)] = -eps + c[(abs(c) < eps) & (c > 0)] = eps + + return c diff --git a/Deep-SAD-PyTorch/src/optim/SemiDGM_trainer.py b/Deep-SAD-PyTorch/src/optim/SemiDGM_trainer.py new file mode 100644 index 0000000..b0269d0 --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/SemiDGM_trainer.py @@ -0,0 +1,188 @@ +from base.base_trainer import BaseTrainer +from base.base_dataset import BaseADDataset +from base.base_net import BaseNet +from optim.variational import SVI, ImportanceWeightedSampler +from utils.misc import binary_cross_entropy +from sklearn.metrics import roc_auc_score + +import logging +import time +import torch +import torch.optim as optim +import numpy as np + + +class SemiDeepGenerativeTrainer(BaseTrainer): + + def __init__(self, alpha: float = 0.1, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, + lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', + n_jobs_dataloader: int = 0): + super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, + n_jobs_dataloader) + + self.alpha = alpha + + # Results + self.train_time = None + self.test_auc = None + self.test_time = None + self.test_scores = None + + def train(self, dataset: BaseADDataset, net: BaseNet): + logger = logging.getLogger() + + # Get train data loader + train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device + net = net.to(self.device) + + # Use importance weighted sampler (Burda et al., 2015) to get a better estimate on the log-likelihood. + sampler = ImportanceWeightedSampler(mc=1, iw=1) + elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler) + + # Set optimizer (Adam optimizer for now) + optimizer = optim.Adam(net.parameters(), lr=self.lr, weight_decay=self.weight_decay) + + # Set learning rate scheduler + scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) + + # Training + logger.info('Starting training...') + start_time = time.time() + net.train() + for epoch in range(self.n_epochs): + + scheduler.step() + if epoch in self.lr_milestones: + logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) + + epoch_loss = 0.0 + n_batches = 0 + epoch_start_time = time.time() + for data in train_loader: + inputs, labels, semi_targets, _ = data + + inputs = inputs.to(self.device) + labels = labels.to(self.device) + semi_targets = semi_targets.to(self.device) + + # Get labeled and unlabeled data and make labels one-hot + inputs = inputs.view(inputs.size(0), -1) + x = inputs[semi_targets != 0] + u = inputs[semi_targets == 0] + y = labels[semi_targets != 0] + if y.nelement() > 1: + y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier + y_onehot.zero_() + y_onehot.scatter_(1, y.view(-1, 1), 1) + + # Zero the network parameter gradients + optimizer.zero_grad() + + # Update network parameters via backpropagation: forward + backward + optimize + if y.nelement() < 2: + L = torch.tensor(0.0).to(self.device) + else: + L = -elbo(x, y_onehot) + U = -elbo(u) + + # Regular cross entropy + if y.nelement() < 2: + classication_loss = torch.tensor(0.0).to(self.device) + else: + # Add auxiliary classification loss q(y|x) + logits = net.classify(x) + eps = 1e-8 + classication_loss = torch.sum(y_onehot * torch.log(logits + eps), dim=1).mean() + + # Overall loss + loss = L - self.alpha * classication_loss + U # J_alpha + + loss.backward() + optimizer.step() + + epoch_loss += loss.item() + n_batches += 1 + + # log epoch statistics + epoch_train_time = time.time() - epoch_start_time + logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s ' + f'| Train Loss: {epoch_loss / n_batches:.6f} |') + + self.train_time = time.time() - start_time + logger.info('Training Time: {:.3f}s'.format(self.train_time)) + logger.info('Finished training.') + + return net + + def test(self, dataset: BaseADDataset, net: BaseNet): + logger = logging.getLogger() + + # Get test data loader + _, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device + net = net.to(self.device) + + # Use importance weighted sampler (Burda et al., 2015) to get a better estimate on the log-likelihood. + sampler = ImportanceWeightedSampler(mc=1, iw=1) + elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler) + + # Testing + logger.info('Starting testing...') + epoch_loss = 0.0 + n_batches = 0 + start_time = time.time() + idx_label_score = [] + net.eval() + with torch.no_grad(): + for data in test_loader: + inputs, labels, _, idx = data + inputs = inputs.to(self.device) + labels = labels.to(self.device) + idx = idx.to(self.device) + + # All test data is considered unlabeled + inputs = inputs.view(inputs.size(0), -1) + u = inputs + y = labels + y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier + y_onehot.zero_() + y_onehot.scatter_(1, y.view(-1, 1), 1) + + # Compute loss + L = -elbo(u, y_onehot) + U = -elbo(u) + + logits = net.classify(u) + eps = 1e-8 + classication_loss = -torch.sum(y_onehot * torch.log(logits + eps), dim=1).mean() + + loss = L + self.alpha * classication_loss + U # J_alpha + + # Compute scores + scores = logits[:, 1] # likelihood/confidence for anomalous class as anomaly score + + # Save triple of (idx, label, score) in a list + idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), + labels.cpu().data.numpy().tolist(), + scores.cpu().data.numpy().tolist())) + + epoch_loss += loss.item() + n_batches += 1 + + self.test_time = time.time() - start_time + self.test_scores = idx_label_score + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.test_auc = roc_auc_score(labels, scores) + + # Log results + logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) + logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) + logger.info('Test Time: {:.3f}s'.format(self.test_time)) + logger.info('Finished testing.') diff --git a/Deep-SAD-PyTorch/src/optim/__init__.py b/Deep-SAD-PyTorch/src/optim/__init__.py new file mode 100644 index 0000000..dd91307 --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/__init__.py @@ -0,0 +1,5 @@ +from .DeepSAD_trainer import DeepSADTrainer +from .ae_trainer import AETrainer +from .SemiDGM_trainer import SemiDeepGenerativeTrainer +from .vae_trainer import VAETrainer +from .variational import SVI, ImportanceWeightedSampler diff --git a/Deep-SAD-PyTorch/src/optim/ae_trainer.py b/Deep-SAD-PyTorch/src/optim/ae_trainer.py new file mode 100644 index 0000000..c148b16 --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/ae_trainer.py @@ -0,0 +1,136 @@ +from base.base_trainer import BaseTrainer +from base.base_dataset import BaseADDataset +from base.base_net import BaseNet +from sklearn.metrics import roc_auc_score + +import logging +import time +import torch +import torch.nn as nn +import torch.optim as optim +import numpy as np + + +class AETrainer(BaseTrainer): + + def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (), + batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0): + super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, + n_jobs_dataloader) + + # Results + self.train_time = None + self.test_auc = None + self.test_time = None + + def train(self, dataset: BaseADDataset, ae_net: BaseNet): + logger = logging.getLogger() + + # Get train data loader + train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set loss + criterion = nn.MSELoss(reduction='none') + + # Set device + ae_net = ae_net.to(self.device) + criterion = criterion.to(self.device) + + # Set optimizer (Adam optimizer for now) + optimizer = optim.Adam(ae_net.parameters(), lr=self.lr, weight_decay=self.weight_decay) + + # Set learning rate scheduler + scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) + + # Training + logger.info('Starting pretraining...') + start_time = time.time() + ae_net.train() + for epoch in range(self.n_epochs): + + scheduler.step() + if epoch in self.lr_milestones: + logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) + + epoch_loss = 0.0 + n_batches = 0 + epoch_start_time = time.time() + for data in train_loader: + inputs, _, _, _ = data + inputs = inputs.to(self.device) + + # Zero the network parameter gradients + optimizer.zero_grad() + + # Update network parameters via backpropagation: forward + backward + optimize + rec = ae_net(inputs) + rec_loss = criterion(rec, inputs) + loss = torch.mean(rec_loss) + loss.backward() + optimizer.step() + + epoch_loss += loss.item() + n_batches += 1 + + # log epoch statistics + epoch_train_time = time.time() - epoch_start_time + logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s ' + f'| Train Loss: {epoch_loss / n_batches:.6f} |') + + self.train_time = time.time() - start_time + logger.info('Pretraining Time: {:.3f}s'.format(self.train_time)) + logger.info('Finished pretraining.') + + return ae_net + + def test(self, dataset: BaseADDataset, ae_net: BaseNet): + logger = logging.getLogger() + + # Get test data loader + _, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set loss + criterion = nn.MSELoss(reduction='none') + + # Set device for network + ae_net = ae_net.to(self.device) + criterion = criterion.to(self.device) + + # Testing + logger.info('Testing autoencoder...') + epoch_loss = 0.0 + n_batches = 0 + start_time = time.time() + idx_label_score = [] + ae_net.eval() + with torch.no_grad(): + for data in test_loader: + inputs, labels, _, idx = data + inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device) + + rec = ae_net(inputs) + rec_loss = criterion(rec, inputs) + scores = torch.mean(rec_loss, dim=tuple(range(1, rec.dim()))) + + # Save triple of (idx, label, score) in a list + idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), + labels.cpu().data.numpy().tolist(), + scores.cpu().data.numpy().tolist())) + + loss = torch.mean(rec_loss) + epoch_loss += loss.item() + n_batches += 1 + + self.test_time = time.time() - start_time + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.test_auc = roc_auc_score(labels, scores) + + # Log results + logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) + logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) + logger.info('Test Time: {:.3f}s'.format(self.test_time)) + logger.info('Finished testing autoencoder.') diff --git a/Deep-SAD-PyTorch/src/optim/vae_trainer.py b/Deep-SAD-PyTorch/src/optim/vae_trainer.py new file mode 100644 index 0000000..940214c --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/vae_trainer.py @@ -0,0 +1,139 @@ +from base.base_trainer import BaseTrainer +from base.base_dataset import BaseADDataset +from base.base_net import BaseNet +from utils.misc import binary_cross_entropy +from sklearn.metrics import roc_auc_score + +import logging +import time +import torch +import torch.optim as optim +import numpy as np + + +class VAETrainer(BaseTrainer): + + def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (), + batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0): + super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, + n_jobs_dataloader) + + # Results + self.train_time = None + self.test_auc = None + self.test_time = None + + def train(self, dataset: BaseADDataset, vae: BaseNet): + logger = logging.getLogger() + + # Get train data loader + train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device + vae = vae.to(self.device) + + # Set optimizer (Adam optimizer for now) + optimizer = optim.Adam(vae.parameters(), lr=self.lr, weight_decay=self.weight_decay) + + # Set learning rate scheduler + scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) + + # Training + logger.info('Starting pretraining...') + start_time = time.time() + vae.train() + for epoch in range(self.n_epochs): + + scheduler.step() + if epoch in self.lr_milestones: + logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) + + epoch_loss = 0.0 + n_batches = 0 + epoch_start_time = time.time() + for data in train_loader: + inputs, _, _, _ = data + inputs = inputs.to(self.device) + inputs = inputs.view(inputs.size(0), -1) + + # Zero the network parameter gradients + optimizer.zero_grad() + + # Update network parameters via backpropagation: forward + backward + optimize + rec = vae(inputs) + + likelihood = -binary_cross_entropy(rec, inputs) + elbo = likelihood - vae.kl_divergence + + # Overall loss + loss = -torch.mean(elbo) + + loss.backward() + optimizer.step() + + epoch_loss += loss.item() + n_batches += 1 + + # log epoch statistics + epoch_train_time = time.time() - epoch_start_time + logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s ' + f'| Train Loss: {epoch_loss / n_batches:.6f} |') + + self.train_time = time.time() - start_time + logger.info('Pretraining Time: {:.3f}s'.format(self.train_time)) + logger.info('Finished pretraining.') + + return vae + + def test(self, dataset: BaseADDataset, vae: BaseNet): + logger = logging.getLogger() + + # Get test data loader + _, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) + + # Set device + vae = vae.to(self.device) + + # Testing + logger.info('Starting testing...') + epoch_loss = 0.0 + n_batches = 0 + start_time = time.time() + idx_label_score = [] + vae.eval() + with torch.no_grad(): + for data in test_loader: + inputs, labels, _, idx = data + inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device) + + inputs = inputs.view(inputs.size(0), -1) + + rec = vae(inputs) + likelihood = -binary_cross_entropy(rec, inputs) + scores = -likelihood # negative likelihood as anomaly score + + # Save triple of (idx, label, score) in a list + idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), + labels.cpu().data.numpy().tolist(), + scores.cpu().data.numpy().tolist())) + + # Overall loss + elbo = likelihood - vae.kl_divergence + loss = -torch.mean(elbo) + + epoch_loss += loss.item() + n_batches += 1 + + self.test_time = time.time() - start_time + + # Compute AUC + _, labels, scores = zip(*idx_label_score) + labels = np.array(labels) + scores = np.array(scores) + self.test_auc = roc_auc_score(labels, scores) + + # Log results + logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) + logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) + logger.info('Test Time: {:.3f}s'.format(self.test_time)) + logger.info('Finished testing variational autoencoder.') diff --git a/Deep-SAD-PyTorch/src/optim/variational.py b/Deep-SAD-PyTorch/src/optim/variational.py new file mode 100644 index 0000000..7444fbc --- /dev/null +++ b/Deep-SAD-PyTorch/src/optim/variational.py @@ -0,0 +1,93 @@ +import torch +import torch.nn.functional as F + +from torch import nn +from itertools import repeat +from utils import enumerate_discrete, log_sum_exp +from networks import log_standard_categorical + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +class ImportanceWeightedSampler(object): + """ + Importance weighted sampler (Burda et al., 2015) to be used together with SVI. + + :param mc: number of Monte Carlo samples + :param iw: number of Importance Weighted samples + """ + + def __init__(self, mc=1, iw=1): + self.mc = mc + self.iw = iw + + def resample(self, x): + return x.repeat(self.mc * self.iw, 1) + + def __call__(self, elbo): + elbo = elbo.view(self.mc, self.iw, -1) + elbo = torch.mean(log_sum_exp(elbo, dim=1, sum_op=torch.mean), dim=0) + return elbo.view(-1) + + +class SVI(nn.Module): + """ + Stochastic variational inference (SVI) optimizer for semi-supervised learning. + + :param model: semi-supervised model to evaluate + :param likelihood: p(x|y,z) for example BCE or MSE + :param beta: warm-up/scaling of KL-term + :param sampler: sampler for x and y, e.g. for Monte Carlo + """ + + base_sampler = ImportanceWeightedSampler(mc=1, iw=1) + + def __init__(self, model, likelihood=F.binary_cross_entropy, beta=repeat(1), sampler=base_sampler): + super(SVI, self).__init__() + self.model = model + self.likelihood = likelihood + self.sampler = sampler + self.beta = beta + + def forward(self, x, y=None): + is_labeled = False if y is None else True + + # Prepare for sampling + xs, ys = (x, y) + + # Enumerate choices of label + if not is_labeled: + ys = enumerate_discrete(xs, self.model.y_dim) + xs = xs.repeat(self.model.y_dim, 1) + + # Increase sampling dimension + xs = self.sampler.resample(xs) + ys = self.sampler.resample(ys) + + reconstruction = self.model(xs, ys) + + # p(x|y,z) + likelihood = -self.likelihood(reconstruction, xs) + + # p(y) + prior = -log_standard_categorical(ys) + + # Equivalent to -L(x, y) + elbo = likelihood + prior - next(self.beta) * self.model.kl_divergence + L = self.sampler(elbo) + + if is_labeled: + return torch.mean(L) + + logits = self.model.classify(x) + + L = L.view_as(logits.t()).t() + + # Calculate entropy H(q(y|x)) and sum over all labels + eps = 1e-8 + H = -torch.sum(torch.mul(logits, torch.log(logits + eps)), dim=-1) + L = torch.sum(torch.mul(logits, L), dim=-1) + + # Equivalent to -U(x) + U = L + H + + return torch.mean(U) diff --git a/Deep-SAD-PyTorch/src/utils/__init__.py b/Deep-SAD-PyTorch/src/utils/__init__.py new file mode 100644 index 0000000..c77c0b7 --- /dev/null +++ b/Deep-SAD-PyTorch/src/utils/__init__.py @@ -0,0 +1,3 @@ +from .config import Config +from .visualization.plot_images_grid import plot_images_grid +from .misc import enumerate_discrete, log_sum_exp, binary_cross_entropy diff --git a/Deep-SAD-PyTorch/src/utils/config.py b/Deep-SAD-PyTorch/src/utils/config.py new file mode 100644 index 0000000..ee06a08 --- /dev/null +++ b/Deep-SAD-PyTorch/src/utils/config.py @@ -0,0 +1,23 @@ +import json + + +class Config(object): + """Base class for experimental setting/configuration.""" + + def __init__(self, settings): + self.settings = settings + + def load_config(self, import_json): + """Load settings dict from import_json (path/filename.json) JSON-file.""" + + with open(import_json, 'r') as fp: + settings = json.load(fp) + + for key, value in settings.items(): + self.settings[key] = value + + def save_config(self, export_json): + """Save settings dict to export_json (path/filename.json) JSON-file.""" + + with open(export_json, 'w') as fp: + json.dump(self.settings, fp) diff --git a/Deep-SAD-PyTorch/src/utils/misc.py b/Deep-SAD-PyTorch/src/utils/misc.py new file mode 100644 index 0000000..fd2c258 --- /dev/null +++ b/Deep-SAD-PyTorch/src/utils/misc.py @@ -0,0 +1,46 @@ +import torch + +from torch.autograd import Variable + + +# Acknowledgements: https://github.com/wohlert/semi-supervised-pytorch +def enumerate_discrete(x, y_dim): + """ + Generates a 'torch.Tensor' of size batch_size x n_labels of the given label. + + :param x: tensor with batch size to mimic + :param y_dim: number of total labels + :return variable + """ + + def batch(batch_size, label): + labels = (torch.ones(batch_size, 1) * label).type(torch.LongTensor) + y = torch.zeros((batch_size, y_dim)) + y.scatter_(1, labels, 1) + return y.type(torch.LongTensor) + + batch_size = x.size(0) + generated = torch.cat([batch(batch_size, i) for i in range(y_dim)]) + + if x.is_cuda: + generated = generated.to(x.device) + + return Variable(generated.float()) + + +def log_sum_exp(tensor, dim=-1, sum_op=torch.sum): + """ + Uses the LogSumExp (LSE) as an approximation for the sum in a log-domain. + + :param tensor: Tensor to compute LSE over + :param dim: dimension to perform operation over + :param sum_op: reductive operation to be applied, e.g. torch.sum or torch.mean + :return: LSE + """ + max, _ = torch.max(tensor, dim=dim, keepdim=True) + return torch.log(sum_op(torch.exp(tensor - max), dim=dim, keepdim=True) + 1e-8) + max + + +def binary_cross_entropy(x, y): + eps = 1e-8 + return -torch.sum(y * torch.log(x + eps) + (1 - y) * torch.log(1 - x + eps), dim=-1) diff --git a/Deep-SAD-PyTorch/src/utils/visualization/plot_images_grid.py b/Deep-SAD-PyTorch/src/utils/visualization/plot_images_grid.py new file mode 100644 index 0000000..d982465 --- /dev/null +++ b/Deep-SAD-PyTorch/src/utils/visualization/plot_images_grid.py @@ -0,0 +1,26 @@ +import torch +import matplotlib +matplotlib.use('Agg') # or 'PS', 'PDF', 'SVG' + +import matplotlib.pyplot as plt +import numpy as np +from torchvision.utils import make_grid + + +def plot_images_grid(x: torch.tensor, export_img, title: str = '', nrow=8, padding=2, normalize=False, pad_value=0): + """Plot 4D Tensor of images of shape (B x C x H x W) as a grid.""" + + grid = make_grid(x, nrow=nrow, padding=padding, normalize=normalize, pad_value=pad_value) + npgrid = grid.cpu().numpy() + + plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation='nearest') + + ax = plt.gca() + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + + if not (title == ''): + plt.title(title) + + plt.savefig(export_img, bbox_inches='tight', pad_inches=0.1) + plt.clf()