black formatted files before changes

This commit is contained in:
Jan Kowalczyk
2024-06-28 11:36:46 +02:00
parent d33c6b1e16
commit 71f9662022
40 changed files with 2938 additions and 1260 deletions

View File

@@ -41,53 +41,80 @@ class DeepSAD(object):
self.ae_optimizer_name = None self.ae_optimizer_name = None
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_auc': None, "test_auc": None,
'test_time': None, "test_time": None,
'test_scores': None, "test_scores": None,
} }
self.ae_results = { self.ae_results = {"train_time": None, "test_auc": None, "test_time": None}
'train_time': None,
'test_auc': None,
'test_time': None
}
def set_network(self, net_name): def set_network(self, net_name):
"""Builds the neural network phi.""" """Builds the neural network phi."""
self.net_name = net_name self.net_name = net_name
self.net = build_network(net_name) self.net = build_network(net_name)
def train(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 50, def train(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): 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 Deep SAD model on the training data.""" """Trains the Deep SAD model on the training data."""
self.optimizer_name = optimizer_name self.optimizer_name = optimizer_name
self.trainer = DeepSADTrainer(self.c, self.eta, optimizer_name=optimizer_name, lr=lr, n_epochs=n_epochs, self.trainer = DeepSADTrainer(
lr_milestones=lr_milestones, batch_size=batch_size, weight_decay=weight_decay, self.c,
device=device, n_jobs_dataloader=n_jobs_dataloader) self.eta,
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,
)
# Get the model # Get the model
self.net = self.trainer.train(dataset, self.net) self.net = self.trainer.train(dataset, self.net)
self.results['train_time'] = self.trainer.train_time self.results["train_time"] = self.trainer.train_time
self.c = self.trainer.c.cpu().data.numpy().tolist() # get as list self.c = self.trainer.c.cpu().data.numpy().tolist() # get as list
def test(self, dataset: BaseADDataset, device: str = 'cuda', n_jobs_dataloader: int = 0): def test(
self, dataset: BaseADDataset, device: str = "cuda", n_jobs_dataloader: int = 0
):
"""Tests the Deep SAD model on the test data.""" """Tests the Deep SAD model on the test data."""
if self.trainer is None: if self.trainer is None:
self.trainer = DeepSADTrainer(self.c, self.eta, device=device, n_jobs_dataloader=n_jobs_dataloader) self.trainer = DeepSADTrainer(
self.c, self.eta, device=device, n_jobs_dataloader=n_jobs_dataloader
)
self.trainer.test(dataset, self.net) self.trainer.test(dataset, self.net)
# Get results # Get results
self.results['test_auc'] = self.trainer.test_auc self.results["test_auc"] = self.trainer.test_auc
self.results['test_time'] = self.trainer.test_time self.results["test_time"] = self.trainer.test_time
self.results['test_scores'] = self.trainer.test_scores 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, def pretrain(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): 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 the weights for the Deep SAD network phi via autoencoder.""" """Pretrains the weights for the Deep SAD network phi via autoencoder."""
# Set autoencoder network # Set autoencoder network
@@ -95,20 +122,27 @@ class DeepSAD(object):
# Train # Train
self.ae_optimizer_name = optimizer_name self.ae_optimizer_name = optimizer_name
self.ae_trainer = AETrainer(optimizer_name, lr=lr, n_epochs=n_epochs, lr_milestones=lr_milestones, self.ae_trainer = AETrainer(
batch_size=batch_size, weight_decay=weight_decay, device=device, optimizer_name,
n_jobs_dataloader=n_jobs_dataloader) 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.ae_net = self.ae_trainer.train(dataset, self.ae_net) self.ae_net = self.ae_trainer.train(dataset, self.ae_net)
# Get train results # Get train results
self.ae_results['train_time'] = self.ae_trainer.train_time self.ae_results["train_time"] = self.ae_trainer.train_time
# Test # Test
self.ae_trainer.test(dataset, self.ae_net) self.ae_trainer.test(dataset, self.ae_net)
# Get test results # Get test results
self.ae_results['test_auc'] = self.ae_trainer.test_auc self.ae_results["test_auc"] = self.ae_trainer.test_auc
self.ae_results['test_time'] = self.ae_trainer.test_time self.ae_results["test_time"] = self.ae_trainer.test_time
# Initialize Deep SAD network weights from pre-trained encoder # Initialize Deep SAD network weights from pre-trained encoder
self.init_network_weights_from_pretraining() self.init_network_weights_from_pretraining()
@@ -132,30 +166,31 @@ class DeepSAD(object):
net_dict = self.net.state_dict() net_dict = self.net.state_dict()
ae_net_dict = self.ae_net.state_dict() if save_ae else None ae_net_dict = self.ae_net.state_dict() if save_ae else None
torch.save({'c': self.c, torch.save(
'net_dict': net_dict, {"c": self.c, "net_dict": net_dict, "ae_net_dict": ae_net_dict},
'ae_net_dict': ae_net_dict}, export_model) export_model,
)
def load_model(self, model_path, load_ae=False, map_location='cpu'): def load_model(self, model_path, load_ae=False, map_location="cpu"):
"""Load Deep SAD model from model_path.""" """Load Deep SAD model from model_path."""
model_dict = torch.load(model_path, map_location=map_location) model_dict = torch.load(model_path, map_location=map_location)
self.c = model_dict['c'] self.c = model_dict["c"]
self.net.load_state_dict(model_dict['net_dict']) self.net.load_state_dict(model_dict["net_dict"])
# load autoencoder parameters if specified # load autoencoder parameters if specified
if load_ae: if load_ae:
if self.ae_net is None: if self.ae_net is None:
self.ae_net = build_autoencoder(self.net_name) self.ae_net = build_autoencoder(self.net_name)
self.ae_net.load_state_dict(model_dict['ae_net_dict']) self.ae_net.load_state_dict(model_dict["ae_net_dict"])
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)
def save_ae_results(self, export_json): def save_ae_results(self, export_json):
"""Save autoencoder results dict to a JSON-file.""" """Save autoencoder results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.ae_results, fp) json.dump(self.ae_results, fp)

View File

@@ -10,15 +10,24 @@ class BaseADDataset(ABC):
self.root = root # root path to data self.root = root # root path to data
self.n_classes = 2 # 0: normal, 1: outlier self.n_classes = 2 # 0: normal, 1: outlier
self.normal_classes = None # tuple with original class labels that define the normal class self.normal_classes = (
self.outlier_classes = None # tuple with original class labels that define the outlier class None # tuple with original class labels that define the normal class
)
self.outlier_classes = (
None # tuple with original class labels that define the outlier class
)
self.train_set = None # must be of type torch.utils.data.Dataset self.train_set = None # must be of type torch.utils.data.Dataset
self.test_set = None # must be of type torch.utils.data.Dataset self.test_set = None # must be of type torch.utils.data.Dataset
@abstractmethod @abstractmethod
def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> ( def loaders(
DataLoader, DataLoader): self,
batch_size: int,
shuffle_train=True,
shuffle_test=False,
num_workers: int = 0,
) -> (DataLoader, DataLoader):
"""Implement data loaders of type torch.utils.data.DataLoader for train_set and test_set.""" """Implement data loaders of type torch.utils.data.DataLoader for train_set and test_set."""
pass pass

View File

@@ -22,5 +22,5 @@ class BaseNet(nn.Module):
"""Network summary.""" """Network summary."""
net_parameters = filter(lambda p: p.requires_grad, self.parameters()) net_parameters = filter(lambda p: p.requires_grad, self.parameters())
params = sum([np.prod(p.size()) for p in net_parameters]) params = sum([np.prod(p.size()) for p in net_parameters])
self.logger.info('Trainable parameters: {}'.format(params)) self.logger.info("Trainable parameters: {}".format(params))
self.logger.info(self) self.logger.info(self)

View File

@@ -6,8 +6,17 @@ from .base_net import BaseNet
class BaseTrainer(ABC): class BaseTrainer(ABC):
"""Trainer base class.""" """Trainer base class."""
def __init__(self, optimizer_name: str, lr: float, n_epochs: int, lr_milestones: tuple, batch_size: int, def __init__(
weight_decay: float, device: str, n_jobs_dataloader: int): 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__() super().__init__()
self.optimizer_name = optimizer_name self.optimizer_name = optimizer_name
self.lr = lr self.lr = lr

View File

@@ -19,15 +19,22 @@ class ODDSDataset(Dataset):
""" """
urls = { urls = {
'arrhythmia': 'https://www.dropbox.com/s/lmlwuspn1sey48r/arrhythmia.mat?dl=1', "arrhythmia": "https://www.dropbox.com/s/lmlwuspn1sey48r/arrhythmia.mat?dl=1",
'cardio': 'https://www.dropbox.com/s/galg3ihvxklf0qi/cardio.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', "satellite": "https://www.dropbox.com/s/dpzxp8jyr9h93k5/satellite.mat?dl=1",
'satimage-2': 'https://www.dropbox.com/s/hckgvu9m6fs441p/satimage-2.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', "shuttle": "https://www.dropbox.com/s/mk8ozgisimfn3dw/shuttle.mat?dl=1",
'thyroid': 'https://www.dropbox.com/s/bih0e15a0fukftb/thyroid.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): def __init__(
self,
root: str,
dataset_name: str,
train=True,
random_state=None,
download=False,
):
super(Dataset, self).__init__() super(Dataset, self).__init__()
self.classes = [0, 1] self.classes = [0, 1]
@@ -37,25 +44,25 @@ class ODDSDataset(Dataset):
self.root = Path(root) self.root = Path(root)
self.dataset_name = dataset_name self.dataset_name = dataset_name
self.train = train # training set or test set self.train = train # training set or test set
self.file_name = self.dataset_name + '.mat' self.file_name = self.dataset_name + ".mat"
self.data_file = self.root / self.file_name self.data_file = self.root / self.file_name
if download: if download:
self.download() self.download()
mat = loadmat(self.data_file) mat = loadmat(self.data_file)
X = mat['X'] X = mat["X"]
y = mat['y'].ravel() y = mat["y"].ravel()
idx_norm = y == 0 idx_norm = y == 0
idx_out = y == 1 idx_out = y == 1
# 60% data for training and 40% for testing; keep outlier ratio # 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], X_train_norm, X_test_norm, y_train_norm, y_test_norm = train_test_split(
test_size=0.4, X[idx_norm], y[idx_norm], test_size=0.4, random_state=random_state
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], X_train_out, X_test_out, y_train_out, y_test_out = train_test_split(
test_size=0.4, X[idx_out], y[idx_out], test_size=0.4, random_state=random_state
random_state=random_state) )
X_train = np.concatenate((X_train_norm, X_train_out)) X_train = np.concatenate((X_train_norm, X_train_out))
X_test = np.concatenate((X_test_norm, X_test_out)) X_test = np.concatenate((X_test_norm, X_test_out))
y_train = np.concatenate((y_train_norm, y_train_out)) y_train = np.concatenate((y_train_norm, y_train_out))
@@ -88,7 +95,11 @@ class ODDSDataset(Dataset):
Returns: Returns:
tuple: (sample, target, semi_target, index) tuple: (sample, target, semi_target, index)
""" """
sample, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[index]) sample, target, semi_target = (
self.data[index],
int(self.targets[index]),
int(self.semi_targets[index]),
)
return sample, target, semi_target, index return sample, target, semi_target, index
@@ -107,4 +118,4 @@ class ODDSDataset(Dataset):
# download file # download file
download_url(self.urls[self.dataset_name], self.root, self.file_name) download_url(self.urls[self.dataset_name], self.root, self.file_name)
print('Done!') print("Done!")

View File

@@ -8,10 +8,25 @@ class TorchvisionDataset(BaseADDataset):
def __init__(self, root: str): def __init__(self, root: str):
super().__init__(root) super().__init__(root)
def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> ( def loaders(
DataLoader, DataLoader): self,
train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train, batch_size: int,
num_workers=num_workers, drop_last=True) shuffle_train=True,
test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test, shuffle_test=False,
num_workers=num_workers, drop_last=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 return train_loader, test_loader

View File

@@ -14,64 +14,215 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('net_name', type=click.Choice(['mnist_DGM_M2', 'mnist_DGM_M1M2', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2', type=click.Choice(
'cifar10_DGM_M2', 'cifar10_DGM_M1M2', [
'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2', "mnist",
'satimage-2_DGM_M2', 'shuttle_DGM_M2', 'thyroid_DGM_M2'])) "fmnist",
@click.argument('xp_path', type=click.Path(exists=True)) "cifar10",
@click.argument('data_path', type=click.Path(exists=True)) "arrhythmia",
@click.option('--load_config', type=click.Path(exists=True), default=None, "cardio",
help='Config JSON-file path (default: None).') "satellite",
@click.option('--load_model', type=click.Path(exists=True), default=None, "satimage-2",
help='Model file path (default: None).') "shuttle",
@click.option('--ratio_known_normal', type=float, default=0.0, "thyroid",
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, @click.argument(
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') "net_name",
@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).') type=click.Choice(
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') [
@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam', "mnist_DGM_M2",
help='Name of the optimizer to use for training the Semi-Supervised Deep Generative model.') "mnist_DGM_M1M2",
@click.option('--lr', type=float, default=0.001, "fmnist_DGM_M2",
help='Initial learning rate for training. Default=0.001') "fmnist_DGM_M1M2",
@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.') "cifar10_DGM_M2",
@click.option('--lr_milestone', type=int, default=0, multiple=True, "cifar10_DGM_M1M2",
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') "arrhythmia_DGM_M2",
@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.') "cardio_DGM_M2",
@click.option('--weight_decay', type=float, default=1e-6, "satellite_DGM_M2",
help='Weight decay (L2 penalty) hyperparameter.') "satimage-2_DGM_M2",
@click.option('--pretrain', type=bool, default=False, help='Pretrain a variational autoencoder.') "shuttle_DGM_M2",
@click.option('--vae_optimizer_name', type=click.Choice(['adam']), default='adam', "thyroid_DGM_M2",
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.argument("xp_path", type=click.Path(exists=True))
@click.option('--vae_lr_milestone', type=int, default=0, multiple=True, @click.argument("data_path", type=click.Path(exists=True))
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') @click.option(
@click.option('--vae_batch_size', type=int, default=128, help='Batch size for variational autoencoder training.') "--load_config",
@click.option('--vae_weight_decay', type=float, default=1e-6, type=click.Path(exists=True),
help='Weight decay (L2 penalty) hyperparameter for variational autoencoder.') default=None,
@click.option('--num_threads', type=int, default=0, help="Config JSON-file path (default: None).",
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, @click.option(
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') "--load_model",
@click.option('--normal_class', type=int, default=0, type=click.Path(exists=True),
help='Specify the normal class of the dataset (all other classes are considered anomalous).') default=None,
@click.option('--known_outlier_class', type=int, default=1, help="Model file path (default: None).",
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') )
@click.option('--n_known_outlier_classes', type=int, default=0, @click.option(
help='Number of known outlier classes.' "--ratio_known_normal",
'If 0, no anomalies are known.' type=float,
'If 1, outlier class as specified in --known_outlier_class option.' default=0.0,
'If > 1, the specified number of outlier classes will be sampled at random.') help="Ratio of known (labeled) normal training examples.",
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, @click.option(
vae_optimizer_name, vae_lr, vae_n_epochs, vae_lr_milestone, vae_batch_size, vae_weight_decay, "--ratio_known_outlier",
num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): 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) Semi-Supervised Deep Generative model (M1+M2 model) from Kingma et al. (2014)
@@ -88,64 +239,78 @@ def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, ra
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s' % log_file) logger.info("Log file is %s" % log_file)
logger.info('Data path is %s' % data_path) logger.info("Data path is %s" % data_path)
logger.info('Export path is %s' % xp_path) logger.info("Export path is %s" % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
logger.info('Network: %s' % net_name) logger.info("Network: %s" % net_name)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Set seed # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Default device to 'cpu' if cuda is not available # Default device to 'cpu' if cuda is not available
if not torch.cuda.is_available(): if not torch.cuda.is_available():
device = 'cpu' device = "cpu"
# Set the number of threads used for parallelizing CPU operations # Set the number of threads used for parallelizing CPU operations
if num_threads > 0: if num_threads > 0:
torch.set_num_threads(num_threads) torch.set_num_threads(num_threads)
logger.info('Computation device: %s' % device) logger.info("Computation device: %s" % device)
logger.info('Number of threads: %d' % num_threads) logger.info("Number of threads: %d" % num_threads)
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize semiDGM model and set neural network phi # Initialize semiDGM model and set neural network phi
alpha = 0.1 * (1 - ratio_known_normal - ratio_known_outlier) / (ratio_known_normal + ratio_known_outlier) alpha = (
0.1
* (1 - ratio_known_normal - ratio_known_outlier)
/ (ratio_known_normal + ratio_known_outlier)
)
semiDGM = SemiDeepGenerativeModel(alpha=alpha) semiDGM = SemiDeepGenerativeModel(alpha=alpha)
# If specified, load model # If specified, load model
@@ -155,86 +320,118 @@ def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, ra
semiDGM.set_network(net_name) semiDGM.set_network(net_name)
# Load model # Load model
semiDGM.load_model(model_path=load_model) semiDGM.load_model(model_path=load_model)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
logger.info('Pretraining: %s' % pretrain) logger.info("Pretraining: %s" % pretrain)
if pretrain: if pretrain:
# Log pretraining details # Log pretraining details
logger.info('Pretraining optimizer: %s' % cfg.settings['vae_optimizer_name']) logger.info("Pretraining optimizer: %s" % cfg.settings["vae_optimizer_name"])
logger.info('Pretraining learning rate: %g' % cfg.settings['vae_lr']) logger.info("Pretraining learning rate: %g" % cfg.settings["vae_lr"])
logger.info('Pretraining epochs: %d' % cfg.settings['vae_n_epochs']) 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(
logger.info('Pretraining batch size: %d' % cfg.settings['vae_batch_size']) "Pretraining learning rate scheduler milestones: %s"
logger.info('Pretraining weight decay: %g' % cfg.settings['vae_weight_decay']) % (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) # Pretrain model on dataset (via variational autoencoder)
semiDGM.set_vae(net_name) semiDGM.set_vae(net_name)
semiDGM.pretrain(dataset, semiDGM.pretrain(
optimizer_name=cfg.settings['vae_optimizer_name'], dataset,
lr=cfg.settings['vae_lr'], optimizer_name=cfg.settings["vae_optimizer_name"],
n_epochs=cfg.settings['vae_n_epochs'], lr=cfg.settings["vae_lr"],
lr_milestones=cfg.settings['vae_lr_milestone'], n_epochs=cfg.settings["vae_n_epochs"],
batch_size=cfg.settings['vae_batch_size'], lr_milestones=cfg.settings["vae_lr_milestone"],
weight_decay=cfg.settings['vae_weight_decay'], batch_size=cfg.settings["vae_batch_size"],
device=device, weight_decay=cfg.settings["vae_weight_decay"],
n_jobs_dataloader=n_jobs_dataloader) device=device,
n_jobs_dataloader=n_jobs_dataloader,
)
# Save pretraining results # Save pretraining results
semiDGM.save_vae_results(export_json=xp_path + '/vae_results.json') semiDGM.save_vae_results(export_json=xp_path + "/vae_results.json")
# Log training details # Log training details
logger.info('Training optimizer: %s' % cfg.settings['optimizer_name']) logger.info("Training optimizer: %s" % cfg.settings["optimizer_name"])
logger.info('Training learning rate: %g' % cfg.settings['lr']) logger.info("Training learning rate: %g" % cfg.settings["lr"])
logger.info('Training epochs: %d' % cfg.settings['n_epochs']) logger.info("Training epochs: %d" % cfg.settings["n_epochs"])
logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],)) logger.info(
logger.info('Training batch size: %d' % cfg.settings['batch_size']) "Training learning rate scheduler milestones: %s"
logger.info('Training weight decay: %g' % cfg.settings['weight_decay']) % (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 # Train model on dataset
semiDGM.set_network(net_name) semiDGM.set_network(net_name)
semiDGM.train(dataset, semiDGM.train(
optimizer_name=cfg.settings['optimizer_name'], dataset,
lr=cfg.settings['lr'], optimizer_name=cfg.settings["optimizer_name"],
n_epochs=cfg.settings['n_epochs'], lr=cfg.settings["lr"],
lr_milestones=cfg.settings['lr_milestone'], n_epochs=cfg.settings["n_epochs"],
batch_size=cfg.settings['batch_size'], lr_milestones=cfg.settings["lr_milestone"],
weight_decay=cfg.settings['weight_decay'], batch_size=cfg.settings["batch_size"],
device=device, weight_decay=cfg.settings["weight_decay"],
n_jobs_dataloader=n_jobs_dataloader) device=device,
n_jobs_dataloader=n_jobs_dataloader,
)
# Test model # Test model
semiDGM.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) semiDGM.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results, model, and configuration # Save results, model, and configuration
semiDGM.save_results(export_json=xp_path + '/results.json') semiDGM.save_results(export_json=xp_path + "/results.json")
semiDGM.save_model(export_model=xp_path + '/model.tar') semiDGM.save_model(export_model=xp_path + "/model.tar")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*semiDGM.results['test_scores']) indices, labels, scores = zip(*semiDGM.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2))) dataset.test_set.data[idx_all_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))) )
)
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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -14,46 +14,138 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('xp_path', type=click.Path(exists=True)) type=click.Choice(
@click.argument('data_path', type=click.Path(exists=True)) [
@click.option('--load_config', type=click.Path(exists=True), default=None, "mnist",
help='Config JSON-file path (default: None).') "fmnist",
@click.option('--load_model', type=click.Path(exists=True), default=None, "cifar10",
help='Model file path (default: None).') "arrhythmia",
@click.option('--ratio_known_normal', type=float, default=0.0, "cardio",
help='Ratio of known (labeled) normal training examples.') "satellite",
@click.option('--ratio_known_outlier', type=float, default=0.0, "satimage-2",
help='Ratio of known (labeled) anomalous training examples.') "shuttle",
@click.option('--ratio_pollution', type=float, default=0.0, "thyroid",
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.argument("xp_path", type=click.Path(exists=True))
@click.option('--max_samples', type=int, default=256, @click.argument("data_path", type=click.Path(exists=True))
help='Set the number of samples drawn to train each base estimator (default: 256).') @click.option(
@click.option('--contamination', type=float, default=0.1, "--load_config",
help='Expected fraction of anomalies in the training set. (default: 0.1).') type=click.Path(exists=True),
@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.') default=None,
@click.option('--hybrid', type=bool, default=False, help="Config JSON-file path (default: None).",
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, @click.option(
help='Model file path to load autoencoder weights (default: None).') "--load_model",
@click.option('--n_jobs_dataloader', type=int, default=0, type=click.Path(exists=True),
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') default=None,
@click.option('--normal_class', type=int, default=0, help="Model file path (default: None).",
help='Specify the normal class of the dataset (all other classes are considered anomalous).') )
@click.option('--known_outlier_class', type=int, default=1, @click.option(
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') "--ratio_known_normal",
@click.option('--n_known_outlier_classes', type=int, default=0, type=float,
help='Number of known outlier classes.' default=0.0,
'If 0, no anomalies are known.' help="Ratio of known (labeled) normal training examples.",
'If 1, outlier class as specified in --known_outlier_class option.' )
'If > 1, the specified number of outlier classes will be sampled at random.') @click.option(
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, "--ratio_known_outlier",
ratio_pollution, seed, n_estimators, max_samples, contamination, n_jobs_model, hybrid, load_ae, type=float,
n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): 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. (Hybrid) Isolation Forest model for anomaly detection.
@@ -69,78 +161,100 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s.' % log_file) logger.info("Log file is %s." % log_file)
logger.info('Data path is %s.' % data_path) logger.info("Data path is %s." % data_path)
logger.info('Export path is %s.' % xp_path) logger.info("Export path is %s." % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Print Isolation Forest configuration # Print Isolation Forest configuration
logger.info('Number of base estimators in the ensemble: %d' % cfg.settings['n_estimators']) logger.info(
logger.info('Number of samples for training each base estimator: %d' % cfg.settings['max_samples']) "Number of base estimators in the ensemble: %d" % cfg.settings["n_estimators"]
logger.info('Contamination parameter: %.2f' % cfg.settings['contamination']) )
logger.info('Number of jobs for model training: %d' % n_jobs_model) logger.info(
logger.info('Hybrid model: %s' % cfg.settings['hybrid']) "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 # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Use 'cpu' as device for Isolation Forest # Use 'cpu' as device for Isolation Forest
device = 'cpu' device = "cpu"
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu torch.multiprocessing.set_sharing_strategy(
logger.info('Computation device: %s' % device) "file_system"
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) ) # fix multiprocessing issue for ubuntu
logger.info("Computation device: %s" % device)
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize Isolation Forest model # Initialize Isolation Forest model
Isoforest = IsoForest(hybrid=cfg.settings['hybrid'], n_estimators=cfg.settings['n_estimators'], Isoforest = IsoForest(
max_samples=cfg.settings['max_samples'], contamination=cfg.settings['contamination'], hybrid=cfg.settings["hybrid"],
n_jobs=n_jobs_model, seed=cfg.settings['seed']) 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 specified, load model parameters from already trained model
if load_model: if load_model:
Isoforest.load_model(import_path=load_model, device=device) Isoforest.load_model(import_path=load_model, device=device)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
# If specified, load model autoencoder weights for a hybrid approach # If specified, load model autoencoder weights for a hybrid approach
if hybrid and load_ae is not None: if hybrid and load_ae is not None:
Isoforest.load_ae(dataset_name, model_path=load_ae) Isoforest.load_ae(dataset_name, model_path=load_ae)
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
# Train model on dataset # Train model on dataset
Isoforest.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) Isoforest.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
@@ -149,35 +263,56 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
Isoforest.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) Isoforest.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results and configuration # Save results and configuration
Isoforest.save_results(export_json=xp_path + '/results.json') Isoforest.save_results(export_json=xp_path + "/results.json")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*Isoforest.results['test_scores']) indices, labels, scores = zip(*Isoforest.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) 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( X_normal_high = torch.tensor(
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) 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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -14,44 +14,133 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('xp_path', type=click.Path(exists=True)) type=click.Choice(
@click.argument('data_path', type=click.Path(exists=True)) [
@click.option('--load_config', type=click.Path(exists=True), default=None, "mnist",
help='Config JSON-file path (default: None).') "fmnist",
@click.option('--load_model', type=click.Path(exists=True), default=None, "cifar10",
help='Model file path (default: None).') "arrhythmia",
@click.option('--ratio_known_normal', type=float, default=0.0, "cardio",
help='Ratio of known (labeled) normal training examples.') "satellite",
@click.option('--ratio_known_outlier', type=float, default=0.0, "satimage-2",
help='Ratio of known (labeled) anomalous training examples.') "shuttle",
@click.option('--ratio_pollution', type=float, default=0.0, "thyroid",
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.argument("xp_path", type=click.Path(exists=True))
@click.option('--grid_search_cv', type=bool, default=True, @click.argument("data_path", type=click.Path(exists=True))
help='Use sklearn GridSearchCV to determine optimal bandwidth') @click.option(
@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.') "--load_config",
@click.option('--hybrid', type=bool, default=False, type=click.Path(exists=True),
help='Train KDE on features extracted from an autoencoder. If True, load_ae must be specified.') default=None,
@click.option('--load_ae', type=click.Path(exists=True), default=None, help="Config JSON-file path (default: None).",
help='Model file path to load autoencoder weights (default: None).') )
@click.option('--n_jobs_dataloader', type=int, default=0, @click.option(
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') "--load_model",
@click.option('--normal_class', type=int, default=0, type=click.Path(exists=True),
help='Specify the normal class of the dataset (all other classes are considered anomalous).') default=None,
@click.option('--known_outlier_class', type=int, default=1, help="Model file path (default: None).",
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') )
@click.option('--n_known_outlier_classes', type=int, default=0, @click.option(
help='Number of known outlier classes.' "--ratio_known_normal",
'If 0, no anomalies are known.' type=float,
'If 1, outlier class as specified in --known_outlier_class option.' default=0.0,
'If > 1, the specified number of outlier classes will be sampled at random.') help="Ratio of known (labeled) normal training examples.",
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, @click.option(
known_outlier_class, n_known_outlier_classes): "--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. (Hybrid) KDE for anomaly detection.
@@ -67,114 +156,157 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s.' % log_file) logger.info("Log file is %s." % log_file)
logger.info('Data path is %s.' % data_path) logger.info("Data path is %s." % data_path)
logger.info('Export path is %s.' % xp_path) logger.info("Export path is %s." % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Print KDE configuration # Print KDE configuration
logger.info('KDE kernel: %s' % cfg.settings['kernel']) logger.info("KDE kernel: %s" % cfg.settings["kernel"])
logger.info('Use GridSearchCV for bandwidth selection: %s' % cfg.settings['grid_search_cv']) logger.info(
logger.info('Number of jobs for model training: %d' % n_jobs_model) "Use GridSearchCV for bandwidth selection: %s" % cfg.settings["grid_search_cv"]
logger.info('Hybrid model: %s' % cfg.settings['hybrid']) )
logger.info("Number of jobs for model training: %d" % n_jobs_model)
logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
# Set seed # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Use 'cpu' as device for KDE # Use 'cpu' as device for KDE
device = 'cpu' device = "cpu"
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu torch.multiprocessing.set_sharing_strategy(
logger.info('Computation device: %s' % device) "file_system"
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) ) # fix multiprocessing issue for ubuntu
logger.info("Computation device: %s" % device)
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize KDE model # Initialize KDE model
kde = KDE(hybrid=cfg.settings['hybrid'], kernel=cfg.settings['kernel'], n_jobs=n_jobs_model, kde = KDE(
seed=cfg.settings['seed']) 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 specified, load model parameters from already trained model
if load_model: if load_model:
kde.load_model(import_path=load_model, device=device) kde.load_model(import_path=load_model, device=device)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
# If specified, load model autoencoder weights for a hybrid approach # If specified, load model autoencoder weights for a hybrid approach
if hybrid and load_ae is not None: if hybrid and load_ae is not None:
kde.load_ae(dataset_name, model_path=load_ae) kde.load_ae(dataset_name, model_path=load_ae)
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
# Train model on dataset # Train model on dataset
kde.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader, kde.train(
bandwidth_GridSearchCV=cfg.settings['grid_search_cv']) dataset,
device=device,
n_jobs_dataloader=n_jobs_dataloader,
bandwidth_GridSearchCV=cfg.settings["grid_search_cv"],
)
# Test model # Test model
kde.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) kde.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results and configuration # Save results and configuration
kde.save_results(export_json=xp_path + '/results.json') kde.save_results(export_json=xp_path + "/results.json")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*kde.results['test_scores']) indices, labels, scores = zip(*kde.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) 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( X_normal_high = torch.tensor(
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) 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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -14,41 +14,127 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('xp_path', type=click.Path(exists=True)) type=click.Choice(
@click.argument('data_path', type=click.Path(exists=True)) [
@click.option('--load_config', type=click.Path(exists=True), default=None, "mnist",
help='Config JSON-file path (default: None).') "fmnist",
@click.option('--load_model', type=click.Path(exists=True), default=None, "cifar10",
help='Model file path (default: None).') "arrhythmia",
@click.option('--ratio_known_normal', type=float, default=0.0, "cardio",
help='Ratio of known (labeled) normal training examples.') "satellite",
@click.option('--ratio_known_outlier', type=float, default=0.0, "satimage-2",
help='Ratio of known (labeled) anomalous training examples.') "shuttle",
@click.option('--ratio_pollution', type=float, default=0.0, "thyroid",
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.argument("xp_path", type=click.Path(exists=True))
@click.option('--hybrid', type=bool, default=False, @click.argument("data_path", type=click.Path(exists=True))
help='Train OC-SVM on features extracted from an autoencoder. If True, load_ae must be specified.') @click.option(
@click.option('--load_ae', type=click.Path(exists=True), default=None, "--load_config",
help='Model file path to load autoencoder weights (default: None).') type=click.Path(exists=True),
@click.option('--n_jobs_dataloader', type=int, default=0, default=None,
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') help="Config JSON-file path (default: None).",
@click.option('--normal_class', type=int, default=0, )
help='Specify the normal class of the dataset (all other classes are considered anomalous).') @click.option(
@click.option('--known_outlier_class', type=int, default=1, "--load_model",
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') type=click.Path(exists=True),
@click.option('--n_known_outlier_classes', type=int, default=0, default=None,
help='Number of known outlier classes.' help="Model file path (default: None).",
'If 0, no anomalies are known.' )
'If 1, outlier class as specified in --known_outlier_class option.' @click.option(
'If > 1, the specified number of outlier classes will be sampled at random.') "--ratio_known_normal",
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, type=float,
ratio_pollution, seed, kernel, nu, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class, default=0.0,
n_known_outlier_classes): 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. (Hybrid) One-Class SVM for anomaly detection.
@@ -64,74 +150,86 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s.' % log_file) logger.info("Log file is %s." % log_file)
logger.info('Data path is %s.' % data_path) logger.info("Data path is %s." % data_path)
logger.info('Export path is %s.' % xp_path) logger.info("Export path is %s." % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Print OC-SVM configuration # Print OC-SVM configuration
logger.info('OC-SVM kernel: %s' % cfg.settings['kernel']) logger.info("OC-SVM kernel: %s" % cfg.settings["kernel"])
logger.info('Nu-paramerter: %.2f' % cfg.settings['nu']) logger.info("Nu-paramerter: %.2f" % cfg.settings["nu"])
logger.info('Hybrid model: %s' % cfg.settings['hybrid']) logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
# Set seed # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Use 'cpu' as device for OC-SVM # Use 'cpu' as device for OC-SVM
device = 'cpu' device = "cpu"
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu torch.multiprocessing.set_sharing_strategy(
logger.info('Computation device: %s' % device) "file_system"
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) ) # fix multiprocessing issue for ubuntu
logger.info("Computation device: %s" % device)
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize OC-SVM model # Initialize OC-SVM model
ocsvm = OCSVM(cfg.settings['kernel'], cfg.settings['nu'], cfg.settings['hybrid']) ocsvm = OCSVM(cfg.settings["kernel"], cfg.settings["nu"], cfg.settings["hybrid"])
# If specified, load model parameters from already trained model # If specified, load model parameters from already trained model
if load_model: if load_model:
ocsvm.load_model(import_path=load_model, device=device) ocsvm.load_model(import_path=load_model, device=device)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
# If specified, load model autoencoder weights for a hybrid approach # If specified, load model autoencoder weights for a hybrid approach
if hybrid and load_ae is not None: if hybrid and load_ae is not None:
ocsvm.load_ae(dataset_name, model_path=load_ae) ocsvm.load_ae(dataset_name, model_path=load_ae)
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
# Train model on dataset # Train model on dataset
ocsvm.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) ocsvm.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
@@ -140,35 +238,56 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
ocsvm.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) ocsvm.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results and configuration # Save results and configuration
ocsvm.save_results(export_json=xp_path + '/results.json') ocsvm.save_results(export_json=xp_path + "/results.json")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*ocsvm.results['test_scores']) indices, labels, scores = zip(*ocsvm.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) 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( X_normal_high = torch.tensor(
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) 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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -15,41 +15,119 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('xp_path', type=click.Path(exists=True)) type=click.Choice(
@click.argument('data_path', type=click.Path(exists=True)) [
@click.option('--load_config', type=click.Path(exists=True), default=None, "mnist",
help='Config JSON-file path (default: None).') "fmnist",
@click.option('--load_model', type=click.Path(exists=True), default=None, "cifar10",
help='Model file path (default: None).') "arrhythmia",
@click.option('--ratio_known_normal', type=float, default=0.0, "cardio",
help='Ratio of known (labeled) normal training examples.') "satellite",
@click.option('--ratio_known_outlier', type=float, default=0.0, "satimage-2",
help='Ratio of known (labeled) anomalous training examples.') "shuttle",
@click.option('--ratio_pollution', type=float, default=0.0, "thyroid",
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.argument("xp_path", type=click.Path(exists=True))
@click.option('--hybrid', type=bool, default=False, @click.argument("data_path", type=click.Path(exists=True))
help='Train SSAD on features extracted from an autoencoder. If True, load_ae must be specified') @click.option(
@click.option('--load_ae', type=click.Path(exists=True), default=None, "--load_config",
help='Model file path to load autoencoder weights (default: None).') type=click.Path(exists=True),
@click.option('--n_jobs_dataloader', type=int, default=0, default=None,
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.') help="Config JSON-file path (default: None).",
@click.option('--normal_class', type=int, default=0, )
help='Specify the normal class of the dataset (all other classes are considered anomalous).') @click.option(
@click.option('--known_outlier_class', type=int, default=1, "--load_model",
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') type=click.Path(exists=True),
@click.option('--n_known_outlier_classes', type=int, default=0, default=None,
help='Number of known outlier classes.' help="Model file path (default: None).",
'If 0, no anomalies are known.' )
'If 1, outlier class as specified in --known_outlier_class option.' @click.option(
'If > 1, the specified number of outlier classes will be sampled at random.') "--ratio_known_normal",
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier, type=float,
ratio_pollution, seed, kernel, kappa, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class, default=0.0,
n_known_outlier_classes): 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. (Hybrid) SSAD for anomaly detection as in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013.
@@ -65,75 +143,91 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s.' % log_file) logger.info("Log file is %s." % log_file)
logger.info('Data path is %s.' % data_path) logger.info("Data path is %s." % data_path)
logger.info('Export path is %s.' % xp_path) logger.info("Export path is %s." % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Print SSAD configuration # Print SSAD configuration
logger.info('SSAD kernel: %s' % cfg.settings['kernel']) logger.info("SSAD kernel: %s" % cfg.settings["kernel"])
logger.info('Kappa-paramerter: %.2f' % cfg.settings['kappa']) logger.info("Kappa-paramerter: %.2f" % cfg.settings["kappa"])
logger.info('Hybrid model: %s' % cfg.settings['hybrid']) logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
# Set seed # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
co.setseed(cfg.settings['seed']) co.setseed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Use 'cpu' as device for SSAD # Use 'cpu' as device for SSAD
device = 'cpu' device = "cpu"
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu torch.multiprocessing.set_sharing_strategy(
logger.info('Computation device: %s' % device) "file_system"
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) ) # fix multiprocessing issue for ubuntu
logger.info("Computation device: %s" % device)
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize SSAD model # Initialize SSAD model
ssad = SSAD(kernel=cfg.settings['kernel'], kappa=cfg.settings['kappa'], hybrid=cfg.settings['hybrid']) ssad = SSAD(
kernel=cfg.settings["kernel"],
kappa=cfg.settings["kappa"],
hybrid=cfg.settings["hybrid"],
)
# If specified, load model parameters from already trained model # If specified, load model parameters from already trained model
if load_model: if load_model:
ssad.load_model(import_path=load_model, device=device) ssad.load_model(import_path=load_model, device=device)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
# If specified, load model autoencoder weights for a hybrid approach # If specified, load model autoencoder weights for a hybrid approach
if hybrid and load_ae is not None: if hybrid and load_ae is not None:
ssad.load_ae(dataset_name, model_path=load_ae) ssad.load_ae(dataset_name, model_path=load_ae)
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae) logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
# Train model on dataset # Train model on dataset
ssad.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) ssad.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
@@ -142,35 +236,56 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
ssad.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) ssad.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results and configuration # Save results and configuration
ssad.save_results(export_json=xp_path + '/results.json') ssad.save_results(export_json=xp_path + "/results.json")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*ssad.results['test_scores']) indices, labels, scores = zip(*ssad.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2))) 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( X_normal_high = torch.tensor(
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2))) 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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -36,17 +36,13 @@ class SemiDeepGenerativeModel(object):
self.vae_optimizer_name = None self.vae_optimizer_name = None
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_auc': None, "test_auc": None,
'test_time': None, "test_time": None,
'test_scores': None, "test_scores": None,
} }
self.vae_results = { self.vae_results = {"train_time": None, "test_auc": None, "test_time": None}
'train_time': None,
'test_auc': None,
'test_time': None
}
def set_vae(self, net_name): def set_vae(self, net_name):
"""Builds the variational autoencoder network for pretraining.""" """Builds the variational autoencoder network for pretraining."""
@@ -58,71 +54,106 @@ class SemiDeepGenerativeModel(object):
self.net_name = net_name self.net_name = net_name
self.net = build_network(net_name, ae_net=self.vae_net) # full M1+M2 model 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, def train(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): 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.""" """Trains the Semi-Supervised Deep Generative model on the training data."""
self.optimizer_name = optimizer_name self.optimizer_name = optimizer_name
self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, optimizer_name=optimizer_name, lr=lr, self.trainer = SemiDeepGenerativeTrainer(
n_epochs=n_epochs, lr_milestones=lr_milestones, batch_size=batch_size, alpha=self.alpha,
weight_decay=weight_decay, device=device, optimizer_name=optimizer_name,
n_jobs_dataloader=n_jobs_dataloader) 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.net = self.trainer.train(dataset, self.net)
self.results['train_time'] = self.trainer.train_time self.results["train_time"] = self.trainer.train_time
def test(self, dataset: BaseADDataset, device: str = 'cuda', n_jobs_dataloader: int = 0): def test(
self, dataset: BaseADDataset, device: str = "cuda", n_jobs_dataloader: int = 0
):
"""Tests the Semi-Supervised Deep Generative model on the test data.""" """Tests the Semi-Supervised Deep Generative model on the test data."""
if self.trainer is None: if self.trainer is None:
self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, device=device, self.trainer = SemiDeepGenerativeTrainer(
n_jobs_dataloader=n_jobs_dataloader) alpha=self.alpha, device=device, n_jobs_dataloader=n_jobs_dataloader
)
self.trainer.test(dataset, self.net) self.trainer.test(dataset, self.net)
# Get results # Get results
self.results['test_auc'] = self.trainer.test_auc self.results["test_auc"] = self.trainer.test_auc
self.results['test_time'] = self.trainer.test_time self.results["test_time"] = self.trainer.test_time
self.results['test_scores'] = self.trainer.test_scores 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, def pretrain(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): 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.""" """Pretrains a variational autoencoder (M1) for the Semi-Supervised Deep Generative model."""
# Train # Train
self.vae_optimizer_name = optimizer_name self.vae_optimizer_name = optimizer_name
self.vae_trainer = VAETrainer(optimizer_name=optimizer_name, lr=lr, n_epochs=n_epochs, self.vae_trainer = VAETrainer(
lr_milestones=lr_milestones, batch_size=batch_size, weight_decay=weight_decay, optimizer_name=optimizer_name,
device=device, n_jobs_dataloader=n_jobs_dataloader) 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) self.vae_net = self.vae_trainer.train(dataset, self.vae_net)
# Get train results # Get train results
self.vae_results['train_time'] = self.vae_trainer.train_time self.vae_results["train_time"] = self.vae_trainer.train_time
# Test # Test
self.vae_trainer.test(dataset, self.vae_net) self.vae_trainer.test(dataset, self.vae_net)
# Get test results # Get test results
self.vae_results['test_auc'] = self.vae_trainer.test_auc self.vae_results["test_auc"] = self.vae_trainer.test_auc
self.vae_results['test_time'] = self.vae_trainer.test_time self.vae_results["test_time"] = self.vae_trainer.test_time
def save_model(self, export_model): def save_model(self, export_model):
"""Save a Semi-Supervised Deep Generative model to export_model.""" """Save a Semi-Supervised Deep Generative model to export_model."""
net_dict = self.net.state_dict() net_dict = self.net.state_dict()
torch.save({'net_dict': net_dict}, export_model) torch.save({"net_dict": net_dict}, export_model)
def load_model(self, model_path): def load_model(self, model_path):
"""Load a Semi-Supervised Deep Generative model from model_path.""" """Load a Semi-Supervised Deep Generative model from model_path."""
model_dict = torch.load(model_path) model_dict = torch.load(model_path)
self.net.load_state_dict(model_dict['net_dict']) self.net.load_state_dict(model_dict["net_dict"])
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)
def save_vae_results(self, export_json): def save_vae_results(self, export_json):
"""Save variational autoencoder results dict to a JSON-file.""" """Save variational autoencoder results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.vae_results, fp) json.dump(self.vae_results, fp)

View File

@@ -14,8 +14,16 @@ from networks.main import build_autoencoder
class IsoForest(object): class IsoForest(object):
"""A class for Isolation Forest models.""" """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, def __init__(
**kwargs): self,
hybrid=False,
n_estimators=100,
max_samples="auto",
contamination=0.1,
n_jobs=-1,
seed=None,
**kwargs
):
"""Init Isolation Forest instance.""" """Init Isolation Forest instance."""
self.n_estimators = n_estimators self.n_estimators = n_estimators
self.max_samples = max_samples self.max_samples = max_samples
@@ -23,26 +31,39 @@ class IsoForest(object):
self.n_jobs = n_jobs self.n_jobs = n_jobs
self.seed = seed self.seed = seed
self.model = IsolationForest(n_estimators=n_estimators, max_samples=max_samples, contamination=contamination, self.model = IsolationForest(
n_jobs=n_jobs, random_state=seed, **kwargs) n_estimators=n_estimators,
max_samples=max_samples,
contamination=contamination,
n_jobs=n_jobs,
random_state=seed,
**kwargs
)
self.hybrid = hybrid self.hybrid = hybrid
self.ae_net = None # autoencoder network for the case of a hybrid model self.ae_net = None # autoencoder network for the case of a hybrid model
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_time': None, "test_time": None,
'test_auc': None, "test_auc": None,
'test_scores': None "test_scores": None,
} }
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): def train(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Trains the Isolation Forest model on the training data.""" """Trains the Isolation Forest model on the training data."""
logger = logging.getLogger() logger = logging.getLogger()
# do not drop last batch for non-SGD optimization shallow_ssad # do not drop last batch for non-SGD optimization shallow_ssad
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, train_loader = DataLoader(
num_workers=n_jobs_dataloader, drop_last=False) dataset=dataset.train_set,
batch_size=128,
shuffle=True,
num_workers=n_jobs_dataloader,
drop_last=False,
)
# Get data from loader # Get data from loader
X = () X = ()
@@ -50,22 +71,28 @@ class IsoForest(object):
inputs, _, _, _ = data inputs, _, _, _ = data
inputs = inputs.to(device) inputs = inputs.to(device)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
X = np.concatenate(X) X = np.concatenate(X)
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
start_time = time.time() start_time = time.time()
self.model.fit(X) self.model.fit(X)
train_time = time.time() - start_time train_time = time.time() - start_time
self.results['train_time'] = train_time self.results["train_time"] = train_time
logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) logger.info("Training Time: {:.3f}s".format(self.results["train_time"]))
logger.info('Finished training.') logger.info("Finished training.")
def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): def test(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Tests the Isolation Forest model on the test data.""" """Tests the Isolation Forest model on the test data."""
logger = logging.getLogger() logger = logging.getLogger()
@@ -78,46 +105,54 @@ class IsoForest(object):
labels = [] labels = []
for data in test_loader: for data in test_loader:
inputs, label_batch, _, idx = data inputs, label_batch, _, idx = data
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) inputs, label_batch, idx = (
inputs.to(device),
label_batch.to(device),
idx.to(device),
)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
idxs += idx.cpu().data.numpy().astype(np.int64).tolist() idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X = np.concatenate(X) X = np.concatenate(X)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
start_time = time.time() start_time = time.time()
scores = (-1.0) * self.model.decision_function(X) scores = (-1.0) * self.model.decision_function(X)
self.results['test_time'] = time.time() - start_time self.results["test_time"] = time.time() - start_time
scores = scores.flatten() scores = scores.flatten()
# Save triples of (idx, label, score) in a list # Save triples of (idx, label, score) in a list
idx_label_score += list(zip(idxs, labels, scores.tolist())) idx_label_score += list(zip(idxs, labels, scores.tolist()))
self.results['test_scores'] = idx_label_score self.results["test_scores"] = idx_label_score
# Compute AUC # Compute AUC
_, labels, scores = zip(*idx_label_score) _, labels, scores = zip(*idx_label_score)
labels = np.array(labels) labels = np.array(labels)
scores = np.array(scores) scores = np.array(scores)
self.results['test_auc'] = roc_auc_score(labels, scores) self.results["test_auc"] = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) logger.info("Test AUC: {:.2f}%".format(100.0 * self.results["test_auc"]))
logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) logger.info("Test Time: {:.3f}s".format(self.results["test_time"]))
logger.info('Finished testing.') logger.info("Finished testing.")
def load_ae(self, dataset_name, model_path): def load_ae(self, dataset_name, model_path):
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid Isolation Forest model.""" """Load pretrained autoencoder from model_path for feature extraction in a hybrid Isolation Forest model."""
model_dict = torch.load(model_path, map_location='cpu') model_dict = torch.load(model_path, map_location="cpu")
ae_net_dict = model_dict['ae_net_dict'] ae_net_dict = model_dict["ae_net_dict"]
if dataset_name in ['mnist', 'fmnist', 'cifar10']: if dataset_name in ["mnist", "fmnist", "cifar10"]:
net_name = dataset_name + '_LeNet' net_name = dataset_name + "_LeNet"
else: else:
net_name = dataset_name + '_mlp' net_name = dataset_name + "_mlp"
if self.ae_net is None: if self.ae_net is None:
self.ae_net = build_autoencoder(net_name) self.ae_net = build_autoencoder(net_name)
@@ -137,11 +172,11 @@ class IsoForest(object):
"""Save Isolation Forest model to export_path.""" """Save Isolation Forest model to export_path."""
pass pass
def load_model(self, import_path, device: str = 'cpu'): def load_model(self, import_path, device: str = "cpu"):
"""Load Isolation Forest model from import_path.""" """Load Isolation Forest model from import_path."""
pass pass
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)

View File

@@ -16,7 +16,7 @@ from networks.main import build_autoencoder
class KDE(object): class KDE(object):
"""A class for Kernel Density Estimation models.""" """A class for Kernel Density Estimation models."""
def __init__(self, hybrid=False, kernel='gaussian', n_jobs=-1, seed=None, **kwargs): def __init__(self, hybrid=False, kernel="gaussian", n_jobs=-1, seed=None, **kwargs):
"""Init Kernel Density Estimation instance.""" """Init Kernel Density Estimation instance."""
self.kernel = kernel self.kernel = kernel
self.n_jobs = n_jobs self.n_jobs = n_jobs
@@ -29,20 +29,30 @@ class KDE(object):
self.ae_net = None # autoencoder network for the case of a hybrid model self.ae_net = None # autoencoder network for the case of a hybrid model
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_time': None, "test_time": None,
'test_auc': None, "test_auc": None,
'test_scores': None "test_scores": None,
} }
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0, def train(
bandwidth_GridSearchCV: bool = True): 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.""" """Trains the Kernel Density Estimation model on the training data."""
logger = logging.getLogger() logger = logging.getLogger()
# do not drop last batch for non-SGD optimization shallow_ssad # do not drop last batch for non-SGD optimization shallow_ssad
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, train_loader = DataLoader(
num_workers=n_jobs_dataloader, drop_last=False) dataset=dataset.train_set,
batch_size=128,
shuffle=True,
num_workers=n_jobs_dataloader,
drop_last=False,
)
# Get data from loader # Get data from loader
X = () X = ()
@@ -50,39 +60,51 @@ class KDE(object):
inputs, _, _, _ = data inputs, _, _, _ = data
inputs = inputs.to(device) inputs = inputs.to(device)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
X = np.concatenate(X) X = np.concatenate(X)
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
start_time = time.time() start_time = time.time()
if bandwidth_GridSearchCV: if bandwidth_GridSearchCV:
# use grid search cross-validation to select bandwidth # use grid search cross-validation to select bandwidth
logger.info('Using GridSearchCV for bandwidth selection...') logger.info("Using GridSearchCV for bandwidth selection...")
params = {'bandwidth': np.logspace(0.5, 5, num=10, base=2)} 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 = GridSearchCV(
KernelDensity(kernel=self.kernel),
params,
n_jobs=self.n_jobs,
cv=5,
verbose=0,
)
hyper_kde.fit(X) hyper_kde.fit(X)
self.bandwidth = hyper_kde.best_estimator_.bandwidth self.bandwidth = hyper_kde.best_estimator_.bandwidth
logger.info('Best bandwidth: {:.8f}'.format(self.bandwidth)) logger.info("Best bandwidth: {:.8f}".format(self.bandwidth))
self.model = hyper_kde.best_estimator_ self.model = hyper_kde.best_estimator_
else: else:
# if exponential kernel, re-initialize kde with bandwidth minimizing the numerical error # if exponential kernel, re-initialize kde with bandwidth minimizing the numerical error
if self.kernel == 'exponential': if self.kernel == "exponential":
self.bandwidth = np.max(pairwise_distances(X)) ** 2 self.bandwidth = np.max(pairwise_distances(X)) ** 2
self.model = KernelDensity(kernel=self.kernel, bandwidth=self.bandwidth) self.model = KernelDensity(kernel=self.kernel, bandwidth=self.bandwidth)
self.model.fit(X) self.model.fit(X)
train_time = time.time() - start_time train_time = time.time() - start_time
self.results['train_time'] = train_time self.results["train_time"] = train_time
logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) logger.info("Training Time: {:.3f}s".format(self.results["train_time"]))
logger.info('Finished training.') logger.info("Finished training.")
def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): def test(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Tests the Kernel Density Estimation model on the test data.""" """Tests the Kernel Density Estimation model on the test data."""
logger = logging.getLogger() logger = logging.getLogger()
@@ -95,46 +117,54 @@ class KDE(object):
labels = [] labels = []
for data in test_loader: for data in test_loader:
inputs, label_batch, _, idx = data inputs, label_batch, _, idx = data
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) inputs, label_batch, idx = (
inputs.to(device),
label_batch.to(device),
idx.to(device),
)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
idxs += idx.cpu().data.numpy().astype(np.int64).tolist() idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X = np.concatenate(X) X = np.concatenate(X)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
start_time = time.time() start_time = time.time()
scores = (-1.0) * self.model.score_samples(X) scores = (-1.0) * self.model.score_samples(X)
self.results['test_time'] = time.time() - start_time self.results["test_time"] = time.time() - start_time
scores = scores.flatten() scores = scores.flatten()
# Save triples of (idx, label, score) in a list # Save triples of (idx, label, score) in a list
idx_label_score += list(zip(idxs, labels, scores.tolist())) idx_label_score += list(zip(idxs, labels, scores.tolist()))
self.results['test_scores'] = idx_label_score self.results["test_scores"] = idx_label_score
# Compute AUC # Compute AUC
_, labels, scores = zip(*idx_label_score) _, labels, scores = zip(*idx_label_score)
labels = np.array(labels) labels = np.array(labels)
scores = np.array(scores) scores = np.array(scores)
self.results['test_auc'] = roc_auc_score(labels, scores) self.results["test_auc"] = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) logger.info("Test AUC: {:.2f}%".format(100.0 * self.results["test_auc"]))
logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) logger.info("Test Time: {:.3f}s".format(self.results["test_time"]))
logger.info('Finished testing.') logger.info("Finished testing.")
def load_ae(self, dataset_name, model_path): def load_ae(self, dataset_name, model_path):
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid KDE model.""" """Load pretrained autoencoder from model_path for feature extraction in a hybrid KDE model."""
model_dict = torch.load(model_path, map_location='cpu') model_dict = torch.load(model_path, map_location="cpu")
ae_net_dict = model_dict['ae_net_dict'] ae_net_dict = model_dict["ae_net_dict"]
if dataset_name in ['mnist', 'fmnist', 'cifar10']: if dataset_name in ["mnist", "fmnist", "cifar10"]:
net_name = dataset_name + '_LeNet' net_name = dataset_name + "_LeNet"
else: else:
net_name = dataset_name + '_mlp' net_name = dataset_name + "_mlp"
if self.ae_net is None: if self.ae_net is None:
self.ae_net = build_autoencoder(net_name) self.ae_net = build_autoencoder(net_name)
@@ -154,11 +184,11 @@ class KDE(object):
"""Save KDE model to export_path.""" """Save KDE model to export_path."""
pass pass
def load_model(self, import_path, device: str = 'cpu'): def load_model(self, import_path, device: str = "cpu"):
"""Load KDE model from import_path.""" """Load KDE model from import_path."""
pass pass
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)

View File

@@ -14,7 +14,7 @@ from networks.main import build_autoencoder
class OCSVM(object): class OCSVM(object):
"""A class for One-Class SVM models.""" """A class for One-Class SVM models."""
def __init__(self, kernel='rbf', nu=0.1, hybrid=False): def __init__(self, kernel="rbf", nu=0.1, hybrid=False):
"""Init OCSVM instance.""" """Init OCSVM instance."""
self.kernel = kernel self.kernel = kernel
self.nu = nu self.nu = nu
@@ -25,25 +25,34 @@ class OCSVM(object):
self.hybrid = hybrid self.hybrid = hybrid
self.ae_net = None # autoencoder network for the case of a hybrid model 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_model = (
None # also init a model with linear kernel if hybrid approach
)
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_time': None, "test_time": None,
'test_auc': None, "test_auc": None,
'test_scores': None, "test_scores": None,
'train_time_linear': None, "train_time_linear": None,
'test_time_linear': None, "test_time_linear": None,
'test_auc_linear': None "test_auc_linear": None,
} }
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): def train(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Trains the OC-SVM model on the training data.""" """Trains the OC-SVM model on the training data."""
logger = logging.getLogger() logger = logging.getLogger()
# do not drop last batch for non-SGD optimization shallow_ssad # do not drop last batch for non-SGD optimization shallow_ssad
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, train_loader = DataLoader(
num_workers=n_jobs_dataloader, drop_last=False) dataset=dataset.train_set,
batch_size=128,
shuffle=True,
num_workers=n_jobs_dataloader,
drop_last=False,
)
# Get data from loader # Get data from loader
X = () X = ()
@@ -51,13 +60,17 @@ class OCSVM(object):
inputs, _, _, _ = data inputs, _, _, _ = data
inputs = inputs.to(device) inputs = inputs.to(device)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
X = np.concatenate(X) X = np.concatenate(X)
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
# Select model via hold-out test set of 1000 samples # Select model via hold-out test set of 1000 samples
gammas = np.logspace(-7, 2, num=10, base=2) gammas = np.logspace(-7, 2, num=10, base=2)
@@ -72,17 +85,31 @@ class OCSVM(object):
inputs, label_batch, _, _ = data inputs, label_batch, _, _ = data
inputs, label_batch = inputs.to(device), label_batch.to(device) inputs, label_batch = inputs.to(device), label_batch.to(device)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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(),) X_test += (X_batch.cpu().data.numpy(),)
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X_test, labels = np.concatenate(X_test), np.array(labels) 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_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 = 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)) 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) perm = np.random.permutation(n_test)
X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal], X_val = np.concatenate(
X_test[perm][labels[perm] == 1][:n_val_outlier])) (
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) labels = np.array([0] * n_val_normal + [1] * n_val_outlier)
i = 1 i = 1
@@ -103,30 +130,36 @@ class OCSVM(object):
# Compute AUC # Compute AUC
auc = roc_auc_score(labels, scores) auc = roc_auc_score(labels, scores)
logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s ' logger.info(
f'| Val AUC: {100. * auc:.2f} |') 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: if auc > best_auc:
best_auc = auc best_auc = auc
self.model = model self.model = model
self.gamma = gamma self.gamma = gamma
self.results['train_time'] = train_time self.results["train_time"] = train_time
i += 1 i += 1
# If hybrid, also train a model with linear kernel # If hybrid, also train a model with linear kernel
if self.hybrid: if self.hybrid:
self.linear_model = OneClassSVM(kernel='linear', nu=self.nu) self.linear_model = OneClassSVM(kernel="linear", nu=self.nu)
start_time = time.time() start_time = time.time()
self.linear_model.fit(X) self.linear_model.fit(X)
train_time = time.time() - start_time train_time = time.time() - start_time
self.results['train_time_linear'] = train_time self.results["train_time_linear"] = train_time
logger.info(f'Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}') logger.info(
logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) f"Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}"
logger.info('Finished training.') )
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): def test(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Tests the OC-SVM model on the test data.""" """Tests the OC-SVM model on the test data."""
logger = logging.getLogger() logger = logging.getLogger()
@@ -139,59 +172,75 @@ class OCSVM(object):
labels = [] labels = []
for data in test_loader: for data in test_loader:
inputs, label_batch, _, idx = data inputs, label_batch, _, idx = data
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) inputs, label_batch, idx = (
inputs.to(device),
label_batch.to(device),
idx.to(device),
)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
idxs += idx.cpu().data.numpy().astype(np.int64).tolist() idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X = np.concatenate(X) X = np.concatenate(X)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
start_time = time.time() start_time = time.time()
scores = (-1.0) * self.model.decision_function(X) scores = (-1.0) * self.model.decision_function(X)
self.results['test_time'] = time.time() - start_time self.results["test_time"] = time.time() - start_time
scores = scores.flatten() scores = scores.flatten()
self.rho = -self.model.intercept_[0] self.rho = -self.model.intercept_[0]
# Save triples of (idx, label, score) in a list # Save triples of (idx, label, score) in a list
idx_label_score += list(zip(idxs, labels, scores.tolist())) idx_label_score += list(zip(idxs, labels, scores.tolist()))
self.results['test_scores'] = idx_label_score self.results["test_scores"] = idx_label_score
# Compute AUC # Compute AUC
_, labels, scores = zip(*idx_label_score) _, labels, scores = zip(*idx_label_score)
labels = np.array(labels) labels = np.array(labels)
scores = np.array(scores) scores = np.array(scores)
self.results['test_auc'] = roc_auc_score(labels, scores) self.results["test_auc"] = roc_auc_score(labels, scores)
# If hybrid, also test model with linear kernel # If hybrid, also test model with linear kernel
if self.hybrid: if self.hybrid:
start_time = time.time() start_time = time.time()
scores_linear = (-1.0) * self.linear_model.decision_function(X) scores_linear = (-1.0) * self.linear_model.decision_function(X)
self.results['test_time_linear'] = time.time() - start_time self.results["test_time_linear"] = time.time() - start_time
scores_linear = scores_linear.flatten() scores_linear = scores_linear.flatten()
self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear) 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(
logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear'])) "Test AUC linear model: {:.2f}%".format(
100.0 * self.results["test_auc_linear"]
)
)
logger.info(
"Test Time linear model: {:.3f}s".format(
self.results["test_time_linear"]
)
)
# Log results # Log results
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) logger.info("Test AUC: {:.2f}%".format(100.0 * self.results["test_auc"]))
logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) logger.info("Test Time: {:.3f}s".format(self.results["test_time"]))
logger.info('Finished testing.') logger.info("Finished testing.")
def load_ae(self, dataset_name, model_path): def load_ae(self, dataset_name, model_path):
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid OC-SVM model.""" """Load pretrained autoencoder from model_path for feature extraction in a hybrid OC-SVM model."""
model_dict = torch.load(model_path, map_location='cpu') model_dict = torch.load(model_path, map_location="cpu")
ae_net_dict = model_dict['ae_net_dict'] ae_net_dict = model_dict["ae_net_dict"]
if dataset_name in ['mnist', 'fmnist', 'cifar10']: if dataset_name in ["mnist", "fmnist", "cifar10"]:
net_name = dataset_name + '_LeNet' net_name = dataset_name + "_LeNet"
else: else:
net_name = dataset_name + '_mlp' net_name = dataset_name + "_mlp"
if self.ae_net is None: if self.ae_net is None:
self.ae_net = build_autoencoder(net_name) self.ae_net = build_autoencoder(net_name)
@@ -211,11 +260,11 @@ class OCSVM(object):
"""Save OC-SVM model to export_path.""" """Save OC-SVM model to export_path."""
pass pass
def load_model(self, import_path, device: str = 'cpu'): def load_model(self, import_path, device: str = "cpu"):
"""Load OC-SVM model from import_path.""" """Load OC-SVM model from import_path."""
pass pass
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)

View File

@@ -8,31 +8,32 @@ from cvxopt.solvers import qp
class ConvexSSAD: class ConvexSSAD:
""" Convex semi-supervised anomaly detection with hinge-loss and L2 regularizer """Convex semi-supervised anomaly detection with hinge-loss and L2 regularizer
as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013 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 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} {w,rho,gamma>=0,xi>=0}
subject to <w,phi(x_i)> >= rho - xi_i subject to <w,phi(x_i)> >= rho - xi_i
y_j<w,phi(x_j)> >= y_j*rho + gamma - xi_j y_j<w,phi(x_j)> >= y_j*rho + gamma - xi_j
And the corresponding dual optimization problem: 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) maximize -0.5 sum_(i,j) alpha_i alpha_j y_i y_j k(x_i,x_j)
{0<=alpha_i<=eta_i} {0<=alpha_i<=eta_i}
subject to kappa <= sum_j alpha_j (for all labeled examples) subject to kappa <= sum_j alpha_j (for all labeled examples)
1 = sum_j y_i alpha_j (for all 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. We introduce labels y_i = +1 for all unlabeled examples which enables us to combine sums.
Note: Only dual solution is supported. Note: Only dual solution is supported.
Written by: Nico Goernitz, TU Berlin, 2013/14 Written by: Nico Goernitz, TU Berlin, 2013/14
""" """
PRECISION = 1e-9 # important: effects the threshold, support vectors and speed! 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): def __init__(self, kernel, y, kappa=1.0, Cp=1.0, Cu=1.0, Cn=1.0):
assert(len(y.shape) == 1) assert len(y.shape) == 1
self.kernel = kernel self.kernel = kernel
self.y = y # (vector) corresponding labels (+1,-1 and 0 for unlabeled) self.y = y # (vector) corresponding labels (+1,-1 and 0 for unlabeled)
self.kappa = kappa # (scalar) regularizer for importance of the margin self.kappa = kappa # (scalar) regularizer for importance of the margin
@@ -53,7 +54,7 @@ class ConvexSSAD:
self.cC = np.zeros(y.size) # cC=Cu (unlabeled) cC=Cp (pos) cC=Cn (neg) self.cC = np.zeros(y.size) # cC=Cu (unlabeled) cC=Cp (pos) cC=Cn (neg)
self.cC[y == 0] = Cu self.cC[y == 0] = Cu
self.cC[y == 1] = Cp self.cC[y == 1] = Cp
self.cC[y ==-1] = Cn self.cC[y == -1] = Cn
self.alphas = None self.alphas = None
self.svs = None # (vector) list of support vector (contains indices) self.svs = None # (vector) list of support vector (contains indices)
@@ -63,14 +64,18 @@ class ConvexSSAD:
# the dual constraint kappa <= sum_{i \in labeled} alpha_i = 0.0 will # the dual constraint kappa <= sum_{i \in labeled} alpha_i = 0.0 will
# prohibit a solution # prohibit a solution
if self.labeled == 0: if self.labeled == 0:
print('There are no labeled examples hence, setting kappa=0.0') print("There are no labeled examples hence, setting kappa=0.0")
self.kappa = 0.0 self.kappa = 0.0
print('Convex semi-supervised anomaly detection with {0} samples ({1} labeled).'.format(self.samples, self.labeled)) print(
"Convex semi-supervised anomaly detection with {0} samples ({1} labeled).".format(
self.samples, self.labeled
)
)
def set_train_kernel(self, kernel): def set_train_kernel(self, kernel):
dim1, dim2 = kernel.shape dim1, dim2 = kernel.shape
print([dim1, dim2]) print([dim1, dim2])
assert(dim1 == dim2 and dim1 == self.samples) assert dim1 == dim2 and dim1 == self.samples
self.kernel = kernel self.kernel = kernel
def fit(self, check_psd_eigs=False): def fit(self, check_psd_eigs=False):
@@ -81,20 +86,20 @@ class ConvexSSAD:
Y = self.cy.dot(self.cy.T) Y = self.cy.dot(self.cy.T)
# generate the final PDS kernel # generate the final PDS kernel
P = matrix(self.kernel*Y) P = matrix(self.kernel * Y)
# check for PSD # check for PSD
if check_psd_eigs: if check_psd_eigs:
eigs = np.linalg.eigvalsh(np.array(P)) eigs = np.linalg.eigvalsh(np.array(P))
if eigs[0] < 0.0: if eigs[0] < 0.0:
print('Smallest eigenvalue is {0}'.format(eigs[0])) print("Smallest eigenvalue is {0}".format(eigs[0]))
P += spdiag([-eigs[0] for i in range(N)]) P += spdiag([-eigs[0] for i in range(N)])
# there is no linear part of the objective # there is no linear part of the objective
q = matrix(0.0, (N, 1)) q = matrix(0.0, (N, 1))
# sum_i y_i alpha_i = A alpha = b = 1.0 # sum_i y_i alpha_i = A alpha = b = 1.0
A = matrix(self.cy, (1, self.samples), 'd') A = matrix(self.cy, (1, self.samples), "d")
b = matrix(1.0, (1, 1)) b = matrix(1.0, (1, 1))
# inequality constraints: G alpha <= h # inequality constraints: G alpha <= h
@@ -107,8 +112,8 @@ class ConvexSSAD:
h = matrix([h1, h2]) h = matrix([h1, h2])
if self.labeled > 0: if self.labeled > 0:
# 3) kappa <= \sum_i labeled_i alpha_i -> -cl' alpha <= -kappa # 3) kappa <= \sum_i labeled_i alpha_i -> -cl' alpha <= -kappa
print('Labeled data found.') print("Labeled data found.")
G3 = -matrix(self.cl, (1, self.cl.size), 'd') G3 = -matrix(self.cl, (1, self.cl.size), "d")
h3 = -matrix(self.kappa, (1, 1)) h3 = -matrix(self.kappa, (1, 1))
G = sparse([G12, -G12, G3]) G = sparse([G12, -G12, G3])
h = matrix([h1, h2, h3]) h = matrix([h1, h2, h3])
@@ -117,27 +122,49 @@ class ConvexSSAD:
sol = qp(P, -q, G, h, A, b) sol = qp(P, -q, G, h, A, b)
# store solution # store solution
self.alphas = np.array(sol['x']) self.alphas = np.array(sol["x"])
# 1. find all support vectors, i.e. 0 < alpha_i <= C # 1. find all support vectors, i.e. 0 < alpha_i <= C
# 2. store all support vector with alpha_i < C in 'margins' # 2. store all support vector with alpha_i < C in 'margins'
self.svs = np.where(self.alphas >= ConvexSSAD.PRECISION)[0] self.svs = np.where(self.alphas >= ConvexSSAD.PRECISION)[0]
# these should sum to one # these should sum to one
print('Validate solution:') print("Validate solution:")
print('- found {0} support vectors'.format(len(self.svs))) print("- found {0} support vectors".format(len(self.svs)))
print('0 <= alpha_i : {0} of {1}'.format(np.sum(0. <= self.alphas), N)) print("0 <= alpha_i : {0} of {1}".format(np.sum(0.0 <= self.alphas), N))
print('- sum_(i) alpha_i cy_i = {0} = 1.0'.format(np.sum(self.alphas*self.cy))) print(
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]))) "- sum_(i) alpha_i cy_i = {0} = 1.0".format(np.sum(self.alphas * self.cy))
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(
print('- sum_(i in positives) alpha_i = {0}'.format(np.sum(self.alphas[self.y == 1]))) "- sum_(i in sv) alpha_i cy_i = {0} ~ 1.0 (approx error)".format(
print('- sum_(i in negatives) alpha_i = {0}'.format(np.sum(self.alphas[self.y ==-1]))) 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) # infer threshold (rho)
psvs = np.where(self.y[self.svs] == 0)[0] psvs = np.where(self.y[self.svs] == 0)[0]
# case 1: unlabeled support vectors available # case 1: unlabeled support vectors available
self.threshold = 0. self.threshold = 0.0
unl_threshold = -1e12 unl_threshold = -1e12
lbl_threshold = -1e12 lbl_threshold = -1e12
if psvs.size > 0: if psvs.size > 0:
@@ -146,7 +173,7 @@ class ConvexSSAD:
unl_threshold = np.max(self.apply(k)) unl_threshold = np.max(self.apply(k))
if np.sum(self.cl) > 1e-12: if np.sum(self.cl) > 1e-12:
# case 2: only labeled examples available # case 2: only labeled examples available
k = self.kernel[:, self.svs] k = self.kernel[:, self.svs]
k = k[self.svs, :] k = k[self.svs, :]
thres = self.apply(k) thres = self.apply(k)
@@ -154,7 +181,7 @@ class ConvexSSAD:
ninds = np.where(self.y[self.svs] == -1)[0] ninds = np.where(self.y[self.svs] == -1)[0]
# only negatives is not possible # only negatives is not possible
if ninds.size > 0 and pinds.size == 0: if ninds.size > 0 and pinds.size == 0:
print('ERROR: Check pre-defined PRECISION.') print("ERROR: Check pre-defined PRECISION.")
lbl_threshold = np.max(thres[ninds]) lbl_threshold = np.max(thres[ninds])
elif ninds.size == 0: elif ninds.size == 0:
lbl_threshold = np.max(thres[pinds]) lbl_threshold = np.max(thres[pinds])
@@ -162,7 +189,7 @@ class ConvexSSAD:
# smallest negative + largest positive # smallest negative + largest positive
p = np.max(thres[pinds]) p = np.max(thres[pinds])
n = np.min(thres[ninds]) n = np.min(thres[ninds])
lbl_threshold = (n+p)/2. lbl_threshold = (n + p) / 2.0
self.threshold = np.max((unl_threshold, lbl_threshold)) self.threshold = np.max((unl_threshold, lbl_threshold))
def get_threshold(self): def get_threshold(self):
@@ -175,8 +202,8 @@ class ConvexSSAD:
return self.alphas return self.alphas
def apply(self, kernel): def apply(self, kernel):
""" Application of dual trained ssad. """Application of dual trained ssad.
kernel = get_kernel(Y, X[:, cssad.svs], kernel_type, kernel_param) kernel = get_kernel(Y, X[:, cssad.svs], kernel_type, kernel_param)
""" """
if kernel.shape[1] == self.samples: if kernel.shape[1] == self.samples:
# if kernel is not restricted to support vectors # if kernel is not restricted to support vectors

View File

@@ -17,7 +17,7 @@ class SSAD(object):
A class for kernel SSAD models as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013. 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): def __init__(self, kernel="rbf", kappa=1.0, Cp=1.0, Cu=1.0, Cn=1.0, hybrid=False):
"""Init SSAD instance.""" """Init SSAD instance."""
self.kernel = kernel self.kernel = kernel
self.kappa = kappa self.kappa = kappa
@@ -32,42 +32,59 @@ class SSAD(object):
self.hybrid = hybrid self.hybrid = hybrid
self.ae_net = None # autoencoder network for the case of a hybrid model 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_model = (
None # also init a model with linear kernel if hybrid approach
)
self.linear_X_svs = None self.linear_X_svs = None
self.results = { self.results = {
'train_time': None, "train_time": None,
'test_time': None, "test_time": None,
'test_auc': None, "test_auc": None,
'test_scores': None, "test_scores": None,
'train_time_linear': None, "train_time_linear": None,
'test_time_linear': None, "test_time_linear": None,
'test_auc_linear': None "test_auc_linear": None,
} }
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0): def train(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Trains the SSAD model on the training data.""" """Trains the SSAD model on the training data."""
logger = logging.getLogger() logger = logging.getLogger()
# do not drop last batch for non-SGD optimization shallow_ssad # do not drop last batch for non-SGD optimization shallow_ssad
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True, train_loader = DataLoader(
num_workers=n_jobs_dataloader, drop_last=False) dataset=dataset.train_set,
batch_size=128,
shuffle=True,
num_workers=n_jobs_dataloader,
drop_last=False,
)
# Get data from loader # Get data from loader
X = () X = ()
semi_targets = [] semi_targets = []
for data in train_loader: for data in train_loader:
inputs, _, semi_targets_batch, _ = data inputs, _, semi_targets_batch, _ = data
inputs, semi_targets_batch = inputs.to(device), semi_targets_batch.to(device) inputs, semi_targets_batch = inputs.to(device), semi_targets_batch.to(
device
)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
semi_targets += semi_targets_batch.cpu().data.numpy().astype(np.int).tolist() semi_targets += (
semi_targets_batch.cpu().data.numpy().astype(np.int).tolist()
)
X, semi_targets = np.concatenate(X), np.array(semi_targets) X, semi_targets = np.concatenate(X), np.array(semi_targets)
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
# Select model via hold-out test set of 1000 samples # Select model via hold-out test set of 1000 samples
gammas = np.logspace(-7, 2, num=10, base=2) gammas = np.logspace(-7, 2, num=10, base=2)
@@ -82,17 +99,31 @@ class SSAD(object):
inputs, label_batch, _, _ = data inputs, label_batch, _, _ = data
inputs, label_batch = inputs.to(device), label_batch.to(device) inputs, label_batch = inputs.to(device), label_batch.to(device)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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(),) X_test += (X_batch.cpu().data.numpy(),)
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X_test, labels = np.concatenate(X_test), np.array(labels) 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_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 = 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)) 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) perm = np.random.permutation(n_test)
X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal], X_val = np.concatenate(
X_test[perm][labels[perm] == 1][:n_val_outlier])) (
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) labels = np.array([0] * n_val_normal + [1] * n_val_outlier)
i = 1 i = 1
@@ -110,21 +141,25 @@ class SSAD(object):
train_time = time.time() - start_time train_time = time.time() - start_time
# Test on small hold-out set from test set # Test on small hold-out set from test set
kernel_val = pairwise_kernels(X_val, X[model.svs, :], metric=self.kernel, gamma=gamma) kernel_val = pairwise_kernels(
X_val, X[model.svs, :], metric=self.kernel, gamma=gamma
)
scores = (-1.0) * model.apply(kernel_val) scores = (-1.0) * model.apply(kernel_val)
scores = scores.flatten() scores = scores.flatten()
# Compute AUC # Compute AUC
auc = roc_auc_score(labels, scores) auc = roc_auc_score(labels, scores)
logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s ' logger.info(
f'| Val AUC: {100. * auc:.2f} |') 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: if auc > best_auc:
best_auc = auc best_auc = auc
self.model = model self.model = model
self.gamma = gamma self.gamma = gamma
self.results['train_time'] = train_time self.results["train_time"] = train_time
i += 1 i += 1
@@ -133,19 +168,25 @@ class SSAD(object):
# If hybrid, also train a model with linear kernel # If hybrid, also train a model with linear kernel
if self.hybrid: if self.hybrid:
linear_kernel = pairwise_kernels(X, X, metric='linear') 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) self.linear_model = ConvexSSAD(
linear_kernel, semi_targets, Cp=self.Cp, Cu=self.Cu, Cn=self.Cn
)
start_time = time.time() start_time = time.time()
self.linear_model.fit() self.linear_model.fit()
train_time = time.time() - start_time train_time = time.time() - start_time
self.results['train_time_linear'] = train_time self.results["train_time_linear"] = train_time
self.linear_X_svs = X[self.linear_model.svs, :] 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(
logger.info('Training Time: {:.3f}s'.format(self.results['train_time'])) f"Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}"
logger.info('Finished training.') )
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): def test(
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
):
"""Tests the SSAD model on the test data.""" """Tests the SSAD model on the test data."""
logger = logging.getLogger() logger = logging.getLogger()
@@ -158,17 +199,25 @@ class SSAD(object):
labels = [] labels = []
for data in test_loader: for data in test_loader:
inputs, label_batch, _, idx = data inputs, label_batch, _, idx = data
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device) inputs, label_batch, idx = (
inputs.to(device),
label_batch.to(device),
idx.to(device),
)
if self.hybrid: if self.hybrid:
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features inputs = self.ae_net.encoder(
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width) 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 += (X_batch.cpu().data.numpy(),)
idxs += idx.cpu().data.numpy().astype(np.int64).tolist() idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist() labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
X = np.concatenate(X) X = np.concatenate(X)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
start_time = time.time() start_time = time.time()
# Build kernel # Build kernel
@@ -176,45 +225,53 @@ class SSAD(object):
scores = (-1.0) * self.model.apply(kernel) scores = (-1.0) * self.model.apply(kernel)
self.results['test_time'] = time.time() - start_time self.results["test_time"] = time.time() - start_time
scores = scores.flatten() scores = scores.flatten()
self.rho = -self.model.threshold self.rho = -self.model.threshold
# Save triples of (idx, label, score) in a list # Save triples of (idx, label, score) in a list
idx_label_score += list(zip(idxs, labels, scores.tolist())) idx_label_score += list(zip(idxs, labels, scores.tolist()))
self.results['test_scores'] = idx_label_score self.results["test_scores"] = idx_label_score
# Compute AUC # Compute AUC
_, labels, scores = zip(*idx_label_score) _, labels, scores = zip(*idx_label_score)
labels = np.array(labels) labels = np.array(labels)
scores = np.array(scores) scores = np.array(scores)
self.results['test_auc'] = roc_auc_score(labels, scores) self.results["test_auc"] = roc_auc_score(labels, scores)
# If hybrid, also test model with linear kernel # If hybrid, also test model with linear kernel
if self.hybrid: if self.hybrid:
start_time = time.time() start_time = time.time()
linear_kernel = pairwise_kernels(X, self.linear_X_svs, metric='linear') linear_kernel = pairwise_kernels(X, self.linear_X_svs, metric="linear")
scores_linear = (-1.0) * self.linear_model.apply(linear_kernel) scores_linear = (-1.0) * self.linear_model.apply(linear_kernel)
self.results['test_time_linear'] = time.time() - start_time self.results["test_time_linear"] = time.time() - start_time
scores_linear = scores_linear.flatten() scores_linear = scores_linear.flatten()
self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear) 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(
logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear'])) "Test AUC linear model: {:.2f}%".format(
100.0 * self.results["test_auc_linear"]
)
)
logger.info(
"Test Time linear model: {:.3f}s".format(
self.results["test_time_linear"]
)
)
# Log results # Log results
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc'])) logger.info("Test AUC: {:.2f}%".format(100.0 * self.results["test_auc"]))
logger.info('Test Time: {:.3f}s'.format(self.results['test_time'])) logger.info("Test Time: {:.3f}s".format(self.results["test_time"]))
logger.info('Finished testing.') logger.info("Finished testing.")
def load_ae(self, dataset_name, model_path): def load_ae(self, dataset_name, model_path):
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid SSAD model.""" """Load pretrained autoencoder from model_path for feature extraction in a hybrid SSAD model."""
model_dict = torch.load(model_path, map_location='cpu') model_dict = torch.load(model_path, map_location="cpu")
ae_net_dict = model_dict['ae_net_dict'] ae_net_dict = model_dict["ae_net_dict"]
if dataset_name in ['mnist', 'fmnist', 'cifar10']: if dataset_name in ["mnist", "fmnist", "cifar10"]:
net_name = dataset_name + '_LeNet' net_name = dataset_name + "_LeNet"
else: else:
net_name = dataset_name + '_mlp' net_name = dataset_name + "_mlp"
if self.ae_net is None: if self.ae_net is None:
self.ae_net = build_autoencoder(net_name) self.ae_net = build_autoencoder(net_name)
@@ -234,11 +291,11 @@ class SSAD(object):
"""Save SSAD model to export_path.""" """Save SSAD model to export_path."""
pass pass
def load_model(self, import_path, device: str = 'cpu'): def load_model(self, import_path, device: str = "cpu"):
"""Load SSAD model from import_path.""" """Load SSAD model from import_path."""
pass pass
def save_results(self, export_json): def save_results(self, export_json):
"""Save results dict to a JSON-file.""" """Save results dict to a JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.results, fp) json.dump(self.results, fp)

View File

@@ -12,8 +12,16 @@ import numpy as np
class CIFAR10_Dataset(TorchvisionDataset): class CIFAR10_Dataset(TorchvisionDataset):
def __init__(self, root: str, normal_class: int = 5, known_outlier_class: int = 3, n_known_outlier_classes: int = 0, def __init__(
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): 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) super().__init__(root)
# Define normal and outlier classes # Define normal and outlier classes
@@ -28,28 +36,48 @@ class CIFAR10_Dataset(TorchvisionDataset):
elif n_known_outlier_classes == 1: elif n_known_outlier_classes == 1:
self.known_outlier_classes = tuple([known_outlier_class]) self.known_outlier_classes = tuple([known_outlier_class])
else: else:
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) self.known_outlier_classes = tuple(
random.sample(self.outlier_classes, n_known_outlier_classes)
)
# CIFAR-10 preprocessing: feature scaling to [0, 1] # CIFAR-10 preprocessing: feature scaling to [0, 1]
transform = transforms.ToTensor() transform = transforms.ToTensor()
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
# Get train set # Get train set
train_set = MyCIFAR10(root=self.root, train=True, transform=transform, target_transform=target_transform, train_set = MyCIFAR10(
download=True) root=self.root,
train=True,
transform=transform,
target_transform=target_transform,
download=True,
)
# Create semi-supervised setting # Create semi-supervised setting
idx, _, semi_targets = create_semisupervised_setting(np.array(train_set.targets), self.normal_classes, idx, _, semi_targets = create_semisupervised_setting(
self.outlier_classes, self.known_outlier_classes, np.array(train_set.targets),
ratio_known_normal, ratio_known_outlier, ratio_pollution) self.normal_classes,
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels 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 # Subset train_set to semi-supervised setup
self.train_set = Subset(train_set, idx) self.train_set = Subset(train_set, idx)
# Get test set # Get test set
self.test_set = MyCIFAR10(root=self.root, train=False, transform=transform, target_transform=target_transform, self.test_set = MyCIFAR10(
download=True) root=self.root,
train=False,
transform=transform,
target_transform=target_transform,
download=True,
)
class MyCIFAR10(CIFAR10): class MyCIFAR10(CIFAR10):
@@ -71,7 +99,11 @@ class MyCIFAR10(CIFAR10):
Returns: Returns:
tuple: (image, target, semi_target, index) tuple: (image, target, semi_target, index)
""" """
img, target, semi_target = self.data[index], self.targets[index], int(self.semi_targets[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 # doing this so that it is consistent with all other datasets
# to return a PIL Image # to return a PIL Image

View File

@@ -11,8 +11,16 @@ import random
class FashionMNIST_Dataset(TorchvisionDataset): class FashionMNIST_Dataset(TorchvisionDataset):
def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0, def __init__(
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): 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) super().__init__(root)
# Define normal and outlier classes # Define normal and outlier classes
@@ -27,28 +35,48 @@ class FashionMNIST_Dataset(TorchvisionDataset):
elif n_known_outlier_classes == 1: elif n_known_outlier_classes == 1:
self.known_outlier_classes = tuple([known_outlier_class]) self.known_outlier_classes = tuple([known_outlier_class])
else: else:
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) self.known_outlier_classes = tuple(
random.sample(self.outlier_classes, n_known_outlier_classes)
)
# FashionMNIST preprocessing: feature scaling to [0, 1] # FashionMNIST preprocessing: feature scaling to [0, 1]
transform = transforms.ToTensor() transform = transforms.ToTensor()
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
# Get train set # Get train set
train_set = MyFashionMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform, train_set = MyFashionMNIST(
download=True) root=self.root,
train=True,
transform=transform,
target_transform=target_transform,
download=True,
)
# Create semi-supervised setting # Create semi-supervised setting
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, idx, _, semi_targets = create_semisupervised_setting(
self.outlier_classes, self.known_outlier_classes, train_set.targets.cpu().data.numpy(),
ratio_known_normal, ratio_known_outlier, ratio_pollution) self.normal_classes,
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels 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 # Subset train_set to semi-supervised setup
self.train_set = Subset(train_set, idx) self.train_set = Subset(train_set, idx)
# Get test set # Get test set
self.test_set = MyFashionMNIST(root=self.root, train=False, transform=transform, self.test_set = MyFashionMNIST(
target_transform=target_transform, download=True) root=self.root,
train=False,
transform=transform,
target_transform=target_transform,
download=True,
)
class MyFashionMNIST(FashionMNIST): class MyFashionMNIST(FashionMNIST):
@@ -70,11 +98,15 @@ class MyFashionMNIST(FashionMNIST):
Returns: Returns:
tuple: (image, target, semi_target, index) tuple: (image, target, semi_target, index)
""" """
img, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[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 # doing this so that it is consistent with all other datasets
# to return a PIL Image # to return a PIL Image
img = Image.fromarray(img.numpy(), mode='L') img = Image.fromarray(img.numpy(), mode="L")
if self.transform is not None: if self.transform is not None:
img = self.transform(img) img = self.transform(img)

View File

@@ -4,51 +4,83 @@ from .cifar10 import CIFAR10_Dataset
from .odds import ODDSADDataset from .odds import ODDSADDataset
def load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes: int = 0, def load_dataset(
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0, dataset_name,
random_state=None): 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.""" """Loads the dataset."""
implemented_datasets = ('mnist', 'fmnist', 'cifar10', implemented_datasets = (
'arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid') "mnist",
"fmnist",
"cifar10",
"arrhythmia",
"cardio",
"satellite",
"satimage-2",
"shuttle",
"thyroid",
)
assert dataset_name in implemented_datasets assert dataset_name in implemented_datasets
dataset = None dataset = None
if dataset_name == 'mnist': if dataset_name == "mnist":
dataset = MNIST_Dataset(root=data_path, dataset = MNIST_Dataset(
normal_class=normal_class, root=data_path,
known_outlier_class=known_outlier_class, normal_class=normal_class,
n_known_outlier_classes=n_known_outlier_classes, known_outlier_class=known_outlier_class,
ratio_known_normal=ratio_known_normal, n_known_outlier_classes=n_known_outlier_classes,
ratio_known_outlier=ratio_known_outlier, ratio_known_normal=ratio_known_normal,
ratio_pollution=ratio_pollution) ratio_known_outlier=ratio_known_outlier,
ratio_pollution=ratio_pollution,
)
if dataset_name == 'fmnist': if dataset_name == "fmnist":
dataset = FashionMNIST_Dataset(root=data_path, dataset = FashionMNIST_Dataset(
normal_class=normal_class, root=data_path,
known_outlier_class=known_outlier_class, normal_class=normal_class,
n_known_outlier_classes=n_known_outlier_classes, known_outlier_class=known_outlier_class,
ratio_known_normal=ratio_known_normal, n_known_outlier_classes=n_known_outlier_classes,
ratio_known_outlier=ratio_known_outlier, ratio_known_normal=ratio_known_normal,
ratio_pollution=ratio_pollution) ratio_known_outlier=ratio_known_outlier,
ratio_pollution=ratio_pollution,
)
if dataset_name == 'cifar10': if dataset_name == "cifar10":
dataset = CIFAR10_Dataset(root=data_path, dataset = CIFAR10_Dataset(
normal_class=normal_class, root=data_path,
known_outlier_class=known_outlier_class, normal_class=normal_class,
n_known_outlier_classes=n_known_outlier_classes, known_outlier_class=known_outlier_class,
ratio_known_normal=ratio_known_normal, n_known_outlier_classes=n_known_outlier_classes,
ratio_known_outlier=ratio_known_outlier, ratio_known_normal=ratio_known_normal,
ratio_pollution=ratio_pollution) ratio_known_outlier=ratio_known_outlier,
ratio_pollution=ratio_pollution,
)
if dataset_name in ('arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid'): if dataset_name in (
dataset = ODDSADDataset(root=data_path, "arrhythmia",
dataset_name=dataset_name, "cardio",
n_known_outlier_classes=n_known_outlier_classes, "satellite",
ratio_known_normal=ratio_known_normal, "satimage-2",
ratio_known_outlier=ratio_known_outlier, "shuttle",
ratio_pollution=ratio_pollution, "thyroid",
random_state=random_state) ):
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 return dataset

View File

@@ -11,8 +11,16 @@ import random
class MNIST_Dataset(TorchvisionDataset): class MNIST_Dataset(TorchvisionDataset):
def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0, def __init__(
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0): 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) super().__init__(root)
# Define normal and outlier classes # Define normal and outlier classes
@@ -27,28 +35,48 @@ class MNIST_Dataset(TorchvisionDataset):
elif n_known_outlier_classes == 1: elif n_known_outlier_classes == 1:
self.known_outlier_classes = tuple([known_outlier_class]) self.known_outlier_classes = tuple([known_outlier_class])
else: else:
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes)) self.known_outlier_classes = tuple(
random.sample(self.outlier_classes, n_known_outlier_classes)
)
# MNIST preprocessing: feature scaling to [0, 1] # MNIST preprocessing: feature scaling to [0, 1]
transform = transforms.ToTensor() transform = transforms.ToTensor()
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes)) target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
# Get train set # Get train set
train_set = MyMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform, train_set = MyMNIST(
download=True) root=self.root,
train=True,
transform=transform,
target_transform=target_transform,
download=True,
)
# Create semi-supervised setting # Create semi-supervised setting
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, idx, _, semi_targets = create_semisupervised_setting(
self.outlier_classes, self.known_outlier_classes, train_set.targets.cpu().data.numpy(),
ratio_known_normal, ratio_known_outlier, ratio_pollution) self.normal_classes,
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels 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 # Subset train_set to semi-supervised setup
self.train_set = Subset(train_set, idx) self.train_set = Subset(train_set, idx)
# Get test set # Get test set
self.test_set = MyMNIST(root=self.root, train=False, transform=transform, target_transform=target_transform, self.test_set = MyMNIST(
download=True) root=self.root,
train=False,
transform=transform,
target_transform=target_transform,
download=True,
)
class MyMNIST(MNIST): class MyMNIST(MNIST):
@@ -70,11 +98,15 @@ class MyMNIST(MNIST):
Returns: Returns:
tuple: (image, target, semi_target, index) tuple: (image, target, semi_target, index)
""" """
img, target, semi_target = self.data[index], int(self.targets[index]), int(self.semi_targets[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 # doing this so that it is consistent with all other datasets
# to return a PIL Image # to return a PIL Image
img = Image.fromarray(img.numpy(), mode='L') img = Image.fromarray(img.numpy(), mode="L")
if self.transform is not None: if self.transform is not None:
img = self.transform(img) img = self.transform(img)

View File

@@ -8,8 +8,16 @@ import torch
class ODDSADDataset(BaseADDataset): class ODDSADDataset(BaseADDataset):
def __init__(self, root: str, dataset_name: str, n_known_outlier_classes: int = 0, ratio_known_normal: float = 0.0, def __init__(
ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0, random_state=None): 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) super().__init__(root)
# Define normal and outlier classes # Define normal and outlier classes
@@ -23,25 +31,58 @@ class ODDSADDataset(BaseADDataset):
self.known_outlier_classes = (1,) self.known_outlier_classes = (1,)
# Get train set # Get train set
train_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=True, random_state=random_state, train_set = ODDSDataset(
download=True) root=self.root,
dataset_name=dataset_name,
train=True,
random_state=random_state,
download=True,
)
# Create semi-supervised setting # Create semi-supervised setting
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes, idx, _, semi_targets = create_semisupervised_setting(
self.outlier_classes, self.known_outlier_classes, train_set.targets.cpu().data.numpy(),
ratio_known_normal, ratio_known_outlier, ratio_pollution) self.normal_classes,
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels 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 # Subset train_set to semi-supervised setup
self.train_set = Subset(train_set, idx) self.train_set = Subset(train_set, idx)
# Get test set # Get test set
self.test_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=False, random_state=random_state) 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) -> ( def loaders(
DataLoader, DataLoader): self,
train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train, batch_size: int,
num_workers=num_workers, drop_last=True) shuffle_train=True,
test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test, shuffle_test=False,
num_workers=num_workers, drop_last=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 return train_loader, test_loader

View File

@@ -2,8 +2,15 @@ import torch
import numpy as np import numpy as np
def create_semisupervised_setting(labels, normal_classes, outlier_classes, known_outlier_classes, def create_semisupervised_setting(
ratio_known_normal, ratio_known_outlier, ratio_pollution): labels,
normal_classes,
outlier_classes,
known_outlier_classes,
ratio_known_normal,
ratio_known_outlier,
ratio_pollution,
):
""" """
Create a semi-supervised data setting. Create a semi-supervised data setting.
:param labels: np.array with labels of all dataset samples :param labels: np.array with labels of all dataset samples
@@ -17,15 +24,31 @@ def create_semisupervised_setting(labels, normal_classes, outlier_classes, known
""" """
idx_normal = np.argwhere(np.isin(labels, normal_classes)).flatten() idx_normal = np.argwhere(np.isin(labels, normal_classes)).flatten()
idx_outlier = np.argwhere(np.isin(labels, outlier_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() idx_known_outlier_candidates = np.argwhere(
np.isin(labels, known_outlier_classes)
).flatten()
n_normal = len(idx_normal) n_normal = len(idx_normal)
# Solve system of linear equations to obtain respective number of samples # Solve system of linear equations to obtain respective number of samples
a = np.array([[1, 1, 0, 0], a = np.array(
[(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)], [1, 1, 0, 0],
[0, -ratio_pollution, (1-ratio_pollution), 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]) b = np.array([n_normal, 0, 0, 0])
x = np.linalg.solve(a, b) x = np.linalg.solve(a, b)
@@ -41,9 +64,13 @@ def create_semisupervised_setting(labels, normal_classes, outlier_classes, known
perm_known_outlier = np.random.permutation(len(idx_known_outlier_candidates)) perm_known_outlier = np.random.permutation(len(idx_known_outlier_candidates))
idx_known_normal = idx_normal[perm_normal[:n_known_normal]].tolist() 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_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_unlabeled_outlier = idx_outlier[perm_outlier[:n_unlabeled_outlier]].tolist()
idx_known_outlier = idx_known_outlier_candidates[perm_known_outlier[:n_known_outlier]].tolist() idx_known_outlier = idx_known_outlier_candidates[
perm_known_outlier[:n_known_outlier]
].tolist()
# Get original class labels # Get original class labels
labels_known_normal = labels[idx_known_normal].tolist() labels_known_normal = labels[idx_known_normal].tolist()
@@ -53,14 +80,32 @@ def create_semisupervised_setting(labels, normal_classes, outlier_classes, known
# Get semi-supervised setting labels # Get semi-supervised setting labels
semi_labels_known_normal = np.ones(n_known_normal).astype(np.int32).tolist() 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_normal = (
semi_labels_unlabeled_outlier = np.zeros(n_unlabeled_outlier).astype(np.int32).tolist() 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() semi_labels_known_outlier = (-np.ones(n_known_outlier).astype(np.int32)).tolist()
# Create final lists # Create final lists
list_idx = idx_known_normal + idx_unlabeled_normal + idx_unlabeled_outlier + idx_known_outlier list_idx = (
list_labels = labels_known_normal + labels_unlabeled_normal + labels_unlabeled_outlier + labels_known_outlier idx_known_normal
list_semi_labels = (semi_labels_known_normal + semi_labels_unlabeled_normal + semi_labels_unlabeled_outlier + idx_unlabeled_normal
+ semi_labels_known_outlier) + 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 return list_idx, list_labels, list_semi_labels

View File

@@ -14,66 +14,222 @@ from datasets.main import load_dataset
# Settings # Settings
################################################################################ ################################################################################
@click.command() @click.command()
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite', @click.argument(
'satimage-2', 'shuttle', 'thyroid'])) "dataset_name",
@click.argument('net_name', type=click.Choice(['mnist_LeNet', 'fmnist_LeNet', 'cifar10_LeNet', 'arrhythmia_mlp', type=click.Choice(
'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', [
'thyroid_mlp'])) "mnist",
@click.argument('xp_path', type=click.Path(exists=True)) "fmnist",
@click.argument('data_path', type=click.Path(exists=True)) "cifar10",
@click.option('--load_config', type=click.Path(exists=True), default=None, "arrhythmia",
help='Config JSON-file path (default: None).') "cardio",
@click.option('--load_model', type=click.Path(exists=True), default=None, "satellite",
help='Model file path (default: None).') "satimage-2",
@click.option('--eta', type=float, default=1.0, help='Deep SAD hyperparameter eta (must be 0 < eta).') "shuttle",
@click.option('--ratio_known_normal', type=float, default=0.0, "thyroid",
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, @click.argument(
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.') "net_name",
@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).') type=click.Choice(
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.') [
@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam', "mnist_LeNet",
help='Name of the optimizer to use for Deep SAD network training.') "fmnist_LeNet",
@click.option('--lr', type=float, default=0.001, "cifar10_LeNet",
help='Initial learning rate for Deep SAD network training. Default=0.001') "arrhythmia_mlp",
@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.') "cardio_mlp",
@click.option('--lr_milestone', type=int, default=0, multiple=True, "satellite_mlp",
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') "satimage-2_mlp",
@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.') "shuttle_mlp",
@click.option('--weight_decay', type=float, default=1e-6, "thyroid_mlp",
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', @click.argument("xp_path", type=click.Path(exists=True))
help='Name of the optimizer to use for autoencoder pretraining.') @click.argument("data_path", type=click.Path(exists=True))
@click.option('--ae_lr', type=float, default=0.001, @click.option(
help='Initial learning rate for autoencoder pretraining. Default=0.001') "--load_config",
@click.option('--ae_n_epochs', type=int, default=100, help='Number of epochs to train autoencoder.') type=click.Path(exists=True),
@click.option('--ae_lr_milestone', type=int, default=0, multiple=True, default=None,
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.') help="Config JSON-file path (default: None).",
@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, @click.option(
help='Weight decay (L2 penalty) hyperparameter for autoencoder objective.') "--load_model",
@click.option('--num_threads', type=int, default=0, type=click.Path(exists=True),
help='Number of threads used for parallelizing CPU operations. 0 means that all resources are used.') default=None,
@click.option('--n_jobs_dataloader', type=int, default=0, help="Model file path (default: None).",
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, @click.option(
help='Specify the normal class of the dataset (all other classes are considered anomalous).') "--eta",
@click.option('--known_outlier_class', type=int, default=1, type=float,
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.') default=1.0,
@click.option('--n_known_outlier_classes', type=int, default=0, help="Deep SAD hyperparameter eta (must be 0 < eta).",
help='Number of known outlier classes.' )
'If 0, no anomalies are known.' @click.option(
'If 1, outlier class as specified in --known_outlier_class option.' "--ratio_known_normal",
'If > 1, the specified number of outlier classes will be sampled at random.') type=float,
def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, eta, default=0.0,
ratio_known_normal, ratio_known_outlier, ratio_pollution, device, seed, help="Ratio of known (labeled) normal training examples.",
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, @click.option(
num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes): "--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. Deep SAD, a method for deep semi-supervised anomaly detection.
@@ -90,150 +246,192 @@ def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, et
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter(
log_file = xp_path + '/log.txt' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log_file = xp_path + "/log.txt"
file_handler = logging.FileHandler(log_file) file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO) file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
# Print paths # Print paths
logger.info('Log file is %s' % log_file) logger.info("Log file is %s" % log_file)
logger.info('Data path is %s' % data_path) logger.info("Data path is %s" % data_path)
logger.info('Export path is %s' % xp_path) logger.info("Export path is %s" % xp_path)
# Print experimental setup # Print experimental setup
logger.info('Dataset: %s' % dataset_name) logger.info("Dataset: %s" % dataset_name)
logger.info('Normal class: %d' % normal_class) logger.info("Normal class: %d" % normal_class)
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal) 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("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution) logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
if n_known_outlier_classes == 1: if n_known_outlier_classes == 1:
logger.info('Known anomaly class: %d' % known_outlier_class) logger.info("Known anomaly class: %d" % known_outlier_class)
else: else:
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes) logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
logger.info('Network: %s' % net_name) logger.info("Network: %s" % net_name)
# If specified, load experiment config from JSON-file # If specified, load experiment config from JSON-file
if load_config: if load_config:
cfg.load_config(import_json=load_config) cfg.load_config(import_json=load_config)
logger.info('Loaded configuration from %s.' % load_config) logger.info("Loaded configuration from %s." % load_config)
# Print model configuration # Print model configuration
logger.info('Eta-parameter: %.2f' % cfg.settings['eta']) logger.info("Eta-parameter: %.2f" % cfg.settings["eta"])
# Set seed # Set seed
if cfg.settings['seed'] != -1: if cfg.settings["seed"] != -1:
random.seed(cfg.settings['seed']) random.seed(cfg.settings["seed"])
np.random.seed(cfg.settings['seed']) np.random.seed(cfg.settings["seed"])
torch.manual_seed(cfg.settings['seed']) torch.manual_seed(cfg.settings["seed"])
torch.cuda.manual_seed(cfg.settings['seed']) torch.cuda.manual_seed(cfg.settings["seed"])
torch.backends.cudnn.deterministic = True torch.backends.cudnn.deterministic = True
logger.info('Set seed to %d.' % cfg.settings['seed']) logger.info("Set seed to %d." % cfg.settings["seed"])
# Default device to 'cpu' if cuda is not available # Default device to 'cpu' if cuda is not available
if not torch.cuda.is_available(): if not torch.cuda.is_available():
device = 'cpu' device = "cpu"
# Set the number of threads used for parallelizing CPU operations # Set the number of threads used for parallelizing CPU operations
if num_threads > 0: if num_threads > 0:
torch.set_num_threads(num_threads) torch.set_num_threads(num_threads)
logger.info('Computation device: %s' % device) logger.info("Computation device: %s" % device)
logger.info('Number of threads: %d' % num_threads) logger.info("Number of threads: %d" % num_threads)
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader) logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
# Load data # Load data
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes, dataset = load_dataset(
ratio_known_normal, ratio_known_outlier, ratio_pollution, dataset_name,
random_state=np.random.RandomState(cfg.settings['seed'])) 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 # Log random sample of known anomaly classes if more than 1 class
if n_known_outlier_classes > 1: if n_known_outlier_classes > 1:
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,)) logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
# Initialize DeepSAD model and set neural network phi # Initialize DeepSAD model and set neural network phi
deepSAD = DeepSAD(cfg.settings['eta']) deepSAD = DeepSAD(cfg.settings["eta"])
deepSAD.set_network(net_name) deepSAD.set_network(net_name)
# If specified, load Deep SAD model (center c, network weights, and possibly autoencoder weights) # If specified, load Deep SAD model (center c, network weights, and possibly autoencoder weights)
if load_model: if load_model:
deepSAD.load_model(model_path=load_model, load_ae=True, map_location=device) deepSAD.load_model(model_path=load_model, load_ae=True, map_location=device)
logger.info('Loading model from %s.' % load_model) logger.info("Loading model from %s." % load_model)
logger.info('Pretraining: %s' % pretrain) logger.info("Pretraining: %s" % pretrain)
if pretrain: if pretrain:
# Log pretraining details # Log pretraining details
logger.info('Pretraining optimizer: %s' % cfg.settings['ae_optimizer_name']) logger.info("Pretraining optimizer: %s" % cfg.settings["ae_optimizer_name"])
logger.info('Pretraining learning rate: %g' % cfg.settings['ae_lr']) logger.info("Pretraining learning rate: %g" % cfg.settings["ae_lr"])
logger.info('Pretraining epochs: %d' % cfg.settings['ae_n_epochs']) 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(
logger.info('Pretraining batch size: %d' % cfg.settings['ae_batch_size']) "Pretraining learning rate scheduler milestones: %s"
logger.info('Pretraining weight decay: %g' % cfg.settings['ae_weight_decay']) % (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) # Pretrain model on dataset (via autoencoder)
deepSAD.pretrain(dataset, deepSAD.pretrain(
optimizer_name=cfg.settings['ae_optimizer_name'], dataset,
lr=cfg.settings['ae_lr'], optimizer_name=cfg.settings["ae_optimizer_name"],
n_epochs=cfg.settings['ae_n_epochs'], lr=cfg.settings["ae_lr"],
lr_milestones=cfg.settings['ae_lr_milestone'], n_epochs=cfg.settings["ae_n_epochs"],
batch_size=cfg.settings['ae_batch_size'], lr_milestones=cfg.settings["ae_lr_milestone"],
weight_decay=cfg.settings['ae_weight_decay'], batch_size=cfg.settings["ae_batch_size"],
device=device, weight_decay=cfg.settings["ae_weight_decay"],
n_jobs_dataloader=n_jobs_dataloader) device=device,
n_jobs_dataloader=n_jobs_dataloader,
)
# Save pretraining results # Save pretraining results
deepSAD.save_ae_results(export_json=xp_path + '/ae_results.json') deepSAD.save_ae_results(export_json=xp_path + "/ae_results.json")
# Log training details # Log training details
logger.info('Training optimizer: %s' % cfg.settings['optimizer_name']) logger.info("Training optimizer: %s" % cfg.settings["optimizer_name"])
logger.info('Training learning rate: %g' % cfg.settings['lr']) logger.info("Training learning rate: %g" % cfg.settings["lr"])
logger.info('Training epochs: %d' % cfg.settings['n_epochs']) logger.info("Training epochs: %d" % cfg.settings["n_epochs"])
logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],)) logger.info(
logger.info('Training batch size: %d' % cfg.settings['batch_size']) "Training learning rate scheduler milestones: %s"
logger.info('Training weight decay: %g' % cfg.settings['weight_decay']) % (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 # Train model on dataset
deepSAD.train(dataset, deepSAD.train(
optimizer_name=cfg.settings['optimizer_name'], dataset,
lr=cfg.settings['lr'], optimizer_name=cfg.settings["optimizer_name"],
n_epochs=cfg.settings['n_epochs'], lr=cfg.settings["lr"],
lr_milestones=cfg.settings['lr_milestone'], n_epochs=cfg.settings["n_epochs"],
batch_size=cfg.settings['batch_size'], lr_milestones=cfg.settings["lr_milestone"],
weight_decay=cfg.settings['weight_decay'], batch_size=cfg.settings["batch_size"],
device=device, weight_decay=cfg.settings["weight_decay"],
n_jobs_dataloader=n_jobs_dataloader) device=device,
n_jobs_dataloader=n_jobs_dataloader,
)
# Test model # Test model
deepSAD.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader) deepSAD.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
# Save results, model, and configuration # Save results, model, and configuration
deepSAD.save_results(export_json=xp_path + '/results.json') deepSAD.save_results(export_json=xp_path + "/results.json")
deepSAD.save_model(export_model=xp_path + '/model.tar') deepSAD.save_model(export_model=xp_path + "/model.tar")
cfg.save_config(export_json=xp_path + '/config.json') cfg.save_config(export_json=xp_path + "/config.json")
# Plot most anomalous and most normal test samples # Plot most anomalous and most normal test samples
indices, labels, scores = zip(*deepSAD.results['test_scores']) indices, labels, scores = zip(*deepSAD.results["test_scores"])
indices, labels, scores = np.array(indices), np.array(labels), np.array(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_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 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", "cifar10"):
if dataset_name in ('mnist', 'fmnist'): if dataset_name in ("mnist", "fmnist"):
X_all_low = dataset.test_set.data[idx_all_sorted[:32], ...].unsqueeze(1) 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_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_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1) 1
)
X_normal_high = dataset.test_set.data[
idx_normal_sorted[-32:], ...
].unsqueeze(1)
if dataset_name == 'cifar10': 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_low = torch.tensor(
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2))) np.transpose(
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2))) dataset.test_set.data[idx_all_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))) )
)
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_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_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_low, export_img=xp_path + "/normals_low", padding=2)
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2) plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@@ -1,10 +1,22 @@
from .main import build_network, build_autoencoder from .main import build_network, build_autoencoder
from .mnist_LeNet import MNIST_LeNet, MNIST_LeNet_Decoder, MNIST_LeNet_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 .fmnist_LeNet import (
from .cifar10_LeNet import CIFAR10_LeNet, CIFAR10_LeNet_Decoder, CIFAR10_LeNet_Autoencoder 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 .mlp import MLP, MLP_Decoder, MLP_Autoencoder
from .layers.stochastic import GaussianSample from .layers.stochastic import GaussianSample
from .layers.standard import Standardize from .layers.standard import Standardize
from .inference.distributions import log_standard_gaussian, log_gaussian, log_standard_categorical from .inference.distributions import (
log_standard_gaussian,
log_gaussian,
log_standard_categorical,
)
from .vae import VariationalAutoencoder, Encoder, Decoder from .vae import VariationalAutoencoder, Encoder, Decoder
from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel

View File

@@ -41,17 +41,27 @@ class CIFAR10_LeNet_Decoder(BaseNet):
self.rep_dim = rep_dim self.rep_dim = rep_dim
self.deconv1 = nn.ConvTranspose2d(int(self.rep_dim / (4 * 4)), 128, 5, bias=False, padding=2) self.deconv1 = nn.ConvTranspose2d(
nn.init.xavier_uniform_(self.deconv1.weight, gain=nn.init.calculate_gain('leaky_relu')) 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.bn2d4 = nn.BatchNorm2d(128, eps=1e-04, affine=False)
self.deconv2 = nn.ConvTranspose2d(128, 64, 5, bias=False, padding=2) 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')) 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.bn2d5 = nn.BatchNorm2d(64, eps=1e-04, affine=False)
self.deconv3 = nn.ConvTranspose2d(64, 32, 5, bias=False, padding=2) 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')) 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.bn2d6 = nn.BatchNorm2d(32, eps=1e-04, affine=False)
self.deconv4 = nn.ConvTranspose2d(32, 3, 5, bias=False, padding=2) 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')) nn.init.xavier_uniform_(
self.deconv4.weight, gain=nn.init.calculate_gain("leaky_relu")
)
def forward(self, x): def forward(self, x):
x = x.view(int(x.size(0)), int(self.rep_dim / (4 * 4)), 4, 4) x = x.view(int(x.size(0)), int(self.rep_dim / (4 * 4)), 4, 4)

View File

@@ -97,7 +97,9 @@ class StackedDeepGenerativeModel(DeepGenerativeModel):
:param features: a pre-trained M1 model of class 'VariationalAutoencoder' trained on the same dataset. :param features: a pre-trained M1 model of class 'VariationalAutoencoder' trained on the same dataset.
""" """
[x_dim, y_dim, z_dim, h_dim] = dims [x_dim, y_dim, z_dim, h_dim] = dims
super(StackedDeepGenerativeModel, self).__init__([features.z_dim, y_dim, z_dim, h_dim]) super(StackedDeepGenerativeModel, self).__init__(
[features.z_dim, y_dim, z_dim, h_dim]
)
# Be sure to reconstruct with the same dimensions # Be sure to reconstruct with the same dimensions
in_features = self.decoder.reconstruction.in_features in_features = self.decoder.reconstruction.in_features

View File

@@ -11,7 +11,7 @@ def log_standard_gaussian(x):
:param x: point to evaluate :param x: point to evaluate
:return: log N(x|0,I) :return: log N(x|0,I)
""" """
return torch.sum(-0.5 * math.log(2 * math.pi) - x ** 2 / 2, dim=-1) return torch.sum(-0.5 * math.log(2 * math.pi) - x**2 / 2, dim=-1)
def log_gaussian(x, mu, log_var): def log_gaussian(x, mu, log_var):
@@ -23,7 +23,11 @@ def log_gaussian(x, mu, log_var):
:param log_var: log variance :param log_var: log variance
:return: log N(x|µ,σI) :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)) 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) return torch.sum(log_pdf, dim=-1)

View File

@@ -21,7 +21,8 @@ class Standardize(Module):
mu: the learnable translation parameter μ. mu: the learnable translation parameter μ.
std: the learnable scale parameter σ. std: the learnable scale parameter σ.
""" """
__constants__ = ['mu']
__constants__ = ["mu"]
def __init__(self, in_features, bias=True, eps=1e-6): def __init__(self, in_features, bias=True, eps=1e-6):
super(Standardize, self).__init__() super(Standardize, self).__init__()
@@ -32,7 +33,7 @@ class Standardize(Module):
if bias: if bias:
self.mu = Parameter(torch.Tensor(in_features)) self.mu = Parameter(torch.Tensor(in_features))
else: else:
self.register_parameter('mu', None) self.register_parameter("mu", None)
self.reset_parameters() self.reset_parameters()
def reset_parameters(self): def reset_parameters(self):
@@ -47,6 +48,6 @@ class Standardize(Module):
return x return x
def extra_repr(self): def extra_repr(self):
return 'in_features={}, out_features={}, bias={}'.format( return "in_features={}, out_features={}, bias={}".format(
self.in_features, self.out_features, self.mu is not None self.in_features, self.out_features, self.mu is not None
) )

View File

@@ -9,78 +9,106 @@ from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel
def build_network(net_name, ae_net=None): def build_network(net_name, ae_net=None):
"""Builds the neural network.""" """Builds the neural network."""
implemented_networks = ('mnist_LeNet', 'mnist_DGM_M2', 'mnist_DGM_M1M2', implemented_networks = (
'fmnist_LeNet', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2', "mnist_LeNet",
'cifar10_LeNet', 'cifar10_DGM_M2', 'cifar10_DGM_M1M2', "mnist_DGM_M2",
'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', "mnist_DGM_M1M2",
'thyroid_mlp', "fmnist_LeNet",
'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2', 'satimage-2_DGM_M2', "fmnist_DGM_M2",
'shuttle_DGM_M2', 'thyroid_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 assert net_name in implemented_networks
net = None net = None
if net_name == 'mnist_LeNet': if net_name == "mnist_LeNet":
net = MNIST_LeNet() net = MNIST_LeNet()
if net_name == 'mnist_DGM_M2': if net_name == "mnist_DGM_M2":
net = DeepGenerativeModel([1*28*28, 2, 32, [128, 64]], classifier_net=MNIST_LeNet) net = DeepGenerativeModel(
[1 * 28 * 28, 2, 32, [128, 64]], classifier_net=MNIST_LeNet
)
if net_name == 'mnist_DGM_M1M2': if net_name == "mnist_DGM_M1M2":
net = StackedDeepGenerativeModel([1*28*28, 2, 32, [128, 64]], features=ae_net) net = StackedDeepGenerativeModel(
[1 * 28 * 28, 2, 32, [128, 64]], features=ae_net
)
if net_name == 'fmnist_LeNet': if net_name == "fmnist_LeNet":
net = FashionMNIST_LeNet() net = FashionMNIST_LeNet()
if net_name == 'fmnist_DGM_M2': if net_name == "fmnist_DGM_M2":
net = DeepGenerativeModel([1*28*28, 2, 64, [256, 128]], classifier_net=FashionMNIST_LeNet) net = DeepGenerativeModel(
[1 * 28 * 28, 2, 64, [256, 128]], classifier_net=FashionMNIST_LeNet
)
if net_name == 'fmnist_DGM_M1M2': if net_name == "fmnist_DGM_M1M2":
net = StackedDeepGenerativeModel([1*28*28, 2, 64, [256, 128]], features=ae_net) net = StackedDeepGenerativeModel(
[1 * 28 * 28, 2, 64, [256, 128]], features=ae_net
)
if net_name == 'cifar10_LeNet': if net_name == "cifar10_LeNet":
net = CIFAR10_LeNet() net = CIFAR10_LeNet()
if net_name == 'cifar10_DGM_M2': if net_name == "cifar10_DGM_M2":
net = DeepGenerativeModel([3*32*32, 2, 128, [512, 256]], classifier_net=CIFAR10_LeNet) net = DeepGenerativeModel(
[3 * 32 * 32, 2, 128, [512, 256]], classifier_net=CIFAR10_LeNet
)
if net_name == 'cifar10_DGM_M1M2': if net_name == "cifar10_DGM_M1M2":
net = StackedDeepGenerativeModel([3*32*32, 2, 128, [512, 256]], features=ae_net) net = StackedDeepGenerativeModel(
[3 * 32 * 32, 2, 128, [512, 256]], features=ae_net
)
if net_name == 'arrhythmia_mlp': if net_name == "arrhythmia_mlp":
net = MLP(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False) net = MLP(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False)
if net_name == 'cardio_mlp': if net_name == "cardio_mlp":
net = MLP(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False) net = MLP(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'satellite_mlp': if net_name == "satellite_mlp":
net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'satimage-2_mlp': if net_name == "satimage-2_mlp":
net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) net = MLP(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'shuttle_mlp': if net_name == "shuttle_mlp":
net = MLP(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False) net = MLP(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'thyroid_mlp': if net_name == "thyroid_mlp":
net = MLP(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False) net = MLP(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False)
if net_name == 'arrhythmia_DGM_M2': if net_name == "arrhythmia_DGM_M2":
net = DeepGenerativeModel([274, 2, 32, [128, 64]]) net = DeepGenerativeModel([274, 2, 32, [128, 64]])
if net_name == 'cardio_DGM_M2': if net_name == "cardio_DGM_M2":
net = DeepGenerativeModel([21, 2, 8, [32, 16]]) net = DeepGenerativeModel([21, 2, 8, [32, 16]])
if net_name == 'satellite_DGM_M2': if net_name == "satellite_DGM_M2":
net = DeepGenerativeModel([36, 2, 8, [32, 16]]) net = DeepGenerativeModel([36, 2, 8, [32, 16]])
if net_name == 'satimage-2_DGM_M2': if net_name == "satimage-2_DGM_M2":
net = DeepGenerativeModel([36, 2, 8, [32, 16]]) net = DeepGenerativeModel([36, 2, 8, [32, 16]])
if net_name == 'shuttle_DGM_M2': if net_name == "shuttle_DGM_M2":
net = DeepGenerativeModel([9, 2, 8, [32, 16]]) net = DeepGenerativeModel([9, 2, 8, [32, 16]])
if net_name == 'thyroid_DGM_M2': if net_name == "thyroid_DGM_M2":
net = DeepGenerativeModel([6, 2, 4, [32, 16]]) net = DeepGenerativeModel([6, 2, 4, [32, 16]])
return net return net
@@ -89,50 +117,59 @@ def build_network(net_name, ae_net=None):
def build_autoencoder(net_name): def build_autoencoder(net_name):
"""Builds the corresponding autoencoder network.""" """Builds the corresponding autoencoder network."""
implemented_networks = ('mnist_LeNet', 'mnist_DGM_M1M2', implemented_networks = (
'fmnist_LeNet', 'fmnist_DGM_M1M2', "mnist_LeNet",
'cifar10_LeNet', 'cifar10_DGM_M1M2', "mnist_DGM_M1M2",
'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp', "fmnist_LeNet",
'thyroid_mlp') "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 assert net_name in implemented_networks
ae_net = None ae_net = None
if net_name == 'mnist_LeNet': if net_name == "mnist_LeNet":
ae_net = MNIST_LeNet_Autoencoder() ae_net = MNIST_LeNet_Autoencoder()
if net_name == 'mnist_DGM_M1M2': if net_name == "mnist_DGM_M1M2":
ae_net = VariationalAutoencoder([1*28*28, 32, [128, 64]]) ae_net = VariationalAutoencoder([1 * 28 * 28, 32, [128, 64]])
if net_name == 'fmnist_LeNet': if net_name == "fmnist_LeNet":
ae_net = FashionMNIST_LeNet_Autoencoder() ae_net = FashionMNIST_LeNet_Autoencoder()
if net_name == 'fmnist_DGM_M1M2': if net_name == "fmnist_DGM_M1M2":
ae_net = VariationalAutoencoder([1*28*28, 64, [256, 128]]) ae_net = VariationalAutoencoder([1 * 28 * 28, 64, [256, 128]])
if net_name == 'cifar10_LeNet': if net_name == "cifar10_LeNet":
ae_net = CIFAR10_LeNet_Autoencoder() ae_net = CIFAR10_LeNet_Autoencoder()
if net_name == 'cifar10_DGM_M1M2': if net_name == "cifar10_DGM_M1M2":
ae_net = VariationalAutoencoder([3*32*32, 128, [512, 256]]) ae_net = VariationalAutoencoder([3 * 32 * 32, 128, [512, 256]])
if net_name == 'arrhythmia_mlp': if net_name == "arrhythmia_mlp":
ae_net = MLP_Autoencoder(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False) ae_net = MLP_Autoencoder(x_dim=274, h_dims=[128, 64], rep_dim=32, bias=False)
if net_name == 'cardio_mlp': if net_name == "cardio_mlp":
ae_net = MLP_Autoencoder(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False) ae_net = MLP_Autoencoder(x_dim=21, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'satellite_mlp': if net_name == "satellite_mlp":
ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'satimage-2_mlp': if net_name == "satimage-2_mlp":
ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False) ae_net = MLP_Autoencoder(x_dim=36, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'shuttle_mlp': if net_name == "shuttle_mlp":
ae_net = MLP_Autoencoder(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False) ae_net = MLP_Autoencoder(x_dim=9, h_dims=[32, 16], rep_dim=8, bias=False)
if net_name == 'thyroid_mlp': if net_name == "thyroid_mlp":
ae_net = MLP_Autoencoder(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False) ae_net = MLP_Autoencoder(x_dim=6, h_dims=[32, 16], rep_dim=4, bias=False)
return ae_net return ae_net

View File

@@ -12,7 +12,10 @@ class MLP(BaseNet):
self.rep_dim = rep_dim self.rep_dim = rep_dim
neurons = [x_dim, *h_dims] neurons = [x_dim, *h_dims]
layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))] layers = [
Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias)
for i in range(1, len(neurons))
]
self.hidden = nn.ModuleList(layers) self.hidden = nn.ModuleList(layers)
self.code = nn.Linear(h_dims[-1], rep_dim, bias=bias) self.code = nn.Linear(h_dims[-1], rep_dim, bias=bias)
@@ -32,7 +35,10 @@ class MLP_Decoder(BaseNet):
self.rep_dim = rep_dim self.rep_dim = rep_dim
neurons = [rep_dim, *h_dims] neurons = [rep_dim, *h_dims]
layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))] layers = [
Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias)
for i in range(1, len(neurons))
]
self.hidden = nn.ModuleList(layers) self.hidden = nn.ModuleList(layers)
self.reconstruction = nn.Linear(h_dims[-1], x_dim, bias=bias) self.reconstruction = nn.Linear(h_dims[-1], x_dim, bias=bias)

View File

@@ -22,7 +22,9 @@ class Encoder(nn.Module):
[x_dim, h_dim, z_dim] = dims [x_dim, h_dim, z_dim] = dims
neurons = [x_dim, *h_dim] neurons = [x_dim, *h_dim]
linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))] linear_layers = [
nn.Linear(neurons[i - 1], neurons[i]) for i in range(1, len(neurons))
]
self.hidden = nn.ModuleList(linear_layers) self.hidden = nn.ModuleList(linear_layers)
self.sample = sample_layer(h_dim[-1], z_dim) self.sample = sample_layer(h_dim[-1], z_dim)
@@ -48,7 +50,9 @@ class Decoder(nn.Module):
[z_dim, h_dim, x_dim] = dims [z_dim, h_dim, x_dim] = dims
neurons = [z_dim, *h_dim] neurons = [z_dim, *h_dim]
linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))] linear_layers = [
nn.Linear(neurons[i - 1], neurons[i]) for i in range(1, len(neurons))
]
self.hidden = nn.ModuleList(linear_layers) self.hidden = nn.ModuleList(linear_layers)
self.reconstruction = nn.Linear(h_dim[-1], x_dim) self.reconstruction = nn.Linear(h_dim[-1], x_dim)

View File

@@ -13,11 +13,29 @@ import numpy as np
class DeepSADTrainer(BaseTrainer): class DeepSADTrainer(BaseTrainer):
def __init__(self, c, eta: float, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, def __init__(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): c,
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, eta: float,
n_jobs_dataloader) 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 # Deep SAD parameters
self.c = torch.tensor(c, device=self.device) if c is not None else None self.c = torch.tensor(c, device=self.device) if c is not None else None
@@ -36,39 +54,50 @@ class DeepSADTrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get train data loader # Get train data loader
train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) train_loader, _ = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device for network # Set device for network
net = net.to(self.device) net = net.to(self.device)
# Set optimizer (Adam optimizer for now) # Set optimizer (Adam optimizer for now)
optimizer = optim.Adam(net.parameters(), lr=self.lr, weight_decay=self.weight_decay) optimizer = optim.Adam(
net.parameters(), lr=self.lr, weight_decay=self.weight_decay
)
# Set learning rate scheduler # Set learning rate scheduler
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) scheduler = optim.lr_scheduler.MultiStepLR(
optimizer, milestones=self.lr_milestones, gamma=0.1
)
# Initialize hypersphere center c (if c not loaded) # Initialize hypersphere center c (if c not loaded)
if self.c is None: if self.c is None:
logger.info('Initializing center c...') logger.info("Initializing center c...")
self.c = self.init_center_c(train_loader, net) self.c = self.init_center_c(train_loader, net)
logger.info('Center c initialized.') logger.info("Center c initialized.")
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
start_time = time.time() start_time = time.time()
net.train() net.train()
for epoch in range(self.n_epochs): for epoch in range(self.n_epochs):
scheduler.step() scheduler.step()
if epoch in self.lr_milestones: if epoch in self.lr_milestones:
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) logger.info(
" LR scheduler: new learning rate is %g"
% float(scheduler.get_lr()[0])
)
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
epoch_start_time = time.time() epoch_start_time = time.time()
for data in train_loader: for data in train_loader:
inputs, _, semi_targets, _ = data inputs, _, semi_targets, _ = data
inputs, semi_targets = inputs.to(self.device), semi_targets.to(self.device) inputs, semi_targets = inputs.to(self.device), semi_targets.to(
self.device
)
# Zero the network parameter gradients # Zero the network parameter gradients
optimizer.zero_grad() optimizer.zero_grad()
@@ -76,7 +105,11 @@ class DeepSADTrainer(BaseTrainer):
# Update network parameters via backpropagation: forward + backward + optimize # Update network parameters via backpropagation: forward + backward + optimize
outputs = net(inputs) outputs = net(inputs)
dist = torch.sum((outputs - self.c) ** 2, dim=1) dist = torch.sum((outputs - self.c) ** 2, dim=1)
losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float())) losses = torch.where(
semi_targets == 0,
dist,
self.eta * ((dist + self.eps) ** semi_targets.float()),
)
loss = torch.mean(losses) loss = torch.mean(losses)
loss.backward() loss.backward()
optimizer.step() optimizer.step()
@@ -86,12 +119,14 @@ class DeepSADTrainer(BaseTrainer):
# log epoch statistics # log epoch statistics
epoch_train_time = time.time() - epoch_start_time 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 ' logger.info(
f'| Train Loss: {epoch_loss / n_batches:.6f} |') 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 self.train_time = time.time() - start_time
logger.info('Training Time: {:.3f}s'.format(self.train_time)) logger.info("Training Time: {:.3f}s".format(self.train_time))
logger.info('Finished training.') logger.info("Finished training.")
return net return net
@@ -99,13 +134,15 @@ class DeepSADTrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get test data loader # Get test data loader
_, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) _, test_loader = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device for network # Set device for network
net = net.to(self.device) net = net.to(self.device)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
start_time = time.time() start_time = time.time()
@@ -122,14 +159,22 @@ class DeepSADTrainer(BaseTrainer):
outputs = net(inputs) outputs = net(inputs)
dist = torch.sum((outputs - self.c) ** 2, dim=1) dist = torch.sum((outputs - self.c) ** 2, dim=1)
losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float())) losses = torch.where(
semi_targets == 0,
dist,
self.eta * ((dist + self.eps) ** semi_targets.float()),
)
loss = torch.mean(losses) loss = torch.mean(losses)
scores = dist scores = dist
# Save triples of (idx, label, score) in a list # Save triples of (idx, label, score) in a list
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), idx_label_score += list(
labels.cpu().data.numpy().tolist(), zip(
scores.cpu().data.numpy().tolist())) idx.cpu().data.numpy().tolist(),
labels.cpu().data.numpy().tolist(),
scores.cpu().data.numpy().tolist(),
)
)
epoch_loss += loss.item() epoch_loss += loss.item()
n_batches += 1 n_batches += 1
@@ -144,10 +189,10 @@ class DeepSADTrainer(BaseTrainer):
self.test_auc = roc_auc_score(labels, scores) self.test_auc = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
logger.info('Test Time: {:.3f}s'.format(self.test_time)) logger.info("Test Time: {:.3f}s".format(self.test_time))
logger.info('Finished testing.') logger.info("Finished testing.")
def init_center_c(self, train_loader: DataLoader, net: BaseNet, eps=0.1): 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.""" """Initialize hypersphere center c as the mean from an initial forward pass on the data."""

View File

@@ -14,11 +14,28 @@ import numpy as np
class SemiDeepGenerativeTrainer(BaseTrainer): class SemiDeepGenerativeTrainer(BaseTrainer):
def __init__(self, alpha: float = 0.1, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, def __init__(
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', self,
n_jobs_dataloader: int = 0): alpha: float = 0.1,
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, optimizer_name: str = "adam",
n_jobs_dataloader) 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 self.alpha = alpha
@@ -32,7 +49,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get train data loader # Get train data loader
train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) train_loader, _ = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device # Set device
net = net.to(self.device) net = net.to(self.device)
@@ -42,20 +61,27 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler) elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler)
# Set optimizer (Adam optimizer for now) # Set optimizer (Adam optimizer for now)
optimizer = optim.Adam(net.parameters(), lr=self.lr, weight_decay=self.weight_decay) optimizer = optim.Adam(
net.parameters(), lr=self.lr, weight_decay=self.weight_decay
)
# Set learning rate scheduler # Set learning rate scheduler
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) scheduler = optim.lr_scheduler.MultiStepLR(
optimizer, milestones=self.lr_milestones, gamma=0.1
)
# Training # Training
logger.info('Starting training...') logger.info("Starting training...")
start_time = time.time() start_time = time.time()
net.train() net.train()
for epoch in range(self.n_epochs): for epoch in range(self.n_epochs):
scheduler.step() scheduler.step()
if epoch in self.lr_milestones: if epoch in self.lr_milestones:
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) logger.info(
" LR scheduler: new learning rate is %g"
% float(scheduler.get_lr()[0])
)
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
@@ -73,7 +99,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
u = inputs[semi_targets == 0] u = inputs[semi_targets == 0]
y = labels[semi_targets != 0] y = labels[semi_targets != 0]
if y.nelement() > 1: if y.nelement() > 1:
y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier y_onehot = torch.Tensor(y.size(0), 2).to(
self.device
) # two labels: 0: normal, 1: outlier
y_onehot.zero_() y_onehot.zero_()
y_onehot.scatter_(1, y.view(-1, 1), 1) y_onehot.scatter_(1, y.view(-1, 1), 1)
@@ -94,7 +122,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
# Add auxiliary classification loss q(y|x) # Add auxiliary classification loss q(y|x)
logits = net.classify(x) logits = net.classify(x)
eps = 1e-8 eps = 1e-8
classication_loss = torch.sum(y_onehot * torch.log(logits + eps), dim=1).mean() classication_loss = torch.sum(
y_onehot * torch.log(logits + eps), dim=1
).mean()
# Overall loss # Overall loss
loss = L - self.alpha * classication_loss + U # J_alpha loss = L - self.alpha * classication_loss + U # J_alpha
@@ -107,12 +137,14 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
# log epoch statistics # log epoch statistics
epoch_train_time = time.time() - epoch_start_time 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 ' logger.info(
f'| Train Loss: {epoch_loss / n_batches:.6f} |') 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 self.train_time = time.time() - start_time
logger.info('Training Time: {:.3f}s'.format(self.train_time)) logger.info("Training Time: {:.3f}s".format(self.train_time))
logger.info('Finished training.') logger.info("Finished training.")
return net return net
@@ -120,7 +152,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get test data loader # Get test data loader
_, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) _, test_loader = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device # Set device
net = net.to(self.device) net = net.to(self.device)
@@ -130,7 +164,7 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler) elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
start_time = time.time() start_time = time.time()
@@ -147,7 +181,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
inputs = inputs.view(inputs.size(0), -1) inputs = inputs.view(inputs.size(0), -1)
u = inputs u = inputs
y = labels y = labels
y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier y_onehot = torch.Tensor(y.size(0), 2).to(
self.device
) # two labels: 0: normal, 1: outlier
y_onehot.zero_() y_onehot.zero_()
y_onehot.scatter_(1, y.view(-1, 1), 1) y_onehot.scatter_(1, y.view(-1, 1), 1)
@@ -157,17 +193,25 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
logits = net.classify(u) logits = net.classify(u)
eps = 1e-8 eps = 1e-8
classication_loss = -torch.sum(y_onehot * torch.log(logits + eps), dim=1).mean() classication_loss = -torch.sum(
y_onehot * torch.log(logits + eps), dim=1
).mean()
loss = L + self.alpha * classication_loss + U # J_alpha loss = L + self.alpha * classication_loss + U # J_alpha
# Compute scores # Compute scores
scores = logits[:, 1] # likelihood/confidence for anomalous class as anomaly score scores = logits[
:, 1
] # likelihood/confidence for anomalous class as anomaly score
# Save triple of (idx, label, score) in a list # Save triple of (idx, label, score) in a list
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), idx_label_score += list(
labels.cpu().data.numpy().tolist(), zip(
scores.cpu().data.numpy().tolist())) idx.cpu().data.numpy().tolist(),
labels.cpu().data.numpy().tolist(),
scores.cpu().data.numpy().tolist(),
)
)
epoch_loss += loss.item() epoch_loss += loss.item()
n_batches += 1 n_batches += 1
@@ -182,7 +226,7 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
self.test_auc = roc_auc_score(labels, scores) self.test_auc = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
logger.info('Test Time: {:.3f}s'.format(self.test_time)) logger.info("Test Time: {:.3f}s".format(self.test_time))
logger.info('Finished testing.') logger.info("Finished testing.")

View File

@@ -13,10 +13,27 @@ import numpy as np
class AETrainer(BaseTrainer): class AETrainer(BaseTrainer):
def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (), def __init__(
batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0): self,
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, optimizer_name: str = "adam",
n_jobs_dataloader) 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 # Results
self.train_time = None self.train_time = None
@@ -27,30 +44,39 @@ class AETrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get train data loader # Get train data loader
train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) train_loader, _ = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set loss # Set loss
criterion = nn.MSELoss(reduction='none') criterion = nn.MSELoss(reduction="none")
# Set device # Set device
ae_net = ae_net.to(self.device) ae_net = ae_net.to(self.device)
criterion = criterion.to(self.device) criterion = criterion.to(self.device)
# Set optimizer (Adam optimizer for now) # Set optimizer (Adam optimizer for now)
optimizer = optim.Adam(ae_net.parameters(), lr=self.lr, weight_decay=self.weight_decay) optimizer = optim.Adam(
ae_net.parameters(), lr=self.lr, weight_decay=self.weight_decay
)
# Set learning rate scheduler # Set learning rate scheduler
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) scheduler = optim.lr_scheduler.MultiStepLR(
optimizer, milestones=self.lr_milestones, gamma=0.1
)
# Training # Training
logger.info('Starting pretraining...') logger.info("Starting pretraining...")
start_time = time.time() start_time = time.time()
ae_net.train() ae_net.train()
for epoch in range(self.n_epochs): for epoch in range(self.n_epochs):
scheduler.step() scheduler.step()
if epoch in self.lr_milestones: if epoch in self.lr_milestones:
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) logger.info(
" LR scheduler: new learning rate is %g"
% float(scheduler.get_lr()[0])
)
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
@@ -74,12 +100,14 @@ class AETrainer(BaseTrainer):
# log epoch statistics # log epoch statistics
epoch_train_time = time.time() - epoch_start_time 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 ' logger.info(
f'| Train Loss: {epoch_loss / n_batches:.6f} |') 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 self.train_time = time.time() - start_time
logger.info('Pretraining Time: {:.3f}s'.format(self.train_time)) logger.info("Pretraining Time: {:.3f}s".format(self.train_time))
logger.info('Finished pretraining.') logger.info("Finished pretraining.")
return ae_net return ae_net
@@ -87,17 +115,19 @@ class AETrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get test data loader # Get test data loader
_, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) _, test_loader = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set loss # Set loss
criterion = nn.MSELoss(reduction='none') criterion = nn.MSELoss(reduction="none")
# Set device for network # Set device for network
ae_net = ae_net.to(self.device) ae_net = ae_net.to(self.device)
criterion = criterion.to(self.device) criterion = criterion.to(self.device)
# Testing # Testing
logger.info('Testing autoencoder...') logger.info("Testing autoencoder...")
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
start_time = time.time() start_time = time.time()
@@ -106,16 +136,24 @@ class AETrainer(BaseTrainer):
with torch.no_grad(): with torch.no_grad():
for data in test_loader: for data in test_loader:
inputs, labels, _, idx = data inputs, labels, _, idx = data
inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device) inputs, labels, idx = (
inputs.to(self.device),
labels.to(self.device),
idx.to(self.device),
)
rec = ae_net(inputs) rec = ae_net(inputs)
rec_loss = criterion(rec, inputs) rec_loss = criterion(rec, inputs)
scores = torch.mean(rec_loss, dim=tuple(range(1, rec.dim()))) scores = torch.mean(rec_loss, dim=tuple(range(1, rec.dim())))
# Save triple of (idx, label, score) in a list # Save triple of (idx, label, score) in a list
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), idx_label_score += list(
labels.cpu().data.numpy().tolist(), zip(
scores.cpu().data.numpy().tolist())) idx.cpu().data.numpy().tolist(),
labels.cpu().data.numpy().tolist(),
scores.cpu().data.numpy().tolist(),
)
)
loss = torch.mean(rec_loss) loss = torch.mean(rec_loss)
epoch_loss += loss.item() epoch_loss += loss.item()
@@ -130,7 +168,7 @@ class AETrainer(BaseTrainer):
self.test_auc = roc_auc_score(labels, scores) self.test_auc = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
logger.info('Test Time: {:.3f}s'.format(self.test_time)) logger.info("Test Time: {:.3f}s".format(self.test_time))
logger.info('Finished testing autoencoder.') logger.info("Finished testing autoencoder.")

View File

@@ -13,10 +13,27 @@ import numpy as np
class VAETrainer(BaseTrainer): class VAETrainer(BaseTrainer):
def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (), def __init__(
batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0): self,
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device, optimizer_name: str = "adam",
n_jobs_dataloader) 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 # Results
self.train_time = None self.train_time = None
@@ -27,26 +44,35 @@ class VAETrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get train data loader # Get train data loader
train_loader, _ = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) train_loader, _ = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device # Set device
vae = vae.to(self.device) vae = vae.to(self.device)
# Set optimizer (Adam optimizer for now) # Set optimizer (Adam optimizer for now)
optimizer = optim.Adam(vae.parameters(), lr=self.lr, weight_decay=self.weight_decay) optimizer = optim.Adam(
vae.parameters(), lr=self.lr, weight_decay=self.weight_decay
)
# Set learning rate scheduler # Set learning rate scheduler
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1) scheduler = optim.lr_scheduler.MultiStepLR(
optimizer, milestones=self.lr_milestones, gamma=0.1
)
# Training # Training
logger.info('Starting pretraining...') logger.info("Starting pretraining...")
start_time = time.time() start_time = time.time()
vae.train() vae.train()
for epoch in range(self.n_epochs): for epoch in range(self.n_epochs):
scheduler.step() scheduler.step()
if epoch in self.lr_milestones: if epoch in self.lr_milestones:
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0])) logger.info(
" LR scheduler: new learning rate is %g"
% float(scheduler.get_lr()[0])
)
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
@@ -76,12 +102,14 @@ class VAETrainer(BaseTrainer):
# log epoch statistics # log epoch statistics
epoch_train_time = time.time() - epoch_start_time 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 ' logger.info(
f'| Train Loss: {epoch_loss / n_batches:.6f} |') 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 self.train_time = time.time() - start_time
logger.info('Pretraining Time: {:.3f}s'.format(self.train_time)) logger.info("Pretraining Time: {:.3f}s".format(self.train_time))
logger.info('Finished pretraining.') logger.info("Finished pretraining.")
return vae return vae
@@ -89,13 +117,15 @@ class VAETrainer(BaseTrainer):
logger = logging.getLogger() logger = logging.getLogger()
# Get test data loader # Get test data loader
_, test_loader = dataset.loaders(batch_size=self.batch_size, num_workers=self.n_jobs_dataloader) _, test_loader = dataset.loaders(
batch_size=self.batch_size, num_workers=self.n_jobs_dataloader
)
# Set device # Set device
vae = vae.to(self.device) vae = vae.to(self.device)
# Testing # Testing
logger.info('Starting testing...') logger.info("Starting testing...")
epoch_loss = 0.0 epoch_loss = 0.0
n_batches = 0 n_batches = 0
start_time = time.time() start_time = time.time()
@@ -104,7 +134,11 @@ class VAETrainer(BaseTrainer):
with torch.no_grad(): with torch.no_grad():
for data in test_loader: for data in test_loader:
inputs, labels, _, idx = data inputs, labels, _, idx = data
inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device) inputs, labels, idx = (
inputs.to(self.device),
labels.to(self.device),
idx.to(self.device),
)
inputs = inputs.view(inputs.size(0), -1) inputs = inputs.view(inputs.size(0), -1)
@@ -113,9 +147,13 @@ class VAETrainer(BaseTrainer):
scores = -likelihood # negative likelihood as anomaly score scores = -likelihood # negative likelihood as anomaly score
# Save triple of (idx, label, score) in a list # Save triple of (idx, label, score) in a list
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(), idx_label_score += list(
labels.cpu().data.numpy().tolist(), zip(
scores.cpu().data.numpy().tolist())) idx.cpu().data.numpy().tolist(),
labels.cpu().data.numpy().tolist(),
scores.cpu().data.numpy().tolist(),
)
)
# Overall loss # Overall loss
elbo = likelihood - vae.kl_divergence elbo = likelihood - vae.kl_divergence
@@ -133,7 +171,7 @@ class VAETrainer(BaseTrainer):
self.test_auc = roc_auc_score(labels, scores) self.test_auc = roc_auc_score(labels, scores)
# Log results # Log results
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches)) logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc)) logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
logger.info('Test Time: {:.3f}s'.format(self.test_time)) logger.info("Test Time: {:.3f}s".format(self.test_time))
logger.info('Finished testing variational autoencoder.') logger.info("Finished testing variational autoencoder.")

View File

@@ -41,7 +41,13 @@ class SVI(nn.Module):
base_sampler = ImportanceWeightedSampler(mc=1, iw=1) base_sampler = ImportanceWeightedSampler(mc=1, iw=1)
def __init__(self, model, likelihood=F.binary_cross_entropy, beta=repeat(1), sampler=base_sampler): def __init__(
self,
model,
likelihood=F.binary_cross_entropy,
beta=repeat(1),
sampler=base_sampler,
):
super(SVI, self).__init__() super(SVI, self).__init__()
self.model = model self.model = model
self.likelihood = likelihood self.likelihood = likelihood

View File

@@ -10,7 +10,7 @@ class Config(object):
def load_config(self, import_json): def load_config(self, import_json):
"""Load settings dict from import_json (path/filename.json) JSON-file.""" """Load settings dict from import_json (path/filename.json) JSON-file."""
with open(import_json, 'r') as fp: with open(import_json, "r") as fp:
settings = json.load(fp) settings = json.load(fp)
for key, value in settings.items(): for key, value in settings.items():
@@ -19,5 +19,5 @@ class Config(object):
def save_config(self, export_json): def save_config(self, export_json):
"""Save settings dict to export_json (path/filename.json) JSON-file.""" """Save settings dict to export_json (path/filename.json) JSON-file."""
with open(export_json, 'w') as fp: with open(export_json, "w") as fp:
json.dump(self.settings, fp) json.dump(self.settings, fp)

View File

@@ -38,7 +38,9 @@ def log_sum_exp(tensor, dim=-1, sum_op=torch.sum):
:return: LSE :return: LSE
""" """
max, _ = torch.max(tensor, dim=dim, keepdim=True) max, _ = torch.max(tensor, dim=dim, keepdim=True)
return torch.log(sum_op(torch.exp(tensor - max), dim=dim, keepdim=True) + 1e-8) + max return (
torch.log(sum_op(torch.exp(tensor - max), dim=dim, keepdim=True) + 1e-8) + max
)
def binary_cross_entropy(x, y): def binary_cross_entropy(x, y):

View File

@@ -1,26 +1,37 @@
import torch import torch
import matplotlib import matplotlib
matplotlib.use('Agg') # or 'PS', 'PDF', 'SVG'
matplotlib.use("Agg") # or 'PS', 'PDF', 'SVG'
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from torchvision.utils import make_grid 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): 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.""" """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) grid = make_grid(
x, nrow=nrow, padding=padding, normalize=normalize, pad_value=pad_value
)
npgrid = grid.cpu().numpy() npgrid = grid.cpu().numpy()
plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation='nearest') plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation="nearest")
ax = plt.gca() ax = plt.gca()
ax.xaxis.set_visible(False) ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False) ax.yaxis.set_visible(False)
if not (title == ''): if not (title == ""):
plt.title(title) plt.title(title)
plt.savefig(export_img, bbox_inches='tight', pad_inches=0.1) plt.savefig(export_img, bbox_inches="tight", pad_inches=0.1)
plt.clf() plt.clf()