black formatted files before changes
This commit is contained in:
@@ -41,53 +41,80 @@ class DeepSAD(object):
|
||||
self.ae_optimizer_name = None
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_auc': None,
|
||||
'test_time': None,
|
||||
'test_scores': None,
|
||||
"train_time": None,
|
||||
"test_auc": None,
|
||||
"test_time": None,
|
||||
"test_scores": None,
|
||||
}
|
||||
|
||||
self.ae_results = {
|
||||
'train_time': None,
|
||||
'test_auc': None,
|
||||
'test_time': None
|
||||
}
|
||||
self.ae_results = {"train_time": None, "test_auc": None, "test_time": None}
|
||||
|
||||
def set_network(self, net_name):
|
||||
"""Builds the neural network phi."""
|
||||
self.net_name = 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,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
def train(
|
||||
self,
|
||||
dataset: BaseADDataset,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 50,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
"""Trains the Deep SAD model on the training data."""
|
||||
|
||||
self.optimizer_name = optimizer_name
|
||||
self.trainer = DeepSADTrainer(self.c, 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)
|
||||
self.trainer = DeepSADTrainer(
|
||||
self.c,
|
||||
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
|
||||
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
|
||||
|
||||
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."""
|
||||
|
||||
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)
|
||||
|
||||
# Get results
|
||||
self.results['test_auc'] = self.trainer.test_auc
|
||||
self.results['test_time'] = self.trainer.test_time
|
||||
self.results['test_scores'] = self.trainer.test_scores
|
||||
self.results["test_auc"] = self.trainer.test_auc
|
||||
self.results["test_time"] = self.trainer.test_time
|
||||
self.results["test_scores"] = self.trainer.test_scores
|
||||
|
||||
def pretrain(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 100,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
def pretrain(
|
||||
self,
|
||||
dataset: BaseADDataset,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 100,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
"""Pretrains the weights for the Deep SAD network phi via autoencoder."""
|
||||
|
||||
# Set autoencoder network
|
||||
@@ -95,20 +122,27 @@ class DeepSAD(object):
|
||||
|
||||
# Train
|
||||
self.ae_optimizer_name = optimizer_name
|
||||
self.ae_trainer = AETrainer(optimizer_name, lr=lr, n_epochs=n_epochs, lr_milestones=lr_milestones,
|
||||
batch_size=batch_size, weight_decay=weight_decay, device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
self.ae_trainer = AETrainer(
|
||||
optimizer_name,
|
||||
lr=lr,
|
||||
n_epochs=n_epochs,
|
||||
lr_milestones=lr_milestones,
|
||||
batch_size=batch_size,
|
||||
weight_decay=weight_decay,
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
self.ae_net = self.ae_trainer.train(dataset, self.ae_net)
|
||||
|
||||
# Get train results
|
||||
self.ae_results['train_time'] = self.ae_trainer.train_time
|
||||
self.ae_results["train_time"] = self.ae_trainer.train_time
|
||||
|
||||
# Test
|
||||
self.ae_trainer.test(dataset, self.ae_net)
|
||||
|
||||
# Get test results
|
||||
self.ae_results['test_auc'] = self.ae_trainer.test_auc
|
||||
self.ae_results['test_time'] = self.ae_trainer.test_time
|
||||
self.ae_results["test_auc"] = self.ae_trainer.test_auc
|
||||
self.ae_results["test_time"] = self.ae_trainer.test_time
|
||||
|
||||
# Initialize Deep SAD network weights from pre-trained encoder
|
||||
self.init_network_weights_from_pretraining()
|
||||
@@ -132,30 +166,31 @@ class DeepSAD(object):
|
||||
net_dict = self.net.state_dict()
|
||||
ae_net_dict = self.ae_net.state_dict() if save_ae else None
|
||||
|
||||
torch.save({'c': self.c,
|
||||
'net_dict': net_dict,
|
||||
'ae_net_dict': ae_net_dict}, export_model)
|
||||
torch.save(
|
||||
{"c": self.c, "net_dict": net_dict, "ae_net_dict": ae_net_dict},
|
||||
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."""
|
||||
|
||||
model_dict = torch.load(model_path, map_location=map_location)
|
||||
|
||||
self.c = model_dict['c']
|
||||
self.net.load_state_dict(model_dict['net_dict'])
|
||||
self.c = model_dict["c"]
|
||||
self.net.load_state_dict(model_dict["net_dict"])
|
||||
|
||||
# load autoencoder parameters if specified
|
||||
if load_ae:
|
||||
if self.ae_net is None:
|
||||
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):
|
||||
"""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)
|
||||
|
||||
def save_ae_results(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -10,15 +10,24 @@ class BaseADDataset(ABC):
|
||||
self.root = root # root path to data
|
||||
|
||||
self.n_classes = 2 # 0: normal, 1: outlier
|
||||
self.normal_classes = 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.normal_classes = (
|
||||
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.test_set = None # must be of type torch.utils.data.Dataset
|
||||
|
||||
@abstractmethod
|
||||
def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> (
|
||||
DataLoader, DataLoader):
|
||||
def loaders(
|
||||
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."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -22,5 +22,5 @@ class BaseNet(nn.Module):
|
||||
"""Network summary."""
|
||||
net_parameters = filter(lambda p: p.requires_grad, self.parameters())
|
||||
params = sum([np.prod(p.size()) for p in net_parameters])
|
||||
self.logger.info('Trainable parameters: {}'.format(params))
|
||||
self.logger.info("Trainable parameters: {}".format(params))
|
||||
self.logger.info(self)
|
||||
|
||||
@@ -6,8 +6,17 @@ from .base_net import BaseNet
|
||||
class BaseTrainer(ABC):
|
||||
"""Trainer base class."""
|
||||
|
||||
def __init__(self, optimizer_name: str, lr: float, n_epochs: int, lr_milestones: tuple, batch_size: int,
|
||||
weight_decay: float, device: str, n_jobs_dataloader: int):
|
||||
def __init__(
|
||||
self,
|
||||
optimizer_name: str,
|
||||
lr: float,
|
||||
n_epochs: int,
|
||||
lr_milestones: tuple,
|
||||
batch_size: int,
|
||||
weight_decay: float,
|
||||
device: str,
|
||||
n_jobs_dataloader: int,
|
||||
):
|
||||
super().__init__()
|
||||
self.optimizer_name = optimizer_name
|
||||
self.lr = lr
|
||||
|
||||
@@ -19,15 +19,22 @@ class ODDSDataset(Dataset):
|
||||
"""
|
||||
|
||||
urls = {
|
||||
'arrhythmia': 'https://www.dropbox.com/s/lmlwuspn1sey48r/arrhythmia.mat?dl=1',
|
||||
'cardio': 'https://www.dropbox.com/s/galg3ihvxklf0qi/cardio.mat?dl=1',
|
||||
'satellite': 'https://www.dropbox.com/s/dpzxp8jyr9h93k5/satellite.mat?dl=1',
|
||||
'satimage-2': 'https://www.dropbox.com/s/hckgvu9m6fs441p/satimage-2.mat?dl=1',
|
||||
'shuttle': 'https://www.dropbox.com/s/mk8ozgisimfn3dw/shuttle.mat?dl=1',
|
||||
'thyroid': 'https://www.dropbox.com/s/bih0e15a0fukftb/thyroid.mat?dl=1'
|
||||
"arrhythmia": "https://www.dropbox.com/s/lmlwuspn1sey48r/arrhythmia.mat?dl=1",
|
||||
"cardio": "https://www.dropbox.com/s/galg3ihvxklf0qi/cardio.mat?dl=1",
|
||||
"satellite": "https://www.dropbox.com/s/dpzxp8jyr9h93k5/satellite.mat?dl=1",
|
||||
"satimage-2": "https://www.dropbox.com/s/hckgvu9m6fs441p/satimage-2.mat?dl=1",
|
||||
"shuttle": "https://www.dropbox.com/s/mk8ozgisimfn3dw/shuttle.mat?dl=1",
|
||||
"thyroid": "https://www.dropbox.com/s/bih0e15a0fukftb/thyroid.mat?dl=1",
|
||||
}
|
||||
|
||||
def __init__(self, root: str, dataset_name: str, train=True, random_state=None, download=False):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
dataset_name: str,
|
||||
train=True,
|
||||
random_state=None,
|
||||
download=False,
|
||||
):
|
||||
super(Dataset, self).__init__()
|
||||
|
||||
self.classes = [0, 1]
|
||||
@@ -37,25 +44,25 @@ class ODDSDataset(Dataset):
|
||||
self.root = Path(root)
|
||||
self.dataset_name = dataset_name
|
||||
self.train = train # training set or test set
|
||||
self.file_name = self.dataset_name + '.mat'
|
||||
self.file_name = self.dataset_name + ".mat"
|
||||
self.data_file = self.root / self.file_name
|
||||
|
||||
if download:
|
||||
self.download()
|
||||
|
||||
mat = loadmat(self.data_file)
|
||||
X = mat['X']
|
||||
y = mat['y'].ravel()
|
||||
X = mat["X"]
|
||||
y = mat["y"].ravel()
|
||||
idx_norm = y == 0
|
||||
idx_out = y == 1
|
||||
|
||||
# 60% data for training and 40% for testing; keep outlier ratio
|
||||
X_train_norm, X_test_norm, y_train_norm, y_test_norm = train_test_split(X[idx_norm], y[idx_norm],
|
||||
test_size=0.4,
|
||||
random_state=random_state)
|
||||
X_train_out, X_test_out, y_train_out, y_test_out = train_test_split(X[idx_out], y[idx_out],
|
||||
test_size=0.4,
|
||||
random_state=random_state)
|
||||
X_train_norm, X_test_norm, y_train_norm, y_test_norm = train_test_split(
|
||||
X[idx_norm], y[idx_norm], test_size=0.4, random_state=random_state
|
||||
)
|
||||
X_train_out, X_test_out, y_train_out, y_test_out = train_test_split(
|
||||
X[idx_out], y[idx_out], test_size=0.4, random_state=random_state
|
||||
)
|
||||
X_train = np.concatenate((X_train_norm, X_train_out))
|
||||
X_test = np.concatenate((X_test_norm, X_test_out))
|
||||
y_train = np.concatenate((y_train_norm, y_train_out))
|
||||
@@ -88,7 +95,11 @@ class ODDSDataset(Dataset):
|
||||
Returns:
|
||||
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
|
||||
|
||||
@@ -107,4 +118,4 @@ class ODDSDataset(Dataset):
|
||||
# download file
|
||||
download_url(self.urls[self.dataset_name], self.root, self.file_name)
|
||||
|
||||
print('Done!')
|
||||
print("Done!")
|
||||
|
||||
@@ -8,10 +8,25 @@ class TorchvisionDataset(BaseADDataset):
|
||||
def __init__(self, root: str):
|
||||
super().__init__(root)
|
||||
|
||||
def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> (
|
||||
DataLoader, DataLoader):
|
||||
train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train,
|
||||
num_workers=num_workers, drop_last=True)
|
||||
test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test,
|
||||
num_workers=num_workers, drop_last=False)
|
||||
def loaders(
|
||||
self,
|
||||
batch_size: int,
|
||||
shuffle_train=True,
|
||||
shuffle_test=False,
|
||||
num_workers: int = 0,
|
||||
) -> (DataLoader, DataLoader):
|
||||
train_loader = DataLoader(
|
||||
dataset=self.train_set,
|
||||
batch_size=batch_size,
|
||||
shuffle=shuffle_train,
|
||||
num_workers=num_workers,
|
||||
drop_last=True,
|
||||
)
|
||||
test_loader = DataLoader(
|
||||
dataset=self.test_set,
|
||||
batch_size=batch_size,
|
||||
shuffle=shuffle_test,
|
||||
num_workers=num_workers,
|
||||
drop_last=False,
|
||||
)
|
||||
return train_loader, test_loader
|
||||
|
||||
@@ -14,64 +14,215 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('net_name', type=click.Choice(['mnist_DGM_M2', 'mnist_DGM_M1M2', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2',
|
||||
'cifar10_DGM_M2', 'cifar10_DGM_M1M2',
|
||||
'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2',
|
||||
'satimage-2_DGM_M2', 'shuttle_DGM_M2', 'thyroid_DGM_M2']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam',
|
||||
help='Name of the optimizer to use for training the Semi-Supervised Deep Generative model.')
|
||||
@click.option('--lr', type=float, default=0.001,
|
||||
help='Initial learning rate for training. Default=0.001')
|
||||
@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.')
|
||||
@click.option('--lr_milestone', type=int, default=0, multiple=True,
|
||||
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.')
|
||||
@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.')
|
||||
@click.option('--weight_decay', type=float, default=1e-6,
|
||||
help='Weight decay (L2 penalty) hyperparameter.')
|
||||
@click.option('--pretrain', type=bool, default=False, help='Pretrain a variational autoencoder.')
|
||||
@click.option('--vae_optimizer_name', type=click.Choice(['adam']), default='adam',
|
||||
help='Name of the optimizer to use for variational autoencoder pretraining.')
|
||||
@click.option('--vae_lr', type=float, default=0.001,
|
||||
help='Initial learning rate for pretraining. Default=0.001')
|
||||
@click.option('--vae_n_epochs', type=int, default=100, help='Number of epochs to train the variational autoencoder.')
|
||||
@click.option('--vae_lr_milestone', type=int, default=0, multiple=True,
|
||||
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.')
|
||||
@click.option('--vae_batch_size', type=int, default=128, help='Batch size for variational autoencoder training.')
|
||||
@click.option('--vae_weight_decay', type=float, default=1e-6,
|
||||
help='Weight decay (L2 penalty) hyperparameter for variational autoencoder.')
|
||||
@click.option('--num_threads', type=int, default=0,
|
||||
help='Number of threads used for parallelizing CPU operations. 0 means that all resources are used.')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier,
|
||||
ratio_pollution, device, seed, optimizer_name, lr, n_epochs, lr_milestone, batch_size, weight_decay, pretrain,
|
||||
vae_optimizer_name, vae_lr, vae_n_epochs, vae_lr_milestone, vae_batch_size, vae_weight_decay,
|
||||
num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument(
|
||||
"net_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist_DGM_M2",
|
||||
"mnist_DGM_M1M2",
|
||||
"fmnist_DGM_M2",
|
||||
"fmnist_DGM_M1M2",
|
||||
"cifar10_DGM_M2",
|
||||
"cifar10_DGM_M1M2",
|
||||
"arrhythmia_DGM_M2",
|
||||
"cardio_DGM_M2",
|
||||
"satellite_DGM_M2",
|
||||
"satimage-2_DGM_M2",
|
||||
"shuttle_DGM_M2",
|
||||
"thyroid_DGM_M2",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--device",
|
||||
type=str,
|
||||
default="cuda",
|
||||
help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).',
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--optimizer_name",
|
||||
type=click.Choice(["adam"]),
|
||||
default="adam",
|
||||
help="Name of the optimizer to use for training the Semi-Supervised Deep Generative model.",
|
||||
)
|
||||
@click.option(
|
||||
"--lr",
|
||||
type=float,
|
||||
default=0.001,
|
||||
help="Initial learning rate for training. Default=0.001",
|
||||
)
|
||||
@click.option("--n_epochs", type=int, default=50, help="Number of epochs to train.")
|
||||
@click.option(
|
||||
"--lr_milestone",
|
||||
type=int,
|
||||
default=0,
|
||||
multiple=True,
|
||||
help="Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.",
|
||||
)
|
||||
@click.option(
|
||||
"--batch_size", type=int, default=128, help="Batch size for mini-batch training."
|
||||
)
|
||||
@click.option(
|
||||
"--weight_decay",
|
||||
type=float,
|
||||
default=1e-6,
|
||||
help="Weight decay (L2 penalty) hyperparameter.",
|
||||
)
|
||||
@click.option(
|
||||
"--pretrain", type=bool, default=False, help="Pretrain a variational autoencoder."
|
||||
)
|
||||
@click.option(
|
||||
"--vae_optimizer_name",
|
||||
type=click.Choice(["adam"]),
|
||||
default="adam",
|
||||
help="Name of the optimizer to use for variational autoencoder pretraining.",
|
||||
)
|
||||
@click.option(
|
||||
"--vae_lr",
|
||||
type=float,
|
||||
default=0.001,
|
||||
help="Initial learning rate for pretraining. Default=0.001",
|
||||
)
|
||||
@click.option(
|
||||
"--vae_n_epochs",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Number of epochs to train the variational autoencoder.",
|
||||
)
|
||||
@click.option(
|
||||
"--vae_lr_milestone",
|
||||
type=int,
|
||||
default=0,
|
||||
multiple=True,
|
||||
help="Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.",
|
||||
)
|
||||
@click.option(
|
||||
"--vae_batch_size",
|
||||
type=int,
|
||||
default=128,
|
||||
help="Batch size for variational autoencoder training.",
|
||||
)
|
||||
@click.option(
|
||||
"--vae_weight_decay",
|
||||
type=float,
|
||||
default=1e-6,
|
||||
help="Weight decay (L2 penalty) hyperparameter for variational autoencoder.",
|
||||
)
|
||||
@click.option(
|
||||
"--num_threads",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of threads used for parallelizing CPU operations. 0 means that all resources are used.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
net_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
device,
|
||||
seed,
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestone,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
pretrain,
|
||||
vae_optimizer_name,
|
||||
vae_lr,
|
||||
vae_n_epochs,
|
||||
vae_lr_milestone,
|
||||
vae_batch_size,
|
||||
vae_weight_decay,
|
||||
num_threads,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
Semi-Supervised Deep Generative model (M1+M2 model) from Kingma et al. (2014)
|
||||
|
||||
@@ -88,64 +239,78 @@ def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, ra
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s' % log_file)
|
||||
logger.info('Data path is %s' % data_path)
|
||||
logger.info('Export path is %s' % xp_path)
|
||||
logger.info("Log file is %s" % log_file)
|
||||
logger.info("Data path is %s" % data_path)
|
||||
logger.info("Export path is %s" % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
else:
|
||||
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes)
|
||||
logger.info('Network: %s' % net_name)
|
||||
logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
|
||||
logger.info("Network: %s" % net_name)
|
||||
|
||||
# If specified, load experiment config from JSON-file
|
||||
if load_config:
|
||||
cfg.load_config(import_json=load_config)
|
||||
logger.info('Loaded configuration from %s.' % load_config)
|
||||
logger.info("Loaded configuration from %s." % load_config)
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Default device to 'cpu' if cuda is not available
|
||||
if not torch.cuda.is_available():
|
||||
device = 'cpu'
|
||||
device = "cpu"
|
||||
# Set the number of threads used for parallelizing CPU operations
|
||||
if num_threads > 0:
|
||||
torch.set_num_threads(num_threads)
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of threads: %d' % num_threads)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of threads: %d" % num_threads)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize semiDGM model and set neural network phi
|
||||
alpha = 0.1 * (1 - ratio_known_normal - ratio_known_outlier) / (ratio_known_normal + ratio_known_outlier)
|
||||
alpha = (
|
||||
0.1
|
||||
* (1 - ratio_known_normal - ratio_known_outlier)
|
||||
/ (ratio_known_normal + ratio_known_outlier)
|
||||
)
|
||||
semiDGM = SemiDeepGenerativeModel(alpha=alpha)
|
||||
|
||||
# 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)
|
||||
# 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:
|
||||
# Log pretraining details
|
||||
logger.info('Pretraining optimizer: %s' % cfg.settings['vae_optimizer_name'])
|
||||
logger.info('Pretraining learning rate: %g' % cfg.settings['vae_lr'])
|
||||
logger.info('Pretraining epochs: %d' % cfg.settings['vae_n_epochs'])
|
||||
logger.info('Pretraining learning rate scheduler milestones: %s' % (cfg.settings['vae_lr_milestone'],))
|
||||
logger.info('Pretraining batch size: %d' % cfg.settings['vae_batch_size'])
|
||||
logger.info('Pretraining weight decay: %g' % cfg.settings['vae_weight_decay'])
|
||||
logger.info("Pretraining optimizer: %s" % cfg.settings["vae_optimizer_name"])
|
||||
logger.info("Pretraining learning rate: %g" % cfg.settings["vae_lr"])
|
||||
logger.info("Pretraining epochs: %d" % cfg.settings["vae_n_epochs"])
|
||||
logger.info(
|
||||
"Pretraining learning rate scheduler milestones: %s"
|
||||
% (cfg.settings["vae_lr_milestone"],)
|
||||
)
|
||||
logger.info("Pretraining batch size: %d" % cfg.settings["vae_batch_size"])
|
||||
logger.info("Pretraining weight decay: %g" % cfg.settings["vae_weight_decay"])
|
||||
|
||||
# Pretrain model on dataset (via variational autoencoder)
|
||||
semiDGM.set_vae(net_name)
|
||||
semiDGM.pretrain(dataset,
|
||||
optimizer_name=cfg.settings['vae_optimizer_name'],
|
||||
lr=cfg.settings['vae_lr'],
|
||||
n_epochs=cfg.settings['vae_n_epochs'],
|
||||
lr_milestones=cfg.settings['vae_lr_milestone'],
|
||||
batch_size=cfg.settings['vae_batch_size'],
|
||||
weight_decay=cfg.settings['vae_weight_decay'],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
semiDGM.pretrain(
|
||||
dataset,
|
||||
optimizer_name=cfg.settings["vae_optimizer_name"],
|
||||
lr=cfg.settings["vae_lr"],
|
||||
n_epochs=cfg.settings["vae_n_epochs"],
|
||||
lr_milestones=cfg.settings["vae_lr_milestone"],
|
||||
batch_size=cfg.settings["vae_batch_size"],
|
||||
weight_decay=cfg.settings["vae_weight_decay"],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Save pretraining results
|
||||
semiDGM.save_vae_results(export_json=xp_path + '/vae_results.json')
|
||||
semiDGM.save_vae_results(export_json=xp_path + "/vae_results.json")
|
||||
|
||||
# Log training details
|
||||
logger.info('Training optimizer: %s' % cfg.settings['optimizer_name'])
|
||||
logger.info('Training learning rate: %g' % cfg.settings['lr'])
|
||||
logger.info('Training epochs: %d' % cfg.settings['n_epochs'])
|
||||
logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],))
|
||||
logger.info('Training batch size: %d' % cfg.settings['batch_size'])
|
||||
logger.info('Training weight decay: %g' % cfg.settings['weight_decay'])
|
||||
logger.info("Training optimizer: %s" % cfg.settings["optimizer_name"])
|
||||
logger.info("Training learning rate: %g" % cfg.settings["lr"])
|
||||
logger.info("Training epochs: %d" % cfg.settings["n_epochs"])
|
||||
logger.info(
|
||||
"Training learning rate scheduler milestones: %s"
|
||||
% (cfg.settings["lr_milestone"],)
|
||||
)
|
||||
logger.info("Training batch size: %d" % cfg.settings["batch_size"])
|
||||
logger.info("Training weight decay: %g" % cfg.settings["weight_decay"])
|
||||
|
||||
# Train model on dataset
|
||||
semiDGM.set_network(net_name)
|
||||
semiDGM.train(dataset,
|
||||
optimizer_name=cfg.settings['optimizer_name'],
|
||||
lr=cfg.settings['lr'],
|
||||
n_epochs=cfg.settings['n_epochs'],
|
||||
lr_milestones=cfg.settings['lr_milestone'],
|
||||
batch_size=cfg.settings['batch_size'],
|
||||
weight_decay=cfg.settings['weight_decay'],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
semiDGM.train(
|
||||
dataset,
|
||||
optimizer_name=cfg.settings["optimizer_name"],
|
||||
lr=cfg.settings["lr"],
|
||||
n_epochs=cfg.settings["n_epochs"],
|
||||
lr_milestones=cfg.settings["lr_milestone"],
|
||||
batch_size=cfg.settings["batch_size"],
|
||||
weight_decay=cfg.settings["weight_decay"],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Test model
|
||||
semiDGM.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
|
||||
# Save results, model, and configuration
|
||||
semiDGM.save_results(export_json=xp_path + '/results.json')
|
||||
semiDGM.save_model(export_model=xp_path + '/model.tar')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
semiDGM.save_results(export_json=xp_path + "/results.json")
|
||||
semiDGM.save_model(export_model=xp_path + "/model.tar")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*semiDGM.results['test_scores'])
|
||||
indices, labels, scores = zip(*semiDGM.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0,3,1,2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2)))
|
||||
X_normal_high = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0,3,1,2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -14,46 +14,138 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--n_estimators', type=int, default=100,
|
||||
help='Set the number of base estimators in the ensemble (default: 100).')
|
||||
@click.option('--max_samples', type=int, default=256,
|
||||
help='Set the number of samples drawn to train each base estimator (default: 256).')
|
||||
@click.option('--contamination', type=float, default=0.1,
|
||||
help='Expected fraction of anomalies in the training set. (default: 0.1).')
|
||||
@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.')
|
||||
@click.option('--hybrid', type=bool, default=False,
|
||||
help='Train model on features extracted from an autoencoder. If True, load_ae must be specified.')
|
||||
@click.option('--load_ae', type=click.Path(exists=True), default=None,
|
||||
help='Model file path to load autoencoder weights (default: None).')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier,
|
||||
ratio_pollution, seed, n_estimators, max_samples, contamination, n_jobs_model, hybrid, load_ae,
|
||||
n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--n_estimators",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Set the number of base estimators in the ensemble (default: 100).",
|
||||
)
|
||||
@click.option(
|
||||
"--max_samples",
|
||||
type=int,
|
||||
default=256,
|
||||
help="Set the number of samples drawn to train each base estimator (default: 256).",
|
||||
)
|
||||
@click.option(
|
||||
"--contamination",
|
||||
type=float,
|
||||
default=0.1,
|
||||
help="Expected fraction of anomalies in the training set. (default: 0.1).",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_model", type=int, default=-1, help="Number of jobs for model training."
|
||||
)
|
||||
@click.option(
|
||||
"--hybrid",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Train model on features extracted from an autoencoder. If True, load_ae must be specified.",
|
||||
)
|
||||
@click.option(
|
||||
"--load_ae",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path to load autoencoder weights (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
seed,
|
||||
n_estimators,
|
||||
max_samples,
|
||||
contamination,
|
||||
n_jobs_model,
|
||||
hybrid,
|
||||
load_ae,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
(Hybrid) Isolation Forest model for anomaly detection.
|
||||
|
||||
@@ -69,78 +161,100 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s.' % log_file)
|
||||
logger.info('Data path is %s.' % data_path)
|
||||
logger.info('Export path is %s.' % xp_path)
|
||||
logger.info("Log file is %s." % log_file)
|
||||
logger.info("Data path is %s." % data_path)
|
||||
logger.info("Export path is %s." % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
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 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
|
||||
logger.info('Number of base estimators in the ensemble: %d' % cfg.settings['n_estimators'])
|
||||
logger.info('Number of samples for training each base estimator: %d' % cfg.settings['max_samples'])
|
||||
logger.info('Contamination parameter: %.2f' % cfg.settings['contamination'])
|
||||
logger.info('Number of jobs for model training: %d' % n_jobs_model)
|
||||
logger.info('Hybrid model: %s' % cfg.settings['hybrid'])
|
||||
logger.info(
|
||||
"Number of base estimators in the ensemble: %d" % cfg.settings["n_estimators"]
|
||||
)
|
||||
logger.info(
|
||||
"Number of samples for training each base estimator: %d"
|
||||
% cfg.settings["max_samples"]
|
||||
)
|
||||
logger.info("Contamination parameter: %.2f" % cfg.settings["contamination"])
|
||||
logger.info("Number of jobs for model training: %d" % n_jobs_model)
|
||||
logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Use 'cpu' as device for Isolation Forest
|
||||
device = 'cpu'
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
device = "cpu"
|
||||
torch.multiprocessing.set_sharing_strategy(
|
||||
"file_system"
|
||||
) # fix multiprocessing issue for ubuntu
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize Isolation Forest model
|
||||
Isoforest = IsoForest(hybrid=cfg.settings['hybrid'], n_estimators=cfg.settings['n_estimators'],
|
||||
max_samples=cfg.settings['max_samples'], contamination=cfg.settings['contamination'],
|
||||
n_jobs=n_jobs_model, seed=cfg.settings['seed'])
|
||||
Isoforest = IsoForest(
|
||||
hybrid=cfg.settings["hybrid"],
|
||||
n_estimators=cfg.settings["n_estimators"],
|
||||
max_samples=cfg.settings["max_samples"],
|
||||
contamination=cfg.settings["contamination"],
|
||||
n_jobs=n_jobs_model,
|
||||
seed=cfg.settings["seed"],
|
||||
)
|
||||
|
||||
# If specified, load model parameters from already trained model
|
||||
if load_model:
|
||||
Isoforest.load_model(import_path=load_model, device=device)
|
||||
logger.info('Loading model from %s.' % load_model)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
|
||||
# If specified, load model autoencoder weights for a hybrid approach
|
||||
if hybrid and load_ae is not None:
|
||||
Isoforest.load_ae(dataset_name, model_path=load_ae)
|
||||
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae)
|
||||
logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
|
||||
|
||||
# Train model on dataset
|
||||
Isoforest.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
@@ -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)
|
||||
|
||||
# Save results and configuration
|
||||
Isoforest.save_results(export_json=xp_path + '/results.json')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
Isoforest.save_results(export_json=xp_path + "/results.json")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*Isoforest.results['test_scores'])
|
||||
indices, labels, scores = zip(*Isoforest.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -14,44 +14,133 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--kernel', type=click.Choice(['gaussian', 'tophat', 'epanechnikov', 'exponential', 'linear', 'cosine']),
|
||||
default='gaussian', help='Kernel for the KDE')
|
||||
@click.option('--grid_search_cv', type=bool, default=True,
|
||||
help='Use sklearn GridSearchCV to determine optimal bandwidth')
|
||||
@click.option('--n_jobs_model', type=int, default=-1, help='Number of jobs for model training.')
|
||||
@click.option('--hybrid', type=bool, default=False,
|
||||
help='Train KDE on features extracted from an autoencoder. If True, load_ae must be specified.')
|
||||
@click.option('--load_ae', type=click.Path(exists=True), default=None,
|
||||
help='Model file path to load autoencoder weights (default: None).')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier,
|
||||
ratio_pollution, seed, kernel, grid_search_cv, n_jobs_model, hybrid, load_ae, n_jobs_dataloader, normal_class,
|
||||
known_outlier_class, n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--kernel",
|
||||
type=click.Choice(
|
||||
["gaussian", "tophat", "epanechnikov", "exponential", "linear", "cosine"]
|
||||
),
|
||||
default="gaussian",
|
||||
help="Kernel for the KDE",
|
||||
)
|
||||
@click.option(
|
||||
"--grid_search_cv",
|
||||
type=bool,
|
||||
default=True,
|
||||
help="Use sklearn GridSearchCV to determine optimal bandwidth",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_model", type=int, default=-1, help="Number of jobs for model training."
|
||||
)
|
||||
@click.option(
|
||||
"--hybrid",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Train KDE on features extracted from an autoencoder. If True, load_ae must be specified.",
|
||||
)
|
||||
@click.option(
|
||||
"--load_ae",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path to load autoencoder weights (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
seed,
|
||||
kernel,
|
||||
grid_search_cv,
|
||||
n_jobs_model,
|
||||
hybrid,
|
||||
load_ae,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
(Hybrid) KDE for anomaly detection.
|
||||
|
||||
@@ -67,114 +156,157 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s.' % log_file)
|
||||
logger.info('Data path is %s.' % data_path)
|
||||
logger.info('Export path is %s.' % xp_path)
|
||||
logger.info("Log file is %s." % log_file)
|
||||
logger.info("Data path is %s." % data_path)
|
||||
logger.info("Export path is %s." % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
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 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
|
||||
logger.info('KDE kernel: %s' % cfg.settings['kernel'])
|
||||
logger.info('Use GridSearchCV for bandwidth selection: %s' % cfg.settings['grid_search_cv'])
|
||||
logger.info('Number of jobs for model training: %d' % n_jobs_model)
|
||||
logger.info('Hybrid model: %s' % cfg.settings['hybrid'])
|
||||
logger.info("KDE kernel: %s" % cfg.settings["kernel"])
|
||||
logger.info(
|
||||
"Use GridSearchCV for bandwidth selection: %s" % cfg.settings["grid_search_cv"]
|
||||
)
|
||||
logger.info("Number of jobs for model training: %d" % n_jobs_model)
|
||||
logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Use 'cpu' as device for KDE
|
||||
device = 'cpu'
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
device = "cpu"
|
||||
torch.multiprocessing.set_sharing_strategy(
|
||||
"file_system"
|
||||
) # fix multiprocessing issue for ubuntu
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize KDE model
|
||||
kde = KDE(hybrid=cfg.settings['hybrid'], kernel=cfg.settings['kernel'], n_jobs=n_jobs_model,
|
||||
seed=cfg.settings['seed'])
|
||||
kde = KDE(
|
||||
hybrid=cfg.settings["hybrid"],
|
||||
kernel=cfg.settings["kernel"],
|
||||
n_jobs=n_jobs_model,
|
||||
seed=cfg.settings["seed"],
|
||||
)
|
||||
|
||||
# If specified, load model parameters from already trained model
|
||||
if load_model:
|
||||
kde.load_model(import_path=load_model, device=device)
|
||||
logger.info('Loading model from %s.' % load_model)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
|
||||
# If specified, load model autoencoder weights for a hybrid approach
|
||||
if hybrid and load_ae is not None:
|
||||
kde.load_ae(dataset_name, model_path=load_ae)
|
||||
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae)
|
||||
logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
|
||||
|
||||
# Train model on dataset
|
||||
kde.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader,
|
||||
bandwidth_GridSearchCV=cfg.settings['grid_search_cv'])
|
||||
kde.train(
|
||||
dataset,
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
bandwidth_GridSearchCV=cfg.settings["grid_search_cv"],
|
||||
)
|
||||
|
||||
# Test model
|
||||
kde.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
|
||||
# Save results and configuration
|
||||
kde.save_results(export_json=xp_path + '/results.json')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
kde.save_results(export_json=xp_path + "/results.json")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*kde.results['test_scores'])
|
||||
indices, labels, scores = zip(*kde.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -14,41 +14,127 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--kernel', type=click.Choice(['rbf', 'linear', 'poly']), default='rbf', help='Kernel for the OC-SVM')
|
||||
@click.option('--nu', type=float, default=0.1, help='OC-SVM hyperparameter nu (must be 0 < nu <= 1).')
|
||||
@click.option('--hybrid', type=bool, default=False,
|
||||
help='Train OC-SVM on features extracted from an autoencoder. If True, load_ae must be specified.')
|
||||
@click.option('--load_ae', type=click.Path(exists=True), default=None,
|
||||
help='Model file path to load autoencoder weights (default: None).')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier,
|
||||
ratio_pollution, seed, kernel, nu, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class,
|
||||
n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--kernel",
|
||||
type=click.Choice(["rbf", "linear", "poly"]),
|
||||
default="rbf",
|
||||
help="Kernel for the OC-SVM",
|
||||
)
|
||||
@click.option(
|
||||
"--nu",
|
||||
type=float,
|
||||
default=0.1,
|
||||
help="OC-SVM hyperparameter nu (must be 0 < nu <= 1).",
|
||||
)
|
||||
@click.option(
|
||||
"--hybrid",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Train OC-SVM on features extracted from an autoencoder. If True, load_ae must be specified.",
|
||||
)
|
||||
@click.option(
|
||||
"--load_ae",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path to load autoencoder weights (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
seed,
|
||||
kernel,
|
||||
nu,
|
||||
hybrid,
|
||||
load_ae,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
(Hybrid) One-Class SVM for anomaly detection.
|
||||
|
||||
@@ -64,74 +150,86 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s.' % log_file)
|
||||
logger.info('Data path is %s.' % data_path)
|
||||
logger.info('Export path is %s.' % xp_path)
|
||||
logger.info("Log file is %s." % log_file)
|
||||
logger.info("Data path is %s." % data_path)
|
||||
logger.info("Export path is %s." % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
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 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
|
||||
logger.info('OC-SVM kernel: %s' % cfg.settings['kernel'])
|
||||
logger.info('Nu-paramerter: %.2f' % cfg.settings['nu'])
|
||||
logger.info('Hybrid model: %s' % cfg.settings['hybrid'])
|
||||
logger.info("OC-SVM kernel: %s" % cfg.settings["kernel"])
|
||||
logger.info("Nu-paramerter: %.2f" % cfg.settings["nu"])
|
||||
logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Use 'cpu' as device for OC-SVM
|
||||
device = 'cpu'
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
device = "cpu"
|
||||
torch.multiprocessing.set_sharing_strategy(
|
||||
"file_system"
|
||||
) # fix multiprocessing issue for ubuntu
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize OC-SVM model
|
||||
ocsvm = OCSVM(cfg.settings['kernel'], cfg.settings['nu'], cfg.settings['hybrid'])
|
||||
ocsvm = OCSVM(cfg.settings["kernel"], cfg.settings["nu"], cfg.settings["hybrid"])
|
||||
|
||||
# If specified, load model parameters from already trained model
|
||||
if load_model:
|
||||
ocsvm.load_model(import_path=load_model, device=device)
|
||||
logger.info('Loading model from %s.' % load_model)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
|
||||
# If specified, load model autoencoder weights for a hybrid approach
|
||||
if hybrid and load_ae is not None:
|
||||
ocsvm.load_ae(dataset_name, model_path=load_ae)
|
||||
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae)
|
||||
logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
|
||||
|
||||
# Train model on dataset
|
||||
ocsvm.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
@@ -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)
|
||||
|
||||
# Save results and configuration
|
||||
ocsvm.save_results(export_json=xp_path + '/results.json')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
ocsvm.save_results(export_json=xp_path + "/results.json")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*ocsvm.results['test_scores'])
|
||||
indices, labels, scores = zip(*ocsvm.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -15,41 +15,119 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--kernel', type=click.Choice(['rbf']), default='rbf', help='Kernel for SSAD')
|
||||
@click.option('--kappa', type=float, default=1.0, help='SSAD hyperparameter kappa.')
|
||||
@click.option('--hybrid', type=bool, default=False,
|
||||
help='Train SSAD on features extracted from an autoencoder. If True, load_ae must be specified')
|
||||
@click.option('--load_ae', type=click.Path(exists=True), default=None,
|
||||
help='Model file path to load autoencoder weights (default: None).')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_normal, ratio_known_outlier,
|
||||
ratio_pollution, seed, kernel, kappa, hybrid, load_ae, n_jobs_dataloader, normal_class, known_outlier_class,
|
||||
n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--kernel", type=click.Choice(["rbf"]), default="rbf", help="Kernel for SSAD"
|
||||
)
|
||||
@click.option("--kappa", type=float, default=1.0, help="SSAD hyperparameter kappa.")
|
||||
@click.option(
|
||||
"--hybrid",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="Train SSAD on features extracted from an autoencoder. If True, load_ae must be specified",
|
||||
)
|
||||
@click.option(
|
||||
"--load_ae",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path to load autoencoder weights (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
seed,
|
||||
kernel,
|
||||
kappa,
|
||||
hybrid,
|
||||
load_ae,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
(Hybrid) SSAD for anomaly detection as in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013.
|
||||
|
||||
@@ -65,75 +143,91 @@ def main(dataset_name, xp_path, data_path, load_config, load_model, ratio_known_
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s.' % log_file)
|
||||
logger.info('Data path is %s.' % data_path)
|
||||
logger.info('Export path is %s.' % xp_path)
|
||||
logger.info("Log file is %s." % log_file)
|
||||
logger.info("Data path is %s." % data_path)
|
||||
logger.info("Export path is %s." % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
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 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
|
||||
logger.info('SSAD kernel: %s' % cfg.settings['kernel'])
|
||||
logger.info('Kappa-paramerter: %.2f' % cfg.settings['kappa'])
|
||||
logger.info('Hybrid model: %s' % cfg.settings['hybrid'])
|
||||
logger.info("SSAD kernel: %s" % cfg.settings["kernel"])
|
||||
logger.info("Kappa-paramerter: %.2f" % cfg.settings["kappa"])
|
||||
logger.info("Hybrid model: %s" % cfg.settings["hybrid"])
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
co.setseed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
co.setseed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Use 'cpu' as device for SSAD
|
||||
device = 'cpu'
|
||||
torch.multiprocessing.set_sharing_strategy('file_system') # fix multiprocessing issue for ubuntu
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
device = "cpu"
|
||||
torch.multiprocessing.set_sharing_strategy(
|
||||
"file_system"
|
||||
) # fix multiprocessing issue for ubuntu
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize SSAD model
|
||||
ssad = SSAD(kernel=cfg.settings['kernel'], kappa=cfg.settings['kappa'], hybrid=cfg.settings['hybrid'])
|
||||
ssad = SSAD(
|
||||
kernel=cfg.settings["kernel"],
|
||||
kappa=cfg.settings["kappa"],
|
||||
hybrid=cfg.settings["hybrid"],
|
||||
)
|
||||
|
||||
# If specified, load model parameters from already trained model
|
||||
if load_model:
|
||||
ssad.load_model(import_path=load_model, device=device)
|
||||
logger.info('Loading model from %s.' % load_model)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
|
||||
# If specified, load model autoencoder weights for a hybrid approach
|
||||
if hybrid and load_ae is not None:
|
||||
ssad.load_ae(dataset_name, model_path=load_ae)
|
||||
logger.info('Loaded pretrained autoencoder for features from %s.' % load_ae)
|
||||
logger.info("Loaded pretrained autoencoder for features from %s." % load_ae)
|
||||
|
||||
# Train model on dataset
|
||||
ssad.train(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
@@ -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)
|
||||
|
||||
# Save results and configuration
|
||||
ssad.save_results(export_json=xp_path + '/results.json')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
ssad.save_results(export_json=xp_path + "/results.json")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*ssad.results['test_scores'])
|
||||
indices, labels, scores = zip(*ssad.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)))
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -36,17 +36,13 @@ class SemiDeepGenerativeModel(object):
|
||||
self.vae_optimizer_name = None
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_auc': None,
|
||||
'test_time': None,
|
||||
'test_scores': None,
|
||||
"train_time": None,
|
||||
"test_auc": None,
|
||||
"test_time": None,
|
||||
"test_scores": None,
|
||||
}
|
||||
|
||||
self.vae_results = {
|
||||
'train_time': None,
|
||||
'test_auc': None,
|
||||
'test_time': None
|
||||
}
|
||||
self.vae_results = {"train_time": None, "test_auc": None, "test_time": None}
|
||||
|
||||
def set_vae(self, net_name):
|
||||
"""Builds the variational autoencoder network for pretraining."""
|
||||
@@ -58,71 +54,106 @@ class SemiDeepGenerativeModel(object):
|
||||
self.net_name = net_name
|
||||
self.net = build_network(net_name, ae_net=self.vae_net) # full M1+M2 model
|
||||
|
||||
def train(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 50,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
def train(
|
||||
self,
|
||||
dataset: BaseADDataset,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 50,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
"""Trains the Semi-Supervised Deep Generative model on the training data."""
|
||||
|
||||
self.optimizer_name = optimizer_name
|
||||
|
||||
self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, optimizer_name=optimizer_name, lr=lr,
|
||||
n_epochs=n_epochs, lr_milestones=lr_milestones, batch_size=batch_size,
|
||||
weight_decay=weight_decay, device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
self.trainer = SemiDeepGenerativeTrainer(
|
||||
alpha=self.alpha,
|
||||
optimizer_name=optimizer_name,
|
||||
lr=lr,
|
||||
n_epochs=n_epochs,
|
||||
lr_milestones=lr_milestones,
|
||||
batch_size=batch_size,
|
||||
weight_decay=weight_decay,
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
self.net = self.trainer.train(dataset, self.net)
|
||||
self.results['train_time'] = self.trainer.train_time
|
||||
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."""
|
||||
|
||||
if self.trainer is None:
|
||||
self.trainer = SemiDeepGenerativeTrainer(alpha=self.alpha, device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
self.trainer = SemiDeepGenerativeTrainer(
|
||||
alpha=self.alpha, device=device, n_jobs_dataloader=n_jobs_dataloader
|
||||
)
|
||||
|
||||
self.trainer.test(dataset, self.net)
|
||||
# Get results
|
||||
self.results['test_auc'] = self.trainer.test_auc
|
||||
self.results['test_time'] = self.trainer.test_time
|
||||
self.results['test_scores'] = self.trainer.test_scores
|
||||
self.results["test_auc"] = self.trainer.test_auc
|
||||
self.results["test_time"] = self.trainer.test_time
|
||||
self.results["test_scores"] = self.trainer.test_scores
|
||||
|
||||
def pretrain(self, dataset: BaseADDataset, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 100,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
def pretrain(
|
||||
self,
|
||||
dataset: BaseADDataset,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 100,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
"""Pretrains a variational autoencoder (M1) for the Semi-Supervised Deep Generative model."""
|
||||
|
||||
# Train
|
||||
self.vae_optimizer_name = optimizer_name
|
||||
self.vae_trainer = VAETrainer(optimizer_name=optimizer_name, lr=lr, n_epochs=n_epochs,
|
||||
lr_milestones=lr_milestones, batch_size=batch_size, weight_decay=weight_decay,
|
||||
device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
self.vae_trainer = VAETrainer(
|
||||
optimizer_name=optimizer_name,
|
||||
lr=lr,
|
||||
n_epochs=n_epochs,
|
||||
lr_milestones=lr_milestones,
|
||||
batch_size=batch_size,
|
||||
weight_decay=weight_decay,
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
self.vae_net = self.vae_trainer.train(dataset, self.vae_net)
|
||||
# Get train results
|
||||
self.vae_results['train_time'] = self.vae_trainer.train_time
|
||||
self.vae_results["train_time"] = self.vae_trainer.train_time
|
||||
|
||||
# Test
|
||||
self.vae_trainer.test(dataset, self.vae_net)
|
||||
# Get test results
|
||||
self.vae_results['test_auc'] = self.vae_trainer.test_auc
|
||||
self.vae_results['test_time'] = self.vae_trainer.test_time
|
||||
self.vae_results["test_auc"] = self.vae_trainer.test_auc
|
||||
self.vae_results["test_time"] = self.vae_trainer.test_time
|
||||
|
||||
def save_model(self, export_model):
|
||||
"""Save a Semi-Supervised Deep Generative model to export_model."""
|
||||
|
||||
net_dict = self.net.state_dict()
|
||||
torch.save({'net_dict': net_dict}, export_model)
|
||||
torch.save({"net_dict": net_dict}, export_model)
|
||||
|
||||
def load_model(self, model_path):
|
||||
"""Load a Semi-Supervised Deep Generative model from model_path."""
|
||||
|
||||
model_dict = torch.load(model_path)
|
||||
self.net.load_state_dict(model_dict['net_dict'])
|
||||
self.net.load_state_dict(model_dict["net_dict"])
|
||||
|
||||
def save_results(self, export_json):
|
||||
"""Save results dict to a JSON-file."""
|
||||
with open(export_json, 'w') as fp:
|
||||
with open(export_json, "w") as fp:
|
||||
json.dump(self.results, fp)
|
||||
|
||||
def save_vae_results(self, export_json):
|
||||
"""Save variational autoencoder results dict to a JSON-file."""
|
||||
with open(export_json, 'w') as fp:
|
||||
with open(export_json, "w") as fp:
|
||||
json.dump(self.vae_results, fp)
|
||||
|
||||
@@ -14,8 +14,16 @@ from networks.main import build_autoencoder
|
||||
class IsoForest(object):
|
||||
"""A class for Isolation Forest models."""
|
||||
|
||||
def __init__(self, hybrid=False, n_estimators=100, max_samples='auto', contamination=0.1, n_jobs=-1, seed=None,
|
||||
**kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
hybrid=False,
|
||||
n_estimators=100,
|
||||
max_samples="auto",
|
||||
contamination=0.1,
|
||||
n_jobs=-1,
|
||||
seed=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Init Isolation Forest instance."""
|
||||
self.n_estimators = n_estimators
|
||||
self.max_samples = max_samples
|
||||
@@ -23,26 +31,39 @@ class IsoForest(object):
|
||||
self.n_jobs = n_jobs
|
||||
self.seed = seed
|
||||
|
||||
self.model = IsolationForest(n_estimators=n_estimators, max_samples=max_samples, contamination=contamination,
|
||||
n_jobs=n_jobs, random_state=seed, **kwargs)
|
||||
self.model = IsolationForest(
|
||||
n_estimators=n_estimators,
|
||||
max_samples=max_samples,
|
||||
contamination=contamination,
|
||||
n_jobs=n_jobs,
|
||||
random_state=seed,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.hybrid = hybrid
|
||||
self.ae_net = None # autoencoder network for the case of a hybrid model
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_time': None,
|
||||
'test_auc': None,
|
||||
'test_scores': None
|
||||
"train_time": None,
|
||||
"test_time": None,
|
||||
"test_auc": None,
|
||||
"test_scores": None,
|
||||
}
|
||||
|
||||
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0):
|
||||
def train(
|
||||
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
|
||||
):
|
||||
"""Trains the Isolation Forest model on the training data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
# do not drop last batch for non-SGD optimization shallow_ssad
|
||||
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True,
|
||||
num_workers=n_jobs_dataloader, drop_last=False)
|
||||
train_loader = DataLoader(
|
||||
dataset=dataset.train_set,
|
||||
batch_size=128,
|
||||
shuffle=True,
|
||||
num_workers=n_jobs_dataloader,
|
||||
drop_last=False,
|
||||
)
|
||||
|
||||
# Get data from loader
|
||||
X = ()
|
||||
@@ -50,22 +71,28 @@ class IsoForest(object):
|
||||
inputs, _, _, _ = data
|
||||
inputs = inputs.to(device)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
start_time = time.time()
|
||||
self.model.fit(X)
|
||||
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('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 Isolation Forest model on the test data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -78,46 +105,54 @@ class IsoForest(object):
|
||||
labels = []
|
||||
for data in test_loader:
|
||||
inputs, label_batch, _, idx = data
|
||||
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device)
|
||||
inputs, label_batch, idx = (
|
||||
inputs.to(device),
|
||||
label_batch.to(device),
|
||||
idx.to(device),
|
||||
)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
start_time = time.time()
|
||||
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()
|
||||
|
||||
# Save triples of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idxs, labels, scores.tolist()))
|
||||
self.results['test_scores'] = idx_label_score
|
||||
self.results["test_scores"] = idx_label_score
|
||||
|
||||
# Compute AUC
|
||||
_, labels, scores = zip(*idx_label_score)
|
||||
labels = np.array(labels)
|
||||
scores = np.array(scores)
|
||||
self.results['test_auc'] = roc_auc_score(labels, scores)
|
||||
self.results["test_auc"] = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc']))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.results['test_time']))
|
||||
logger.info('Finished testing.')
|
||||
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("Finished testing.")
|
||||
|
||||
def load_ae(self, dataset_name, model_path):
|
||||
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid Isolation Forest model."""
|
||||
|
||||
model_dict = torch.load(model_path, map_location='cpu')
|
||||
ae_net_dict = model_dict['ae_net_dict']
|
||||
if dataset_name in ['mnist', 'fmnist', 'cifar10']:
|
||||
net_name = dataset_name + '_LeNet'
|
||||
model_dict = torch.load(model_path, map_location="cpu")
|
||||
ae_net_dict = model_dict["ae_net_dict"]
|
||||
if dataset_name in ["mnist", "fmnist", "cifar10"]:
|
||||
net_name = dataset_name + "_LeNet"
|
||||
else:
|
||||
net_name = dataset_name + '_mlp'
|
||||
net_name = dataset_name + "_mlp"
|
||||
|
||||
if self.ae_net is None:
|
||||
self.ae_net = build_autoencoder(net_name)
|
||||
@@ -137,11 +172,11 @@ class IsoForest(object):
|
||||
"""Save Isolation Forest model to export_path."""
|
||||
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."""
|
||||
pass
|
||||
|
||||
def save_results(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -16,7 +16,7 @@ from networks.main import build_autoencoder
|
||||
class KDE(object):
|
||||
"""A class for Kernel Density Estimation models."""
|
||||
|
||||
def __init__(self, hybrid=False, kernel='gaussian', n_jobs=-1, seed=None, **kwargs):
|
||||
def __init__(self, hybrid=False, kernel="gaussian", n_jobs=-1, seed=None, **kwargs):
|
||||
"""Init Kernel Density Estimation instance."""
|
||||
self.kernel = kernel
|
||||
self.n_jobs = n_jobs
|
||||
@@ -29,20 +29,30 @@ class KDE(object):
|
||||
self.ae_net = None # autoencoder network for the case of a hybrid model
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_time': None,
|
||||
'test_auc': None,
|
||||
'test_scores': None
|
||||
"train_time": None,
|
||||
"test_time": None,
|
||||
"test_auc": None,
|
||||
"test_scores": None,
|
||||
}
|
||||
|
||||
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0,
|
||||
bandwidth_GridSearchCV: bool = True):
|
||||
def train(
|
||||
self,
|
||||
dataset: BaseADDataset,
|
||||
device: str = "cpu",
|
||||
n_jobs_dataloader: int = 0,
|
||||
bandwidth_GridSearchCV: bool = True,
|
||||
):
|
||||
"""Trains the Kernel Density Estimation model on the training data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
# do not drop last batch for non-SGD optimization shallow_ssad
|
||||
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True,
|
||||
num_workers=n_jobs_dataloader, drop_last=False)
|
||||
train_loader = DataLoader(
|
||||
dataset=dataset.train_set,
|
||||
batch_size=128,
|
||||
shuffle=True,
|
||||
num_workers=n_jobs_dataloader,
|
||||
drop_last=False,
|
||||
)
|
||||
|
||||
# Get data from loader
|
||||
X = ()
|
||||
@@ -50,39 +60,51 @@ class KDE(object):
|
||||
inputs, _, _, _ = data
|
||||
inputs = inputs.to(device)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
start_time = time.time()
|
||||
|
||||
if bandwidth_GridSearchCV:
|
||||
# use grid search cross-validation to select bandwidth
|
||||
logger.info('Using GridSearchCV for bandwidth selection...')
|
||||
params = {'bandwidth': np.logspace(0.5, 5, num=10, base=2)}
|
||||
hyper_kde = GridSearchCV(KernelDensity(kernel=self.kernel), params, n_jobs=self.n_jobs, cv=5, verbose=0)
|
||||
logger.info("Using GridSearchCV for bandwidth selection...")
|
||||
params = {"bandwidth": np.logspace(0.5, 5, num=10, base=2)}
|
||||
hyper_kde = GridSearchCV(
|
||||
KernelDensity(kernel=self.kernel),
|
||||
params,
|
||||
n_jobs=self.n_jobs,
|
||||
cv=5,
|
||||
verbose=0,
|
||||
)
|
||||
hyper_kde.fit(X)
|
||||
self.bandwidth = hyper_kde.best_estimator_.bandwidth
|
||||
logger.info('Best bandwidth: {:.8f}'.format(self.bandwidth))
|
||||
logger.info("Best bandwidth: {:.8f}".format(self.bandwidth))
|
||||
self.model = hyper_kde.best_estimator_
|
||||
else:
|
||||
# if exponential kernel, re-initialize kde with bandwidth minimizing the numerical error
|
||||
if self.kernel == 'exponential':
|
||||
if self.kernel == "exponential":
|
||||
self.bandwidth = np.max(pairwise_distances(X)) ** 2
|
||||
self.model = KernelDensity(kernel=self.kernel, bandwidth=self.bandwidth)
|
||||
|
||||
self.model.fit(X)
|
||||
|
||||
train_time = time.time() - start_time
|
||||
self.results['train_time'] = train_time
|
||||
self.results["train_time"] = train_time
|
||||
|
||||
logger.info('Training Time: {:.3f}s'.format(self.results['train_time']))
|
||||
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 Kernel Density Estimation model on the test data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -95,46 +117,54 @@ class KDE(object):
|
||||
labels = []
|
||||
for data in test_loader:
|
||||
inputs, label_batch, _, idx = data
|
||||
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device)
|
||||
inputs, label_batch, idx = (
|
||||
inputs.to(device),
|
||||
label_batch.to(device),
|
||||
idx.to(device),
|
||||
)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
start_time = time.time()
|
||||
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()
|
||||
|
||||
# Save triples of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idxs, labels, scores.tolist()))
|
||||
self.results['test_scores'] = idx_label_score
|
||||
self.results["test_scores"] = idx_label_score
|
||||
|
||||
# Compute AUC
|
||||
_, labels, scores = zip(*idx_label_score)
|
||||
labels = np.array(labels)
|
||||
scores = np.array(scores)
|
||||
self.results['test_auc'] = roc_auc_score(labels, scores)
|
||||
self.results["test_auc"] = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc']))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.results['test_time']))
|
||||
logger.info('Finished testing.')
|
||||
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("Finished testing.")
|
||||
|
||||
def load_ae(self, dataset_name, model_path):
|
||||
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid KDE model."""
|
||||
|
||||
model_dict = torch.load(model_path, map_location='cpu')
|
||||
ae_net_dict = model_dict['ae_net_dict']
|
||||
if dataset_name in ['mnist', 'fmnist', 'cifar10']:
|
||||
net_name = dataset_name + '_LeNet'
|
||||
model_dict = torch.load(model_path, map_location="cpu")
|
||||
ae_net_dict = model_dict["ae_net_dict"]
|
||||
if dataset_name in ["mnist", "fmnist", "cifar10"]:
|
||||
net_name = dataset_name + "_LeNet"
|
||||
else:
|
||||
net_name = dataset_name + '_mlp'
|
||||
net_name = dataset_name + "_mlp"
|
||||
|
||||
if self.ae_net is None:
|
||||
self.ae_net = build_autoencoder(net_name)
|
||||
@@ -154,11 +184,11 @@ class KDE(object):
|
||||
"""Save KDE model to export_path."""
|
||||
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."""
|
||||
pass
|
||||
|
||||
def save_results(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -14,7 +14,7 @@ from networks.main import build_autoencoder
|
||||
class OCSVM(object):
|
||||
"""A class for One-Class SVM models."""
|
||||
|
||||
def __init__(self, kernel='rbf', nu=0.1, hybrid=False):
|
||||
def __init__(self, kernel="rbf", nu=0.1, hybrid=False):
|
||||
"""Init OCSVM instance."""
|
||||
self.kernel = kernel
|
||||
self.nu = nu
|
||||
@@ -25,25 +25,34 @@ class OCSVM(object):
|
||||
|
||||
self.hybrid = hybrid
|
||||
self.ae_net = None # autoencoder network for the case of a hybrid model
|
||||
self.linear_model = None # also init a model with linear kernel if hybrid approach
|
||||
self.linear_model = (
|
||||
None # also init a model with linear kernel if hybrid approach
|
||||
)
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_time': None,
|
||||
'test_auc': None,
|
||||
'test_scores': None,
|
||||
'train_time_linear': None,
|
||||
'test_time_linear': None,
|
||||
'test_auc_linear': None
|
||||
"train_time": None,
|
||||
"test_time": None,
|
||||
"test_auc": None,
|
||||
"test_scores": None,
|
||||
"train_time_linear": None,
|
||||
"test_time_linear": None,
|
||||
"test_auc_linear": None,
|
||||
}
|
||||
|
||||
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0):
|
||||
def train(
|
||||
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
|
||||
):
|
||||
"""Trains the OC-SVM model on the training data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
# do not drop last batch for non-SGD optimization shallow_ssad
|
||||
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True,
|
||||
num_workers=n_jobs_dataloader, drop_last=False)
|
||||
train_loader = DataLoader(
|
||||
dataset=dataset.train_set,
|
||||
batch_size=128,
|
||||
shuffle=True,
|
||||
num_workers=n_jobs_dataloader,
|
||||
drop_last=False,
|
||||
)
|
||||
|
||||
# Get data from loader
|
||||
X = ()
|
||||
@@ -51,13 +60,17 @@ class OCSVM(object):
|
||||
inputs, _, _, _ = data
|
||||
inputs = inputs.to(device)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
|
||||
# Select model via hold-out test set of 1000 samples
|
||||
gammas = np.logspace(-7, 2, num=10, base=2)
|
||||
@@ -72,17 +85,31 @@ class OCSVM(object):
|
||||
inputs, label_batch, _, _ = data
|
||||
inputs, label_batch = inputs.to(device), label_batch.to(device)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X_test += (X_batch.cpu().data.numpy(),)
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X_test, labels = np.concatenate(X_test), np.array(labels)
|
||||
n_test, n_normal, n_outlier = len(X_test), np.sum(labels == 0), np.sum(labels == 1)
|
||||
n_test, n_normal, n_outlier = (
|
||||
len(X_test),
|
||||
np.sum(labels == 0),
|
||||
np.sum(labels == 1),
|
||||
)
|
||||
n_val = int(0.1 * n_test)
|
||||
n_val_normal, n_val_outlier = int(n_val * (n_normal/n_test)), int(n_val * (n_outlier/n_test))
|
||||
n_val_normal, n_val_outlier = int(n_val * (n_normal / n_test)), int(
|
||||
n_val * (n_outlier / n_test)
|
||||
)
|
||||
perm = np.random.permutation(n_test)
|
||||
X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal],
|
||||
X_test[perm][labels[perm] == 1][:n_val_outlier]))
|
||||
X_val = np.concatenate(
|
||||
(
|
||||
X_test[perm][labels[perm] == 0][:n_val_normal],
|
||||
X_test[perm][labels[perm] == 1][:n_val_outlier],
|
||||
)
|
||||
)
|
||||
labels = np.array([0] * n_val_normal + [1] * n_val_outlier)
|
||||
|
||||
i = 1
|
||||
@@ -103,30 +130,36 @@ class OCSVM(object):
|
||||
# Compute AUC
|
||||
auc = roc_auc_score(labels, scores)
|
||||
|
||||
logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s '
|
||||
f'| Val AUC: {100. * auc:.2f} |')
|
||||
logger.info(
|
||||
f" | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s "
|
||||
f"| Val AUC: {100. * auc:.2f} |"
|
||||
)
|
||||
|
||||
if auc > best_auc:
|
||||
best_auc = auc
|
||||
self.model = model
|
||||
self.gamma = gamma
|
||||
self.results['train_time'] = train_time
|
||||
self.results["train_time"] = train_time
|
||||
|
||||
i += 1
|
||||
|
||||
# If hybrid, also train a model with linear kernel
|
||||
if self.hybrid:
|
||||
self.linear_model = OneClassSVM(kernel='linear', nu=self.nu)
|
||||
self.linear_model = OneClassSVM(kernel="linear", nu=self.nu)
|
||||
start_time = time.time()
|
||||
self.linear_model.fit(X)
|
||||
train_time = time.time() - start_time
|
||||
self.results['train_time_linear'] = train_time
|
||||
self.results["train_time_linear"] = train_time
|
||||
|
||||
logger.info(f'Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}')
|
||||
logger.info('Training Time: {:.3f}s'.format(self.results['train_time']))
|
||||
logger.info('Finished training.')
|
||||
logger.info(
|
||||
f"Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}"
|
||||
)
|
||||
logger.info("Training Time: {:.3f}s".format(self.results["train_time"]))
|
||||
logger.info("Finished training.")
|
||||
|
||||
def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0):
|
||||
def test(
|
||||
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
|
||||
):
|
||||
"""Tests the OC-SVM model on the test data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -139,59 +172,75 @@ class OCSVM(object):
|
||||
labels = []
|
||||
for data in test_loader:
|
||||
inputs, label_batch, _, idx = data
|
||||
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device)
|
||||
inputs, label_batch, idx = (
|
||||
inputs.to(device),
|
||||
label_batch.to(device),
|
||||
idx.to(device),
|
||||
)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
start_time = time.time()
|
||||
|
||||
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()
|
||||
self.rho = -self.model.intercept_[0]
|
||||
|
||||
# Save triples of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idxs, labels, scores.tolist()))
|
||||
self.results['test_scores'] = idx_label_score
|
||||
self.results["test_scores"] = idx_label_score
|
||||
|
||||
# Compute AUC
|
||||
_, labels, scores = zip(*idx_label_score)
|
||||
labels = np.array(labels)
|
||||
scores = np.array(scores)
|
||||
self.results['test_auc'] = roc_auc_score(labels, scores)
|
||||
self.results["test_auc"] = roc_auc_score(labels, scores)
|
||||
|
||||
# If hybrid, also test model with linear kernel
|
||||
if self.hybrid:
|
||||
start_time = time.time()
|
||||
scores_linear = (-1.0) * self.linear_model.decision_function(X)
|
||||
self.results['test_time_linear'] = time.time() - start_time
|
||||
self.results["test_time_linear"] = time.time() - start_time
|
||||
scores_linear = scores_linear.flatten()
|
||||
self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear)
|
||||
logger.info('Test AUC linear model: {:.2f}%'.format(100. * self.results['test_auc_linear']))
|
||||
logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear']))
|
||||
self.results["test_auc_linear"] = roc_auc_score(labels, scores_linear)
|
||||
logger.info(
|
||||
"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
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc']))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.results['test_time']))
|
||||
logger.info('Finished testing.')
|
||||
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("Finished testing.")
|
||||
|
||||
def load_ae(self, dataset_name, model_path):
|
||||
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid OC-SVM model."""
|
||||
|
||||
model_dict = torch.load(model_path, map_location='cpu')
|
||||
ae_net_dict = model_dict['ae_net_dict']
|
||||
if dataset_name in ['mnist', 'fmnist', 'cifar10']:
|
||||
net_name = dataset_name + '_LeNet'
|
||||
model_dict = torch.load(model_path, map_location="cpu")
|
||||
ae_net_dict = model_dict["ae_net_dict"]
|
||||
if dataset_name in ["mnist", "fmnist", "cifar10"]:
|
||||
net_name = dataset_name + "_LeNet"
|
||||
else:
|
||||
net_name = dataset_name + '_mlp'
|
||||
net_name = dataset_name + "_mlp"
|
||||
|
||||
if self.ae_net is None:
|
||||
self.ae_net = build_autoencoder(net_name)
|
||||
@@ -211,11 +260,11 @@ class OCSVM(object):
|
||||
"""Save OC-SVM model to export_path."""
|
||||
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."""
|
||||
pass
|
||||
|
||||
def save_results(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -8,31 +8,32 @@ from cvxopt.solvers import qp
|
||||
|
||||
|
||||
class ConvexSSAD:
|
||||
""" Convex semi-supervised anomaly detection with hinge-loss and L2 regularizer
|
||||
as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013
|
||||
"""Convex semi-supervised anomaly detection with hinge-loss and L2 regularizer
|
||||
as described in Goernitz et al., Towards Supervised Anomaly Detection, JAIR, 2013
|
||||
|
||||
minimize 0.5 ||w||^2_2 - rho - kappa*gamma + eta_u sum_i xi_i + eta_l sum_j xi_j
|
||||
{w,rho,gamma>=0,xi>=0}
|
||||
subject to <w,phi(x_i)> >= rho - xi_i
|
||||
y_j<w,phi(x_j)> >= y_j*rho + gamma - 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}
|
||||
subject to <w,phi(x_i)> >= rho - xi_i
|
||||
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)
|
||||
{0<=alpha_i<=eta_i}
|
||||
subject to kappa <= sum_j alpha_j (for all labeled examples)
|
||||
1 = sum_j y_i alpha_j (for all examples)
|
||||
maximize -0.5 sum_(i,j) alpha_i alpha_j y_i y_j k(x_i,x_j)
|
||||
{0<=alpha_i<=eta_i}
|
||||
subject to kappa <= sum_j alpha_j (for all labeled examples)
|
||||
1 = sum_j y_i alpha_j (for all examples)
|
||||
|
||||
We introduce labels y_i = +1 for all unlabeled examples which enables us to combine sums.
|
||||
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!
|
||||
|
||||
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.y = y # (vector) corresponding labels (+1,-1 and 0 for unlabeled)
|
||||
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[y == 0] = Cu
|
||||
self.cC[y == 1] = Cp
|
||||
self.cC[y ==-1] = Cn
|
||||
self.cC[y == -1] = Cn
|
||||
|
||||
self.alphas = None
|
||||
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
|
||||
# prohibit a solution
|
||||
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
|
||||
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):
|
||||
dim1, dim2 = kernel.shape
|
||||
print([dim1, dim2])
|
||||
assert(dim1 == dim2 and dim1 == self.samples)
|
||||
assert dim1 == dim2 and dim1 == self.samples
|
||||
self.kernel = kernel
|
||||
|
||||
def fit(self, check_psd_eigs=False):
|
||||
@@ -81,20 +86,20 @@ class ConvexSSAD:
|
||||
Y = self.cy.dot(self.cy.T)
|
||||
|
||||
# generate the final PDS kernel
|
||||
P = matrix(self.kernel*Y)
|
||||
P = matrix(self.kernel * Y)
|
||||
|
||||
# check for PSD
|
||||
if check_psd_eigs:
|
||||
eigs = np.linalg.eigvalsh(np.array(P))
|
||||
if eigs[0] < 0.0:
|
||||
print('Smallest eigenvalue is {0}'.format(eigs[0]))
|
||||
print("Smallest eigenvalue is {0}".format(eigs[0]))
|
||||
P += spdiag([-eigs[0] for i in range(N)])
|
||||
|
||||
# there is no linear part of the objective
|
||||
q = matrix(0.0, (N, 1))
|
||||
|
||||
# sum_i y_i alpha_i = A alpha = b = 1.0
|
||||
A = matrix(self.cy, (1, self.samples), 'd')
|
||||
A = matrix(self.cy, (1, self.samples), "d")
|
||||
b = matrix(1.0, (1, 1))
|
||||
|
||||
# inequality constraints: G alpha <= h
|
||||
@@ -107,8 +112,8 @@ class ConvexSSAD:
|
||||
h = matrix([h1, h2])
|
||||
if self.labeled > 0:
|
||||
# 3) kappa <= \sum_i labeled_i alpha_i -> -cl' alpha <= -kappa
|
||||
print('Labeled data found.')
|
||||
G3 = -matrix(self.cl, (1, self.cl.size), 'd')
|
||||
print("Labeled data found.")
|
||||
G3 = -matrix(self.cl, (1, self.cl.size), "d")
|
||||
h3 = -matrix(self.kappa, (1, 1))
|
||||
G = sparse([G12, -G12, G3])
|
||||
h = matrix([h1, h2, h3])
|
||||
@@ -117,27 +122,49 @@ class ConvexSSAD:
|
||||
sol = qp(P, -q, G, h, A, b)
|
||||
|
||||
# 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
|
||||
# 2. store all support vector with alpha_i < C in 'margins'
|
||||
self.svs = np.where(self.alphas >= ConvexSSAD.PRECISION)[0]
|
||||
|
||||
# these should sum to one
|
||||
print('Validate solution:')
|
||||
print('- found {0} support vectors'.format(len(self.svs)))
|
||||
print('0 <= alpha_i : {0} of {1}'.format(np.sum(0. <= self.alphas), N))
|
||||
print('- sum_(i) alpha_i cy_i = {0} = 1.0'.format(np.sum(self.alphas*self.cy)))
|
||||
print('- sum_(i in sv) alpha_i cy_i = {0} ~ 1.0 (approx error)'.format(np.sum(self.alphas[self.svs]*self.cy[self.svs])))
|
||||
print('- sum_(i in labeled) alpha_i = {0} >= {1} = kappa'.format(np.sum(self.alphas[self.cl == 1]), self.kappa))
|
||||
print('- sum_(i in unlabeled) alpha_i = {0}'.format(np.sum(self.alphas[self.y == 0])))
|
||||
print('- sum_(i in positives) alpha_i = {0}'.format(np.sum(self.alphas[self.y == 1])))
|
||||
print('- sum_(i in negatives) alpha_i = {0}'.format(np.sum(self.alphas[self.y ==-1])))
|
||||
print("Validate solution:")
|
||||
print("- found {0} support vectors".format(len(self.svs)))
|
||||
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(
|
||||
"- sum_(i in sv) alpha_i cy_i = {0} ~ 1.0 (approx error)".format(
|
||||
np.sum(self.alphas[self.svs] * self.cy[self.svs])
|
||||
)
|
||||
)
|
||||
print(
|
||||
"- sum_(i in labeled) alpha_i = {0} >= {1} = kappa".format(
|
||||
np.sum(self.alphas[self.cl == 1]), self.kappa
|
||||
)
|
||||
)
|
||||
print(
|
||||
"- sum_(i in unlabeled) alpha_i = {0}".format(
|
||||
np.sum(self.alphas[self.y == 0])
|
||||
)
|
||||
)
|
||||
print(
|
||||
"- sum_(i in positives) alpha_i = {0}".format(
|
||||
np.sum(self.alphas[self.y == 1])
|
||||
)
|
||||
)
|
||||
print(
|
||||
"- sum_(i in negatives) alpha_i = {0}".format(
|
||||
np.sum(self.alphas[self.y == -1])
|
||||
)
|
||||
)
|
||||
|
||||
# infer threshold (rho)
|
||||
psvs = np.where(self.y[self.svs] == 0)[0]
|
||||
# case 1: unlabeled support vectors available
|
||||
self.threshold = 0.
|
||||
self.threshold = 0.0
|
||||
unl_threshold = -1e12
|
||||
lbl_threshold = -1e12
|
||||
if psvs.size > 0:
|
||||
@@ -146,7 +173,7 @@ class ConvexSSAD:
|
||||
unl_threshold = np.max(self.apply(k))
|
||||
|
||||
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 = k[self.svs, :]
|
||||
thres = self.apply(k)
|
||||
@@ -154,7 +181,7 @@ class ConvexSSAD:
|
||||
ninds = np.where(self.y[self.svs] == -1)[0]
|
||||
# only negatives is not possible
|
||||
if ninds.size > 0 and pinds.size == 0:
|
||||
print('ERROR: Check pre-defined PRECISION.')
|
||||
print("ERROR: Check pre-defined PRECISION.")
|
||||
lbl_threshold = np.max(thres[ninds])
|
||||
elif ninds.size == 0:
|
||||
lbl_threshold = np.max(thres[pinds])
|
||||
@@ -162,7 +189,7 @@ class ConvexSSAD:
|
||||
# smallest negative + largest positive
|
||||
p = np.max(thres[pinds])
|
||||
n = np.min(thres[ninds])
|
||||
lbl_threshold = (n+p)/2.
|
||||
lbl_threshold = (n + p) / 2.0
|
||||
self.threshold = np.max((unl_threshold, lbl_threshold))
|
||||
|
||||
def get_threshold(self):
|
||||
@@ -175,8 +202,8 @@ class ConvexSSAD:
|
||||
return self.alphas
|
||||
|
||||
def apply(self, kernel):
|
||||
""" Application of dual trained ssad.
|
||||
kernel = get_kernel(Y, X[:, cssad.svs], kernel_type, kernel_param)
|
||||
"""Application of dual trained ssad.
|
||||
kernel = get_kernel(Y, X[:, cssad.svs], kernel_type, kernel_param)
|
||||
"""
|
||||
if kernel.shape[1] == self.samples:
|
||||
# if kernel is not restricted to support vectors
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
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."""
|
||||
self.kernel = kernel
|
||||
self.kappa = kappa
|
||||
@@ -32,42 +32,59 @@ class SSAD(object):
|
||||
|
||||
self.hybrid = hybrid
|
||||
self.ae_net = None # autoencoder network for the case of a hybrid model
|
||||
self.linear_model = None # also init a model with linear kernel if hybrid approach
|
||||
self.linear_model = (
|
||||
None # also init a model with linear kernel if hybrid approach
|
||||
)
|
||||
self.linear_X_svs = None
|
||||
|
||||
self.results = {
|
||||
'train_time': None,
|
||||
'test_time': None,
|
||||
'test_auc': None,
|
||||
'test_scores': None,
|
||||
'train_time_linear': None,
|
||||
'test_time_linear': None,
|
||||
'test_auc_linear': None
|
||||
"train_time": None,
|
||||
"test_time": None,
|
||||
"test_auc": None,
|
||||
"test_scores": None,
|
||||
"train_time_linear": None,
|
||||
"test_time_linear": None,
|
||||
"test_auc_linear": None,
|
||||
}
|
||||
|
||||
def train(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0):
|
||||
def train(
|
||||
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
|
||||
):
|
||||
"""Trains the SSAD model on the training data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
# do not drop last batch for non-SGD optimization shallow_ssad
|
||||
train_loader = DataLoader(dataset=dataset.train_set, batch_size=128, shuffle=True,
|
||||
num_workers=n_jobs_dataloader, drop_last=False)
|
||||
train_loader = DataLoader(
|
||||
dataset=dataset.train_set,
|
||||
batch_size=128,
|
||||
shuffle=True,
|
||||
num_workers=n_jobs_dataloader,
|
||||
drop_last=False,
|
||||
)
|
||||
|
||||
# Get data from loader
|
||||
X = ()
|
||||
semi_targets = []
|
||||
for data in train_loader:
|
||||
inputs, _, semi_targets_batch, _ = data
|
||||
inputs, semi_targets_batch = inputs.to(device), semi_targets_batch.to(device)
|
||||
inputs, semi_targets_batch = inputs.to(device), semi_targets_batch.to(
|
||||
device
|
||||
)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
semi_targets += semi_targets_batch.cpu().data.numpy().astype(np.int).tolist()
|
||||
semi_targets += (
|
||||
semi_targets_batch.cpu().data.numpy().astype(np.int).tolist()
|
||||
)
|
||||
X, semi_targets = np.concatenate(X), np.array(semi_targets)
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
|
||||
# Select model via hold-out test set of 1000 samples
|
||||
gammas = np.logspace(-7, 2, num=10, base=2)
|
||||
@@ -82,17 +99,31 @@ class SSAD(object):
|
||||
inputs, label_batch, _, _ = data
|
||||
inputs, label_batch = inputs.to(device), label_batch.to(device)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X_test += (X_batch.cpu().data.numpy(),)
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X_test, labels = np.concatenate(X_test), np.array(labels)
|
||||
n_test, n_normal, n_outlier = len(X_test), np.sum(labels == 0), np.sum(labels == 1)
|
||||
n_test, n_normal, n_outlier = (
|
||||
len(X_test),
|
||||
np.sum(labels == 0),
|
||||
np.sum(labels == 1),
|
||||
)
|
||||
n_val = int(0.1 * n_test)
|
||||
n_val_normal, n_val_outlier = int(n_val * (n_normal/n_test)), int(n_val * (n_outlier/n_test))
|
||||
n_val_normal, n_val_outlier = int(n_val * (n_normal / n_test)), int(
|
||||
n_val * (n_outlier / n_test)
|
||||
)
|
||||
perm = np.random.permutation(n_test)
|
||||
X_val = np.concatenate((X_test[perm][labels[perm] == 0][:n_val_normal],
|
||||
X_test[perm][labels[perm] == 1][:n_val_outlier]))
|
||||
X_val = np.concatenate(
|
||||
(
|
||||
X_test[perm][labels[perm] == 0][:n_val_normal],
|
||||
X_test[perm][labels[perm] == 1][:n_val_outlier],
|
||||
)
|
||||
)
|
||||
labels = np.array([0] * n_val_normal + [1] * n_val_outlier)
|
||||
|
||||
i = 1
|
||||
@@ -110,21 +141,25 @@ class SSAD(object):
|
||||
train_time = time.time() - start_time
|
||||
|
||||
# Test on small hold-out set from test set
|
||||
kernel_val = pairwise_kernels(X_val, X[model.svs, :], metric=self.kernel, gamma=gamma)
|
||||
kernel_val = pairwise_kernels(
|
||||
X_val, X[model.svs, :], metric=self.kernel, gamma=gamma
|
||||
)
|
||||
scores = (-1.0) * model.apply(kernel_val)
|
||||
scores = scores.flatten()
|
||||
|
||||
# Compute AUC
|
||||
auc = roc_auc_score(labels, scores)
|
||||
|
||||
logger.info(f' | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s '
|
||||
f'| Val AUC: {100. * auc:.2f} |')
|
||||
logger.info(
|
||||
f" | Model {i:02}/{len(gammas):02} | Gamma: {gamma:.8f} | Train Time: {train_time:.3f}s "
|
||||
f"| Val AUC: {100. * auc:.2f} |"
|
||||
)
|
||||
|
||||
if auc > best_auc:
|
||||
best_auc = auc
|
||||
self.model = model
|
||||
self.gamma = gamma
|
||||
self.results['train_time'] = train_time
|
||||
self.results["train_time"] = train_time
|
||||
|
||||
i += 1
|
||||
|
||||
@@ -133,19 +168,25 @@ class SSAD(object):
|
||||
|
||||
# If hybrid, also train a model with linear kernel
|
||||
if self.hybrid:
|
||||
linear_kernel = pairwise_kernels(X, X, metric='linear')
|
||||
self.linear_model = ConvexSSAD(linear_kernel, semi_targets, Cp=self.Cp, Cu=self.Cu, Cn=self.Cn)
|
||||
linear_kernel = pairwise_kernels(X, X, metric="linear")
|
||||
self.linear_model = ConvexSSAD(
|
||||
linear_kernel, semi_targets, Cp=self.Cp, Cu=self.Cu, Cn=self.Cn
|
||||
)
|
||||
start_time = time.time()
|
||||
self.linear_model.fit()
|
||||
train_time = time.time() - start_time
|
||||
self.results['train_time_linear'] = train_time
|
||||
self.results["train_time_linear"] = train_time
|
||||
self.linear_X_svs = X[self.linear_model.svs, :]
|
||||
|
||||
logger.info(f'Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}')
|
||||
logger.info('Training Time: {:.3f}s'.format(self.results['train_time']))
|
||||
logger.info('Finished training.')
|
||||
logger.info(
|
||||
f"Best Model: | Gamma: {self.gamma:.8f} | AUC: {100. * best_auc:.2f}"
|
||||
)
|
||||
logger.info("Training Time: {:.3f}s".format(self.results["train_time"]))
|
||||
logger.info("Finished training.")
|
||||
|
||||
def test(self, dataset: BaseADDataset, device: str = 'cpu', n_jobs_dataloader: int = 0):
|
||||
def test(
|
||||
self, dataset: BaseADDataset, device: str = "cpu", n_jobs_dataloader: int = 0
|
||||
):
|
||||
"""Tests the SSAD model on the test data."""
|
||||
logger = logging.getLogger()
|
||||
|
||||
@@ -158,17 +199,25 @@ class SSAD(object):
|
||||
labels = []
|
||||
for data in test_loader:
|
||||
inputs, label_batch, _, idx = data
|
||||
inputs, label_batch, idx = inputs.to(device), label_batch.to(device), idx.to(device)
|
||||
inputs, label_batch, idx = (
|
||||
inputs.to(device),
|
||||
label_batch.to(device),
|
||||
idx.to(device),
|
||||
)
|
||||
if self.hybrid:
|
||||
inputs = self.ae_net.encoder(inputs) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(inputs.size(0), -1) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
inputs = self.ae_net.encoder(
|
||||
inputs
|
||||
) # in hybrid approach, take code representation of AE as features
|
||||
X_batch = inputs.view(
|
||||
inputs.size(0), -1
|
||||
) # X_batch.shape = (batch_size, n_channels * height * width)
|
||||
X += (X_batch.cpu().data.numpy(),)
|
||||
idxs += idx.cpu().data.numpy().astype(np.int64).tolist()
|
||||
labels += label_batch.cpu().data.numpy().astype(np.int64).tolist()
|
||||
X = np.concatenate(X)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
start_time = time.time()
|
||||
|
||||
# Build kernel
|
||||
@@ -176,45 +225,53 @@ class SSAD(object):
|
||||
|
||||
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()
|
||||
self.rho = -self.model.threshold
|
||||
|
||||
# Save triples of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idxs, labels, scores.tolist()))
|
||||
self.results['test_scores'] = idx_label_score
|
||||
self.results["test_scores"] = idx_label_score
|
||||
|
||||
# Compute AUC
|
||||
_, labels, scores = zip(*idx_label_score)
|
||||
labels = np.array(labels)
|
||||
scores = np.array(scores)
|
||||
self.results['test_auc'] = roc_auc_score(labels, scores)
|
||||
self.results["test_auc"] = roc_auc_score(labels, scores)
|
||||
|
||||
# If hybrid, also test model with linear kernel
|
||||
if self.hybrid:
|
||||
start_time = time.time()
|
||||
linear_kernel = pairwise_kernels(X, self.linear_X_svs, metric='linear')
|
||||
linear_kernel = pairwise_kernels(X, self.linear_X_svs, metric="linear")
|
||||
scores_linear = (-1.0) * self.linear_model.apply(linear_kernel)
|
||||
self.results['test_time_linear'] = time.time() - start_time
|
||||
self.results["test_time_linear"] = time.time() - start_time
|
||||
scores_linear = scores_linear.flatten()
|
||||
self.results['test_auc_linear'] = roc_auc_score(labels, scores_linear)
|
||||
logger.info('Test AUC linear model: {:.2f}%'.format(100. * self.results['test_auc_linear']))
|
||||
logger.info('Test Time linear model: {:.3f}s'.format(self.results['test_time_linear']))
|
||||
self.results["test_auc_linear"] = roc_auc_score(labels, scores_linear)
|
||||
logger.info(
|
||||
"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
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.results['test_auc']))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.results['test_time']))
|
||||
logger.info('Finished testing.')
|
||||
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("Finished testing.")
|
||||
|
||||
def load_ae(self, dataset_name, model_path):
|
||||
"""Load pretrained autoencoder from model_path for feature extraction in a hybrid SSAD model."""
|
||||
|
||||
model_dict = torch.load(model_path, map_location='cpu')
|
||||
ae_net_dict = model_dict['ae_net_dict']
|
||||
if dataset_name in ['mnist', 'fmnist', 'cifar10']:
|
||||
net_name = dataset_name + '_LeNet'
|
||||
model_dict = torch.load(model_path, map_location="cpu")
|
||||
ae_net_dict = model_dict["ae_net_dict"]
|
||||
if dataset_name in ["mnist", "fmnist", "cifar10"]:
|
||||
net_name = dataset_name + "_LeNet"
|
||||
else:
|
||||
net_name = dataset_name + '_mlp'
|
||||
net_name = dataset_name + "_mlp"
|
||||
|
||||
if self.ae_net is None:
|
||||
self.ae_net = build_autoencoder(net_name)
|
||||
@@ -234,11 +291,11 @@ class SSAD(object):
|
||||
"""Save SSAD model to export_path."""
|
||||
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."""
|
||||
pass
|
||||
|
||||
def save_results(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -12,8 +12,16 @@ import numpy as np
|
||||
|
||||
class CIFAR10_Dataset(TorchvisionDataset):
|
||||
|
||||
def __init__(self, root: str, normal_class: int = 5, known_outlier_class: int = 3, n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
normal_class: int = 5,
|
||||
known_outlier_class: int = 3,
|
||||
n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0,
|
||||
ratio_pollution: float = 0.0,
|
||||
):
|
||||
super().__init__(root)
|
||||
|
||||
# Define normal and outlier classes
|
||||
@@ -28,28 +36,48 @@ class CIFAR10_Dataset(TorchvisionDataset):
|
||||
elif n_known_outlier_classes == 1:
|
||||
self.known_outlier_classes = tuple([known_outlier_class])
|
||||
else:
|
||||
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes))
|
||||
self.known_outlier_classes = tuple(
|
||||
random.sample(self.outlier_classes, n_known_outlier_classes)
|
||||
)
|
||||
|
||||
# CIFAR-10 preprocessing: feature scaling to [0, 1]
|
||||
transform = transforms.ToTensor()
|
||||
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
|
||||
|
||||
# Get train set
|
||||
train_set = MyCIFAR10(root=self.root, train=True, transform=transform, target_transform=target_transform,
|
||||
download=True)
|
||||
train_set = MyCIFAR10(
|
||||
root=self.root,
|
||||
train=True,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
# Create semi-supervised setting
|
||||
idx, _, semi_targets = create_semisupervised_setting(np.array(train_set.targets), self.normal_classes,
|
||||
self.outlier_classes, self.known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution)
|
||||
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels
|
||||
idx, _, semi_targets = create_semisupervised_setting(
|
||||
np.array(train_set.targets),
|
||||
self.normal_classes,
|
||||
self.outlier_classes,
|
||||
self.known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
)
|
||||
train_set.semi_targets[idx] = torch.tensor(
|
||||
semi_targets
|
||||
) # set respective semi-supervised labels
|
||||
|
||||
# Subset train_set to semi-supervised setup
|
||||
self.train_set = Subset(train_set, idx)
|
||||
|
||||
# Get test set
|
||||
self.test_set = MyCIFAR10(root=self.root, train=False, transform=transform, target_transform=target_transform,
|
||||
download=True)
|
||||
self.test_set = MyCIFAR10(
|
||||
root=self.root,
|
||||
train=False,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
|
||||
class MyCIFAR10(CIFAR10):
|
||||
@@ -71,7 +99,11 @@ class MyCIFAR10(CIFAR10):
|
||||
Returns:
|
||||
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
|
||||
# to return a PIL Image
|
||||
|
||||
@@ -11,8 +11,16 @@ import random
|
||||
|
||||
class FashionMNIST_Dataset(TorchvisionDataset):
|
||||
|
||||
def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
normal_class: int = 0,
|
||||
known_outlier_class: int = 1,
|
||||
n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0,
|
||||
ratio_pollution: float = 0.0,
|
||||
):
|
||||
super().__init__(root)
|
||||
|
||||
# Define normal and outlier classes
|
||||
@@ -27,28 +35,48 @@ class FashionMNIST_Dataset(TorchvisionDataset):
|
||||
elif n_known_outlier_classes == 1:
|
||||
self.known_outlier_classes = tuple([known_outlier_class])
|
||||
else:
|
||||
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes))
|
||||
self.known_outlier_classes = tuple(
|
||||
random.sample(self.outlier_classes, n_known_outlier_classes)
|
||||
)
|
||||
|
||||
# FashionMNIST preprocessing: feature scaling to [0, 1]
|
||||
transform = transforms.ToTensor()
|
||||
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
|
||||
|
||||
# Get train set
|
||||
train_set = MyFashionMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform,
|
||||
download=True)
|
||||
train_set = MyFashionMNIST(
|
||||
root=self.root,
|
||||
train=True,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
# Create semi-supervised setting
|
||||
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes,
|
||||
self.outlier_classes, self.known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution)
|
||||
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels
|
||||
idx, _, semi_targets = create_semisupervised_setting(
|
||||
train_set.targets.cpu().data.numpy(),
|
||||
self.normal_classes,
|
||||
self.outlier_classes,
|
||||
self.known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
)
|
||||
train_set.semi_targets[idx] = torch.tensor(
|
||||
semi_targets
|
||||
) # set respective semi-supervised labels
|
||||
|
||||
# Subset train_set to semi-supervised setup
|
||||
self.train_set = Subset(train_set, idx)
|
||||
|
||||
# Get test set
|
||||
self.test_set = MyFashionMNIST(root=self.root, train=False, transform=transform,
|
||||
target_transform=target_transform, download=True)
|
||||
self.test_set = MyFashionMNIST(
|
||||
root=self.root,
|
||||
train=False,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
|
||||
class MyFashionMNIST(FashionMNIST):
|
||||
@@ -70,11 +98,15 @@ class MyFashionMNIST(FashionMNIST):
|
||||
Returns:
|
||||
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
|
||||
# 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:
|
||||
img = self.transform(img)
|
||||
|
||||
@@ -4,51 +4,83 @@ from .cifar10 import CIFAR10_Dataset
|
||||
from .odds import ODDSADDataset
|
||||
|
||||
|
||||
def load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0,
|
||||
random_state=None):
|
||||
def load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0,
|
||||
ratio_pollution: float = 0.0,
|
||||
random_state=None,
|
||||
):
|
||||
"""Loads the dataset."""
|
||||
|
||||
implemented_datasets = ('mnist', 'fmnist', 'cifar10',
|
||||
'arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid')
|
||||
implemented_datasets = (
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
)
|
||||
assert dataset_name in implemented_datasets
|
||||
|
||||
dataset = None
|
||||
|
||||
if dataset_name == 'mnist':
|
||||
dataset = MNIST_Dataset(root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution)
|
||||
if dataset_name == "mnist":
|
||||
dataset = MNIST_Dataset(
|
||||
root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution,
|
||||
)
|
||||
|
||||
if dataset_name == 'fmnist':
|
||||
dataset = FashionMNIST_Dataset(root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution)
|
||||
if dataset_name == "fmnist":
|
||||
dataset = FashionMNIST_Dataset(
|
||||
root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution,
|
||||
)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
dataset = CIFAR10_Dataset(root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution)
|
||||
if dataset_name == "cifar10":
|
||||
dataset = CIFAR10_Dataset(
|
||||
root=data_path,
|
||||
normal_class=normal_class,
|
||||
known_outlier_class=known_outlier_class,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution,
|
||||
)
|
||||
|
||||
if dataset_name in ('arrhythmia', 'cardio', 'satellite', 'satimage-2', 'shuttle', 'thyroid'):
|
||||
dataset = ODDSADDataset(root=data_path,
|
||||
dataset_name=dataset_name,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution,
|
||||
random_state=random_state)
|
||||
if dataset_name in (
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
):
|
||||
dataset = ODDSADDataset(
|
||||
root=data_path,
|
||||
dataset_name=dataset_name,
|
||||
n_known_outlier_classes=n_known_outlier_classes,
|
||||
ratio_known_normal=ratio_known_normal,
|
||||
ratio_known_outlier=ratio_known_outlier,
|
||||
ratio_pollution=ratio_pollution,
|
||||
random_state=random_state,
|
||||
)
|
||||
|
||||
return dataset
|
||||
|
||||
@@ -11,8 +11,16 @@ import random
|
||||
|
||||
class MNIST_Dataset(TorchvisionDataset):
|
||||
|
||||
def __init__(self, root: str, normal_class: int = 0, known_outlier_class: int = 1, n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0, ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
normal_class: int = 0,
|
||||
known_outlier_class: int = 1,
|
||||
n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0,
|
||||
ratio_pollution: float = 0.0,
|
||||
):
|
||||
super().__init__(root)
|
||||
|
||||
# Define normal and outlier classes
|
||||
@@ -27,28 +35,48 @@ class MNIST_Dataset(TorchvisionDataset):
|
||||
elif n_known_outlier_classes == 1:
|
||||
self.known_outlier_classes = tuple([known_outlier_class])
|
||||
else:
|
||||
self.known_outlier_classes = tuple(random.sample(self.outlier_classes, n_known_outlier_classes))
|
||||
self.known_outlier_classes = tuple(
|
||||
random.sample(self.outlier_classes, n_known_outlier_classes)
|
||||
)
|
||||
|
||||
# MNIST preprocessing: feature scaling to [0, 1]
|
||||
transform = transforms.ToTensor()
|
||||
target_transform = transforms.Lambda(lambda x: int(x in self.outlier_classes))
|
||||
|
||||
# Get train set
|
||||
train_set = MyMNIST(root=self.root, train=True, transform=transform, target_transform=target_transform,
|
||||
download=True)
|
||||
train_set = MyMNIST(
|
||||
root=self.root,
|
||||
train=True,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
# Create semi-supervised setting
|
||||
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes,
|
||||
self.outlier_classes, self.known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution)
|
||||
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels
|
||||
idx, _, semi_targets = create_semisupervised_setting(
|
||||
train_set.targets.cpu().data.numpy(),
|
||||
self.normal_classes,
|
||||
self.outlier_classes,
|
||||
self.known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
)
|
||||
train_set.semi_targets[idx] = torch.tensor(
|
||||
semi_targets
|
||||
) # set respective semi-supervised labels
|
||||
|
||||
# Subset train_set to semi-supervised setup
|
||||
self.train_set = Subset(train_set, idx)
|
||||
|
||||
# Get test set
|
||||
self.test_set = MyMNIST(root=self.root, train=False, transform=transform, target_transform=target_transform,
|
||||
download=True)
|
||||
self.test_set = MyMNIST(
|
||||
root=self.root,
|
||||
train=False,
|
||||
transform=transform,
|
||||
target_transform=target_transform,
|
||||
download=True,
|
||||
)
|
||||
|
||||
|
||||
class MyMNIST(MNIST):
|
||||
@@ -70,11 +98,15 @@ class MyMNIST(MNIST):
|
||||
Returns:
|
||||
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
|
||||
# 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:
|
||||
img = self.transform(img)
|
||||
|
||||
@@ -8,8 +8,16 @@ import torch
|
||||
|
||||
class ODDSADDataset(BaseADDataset):
|
||||
|
||||
def __init__(self, root: str, dataset_name: str, n_known_outlier_classes: int = 0, ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0, ratio_pollution: float = 0.0, random_state=None):
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
dataset_name: str,
|
||||
n_known_outlier_classes: int = 0,
|
||||
ratio_known_normal: float = 0.0,
|
||||
ratio_known_outlier: float = 0.0,
|
||||
ratio_pollution: float = 0.0,
|
||||
random_state=None,
|
||||
):
|
||||
super().__init__(root)
|
||||
|
||||
# Define normal and outlier classes
|
||||
@@ -23,25 +31,58 @@ class ODDSADDataset(BaseADDataset):
|
||||
self.known_outlier_classes = (1,)
|
||||
|
||||
# Get train set
|
||||
train_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=True, random_state=random_state,
|
||||
download=True)
|
||||
train_set = ODDSDataset(
|
||||
root=self.root,
|
||||
dataset_name=dataset_name,
|
||||
train=True,
|
||||
random_state=random_state,
|
||||
download=True,
|
||||
)
|
||||
|
||||
# Create semi-supervised setting
|
||||
idx, _, semi_targets = create_semisupervised_setting(train_set.targets.cpu().data.numpy(), self.normal_classes,
|
||||
self.outlier_classes, self.known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution)
|
||||
train_set.semi_targets[idx] = torch.tensor(semi_targets) # set respective semi-supervised labels
|
||||
idx, _, semi_targets = create_semisupervised_setting(
|
||||
train_set.targets.cpu().data.numpy(),
|
||||
self.normal_classes,
|
||||
self.outlier_classes,
|
||||
self.known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
)
|
||||
train_set.semi_targets[idx] = torch.tensor(
|
||||
semi_targets
|
||||
) # set respective semi-supervised labels
|
||||
|
||||
# Subset train_set to semi-supervised setup
|
||||
self.train_set = Subset(train_set, idx)
|
||||
|
||||
# Get test set
|
||||
self.test_set = ODDSDataset(root=self.root, dataset_name=dataset_name, train=False, random_state=random_state)
|
||||
self.test_set = ODDSDataset(
|
||||
root=self.root,
|
||||
dataset_name=dataset_name,
|
||||
train=False,
|
||||
random_state=random_state,
|
||||
)
|
||||
|
||||
def loaders(self, batch_size: int, shuffle_train=True, shuffle_test=False, num_workers: int = 0) -> (
|
||||
DataLoader, DataLoader):
|
||||
train_loader = DataLoader(dataset=self.train_set, batch_size=batch_size, shuffle=shuffle_train,
|
||||
num_workers=num_workers, drop_last=True)
|
||||
test_loader = DataLoader(dataset=self.test_set, batch_size=batch_size, shuffle=shuffle_test,
|
||||
num_workers=num_workers, drop_last=False)
|
||||
def loaders(
|
||||
self,
|
||||
batch_size: int,
|
||||
shuffle_train=True,
|
||||
shuffle_test=False,
|
||||
num_workers: int = 0,
|
||||
) -> (DataLoader, DataLoader):
|
||||
train_loader = DataLoader(
|
||||
dataset=self.train_set,
|
||||
batch_size=batch_size,
|
||||
shuffle=shuffle_train,
|
||||
num_workers=num_workers,
|
||||
drop_last=True,
|
||||
)
|
||||
test_loader = DataLoader(
|
||||
dataset=self.test_set,
|
||||
batch_size=batch_size,
|
||||
shuffle=shuffle_test,
|
||||
num_workers=num_workers,
|
||||
drop_last=False,
|
||||
)
|
||||
return train_loader, test_loader
|
||||
|
||||
@@ -2,10 +2,17 @@ import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
def create_semisupervised_setting(labels, normal_classes, outlier_classes, known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution):
|
||||
def create_semisupervised_setting(
|
||||
labels,
|
||||
normal_classes,
|
||||
outlier_classes,
|
||||
known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
):
|
||||
"""
|
||||
Create a semi-supervised data setting.
|
||||
Create a semi-supervised data setting.
|
||||
:param labels: np.array with labels of all dataset samples
|
||||
:param normal_classes: tuple with normal class labels
|
||||
:param outlier_classes: tuple with anomaly class labels
|
||||
@@ -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_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)
|
||||
|
||||
# Solve system of linear equations to obtain respective number of samples
|
||||
a = np.array([[1, 1, 0, 0],
|
||||
[(1-ratio_known_normal), -ratio_known_normal, -ratio_known_normal, -ratio_known_normal],
|
||||
[-ratio_known_outlier, -ratio_known_outlier, -ratio_known_outlier, (1-ratio_known_outlier)],
|
||||
[0, -ratio_pollution, (1-ratio_pollution), 0]])
|
||||
a = np.array(
|
||||
[
|
||||
[1, 1, 0, 0],
|
||||
[
|
||||
(1 - ratio_known_normal),
|
||||
-ratio_known_normal,
|
||||
-ratio_known_normal,
|
||||
-ratio_known_normal,
|
||||
],
|
||||
[
|
||||
-ratio_known_outlier,
|
||||
-ratio_known_outlier,
|
||||
-ratio_known_outlier,
|
||||
(1 - ratio_known_outlier),
|
||||
],
|
||||
[0, -ratio_pollution, (1 - ratio_pollution), 0],
|
||||
]
|
||||
)
|
||||
b = np.array([n_normal, 0, 0, 0])
|
||||
x = np.linalg.solve(a, b)
|
||||
|
||||
@@ -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))
|
||||
|
||||
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_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
|
||||
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
|
||||
semi_labels_known_normal = np.ones(n_known_normal).astype(np.int32).tolist()
|
||||
semi_labels_unlabeled_normal = np.zeros(n_unlabeled_normal).astype(np.int32).tolist()
|
||||
semi_labels_unlabeled_outlier = np.zeros(n_unlabeled_outlier).astype(np.int32).tolist()
|
||||
semi_labels_unlabeled_normal = (
|
||||
np.zeros(n_unlabeled_normal).astype(np.int32).tolist()
|
||||
)
|
||||
semi_labels_unlabeled_outlier = (
|
||||
np.zeros(n_unlabeled_outlier).astype(np.int32).tolist()
|
||||
)
|
||||
semi_labels_known_outlier = (-np.ones(n_known_outlier).astype(np.int32)).tolist()
|
||||
|
||||
# Create final lists
|
||||
list_idx = idx_known_normal + idx_unlabeled_normal + idx_unlabeled_outlier + idx_known_outlier
|
||||
list_labels = labels_known_normal + labels_unlabeled_normal + labels_unlabeled_outlier + labels_known_outlier
|
||||
list_semi_labels = (semi_labels_known_normal + semi_labels_unlabeled_normal + semi_labels_unlabeled_outlier
|
||||
+ semi_labels_known_outlier)
|
||||
list_idx = (
|
||||
idx_known_normal
|
||||
+ idx_unlabeled_normal
|
||||
+ idx_unlabeled_outlier
|
||||
+ idx_known_outlier
|
||||
)
|
||||
list_labels = (
|
||||
labels_known_normal
|
||||
+ labels_unlabeled_normal
|
||||
+ labels_unlabeled_outlier
|
||||
+ labels_known_outlier
|
||||
)
|
||||
list_semi_labels = (
|
||||
semi_labels_known_normal
|
||||
+ semi_labels_unlabeled_normal
|
||||
+ semi_labels_unlabeled_outlier
|
||||
+ semi_labels_known_outlier
|
||||
)
|
||||
|
||||
return list_idx, list_labels, list_semi_labels
|
||||
|
||||
@@ -14,66 +14,222 @@ from datasets.main import load_dataset
|
||||
# Settings
|
||||
################################################################################
|
||||
@click.command()
|
||||
@click.argument('dataset_name', type=click.Choice(['mnist', 'fmnist', 'cifar10', 'arrhythmia', 'cardio', 'satellite',
|
||||
'satimage-2', 'shuttle', 'thyroid']))
|
||||
@click.argument('net_name', type=click.Choice(['mnist_LeNet', 'fmnist_LeNet', 'cifar10_LeNet', 'arrhythmia_mlp',
|
||||
'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp',
|
||||
'thyroid_mlp']))
|
||||
@click.argument('xp_path', type=click.Path(exists=True))
|
||||
@click.argument('data_path', type=click.Path(exists=True))
|
||||
@click.option('--load_config', type=click.Path(exists=True), default=None,
|
||||
help='Config JSON-file path (default: None).')
|
||||
@click.option('--load_model', type=click.Path(exists=True), default=None,
|
||||
help='Model file path (default: None).')
|
||||
@click.option('--eta', type=float, default=1.0, help='Deep SAD hyperparameter eta (must be 0 < eta).')
|
||||
@click.option('--ratio_known_normal', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) normal training examples.')
|
||||
@click.option('--ratio_known_outlier', type=float, default=0.0,
|
||||
help='Ratio of known (labeled) anomalous training examples.')
|
||||
@click.option('--ratio_pollution', type=float, default=0.0,
|
||||
help='Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.')
|
||||
@click.option('--device', type=str, default='cuda', help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).')
|
||||
@click.option('--seed', type=int, default=-1, help='Set seed. If -1, use randomization.')
|
||||
@click.option('--optimizer_name', type=click.Choice(['adam']), default='adam',
|
||||
help='Name of the optimizer to use for Deep SAD network training.')
|
||||
@click.option('--lr', type=float, default=0.001,
|
||||
help='Initial learning rate for Deep SAD network training. Default=0.001')
|
||||
@click.option('--n_epochs', type=int, default=50, help='Number of epochs to train.')
|
||||
@click.option('--lr_milestone', type=int, default=0, multiple=True,
|
||||
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.')
|
||||
@click.option('--batch_size', type=int, default=128, help='Batch size for mini-batch training.')
|
||||
@click.option('--weight_decay', type=float, default=1e-6,
|
||||
help='Weight decay (L2 penalty) hyperparameter for Deep SAD objective.')
|
||||
@click.option('--pretrain', type=bool, default=True,
|
||||
help='Pretrain neural network parameters via autoencoder.')
|
||||
@click.option('--ae_optimizer_name', type=click.Choice(['adam']), default='adam',
|
||||
help='Name of the optimizer to use for autoencoder pretraining.')
|
||||
@click.option('--ae_lr', type=float, default=0.001,
|
||||
help='Initial learning rate for autoencoder pretraining. Default=0.001')
|
||||
@click.option('--ae_n_epochs', type=int, default=100, help='Number of epochs to train autoencoder.')
|
||||
@click.option('--ae_lr_milestone', type=int, default=0, multiple=True,
|
||||
help='Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.')
|
||||
@click.option('--ae_batch_size', type=int, default=128, help='Batch size for mini-batch autoencoder training.')
|
||||
@click.option('--ae_weight_decay', type=float, default=1e-6,
|
||||
help='Weight decay (L2 penalty) hyperparameter for autoencoder objective.')
|
||||
@click.option('--num_threads', type=int, default=0,
|
||||
help='Number of threads used for parallelizing CPU operations. 0 means that all resources are used.')
|
||||
@click.option('--n_jobs_dataloader', type=int, default=0,
|
||||
help='Number of workers for data loading. 0 means that the data will be loaded in the main process.')
|
||||
@click.option('--normal_class', type=int, default=0,
|
||||
help='Specify the normal class of the dataset (all other classes are considered anomalous).')
|
||||
@click.option('--known_outlier_class', type=int, default=1,
|
||||
help='Specify the known outlier class of the dataset for semi-supervised anomaly detection.')
|
||||
@click.option('--n_known_outlier_classes', type=int, default=0,
|
||||
help='Number of known outlier classes.'
|
||||
'If 0, no anomalies are known.'
|
||||
'If 1, outlier class as specified in --known_outlier_class option.'
|
||||
'If > 1, the specified number of outlier classes will be sampled at random.')
|
||||
def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, eta,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution, device, seed,
|
||||
optimizer_name, lr, n_epochs, lr_milestone, batch_size, weight_decay,
|
||||
pretrain, ae_optimizer_name, ae_lr, ae_n_epochs, ae_lr_milestone, ae_batch_size, ae_weight_decay,
|
||||
num_threads, n_jobs_dataloader, normal_class, known_outlier_class, n_known_outlier_classes):
|
||||
@click.argument(
|
||||
"dataset_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist",
|
||||
"fmnist",
|
||||
"cifar10",
|
||||
"arrhythmia",
|
||||
"cardio",
|
||||
"satellite",
|
||||
"satimage-2",
|
||||
"shuttle",
|
||||
"thyroid",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument(
|
||||
"net_name",
|
||||
type=click.Choice(
|
||||
[
|
||||
"mnist_LeNet",
|
||||
"fmnist_LeNet",
|
||||
"cifar10_LeNet",
|
||||
"arrhythmia_mlp",
|
||||
"cardio_mlp",
|
||||
"satellite_mlp",
|
||||
"satimage-2_mlp",
|
||||
"shuttle_mlp",
|
||||
"thyroid_mlp",
|
||||
]
|
||||
),
|
||||
)
|
||||
@click.argument("xp_path", type=click.Path(exists=True))
|
||||
@click.argument("data_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--load_config",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Config JSON-file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--load_model",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Model file path (default: None).",
|
||||
)
|
||||
@click.option(
|
||||
"--eta",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Deep SAD hyperparameter eta (must be 0 < eta).",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_normal",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) normal training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_known_outlier",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Ratio of known (labeled) anomalous training examples.",
|
||||
)
|
||||
@click.option(
|
||||
"--ratio_pollution",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Pollution ratio of unlabeled training data with unknown (unlabeled) anomalies.",
|
||||
)
|
||||
@click.option(
|
||||
"--device",
|
||||
type=str,
|
||||
default="cuda",
|
||||
help='Computation device to use ("cpu", "cuda", "cuda:2", etc.).',
|
||||
)
|
||||
@click.option(
|
||||
"--seed", type=int, default=-1, help="Set seed. If -1, use randomization."
|
||||
)
|
||||
@click.option(
|
||||
"--optimizer_name",
|
||||
type=click.Choice(["adam"]),
|
||||
default="adam",
|
||||
help="Name of the optimizer to use for Deep SAD network training.",
|
||||
)
|
||||
@click.option(
|
||||
"--lr",
|
||||
type=float,
|
||||
default=0.001,
|
||||
help="Initial learning rate for Deep SAD network training. Default=0.001",
|
||||
)
|
||||
@click.option("--n_epochs", type=int, default=50, help="Number of epochs to train.")
|
||||
@click.option(
|
||||
"--lr_milestone",
|
||||
type=int,
|
||||
default=0,
|
||||
multiple=True,
|
||||
help="Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.",
|
||||
)
|
||||
@click.option(
|
||||
"--batch_size", type=int, default=128, help="Batch size for mini-batch training."
|
||||
)
|
||||
@click.option(
|
||||
"--weight_decay",
|
||||
type=float,
|
||||
default=1e-6,
|
||||
help="Weight decay (L2 penalty) hyperparameter for Deep SAD objective.",
|
||||
)
|
||||
@click.option(
|
||||
"--pretrain",
|
||||
type=bool,
|
||||
default=True,
|
||||
help="Pretrain neural network parameters via autoencoder.",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_optimizer_name",
|
||||
type=click.Choice(["adam"]),
|
||||
default="adam",
|
||||
help="Name of the optimizer to use for autoencoder pretraining.",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_lr",
|
||||
type=float,
|
||||
default=0.001,
|
||||
help="Initial learning rate for autoencoder pretraining. Default=0.001",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_n_epochs",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Number of epochs to train autoencoder.",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_lr_milestone",
|
||||
type=int,
|
||||
default=0,
|
||||
multiple=True,
|
||||
help="Lr scheduler milestones at which lr is multiplied by 0.1. Can be multiple and must be increasing.",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_batch_size",
|
||||
type=int,
|
||||
default=128,
|
||||
help="Batch size for mini-batch autoencoder training.",
|
||||
)
|
||||
@click.option(
|
||||
"--ae_weight_decay",
|
||||
type=float,
|
||||
default=1e-6,
|
||||
help="Weight decay (L2 penalty) hyperparameter for autoencoder objective.",
|
||||
)
|
||||
@click.option(
|
||||
"--num_threads",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of threads used for parallelizing CPU operations. 0 means that all resources are used.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_jobs_dataloader",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of workers for data loading. 0 means that the data will be loaded in the main process.",
|
||||
)
|
||||
@click.option(
|
||||
"--normal_class",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Specify the normal class of the dataset (all other classes are considered anomalous).",
|
||||
)
|
||||
@click.option(
|
||||
"--known_outlier_class",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Specify the known outlier class of the dataset for semi-supervised anomaly detection.",
|
||||
)
|
||||
@click.option(
|
||||
"--n_known_outlier_classes",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of known outlier classes."
|
||||
"If 0, no anomalies are known."
|
||||
"If 1, outlier class as specified in --known_outlier_class option."
|
||||
"If > 1, the specified number of outlier classes will be sampled at random.",
|
||||
)
|
||||
def main(
|
||||
dataset_name,
|
||||
net_name,
|
||||
xp_path,
|
||||
data_path,
|
||||
load_config,
|
||||
load_model,
|
||||
eta,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
device,
|
||||
seed,
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestone,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
pretrain,
|
||||
ae_optimizer_name,
|
||||
ae_lr,
|
||||
ae_n_epochs,
|
||||
ae_lr_milestone,
|
||||
ae_batch_size,
|
||||
ae_weight_decay,
|
||||
num_threads,
|
||||
n_jobs_dataloader,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
):
|
||||
"""
|
||||
Deep SAD, a method for deep semi-supervised anomaly detection.
|
||||
|
||||
@@ -90,150 +246,192 @@ def main(dataset_name, net_name, xp_path, data_path, load_config, load_model, et
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
log_file = xp_path + '/log.txt'
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
log_file = xp_path + "/log.txt"
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Print paths
|
||||
logger.info('Log file is %s' % log_file)
|
||||
logger.info('Data path is %s' % data_path)
|
||||
logger.info('Export path is %s' % xp_path)
|
||||
logger.info("Log file is %s" % log_file)
|
||||
logger.info("Data path is %s" % data_path)
|
||||
logger.info("Export path is %s" % xp_path)
|
||||
|
||||
# Print experimental setup
|
||||
logger.info('Dataset: %s' % dataset_name)
|
||||
logger.info('Normal class: %d' % normal_class)
|
||||
logger.info('Ratio of labeled normal train samples: %.2f' % ratio_known_normal)
|
||||
logger.info('Ratio of labeled anomalous samples: %.2f' % ratio_known_outlier)
|
||||
logger.info('Pollution ratio of unlabeled train data: %.2f' % ratio_pollution)
|
||||
logger.info("Dataset: %s" % dataset_name)
|
||||
logger.info("Normal class: %d" % normal_class)
|
||||
logger.info("Ratio of labeled normal train samples: %.2f" % ratio_known_normal)
|
||||
logger.info("Ratio of labeled anomalous samples: %.2f" % ratio_known_outlier)
|
||||
logger.info("Pollution ratio of unlabeled train data: %.2f" % ratio_pollution)
|
||||
if n_known_outlier_classes == 1:
|
||||
logger.info('Known anomaly class: %d' % known_outlier_class)
|
||||
logger.info("Known anomaly class: %d" % known_outlier_class)
|
||||
else:
|
||||
logger.info('Number of known anomaly classes: %d' % n_known_outlier_classes)
|
||||
logger.info('Network: %s' % net_name)
|
||||
logger.info("Number of known anomaly classes: %d" % n_known_outlier_classes)
|
||||
logger.info("Network: %s" % net_name)
|
||||
|
||||
# If specified, load experiment config from JSON-file
|
||||
if load_config:
|
||||
cfg.load_config(import_json=load_config)
|
||||
logger.info('Loaded configuration from %s.' % load_config)
|
||||
logger.info("Loaded configuration from %s." % load_config)
|
||||
|
||||
# Print model configuration
|
||||
logger.info('Eta-parameter: %.2f' % cfg.settings['eta'])
|
||||
logger.info("Eta-parameter: %.2f" % cfg.settings["eta"])
|
||||
|
||||
# Set seed
|
||||
if cfg.settings['seed'] != -1:
|
||||
random.seed(cfg.settings['seed'])
|
||||
np.random.seed(cfg.settings['seed'])
|
||||
torch.manual_seed(cfg.settings['seed'])
|
||||
torch.cuda.manual_seed(cfg.settings['seed'])
|
||||
if cfg.settings["seed"] != -1:
|
||||
random.seed(cfg.settings["seed"])
|
||||
np.random.seed(cfg.settings["seed"])
|
||||
torch.manual_seed(cfg.settings["seed"])
|
||||
torch.cuda.manual_seed(cfg.settings["seed"])
|
||||
torch.backends.cudnn.deterministic = True
|
||||
logger.info('Set seed to %d.' % cfg.settings['seed'])
|
||||
logger.info("Set seed to %d." % cfg.settings["seed"])
|
||||
|
||||
# Default device to 'cpu' if cuda is not available
|
||||
if not torch.cuda.is_available():
|
||||
device = 'cpu'
|
||||
device = "cpu"
|
||||
# Set the number of threads used for parallelizing CPU operations
|
||||
if num_threads > 0:
|
||||
torch.set_num_threads(num_threads)
|
||||
logger.info('Computation device: %s' % device)
|
||||
logger.info('Number of threads: %d' % num_threads)
|
||||
logger.info('Number of dataloader workers: %d' % n_jobs_dataloader)
|
||||
logger.info("Computation device: %s" % device)
|
||||
logger.info("Number of threads: %d" % num_threads)
|
||||
logger.info("Number of dataloader workers: %d" % n_jobs_dataloader)
|
||||
|
||||
# Load data
|
||||
dataset = load_dataset(dataset_name, data_path, normal_class, known_outlier_class, n_known_outlier_classes,
|
||||
ratio_known_normal, ratio_known_outlier, ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings['seed']))
|
||||
dataset = load_dataset(
|
||||
dataset_name,
|
||||
data_path,
|
||||
normal_class,
|
||||
known_outlier_class,
|
||||
n_known_outlier_classes,
|
||||
ratio_known_normal,
|
||||
ratio_known_outlier,
|
||||
ratio_pollution,
|
||||
random_state=np.random.RandomState(cfg.settings["seed"]),
|
||||
)
|
||||
# Log random sample of known anomaly classes if more than 1 class
|
||||
if n_known_outlier_classes > 1:
|
||||
logger.info('Known anomaly classes: %s' % (dataset.known_outlier_classes,))
|
||||
logger.info("Known anomaly classes: %s" % (dataset.known_outlier_classes,))
|
||||
|
||||
# Initialize DeepSAD model and set neural network phi
|
||||
deepSAD = DeepSAD(cfg.settings['eta'])
|
||||
deepSAD = DeepSAD(cfg.settings["eta"])
|
||||
deepSAD.set_network(net_name)
|
||||
|
||||
# If specified, load Deep SAD model (center c, network weights, and possibly autoencoder weights)
|
||||
if load_model:
|
||||
deepSAD.load_model(model_path=load_model, load_ae=True, map_location=device)
|
||||
logger.info('Loading model from %s.' % load_model)
|
||||
logger.info("Loading model from %s." % load_model)
|
||||
|
||||
logger.info('Pretraining: %s' % pretrain)
|
||||
logger.info("Pretraining: %s" % pretrain)
|
||||
if pretrain:
|
||||
# Log pretraining details
|
||||
logger.info('Pretraining optimizer: %s' % cfg.settings['ae_optimizer_name'])
|
||||
logger.info('Pretraining learning rate: %g' % cfg.settings['ae_lr'])
|
||||
logger.info('Pretraining epochs: %d' % cfg.settings['ae_n_epochs'])
|
||||
logger.info('Pretraining learning rate scheduler milestones: %s' % (cfg.settings['ae_lr_milestone'],))
|
||||
logger.info('Pretraining batch size: %d' % cfg.settings['ae_batch_size'])
|
||||
logger.info('Pretraining weight decay: %g' % cfg.settings['ae_weight_decay'])
|
||||
logger.info("Pretraining optimizer: %s" % cfg.settings["ae_optimizer_name"])
|
||||
logger.info("Pretraining learning rate: %g" % cfg.settings["ae_lr"])
|
||||
logger.info("Pretraining epochs: %d" % cfg.settings["ae_n_epochs"])
|
||||
logger.info(
|
||||
"Pretraining learning rate scheduler milestones: %s"
|
||||
% (cfg.settings["ae_lr_milestone"],)
|
||||
)
|
||||
logger.info("Pretraining batch size: %d" % cfg.settings["ae_batch_size"])
|
||||
logger.info("Pretraining weight decay: %g" % cfg.settings["ae_weight_decay"])
|
||||
|
||||
# Pretrain model on dataset (via autoencoder)
|
||||
deepSAD.pretrain(dataset,
|
||||
optimizer_name=cfg.settings['ae_optimizer_name'],
|
||||
lr=cfg.settings['ae_lr'],
|
||||
n_epochs=cfg.settings['ae_n_epochs'],
|
||||
lr_milestones=cfg.settings['ae_lr_milestone'],
|
||||
batch_size=cfg.settings['ae_batch_size'],
|
||||
weight_decay=cfg.settings['ae_weight_decay'],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
deepSAD.pretrain(
|
||||
dataset,
|
||||
optimizer_name=cfg.settings["ae_optimizer_name"],
|
||||
lr=cfg.settings["ae_lr"],
|
||||
n_epochs=cfg.settings["ae_n_epochs"],
|
||||
lr_milestones=cfg.settings["ae_lr_milestone"],
|
||||
batch_size=cfg.settings["ae_batch_size"],
|
||||
weight_decay=cfg.settings["ae_weight_decay"],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Save pretraining results
|
||||
deepSAD.save_ae_results(export_json=xp_path + '/ae_results.json')
|
||||
deepSAD.save_ae_results(export_json=xp_path + "/ae_results.json")
|
||||
|
||||
# Log training details
|
||||
logger.info('Training optimizer: %s' % cfg.settings['optimizer_name'])
|
||||
logger.info('Training learning rate: %g' % cfg.settings['lr'])
|
||||
logger.info('Training epochs: %d' % cfg.settings['n_epochs'])
|
||||
logger.info('Training learning rate scheduler milestones: %s' % (cfg.settings['lr_milestone'],))
|
||||
logger.info('Training batch size: %d' % cfg.settings['batch_size'])
|
||||
logger.info('Training weight decay: %g' % cfg.settings['weight_decay'])
|
||||
logger.info("Training optimizer: %s" % cfg.settings["optimizer_name"])
|
||||
logger.info("Training learning rate: %g" % cfg.settings["lr"])
|
||||
logger.info("Training epochs: %d" % cfg.settings["n_epochs"])
|
||||
logger.info(
|
||||
"Training learning rate scheduler milestones: %s"
|
||||
% (cfg.settings["lr_milestone"],)
|
||||
)
|
||||
logger.info("Training batch size: %d" % cfg.settings["batch_size"])
|
||||
logger.info("Training weight decay: %g" % cfg.settings["weight_decay"])
|
||||
|
||||
# Train model on dataset
|
||||
deepSAD.train(dataset,
|
||||
optimizer_name=cfg.settings['optimizer_name'],
|
||||
lr=cfg.settings['lr'],
|
||||
n_epochs=cfg.settings['n_epochs'],
|
||||
lr_milestones=cfg.settings['lr_milestone'],
|
||||
batch_size=cfg.settings['batch_size'],
|
||||
weight_decay=cfg.settings['weight_decay'],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader)
|
||||
deepSAD.train(
|
||||
dataset,
|
||||
optimizer_name=cfg.settings["optimizer_name"],
|
||||
lr=cfg.settings["lr"],
|
||||
n_epochs=cfg.settings["n_epochs"],
|
||||
lr_milestones=cfg.settings["lr_milestone"],
|
||||
batch_size=cfg.settings["batch_size"],
|
||||
weight_decay=cfg.settings["weight_decay"],
|
||||
device=device,
|
||||
n_jobs_dataloader=n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Test model
|
||||
deepSAD.test(dataset, device=device, n_jobs_dataloader=n_jobs_dataloader)
|
||||
|
||||
# Save results, model, and configuration
|
||||
deepSAD.save_results(export_json=xp_path + '/results.json')
|
||||
deepSAD.save_model(export_model=xp_path + '/model.tar')
|
||||
cfg.save_config(export_json=xp_path + '/config.json')
|
||||
deepSAD.save_results(export_json=xp_path + "/results.json")
|
||||
deepSAD.save_model(export_model=xp_path + "/model.tar")
|
||||
cfg.save_config(export_json=xp_path + "/config.json")
|
||||
|
||||
# Plot most anomalous and most normal test samples
|
||||
indices, labels, scores = zip(*deepSAD.results['test_scores'])
|
||||
indices, labels, scores = zip(*deepSAD.results["test_scores"])
|
||||
indices, labels, scores = np.array(indices), np.array(labels), np.array(scores)
|
||||
idx_all_sorted = indices[np.argsort(scores)] # from lowest to highest score
|
||||
idx_normal_sorted = indices[labels == 0][np.argsort(scores[labels == 0])] # from lowest to highest score
|
||||
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_high = dataset.test_set.data[idx_all_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(1)
|
||||
X_normal_high = dataset.test_set.data[idx_normal_sorted[-32:], ...].unsqueeze(1)
|
||||
X_normal_low = dataset.test_set.data[idx_normal_sorted[:32], ...].unsqueeze(
|
||||
1
|
||||
)
|
||||
X_normal_high = dataset.test_set.data[
|
||||
idx_normal_sorted[-32:], ...
|
||||
].unsqueeze(1)
|
||||
|
||||
if dataset_name == 'cifar10':
|
||||
X_all_low = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[:32], ...], (0,3,1,2)))
|
||||
X_all_high = torch.tensor(np.transpose(dataset.test_set.data[idx_all_sorted[-32:], ...], (0,3,1,2)))
|
||||
X_normal_low = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[:32], ...], (0,3,1,2)))
|
||||
X_normal_high = torch.tensor(np.transpose(dataset.test_set.data[idx_normal_sorted[-32:], ...], (0,3,1,2)))
|
||||
if dataset_name == "cifar10":
|
||||
X_all_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_all_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_all_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_low = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[:32], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
X_normal_high = torch.tensor(
|
||||
np.transpose(
|
||||
dataset.test_set.data[idx_normal_sorted[-32:], ...], (0, 3, 1, 2)
|
||||
)
|
||||
)
|
||||
|
||||
plot_images_grid(X_all_low, export_img=xp_path + '/all_low', padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + '/all_high', padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + '/normals_low', padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + '/normals_high', padding=2)
|
||||
plot_images_grid(X_all_low, export_img=xp_path + "/all_low", padding=2)
|
||||
plot_images_grid(X_all_high, export_img=xp_path + "/all_high", padding=2)
|
||||
plot_images_grid(X_normal_low, export_img=xp_path + "/normals_low", padding=2)
|
||||
plot_images_grid(X_normal_high, export_img=xp_path + "/normals_high", padding=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
from .main import build_network, build_autoencoder
|
||||
from .mnist_LeNet import MNIST_LeNet, MNIST_LeNet_Decoder, MNIST_LeNet_Autoencoder
|
||||
from .fmnist_LeNet import FashionMNIST_LeNet, FashionMNIST_LeNet_Decoder, FashionMNIST_LeNet_Autoencoder
|
||||
from .cifar10_LeNet import CIFAR10_LeNet, CIFAR10_LeNet_Decoder, CIFAR10_LeNet_Autoencoder
|
||||
from .fmnist_LeNet import (
|
||||
FashionMNIST_LeNet,
|
||||
FashionMNIST_LeNet_Decoder,
|
||||
FashionMNIST_LeNet_Autoencoder,
|
||||
)
|
||||
from .cifar10_LeNet import (
|
||||
CIFAR10_LeNet,
|
||||
CIFAR10_LeNet_Decoder,
|
||||
CIFAR10_LeNet_Autoencoder,
|
||||
)
|
||||
from .mlp import MLP, MLP_Decoder, MLP_Autoencoder
|
||||
from .layers.stochastic import GaussianSample
|
||||
from .layers.standard import Standardize
|
||||
from .inference.distributions import log_standard_gaussian, log_gaussian, log_standard_categorical
|
||||
from .inference.distributions import (
|
||||
log_standard_gaussian,
|
||||
log_gaussian,
|
||||
log_standard_categorical,
|
||||
)
|
||||
from .vae import VariationalAutoencoder, Encoder, Decoder
|
||||
from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel
|
||||
|
||||
@@ -41,17 +41,27 @@ class CIFAR10_LeNet_Decoder(BaseNet):
|
||||
|
||||
self.rep_dim = rep_dim
|
||||
|
||||
self.deconv1 = nn.ConvTranspose2d(int(self.rep_dim / (4 * 4)), 128, 5, bias=False, padding=2)
|
||||
nn.init.xavier_uniform_(self.deconv1.weight, gain=nn.init.calculate_gain('leaky_relu'))
|
||||
self.deconv1 = nn.ConvTranspose2d(
|
||||
int(self.rep_dim / (4 * 4)), 128, 5, bias=False, padding=2
|
||||
)
|
||||
nn.init.xavier_uniform_(
|
||||
self.deconv1.weight, gain=nn.init.calculate_gain("leaky_relu")
|
||||
)
|
||||
self.bn2d4 = nn.BatchNorm2d(128, eps=1e-04, affine=False)
|
||||
self.deconv2 = nn.ConvTranspose2d(128, 64, 5, bias=False, padding=2)
|
||||
nn.init.xavier_uniform_(self.deconv2.weight, gain=nn.init.calculate_gain('leaky_relu'))
|
||||
nn.init.xavier_uniform_(
|
||||
self.deconv2.weight, gain=nn.init.calculate_gain("leaky_relu")
|
||||
)
|
||||
self.bn2d5 = nn.BatchNorm2d(64, eps=1e-04, affine=False)
|
||||
self.deconv3 = nn.ConvTranspose2d(64, 32, 5, bias=False, padding=2)
|
||||
nn.init.xavier_uniform_(self.deconv3.weight, gain=nn.init.calculate_gain('leaky_relu'))
|
||||
nn.init.xavier_uniform_(
|
||||
self.deconv3.weight, gain=nn.init.calculate_gain("leaky_relu")
|
||||
)
|
||||
self.bn2d6 = nn.BatchNorm2d(32, eps=1e-04, affine=False)
|
||||
self.deconv4 = nn.ConvTranspose2d(32, 3, 5, bias=False, padding=2)
|
||||
nn.init.xavier_uniform_(self.deconv4.weight, gain=nn.init.calculate_gain('leaky_relu'))
|
||||
nn.init.xavier_uniform_(
|
||||
self.deconv4.weight, gain=nn.init.calculate_gain("leaky_relu")
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = x.view(int(x.size(0)), int(self.rep_dim / (4 * 4)), 4, 4)
|
||||
|
||||
@@ -97,7 +97,9 @@ class StackedDeepGenerativeModel(DeepGenerativeModel):
|
||||
:param features: a pre-trained M1 model of class 'VariationalAutoencoder' trained on the same dataset.
|
||||
"""
|
||||
[x_dim, y_dim, z_dim, h_dim] = dims
|
||||
super(StackedDeepGenerativeModel, self).__init__([features.z_dim, y_dim, z_dim, h_dim])
|
||||
super(StackedDeepGenerativeModel, self).__init__(
|
||||
[features.z_dim, y_dim, z_dim, h_dim]
|
||||
)
|
||||
|
||||
# Be sure to reconstruct with the same dimensions
|
||||
in_features = self.decoder.reconstruction.in_features
|
||||
|
||||
@@ -11,7 +11,7 @@ def log_standard_gaussian(x):
|
||||
:param x: point to evaluate
|
||||
:return: log N(x|0,I)
|
||||
"""
|
||||
return torch.sum(-0.5 * math.log(2 * math.pi) - x ** 2 / 2, dim=-1)
|
||||
return torch.sum(-0.5 * math.log(2 * math.pi) - x**2 / 2, dim=-1)
|
||||
|
||||
|
||||
def log_gaussian(x, mu, log_var):
|
||||
@@ -23,7 +23,11 @@ def log_gaussian(x, mu, log_var):
|
||||
:param log_var: log variance
|
||||
:return: log N(x|µ,σI)
|
||||
"""
|
||||
log_pdf = -0.5 * math.log(2 * math.pi) - log_var / 2 - (x - mu)**2 / (2 * torch.exp(log_var))
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ class Standardize(Module):
|
||||
mu: the learnable translation parameter μ.
|
||||
std: the learnable scale parameter σ.
|
||||
"""
|
||||
__constants__ = ['mu']
|
||||
|
||||
__constants__ = ["mu"]
|
||||
|
||||
def __init__(self, in_features, bias=True, eps=1e-6):
|
||||
super(Standardize, self).__init__()
|
||||
@@ -32,7 +33,7 @@ class Standardize(Module):
|
||||
if bias:
|
||||
self.mu = Parameter(torch.Tensor(in_features))
|
||||
else:
|
||||
self.register_parameter('mu', None)
|
||||
self.register_parameter("mu", None)
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
@@ -47,6 +48,6 @@ class Standardize(Module):
|
||||
return x
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@@ -9,78 +9,106 @@ from .dgm import DeepGenerativeModel, StackedDeepGenerativeModel
|
||||
def build_network(net_name, ae_net=None):
|
||||
"""Builds the neural network."""
|
||||
|
||||
implemented_networks = ('mnist_LeNet', 'mnist_DGM_M2', 'mnist_DGM_M1M2',
|
||||
'fmnist_LeNet', 'fmnist_DGM_M2', 'fmnist_DGM_M1M2',
|
||||
'cifar10_LeNet', 'cifar10_DGM_M2', 'cifar10_DGM_M1M2',
|
||||
'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp',
|
||||
'thyroid_mlp',
|
||||
'arrhythmia_DGM_M2', 'cardio_DGM_M2', 'satellite_DGM_M2', 'satimage-2_DGM_M2',
|
||||
'shuttle_DGM_M2', 'thyroid_DGM_M2')
|
||||
implemented_networks = (
|
||||
"mnist_LeNet",
|
||||
"mnist_DGM_M2",
|
||||
"mnist_DGM_M1M2",
|
||||
"fmnist_LeNet",
|
||||
"fmnist_DGM_M2",
|
||||
"fmnist_DGM_M1M2",
|
||||
"cifar10_LeNet",
|
||||
"cifar10_DGM_M2",
|
||||
"cifar10_DGM_M1M2",
|
||||
"arrhythmia_mlp",
|
||||
"cardio_mlp",
|
||||
"satellite_mlp",
|
||||
"satimage-2_mlp",
|
||||
"shuttle_mlp",
|
||||
"thyroid_mlp",
|
||||
"arrhythmia_DGM_M2",
|
||||
"cardio_DGM_M2",
|
||||
"satellite_DGM_M2",
|
||||
"satimage-2_DGM_M2",
|
||||
"shuttle_DGM_M2",
|
||||
"thyroid_DGM_M2",
|
||||
)
|
||||
assert net_name in implemented_networks
|
||||
|
||||
net = None
|
||||
|
||||
if net_name == 'mnist_LeNet':
|
||||
if net_name == "mnist_LeNet":
|
||||
net = MNIST_LeNet()
|
||||
|
||||
if net_name == 'mnist_DGM_M2':
|
||||
net = DeepGenerativeModel([1*28*28, 2, 32, [128, 64]], classifier_net=MNIST_LeNet)
|
||||
if net_name == "mnist_DGM_M2":
|
||||
net = DeepGenerativeModel(
|
||||
[1 * 28 * 28, 2, 32, [128, 64]], classifier_net=MNIST_LeNet
|
||||
)
|
||||
|
||||
if net_name == 'mnist_DGM_M1M2':
|
||||
net = StackedDeepGenerativeModel([1*28*28, 2, 32, [128, 64]], features=ae_net)
|
||||
if net_name == "mnist_DGM_M1M2":
|
||||
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()
|
||||
|
||||
if net_name == 'fmnist_DGM_M2':
|
||||
net = DeepGenerativeModel([1*28*28, 2, 64, [256, 128]], classifier_net=FashionMNIST_LeNet)
|
||||
if net_name == "fmnist_DGM_M2":
|
||||
net = DeepGenerativeModel(
|
||||
[1 * 28 * 28, 2, 64, [256, 128]], classifier_net=FashionMNIST_LeNet
|
||||
)
|
||||
|
||||
if net_name == 'fmnist_DGM_M1M2':
|
||||
net = StackedDeepGenerativeModel([1*28*28, 2, 64, [256, 128]], features=ae_net)
|
||||
if net_name == "fmnist_DGM_M1M2":
|
||||
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()
|
||||
|
||||
if net_name == 'cifar10_DGM_M2':
|
||||
net = DeepGenerativeModel([3*32*32, 2, 128, [512, 256]], classifier_net=CIFAR10_LeNet)
|
||||
if net_name == "cifar10_DGM_M2":
|
||||
net = DeepGenerativeModel(
|
||||
[3 * 32 * 32, 2, 128, [512, 256]], classifier_net=CIFAR10_LeNet
|
||||
)
|
||||
|
||||
if net_name == 'cifar10_DGM_M1M2':
|
||||
net = StackedDeepGenerativeModel([3*32*32, 2, 128, [512, 256]], features=ae_net)
|
||||
if net_name == "cifar10_DGM_M1M2":
|
||||
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)
|
||||
|
||||
if net_name == 'cardio_mlp':
|
||||
if net_name == "cardio_mlp":
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
if net_name == 'shuttle_mlp':
|
||||
if net_name == "shuttle_mlp":
|
||||
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)
|
||||
|
||||
if net_name == 'arrhythmia_DGM_M2':
|
||||
if net_name == "arrhythmia_DGM_M2":
|
||||
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]])
|
||||
|
||||
if net_name == 'satellite_DGM_M2':
|
||||
if net_name == "satellite_DGM_M2":
|
||||
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]])
|
||||
|
||||
if net_name == 'shuttle_DGM_M2':
|
||||
if net_name == "shuttle_DGM_M2":
|
||||
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]])
|
||||
|
||||
return net
|
||||
@@ -89,50 +117,59 @@ def build_network(net_name, ae_net=None):
|
||||
def build_autoencoder(net_name):
|
||||
"""Builds the corresponding autoencoder network."""
|
||||
|
||||
implemented_networks = ('mnist_LeNet', 'mnist_DGM_M1M2',
|
||||
'fmnist_LeNet', 'fmnist_DGM_M1M2',
|
||||
'cifar10_LeNet', 'cifar10_DGM_M1M2',
|
||||
'arrhythmia_mlp', 'cardio_mlp', 'satellite_mlp', 'satimage-2_mlp', 'shuttle_mlp',
|
||||
'thyroid_mlp')
|
||||
implemented_networks = (
|
||||
"mnist_LeNet",
|
||||
"mnist_DGM_M1M2",
|
||||
"fmnist_LeNet",
|
||||
"fmnist_DGM_M1M2",
|
||||
"cifar10_LeNet",
|
||||
"cifar10_DGM_M1M2",
|
||||
"arrhythmia_mlp",
|
||||
"cardio_mlp",
|
||||
"satellite_mlp",
|
||||
"satimage-2_mlp",
|
||||
"shuttle_mlp",
|
||||
"thyroid_mlp",
|
||||
)
|
||||
|
||||
assert net_name in implemented_networks
|
||||
|
||||
ae_net = None
|
||||
|
||||
if net_name == 'mnist_LeNet':
|
||||
if net_name == "mnist_LeNet":
|
||||
ae_net = MNIST_LeNet_Autoencoder()
|
||||
|
||||
if net_name == 'mnist_DGM_M1M2':
|
||||
ae_net = VariationalAutoencoder([1*28*28, 32, [128, 64]])
|
||||
if net_name == "mnist_DGM_M1M2":
|
||||
ae_net = VariationalAutoencoder([1 * 28 * 28, 32, [128, 64]])
|
||||
|
||||
if net_name == 'fmnist_LeNet':
|
||||
if net_name == "fmnist_LeNet":
|
||||
ae_net = FashionMNIST_LeNet_Autoencoder()
|
||||
|
||||
if net_name == 'fmnist_DGM_M1M2':
|
||||
ae_net = VariationalAutoencoder([1*28*28, 64, [256, 128]])
|
||||
if net_name == "fmnist_DGM_M1M2":
|
||||
ae_net = VariationalAutoencoder([1 * 28 * 28, 64, [256, 128]])
|
||||
|
||||
if net_name == 'cifar10_LeNet':
|
||||
if net_name == "cifar10_LeNet":
|
||||
ae_net = CIFAR10_LeNet_Autoencoder()
|
||||
|
||||
if net_name == 'cifar10_DGM_M1M2':
|
||||
ae_net = VariationalAutoencoder([3*32*32, 128, [512, 256]])
|
||||
if net_name == "cifar10_DGM_M1M2":
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
return ae_net
|
||||
|
||||
@@ -12,7 +12,10 @@ class MLP(BaseNet):
|
||||
self.rep_dim = rep_dim
|
||||
|
||||
neurons = [x_dim, *h_dims]
|
||||
layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))]
|
||||
layers = [
|
||||
Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias)
|
||||
for i in range(1, len(neurons))
|
||||
]
|
||||
|
||||
self.hidden = nn.ModuleList(layers)
|
||||
self.code = nn.Linear(h_dims[-1], rep_dim, bias=bias)
|
||||
@@ -32,7 +35,10 @@ class MLP_Decoder(BaseNet):
|
||||
self.rep_dim = rep_dim
|
||||
|
||||
neurons = [rep_dim, *h_dims]
|
||||
layers = [Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias) for i in range(1, len(neurons))]
|
||||
layers = [
|
||||
Linear_BN_leakyReLU(neurons[i - 1], neurons[i], bias=bias)
|
||||
for i in range(1, len(neurons))
|
||||
]
|
||||
|
||||
self.hidden = nn.ModuleList(layers)
|
||||
self.reconstruction = nn.Linear(h_dims[-1], x_dim, bias=bias)
|
||||
|
||||
@@ -22,7 +22,9 @@ class Encoder(nn.Module):
|
||||
|
||||
[x_dim, h_dim, z_dim] = dims
|
||||
neurons = [x_dim, *h_dim]
|
||||
linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))]
|
||||
linear_layers = [
|
||||
nn.Linear(neurons[i - 1], neurons[i]) for i in range(1, len(neurons))
|
||||
]
|
||||
|
||||
self.hidden = nn.ModuleList(linear_layers)
|
||||
self.sample = sample_layer(h_dim[-1], z_dim)
|
||||
@@ -48,7 +50,9 @@ class Decoder(nn.Module):
|
||||
|
||||
[z_dim, h_dim, x_dim] = dims
|
||||
neurons = [z_dim, *h_dim]
|
||||
linear_layers = [nn.Linear(neurons[i-1], neurons[i]) for i in range(1, len(neurons))]
|
||||
linear_layers = [
|
||||
nn.Linear(neurons[i - 1], neurons[i]) for i in range(1, len(neurons))
|
||||
]
|
||||
|
||||
self.hidden = nn.ModuleList(linear_layers)
|
||||
self.reconstruction = nn.Linear(h_dim[-1], x_dim)
|
||||
|
||||
@@ -13,11 +13,29 @@ import numpy as np
|
||||
|
||||
class DeepSADTrainer(BaseTrainer):
|
||||
|
||||
def __init__(self, c, eta: float, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device,
|
||||
n_jobs_dataloader)
|
||||
def __init__(
|
||||
self,
|
||||
c,
|
||||
eta: float,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 150,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
super().__init__(
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestones,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
device,
|
||||
n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Deep SAD parameters
|
||||
self.c = torch.tensor(c, device=self.device) if c is not None else None
|
||||
@@ -36,39 +54,50 @@ class DeepSADTrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
net = net.to(self.device)
|
||||
|
||||
# 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
|
||||
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)
|
||||
if self.c is None:
|
||||
logger.info('Initializing center c...')
|
||||
logger.info("Initializing center c...")
|
||||
self.c = self.init_center_c(train_loader, net)
|
||||
logger.info('Center c initialized.')
|
||||
logger.info("Center c initialized.")
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
start_time = time.time()
|
||||
net.train()
|
||||
for epoch in range(self.n_epochs):
|
||||
|
||||
scheduler.step()
|
||||
if epoch in self.lr_milestones:
|
||||
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0]))
|
||||
logger.info(
|
||||
" LR scheduler: new learning rate is %g"
|
||||
% float(scheduler.get_lr()[0])
|
||||
)
|
||||
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
epoch_start_time = time.time()
|
||||
for data in train_loader:
|
||||
inputs, _, semi_targets, _ = data
|
||||
inputs, semi_targets = inputs.to(self.device), semi_targets.to(self.device)
|
||||
inputs, semi_targets = inputs.to(self.device), semi_targets.to(
|
||||
self.device
|
||||
)
|
||||
|
||||
# Zero the network parameter gradients
|
||||
optimizer.zero_grad()
|
||||
@@ -76,7 +105,11 @@ class DeepSADTrainer(BaseTrainer):
|
||||
# Update network parameters via backpropagation: forward + backward + optimize
|
||||
outputs = net(inputs)
|
||||
dist = torch.sum((outputs - self.c) ** 2, dim=1)
|
||||
losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float()))
|
||||
losses = torch.where(
|
||||
semi_targets == 0,
|
||||
dist,
|
||||
self.eta * ((dist + self.eps) ** semi_targets.float()),
|
||||
)
|
||||
loss = torch.mean(losses)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
@@ -86,12 +119,14 @@ class DeepSADTrainer(BaseTrainer):
|
||||
|
||||
# log epoch statistics
|
||||
epoch_train_time = time.time() - epoch_start_time
|
||||
logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s '
|
||||
f'| Train Loss: {epoch_loss / n_batches:.6f} |')
|
||||
logger.info(
|
||||
f"| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s "
|
||||
f"| Train Loss: {epoch_loss / n_batches:.6f} |"
|
||||
)
|
||||
|
||||
self.train_time = time.time() - start_time
|
||||
logger.info('Training Time: {:.3f}s'.format(self.train_time))
|
||||
logger.info('Finished training.')
|
||||
logger.info("Training Time: {:.3f}s".format(self.train_time))
|
||||
logger.info("Finished training.")
|
||||
|
||||
return net
|
||||
|
||||
@@ -99,13 +134,15 @@ class DeepSADTrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
net = net.to(self.device)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
start_time = time.time()
|
||||
@@ -122,14 +159,22 @@ class DeepSADTrainer(BaseTrainer):
|
||||
|
||||
outputs = net(inputs)
|
||||
dist = torch.sum((outputs - self.c) ** 2, dim=1)
|
||||
losses = torch.where(semi_targets == 0, dist, self.eta * ((dist + self.eps) ** semi_targets.float()))
|
||||
losses = torch.where(
|
||||
semi_targets == 0,
|
||||
dist,
|
||||
self.eta * ((dist + self.eps) ** semi_targets.float()),
|
||||
)
|
||||
loss = torch.mean(losses)
|
||||
scores = dist
|
||||
|
||||
# Save triples of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist()))
|
||||
idx_label_score += list(
|
||||
zip(
|
||||
idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist(),
|
||||
)
|
||||
)
|
||||
|
||||
epoch_loss += loss.item()
|
||||
n_batches += 1
|
||||
@@ -144,10 +189,10 @@ class DeepSADTrainer(BaseTrainer):
|
||||
self.test_auc = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches))
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.test_time))
|
||||
logger.info('Finished testing.')
|
||||
logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
|
||||
logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
|
||||
logger.info("Test Time: {:.3f}s".format(self.test_time))
|
||||
logger.info("Finished testing.")
|
||||
|
||||
def init_center_c(self, train_loader: DataLoader, net: BaseNet, eps=0.1):
|
||||
"""Initialize hypersphere center c as the mean from an initial forward pass on the data."""
|
||||
|
||||
@@ -14,11 +14,28 @@ import numpy as np
|
||||
|
||||
class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
|
||||
def __init__(self, alpha: float = 0.1, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150,
|
||||
lr_milestones: tuple = (), batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda',
|
||||
n_jobs_dataloader: int = 0):
|
||||
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device,
|
||||
n_jobs_dataloader)
|
||||
def __init__(
|
||||
self,
|
||||
alpha: float = 0.1,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 150,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
super().__init__(
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestones,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
device,
|
||||
n_jobs_dataloader,
|
||||
)
|
||||
|
||||
self.alpha = alpha
|
||||
|
||||
@@ -32,7 +49,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
net = net.to(self.device)
|
||||
@@ -42,20 +61,27 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler)
|
||||
|
||||
# Set optimizer (Adam optimizer for now)
|
||||
optimizer = optim.Adam(net.parameters(), lr=self.lr, weight_decay=self.weight_decay)
|
||||
optimizer = optim.Adam(
|
||||
net.parameters(), lr=self.lr, weight_decay=self.weight_decay
|
||||
)
|
||||
|
||||
# Set learning rate scheduler
|
||||
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1)
|
||||
scheduler = optim.lr_scheduler.MultiStepLR(
|
||||
optimizer, milestones=self.lr_milestones, gamma=0.1
|
||||
)
|
||||
|
||||
# Training
|
||||
logger.info('Starting training...')
|
||||
logger.info("Starting training...")
|
||||
start_time = time.time()
|
||||
net.train()
|
||||
for epoch in range(self.n_epochs):
|
||||
|
||||
scheduler.step()
|
||||
if epoch in self.lr_milestones:
|
||||
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0]))
|
||||
logger.info(
|
||||
" LR scheduler: new learning rate is %g"
|
||||
% float(scheduler.get_lr()[0])
|
||||
)
|
||||
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
@@ -73,7 +99,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
u = inputs[semi_targets == 0]
|
||||
y = labels[semi_targets != 0]
|
||||
if y.nelement() > 1:
|
||||
y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier
|
||||
y_onehot = torch.Tensor(y.size(0), 2).to(
|
||||
self.device
|
||||
) # two labels: 0: normal, 1: outlier
|
||||
y_onehot.zero_()
|
||||
y_onehot.scatter_(1, y.view(-1, 1), 1)
|
||||
|
||||
@@ -94,7 +122,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
# Add auxiliary classification loss q(y|x)
|
||||
logits = net.classify(x)
|
||||
eps = 1e-8
|
||||
classication_loss = torch.sum(y_onehot * torch.log(logits + eps), dim=1).mean()
|
||||
classication_loss = torch.sum(
|
||||
y_onehot * torch.log(logits + eps), dim=1
|
||||
).mean()
|
||||
|
||||
# Overall loss
|
||||
loss = L - self.alpha * classication_loss + U # J_alpha
|
||||
@@ -107,12 +137,14 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
|
||||
# log epoch statistics
|
||||
epoch_train_time = time.time() - epoch_start_time
|
||||
logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s '
|
||||
f'| Train Loss: {epoch_loss / n_batches:.6f} |')
|
||||
logger.info(
|
||||
f"| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s "
|
||||
f"| Train Loss: {epoch_loss / n_batches:.6f} |"
|
||||
)
|
||||
|
||||
self.train_time = time.time() - start_time
|
||||
logger.info('Training Time: {:.3f}s'.format(self.train_time))
|
||||
logger.info('Finished training.')
|
||||
logger.info("Training Time: {:.3f}s".format(self.train_time))
|
||||
logger.info("Finished training.")
|
||||
|
||||
return net
|
||||
|
||||
@@ -120,7 +152,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
net = net.to(self.device)
|
||||
@@ -130,7 +164,7 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
elbo = SVI(net, likelihood=binary_cross_entropy, sampler=sampler)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
start_time = time.time()
|
||||
@@ -147,7 +181,9 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
inputs = inputs.view(inputs.size(0), -1)
|
||||
u = inputs
|
||||
y = labels
|
||||
y_onehot = torch.Tensor(y.size(0), 2).to(self.device) # two labels: 0: normal, 1: outlier
|
||||
y_onehot = torch.Tensor(y.size(0), 2).to(
|
||||
self.device
|
||||
) # two labels: 0: normal, 1: outlier
|
||||
y_onehot.zero_()
|
||||
y_onehot.scatter_(1, y.view(-1, 1), 1)
|
||||
|
||||
@@ -157,17 +193,25 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
|
||||
logits = net.classify(u)
|
||||
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
|
||||
|
||||
# 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
|
||||
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist()))
|
||||
idx_label_score += list(
|
||||
zip(
|
||||
idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist(),
|
||||
)
|
||||
)
|
||||
|
||||
epoch_loss += loss.item()
|
||||
n_batches += 1
|
||||
@@ -182,7 +226,7 @@ class SemiDeepGenerativeTrainer(BaseTrainer):
|
||||
self.test_auc = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches))
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.test_time))
|
||||
logger.info('Finished testing.')
|
||||
logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
|
||||
logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
|
||||
logger.info("Test Time: {:.3f}s".format(self.test_time))
|
||||
logger.info("Finished testing.")
|
||||
|
||||
@@ -13,10 +13,27 @@ import numpy as np
|
||||
|
||||
class AETrainer(BaseTrainer):
|
||||
|
||||
def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (),
|
||||
batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0):
|
||||
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device,
|
||||
n_jobs_dataloader)
|
||||
def __init__(
|
||||
self,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 150,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
super().__init__(
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestones,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
device,
|
||||
n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Results
|
||||
self.train_time = None
|
||||
@@ -27,30 +44,39 @@ class AETrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
criterion = nn.MSELoss(reduction='none')
|
||||
criterion = nn.MSELoss(reduction="none")
|
||||
|
||||
# Set device
|
||||
ae_net = ae_net.to(self.device)
|
||||
criterion = criterion.to(self.device)
|
||||
|
||||
# Set optimizer (Adam optimizer for now)
|
||||
optimizer = optim.Adam(ae_net.parameters(), lr=self.lr, weight_decay=self.weight_decay)
|
||||
optimizer = optim.Adam(
|
||||
ae_net.parameters(), lr=self.lr, weight_decay=self.weight_decay
|
||||
)
|
||||
|
||||
# Set learning rate scheduler
|
||||
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=self.lr_milestones, gamma=0.1)
|
||||
scheduler = optim.lr_scheduler.MultiStepLR(
|
||||
optimizer, milestones=self.lr_milestones, gamma=0.1
|
||||
)
|
||||
|
||||
# Training
|
||||
logger.info('Starting pretraining...')
|
||||
logger.info("Starting pretraining...")
|
||||
start_time = time.time()
|
||||
ae_net.train()
|
||||
for epoch in range(self.n_epochs):
|
||||
|
||||
scheduler.step()
|
||||
if epoch in self.lr_milestones:
|
||||
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0]))
|
||||
logger.info(
|
||||
" LR scheduler: new learning rate is %g"
|
||||
% float(scheduler.get_lr()[0])
|
||||
)
|
||||
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
@@ -74,12 +100,14 @@ class AETrainer(BaseTrainer):
|
||||
|
||||
# log epoch statistics
|
||||
epoch_train_time = time.time() - epoch_start_time
|
||||
logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s '
|
||||
f'| Train Loss: {epoch_loss / n_batches:.6f} |')
|
||||
logger.info(
|
||||
f"| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s "
|
||||
f"| Train Loss: {epoch_loss / n_batches:.6f} |"
|
||||
)
|
||||
|
||||
self.train_time = time.time() - start_time
|
||||
logger.info('Pretraining Time: {:.3f}s'.format(self.train_time))
|
||||
logger.info('Finished pretraining.')
|
||||
logger.info("Pretraining Time: {:.3f}s".format(self.train_time))
|
||||
logger.info("Finished pretraining.")
|
||||
|
||||
return ae_net
|
||||
|
||||
@@ -87,17 +115,19 @@ class AETrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
criterion = nn.MSELoss(reduction='none')
|
||||
criterion = nn.MSELoss(reduction="none")
|
||||
|
||||
# Set device for network
|
||||
ae_net = ae_net.to(self.device)
|
||||
criterion = criterion.to(self.device)
|
||||
|
||||
# Testing
|
||||
logger.info('Testing autoencoder...')
|
||||
logger.info("Testing autoencoder...")
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
start_time = time.time()
|
||||
@@ -106,16 +136,24 @@ class AETrainer(BaseTrainer):
|
||||
with torch.no_grad():
|
||||
for data in test_loader:
|
||||
inputs, labels, _, idx = data
|
||||
inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device)
|
||||
inputs, labels, idx = (
|
||||
inputs.to(self.device),
|
||||
labels.to(self.device),
|
||||
idx.to(self.device),
|
||||
)
|
||||
|
||||
rec = ae_net(inputs)
|
||||
rec_loss = criterion(rec, inputs)
|
||||
scores = torch.mean(rec_loss, dim=tuple(range(1, rec.dim())))
|
||||
|
||||
# Save triple of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist()))
|
||||
idx_label_score += list(
|
||||
zip(
|
||||
idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist(),
|
||||
)
|
||||
)
|
||||
|
||||
loss = torch.mean(rec_loss)
|
||||
epoch_loss += loss.item()
|
||||
@@ -130,7 +168,7 @@ class AETrainer(BaseTrainer):
|
||||
self.test_auc = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches))
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.test_time))
|
||||
logger.info('Finished testing autoencoder.')
|
||||
logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
|
||||
logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
|
||||
logger.info("Test Time: {:.3f}s".format(self.test_time))
|
||||
logger.info("Finished testing autoencoder.")
|
||||
|
||||
@@ -13,10 +13,27 @@ import numpy as np
|
||||
|
||||
class VAETrainer(BaseTrainer):
|
||||
|
||||
def __init__(self, optimizer_name: str = 'adam', lr: float = 0.001, n_epochs: int = 150, lr_milestones: tuple = (),
|
||||
batch_size: int = 128, weight_decay: float = 1e-6, device: str = 'cuda', n_jobs_dataloader: int = 0):
|
||||
super().__init__(optimizer_name, lr, n_epochs, lr_milestones, batch_size, weight_decay, device,
|
||||
n_jobs_dataloader)
|
||||
def __init__(
|
||||
self,
|
||||
optimizer_name: str = "adam",
|
||||
lr: float = 0.001,
|
||||
n_epochs: int = 150,
|
||||
lr_milestones: tuple = (),
|
||||
batch_size: int = 128,
|
||||
weight_decay: float = 1e-6,
|
||||
device: str = "cuda",
|
||||
n_jobs_dataloader: int = 0,
|
||||
):
|
||||
super().__init__(
|
||||
optimizer_name,
|
||||
lr,
|
||||
n_epochs,
|
||||
lr_milestones,
|
||||
batch_size,
|
||||
weight_decay,
|
||||
device,
|
||||
n_jobs_dataloader,
|
||||
)
|
||||
|
||||
# Results
|
||||
self.train_time = None
|
||||
@@ -27,26 +44,35 @@ class VAETrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
vae = vae.to(self.device)
|
||||
|
||||
# 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
|
||||
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
|
||||
logger.info('Starting pretraining...')
|
||||
logger.info("Starting pretraining...")
|
||||
start_time = time.time()
|
||||
vae.train()
|
||||
for epoch in range(self.n_epochs):
|
||||
|
||||
scheduler.step()
|
||||
if epoch in self.lr_milestones:
|
||||
logger.info(' LR scheduler: new learning rate is %g' % float(scheduler.get_lr()[0]))
|
||||
logger.info(
|
||||
" LR scheduler: new learning rate is %g"
|
||||
% float(scheduler.get_lr()[0])
|
||||
)
|
||||
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
@@ -76,12 +102,14 @@ class VAETrainer(BaseTrainer):
|
||||
|
||||
# log epoch statistics
|
||||
epoch_train_time = time.time() - epoch_start_time
|
||||
logger.info(f'| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s '
|
||||
f'| Train Loss: {epoch_loss / n_batches:.6f} |')
|
||||
logger.info(
|
||||
f"| Epoch: {epoch + 1:03}/{self.n_epochs:03} | Train Time: {epoch_train_time:.3f}s "
|
||||
f"| Train Loss: {epoch_loss / n_batches:.6f} |"
|
||||
)
|
||||
|
||||
self.train_time = time.time() - start_time
|
||||
logger.info('Pretraining Time: {:.3f}s'.format(self.train_time))
|
||||
logger.info('Finished pretraining.')
|
||||
logger.info("Pretraining Time: {:.3f}s".format(self.train_time))
|
||||
logger.info("Finished pretraining.")
|
||||
|
||||
return vae
|
||||
|
||||
@@ -89,13 +117,15 @@ class VAETrainer(BaseTrainer):
|
||||
logger = logging.getLogger()
|
||||
|
||||
# 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
|
||||
vae = vae.to(self.device)
|
||||
|
||||
# Testing
|
||||
logger.info('Starting testing...')
|
||||
logger.info("Starting testing...")
|
||||
epoch_loss = 0.0
|
||||
n_batches = 0
|
||||
start_time = time.time()
|
||||
@@ -104,7 +134,11 @@ class VAETrainer(BaseTrainer):
|
||||
with torch.no_grad():
|
||||
for data in test_loader:
|
||||
inputs, labels, _, idx = data
|
||||
inputs, labels, idx = inputs.to(self.device), labels.to(self.device), idx.to(self.device)
|
||||
inputs, labels, idx = (
|
||||
inputs.to(self.device),
|
||||
labels.to(self.device),
|
||||
idx.to(self.device),
|
||||
)
|
||||
|
||||
inputs = inputs.view(inputs.size(0), -1)
|
||||
|
||||
@@ -113,9 +147,13 @@ class VAETrainer(BaseTrainer):
|
||||
scores = -likelihood # negative likelihood as anomaly score
|
||||
|
||||
# Save triple of (idx, label, score) in a list
|
||||
idx_label_score += list(zip(idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist()))
|
||||
idx_label_score += list(
|
||||
zip(
|
||||
idx.cpu().data.numpy().tolist(),
|
||||
labels.cpu().data.numpy().tolist(),
|
||||
scores.cpu().data.numpy().tolist(),
|
||||
)
|
||||
)
|
||||
|
||||
# Overall loss
|
||||
elbo = likelihood - vae.kl_divergence
|
||||
@@ -133,7 +171,7 @@ class VAETrainer(BaseTrainer):
|
||||
self.test_auc = roc_auc_score(labels, scores)
|
||||
|
||||
# Log results
|
||||
logger.info('Test Loss: {:.6f}'.format(epoch_loss / n_batches))
|
||||
logger.info('Test AUC: {:.2f}%'.format(100. * self.test_auc))
|
||||
logger.info('Test Time: {:.3f}s'.format(self.test_time))
|
||||
logger.info('Finished testing variational autoencoder.')
|
||||
logger.info("Test Loss: {:.6f}".format(epoch_loss / n_batches))
|
||||
logger.info("Test AUC: {:.2f}%".format(100.0 * self.test_auc))
|
||||
logger.info("Test Time: {:.3f}s".format(self.test_time))
|
||||
logger.info("Finished testing variational autoencoder.")
|
||||
|
||||
@@ -41,7 +41,13 @@ class SVI(nn.Module):
|
||||
|
||||
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__()
|
||||
self.model = model
|
||||
self.likelihood = likelihood
|
||||
|
||||
@@ -10,7 +10,7 @@ class Config(object):
|
||||
def load_config(self, import_json):
|
||||
"""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)
|
||||
|
||||
for key, value in settings.items():
|
||||
@@ -19,5 +19,5 @@ class Config(object):
|
||||
def save_config(self, export_json):
|
||||
"""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)
|
||||
|
||||
@@ -38,7 +38,9 @@ def log_sum_exp(tensor, dim=-1, sum_op=torch.sum):
|
||||
:return: LSE
|
||||
"""
|
||||
max, _ = torch.max(tensor, dim=dim, keepdim=True)
|
||||
return torch.log(sum_op(torch.exp(tensor - max), dim=dim, keepdim=True) + 1e-8) + max
|
||||
return (
|
||||
torch.log(sum_op(torch.exp(tensor - max), dim=dim, keepdim=True) + 1e-8) + max
|
||||
)
|
||||
|
||||
|
||||
def binary_cross_entropy(x, y):
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
import torch
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # or 'PS', 'PDF', 'SVG'
|
||||
|
||||
matplotlib.use("Agg") # or 'PS', 'PDF', 'SVG'
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from torchvision.utils import make_grid
|
||||
|
||||
|
||||
def plot_images_grid(x: torch.tensor, export_img, title: str = '', nrow=8, padding=2, normalize=False, pad_value=0):
|
||||
def plot_images_grid(
|
||||
x: torch.tensor,
|
||||
export_img,
|
||||
title: str = "",
|
||||
nrow=8,
|
||||
padding=2,
|
||||
normalize=False,
|
||||
pad_value=0,
|
||||
):
|
||||
"""Plot 4D Tensor of images of shape (B x C x H x W) as a grid."""
|
||||
|
||||
grid = make_grid(x, nrow=nrow, padding=padding, normalize=normalize, pad_value=pad_value)
|
||||
grid = make_grid(
|
||||
x, nrow=nrow, padding=padding, normalize=normalize, pad_value=pad_value
|
||||
)
|
||||
npgrid = grid.cpu().numpy()
|
||||
|
||||
plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation='nearest')
|
||||
plt.imshow(np.transpose(npgrid, (1, 2, 0)), interpolation="nearest")
|
||||
|
||||
ax = plt.gca()
|
||||
ax.xaxis.set_visible(False)
|
||||
ax.yaxis.set_visible(False)
|
||||
|
||||
if not (title == ''):
|
||||
if not (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()
|
||||
|
||||
Reference in New Issue
Block a user