From ce15c10d0d8e430449766d8a0030a1c475da1ca8 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Fri, 15 Dec 2023 18:15:57 -0500 Subject: [PATCH 1/9] Update hypersphere radius, and filter training data with active_mask --- pygod/detector/base.py | 52 ++++++++++++++++++++++++++++------------- pygod/detector/ocgnn.py | 7 ++++-- pygod/nn/ocgnn.py | 21 ++++++++++++----- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/pygod/detector/base.py b/pygod/detector/base.py index 72deb8e4..c097544e 100644 --- a/pygod/detector/base.py +++ b/pygod/detector/base.py @@ -473,15 +473,23 @@ def fit(self, data, label=None): else: self.emb[node_idx[:batch_size]] = \ self.model.emb[:batch_size].cpu() - self.decision_score_[node_idx[:batch_size]] = score + if 'active_mask' in data.keys(): + self.decision_score_[node_idx[:batch_size][data.active_mask]] = score + else: + self.decision_score_[node_idx[:batch_size]] = score optimizer.zero_grad() loss.backward() optimizer.step() - - loss_value = epoch_loss / data.x.shape[0] + if 'active_mask' in data.keys(): + loss_value = epoch_loss / data.x[data.active_mask, :].shape[0] + else: + loss_value = epoch_loss / data.x.shape[0] if self.gan: - loss_value = (self.epoch_loss_g / data.x.shape[0], loss_value) + if 'active_mask' in data.keys(): + loss_value = (self.epoch_loss_g / data.x[data.active_mask, :].shape[0], loss_value) + else: + loss_value = (self.epoch_loss_g / data.x.shape[0], loss_value) logger(epoch=epoch, loss=loss_value, score=self.decision_score_, @@ -489,7 +497,8 @@ def fit(self, data, label=None): time=time.time() - start_time, verbose=self.verbose, train=True) - + if 'active_mask' in data.keys(): + self.decision_score_ = self.decision_score_[data.active_mask] self._process_decision_score() return self @@ -510,7 +519,7 @@ def decision_function(self, data, label=None): self.emb = torch.zeros(data.x.shape[0], self.hid_dim) start_time = time.time() for sampled_data in loader: - loss, score = self.forward_model(sampled_data) + loss, score = self.forward_model(sampled_data, is_train=False) batch_size = sampled_data.batch_size node_idx = sampled_data.n_id if self.save_emb: @@ -522,15 +531,26 @@ def decision_function(self, data, label=None): else: self.emb[node_idx[:batch_size]] = \ self.model.emb[:batch_size].cpu() - - outlier_score[node_idx[:batch_size]] = score - - logger(loss=loss.item() / data.x.shape[0], - score=outlier_score, - target=label, - time=time.time() - start_time, - verbose=self.verbose, - train=False) + if 'active_mask' in data.keys(): + outlier_score[node_idx[:batch_size][data.active_mask]] = score + else: + outlier_score[node_idx[:batch_size]] = score + if 'active_mask' in data.keys(): + logger(loss=loss.item() / data.x[data.active_mask, :].shape[0], + score=outlier_score, + target=label, + time=time.time() - start_time, + verbose=self.verbose, + train=False) + else: + logger(loss=loss.item() / data.x.shape[0], + score=outlier_score, + target=label, + time=time.time() - start_time, + verbose=self.verbose, + train=False) + if 'active_mask' in data.keys(): + outlier_score = outlier_score[data.active_mask] return outlier_score def predict(self, @@ -629,7 +649,7 @@ def init_model(self): """ @abstractmethod - def forward_model(self, data): + def forward_model(self, data, is_train=True): """ Forward pass of the neural network detector. diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index 07127e8a..3926e5a5 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -155,13 +155,16 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size x = data.x.to(self.device) edge_index = data.edge_index.to(self.device) emb = self.model(x, edge_index) - loss, score = self.model.loss_func(emb[:batch_size]) + if 'active_mask' in data.keys(): + loss, score = self.model.loss_func(emb[:batch_size][data.active_mask, :], is_train=is_train) + else: + loss, score = self.model.loss_func(emb[:batch_size], is_train=is_train) return loss, score.detach().cpu() diff --git a/pygod/nn/ocgnn.py b/pygod/nn/ocgnn.py index 38ced965..8ec62052 100644 --- a/pygod/nn/ocgnn.py +++ b/pygod/nn/ocgnn.py @@ -92,7 +92,7 @@ def forward(self, x, edge_index): self.emb = self.gnn(x, edge_index) return self.emb - def loss_func(self, emb): + def loss_func(self, emb, is_train=True): """ Loss function for OCGNN @@ -108,17 +108,26 @@ def loss_func(self, emb): score : torch.Tensor Outlier scores of shape :math:`N` with gradients. """ + if is_train: + with torch.no_grad(): + self.c = torch.mean(emb, 0) + self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps + self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps dist = torch.sum(torch.pow(emb - self.c, 2), 1) score = dist - self.r ** 2 loss = self.r ** 2 + 1 / self.beta * torch.mean(torch.relu(score)) - if self.warmup > 0: + if is_train: with torch.no_grad(): - self.warmup -= 1 self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) - self.c = torch.mean(emb, 0) - self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps - self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps + + # if self.warmup > 0: + # with torch.no_grad(): + # self.warmup -= 1 + # self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) + # self.c = torch.mean(emb, 0) + # self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps + # self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps return loss, score From 77d754c65dbac882fe290520458f5616fa485086 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Fri, 15 Dec 2023 18:26:30 -0500 Subject: [PATCH 2/9] Update hypersphere radius, and filter training data with active_mask --- pygod/detector/ocgnn.py | 6 +++--- pygod/nn/ocgnn.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index 3926e5a5..66dbf4f5 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -9,7 +9,7 @@ from . import DeepDetector from ..nn import OCGNNBase - +import math class OCGNN(DeepDetector): """ @@ -58,7 +58,7 @@ class OCGNN(DeepDetector): The weight between the reconstruction loss and radius. Default: ``0.5``. warmup : int, optional - The number of epochs for warm-up training. Default: ``2``. + The number of epochs for warm-up training. Default: ``inf``. eps : float, optional The slack variable. Default: ``0.001``. verbose : int, optional @@ -109,7 +109,7 @@ def __init__(self, batch_size=0, num_neigh=-1, beta=0.5, - warmup=2, + warmup=math.inf, eps=0.001, verbose=0, save_emb=False, diff --git a/pygod/nn/ocgnn.py b/pygod/nn/ocgnn.py index 8ec62052..0041e450 100644 --- a/pygod/nn/ocgnn.py +++ b/pygod/nn/ocgnn.py @@ -109,18 +109,20 @@ def loss_func(self, emb, is_train=True): Outlier scores of shape :math:`N` with gradients. """ if is_train: - with torch.no_grad(): - self.c = torch.mean(emb, 0) - self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps - self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps + if self.warmup > 0: + with torch.no_grad(): + self.c = torch.mean(emb, 0) + self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps + self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps dist = torch.sum(torch.pow(emb - self.c, 2), 1) score = dist - self.r ** 2 loss = self.r ** 2 + 1 / self.beta * torch.mean(torch.relu(score)) if is_train: - with torch.no_grad(): - self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) + if self.warmup > 0: + with torch.no_grad(): + self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) # if self.warmup > 0: # with torch.no_grad(): From 26952732663670faffeef2865d16ecb45338ec5e Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Fri, 15 Dec 2023 18:28:26 -0500 Subject: [PATCH 3/9] Set default warmup as math.inf --- pygod/nn/ocgnn.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pygod/nn/ocgnn.py b/pygod/nn/ocgnn.py index 0041e450..0c621910 100644 --- a/pygod/nn/ocgnn.py +++ b/pygod/nn/ocgnn.py @@ -1,7 +1,7 @@ import torch import torch.nn as nn from torch_geometric.nn import GCN - +import math class OCGNNBase(nn.Module): """ @@ -35,7 +35,7 @@ class OCGNNBase(nn.Module): The weight between the reconstruction loss and radius. Default: ``0.5``. warmup : int, optional - The number of epochs for warm-up training. Default: ``2``. + The number of epochs for warm-up training. Default: ``inf``. eps : float, optional The slack variable. Default: ``0.001``. **kwargs @@ -50,7 +50,7 @@ def __init__(self, act=torch.nn.functional.relu, backbone=GCN, beta=0.5, - warmup=2, + warmup=math.inf, eps=0.001, **kwargs): super(OCGNNBase, self).__init__() @@ -124,12 +124,4 @@ def loss_func(self, emb, is_train=True): with torch.no_grad(): self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) - # if self.warmup > 0: - # with torch.no_grad(): - # self.warmup -= 1 - # self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) - # self.c = torch.mean(emb, 0) - # self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps - # self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps - return loss, score From 4a36ac8fa580f80b70ff1d18c9b5c55c209d7642 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Mon, 18 Dec 2023 23:12:02 -0500 Subject: [PATCH 4/9] Updating other models --- cache/nmd_5a90b8b03F.pt | Bin 0 -> 2624 bytes cache/nmd_6d7e94a03F.pt | Bin 0 -> 832 bytes cache/nmd_d5126beb3F.pt | Bin 0 -> 832 bytes docs/examples/intro.py | 1 + pygod/detector/adone.py | 34 +++++++++++++++++++++++----------- pygod/detector/anomalydae.py | 26 +++++++++++++++++--------- pygod/detector/cola.py | 2 +- pygod/detector/conad.py | 20 +++++++++++++------- pygod/detector/dominant.py | 20 +++++++++++++------- pygod/detector/done.py | 28 +++++++++++++++++++--------- pygod/detector/gaan.py | 2 +- pygod/detector/gae.py | 13 +++++++++---- pygod/detector/guide.py | 20 +++++++++++++------- pygod/test/__init__.py | 0 14 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 cache/nmd_5a90b8b03F.pt create mode 100644 cache/nmd_6d7e94a03F.pt create mode 100644 cache/nmd_d5126beb3F.pt create mode 100644 pygod/test/__init__.py diff --git a/cache/nmd_5a90b8b03F.pt b/cache/nmd_5a90b8b03F.pt new file mode 100644 index 0000000000000000000000000000000000000000..aa31a532dbca2f077020b45b986b4941485f13fc GIT binary patch literal 2624 zcmai$PiQ1n5XNhg*(5vbA&C3uAj=>gBCN^auCT&_&qjhUD~{_Qf|r@eWY`49WO~wB zJoah9TM!QcL7q2{If|z~?!iO$=1Kav;6aaj^!rWsOFCnMH564}ef8?qd#`)?b=vcF zv!x~bkKD9tw)@Ri_uc+`tx<6&GYhBVekd;R3IBG`!MHfqXg#q%5U zvV-wKT>to_mc&;+ZCg8O*Pq4JGe{co z(p!(>t4Z8=6kl8Iv=^6tJNo(WA5{+RT)Ncj_Qpj^U2CmJx5Glq_0AgAbt?UVS^B`%`lp3jGnPQnN0)I=~vJ~;o?4@F%-ZW2K zM==xUq+UK2$(-4V@EBg=-TCq0f5e4)Ms8&GOxB4@rPL#j{+8uG5N}dA@J0oD#o(&- zUK9R9dDA_TZ3&;q8o9C`UBHL=y>i$;=3%@OcJi@E=8C-=Q%?eB{$82yg>yJmK9>2y z{7x~i7#7K0vERRtFY&I(4fDe{BeTF$kFyK)GVz>GPfEXF`sq+lKA5u{gC*2`C|s91 zSIqxHF?yQrmtw8pE8h#VmrW^>yJG&(519D^ratEro-Z)x7v{^9|GMzFQV)Ctrj{%? zlTC#)RDKU!KCj2}iBAPva$w@@SE%0+cO?9SG!^HhEyb`1F?#p1)X!yCqOD|j-)L*VR&tG*s`KhUwWT-{g~@%Oa^Oax+;3 zd|auY3s=|E6vz2b=McxZx^PeN(EqRUz2ac(GvVcu$MRni&Nmg$d?BCu+v3?XqlT1v znJfbK6>Rb?I(uJ3eR^`r&7}Ba@+7G{zdkQaf6R+GryTiMg#7A$=fd}*q6gMT-cco= zepuTH^~Av+R(L1>d-2~&Q*q>T9;bU&=6l(&f6U*MBAF}R$DVNDc|Tb_8+7per^jE+ zsXesQu3=8^MAa+AO^9=H$qi|?NOg(V&lUJf=z=0^t=SUQ9&em0aj4tdOo?)5I_-sp rJ7wea&0I4*4VXNha_JMAkA#OC!QzsUU;uza!v literal 0 HcmV?d00001 diff --git a/cache/nmd_6d7e94a03F.pt b/cache/nmd_6d7e94a03F.pt new file mode 100644 index 0000000000000000000000000000000000000000..0aefc880fea6fd01aff25b16bd0636dab28dfc9d GIT binary patch literal 832 zcmWIWW@cev;NW1u0OAbX40*XJ@n$LJsg@>*2F7msDTyVCdIi}zZcgkBQ4r9;lw6Wu zl$@a#Us{rxQ_K}#l$unUnUfM6t#Ng-EIA-7(DH#=_su93X5ENf{sH3yTHM*%N>!fiP}gh%v~Z z`=Tths2CVXZcY+7yk3BOgLypKz$zI&Bmq!RV&A=3)cloVITmb&p@~gLSUbR!hj8w YU>WW~`Jj{;;LXYg5@QBJka~z(09MbJ1ONa4 literal 0 HcmV?d00001 diff --git a/cache/nmd_d5126beb3F.pt b/cache/nmd_d5126beb3F.pt new file mode 100644 index 0000000000000000000000000000000000000000..dd90f522e41a593ade46b145f3a7c934fcfebcbd GIT binary patch literal 832 zcmWIWW@cev;NW1u0OAbX40*XJ@hPT;MrKK=NycvaDTyVCdIi}zZcgkBQ4r9;lw6Wu zl$@a#Us{rxQ_K}#l$unUnUfM6t#Ng-EIA-7(DH#=_su93X5ENf{sH3yTHM*%N>!fiP}gh%v~Z z`=Tths2CVXZcY+7yk3BOgLypKz$zI&Bmq!RV&A=3)cloVITmb&p@~gLSUbR!hj8w YU>WW~`Jj{;;LXYg5@QBJka~z(0I4UJ?f?J) literal 0 HcmV?d00001 diff --git a/docs/examples/intro.py b/docs/examples/intro.py index 3c47f7a4..94a0a05e 100644 --- a/docs/examples/intro.py +++ b/docs/examples/intro.py @@ -69,6 +69,7 @@ # To train the detector with the loaded data, simply feed the # ``torch_geometric.data.Data`` object into the detector via ``fit``. +# To train the model with train_mask only, use `data.active_mask = data.train_mask` detector.fit(data) diff --git a/pygod/detector/adone.py b/pygod/detector/adone.py index 0d694fa0..5c5dff67 100644 --- a/pygod/detector/adone.py +++ b/pygod/detector/adone.py @@ -189,7 +189,7 @@ def init_model(self, **kwargs): w5=self.w5, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -198,16 +198,28 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_, h_a, h_s, dna, dns, dis_a, dis_s = self.model(x, s, edge_index) - loss, oa, os, oc = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - h_a[:batch_size], - h_s[:batch_size], - dna[:batch_size], - dns[:batch_size], - dis_a[:batch_size], - dis_s[:batch_size]) + if 'active_mask' in data.keys(): + loss, oa, os, oc = self.model.loss_func(x[:batch_size][data.active_mask, :], + x_[:batch_size][data.active_mask, :], + s[:batch_size][data.active_mask, :], + s_[:batch_size][data.active_mask, :], + h_a[:batch_size][data.active_mask, :], + h_s[:batch_size][data.active_mask, :], + dna[:batch_size][data.active_mask, :], + dns[:batch_size][data.active_mask, :], + dis_a[:batch_size][data.active_mask, :], + dis_s[:batch_size][data.active_mask, :]) + else: + loss, oa, os, oc = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + h_a[:batch_size], + h_s[:batch_size], + dna[:batch_size], + dns[:batch_size], + dis_a[:batch_size], + dis_s[:batch_size]) self.attribute_score_[node_idx[:batch_size]] = oa.detach().cpu() self.structural_score_[node_idx[:batch_size]] = os.detach().cpu() diff --git a/pygod/detector/anomalydae.py b/pygod/detector/anomalydae.py index 3f5919ef..caebe392 100644 --- a/pygod/detector/anomalydae.py +++ b/pygod/detector/anomalydae.py @@ -167,7 +167,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -181,14 +181,22 @@ def forward_model(self, data): weight = 1 - self.alpha pos_weight_a = self.eta / (1 + self.eta) pos_weight_s = self.theta / (1 + self.theta) - - score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - weight, - pos_weight_a, - pos_weight_s) + if 'active_mask' in data.keys(): + score = self.model.loss_func(x[:batch_size][data.active_mask, :], + x_[:batch_size][data.active_mask, :], + s[:batch_size, node_idx][data.active_mask, :], + s_[:batch_size][data.active_mask, :], + weight, + pos_weight_a, + pos_weight_s) + else: + score = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + weight, + pos_weight_a, + pos_weight_s) loss = torch.mean(score) diff --git a/pygod/detector/cola.py b/pygod/detector/cola.py index 2b79c281..10f15676 100644 --- a/pygod/detector/cola.py +++ b/pygod/detector/cola.py @@ -137,7 +137,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size x = data.x.to(self.device) diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index d4a4a849..684140c3 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -185,7 +185,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -204,12 +204,18 @@ def forward_model(self, data): x_, s_ = self.model(x, edge_index) h = self.model.emb - - score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - self.weight) + if 'active_mask' in data.keys(): + score = self.model.loss_func(x[:batch_size][data.active_mask, :], + x_[:batch_size][data.active_mask, :], + s[:batch_size, node_idx][data.active_mask, :], + s_[:batch_size][data.active_mask, :], + self.weight) + else: + score = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + self.weight) if self.model.training: margin_loss = self.margin_loss_func(h, h, h_aug) * label_aug diff --git a/pygod/detector/dominant.py b/pygod/detector/dominant.py index 735f3658..16a0ed8e 100644 --- a/pygod/detector/dominant.py +++ b/pygod/detector/dominant.py @@ -150,7 +150,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -159,12 +159,18 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, edge_index) - - score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - self.weight) + if 'active_mask' in data.keys(): + score = self.model.loss_func(x[:batch_size][data.active_mask, :], + x_[:batch_size][data.active_mask, :], + s[:batch_size, node_idx][data.active_mask, :], + s_[:batch_size][data.active_mask, :], + self.weight) + else: + score = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + self.weight) loss = torch.mean(score) diff --git a/pygod/detector/done.py b/pygod/detector/done.py index c7f04062..38911f17 100644 --- a/pygod/detector/done.py +++ b/pygod/detector/done.py @@ -188,7 +188,7 @@ def init_model(self, **kwargs): w5=self.w5, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -197,14 +197,24 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_, h_a, h_s, dna, dns = self.model(x, s, edge_index) - loss, oa, os, oc = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - h_a[:batch_size], - h_s[:batch_size], - dna[:batch_size], - dns[:batch_size]) + if 'active_mask' in data.keys(): + loss, oa, os, oc = self.model.loss_func(x[:batch_size][data.active_mask,:], + x_[:batch_size][data.active_mask,:], + s[:batch_size][data.active_mask,:], + s_[:batch_size][data.active_mask,:], + h_a[:batch_size][data.active_mask,:], + h_s[:batch_size][data.active_mask,:], + dna[:batch_size][data.active_mask,:], + dns[:batch_size][data.active_mask,:]) + else: + loss, oa, os, oc = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + h_a[:batch_size], + h_s[:batch_size], + dna[:batch_size], + dns[:batch_size]) self.attribute_score_[node_idx[:batch_size]] = oa.detach().cpu() self.structural_score_[node_idx[:batch_size]] = os.detach().cpu() diff --git a/pygod/detector/gaan.py b/pygod/detector/gaan.py index c1ea59b7..215fd084 100644 --- a/pygod/detector/gaan.py +++ b/pygod/detector/gaan.py @@ -160,7 +160,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id diff --git a/pygod/detector/gae.py b/pygod/detector/gae.py index 01796a16..a9a81bac 100644 --- a/pygod/detector/gae.py +++ b/pygod/detector/gae.py @@ -151,7 +151,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size node_idx = data.n_id @@ -165,9 +165,14 @@ def forward_model(self, data): h = self.model(x, edge_index) target = s if self.recon_s else x - score = torch.mean(self.model.loss_func(target[:batch_size], - h[:batch_size], - reduction='none'), dim=1) + if 'active_mask' in data.keys(): + score = torch.mean(self.model.loss_func(target[:batch_size][data.active_mask,:], + h[:batch_size][data.active_mask,:], + reduction='none'), dim=1) + else: + score = torch.mean(self.model.loss_func(target[:batch_size], + h[:batch_size], + reduction='none'), dim=1) loss = torch.mean(score) diff --git a/pygod/detector/guide.py b/pygod/detector/guide.py index 30cb11a5..5d5014bb 100644 --- a/pygod/detector/guide.py +++ b/pygod/detector/guide.py @@ -184,7 +184,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data): + def forward_model(self, data, is_train=True): batch_size = data.batch_size @@ -193,12 +193,18 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, s, edge_index) - - score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - self.alpha) + if 'active_mask' in data.keys(): + score = self.model.loss_func(x[:batch_size][data.active_mask, :], + x_[:batch_size][data.active_mask, :], + s[:batch_size][data.active_mask, :], + s_[:batch_size][data.active_mask, :], + self.alpha) + else: + score = self.model.loss_func(x[:batch_size], + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + self.alpha) loss = torch.mean(score) diff --git a/pygod/test/__init__.py b/pygod/test/__init__.py new file mode 100644 index 00000000..e69de29b From ecc2caa6a4519e7c1780566ce086d2f581ac191d Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Tue, 2 Jan 2024 21:07:32 -0500 Subject: [PATCH 5/9] Keep accepted changes and Revert rest --- docs/examples/intro.py | 3 +-- pygod/detector/adone.py | 16 ++------------- pygod/detector/anomalydae.py | 13 ++---------- pygod/detector/base.py | 38 +++++++----------------------------- pygod/detector/cola.py | 2 +- pygod/detector/conad.py | 11 ++--------- pygod/detector/dominant.py | 11 ++--------- pygod/detector/done.py | 14 ++----------- pygod/detector/gaan.py | 2 +- pygod/detector/gae.py | 9 ++------- pygod/detector/guide.py | 11 ++--------- pygod/detector/ocgnn.py | 11 ++++------- pygod/nn/ocgnn.py | 26 ++++++++++++------------ 13 files changed, 40 insertions(+), 127 deletions(-) diff --git a/docs/examples/intro.py b/docs/examples/intro.py index 94a0a05e..76d4ba71 100644 --- a/docs/examples/intro.py +++ b/docs/examples/intro.py @@ -69,7 +69,6 @@ # To train the detector with the loaded data, simply feed the # ``torch_geometric.data.Data`` object into the detector via ``fit``. -# To train the model with train_mask only, use `data.active_mask = data.train_mask` detector.fit(data) @@ -108,4 +107,4 @@ from pygod.metric import eval_roc_auc auc_score = eval_roc_auc(data.y, score) -print('AUC Score:', auc_score) +print('AUC Score:', auc_score) \ No newline at end of file diff --git a/pygod/detector/adone.py b/pygod/detector/adone.py index 5c5dff67..989f850d 100644 --- a/pygod/detector/adone.py +++ b/pygod/detector/adone.py @@ -189,7 +189,7 @@ def init_model(self, **kwargs): w5=self.w5, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -198,19 +198,7 @@ def forward_model(self, data, is_train=True): edge_index = data.edge_index.to(self.device) x_, s_, h_a, h_s, dna, dns, dis_a, dis_s = self.model(x, s, edge_index) - if 'active_mask' in data.keys(): - loss, oa, os, oc = self.model.loss_func(x[:batch_size][data.active_mask, :], - x_[:batch_size][data.active_mask, :], - s[:batch_size][data.active_mask, :], - s_[:batch_size][data.active_mask, :], - h_a[:batch_size][data.active_mask, :], - h_s[:batch_size][data.active_mask, :], - dna[:batch_size][data.active_mask, :], - dns[:batch_size][data.active_mask, :], - dis_a[:batch_size][data.active_mask, :], - dis_s[:batch_size][data.active_mask, :]) - else: - loss, oa, os, oc = self.model.loss_func(x[:batch_size], + loss, oa, os, oc = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size], s_[:batch_size], diff --git a/pygod/detector/anomalydae.py b/pygod/detector/anomalydae.py index caebe392..4b5eb66f 100644 --- a/pygod/detector/anomalydae.py +++ b/pygod/detector/anomalydae.py @@ -167,7 +167,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -181,16 +181,7 @@ def forward_model(self, data, is_train=True): weight = 1 - self.alpha pos_weight_a = self.eta / (1 + self.eta) pos_weight_s = self.theta / (1 + self.theta) - if 'active_mask' in data.keys(): - score = self.model.loss_func(x[:batch_size][data.active_mask, :], - x_[:batch_size][data.active_mask, :], - s[:batch_size, node_idx][data.active_mask, :], - s_[:batch_size][data.active_mask, :], - weight, - pos_weight_a, - pos_weight_s) - else: - score = self.model.loss_func(x[:batch_size], + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], s_[:batch_size], diff --git a/pygod/detector/base.py b/pygod/detector/base.py index c097544e..75e43ad1 100644 --- a/pygod/detector/base.py +++ b/pygod/detector/base.py @@ -473,23 +473,14 @@ def fit(self, data, label=None): else: self.emb[node_idx[:batch_size]] = \ self.model.emb[:batch_size].cpu() - if 'active_mask' in data.keys(): - self.decision_score_[node_idx[:batch_size][data.active_mask]] = score - else: - self.decision_score_[node_idx[:batch_size]] = score + self.decision_score_[node_idx[:batch_size]] = score optimizer.zero_grad() loss.backward() optimizer.step() - if 'active_mask' in data.keys(): - loss_value = epoch_loss / data.x[data.active_mask, :].shape[0] - else: - loss_value = epoch_loss / data.x.shape[0] + loss_value = epoch_loss / data.x.shape[0] if self.gan: - if 'active_mask' in data.keys(): - loss_value = (self.epoch_loss_g / data.x[data.active_mask, :].shape[0], loss_value) - else: - loss_value = (self.epoch_loss_g / data.x.shape[0], loss_value) + loss_value = (self.epoch_loss_g / data.x.shape[0], loss_value) logger(epoch=epoch, loss=loss_value, score=self.decision_score_, @@ -497,8 +488,6 @@ def fit(self, data, label=None): time=time.time() - start_time, verbose=self.verbose, train=True) - if 'active_mask' in data.keys(): - self.decision_score_ = self.decision_score_[data.active_mask] self._process_decision_score() return self @@ -519,7 +508,7 @@ def decision_function(self, data, label=None): self.emb = torch.zeros(data.x.shape[0], self.hid_dim) start_time = time.time() for sampled_data in loader: - loss, score = self.forward_model(sampled_data, is_train=False) + loss, score = self.forward_model(sampled_data) batch_size = sampled_data.batch_size node_idx = sampled_data.n_id if self.save_emb: @@ -531,26 +520,13 @@ def decision_function(self, data, label=None): else: self.emb[node_idx[:batch_size]] = \ self.model.emb[:batch_size].cpu() - if 'active_mask' in data.keys(): - outlier_score[node_idx[:batch_size][data.active_mask]] = score - else: - outlier_score[node_idx[:batch_size]] = score - if 'active_mask' in data.keys(): - logger(loss=loss.item() / data.x[data.active_mask, :].shape[0], - score=outlier_score, - target=label, - time=time.time() - start_time, - verbose=self.verbose, - train=False) - else: - logger(loss=loss.item() / data.x.shape[0], + outlier_score[node_idx[:batch_size]] = score + logger(loss=loss.item() / data.x.shape[0], score=outlier_score, target=label, time=time.time() - start_time, verbose=self.verbose, train=False) - if 'active_mask' in data.keys(): - outlier_score = outlier_score[data.active_mask] return outlier_score def predict(self, @@ -649,7 +625,7 @@ def init_model(self): """ @abstractmethod - def forward_model(self, data, is_train=True): + def forward_model(self, data): """ Forward pass of the neural network detector. diff --git a/pygod/detector/cola.py b/pygod/detector/cola.py index 10f15676..2b79c281 100644 --- a/pygod/detector/cola.py +++ b/pygod/detector/cola.py @@ -137,7 +137,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size x = data.x.to(self.device) diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index 684140c3..a65a1fb3 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -185,7 +185,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -204,14 +204,7 @@ def forward_model(self, data, is_train=True): x_, s_ = self.model(x, edge_index) h = self.model.emb - if 'active_mask' in data.keys(): - score = self.model.loss_func(x[:batch_size][data.active_mask, :], - x_[:batch_size][data.active_mask, :], - s[:batch_size, node_idx][data.active_mask, :], - s_[:batch_size][data.active_mask, :], - self.weight) - else: - score = self.model.loss_func(x[:batch_size], + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], s_[:batch_size], diff --git a/pygod/detector/dominant.py b/pygod/detector/dominant.py index 16a0ed8e..94b64dde 100644 --- a/pygod/detector/dominant.py +++ b/pygod/detector/dominant.py @@ -150,7 +150,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -159,14 +159,7 @@ def forward_model(self, data, is_train=True): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, edge_index) - if 'active_mask' in data.keys(): - score = self.model.loss_func(x[:batch_size][data.active_mask, :], - x_[:batch_size][data.active_mask, :], - s[:batch_size, node_idx][data.active_mask, :], - s_[:batch_size][data.active_mask, :], - self.weight) - else: - score = self.model.loss_func(x[:batch_size], + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], s_[:batch_size], diff --git a/pygod/detector/done.py b/pygod/detector/done.py index 38911f17..a0eb86d6 100644 --- a/pygod/detector/done.py +++ b/pygod/detector/done.py @@ -188,7 +188,7 @@ def init_model(self, **kwargs): w5=self.w5, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -197,17 +197,7 @@ def forward_model(self, data, is_train=True): edge_index = data.edge_index.to(self.device) x_, s_, h_a, h_s, dna, dns = self.model(x, s, edge_index) - if 'active_mask' in data.keys(): - loss, oa, os, oc = self.model.loss_func(x[:batch_size][data.active_mask,:], - x_[:batch_size][data.active_mask,:], - s[:batch_size][data.active_mask,:], - s_[:batch_size][data.active_mask,:], - h_a[:batch_size][data.active_mask,:], - h_s[:batch_size][data.active_mask,:], - dna[:batch_size][data.active_mask,:], - dns[:batch_size][data.active_mask,:]) - else: - loss, oa, os, oc = self.model.loss_func(x[:batch_size], + loss, oa, os, oc = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size], s_[:batch_size], diff --git a/pygod/detector/gaan.py b/pygod/detector/gaan.py index 215fd084..c1ea59b7 100644 --- a/pygod/detector/gaan.py +++ b/pygod/detector/gaan.py @@ -160,7 +160,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id diff --git a/pygod/detector/gae.py b/pygod/detector/gae.py index a9a81bac..0db79f10 100644 --- a/pygod/detector/gae.py +++ b/pygod/detector/gae.py @@ -151,7 +151,7 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size node_idx = data.n_id @@ -165,12 +165,7 @@ def forward_model(self, data, is_train=True): h = self.model(x, edge_index) target = s if self.recon_s else x - if 'active_mask' in data.keys(): - score = torch.mean(self.model.loss_func(target[:batch_size][data.active_mask,:], - h[:batch_size][data.active_mask,:], - reduction='none'), dim=1) - else: - score = torch.mean(self.model.loss_func(target[:batch_size], + score = torch.mean(self.model.loss_func(target[:batch_size], h[:batch_size], reduction='none'), dim=1) diff --git a/pygod/detector/guide.py b/pygod/detector/guide.py index 5d5014bb..e913567c 100644 --- a/pygod/detector/guide.py +++ b/pygod/detector/guide.py @@ -184,7 +184,7 @@ def init_model(self, **kwargs): act=self.act, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size @@ -193,14 +193,7 @@ def forward_model(self, data, is_train=True): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, s, edge_index) - if 'active_mask' in data.keys(): - score = self.model.loss_func(x[:batch_size][data.active_mask, :], - x_[:batch_size][data.active_mask, :], - s[:batch_size][data.active_mask, :], - s_[:batch_size][data.active_mask, :], - self.alpha) - else: - score = self.model.loss_func(x[:batch_size], + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size], s_[:batch_size], diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index 66dbf4f5..bdd6d32a 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -9,7 +9,6 @@ from . import DeepDetector from ..nn import OCGNNBase -import math class OCGNN(DeepDetector): """ @@ -109,7 +108,7 @@ def __init__(self, batch_size=0, num_neigh=-1, beta=0.5, - warmup=math.inf, + warmup=2, eps=0.001, verbose=0, save_emb=False, @@ -155,16 +154,14 @@ def init_model(self, **kwargs): backbone=self.backbone, **kwargs).to(self.device) - def forward_model(self, data, is_train=True): + def forward_model(self, data): batch_size = data.batch_size x = data.x.to(self.device) edge_index = data.edge_index.to(self.device) emb = self.model(x, edge_index) - if 'active_mask' in data.keys(): - loss, score = self.model.loss_func(emb[:batch_size][data.active_mask, :], is_train=is_train) - else: - loss, score = self.model.loss_func(emb[:batch_size], is_train=is_train) + + loss, score = self.model.loss_func(emb[:batch_size]) return loss, score.detach().cpu() diff --git a/pygod/nn/ocgnn.py b/pygod/nn/ocgnn.py index 0c621910..b4e94e58 100644 --- a/pygod/nn/ocgnn.py +++ b/pygod/nn/ocgnn.py @@ -1,7 +1,6 @@ import torch import torch.nn as nn from torch_geometric.nn import GCN -import math class OCGNNBase(nn.Module): """ @@ -35,7 +34,7 @@ class OCGNNBase(nn.Module): The weight between the reconstruction loss and radius. Default: ``0.5``. warmup : int, optional - The number of epochs for warm-up training. Default: ``inf``. + The number of epochs for warm-up training. Default: ``2``. eps : float, optional The slack variable. Default: ``0.001``. **kwargs @@ -50,7 +49,7 @@ def __init__(self, act=torch.nn.functional.relu, backbone=GCN, beta=0.5, - warmup=math.inf, + warmup=2, eps=0.001, **kwargs): super(OCGNNBase, self).__init__() @@ -92,7 +91,7 @@ def forward(self, x, edge_index): self.emb = self.gnn(x, edge_index) return self.emb - def loss_func(self, emb, is_train=True): + def loss_func(self, emb): """ Loss function for OCGNN @@ -108,20 +107,19 @@ def loss_func(self, emb, is_train=True): score : torch.Tensor Outlier scores of shape :math:`N` with gradients. """ - if is_train: - if self.warmup > 0: - with torch.no_grad(): - self.c = torch.mean(emb, 0) - self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps - self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps + if self.warmup > 0: + with torch.no_grad(): + self.warmup -= 1 + self.c = torch.mean(emb, 0) + self.c[(abs(self.c) < self.eps) & (self.c < 0)] = -self.eps + self.c[(abs(self.c) < self.eps) & (self.c > 0)] = self.eps dist = torch.sum(torch.pow(emb - self.c, 2), 1) score = dist - self.r ** 2 loss = self.r ** 2 + 1 / self.beta * torch.mean(torch.relu(score)) - if is_train: - if self.warmup > 0: - with torch.no_grad(): - self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) + if self.warmup > 0: + with torch.no_grad(): + self.r = torch.quantile(torch.sqrt(dist), 1 - self.beta) return loss, score From a1349438eca3b207f38ad20615db1c9c50941677 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Tue, 2 Jan 2024 21:21:28 -0500 Subject: [PATCH 6/9] Keep accepted changes and Revert rest --- pygod/detector/adone.py | 18 +++++++++--------- pygod/detector/anomalydae.py | 12 ++++++------ pygod/detector/base.py | 14 +++++++++----- pygod/detector/conad.py | 8 ++++---- pygod/detector/dominant.py | 8 ++++---- pygod/detector/done.py | 14 +++++++------- pygod/detector/gae.py | 4 ++-- pygod/detector/guide.py | 8 ++++---- pygod/detector/ocgnn.py | 3 +-- 9 files changed, 46 insertions(+), 43 deletions(-) diff --git a/pygod/detector/adone.py b/pygod/detector/adone.py index 989f850d..0d694fa0 100644 --- a/pygod/detector/adone.py +++ b/pygod/detector/adone.py @@ -199,15 +199,15 @@ def forward_model(self, data): x_, s_, h_a, h_s, dna, dns, dis_a, dis_s = self.model(x, s, edge_index) loss, oa, os, oc = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - h_a[:batch_size], - h_s[:batch_size], - dna[:batch_size], - dns[:batch_size], - dis_a[:batch_size], - dis_s[:batch_size]) + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + h_a[:batch_size], + h_s[:batch_size], + dna[:batch_size], + dns[:batch_size], + dis_a[:batch_size], + dis_s[:batch_size]) self.attribute_score_[node_idx[:batch_size]] = oa.detach().cpu() self.structural_score_[node_idx[:batch_size]] = os.detach().cpu() diff --git a/pygod/detector/anomalydae.py b/pygod/detector/anomalydae.py index 4b5eb66f..e1712d19 100644 --- a/pygod/detector/anomalydae.py +++ b/pygod/detector/anomalydae.py @@ -182,12 +182,12 @@ def forward_model(self, data): pos_weight_a = self.eta / (1 + self.eta) pos_weight_s = self.theta / (1 + self.theta) score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - weight, - pos_weight_a, - pos_weight_s) + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + weight, + pos_weight_a, + pos_weight_s) loss = torch.mean(score) diff --git a/pygod/detector/base.py b/pygod/detector/base.py index 75e43ad1..72deb8e4 100644 --- a/pygod/detector/base.py +++ b/pygod/detector/base.py @@ -478,6 +478,7 @@ def fit(self, data, label=None): optimizer.zero_grad() loss.backward() optimizer.step() + loss_value = epoch_loss / data.x.shape[0] if self.gan: loss_value = (self.epoch_loss_g / data.x.shape[0], loss_value) @@ -488,6 +489,7 @@ def fit(self, data, label=None): time=time.time() - start_time, verbose=self.verbose, train=True) + self._process_decision_score() return self @@ -520,13 +522,15 @@ def decision_function(self, data, label=None): else: self.emb[node_idx[:batch_size]] = \ self.model.emb[:batch_size].cpu() + outlier_score[node_idx[:batch_size]] = score + logger(loss=loss.item() / data.x.shape[0], - score=outlier_score, - target=label, - time=time.time() - start_time, - verbose=self.verbose, - train=False) + score=outlier_score, + target=label, + time=time.time() - start_time, + verbose=self.verbose, + train=False) return outlier_score def predict(self, diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index a65a1fb3..16db4a57 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -205,10 +205,10 @@ def forward_model(self, data): x_, s_ = self.model(x, edge_index) h = self.model.emb score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - self.weight) + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + self.weight) if self.model.training: margin_loss = self.margin_loss_func(h, h, h_aug) * label_aug diff --git a/pygod/detector/dominant.py b/pygod/detector/dominant.py index 94b64dde..afe20dcb 100644 --- a/pygod/detector/dominant.py +++ b/pygod/detector/dominant.py @@ -160,10 +160,10 @@ def forward_model(self, data): x_, s_ = self.model(x, edge_index) score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size, node_idx], - s_[:batch_size], - self.weight) + x_[:batch_size], + s[:batch_size, node_idx], + s_[:batch_size], + self.weight) loss = torch.mean(score) diff --git a/pygod/detector/done.py b/pygod/detector/done.py index a0eb86d6..c7f04062 100644 --- a/pygod/detector/done.py +++ b/pygod/detector/done.py @@ -198,13 +198,13 @@ def forward_model(self, data): x_, s_, h_a, h_s, dna, dns = self.model(x, s, edge_index) loss, oa, os, oc = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - h_a[:batch_size], - h_s[:batch_size], - dna[:batch_size], - dns[:batch_size]) + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + h_a[:batch_size], + h_s[:batch_size], + dna[:batch_size], + dns[:batch_size]) self.attribute_score_[node_idx[:batch_size]] = oa.detach().cpu() self.structural_score_[node_idx[:batch_size]] = os.detach().cpu() diff --git a/pygod/detector/gae.py b/pygod/detector/gae.py index 0db79f10..01796a16 100644 --- a/pygod/detector/gae.py +++ b/pygod/detector/gae.py @@ -166,8 +166,8 @@ def forward_model(self, data): target = s if self.recon_s else x score = torch.mean(self.model.loss_func(target[:batch_size], - h[:batch_size], - reduction='none'), dim=1) + h[:batch_size], + reduction='none'), dim=1) loss = torch.mean(score) diff --git a/pygod/detector/guide.py b/pygod/detector/guide.py index e913567c..8cfaf9c4 100644 --- a/pygod/detector/guide.py +++ b/pygod/detector/guide.py @@ -194,10 +194,10 @@ def forward_model(self, data): x_, s_ = self.model(x, s, edge_index) score = self.model.loss_func(x[:batch_size], - x_[:batch_size], - s[:batch_size], - s_[:batch_size], - self.alpha) + x_[:batch_size], + s[:batch_size], + s_[:batch_size], + self.alpha) loss = torch.mean(score) diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index bdd6d32a..878de79e 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -57,7 +57,7 @@ class OCGNN(DeepDetector): The weight between the reconstruction loss and radius. Default: ``0.5``. warmup : int, optional - The number of epochs for warm-up training. Default: ``inf``. + The number of epochs for warm-up training. Default: ``2``. eps : float, optional The slack variable. Default: ``0.001``. verbose : int, optional @@ -161,7 +161,6 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) emb = self.model(x, edge_index) - loss, score = self.model.loss_func(emb[:batch_size]) return loss, score.detach().cpu() From b36165d79f5762dece8a1cbcd878b73e4cdd2044 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Tue, 2 Jan 2024 23:10:36 -0500 Subject: [PATCH 7/9] Keep accepted changes and Revert rest --- cache/nmd_5a90b8b03F.pt | Bin 2624 -> 0 bytes cache/nmd_6d7e94a03F.pt | Bin 832 -> 0 bytes cache/nmd_d5126beb3F.pt | Bin 832 -> 0 bytes pygod/detector/anomalydae.py | 3 ++- pygod/detector/conad.py | 5 +++-- pygod/detector/dominant.py | 3 ++- pygod/detector/guide.py | 3 ++- pygod/detector/ocgnn.py | 3 ++- pygod/nn/ocgnn.py | 1 + pygod/test/__init__.py | 0 10 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 cache/nmd_5a90b8b03F.pt delete mode 100644 cache/nmd_6d7e94a03F.pt delete mode 100644 cache/nmd_d5126beb3F.pt delete mode 100644 pygod/test/__init__.py diff --git a/cache/nmd_5a90b8b03F.pt b/cache/nmd_5a90b8b03F.pt deleted file mode 100644 index aa31a532dbca2f077020b45b986b4941485f13fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2624 zcmai$PiQ1n5XNhg*(5vbA&C3uAj=>gBCN^auCT&_&qjhUD~{_Qf|r@eWY`49WO~wB zJoah9TM!QcL7q2{If|z~?!iO$=1Kav;6aaj^!rWsOFCnMH564}ef8?qd#`)?b=vcF zv!x~bkKD9tw)@Ri_uc+`tx<6&GYhBVekd;R3IBG`!MHfqXg#q%5U zvV-wKT>to_mc&;+ZCg8O*Pq4JGe{co z(p!(>t4Z8=6kl8Iv=^6tJNo(WA5{+RT)Ncj_Qpj^U2CmJx5Glq_0AgAbt?UVS^B`%`lp3jGnPQnN0)I=~vJ~;o?4@F%-ZW2K zM==xUq+UK2$(-4V@EBg=-TCq0f5e4)Ms8&GOxB4@rPL#j{+8uG5N}dA@J0oD#o(&- zUK9R9dDA_TZ3&;q8o9C`UBHL=y>i$;=3%@OcJi@E=8C-=Q%?eB{$82yg>yJmK9>2y z{7x~i7#7K0vERRtFY&I(4fDe{BeTF$kFyK)GVz>GPfEXF`sq+lKA5u{gC*2`C|s91 zSIqxHF?yQrmtw8pE8h#VmrW^>yJG&(519D^ratEro-Z)x7v{^9|GMzFQV)Ctrj{%? zlTC#)RDKU!KCj2}iBAPva$w@@SE%0+cO?9SG!^HhEyb`1F?#p1)X!yCqOD|j-)L*VR&tG*s`KhUwWT-{g~@%Oa^Oax+;3 zd|auY3s=|E6vz2b=McxZx^PeN(EqRUz2ac(GvVcu$MRni&Nmg$d?BCu+v3?XqlT1v znJfbK6>Rb?I(uJ3eR^`r&7}Ba@+7G{zdkQaf6R+GryTiMg#7A$=fd}*q6gMT-cco= zepuTH^~Av+R(L1>d-2~&Q*q>T9;bU&=6l(&f6U*MBAF}R$DVNDc|Tb_8+7per^jE+ zsXesQu3=8^MAa+AO^9=H$qi|?NOg(V&lUJf=z=0^t=SUQ9&em0aj4tdOo?)5I_-sp rJ7wea&0I4*4VXNha_JMAkA#OC!QzsUU;uza!v diff --git a/cache/nmd_6d7e94a03F.pt b/cache/nmd_6d7e94a03F.pt deleted file mode 100644 index 0aefc880fea6fd01aff25b16bd0636dab28dfc9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 832 zcmWIWW@cev;NW1u0OAbX40*XJ@n$LJsg@>*2F7msDTyVCdIi}zZcgkBQ4r9;lw6Wu zl$@a#Us{rxQ_K}#l$unUnUfM6t#Ng-EIA-7(DH#=_su93X5ENf{sH3yTHM*%N>!fiP}gh%v~Z z`=Tths2CVXZcY+7yk3BOgLypKz$zI&Bmq!RV&A=3)cloVITmb&p@~gLSUbR!hj8w YU>WW~`Jj{;;LXYg5@QBJka~z(09MbJ1ONa4 diff --git a/cache/nmd_d5126beb3F.pt b/cache/nmd_d5126beb3F.pt deleted file mode 100644 index dd90f522e41a593ade46b145f3a7c934fcfebcbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 832 zcmWIWW@cev;NW1u0OAbX40*XJ@hPT;MrKK=NycvaDTyVCdIi}zZcgkBQ4r9;lw6Wu zl$@a#Us{rxQ_K}#l$unUnUfM6t#Ng-EIA-7(DH#=_su93X5ENf{sH3yTHM*%N>!fiP}gh%v~Z z`=Tths2CVXZcY+7yk3BOgLypKz$zI&Bmq!RV&A=3)cloVITmb&p@~gLSUbR!hj8w YU>WW~`Jj{;;LXYg5@QBJka~z(0I4UJ?f?J) diff --git a/pygod/detector/anomalydae.py b/pygod/detector/anomalydae.py index e1712d19..408b702f 100644 --- a/pygod/detector/anomalydae.py +++ b/pygod/detector/anomalydae.py @@ -181,6 +181,7 @@ def forward_model(self, data): weight = 1 - self.alpha pos_weight_a = self.eta / (1 + self.eta) pos_weight_s = self.theta / (1 + self.theta) + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], @@ -191,4 +192,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() + return loss, score.detach().cpu() \ No newline at end of file diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index 16db4a57..c2dc14df 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -204,6 +204,7 @@ def forward_model(self, data): x_, s_ = self.model(x, edge_index) h = self.model.emb + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], @@ -227,7 +228,7 @@ def _data_augmentation(self, data): Attribute, disproportionate Structure, high-degree Structure, outlying - + Parameters ----------- data : torch_geometric.data.Data @@ -286,4 +287,4 @@ def _data_augmentation(self, data): inv_idx[node_idx] = torch.arange(batch_size) edge_index_aug[1] = inv_idx[edge_index_aug[1]] - return feat_aug, edge_index_aug, label_aug + return feat_aug, edge_index_aug, label_aug \ No newline at end of file diff --git a/pygod/detector/dominant.py b/pygod/detector/dominant.py index afe20dcb..ade6f387 100644 --- a/pygod/detector/dominant.py +++ b/pygod/detector/dominant.py @@ -159,6 +159,7 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, edge_index) + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size, node_idx], @@ -167,4 +168,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() + return loss, score.detach().cpu() \ No newline at end of file diff --git a/pygod/detector/guide.py b/pygod/detector/guide.py index 8cfaf9c4..ec5fc409 100644 --- a/pygod/detector/guide.py +++ b/pygod/detector/guide.py @@ -193,6 +193,7 @@ def forward_model(self, data): edge_index = data.edge_index.to(self.device) x_, s_ = self.model(x, s, edge_index) + score = self.model.loss_func(x[:batch_size], x_[:batch_size], s[:batch_size], @@ -201,4 +202,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() + return loss, score.detach().cpu() \ No newline at end of file diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index 878de79e..5f73f54c 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -10,6 +10,7 @@ from . import DeepDetector from ..nn import OCGNNBase + class OCGNN(DeepDetector): """ One-Class Graph Neural Networks for Anomaly Detection in @@ -163,4 +164,4 @@ def forward_model(self, data): emb = self.model(x, edge_index) loss, score = self.model.loss_func(emb[:batch_size]) - return loss, score.detach().cpu() + return loss, score.detach().cpu() \ No newline at end of file diff --git a/pygod/nn/ocgnn.py b/pygod/nn/ocgnn.py index b4e94e58..ae186241 100644 --- a/pygod/nn/ocgnn.py +++ b/pygod/nn/ocgnn.py @@ -2,6 +2,7 @@ import torch.nn as nn from torch_geometric.nn import GCN + class OCGNNBase(nn.Module): """ One-Class Graph Neural Networks for Anomaly Detection in diff --git a/pygod/test/__init__.py b/pygod/test/__init__.py deleted file mode 100644 index e69de29b..00000000 From 76f3fb9be8c6034155c7e8dc957a66a96cc05be3 Mon Sep 17 00:00:00 2001 From: Ahmed Aly Date: Tue, 2 Jan 2024 23:19:07 -0500 Subject: [PATCH 8/9] Keep accepted changes and Revert rest --- docs/examples/intro.py | 2 +- pygod/detector/anomalydae.py | 2 +- pygod/detector/conad.py | 2 +- pygod/detector/dominant.py | 2 +- pygod/detector/guide.py | 2 +- pygod/detector/ocgnn.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/examples/intro.py b/docs/examples/intro.py index 76d4ba71..3c47f7a4 100644 --- a/docs/examples/intro.py +++ b/docs/examples/intro.py @@ -107,4 +107,4 @@ from pygod.metric import eval_roc_auc auc_score = eval_roc_auc(data.y, score) -print('AUC Score:', auc_score) \ No newline at end of file +print('AUC Score:', auc_score) diff --git a/pygod/detector/anomalydae.py b/pygod/detector/anomalydae.py index 408b702f..3f5919ef 100644 --- a/pygod/detector/anomalydae.py +++ b/pygod/detector/anomalydae.py @@ -192,4 +192,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() \ No newline at end of file + return loss, score.detach().cpu() diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index c2dc14df..888e41de 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -287,4 +287,4 @@ def _data_augmentation(self, data): inv_idx[node_idx] = torch.arange(batch_size) edge_index_aug[1] = inv_idx[edge_index_aug[1]] - return feat_aug, edge_index_aug, label_aug \ No newline at end of file + return feat_aug, edge_index_aug, label_aug diff --git a/pygod/detector/dominant.py b/pygod/detector/dominant.py index ade6f387..735f3658 100644 --- a/pygod/detector/dominant.py +++ b/pygod/detector/dominant.py @@ -168,4 +168,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() \ No newline at end of file + return loss, score.detach().cpu() diff --git a/pygod/detector/guide.py b/pygod/detector/guide.py index ec5fc409..30cb11a5 100644 --- a/pygod/detector/guide.py +++ b/pygod/detector/guide.py @@ -202,4 +202,4 @@ def forward_model(self, data): loss = torch.mean(score) - return loss, score.detach().cpu() \ No newline at end of file + return loss, score.detach().cpu() diff --git a/pygod/detector/ocgnn.py b/pygod/detector/ocgnn.py index 5f73f54c..07127e8a 100644 --- a/pygod/detector/ocgnn.py +++ b/pygod/detector/ocgnn.py @@ -164,4 +164,4 @@ def forward_model(self, data): emb = self.model(x, edge_index) loss, score = self.model.loss_func(emb[:batch_size]) - return loss, score.detach().cpu() \ No newline at end of file + return loss, score.detach().cpu() From b1d1d0508cb92b312fda31755cdc12f32ddb6f4e Mon Sep 17 00:00:00 2001 From: Ahmed Amer Ali <70305408+ahmed3amerai@users.noreply.github.com> Date: Tue, 2 Jan 2024 23:23:27 -0500 Subject: [PATCH 9/9] Cleaning the pull request --- pygod/detector/conad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygod/detector/conad.py b/pygod/detector/conad.py index 888e41de..d4a4a849 100644 --- a/pygod/detector/conad.py +++ b/pygod/detector/conad.py @@ -228,7 +228,7 @@ def _data_augmentation(self, data): Attribute, disproportionate Structure, high-degree Structure, outlying - + Parameters ----------- data : torch_geometric.data.Data