├── BWGNN.py
├── README.md
├── dataset.py
├── figures
├── heatmap.png
├── heterophily.png
└── topology.png
└── main.py
/BWGNN.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | import dgl.function as fn
5 | import sympy
6 | import scipy
7 | import numpy as np
8 | from torch import nn
9 | from torch.nn import init
10 |
11 | '''
12 | BWGNN model from "https://github.com/squareRoot3/Rethinking-Anomaly-Detection"
13 | '''
14 | class PolyConv(nn.Module):
15 | def __init__(self,
16 | in_feats,
17 | out_feats,
18 | theta,
19 | activation=F.leaky_relu,
20 | lin=False,
21 | bias=False):
22 | super(PolyConv, self).__init__()
23 | self._theta = theta
24 | self._k = len(self._theta)
25 | self._in_feats = in_feats
26 | self._out_feats = out_feats
27 | self.activation = activation
28 | self.linear = nn.Linear(in_feats, out_feats, bias)
29 | self.lin = lin
30 | # self.reset_parameters()
31 | # self.linear2 = nn.Linear(out_feats, out_feats, bias)
32 |
33 | def reset_parameters(self):
34 | if self.linear.weight is not None:
35 | init.xavier_uniform_(self.linear.weight)
36 | if self.linear.bias is not None:
37 | init.zeros_(self.linear.bias)
38 |
39 | def forward(self, graph, feat):
40 | def unnLaplacian(feat, D_invsqrt, graph):
41 | """ Operation Feat * D^-1/2 A D^-1/2 """
42 | graph.ndata['h'] = feat * D_invsqrt
43 | graph.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'h'))
44 | return feat - graph.ndata.pop('h') * D_invsqrt
45 |
46 | with graph.local_scope():
47 | D_invsqrt = torch.pow(graph.in_degrees().float().clamp(
48 | min=1), -0.5).unsqueeze(-1).to(feat.device)
49 | h = self._theta[0]*feat
50 | for k in range(1, self._k):
51 | feat = unnLaplacian(feat, D_invsqrt, graph)
52 | h += self._theta[k]*feat
53 | h_copy = h
54 | if self.lin:
55 | h = self.linear(h)
56 | h = self.activation(h)
57 | return h
58 |
59 | class PolyConvBatch(nn.Module):
60 | def __init__(self,
61 | in_feats,
62 | out_feats,
63 | theta,
64 | activation=F.leaky_relu,
65 | lin=False,
66 | bias=False):
67 | super(PolyConvBatch, self).__init__()
68 | self._theta = theta
69 | self._k = len(self._theta)
70 | self._in_feats = in_feats
71 | self._out_feats = out_feats
72 | self.activation = activation
73 |
74 | def reset_parameters(self):
75 | if self.linear.weight is not None:
76 | init.xavier_uniform_(self.linear.weight)
77 | if self.linear.bias is not None:
78 | init.zeros_(self.linear.bias)
79 |
80 | def forward(self, block, feat):
81 | def unnLaplacian(feat, D_invsqrt, block):
82 | """ Operation Feat * D^-1/2 A D^-1/2 """
83 | block.srcdata['h'] = feat * D_invsqrt
84 | block.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'h'))
85 | return feat - block.srcdata.pop('h') * D_invsqrt
86 |
87 | with block.local_scope():
88 | D_invsqrt = torch.pow(block.out_degrees().float().clamp(
89 | min=1), -0.5).unsqueeze(-1).to(feat.device)
90 | h = self._theta[0]*feat
91 | for k in range(1, self._k):
92 | feat = unnLaplacian(feat, D_invsqrt, block)
93 | h += self._theta[k]*feat
94 | return h
95 |
96 |
97 | def calculate_theta2(d):
98 | thetas = []
99 | x = sympy.symbols('x')
100 | for i in range(d+1):
101 | f = sympy.poly((x/2) ** i * (1 - x/2) ** (d-i) / (scipy.special.beta(i+1, d+1-i)))
102 | coeff = f.all_coeffs()
103 | inv_coeff = []
104 | for i in range(d+1):
105 | inv_coeff.append(float(coeff[d-i]))
106 | thetas.append(inv_coeff)
107 | return thetas
108 |
109 |
110 | class BWGNN(nn.Module):
111 | def __init__(self, in_feats, h_feats, num_classes, graph, d=2, batch=False):
112 | super(BWGNN, self).__init__()
113 | self.g = graph
114 | self.thetas = calculate_theta2(d=d)
115 | self.conv = []
116 | for i in range(len(self.thetas)):
117 | if not batch:
118 | self.conv.append(PolyConv(h_feats, h_feats, self.thetas[i], lin=False))
119 | else:
120 | self.conv.append(PolyConvBatch(h_feats, h_feats, self.thetas[i], lin=False))
121 | self.linear = nn.Linear(in_feats, h_feats)
122 | self.linear2 = nn.Linear(h_feats, h_feats)
123 | self.linear3 = nn.Linear(h_feats*len(self.conv), h_feats)
124 | self.linear4 = nn.Linear(h_feats, num_classes)
125 | self.act = nn.ReLU()
126 | self.d = d
127 |
128 | def forward(self, in_feat):
129 | h = self.linear(in_feat)
130 | h = self.act(h)
131 | h = self.linear2(h)
132 | h = self.act(h)
133 | h_final = torch.zeros([len(in_feat), 0])
134 | # h0_final = []
135 | for conv in self.conv:
136 | h0 = conv(self.g, h)
137 | h_final = torch.cat([h_final, h0], -1)
138 | # print(h_final.shape)
139 | h = self.linear3(h_final)
140 | h = self.act(h)
141 | h = self.linear4(h)
142 | return h
143 |
144 | def testlarge(self, g, in_feat):
145 | h = self.linear(in_feat)
146 | h = self.act(h)
147 | h = self.linear2(h)
148 | h = self.act(h)
149 | h_final = torch.zeros([len(in_feat), 0])
150 | for conv in self.conv:
151 | h0 = conv(g, h)
152 | h_final = torch.cat([h_final, h0], -1)
153 | # print(h_final.shape)
154 | h = self.linear3(h_final)
155 | h = self.act(h)
156 | h = self.linear4(h)
157 | return h
158 |
159 | def batch(self, blocks, in_feat):
160 | h = self.linear(in_feat)
161 | h = self.act(h)
162 | h = self.linear2(h)
163 | h = self.act(h)
164 |
165 | h_final = torch.zeros([len(in_feat),0])
166 | for conv in self.conv:
167 | h0 = conv(blocks[0], h)
168 | h_final = torch.cat([h_final, h0], -1)
169 | # print(h_final.shape)
170 | h = self.linear3(h_final)
171 | h = self.act(h)
172 | h = self.linear4(h)
173 | return h
174 |
175 |
176 | # heterogeneous graph
177 | class BWGNN_Hetero(nn.Module):
178 | def __init__(self, in_feats, h_feats, num_classes, graph, d=2):
179 | super(BWGNN_Hetero, self).__init__()
180 | self.g = graph
181 | self.thetas = calculate_theta2(d=d)
182 | self.h_feats = h_feats
183 | self.conv = [PolyConv(h_feats, h_feats, theta, lin=False) for theta in self.thetas]
184 | self.linear = nn.Linear(in_feats, h_feats)
185 | self.linear2 = nn.Linear(h_feats, h_feats)
186 | self.linear3 = nn.Linear(h_feats*len(self.conv), h_feats)
187 | self.linear4 = nn.Linear(h_feats, num_classes)
188 | self.act = nn.LeakyReLU()
189 | # print(self.thetas)
190 | for param in self.parameters():
191 | print(type(param), param.size())
192 |
193 | def forward(self, in_feat):
194 | h = self.linear(in_feat)
195 | h = self.act(h)
196 | h = self.linear2(h)
197 | h = self.act(h)
198 | h_all = []
199 |
200 | for relation in self.g.canonical_etypes:
201 | # print(relation)
202 | h_final = torch.zeros([len(in_feat), 0])
203 | for conv in self.conv:
204 | h0 = conv(self.g[relation], h)
205 | h_final = torch.cat([h_final, h0], -1)
206 | # print(h_final.shape)
207 | h = self.linear3(h_final)
208 | h_all.append(h)
209 |
210 | h_all = torch.stack(h_all).sum(0)
211 | h_all = self.act(h_all)
212 | h_all = self.linear4(h_all)
213 | return h_all
214 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GHRN: Addressing Heterophily in Graph Anomaly Detection: A Perspective of Graph Spectrum
2 |
3 | This is a PyTorch implementation of
4 |
5 | Addressing Heterophily in Graph Anomaly Detection: A Perspective of Graph Spectrum (WWW2023)
6 |
7 | # Overview
8 | In this work, we aim to address the heterophily problem in the spectral domain. We point out that heterophily is positively associated with the frequency of a graph. Towards this end, we could prune inter-class edges by simply emphasizing and delineating the high-frequency components of the graph. We adopt graph Laplacian to measure the extent of 1-hop label changing of the center node and indicate high-frequency components. Our indicator can effectively reduce the heterophily degree of the graph and is less likely to be influenced by the prediction error.
9 |
10 |
11 |
12 |
13 |
14 | # Some questions
15 | 1. What is heterophily and how does it affect the performance of the GNNs?
16 | Heterophily indicates the edges connecting nodes with different labels. Low-pass filters like GCN could undermine the discriminative
17 | information of the anomalies on heterophilous graphs.
18 |
19 |
20 |
21 |
22 |
23 | 2. How does indicator work?
24 | GHRN will calculate the post-aggregation matrix for the graph, and a smaller value means a larger probability of the inter-class edges.
25 |
26 |