├── README.md ├── dataset └── get_data.sh ├── gckn ├── __init__.py ├── data.py ├── data_io.py ├── dynamic_pooling │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ └── pooling.cpython-36.pyc │ ├── pooling.cpp │ ├── pooling.py │ ├── pooling_cpu.cpython-36m-x86_64-linux-gnu.so │ ├── pooling_cuda.cpp │ ├── pooling_cuda.cpython-36m-x86_64-linux-gnu.so │ └── pooling_cuda_kernel.cu ├── gckn_fast │ ├── __init__.py │ ├── gckn_fast.cpp │ ├── gckn_fast.py │ ├── gckn_fast_cpu.cpython-36m-x86_64-linux-gnu.so │ ├── gckn_fast_cuda.cpp │ ├── gckn_fast_cuda.cpython-36m-x86_64-linux-gnu.so │ └── gckn_fast_cuda_kernel.cu ├── graphs │ ├── __init__.py │ ├── graphs_fast.c │ ├── graphs_fast.cpython-36m-x86_64-linux-gnu.so │ ├── graphs_fast.pyx │ └── setup.py ├── kernels.py ├── layers.py ├── loss.py ├── models.py ├── ops.py ├── path_conv_agg.py ├── setup.py └── utils.py ├── main.py └── result ├── gskn_MUTAG.txt └── labels.txt /README.md: -------------------------------------------------------------------------------- 1 | # GSKN: Theoretically Improving Graph Neural Networks via Anonymous Walk Graph Kernels 2 | 3 | The repository implements GSKN described in the following paper 4 | > GSKN: Theoretically Improving Graph Neural Networks via Anonymous Walk Graph Kernels (WWW 2021, Research Track, Full Paper) 5 | 6 | For more details, please see our [Paper](https://arxiv.org/abs/2104.02995). 7 | 8 | #### Installation 9 | We strongly recommend users to use miniconda to install the following packages 10 | 11 | ``` 12 | python=3.6.2 13 | numpy 14 | scikit-learn=0.21 15 | pytorch=1.3.1 16 | torchvision=0.4.2 17 | pandas 18 | networkx 19 | Cython 20 | cyanure 21 | ``` 22 | 23 | All the above packages can be installed with `conda install` except `cyanure`, which can be installed with `pip install cyanure-mkl`. 24 | 25 | CUDA Toolkit also needs to be downloaded with the same version as used in Pytorch. Then place it under the path `$PATH_TO_CUDA` and run `export CUDA_HOME=$PATH_TO_CUDA`. 26 | 27 | Finally run `make`, and it may take few minutes to compile. 28 | 29 | 30 | #### Data 31 | 32 | 33 | Run `cd dataset; bash get_data.sh` to download and unzip datasets. We provide here 3 types of datasets: datasets without node attributes (IMDBBINARY, IMDBMULTI, COLLAB), datasets with discrete node attributes (MUTAG, PROTEINS, PTC) and datasets with continuous node attributes (BZR, COX2, PROTEINS_full). All the datasets can be downloaded and extracted from [this site](https://ls11-www.cs.tu-dortmund.de/staff/morris/graphkerneldatasets). 34 | 35 | #### run 36 | 37 | ``` 38 | export PYTHONPATH=$PWD:$PYTHONPATH 39 | python main.py --dataset MUTAG --sigma 1.5 --hidden_size 16 --aggregation --anonymous_walk_length 6 --anonymous_walks_per_node 30 40 | ``` 41 | 42 | #### Acknowledgments 43 | Certain parts of this project are partially derived from [GCKN](https://github.com/claying/GCKN) and [GraphSTONE](https://github.com/YimiAChack/GraphSTONE). 44 | -------------------------------------------------------------------------------- /dataset/get_data.sh: -------------------------------------------------------------------------------- 1 | wget http://pascal.inrialpes.fr/data2/dchen/data/graphs.zip 2 | unzip graphs.zip -------------------------------------------------------------------------------- /gckn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/__init__.py -------------------------------------------------------------------------------- /gckn/data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import random 4 | import networkx as nx 5 | import numpy as np 6 | import torch 7 | from sklearn.model_selection import StratifiedKFold 8 | from .data_io import load_graphdata 9 | from .graphs import get_paths, get_walks 10 | 11 | 12 | DATA = ['Mutagenicity', 'BZR', 'COX2', 'ENZYMES', 'FRANKENSTEIN', 'PROTEINS_full'] 13 | 14 | class S2VGraph(object): 15 | def __init__(self, g, label, node_tags=None, node_features=None): 16 | ''' 17 | g: a networkx graph 18 | label: an integer graph label 19 | node_tags: a list of integer node tags 20 | node_features: a numpy float tensor, one-hot representation of the tag that is used as input to neural nets 21 | neighbors: list of neighbors (without self-loop) 22 | ''' 23 | self.label = label 24 | self.g = g 25 | self.node_tags = node_tags 26 | self.neighbors = [] 27 | self.node_features = 0 28 | 29 | self.max_neighbor = 0 30 | self.mean_neighbor = 0 31 | 32 | 33 | def load_data(dataset, datapath='dataset', degree_as_tag=False): 34 | ''' 35 | dataset: name of dataset 36 | test_proportion: ratio of test train split 37 | seed: random seed for random splitting of dataset 38 | ''' 39 | print('loading data') 40 | if dataset in DATA: 41 | return load_graphdata(dataset, datapath) 42 | g_list = [] 43 | label_dict = {} 44 | feat_dict = {} 45 | 46 | with open('{}/{}/{}.txt'.format(datapath, dataset, dataset), 'r') as f: 47 | n_g = int(f.readline().strip()) 48 | for i in range(n_g): 49 | row = f.readline().strip().split() 50 | n, l = [int(w) for w in row] 51 | if not l in label_dict: 52 | mapped = len(label_dict) 53 | label_dict[l] = mapped 54 | g = nx.Graph() 55 | node_tags = [] 56 | node_features = [] 57 | n_edges = 0 58 | for j in range(n): 59 | g.add_node(j) 60 | row = f.readline().strip().split() 61 | tmp = int(row[1]) + 2 62 | if tmp == len(row): 63 | # no node attributes 64 | row = [int(w) for w in row] 65 | attr = None 66 | else: 67 | row, attr = [int(w) for w in row[:tmp]], np.array([float(w) for w in row[tmp:]]) 68 | if not row[0] in feat_dict: 69 | mapped = len(feat_dict) 70 | feat_dict[row[0]] = mapped 71 | node_tags.append(feat_dict[row[0]]) 72 | 73 | if tmp > len(row): 74 | node_features.append(attr) 75 | 76 | n_edges += row[1] 77 | for k in range(2, len(row)): 78 | g.add_edge(j, row[k]) 79 | 80 | if node_features != []: 81 | node_features = np.stack(node_features) 82 | node_feature_flag = True 83 | else: 84 | node_features = None 85 | node_feature_flag = False 86 | 87 | assert len(g) == n 88 | 89 | g_list.append(S2VGraph(g, l, node_tags)) 90 | 91 | 92 | for g in g_list: 93 | g.neighbors = [[] for i in range(len(g.g))] 94 | for i, j in g.g.edges(): 95 | g.neighbors[i].append(j) 96 | g.neighbors[j].append(i) 97 | degree_list = [] 98 | for i in range(len(g.g)): 99 | g.neighbors[i] = g.neighbors[i] 100 | degree_list.append(len(g.neighbors[i])) 101 | g.max_neighbor = max(degree_list) 102 | g.mean_neighbor = (sum(degree_list) + len(degree_list) - 1) // len(degree_list) 103 | 104 | g.label = label_dict[g.label] 105 | 106 | edges = [list(pair) for pair in g.g.edges()] 107 | edges.extend([[i, j] for j, i in edges]) 108 | 109 | deg_list = list(dict(g.g.degree(range(len(g.g)))).values()) 110 | 111 | if degree_as_tag: 112 | for g in g_list: 113 | g.node_tags = list(dict(g.g.degree(range(len(g.g)))).values()) 114 | 115 | #Extracting unique tag labels 116 | tagset = set([]) 117 | for g in g_list: 118 | tagset = tagset.union(set(g.node_tags)) 119 | 120 | tagset = list(tagset) 121 | tag2index = {tagset[i]:i for i in range(len(tagset))} 122 | 123 | for g in g_list: 124 | g.node_features = np.zeros([len(g.node_tags), len(tagset)]) 125 | g.node_features[range(len(g.node_tags)), [tag2index[tag] for tag in g.node_tags]] = 1 126 | 127 | 128 | print('# classes: %d' % len(label_dict)) 129 | print('# maximum node tag: %d' % len(tagset)) 130 | 131 | print("# data: %d" % len(g_list)) 132 | 133 | return g_list, len(label_dict) 134 | 135 | def separate_data(graph_list, seed, fold_idx): 136 | assert 0 <= fold_idx and fold_idx < 10, "fold_idx must be from 0 to 9." 137 | skf = StratifiedKFold(n_splits=10, shuffle = True, random_state = seed) 138 | 139 | labels = [graph.label for graph in graph_list] 140 | idx_list = [] 141 | for idx in skf.split(np.zeros(len(labels)), labels): 142 | idx_list.append(idx) 143 | train_idx, test_idx = idx_list[fold_idx] 144 | 145 | train_graph_list = [graph_list[i] for i in train_idx] 146 | test_graph_list = [graph_list[i] for i in test_idx] 147 | 148 | return train_graph_list, test_graph_list 149 | 150 | def get_path_indices(paths, n_paths_for_graph, n_nodes): 151 | """ 152 | paths: all_paths x k 153 | n_paths:: n_graphs (sum=all_paths) 154 | """ 155 | incr_indices = torch.cat([torch.zeros(1, dtype=torch.long), n_nodes[:-1]]) 156 | incr_indices = incr_indices.cumsum(dim=0) 157 | incr_indices = incr_indices.repeat_interleave(n_paths_for_graph, dim=0).view(-1, 1) 158 | paths = paths + incr_indices 159 | return paths 160 | 161 | class PathLoader(object): 162 | def __init__(self, graphs, k, batch_size, anonymous_walk_length, anonymous_walks_per_node, aggregation=True, 163 | dataset='MUTAG', padding=False, walk=False, mask=False): 164 | # self.data = data 165 | self.dataset = dataset 166 | self.graphs = graphs 167 | self.batch_size = batch_size 168 | self.aggregation = aggregation 169 | self.input_size = graphs[0].node_features.shape[-1] 170 | self.n = len(graphs) 171 | self.k = k 172 | self.data = None 173 | self.labels = None 174 | self.padding = padding 175 | self.walk = walk 176 | self.mask = mask 177 | self.anonymous_walk_length = anonymous_walk_length 178 | self.anonymous_walks_per_node = anonymous_walks_per_node 179 | 180 | def __len__(self): 181 | return (self.n + self.batch_size - 1) // self.batch_size 182 | 183 | def get_all_paths(self, dirname=None): 184 | all_paths = [] 185 | n_paths = [] 186 | anonymous_walks = [] 187 | n_nodes = torch.zeros(self.n, dtype=torch.long) 188 | if self.aggregation: 189 | n_paths_for_graph = torch.zeros((self.n, self.k), dtype=torch.long) 190 | # else: 191 | # n_paths_for_graph = torch.zeros(self.n, dtype=torch.long) 192 | features = [] 193 | labels = torch.zeros(self.n, dtype=torch.long) 194 | mask_vec = [] 195 | if dirname is not None and self.dataset == 'COLLAB': 196 | if os.path.exists(dirname + '/all_paths_{}.pkl'.format(self.k)): 197 | self.data = torch.load(dirname + '/all_paths_{}.pkl'.format(self.k)) 198 | self.labels = self.data['labels'] 199 | return 200 | 201 | for i, g in enumerate(self.graphs): 202 | print("graph id:", i) 203 | if self.walk: 204 | p, c = get_walks(g, self.k) 205 | else: 206 | p, c = get_paths(g, self.k) 207 | 208 | if self.aggregation: 209 | all_paths.append([torch.from_numpy(p[j]) for j in range(self.k)]) 210 | n_paths.append([torch.from_numpy(c[:, j]) for j in range(self.k)]) 211 | n_paths_for_graph[i] = torch.LongTensor([len(p[j]) for j in range(self.k)]) 212 | if self.mask: 213 | mask_vec.append([torch.ones(len(p[j])) for j in range(self.k)]) 214 | # else: 215 | # all_paths.append(torch.from_numpy(p[-1])) 216 | # n_paths.append(torch.from_numpy(c[:, -1])) 217 | # n_paths_for_graph[i] = len(p[-1]) 218 | # mask_vec.append(torch.ones(len(p[-1]))) 219 | n_nodes[i] = len(g.neighbors) 220 | features.append(torch.from_numpy(g.node_features.astype('float32'))) 221 | 222 | labels[i] = g.label 223 | graph_anonymous_walks = self.generate_anonymous_walks(g) 224 | anonymous_walks.append(graph_anonymous_walks) 225 | 226 | self.data = { 227 | 'features': features, 228 | 'paths': all_paths, 229 | 'n_paths': n_paths, 230 | 'n_paths_for_graph': n_paths_for_graph, 231 | 'n_nodes': n_nodes, 232 | 'labels': labels, 233 | 'anonymous_walks': anonymous_walks, # [[g1], [g2]] 234 | } 235 | self.mask_vec = mask_vec 236 | self.labels = labels 237 | if dirname is not None and self.dataset == 'COLLAB': 238 | torch.save(self.data, dirname + '/all_paths_{}.pkl'.format(self.k)) 239 | 240 | def generate_random_walk(self, g=None, start=None, rand=random.Random(), return_prob = 0): 241 | path = [start] 242 | while len(path) < self.anonymous_walk_length: 243 | cur = path[-1] 244 | if len(g.neighbors[cur]) > 0: 245 | path.append(np.random.choice(a= g.neighbors[cur], replace=False)) 246 | else: 247 | break 248 | 249 | random_walk_seq = [str(node) for node in path] 250 | while len(random_walk_seq) < self.anonymous_walk_length: 251 | random_walk_seq.append(random_walk_seq[-1]) 252 | return random_walk_seq 253 | 254 | def random_to_anonymous_walk(self, random_walk_seq): 255 | cnt = 0 256 | node_cnt = dict() 257 | anonymous_walk_seq = [] 258 | for node in random_walk_seq: 259 | if node not in node_cnt: 260 | node_cnt[node] = cnt 261 | cnt += 1 262 | anonymous_walk_seq.append(node_cnt[node]) 263 | 264 | anonymous_walk_seq = [int(node) for node in anonymous_walk_seq] 265 | return anonymous_walk_seq 266 | 267 | def generate_anonymous_walks(self, g=None): # qingqing 268 | walks = [] 269 | for node in g.g.nodes(): 270 | for _ in range(self.anonymous_walks_per_node): 271 | random_walk = self.generate_random_walk(g, node) 272 | anonymous_walk = self.random_to_anonymous_walk(random_walk) 273 | walks.append(anonymous_walk) 274 | 275 | walks = torch.from_numpy(np.array(walks)) 276 | return walks 277 | 278 | def make_batch(self, shuffle=True): 279 | if shuffle: 280 | indices = np.random.permutation(self.n) 281 | else: 282 | indices = list(range(self.n)) 283 | features = self.data['features'] 284 | anonymous_walks = self.data['anonymous_walks'] 285 | # print("#", self.data['n_paths'][0]) 286 | 287 | for index in range(0, self.n, self.batch_size): 288 | idx = indices[index:min(index + self.batch_size, self.n)] 289 | current_features = torch.cat([features[i] for i in idx]) 290 | current_anonymous_walks = torch.cat([anonymous_walks[i] for i in idx]) 291 | # current_anonymous_walks : [num_total_nodes * aw_per_node, aw_length] 292 | 293 | if self.padding: 294 | current_features = torch.cat([torch.zeros(self.input_size).view(1, -1), current_features]) 295 | 296 | if self.aggregation: 297 | current_paths = [torch.cat([self.data['paths'][i][j] for i in idx]) for j in range(self.k)] 298 | else: 299 | current_paths = torch.cat([self.data['paths'][i] for i in idx]) + self.padding 300 | current_n_paths_for_graph = self.data['n_paths_for_graph'][idx] 301 | current_n_nodes = self.data['n_nodes'][idx] 302 | 303 | 304 | if self.aggregation: 305 | current_paths = [get_path_indices( 306 | current_paths[j], current_n_paths_for_graph[:, j], 307 | current_n_nodes) for j in range(self.k)] 308 | current_n_paths = [torch.cat( 309 | [self.data['n_paths'][i][j] for i in idx]) for j in range(self.k)] 310 | else: 311 | current_paths = get_path_indices( 312 | current_paths, current_n_paths_for_graph, current_n_nodes) 313 | current_n_paths = torch.cat([self.data['n_paths'][i] for i in idx]) 314 | yield {'features': current_features, 315 | 'paths': current_paths, 316 | 'n_paths': current_n_paths, 317 | 'n_nodes': current_n_nodes, 318 | 'labels': self.labels[idx], 319 | 'anonymous_walks': current_anonymous_walks 320 | } 321 | -------------------------------------------------------------------------------- /gckn/data_io.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import statistics 4 | import numpy as np 5 | import networkx as nx 6 | 7 | 8 | class S2VGraph(object): 9 | def __init__(self, g, label, node_tags=None, node_features=None): 10 | ''' 11 | g: a networkx graph 12 | label: an integer graph label 13 | node_tags: a list of integer node tags 14 | node_features: a numpy float tensor, one-hot representation of the tag that is used as input to neural nets 15 | neighbors: list of neighbors (without self-loop) 16 | ''' 17 | self.label = label 18 | self.g = g 19 | self.node_tags = node_tags 20 | self.neighbors = [] 21 | self.node_features = 0 22 | 23 | self.max_neighbor = 0 24 | self.mean_neighbor = 0 25 | 26 | def load_graphdata(dataname, datadir='dataset', max_nodes=None, edge_labels=False): 27 | """ Read data from https://ls11-www.cs.tu-dortmund.de/staff/morris/graphkerneldatasets 28 | graph index starts with 1 in file 29 | 30 | Returns: 31 | List of networkx objects with graph and node labels 32 | """ 33 | prefix = os.path.join(datadir, dataname, dataname) 34 | filename_graph_indic = prefix + "_graph_indicator.txt" 35 | # index of graphs that a given node belongs to 36 | graph_indic = {} 37 | with open(filename_graph_indic) as f: 38 | i = 1 39 | for line in f: 40 | line = line.strip("\n") 41 | graph_indic[i] = int(line) 42 | i += 1 43 | 44 | filename_nodes = prefix + "_node_labels.txt" 45 | node_labels = [] 46 | min_label_val = None 47 | try: 48 | with open(filename_nodes) as f: 49 | for line in f: 50 | line = line.strip("\n") 51 | l = int(line) 52 | node_labels += [l] 53 | if min_label_val is None or min_label_val > l: 54 | min_label_val = l 55 | # assume that node labels are consecutive 56 | num_unique_node_labels = max(node_labels) - min_label_val + 1 57 | node_labels = [l - min_label_val for l in node_labels] 58 | except IOError: 59 | print("No node labels") 60 | 61 | filename_node_attrs = prefix + "_node_attributes.txt" 62 | node_attrs = [] 63 | try: 64 | with open(filename_node_attrs) as f: 65 | for line in f: 66 | line = line.strip("\s\n") 67 | attrs = [ 68 | float(attr) for attr in re.split("[,\s]+", line) if not attr == "" 69 | ] 70 | node_attrs.append(np.array(attrs)) 71 | except IOError: 72 | print("No node attributes") 73 | 74 | label_has_zero = False 75 | filename_graphs = prefix + "_graph_labels.txt" 76 | graph_labels = [] 77 | 78 | label_vals = [] 79 | with open(filename_graphs) as f: 80 | for line in f: 81 | line = line.strip("\n") 82 | val = int(line) 83 | if val not in label_vals: 84 | label_vals.append(val) 85 | graph_labels.append(val) 86 | 87 | label_map_to_int = {val: i for i, val in enumerate(label_vals)} 88 | graph_labels = np.array([label_map_to_int[l] for l in graph_labels]) 89 | 90 | if edge_labels: 91 | # For Tox21_AHR we want to know edge labels 92 | filename_edges = prefix + "_edge_labels.txt" 93 | edge_labels = [] 94 | 95 | edge_label_vals = [] 96 | with open(filename_edges) as f: 97 | for line in f: 98 | line = line.strip("\n") 99 | val = int(line) 100 | if val not in edge_label_vals: 101 | edge_label_vals.append(val) 102 | edge_labels.append(val) 103 | 104 | edge_label_map_to_int = {val: i for i, val in enumerate(edge_label_vals)} 105 | 106 | filename_adj = prefix + "_A.txt" 107 | adj_list = {i: [] for i in range(1, len(graph_labels) + 1)} 108 | # edge_label_list={i:[] for i in range(1,len(graph_labels)+1)} 109 | index_graph = {i: [] for i in range(1, len(graph_labels) + 1)} 110 | num_edges = 0 111 | with open(filename_adj) as f: 112 | for line in f: 113 | line = line.strip("\n").split(",") 114 | e0, e1 = (int(line[0].strip(" ")), int(line[1].strip(" "))) 115 | adj_list[graph_indic[e0]].append((e0, e1)) 116 | index_graph[graph_indic[e0]] += [e0, e1] 117 | # edge_label_list[graph_indic[e0]].append(edge_labels[num_edges]) 118 | num_edges += 1 119 | for k in index_graph.keys(): 120 | index_graph[k] = [u - 1 for u in set(index_graph[k])] 121 | 122 | graphs = [] 123 | for i in range(1, 1 + len(adj_list)): 124 | # indexed from 1 here 125 | G = nx.from_edgelist(adj_list[i]) 126 | 127 | if max_nodes is not None and G.number_of_nodes() > max_nodes: 128 | continue 129 | 130 | # add features and labels 131 | G.graph["label"] = graph_labels[i - 1] 132 | 133 | # Special label for aromaticity experiment 134 | # aromatic_edge = 2 135 | # G.graph['aromatic'] = aromatic_edge in edge_label_list[i] 136 | 137 | for u in G.nodes(): 138 | if len(node_labels) > 0: 139 | node_label_one_hot = [0] * num_unique_node_labels 140 | node_label = node_labels[u - 1] 141 | node_label_one_hot[node_label] = 1 142 | G.nodes[u]["label"] = node_label_one_hot 143 | G.nodes[u]["tag"] = node_label 144 | if len(node_attrs) > 0: 145 | G.nodes[u]["feat"] = node_attrs[u - 1] 146 | if len(node_attrs) > 0: 147 | G.graph["feat_dim"] = node_attrs[0].shape[0] 148 | 149 | # relabeling 150 | mapping = {} 151 | it = 0 152 | if float(nx.__version__) < 2.0: 153 | for n in G.nodes(): 154 | mapping[n] = it 155 | it += 1 156 | else: 157 | for n in G.nodes: 158 | mapping[n] = it 159 | it += 1 160 | 161 | # indexed from 0 162 | G = nx.relabel_nodes(G, mapping) 163 | 164 | l = int(G.graph['label']) 165 | adj = [[] for i in range(len(G))] 166 | for i, j in G.edges(): 167 | adj[i].append(j) 168 | adj[j].append(i) 169 | 170 | degree_list = [] 171 | for i in range(len(G)): 172 | degree_list.append(len(adj[i])) 173 | if len(node_attrs) > 0: 174 | node_features = [G.nodes[u]['feat'] for u in G.nodes()] 175 | node_features = np.asarray(node_features) 176 | if len(node_labels) > 0: 177 | node_labels_one_hot = np.asarray([G.nodes[u]['label'] for u in G.nodes()]) 178 | #node_features = np.hstack([node_features, node_labels_one_hot]) 179 | else: 180 | node_features = [G.nodes[u]['label'] for u in G.nodes()] 181 | node_features = np.asarray(node_features) 182 | node_labels_one_hot = node_features 183 | # node_features = node_features / np.linalg.norm(node_features, axis=-1, keepdims=True).clip(min=1e-05) 184 | # print(node_labels) 185 | G = S2VGraph(G, l, node_labels) 186 | if edge_labels: 187 | G.edge_labels = edge_labels 188 | G.neighbors = adj 189 | G.max_neighbor = max(degree_list) 190 | G.mean_neighbor = (sum(degree_list) + len(degree_list) - 1) // len(degree_list) 191 | G.degree_list = degree_list 192 | G.node_features = node_features 193 | G.node_labels = node_labels_one_hot 194 | graphs.append(G) 195 | return graphs, int(max(graph_labels)) + 1 196 | 197 | def get_motif(mask, path_indices, graph, max_component=True, eps=0.1): 198 | if not isinstance(mask, list): 199 | mask = [mask] 200 | if not isinstance(path_indices, list): 201 | path_indices = [path_indices] 202 | g = nx.Graph() 203 | g.add_nodes_from(graph.nodes()) 204 | n = len(g.nodes()) 205 | for node in graph.nodes(): 206 | g.nodes[node]['tag'] = graph.nodes[node]['tag'] 207 | 208 | # edge_list = [] 209 | adj = np.zeros((n, n)) 210 | for m, path in zip(mask, path_indices): 211 | if len(path[0]) <= 1: 212 | continue 213 | for i in range(len(m)): 214 | if m[i] > eps: 215 | p = path[i] 216 | for j in range(len(p) - 1): 217 | adj[p[j], p[j+1]] += m[i] 218 | adj /= np.max(adj) 219 | edge_list = [(i, j, adj[i, j]) for i in range(n) for j in range(n) if adj[i, j] > eps] 220 | # print(adj) 221 | g.add_weighted_edges_from(edge_list) 222 | 223 | if max_component: 224 | largest_cc = max(nx.connected_components(g), key=len) 225 | g = g.subgraph(largest_cc).copy() 226 | else: 227 | # remove zero degree nodes 228 | g.remove_nodes_from(list(nx.isolates(g))) 229 | return g 230 | 231 | 232 | def log_graph( 233 | graph, 234 | outdir, 235 | filename, 236 | identify_self=False, 237 | nodecolor="tag", 238 | fig_size=(4, 3), 239 | dpi=300, 240 | label_node_feat=True, 241 | edge_vmax=None, 242 | args=None, 243 | eps=1e-6, 244 | ): 245 | """ 246 | Args: 247 | nodecolor: the color of node, can be determined by 'label', or 'feat'. For feat, it needs to 248 | be one-hot' 249 | """ 250 | if len(graph.edges) == 0: 251 | return 252 | import matplotlib.pyplot as plt 253 | plt.switch_backend("agg") 254 | cmap = plt.get_cmap("tab20") 255 | plt.switch_backend("agg") 256 | fig = plt.figure(figsize=fig_size, dpi=dpi) 257 | 258 | node_colors = [] 259 | # edge_colors = [min(max(w, 0.0), 1.0) for (u,v,w) in Gc.edges.data('weight', default=1)] 260 | edge_colors = [w for (u, v, w) in graph.edges.data("weight", default=1)] 261 | 262 | # maximum value for node color 263 | vmax = 19 264 | # for i in graph.nodes(): 265 | # if nodecolor == "feat" and "feat" in graph.nodes[i]: 266 | # num_classes = graph.nodes[i]["feat"].size()[0] 267 | # if num_classes >= 10: 268 | # cmap = plt.get_cmap("tab20") 269 | # vmax = 19 270 | # elif num_classes >= 8: 271 | # cmap = plt.get_cmap("tab10") 272 | # vmax = 9 273 | # break 274 | 275 | feat_labels = {} 276 | for i in graph.nodes(): 277 | if identify_self and "self" in graph.nodes[i]: 278 | node_colors.append(0) 279 | elif nodecolor == "tag" and "tag" in graph.nodes[i]: 280 | node_colors.append(graph.nodes[i]["tag"]) 281 | feat_labels[i] = graph.nodes[i]["tag"] 282 | elif nodecolor == "feat" and "feat" in Gc.nodes[i]: 283 | # print(Gc.nodes[i]['feat']) 284 | feat = graph.nodes[i]["feat"].detach().numpy() 285 | # idx with pos val in 1D array 286 | feat_class = 0 287 | for j in range(len(feat)): 288 | if feat[j] == 1: 289 | feat_class = j 290 | break 291 | node_colors.append(feat_class) 292 | feat_labels[i] = feat_class 293 | else: 294 | node_colors.append(1) 295 | if not label_node_feat: 296 | feat_labels = None 297 | 298 | plt.switch_backend("agg") 299 | fig = plt.figure(figsize=fig_size, dpi=dpi) 300 | 301 | if graph.number_of_nodes() == 0: 302 | raise Exception("empty graph") 303 | if graph.number_of_edges() == 0: 304 | raise Exception("empty edge") 305 | # remove_nodes = [] 306 | if len(graph.nodes) > 20: 307 | pos_layout = nx.kamada_kawai_layout(graph, weight=None) 308 | else: 309 | pos_layout = nx.kamada_kawai_layout(graph, weight=None) 310 | 311 | weights = [d for (u, v, d) in graph.edges(data="weight", default=1)] 312 | if edge_vmax is None: 313 | edge_vmax = statistics.median_high( 314 | [d for (u, v, d) in graph.edges(data="weight", default=1)] 315 | ) 316 | min_color = min([d for (u, v, d) in graph.edges(data="weight", default=1)]) 317 | # color range: gray to black 318 | edge_vmin = 2 * min_color - edge_vmax 319 | print(edge_vmin) 320 | print(edge_vmax) 321 | print(edge_colors) 322 | nx.draw( 323 | graph, 324 | pos=pos_layout, 325 | with_labels=False, 326 | font_size=4, 327 | labels=feat_labels, 328 | node_color=node_colors, 329 | vmin=0, 330 | vmax=vmax, 331 | cmap=cmap, 332 | edge_color=edge_colors, 333 | edge_cmap=plt.get_cmap("Greys"), 334 | edge_vmin=edge_vmin-eps, 335 | edge_vmax=edge_vmax, 336 | width=1.3, 337 | node_size=100, 338 | alpha=0.9, 339 | ) 340 | fig.axes[0].xaxis.set_visible(False) 341 | fig.canvas.draw() 342 | 343 | save_path = os.path.join(outdir, filename) 344 | os.makedirs(os.path.dirname(save_path), exist_ok=True) 345 | nx.write_gpickle(graph, os.path.splitext(save_path)[0] + '.gpickle') 346 | plt.savefig(save_path, format="pdf") 347 | 348 | 349 | if __name__ == "__main__": 350 | graphs = load_data('Mutagenicity', '../dataset') 351 | # print(list(graphs[0].adjacency())) 352 | print([list(adj.keys()) for _, adj in graphs[0].adjacency()]) 353 | print(graphs[0].graph['label']) 354 | print(len(graphs)) 355 | -------------------------------------------------------------------------------- /gckn/dynamic_pooling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/dynamic_pooling/__init__.py -------------------------------------------------------------------------------- /gckn/dynamic_pooling/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/dynamic_pooling/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /gckn/dynamic_pooling/__pycache__/pooling.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/dynamic_pooling/__pycache__/pooling.cpython-36.pyc -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | template 8 | inline void THBlas_axpy(int64_t n, T a, T *x, int64_t incx, T *y, int64_t incy); 9 | 10 | #define AXPY_SPECIALIZATION(ctype,name) \ 11 | template<> \ 12 | inline void THBlas_axpy(int64_t n, ctype a, ctype *x, int64_t incx, \ 13 | ctype *y, int64_t incy) { \ 14 | TH ## name ## Blas_axpy(n, a, x, incx, y, incy); \ 15 | } 16 | 17 | AT_FORALL_SCALAR_TYPES(AXPY_SPECIALIZATION) 18 | 19 | 20 | template 21 | void dpooling_max_forward_worker( 22 | torch::Tensor output, 23 | const torch::Tensor input, const torch::Tensor kernel_size, 24 | const torch::Tensor indices, 25 | int64_t hidden_size, int64_t size_out) { 26 | 27 | auto kernel_size_accessor = kernel_size.accessor(); 28 | auto output_accessor = output.accessor(); 29 | auto input_accessor = input.accessor(); 30 | auto indices_accessor = indices.accessor(); 31 | 32 | torch::parallel_for(0, hidden_size, 0, [&](int64_t start, int64_t end) { 33 | for (auto col = start; col < end; ++col) { 34 | for (int64_t row = 0; row < size_out; ++row) { 35 | int64_t s = (row == 0) ? 0 : kernel_size_accessor[row - 1]; 36 | // auto max_val = std::numeric_limits::lowest(); 37 | scalar_t max_val = 0; 38 | int64_t max_index = -1; 39 | for (int64_t k = s; k < kernel_size_accessor[row]; ++k) { 40 | auto val = input_accessor[k][col]; 41 | if (val > max_val) { 42 | max_val = val; 43 | max_index = k; 44 | } 45 | } 46 | output_accessor[row][col] = max_val; 47 | indices_accessor[row][col] = max_index; 48 | } 49 | } 50 | }); 51 | } 52 | 53 | std::vector dpooling_max_forward_cpu( 54 | torch::Tensor input, 55 | torch::Tensor kernel_size) { 56 | // input: H_in x hidden_size 57 | // kernel_size: H_out; sum(kernel_size) = H 58 | // output: H_out x hidden_size 59 | 60 | const int64_t hidden_size = input.size(1); 61 | const int64_t size_out = kernel_size.size(0); 62 | auto output = torch::empty({size_out, hidden_size}, input.options()); 63 | auto indices = torch::empty({size_out, hidden_size}, kernel_size.options()); 64 | 65 | auto commonDtype = promoteTypes(output.scalar_type(), input.scalar_type()); 66 | 67 | // const auto indices_size = kernel_size.cumsum(0); 68 | 69 | AT_DISPATCH_FLOATING_TYPES( 70 | commonDtype, "dpooling_max_forward", [&] { 71 | dpooling_max_forward_worker(output, input, kernel_size, indices, hidden_size, size_out); 72 | } 73 | ); 74 | 75 | return {output, indices}; 76 | } 77 | 78 | template 79 | void dpooling_max_backward_worker( 80 | torch::Tensor d_input, 81 | const torch::Tensor d_output, const torch::Tensor indices, 82 | int64_t hidden_size, int64_t size) { 83 | 84 | auto d_output_accessor = d_output.accessor(); 85 | auto d_input_accessor = d_input.accessor(); 86 | auto indices_accessor = indices.accessor(); 87 | 88 | torch::parallel_for(0, hidden_size, 0, [&](int64_t start, int64_t end) { 89 | for (auto col = start; col < end; ++col) { 90 | torch::parallel_for(0, size, 0, [&](int64_t s, int64_t e) { 91 | for (auto row = s; row < e; ++row) { 92 | int64_t input_row = indices_accessor[row][col]; 93 | if (input_row != -1) 94 | d_input_accessor[input_row][col] = d_output_accessor[row][col]; 95 | } 96 | }); 97 | } 98 | }); 99 | } 100 | 101 | void dpooling_max_backward_cpu( 102 | torch::Tensor d_input, 103 | torch::Tensor d_output, 104 | torch::Tensor indices) { 105 | 106 | const auto size = d_output.size(0); 107 | const auto hidden_size = d_input.size(1); 108 | 109 | auto commonDtype = promoteTypes(d_output.scalar_type(), d_input.scalar_type()); 110 | 111 | AT_DISPATCH_FLOATING_TYPES( 112 | commonDtype, "dpooling_max_backward", [&] { 113 | dpooling_max_backward_worker(d_input, d_output, indices, hidden_size, size); 114 | } 115 | ); 116 | } 117 | 118 | template 119 | void dpooling_sum_forward_worker( 120 | torch::Tensor output, 121 | const torch::Tensor input, const torch::Tensor kernel_size, 122 | int64_t hidden_size, int64_t size_out, bool mean) { 123 | 124 | auto kernel_size_accessor = kernel_size.accessor(); 125 | scalar_t* output_ptr = output.data_ptr(); 126 | scalar_t* input_ptr = input.data_ptr(); 127 | 128 | torch::parallel_for(0, size_out, 0, [&](int64_t start, int64_t end) { 129 | for (auto row = start; row < end; ++row) { 130 | int64_t s = (row == 0) ? 0 : kernel_size_accessor[row - 1]; 131 | int64_t e = kernel_size_accessor[row]; 132 | scalar_t val = 1; 133 | if (mean) 134 | val = 1. / (e - s); 135 | for (int64_t k = s; k < e; ++k) { 136 | THBlas_axpy(hidden_size, val, 137 | input_ptr + k * hidden_size, 1, 138 | output_ptr + row * hidden_size, 1); 139 | } 140 | } 141 | }); 142 | } 143 | 144 | torch::Tensor dpooling_sum_forward_cpu( 145 | torch::Tensor input, 146 | torch::Tensor kernel_size, 147 | bool mean) { 148 | // input: H_in x hidden_size 149 | // kernel_size: H_out; sum(kernel_size) = H 150 | // output: H_out x hidden_size 151 | 152 | const int64_t hidden_size = input.size(1); 153 | const int64_t size_out = kernel_size.size(0); 154 | auto output = torch::zeros({size_out, hidden_size}, input.options()); 155 | 156 | auto commonDtype = promoteTypes(output.scalar_type(), input.scalar_type()); 157 | 158 | // const auto indices_size = kernel_size.cumsum(0); 159 | 160 | AT_DISPATCH_FLOATING_TYPES( 161 | commonDtype, "dpooling_sum_forward", [&] { 162 | dpooling_sum_forward_worker(output, input, kernel_size, hidden_size, size_out, mean); 163 | } 164 | ); 165 | 166 | return output; 167 | } 168 | 169 | template 170 | void dpooling_sum_backward_worker( 171 | torch::Tensor d_input, 172 | const torch::Tensor d_output, const torch::Tensor kernel_size, 173 | int64_t hidden_size, int64_t size_out, bool mean) { 174 | 175 | auto kernel_size_accessor = kernel_size.accessor(); 176 | scalar_t* d_output_ptr = d_output.data_ptr(); 177 | scalar_t* d_input_ptr = d_input.data_ptr(); 178 | 179 | torch::parallel_for(0, size_out, 0, [&](int64_t start, int64_t end) { 180 | for (auto row = start; row < end; ++row) { 181 | int64_t s = (row == 0) ? 0 : kernel_size_accessor[row - 1]; 182 | int64_t e = kernel_size_accessor[row]; 183 | scalar_t val = 1; 184 | if (mean) 185 | val = 1. / (e - s); 186 | for (int64_t k = s; k < e; ++k) { 187 | THBlas_axpy(hidden_size, val, 188 | d_output_ptr + row * hidden_size, 1, 189 | d_input_ptr + k * hidden_size, 1); 190 | } 191 | } 192 | }); 193 | } 194 | 195 | void dpooling_sum_backward_cpu( 196 | torch::Tensor d_input, 197 | torch::Tensor d_output, 198 | torch::Tensor kernel_size, 199 | bool mean) { 200 | 201 | const auto size_out = d_output.size(0); 202 | const auto hidden_size = d_input.size(1); 203 | 204 | auto commonDtype = promoteTypes(d_output.scalar_type(), d_input.scalar_type()); 205 | 206 | AT_DISPATCH_FLOATING_TYPES( 207 | commonDtype, "dpooling_sum_backward", [&] { 208 | dpooling_sum_backward_worker(d_input, d_output, kernel_size, hidden_size, size_out, mean); 209 | } 210 | ); 211 | } 212 | 213 | 214 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 215 | m.def("max_forward", &dpooling_max_forward_cpu, "dynamic max pooling forward (CPU)"); 216 | m.def("max_backward", &dpooling_max_backward_cpu, "dynamic max pooling backward (CPU)"); 217 | m.def("sum_forward", &dpooling_sum_forward_cpu, "dynamic sum/mean pooling forward (CPU)"); 218 | m.def("sum_backward", &dpooling_sum_backward_cpu, "dynamic sum/mean pooling backward (CPU)"); 219 | } 220 | -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import torch 4 | # from torch.utils.cpp_extension import load 5 | from gckn.dynamic_pooling import pooling_cpu 6 | if torch.cuda.is_available(): 7 | from gckn.dynamic_pooling import pooling_cuda 8 | 9 | 10 | curr_folder = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | # if torch.cuda.is_available(): 13 | # pooling_cuda = load( 14 | # name='pooling_cuda', 15 | # sources=["/".join([curr_folder, 'pooling_cuda.cpp']), 16 | # "/".join([curr_folder, 'pooling_cuda_kernel.cu'])], 17 | # verbose=False) 18 | 19 | # pooling_cpu = load( 20 | # name='pooling', 21 | # sources=["/".join([curr_folder, 'pooling.cpp'])], 22 | # verbose=False) 23 | 24 | def dpooling_forward(input, kernel_size, pooling='sum'): 25 | kernel_size = kernel_size.cumsum(0) 26 | active_indices = kernel_size 27 | if pooling == 'max': 28 | if input.is_cuda: 29 | output, active_indices = pooling_cuda.max_forward(input, kernel_size) 30 | else: 31 | output, active_indices = pooling_cpu.max_forward(input, kernel_size) 32 | elif pooling == 'sum': 33 | if input.is_cuda: 34 | output = pooling_cuda.sum_forward(input, kernel_size, False) 35 | else: 36 | output = pooling_cpu.sum_forward(input, kernel_size, False) 37 | elif pooling == 'mean': 38 | if input.is_cuda: 39 | output = pooling_cuda.sum_forward(input, kernel_size, True) 40 | else: 41 | output = pooling_cpu.sum_forward(input, kernel_size, True) 42 | return output, active_indices 43 | 44 | def dpooling_backward(grad_input, grad_output, indices, pooling='sum'): 45 | if pooling == 'max': 46 | if grad_output.is_cuda: 47 | pooling_cuda.max_backward(grad_input, grad_output, indices) 48 | else: 49 | pooling_cpu.max_backward(grad_input, grad_output, indices) 50 | elif pooling == 'sum': 51 | if grad_output.is_cuda: 52 | pooling_cuda.sum_backward(grad_input, grad_output, indices, False) 53 | else: 54 | pooling_cpu.sum_backward(grad_input, grad_output, indices, False) 55 | elif pooling == 'mean': 56 | if grad_output.is_cuda: 57 | pooling_cuda.sum_backward(grad_input, grad_output, indices, True) 58 | else: 59 | pooling_cpu.sum_backward(grad_input, grad_output, indices, True) 60 | 61 | class DPoolingMax(torch.autograd.Function): 62 | @staticmethod 63 | def forward(ctx, input, kernel_size): 64 | kernel_size = kernel_size.cumsum(0) 65 | if input.is_cuda: 66 | output, active_indices = pooling_cuda.max_forward(input, kernel_size) 67 | else: 68 | output, active_indices = pooling_cpu.max_forward(input, kernel_size) 69 | ctx.save_for_backward(active_indices) 70 | ctx.size = input.shape 71 | return output 72 | 73 | @staticmethod 74 | def backward(ctx, grad_output): 75 | grad_input = grad_output.new_zeros(ctx.size) 76 | if grad_output.is_cuda: 77 | pooling_cuda.max_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables) 78 | else: 79 | pooling_cpu.max_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables) 80 | return grad_input, None 81 | 82 | class DPoolingSum(torch.autograd.Function): 83 | @staticmethod 84 | def forward(ctx, input, kernel_size, mean=False): 85 | kernel_size = kernel_size.cumsum(0) 86 | if input.is_cuda: 87 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 88 | kernel_size = kernel_size.to(device) 89 | output = pooling_cuda.sum_forward(input, kernel_size, mean) 90 | else: 91 | output = pooling_cpu.sum_forward(input, kernel_size, mean) 92 | ctx.save_for_backward(kernel_size) 93 | ctx.mean = mean 94 | ctx.size = input.shape 95 | return output 96 | 97 | @staticmethod 98 | def backward(ctx, grad_output): 99 | grad_input = grad_output.new_zeros(ctx.size) 100 | if grad_output.is_cuda: 101 | pooling_cuda.sum_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables, ctx.mean) 102 | else: 103 | pooling_cpu.sum_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables, ctx.mean) 104 | return grad_input, None, None 105 | 106 | def dpooling(input, kernel_size, pooling='sum'): 107 | if pooling == 'sum': 108 | return DPoolingSum.apply(input, kernel_size, False) 109 | elif pooling == 'mean': 110 | return DPoolingSum.apply(input, kernel_size, True) 111 | elif pooling == 'max': 112 | return DPoolingMax.apply(input, kernel_size) 113 | else: 114 | raise ValueError('Not implemented!') 115 | 116 | def dpooling_max_pad(input, kernel_size): 117 | output = torch.split(input, list(kernel_size)) 118 | output = torch.nn.utils.rnn.pad_sequence(output) 119 | output,_ = output.max(dim=0) 120 | return output 121 | 122 | def dpooling_torch(input, kernel_size, pooling='sum'): 123 | if pooling == 'max': 124 | return dpooling_max_pad(input, kernel_size) 125 | all_paths = input.shape[0] 126 | row = torch.arange(len(kernel_size), device=kernel_size.device).repeat_interleave(kernel_size, dim=0) 127 | col = torch.arange(all_paths, device=kernel_size.device) 128 | indices = torch.stack([row, col], dim=0) 129 | if pooling == 'sum': 130 | data = torch.ones(all_paths, device=kernel_size.device) 131 | elif pooling == 'mean': 132 | data = 1. / kernel_size.float() 133 | data = data.repeat_interleave(kernel_size, dim=0) 134 | else: 135 | raise ValueError('Not implemented!') 136 | pool_matrix = torch.sparse.FloatTensor( 137 | indices, data, torch.Size([len(kernel_size), all_paths])) 138 | 139 | return pool_matrix.mm(input) 140 | 141 | 142 | def test(cuda=False): 143 | torch.manual_seed(1234) 144 | pooling = 'max' 145 | 146 | size = 500 147 | max_length = 100 148 | hidden_size = 128 149 | kernel_size = torch.randint(0, max_length, (size,)) 150 | x = torch.rand(kernel_size.sum(), hidden_size) 151 | # kernel_size = torch.LongTensor([4, 5, 1]) 152 | if cuda: 153 | x = x.cuda() 154 | kernel_size = kernel_size.cuda() 155 | 156 | x.requires_grad_() 157 | print('start') 158 | out = dpooling(x, kernel_size, pooling=pooling) 159 | out1 = out.data 160 | out = out.mean() 161 | out.backward() 162 | grad1 = x.grad.data 163 | 164 | x.grad = None 165 | out = dpooling_torch(x, kernel_size, pooling=pooling) 166 | out2 = out.data 167 | out = out.mean() 168 | out.backward() 169 | grad2 = x.grad.data 170 | # print(x) 171 | # print(kernel_size) 172 | # print(out1) 173 | # print(out2) 174 | # print(grad1) 175 | # print(grad2) 176 | 177 | print(torch.max(torch.abs(out1 - out2))) 178 | print(torch.max(torch.abs(grad1 - grad2))) 179 | 180 | import time 181 | forward = 0 182 | backward = 0 183 | n_iter = 100 184 | for _ in range(n_iter): 185 | start = time.time() 186 | out = dpooling(x, kernel_size, pooling=pooling) 187 | if cuda: 188 | torch.cuda.synchronize() 189 | forward += time.time() - start 190 | 191 | out = out.mean() 192 | start = time.time() 193 | out.backward() 194 | if cuda: 195 | torch.cuda.synchronize() 196 | backward += time.time() - start 197 | 198 | print('Mine Forward: {:.3f} ms | Backward {:.3f} ms'.format(forward * 1e3/n_iter, backward * 1e3/n_iter)) 199 | 200 | import time 201 | forward = 0 202 | backward = 0 203 | n_iter = 100 204 | for _ in range(n_iter): 205 | start = time.time() 206 | out = dpooling_torch(x, kernel_size, pooling=pooling) 207 | if cuda: 208 | torch.cuda.synchronize() 209 | forward += time.time() - start 210 | 211 | out = out.mean() 212 | start = time.time() 213 | out.backward() 214 | if cuda: 215 | torch.cuda.synchronize() 216 | backward += time.time() - start 217 | 218 | print('Pytorch Forward: {:.3f} ms | Backward {:.3f} ms'.format(forward * 1e3/n_iter, backward * 1e3/n_iter)) 219 | 220 | 221 | if __name__ == "__main__": 222 | test(cuda=False) 223 | -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling_cpu.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/dynamic_pooling/pooling_cpu.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling_cuda.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | // CUDA forward declarations 6 | 7 | std::vector dpooling_max_cuda_forward( 8 | torch::Tensor input, 9 | torch::Tensor kernel_size); 10 | 11 | void dpooling_max_cuda_backward( 12 | torch::Tensor d_input, 13 | torch::Tensor d_output, 14 | torch::Tensor indices); 15 | 16 | torch::Tensor dpooling_sum_cuda_forward( 17 | torch::Tensor input, 18 | torch::Tensor kernel_size, 19 | bool mean); 20 | 21 | void dpooling_sum_cuda_backward( 22 | torch::Tensor d_input, 23 | torch::Tensor d_output, 24 | torch::Tensor kernel_size, 25 | bool mean); 26 | 27 | // C++ interface 28 | 29 | #define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor") 30 | #define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") 31 | #define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) 32 | 33 | std::vector dpooling_max_forward( 34 | torch::Tensor input, 35 | torch::Tensor kernel_size) { 36 | CHECK_INPUT(input); 37 | CHECK_INPUT(kernel_size); 38 | 39 | return dpooling_max_cuda_forward(input, kernel_size); 40 | } 41 | 42 | void dpooling_max_backward( 43 | torch::Tensor d_input, 44 | torch::Tensor d_output, 45 | torch::Tensor indices) { 46 | CHECK_INPUT(d_output); 47 | CHECK_INPUT(d_input); 48 | CHECK_INPUT(indices); 49 | dpooling_max_cuda_backward(d_input, d_output, indices); 50 | } 51 | 52 | torch::Tensor dpooling_sum_forward( 53 | torch::Tensor input, 54 | torch::Tensor kernel_size, 55 | bool mean) { 56 | CHECK_INPUT(input); 57 | CHECK_INPUT(kernel_size); 58 | 59 | return dpooling_sum_cuda_forward(input, kernel_size, mean); 60 | } 61 | 62 | void dpooling_sum_backward( 63 | torch::Tensor d_input, 64 | torch::Tensor d_output, 65 | torch::Tensor kernel_size, 66 | bool mean) { 67 | CHECK_INPUT(d_input); 68 | CHECK_INPUT(d_output); 69 | CHECK_INPUT(kernel_size); 70 | dpooling_sum_cuda_backward(d_input, d_output, kernel_size, mean); 71 | } 72 | 73 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 74 | m.def("max_forward", &dpooling_max_forward, "dynamic max pooling forward (CUDA)"); 75 | m.def("max_backward", &dpooling_max_backward, "dynamic max pooling backward (CUDA)"); 76 | m.def("sum_forward", &dpooling_sum_forward, "dynamic sum/mean pooling forward (CUDA)"); 77 | m.def("sum_backward", &dpooling_sum_backward, "dynamic sum/mean pooling backward (CUDA)"); 78 | } 79 | -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling_cuda.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/dynamic_pooling/pooling_cuda.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /gckn/dynamic_pooling/pooling_cuda_kernel.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | 6 | namespace { 7 | template 8 | __global__ void dpooling_max_cuda_forward_kernel( 9 | const scalar_t* __restrict__ input, 10 | const int64_t* __restrict__ kernel_size, 11 | scalar_t* __restrict__ output, 12 | int64_t* __restrict__ indices, 13 | size_t hidden_size) { 14 | const int column = blockIdx.x * blockDim.x + threadIdx.x; 15 | const int row = blockIdx.y; 16 | const int index = row * hidden_size + column; 17 | 18 | const int64_t start = (row == 0) ? 0 : kernel_size[row - 1]; 19 | int64_t curr_index; 20 | scalar_t val; 21 | scalar_t max_val = 0; 22 | int64_t max_index = -1; 23 | 24 | if (column < hidden_size) { 25 | for (int64_t k = start; k < kernel_size[row]; ++k) { 26 | curr_index = k * hidden_size + column; 27 | val = input[curr_index]; 28 | if (val > max_val) { 29 | max_val = val; 30 | max_index = k; 31 | } 32 | } 33 | output[index] = max_val; 34 | indices[index] = max_index; 35 | } 36 | } 37 | 38 | template 39 | __global__ void dpooling_max_cuda_backward_kernel( 40 | const scalar_t* __restrict__ d_output, 41 | const int64_t* __restrict__ indices, 42 | scalar_t* __restrict__ d_input, 43 | size_t hidden_size) { 44 | const int column = blockIdx.x * blockDim.x + threadIdx.x; 45 | const int row = blockIdx.y; 46 | const int index = row * hidden_size + column; 47 | const int64_t index_row = indices[index]; 48 | 49 | if (column < hidden_size) { 50 | if (index_row != -1) 51 | d_input[index_row * hidden_size + column] = d_output[index]; 52 | } 53 | } 54 | 55 | template 56 | __global__ void dpooling_sum_cuda_forward_kernel( 57 | scalar_t* __restrict__ output, 58 | const scalar_t* __restrict__ input, 59 | const int64_t* __restrict__ kernel_size, 60 | int64_t hidden_size, bool mean) { 61 | const int column = blockIdx.x * blockDim.x + threadIdx.x; 62 | const int row = blockIdx.y; 63 | const int index = row * hidden_size + column; 64 | 65 | const int64_t start = (row == 0) ? 0 : kernel_size[row - 1]; 66 | const int64_t end = kernel_size[row]; 67 | int64_t curr_index = start * hidden_size + column; 68 | scalar_t val = 1; 69 | if (mean) 70 | val = 1. / (end - start); 71 | 72 | if (column < hidden_size) { 73 | for (int64_t k = start; k < end; ++k) { 74 | output[index] += val * input[curr_index]; 75 | curr_index += hidden_size; 76 | } 77 | } 78 | } 79 | 80 | template 81 | __global__ void dpooling_sum_cuda_backward_kernel( 82 | scalar_t* __restrict__ d_input, 83 | const scalar_t* __restrict__ d_output, 84 | const int64_t* __restrict__ kernel_size, 85 | int64_t hidden_size, bool mean) { 86 | const int column = blockIdx.x * blockDim.x + threadIdx.x; 87 | const int row = blockIdx.y; 88 | const int index = row * hidden_size + column; 89 | 90 | const int64_t start = (row == 0) ? 0 : kernel_size[row - 1]; 91 | const int64_t end = kernel_size[row]; 92 | int64_t curr_index = start * hidden_size + column; 93 | scalar_t val = 1; 94 | if (mean) 95 | val = 1. / (end - start); 96 | 97 | if (column < hidden_size) { 98 | for (int64_t k = start; k < end; ++k) { 99 | d_input[curr_index] += val * d_output[index]; 100 | curr_index += hidden_size; 101 | } 102 | } 103 | } 104 | 105 | } 106 | 107 | std::vector dpooling_max_cuda_forward( 108 | torch::Tensor input, 109 | torch::Tensor kernel_size) { 110 | // input: H_in x hidden_size 111 | // kernel_size: H_out; sum(kernel_size) = H 112 | // output: H_out x hidden_size 113 | 114 | const auto size = input.size(0); 115 | const auto hidden_size = input.size(1); 116 | const auto size_out = kernel_size.size(0); 117 | auto output = torch::zeros({size_out, hidden_size}, input.options()); 118 | auto indices = torch::zeros({size_out, hidden_size}, kernel_size.options()); 119 | 120 | // const auto indices_size = kernel_size.cumsum(0); 121 | 122 | const int threads = 1024; 123 | const dim3 blocks((hidden_size + threads - 1) / threads, size_out); 124 | 125 | AT_DISPATCH_FLOATING_TYPES(input.type(), "dpooling_max_forward_cuda", ([&] { 126 | dpooling_max_cuda_forward_kernel<<>>( 127 | input.data_ptr(), 128 | kernel_size.data_ptr(), 129 | output.data_ptr(), 130 | indices.data_ptr(), 131 | hidden_size); 132 | })); 133 | 134 | return {output, indices}; 135 | } 136 | 137 | void dpooling_max_cuda_backward( 138 | torch::Tensor d_input, 139 | torch::Tensor d_output, 140 | torch::Tensor indices) { 141 | 142 | const auto size = d_output.size(0); 143 | const auto hidden_size = d_input.size(1); 144 | 145 | const int threads = 1024; 146 | const dim3 blocks((hidden_size + threads - 1) / threads, size); 147 | 148 | AT_DISPATCH_FLOATING_TYPES(d_output.type(), "dpooling_max_backward_cuda", ([&] { 149 | dpooling_max_cuda_backward_kernel<<>>( 150 | d_output.data_ptr(), 151 | indices.data_ptr(), 152 | d_input.data_ptr(), 153 | hidden_size); 154 | })); 155 | } 156 | 157 | torch::Tensor dpooling_sum_cuda_forward( 158 | torch::Tensor input, 159 | torch::Tensor kernel_size, 160 | bool mean) { 161 | // input: H_in x hidden_size 162 | // kernel_size: H_out; sum(kernel_size) = H 163 | // output: H_out x hidden_size 164 | const int64_t hidden_size = input.size(1); 165 | const int64_t size_out = kernel_size.size(0); 166 | auto output = torch::zeros({size_out, hidden_size}, input.options()); 167 | 168 | const int threads = 1024; 169 | const dim3 blocks((hidden_size + threads - 1) / threads, size_out); 170 | 171 | AT_DISPATCH_FLOATING_TYPES(input.type(), "dpooling_sum_forward_cuda", ([&] { 172 | dpooling_sum_cuda_forward_kernel<<>>( 173 | output.data_ptr(), 174 | input.data_ptr(), 175 | kernel_size.data_ptr(), 176 | hidden_size, 177 | mean); 178 | })); 179 | 180 | return output; 181 | } 182 | 183 | void dpooling_sum_cuda_backward( 184 | torch::Tensor d_input, 185 | torch::Tensor d_output, 186 | torch::Tensor kernel_size, 187 | bool mean) { 188 | const auto size_out = d_output.size(0); 189 | const auto hidden_size = d_input.size(1); 190 | 191 | const int threads = 1024; 192 | const dim3 blocks((hidden_size + threads - 1) / threads, size_out); 193 | 194 | AT_DISPATCH_FLOATING_TYPES(d_output.type(), "dpooling_sum_forward_cuda", ([&] { 195 | dpooling_sum_cuda_backward_kernel<<>>( 196 | d_input.data_ptr(), 197 | d_output.data_ptr(), 198 | kernel_size.data_ptr(), 199 | hidden_size, 200 | mean); 201 | })); 202 | } 203 | 204 | -------------------------------------------------------------------------------- /gckn/gckn_fast/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/gckn_fast/__init__.py -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | template 7 | inline void THBlas_axpy(int64_t n, T a, T *x, int64_t incx, T *y, int64_t incy); 8 | 9 | #define AXPY_SPECIALIZATION(ctype,name) \ 10 | template<> \ 11 | inline void THBlas_axpy(int64_t n, ctype a, ctype *x, int64_t incx, \ 12 | ctype *y, int64_t incy) { \ 13 | TH ## name ## Blas_axpy(n, a, x, incx, y, incy); \ 14 | } 15 | 16 | AT_FORALL_SCALAR_TYPES(AXPY_SPECIALIZATION) 17 | 18 | template 19 | void path_conv_forward_worker( 20 | torch::Tensor output, 21 | const torch::Tensor path_indices, const torch::Tensor features, 22 | int64_t n_paths, int64_t path_size, int64_t feat_path_size, int64_t hidden_size) { 23 | 24 | auto path_indices_accessor = path_indices.accessor(); 25 | scalar_t* features_ptr = features.data_ptr(); 26 | scalar_t* output_ptr = output.data_ptr(); 27 | // scalar_t val = 1. / path_size; 28 | 29 | // int64_t i, j; 30 | // for (i = 0; i < n_paths; ++i) { 31 | // for (j = 0; j < path_size; ++j) { 32 | // int64_t node_idx = path_indices_accessor[i][j]; 33 | // THBlas_axpy(hidden_size, val, 34 | // features_ptr + node_idx * path_size * hidden_size + j * hidden_size, 1, 35 | // output_ptr + i * hidden_size, 1); 36 | // } 37 | // } 38 | torch::parallel_for(0, n_paths, 0, [&](int64_t start, int64_t end) { 39 | for (auto i = start; i < end; i++) { 40 | for (int64_t j = 0; j < path_size; j++) { 41 | int64_t node_idx = path_indices_accessor[i][j]; 42 | THBlas_axpy(hidden_size, 1., 43 | features_ptr + (node_idx * feat_path_size + j) * hidden_size, 1, 44 | output_ptr + i * hidden_size, 1); 45 | } 46 | } 47 | }); 48 | } 49 | 50 | torch::Tensor path_conv_forward_cpu( 51 | torch::Tensor path_indices, 52 | torch::Tensor features) { 53 | // path_indices: n_paths x path_size (value < n_nodes) 54 | // features: n_nodes x path_size x hidden_size x (in_path_size) 55 | // output: n_paths x hidden_size x (in_path_size) 56 | const int64_t n_paths = path_indices.size(0); 57 | const int64_t path_size = path_indices.size(1); 58 | const int64_t feat_path_size = features.size(1); // should be >= path_size 59 | const int64_t hidden_size = features.size(2); 60 | 61 | auto output = torch::zeros({n_paths, hidden_size}, features.options()); 62 | auto commonDtype = promoteTypes(features.scalar_type(), output.scalar_type()); 63 | 64 | AT_DISPATCH_ALL_TYPES( 65 | commonDtype, "path_conv_forward", [&] { 66 | path_conv_forward_worker(output, path_indices, features, n_paths, path_size, feat_path_size, hidden_size); 67 | } 68 | ); 69 | 70 | output /= path_size; 71 | 72 | return output; 73 | } 74 | 75 | template 76 | void path_conv_backward_worker( 77 | torch::Tensor d_input, 78 | const torch::Tensor path_indices, const torch::Tensor d_output, 79 | int64_t n_paths, int64_t path_size, int64_t feat_path_size, int64_t hidden_size) { 80 | 81 | auto path_indices_accessor = path_indices.accessor(); 82 | scalar_t* d_input_ptr = d_input.data_ptr(); 83 | scalar_t* d_output_ptr = d_output.data_ptr(); 84 | // scalar_t val = 1. / path_size; 85 | 86 | torch::parallel_for(0, n_paths, 0, [&](int64_t start, int64_t end) { 87 | for (auto i = start; i < end; ++i) { 88 | for (int64_t j = 0; j < path_size; ++j) { 89 | int64_t node_idx = path_indices_accessor[i][j]; 90 | THBlas_axpy(hidden_size, 1., 91 | d_output_ptr + i * hidden_size, 1, 92 | d_input_ptr + (node_idx * feat_path_size + j) * hidden_size, 1); 93 | } 94 | } 95 | }); 96 | } 97 | 98 | void path_conv_backward_cpu( 99 | torch::Tensor d_input, 100 | torch::Tensor d_output, 101 | torch::Tensor path_indices) { 102 | const int64_t n_paths = path_indices.size(0); 103 | const int64_t path_size = path_indices.size(1); 104 | const int64_t feat_path_size = d_input.size(1); 105 | const int64_t hidden_size = d_output.size(1); 106 | // auto d_input = torch::zeros({n_nodes, path_size, hidden_size}, d_output.options()); 107 | 108 | auto commonDtype = promoteTypes(d_input.scalar_type(), d_output.scalar_type()); 109 | 110 | AT_DISPATCH_ALL_TYPES( 111 | commonDtype, "path_conv_backward", [&] { 112 | path_conv_backward_worker(d_input, path_indices, d_output, n_paths, path_size, feat_path_size, hidden_size); 113 | } 114 | ); 115 | d_input /= path_size; 116 | 117 | } 118 | 119 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 120 | m.def("path_conv_forward", &path_conv_forward_cpu, "path kernel mapping forward (CPU)"); 121 | m.def("path_conv_backward", &path_conv_backward_cpu, "path kernel mapping backward (CPU)"); 122 | } 123 | -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import torch 4 | # from torch.utils.cpp_extension import load 5 | from gckn.gckn_fast import gckn_fast_cpu 6 | if torch.cuda.is_available(): 7 | from gckn.gckn_fast import gckn_fast_cuda 8 | 9 | # curr_folder = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | # gckn_fast_cpu = load( 12 | # name='gckn_fast', 13 | # sources=[os.path.join(curr_folder, 'gckn_fast.cpp')], 14 | # verbose=True) 15 | 16 | # if torch.cuda.is_available(): 17 | # gckn_fast_cuda = load( 18 | # name='gckn_fast_cuda', 19 | # sources=[os.path.join(curr_folder, 'gckn_fast_cuda.cpp'), 20 | # os.path.join(curr_folder, 'gckn_fast_cuda_kernel.cu')], 21 | # verbose=True) 22 | 23 | def path_conv_forward(path_indices, features): 24 | if features.is_cuda: 25 | output = gckn_fast_cuda.path_conv_forward(path_indices, features) 26 | else: 27 | output = gckn_fast_cpu.path_conv_forward(path_indices, features) 28 | return output 29 | 30 | def path_conv_backward(grad_input, grad_output, path_indices): 31 | if grad_output.is_cuda: 32 | gckn_fast_cuda.path_conv_backward(grad_input, grad_output, path_indices) 33 | else: 34 | gckn_fast_cpu.path_conv_backward(grad_input, grad_output, path_indices) 35 | 36 | class PathConv(torch.autograd.Function): 37 | @staticmethod 38 | def forward(ctx, path_indices, features): 39 | if features.is_cuda: 40 | output = gckn_fast_cuda.path_conv_forward(path_indices, features) 41 | else: 42 | output = gckn_fast_cpu.path_conv_forward(path_indices, features) 43 | ctx.save_for_backward(path_indices) 44 | ctx.size = features.size() 45 | return output 46 | 47 | @staticmethod 48 | def backward(ctx, grad_output): 49 | grad_input = grad_output.new_zeros(ctx.size) 50 | if grad_output.is_cuda: 51 | gckn_fast_cuda.path_conv_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables) 52 | else: 53 | gckn_fast_cpu.path_conv_backward(grad_input, grad_output.contiguous(), *ctx.saved_variables) 54 | return None, grad_input 55 | 56 | def path_conv(path_indices, features): 57 | import torch.nn.functional as F 58 | output = F.embedding(path_indices, features.view(features.shape[0], -1)).view( 59 | path_indices.shape[0], path_indices.shape[1], features.shape[1], features.shape[2]) 60 | output = output.diagonal(dim1=1, dim2=2) 61 | # output: all_paths x path_size x path_size x hidden_size 62 | # -> all_paths x hidden_size x path_size 63 | output = output.mean(dim=-1) 64 | return output 65 | 66 | def test(cuda=False): 67 | torch.manual_seed(1234) 68 | 69 | path_size = 5 70 | n_nodes = 100 71 | hidden_size = 32 72 | n_paths = 100000 73 | x = torch.randn(n_nodes, path_size, hidden_size) 74 | path_indices = torch.randint(0, n_nodes, (n_paths, path_size)) 75 | 76 | if cuda: 77 | x = x.cuda() 78 | path_indices = path_indices.cuda() 79 | 80 | x.requires_grad_() 81 | print('start') 82 | out = PathConv.apply(path_indices, x) 83 | out1 = out.data 84 | out = out.mean() 85 | out.backward() 86 | grad1 = x.grad.data 87 | x.grad = None 88 | 89 | out = path_conv(path_indices, x) 90 | out2 = out.data 91 | out = out.mean() 92 | out.backward() 93 | grad2 = x.grad.data 94 | # print(out1) 95 | # print(out2) 96 | print(torch.max(torch.abs(out1 - out2))) 97 | print(torch.max(torch.abs(grad1 - grad2))) 98 | 99 | import time 100 | forward = 0 101 | backward = 0 102 | n_iter = 10 103 | for _ in range(n_iter): 104 | start = time.time() 105 | out = PathConv.apply(path_indices, x) 106 | if cuda: 107 | torch.cuda.synchronize() 108 | forward += time.time() - start 109 | 110 | out = out.mean() 111 | start = time.time() 112 | out.backward() 113 | if cuda: 114 | torch.cuda.synchronize() 115 | backward += time.time() - start 116 | 117 | print('Mine Forward: {:.3f} ms | Backward {:.3f} ms'.format(forward * 1e3/n_iter, backward * 1e3/n_iter)) 118 | 119 | import time 120 | forward = 0 121 | backward = 0 122 | n_iter = 10 123 | for _ in range(n_iter): 124 | start = time.time() 125 | out = path_conv(path_indices, x) 126 | if cuda: 127 | torch.cuda.synchronize() 128 | forward += time.time() - start 129 | 130 | out = out.mean() 131 | start = time.time() 132 | out.backward() 133 | if cuda: 134 | torch.cuda.synchronize() 135 | backward += time.time() - start 136 | 137 | print('Pytorch Forward: {:.3f} ms | Backward {:.3f} ms'.format(forward * 1e3/n_iter, backward * 1e3/n_iter)) 138 | 139 | 140 | if __name__ == "__main__": 141 | test(cuda=False) 142 | -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast_cpu.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/gckn_fast/gckn_fast_cpu.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast_cuda.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // CUDA declarations 4 | 5 | torch::Tensor path_conv_forward_cuda( 6 | torch::Tensor path_indices, 7 | torch::Tensor features); 8 | 9 | void path_conv_backward_cuda( 10 | torch::Tensor d_input, 11 | torch::Tensor d_output, 12 | torch::Tensor path_indices); 13 | 14 | 15 | // C++ interface 16 | 17 | #define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor") 18 | #define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") 19 | #define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) 20 | 21 | torch::Tensor path_conv_forward( 22 | torch::Tensor path_indices, 23 | torch::Tensor features) { 24 | CHECK_INPUT(path_indices); 25 | CHECK_INPUT(features); 26 | 27 | return path_conv_forward_cuda(path_indices, features); 28 | } 29 | 30 | void path_conv_backward( 31 | torch::Tensor d_input, 32 | torch::Tensor d_output, 33 | torch::Tensor path_indices) { 34 | CHECK_INPUT(path_indices); 35 | CHECK_INPUT(d_input); 36 | CHECK_INPUT(d_output); 37 | 38 | path_conv_backward_cuda(d_input, d_output, path_indices); 39 | } 40 | 41 | PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { 42 | m.def("path_conv_forward", &path_conv_forward, "path kernel mapping forward (CUDA)"); 43 | m.def("path_conv_backward", &path_conv_backward, "path kernel mapping backward (CUDA)"); 44 | } 45 | -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast_cuda.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/gckn_fast/gckn_fast_cuda.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /gckn/gckn_fast/gckn_fast_cuda_kernel.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace { 4 | template 5 | __global__ void path_conv_forward_cuda_kernel( 6 | scalar_t* __restrict__ output, 7 | const int64_t* __restrict__ path_indices, 8 | const scalar_t* __restrict__ features, 9 | int64_t n_paths, 10 | int64_t path_size, 11 | int64_t feat_path_size, 12 | int64_t hidden_size) { 13 | 14 | const int row = blockIdx.x * blockDim.x + threadIdx.x; 15 | const int col = blockIdx.y; 16 | const int index = row * hidden_size + col; 17 | scalar_t val = 1. / path_size; 18 | int64_t node_idx; 19 | 20 | if (col < hidden_size && row < n_paths) { 21 | for (int64_t j = 0; j < path_size; ++j){ 22 | node_idx = path_indices[row * path_size + j]; 23 | output[index] += val * features[(node_idx * feat_path_size + j) * hidden_size + col]; 24 | } 25 | } 26 | 27 | } 28 | 29 | template 30 | __global__ void path_conv_backward_cuda_kernel( 31 | scalar_t* __restrict__ d_input, 32 | const int64_t* __restrict__ path_indices, 33 | const scalar_t* __restrict__ d_output, 34 | int64_t n_paths, 35 | int64_t path_size, 36 | int64_t feat_path_size, 37 | int64_t hidden_size) { 38 | 39 | const int row = blockIdx.x * blockDim.x + threadIdx.x; 40 | const int col = blockIdx.y; 41 | const int index = row * hidden_size + col; 42 | scalar_t val = 1. / path_size; 43 | int64_t node_idx; 44 | 45 | if (col < hidden_size && row < n_paths) { 46 | for (int64_t j = 0; j < path_size; ++j){ 47 | node_idx = path_indices[row * path_size + j]; 48 | d_input[(node_idx * feat_path_size + j) * hidden_size + col] += val * d_output[index]; 49 | } 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | torch::Tensor path_conv_forward_cuda( 57 | torch::Tensor path_indices, 58 | torch::Tensor features) { 59 | // path_indices: n_paths x path_size (value < n_nodes) 60 | // features: n_nodes x path_size x hidden_size x (in_path_size) 61 | // output: n_paths x hidden_size x (in_path_size) 62 | const int64_t n_paths = path_indices.size(0); 63 | const int64_t path_size = path_indices.size(1); 64 | const int64_t feat_path_size = features.size(1); 65 | const int64_t hidden_size = features.size(2); 66 | 67 | auto output = torch::zeros({n_paths, hidden_size}, features.options()); 68 | 69 | const int threads = 1024; 70 | const dim3 blocks((n_paths + threads - 1) / threads, hidden_size); 71 | 72 | AT_DISPATCH_FLOATING_TYPES(features.type(), "path_conv_forward_cuda", ([&] { 73 | path_conv_forward_cuda_kernel<<>>( 74 | output.data_ptr(), 75 | path_indices.data_ptr(), 76 | features.data_ptr(), 77 | n_paths, 78 | path_size, 79 | feat_path_size, 80 | hidden_size); 81 | })); 82 | 83 | return output; 84 | } 85 | 86 | void path_conv_backward_cuda( 87 | torch::Tensor d_input, 88 | torch::Tensor d_output, 89 | torch::Tensor path_indices) { 90 | const int64_t n_paths = path_indices.size(0); 91 | const int64_t path_size = path_indices.size(1); 92 | const int64_t feat_path_size = d_input.size(1); 93 | const int64_t hidden_size = d_output.size(1); 94 | 95 | // auto commonDtype = promoteTypes(d_input.scalar_type(), d_output.scalar_type()); 96 | 97 | const int threads = 1024; 98 | const dim3 blocks((n_paths + threads - 1) / threads, hidden_size); 99 | 100 | AT_DISPATCH_FLOATING_TYPES(d_output.type(), "path_conv_backward_cuda", ([&] { 101 | path_conv_backward_cuda_kernel<<>>( 102 | d_input.data_ptr(), 103 | path_indices.data_ptr(), 104 | d_output.data_ptr(), 105 | n_paths, 106 | path_size, 107 | feat_path_size, 108 | hidden_size); 109 | })); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /gckn/graphs/__init__.py: -------------------------------------------------------------------------------- 1 | from .graphs_fast import get_paths, get_walks -------------------------------------------------------------------------------- /gckn/graphs/graphs_fast.cpython-36m-x86_64-linux-gnu.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YimiAChack/GSKN/2618a6667ea2c9ae0a6710a1cd4703e10a7ea1a1/gckn/graphs/graphs_fast.cpython-36m-x86_64-linux-gnu.so -------------------------------------------------------------------------------- /gckn/graphs/graphs_fast.pyx: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # cython: linetrace=True 3 | # cython: cdivision=True 4 | # cython: boundscheck=False 5 | # cython: wraparound=False 6 | # cython: language_level=3 7 | 8 | from libc.stdlib cimport malloc, free 9 | cimport numpy as np 10 | import numpy as np 11 | np.import_array() 12 | 13 | 14 | ctypedef np.uint16_t uint16_t 15 | ctypedef np.int64_t int64_t 16 | 17 | cdef struct stack: 18 | int max_size 19 | int top 20 | uint16_t *items 21 | 22 | cdef stack* init_stack(int capacity) nogil: 23 | cdef stack *pt = malloc(sizeof(stack)) 24 | pt.max_size = capacity 25 | pt.top = -1 26 | pt.items = malloc(capacity * sizeof(uint16_t)) 27 | return pt 28 | 29 | cdef int size(stack *pt) nogil: 30 | return pt.top + 1 31 | 32 | cdef void free_stack(stack *pt) nogil: 33 | free(pt.items) 34 | free(pt) 35 | 36 | cdef void clear_stack(stack *pt) nogil: 37 | pt.top = -1 38 | 39 | cdef bint empty(stack *pt) nogil: 40 | return pt.top == -1 41 | 42 | cdef uint16_t pop_back(stack *pt) nogil: 43 | pt.top -= 1 44 | return pt.items[pt.top + 1] 45 | 46 | cdef void push_back(stack *pt, uint16_t x) nogil: 47 | pt.top += 1 48 | pt.items[pt.top] = x 49 | 50 | cdef class Graph(object): 51 | cdef int n, m 52 | cdef uint16_t *edges 53 | cdef uint16_t **neighbors 54 | 55 | def __cinit__(self, adj_list): 56 | n = len(adj_list) 57 | m = sum([len(adj) for adj in adj_list]) 58 | cdef uint16_t *edges = malloc(m * sizeof(uint16_t)) 59 | cdef uint16_t **neighbors = malloc((n + 1) * sizeof(uint16_t *)) 60 | cdef uint16_t *edges_iter = &edges[0] 61 | for i in range(n): 62 | neighbors[i] = &edges_iter[0] 63 | for adj in adj_list[i]: 64 | edges_iter[0] = adj 65 | edges_iter += 1 66 | neighbors[n] = &edges_iter[0] 67 | 68 | self.n = n 69 | self.m = self.m 70 | self.neighbors = neighbors 71 | self.edges = edges 72 | 73 | def __dealloc__(self): 74 | if self.edges is not NULL: 75 | free(self.edges) 76 | if self.neighbors is not NULL: 77 | free(self.neighbors) 78 | 79 | cdef void dfs_search(self, uint16_t node, bint *visited) nogil: 80 | visited[node] = True 81 | cdef uint16_t *adj = self.neighbors[node] 82 | while adj != self.neighbors[node + 1]: 83 | if not visited[adj[0]]: 84 | self.dfs_search(adj[0], visited) 85 | adj += 1 86 | 87 | cdef void all_paths(self, uint16_t node, bint *visited, stack *vs, 88 | int depth, uint16_t[:, :, ::1] all_paths, int64_t[:] counts) nogil: 89 | visited[node] = True 90 | push_back(vs, node) 91 | 92 | cdef int j 93 | cdef int index = size(vs) - 1 94 | for j in range(size(vs)): 95 | all_paths[index][counts[index]][j] = vs.items[j] 96 | counts[index] += 1 97 | 98 | if depth == 0: 99 | visited[pop_back(vs)] = False 100 | return 101 | 102 | cdef uint16_t *adj = self.neighbors[node] 103 | while adj < self.neighbors[node + 1]: 104 | if not visited[adj[0]]: 105 | self.all_paths(adj[0], visited, vs, depth - 1, all_paths, counts) 106 | adj += 1 107 | if adj == self.neighbors[node + 1]: 108 | visited[pop_back(vs)] = False 109 | 110 | cdef void all_walks(self, uint16_t node, stack *vs, 111 | int depth, uint16_t[:, :, ::1] all_paths, int64_t[:] counts) nogil: 112 | push_back(vs, node) 113 | 114 | cdef int j 115 | cdef int index = size(vs) - 1 116 | for j in range(size(vs)): 117 | all_paths[index][counts[index]][j] = vs.items[j] 118 | counts[index] += 1 119 | 120 | if depth == 0: 121 | pop_back(vs) 122 | return 123 | 124 | cdef uint16_t *adj = self.neighbors[node] 125 | while adj < self.neighbors[node + 1]: 126 | self.all_walks(adj[0], vs, depth - 1, all_paths, counts) 127 | adj += 1 128 | if adj == self.neighbors[node + 1]: 129 | pop_back(vs) 130 | 131 | def get_paths(graph, int k): 132 | # 不允许重复的nodes 133 | cdef int n = len(graph.neighbors) 134 | cdef Graph g = Graph(graph.neighbors) 135 | cdef int d = graph.mean_neighbor #, graph.max_neighbor) 136 | cdef int dmax = graph.max_neighbor 137 | cdef bint *visited = malloc(n * sizeof(bint)) 138 | cdef stack *vs = init_stack(k) 139 | 140 | cdef int64_t[:] all_counts = np.zeros(k, dtype=np.int64) 141 | cdef int64_t[:, ::1] counts = np.empty((n, k), dtype=np.int64) 142 | cdef int64_t[:] prev_counts = np.zeros(k, dtype=np.int64) 143 | 144 | cdef int i, j 145 | cdef int max_size = n * d ** (k - 1) * 10 146 | 147 | cdef uint16_t[:, :, ::1] all_paths = np.zeros((k, max_size, k), dtype=np.uint16) 148 | 149 | with nogil: 150 | for i in range(n): 151 | for j in range(n): 152 | visited[j] = False 153 | clear_stack(vs) 154 | g.all_paths(i, visited, vs, k - 1, all_paths, all_counts) 155 | for j in range(k): 156 | counts[i, j] = all_counts[j] - prev_counts[j] 157 | prev_counts[j] = all_counts[j] 158 | 159 | free(visited) 160 | free_stack(vs) 161 | 162 | paths = [] 163 | for j in range(k): 164 | paths.append(np.asarray(all_paths[j][:all_counts[j], :j+1]).astype('int64')) 165 | 166 | return paths, np.asarray(counts) 167 | 168 | def get_walks(graph, int k): 169 | cdef int n = len(graph.neighbors) 170 | cdef Graph g = Graph(graph.neighbors) 171 | cdef int d = graph.mean_neighbor #, graph.max_neighbor) 172 | cdef int dmax = graph.max_neighbor 173 | cdef stack *vs = init_stack(k) 174 | 175 | cdef int64_t[:] all_counts = np.zeros(k, dtype=np.int64) 176 | cdef int64_t[:, ::1] counts = np.empty((n, k), dtype=np.int64) 177 | cdef int64_t[:] prev_counts = np.zeros(k, dtype=np.int64) 178 | 179 | cdef int i, j 180 | cdef int max_size = n * d ** (k - 1) * (dmax) 181 | 182 | cdef uint16_t[:, :, ::1] all_paths = np.zeros((k, max_size, k), dtype=np.uint16) 183 | 184 | with nogil: 185 | for i in range(n): 186 | clear_stack(vs) 187 | g.all_walks(i, vs, k - 1, all_paths, all_counts) 188 | for j in range(k): 189 | counts[i, j] = all_counts[j] - prev_counts[j] 190 | prev_counts[j] = all_counts[j] 191 | 192 | free_stack(vs) 193 | 194 | paths = [] 195 | for j in range(k): 196 | paths.append(np.asarray(all_paths[j][:all_counts[j], :j+1]).astype('int64')) 197 | 198 | return paths, np.asarray(counts) 199 | -------------------------------------------------------------------------------- /gckn/graphs/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.extension import Extension 2 | 3 | import numpy 4 | 5 | 6 | def configuration(parent_package='', top_path=None): 7 | from numpy.distutils.misc_util import Configuration 8 | 9 | config = Configuration('graphs', parent_package, top_path) 10 | 11 | extensions = [ 12 | Extension("gckn.graphs.graphs_fast", 13 | ['gckn/graphs/graphs_fast.pyx'], 14 | extra_compile_args = ["-ffast-math"], 15 | include_dirs = numpy.get_include() 16 | ) 17 | ] 18 | 19 | 20 | config.ext_modules += extensions 21 | 22 | return config 23 | 24 | 25 | if __name__ == '__main__': 26 | from numpy.distutils.core import setup 27 | setup(**configuration(parent_package='gckn/graphs', top_path="/home/tmp/gckn_/").todict()) 28 | -------------------------------------------------------------------------------- /gckn/kernels.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | 4 | def exp(x, alpha): 5 | return torch.exp(alpha*(x - 1.)) 6 | 7 | def linear(x, alpha): 8 | return x 9 | 10 | def d_exp(x, alpha): 11 | return alpha * exp(x, alpha) 12 | 13 | kernels = { 14 | "exp": exp, 15 | "linear": linear 16 | } 17 | 18 | d_kernels = { 19 | "exp": d_exp, 20 | "linear": linear 21 | } 22 | -------------------------------------------------------------------------------- /gckn/layers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import math 3 | import torch 4 | from torch import nn 5 | import torch.nn.functional as F 6 | from . import ops 7 | from .kernels import kernels, d_kernels 8 | from .utils import EPS, normalize_, spherical_kmeans 9 | from .dynamic_pooling.pooling import dpooling 10 | from .path_conv_agg import path_conv_agg 11 | 12 | import numpy as np 13 | from scipy import optimize 14 | from sklearn.linear_model.base import LinearModel, LinearClassifierMixin 15 | from sklearn.decomposition import PCA 16 | 17 | 18 | class PathLayer(nn.Module): 19 | def __init__(self, input_size, hidden_size, path_size=1, features_anonymous_dim = 5, anonymous_walk_length = 5, 20 | kernel_func='exp', kernel_args=[0.5], pooling='mean', 21 | aggregation=False): 22 | super().__init__() 23 | self.input_size = input_size 24 | self.hidden_size = hidden_size 25 | self.path_size = path_size 26 | 27 | self.features_anonymous_dim = features_anonymous_dim 28 | self.anonymous_walk_length = anonymous_walk_length 29 | 30 | self.pooling = pooling 31 | self.aggregation = aggregation and (path_size > 1) 32 | 33 | self.kernel_func = kernel_func 34 | if isinstance(kernel_args, (int, float)): 35 | kernel_args = [kernel_args] 36 | if kernel_func == 'exp': 37 | kernel_args = [1. / kernel_arg ** 2 for kernel_arg in kernel_args] 38 | self.kernel_args = kernel_args# [kernel_arg / path_size for kernel_arg in kernel_args] 39 | 40 | self.kernel_func = kernels[kernel_func] 41 | self.kappa = lambda x: self.kernel_func(x, *self.kernel_args) 42 | d_kernel_func = d_kernels[kernel_func] 43 | self.d_kappa = lambda x: d_kernel_func(x, *self.kernel_args) 44 | 45 | self._need_lintrans_computed = True 46 | self.weight = nn.Parameter( 47 | torch.Tensor(path_size, hidden_size, input_size)) 48 | self.weight_anonym = nn.Parameter( 49 | torch.Tensor(anonymous_walk_length, hidden_size, self.features_anonymous_dim)) # input_size: 特征长度 50 | 51 | if self.aggregation: 52 | self.register_buffer('lintrans', 53 | torch.Tensor(path_size, hidden_size, hidden_size)) 54 | self.register_buffer('divider', 55 | torch.arange(1., path_size + 1).view(-1, 1, 1)) 56 | else: 57 | self.register_buffer('lintrans', 58 | torch.Tensor(hidden_size, hidden_size)) 59 | 60 | self.reset_parameters() 61 | 62 | def reset_parameters(self): 63 | stdv = 1. / math.sqrt(self.hidden_size) 64 | for w in self.parameters(): 65 | if w.dim() > 1: 66 | w.data.uniform_(-stdv, stdv) 67 | self.normalize_() 68 | 69 | def normalize_(self): 70 | normalize_(self.weight.data, dim=-1) 71 | normalize_(self.weight_anonym.data, dim=-1) 72 | 73 | def train(self, mode=True): 74 | super().train(mode) 75 | self._need_lintrans_computed = True 76 | 77 | def _compute_lintrans(self, weight): 78 | if not self._need_lintrans_computed: 79 | return self.lintrans 80 | lintrans = torch.bmm(weight, weight.permute(0, 2, 1)) 81 | if self.aggregation: 82 | lintrans = lintrans.cumsum(dim=0) / self.divider 83 | else: 84 | lintrans = lintrans.mean(dim=0) 85 | lintrans = self.kappa(lintrans) 86 | lintrans = ops.matrix_inverse_sqrt(lintrans) 87 | 88 | if not self.training: 89 | self._need_lintrans_computed = False 90 | self.lintrans.data.copy_(lintrans.data) 91 | return lintrans 92 | 93 | 94 | def sample_paths(self, features, paths_indices, n_sampling_paths=1000): 95 | """Sample paths for a given of features and paths 96 | features: n_nodes x (input_path_size) x input_size 97 | paths_indices: n_paths x path_size 98 | output: n_sampling_paths x path_size x input_size 99 | """ 100 | 101 | paths_indices = paths_indices[self.path_size - 1] 102 | 103 | n_all_paths = paths_indices.shape[0] 104 | indices = torch.randperm(n_all_paths)[ : min(n_all_paths, n_sampling_paths)] 105 | paths = F.embedding(paths_indices[indices], features) 106 | return paths 107 | 108 | def sample_anonymous_walks(self, paths, n_sampling_paths=1000): 109 | features = torch.from_numpy(np.eye(self.features_anonymous_dim)) 110 | n_all_paths = paths.shape[0] 111 | indices = torch.randperm(n_all_paths)[ : min(n_all_paths, n_sampling_paths)] 112 | paths = F.embedding(paths[indices], features) 113 | return paths 114 | 115 | 116 | def unsup_train(self, paths, paths_anonym, init=None): 117 | """Unsupervised training for path layer 118 | paths: n x path_size x input_size [7442, 2, 7] 119 | paths_anonym: n x path_size x one_hot 120 | self.weight: path_size x hidden_size x input_size 121 | """ 122 | normalize_(paths, dim=-1) 123 | weight = spherical_kmeans(paths, self.hidden_size, init='kmeans++') # [n_clusters(hidden_size), kmer_size, n_features] 124 | 125 | weight = weight.permute(1, 0, 2) # [kmer_size, n_clusters, n_features] [2, 32, 7] 126 | self.weight.data.copy_(weight) 127 | 128 | normalize_(paths_anonym, dim=-1) 129 | weight_anonym = spherical_kmeans(paths_anonym, self.hidden_size, init='kmeans++') 130 | weight_anonym = weight_anonym.permute(1, 0, 2) # [kmer_size, n_clusters, n_features] [2, 32, 7] 131 | self.weight_anonym.data.copy_(weight_anonym) 132 | 133 | 134 | self.normalize_() 135 | self._need_lintrans_computed = True 136 | 137 | def conv_pred(self, flag_anonymous, features, paths_indices, weight, path_size, other_info): 138 | norms = features.norm(dim=-1, keepdim=True) # norms: n_nodes x (input_path_size) x 1 139 | #output = features / norms.clamp(min=EPS) 140 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 141 | features = features.to(device) 142 | weight = weight.to(device) 143 | output = torch.tensordot(features, weight, dims=[[-1], [-1]]) 144 | output = output / norms.clamp(min=EPS).unsqueeze(2) 145 | n_nodes = output.shape[0] 146 | if output.ndim == 4: 147 | output = output.permute(0, 2, 1, 3).contiguous() 148 | 149 | ## prepare masks 150 | mask = None 151 | if self.aggregation: 152 | mask = [None for _ in range(path_size)] 153 | if 'mask' in other_info and path_size > 1: 154 | mask = other_info['mask'] 155 | 156 | output = output.view(n_nodes, path_size, -1) 157 | # output: n_nodes x path_size x (input_path_size x hidden_size) 158 | 159 | if flag_anonymous == False and self.aggregation: 160 | outputs = [] 161 | for i in range(path_size): 162 | embeded = path_conv_agg( 163 | output, paths_indices[i], other_info['n_paths'][i], 164 | self.pooling, self.kappa, self.d_kappa, mask[i]) 165 | outputs.append(embeded) 166 | output = torch.stack(outputs, dim=0) 167 | output = output.view(path_size, -1, self.hidden_size) 168 | # output: path_size x (n_nodes x (input_path_size)) x hidden_size 169 | output = norms.view(1, -1, 1) * output 170 | 171 | lintrans = self._compute_lintrans(weight) 172 | 173 | output = output.bmm(lintrans) 174 | output = output.permute(1, 0, 2) 175 | output = output.reshape(n_nodes, -1, self.hidden_size) 176 | output = output.contiguous() 177 | 178 | if flag_anonymous: # not aggregation 179 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 180 | paths_indices = paths_indices.to(device) 181 | output = path_conv_agg( 182 | output, paths_indices, other_info['n_paths'][self.path_size - 1], 183 | self.pooling, self.kappa, self.d_kappa, None) 184 | output = output.view(n_nodes, -1, self.hidden_size) 185 | output = norms.view(n_nodes, -1, 1) * output 186 | 187 | lintrans = self._compute_lintrans(weight) 188 | output = torch.tensordot(output, lintrans, dims=[[-1], [-1]]) 189 | output = torch.squeeze(output, dim=1) 190 | 191 | return output 192 | 193 | def forward(self, features, paths_indices, anonymous_walks, other_info): 194 | """ 195 | features: n_nodes x (input_path_size) x input_size 196 | paths_indices: n_paths x path_size (values < n_nodes) 197 | output: n_nodes x ((input_path_size) x path_size) x input_size 198 | """ 199 | self.normalize_() 200 | output = self.conv_pred(False, features, paths_indices, self.weight, self.path_size, other_info) 201 | pca = PCA(n_components=self.anonymous_walk_length) 202 | pca.fit(anonymous_walks.reshape(features.shape[0],-1)) 203 | features_anonymous = pca.transform(anonymous_walks.reshape(features.shape[0],-1)) 204 | features_anonymous = torch.from_numpy(features_anonymous).float() 205 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 206 | features_anonymous = features_anonymous.to(device) 207 | output_anonymous = self.conv_pred(True, features_anonymous, anonymous_walks, self.weight_anonym, self.anonymous_walk_length, other_info) 208 | output = torch.cat((output, output_anonymous), 1) 209 | 210 | return output 211 | 212 | 213 | class NodePooling(nn.Module): 214 | def __init__(self, pooling='mean'): 215 | super().__init__() 216 | self.pooling = pooling 217 | 218 | def forward(self, features, other_info): 219 | """ 220 | features: n_nodes x (input_path_size) x input_size 221 | output: n_graphs x input_size 222 | """ 223 | features = features.permute(0, 2, 1).contiguous() 224 | n_nodes = features.shape[0] 225 | output = dpooling(features.view(n_nodes, -1), other_info['n_nodes'], self.pooling) 226 | 227 | return output -------------------------------------------------------------------------------- /gckn/loss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import torch 3 | from torch import nn 4 | import torch.nn.functional as F 5 | from torch.nn.modules.loss import _Loss 6 | 7 | 8 | class HingeLoss(_Loss): 9 | def __init__(self, nclass=10, weight=None, size_average=None, reduce=None, 10 | reduction='elementwise_mean', pos_weight=None, squared=True): 11 | super(HingeLoss, self).__init__(size_average, reduce, reduction) 12 | self.nclass = nclass 13 | self.squared = squared 14 | self.register_buffer('weight', weight) 15 | self.register_buffer('pos_weight', pos_weight) 16 | 17 | def forward(self, input, target): 18 | if not (target.size(0) == input.size(0)): 19 | raise ValueError( 20 | "Target size ({}) must be the same as input size ({})".format(target.size(), input.size())) 21 | if self.pos_weight is not None: 22 | pos_weight = 1 + (self.pos_weight - 1) * target 23 | target = 2 * F.one_hot(target, num_classes=self.nclass) - 1 24 | target = target.float() 25 | loss = F.relu(1. - target * input) 26 | if self.squared: 27 | loss = 0.5 * loss ** 2 28 | if self.weight is not None: 29 | loss = loss * self.weight 30 | if self.pos_weight is not None: 31 | loss = loss * pos_weight 32 | loss = loss.sum(dim=-1) 33 | if self.reduction == 'none': 34 | return loss 35 | elif self.reduction == 'elementwise_mean': 36 | return loss.mean() 37 | else: 38 | return loss.sum() 39 | 40 | LOSS = { 41 | 'ce': nn.CrossEntropyLoss, 42 | 'hinge': HingeLoss, 43 | } 44 | -------------------------------------------------------------------------------- /gckn/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import torch 3 | from torch import nn 4 | # from .layers import Linear 5 | from .layers import PathLayer, NodePooling 6 | import numpy as np 7 | 8 | 9 | class PathSequential(nn.Module): 10 | def __init__(self, input_size, hidden_sizes, path_sizes, anonymous_walk_length, 11 | kernel_funcs=None, kernel_args_list=None, 12 | pooling='mean', #global_pooling='sum', 13 | aggregation=False, **kwargs): 14 | super(PathSequential, self).__init__() 15 | self.input_size = input_size 16 | self.hidden_sizes = hidden_sizes # 32 17 | 18 | self.path_sizes = path_sizes # 3 19 | self.n_layers = len(hidden_sizes) 20 | self.aggregation = aggregation 21 | self.features_anonymous_dim = anonymous_walk_length 22 | self.anonymous_walk_length = anonymous_walk_length 23 | 24 | layers = [] 25 | output_size = hidden_sizes[-1] 26 | for i in range(self.n_layers): 27 | if kernel_funcs is None: 28 | kernel_func = "exp" 29 | else: 30 | kernel_func = kernel_funcs[i] 31 | if kernel_args_list is None: 32 | kernel_args = 0.5 33 | else: 34 | kernel_args = kernel_args_list[i] 35 | 36 | layer = PathLayer(input_size, hidden_sizes[i], path_sizes[i], self.features_anonymous_dim, anonymous_walk_length, 37 | kernel_func, kernel_args, pooling, aggregation, **kwargs) 38 | layers.append(layer) 39 | input_size = hidden_sizes[i] 40 | if aggregation: 41 | output_size *= path_sizes[i] 42 | self.output_size = output_size 43 | 44 | self.layers = nn.ModuleList(layers) 45 | 46 | def __getitem__(self, idx): 47 | return self.layers[idx] 48 | 49 | def __len__(self): 50 | return self.n_layers 51 | 52 | def __iter__(self): 53 | return iter(self.layers._modules.values()) 54 | 55 | def forward(self, features, paths_indices, anonymous_walks, other_info): 56 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 57 | output = features.to(device) 58 | paths_indices = [path_index.to(device) for path_index in paths_indices] 59 | for layer in self.layers: 60 | output = layer(output, paths_indices, anonymous_walks, other_info) 61 | return output 62 | 63 | def normalize_(self): 64 | for module in self.layers: 65 | module.normalize_() 66 | 67 | def unsup_train(self, data_loader, n_sampling_paths=100000, init=None, use_cuda=False): 68 | self.train(False) 69 | for i, layer in enumerate(self.layers): 70 | print("Training layer {}".format(i + 1)) 71 | n_sampled_paths = 0 72 | n_sampled_anonymous_walks = 0 73 | n_sampling_anonymous_walks = n_sampling_paths 74 | try: 75 | n_paths_per_batch = ( 76 | n_sampling_paths + len(data_loader) - 1) // len(data_loader) 77 | except: 78 | n_paths_per_batch = 1000 79 | 80 | paths = torch.Tensor( 81 | n_sampling_paths, layer.path_size, layer.input_size) 82 | anonymous_walks = torch.Tensor(n_sampling_paths, self.anonymous_walk_length, self.features_anonymous_dim) 83 | 84 | if use_cuda: 85 | paths = paths.cuda() 86 | anonymous_walks = anonymous_walks.cuda() 87 | 88 | for data in data_loader.make_batch(): 89 | if n_sampled_paths >= n_sampling_paths: 90 | continue 91 | features = data['features'] 92 | paths_indices = data['paths'] 93 | n_paths = data['n_paths'] 94 | n_nodes = data['n_nodes'] 95 | anonymous_indices = data['anonymous_walks'] 96 | 97 | 98 | if use_cuda: 99 | features = features.cuda() 100 | if isinstance(n_paths, list): 101 | paths_indices = [p.cuda() for p in paths_indices] 102 | n_paths = [p.cuda() for p in n_paths] 103 | else: 104 | paths_indices = paths_indices.cuda() 105 | n_paths = n_paths.cuda() 106 | anonymous_indices = anonymous_indices.cuda() 107 | n_nodes = n_nodes.cuda() 108 | with torch.no_grad(): 109 | paths_batch = layer.sample_paths( 110 | features, paths_indices, n_paths_per_batch) 111 | anonymous_batch = layer.sample_anonymous_walks( 112 | anonymous_indices, n_paths_per_batch) 113 | 114 | size = min(paths_batch.shape[0], n_sampling_paths - n_sampled_paths) 115 | size_anonymous = min(anonymous_batch.shape[0], n_sampling_anonymous_walks - n_sampled_anonymous_walks) 116 | paths[n_sampled_paths: n_sampled_paths + size] = paths_batch[ : size] 117 | anonymous_walks[n_sampled_anonymous_walks: n_sampled_anonymous_walks + size_anonymous] = anonymous_batch[ : size_anonymous] 118 | n_sampled_paths += size 119 | n_sampled_anonymous_walks += size_anonymous 120 | 121 | paths = paths[:n_sampled_paths] 122 | anonymous_walks = anonymous_walks[:n_sampled_anonymous_walks] 123 | layer.unsup_train(paths, anonymous_walks, init=init) 124 | 125 | 126 | class GCKNetFeature(nn.Module): 127 | def __init__(self, input_size, hidden_size, path_sizes, anonymous_walk_length, 128 | kernel_funcs=None, kernel_args_list=None, 129 | pooling='mean', global_pooling='sum', 130 | aggregation=False, **kwargs): 131 | super().__init__() 132 | self.path_layers = PathSequential( 133 | input_size, hidden_size, path_sizes, anonymous_walk_length, 134 | kernel_funcs, kernel_args_list, 135 | pooling, aggregation, **kwargs) 136 | self.node_pooling = NodePooling(global_pooling) 137 | self.output_size = self.path_layers.output_size 138 | 139 | def forward(self, input, paths_indices, anonymous_walks, other_info): 140 | output = self.path_layers(input, paths_indices, anonymous_walks, other_info) 141 | return self.node_pooling(output, other_info) 142 | 143 | def unsup_train(self, data_loader, n_sampling_paths=100000, 144 | init=None, use_cuda=False): 145 | self.path_layers.unsup_train(data_loader, n_sampling_paths, 146 | init, use_cuda) 147 | 148 | def predict(self, data_loader, use_cuda=False): 149 | if use_cuda: 150 | self.cuda() 151 | self.eval() # torch 152 | output = torch.Tensor(data_loader.n, self.output_size * 2) 153 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 154 | 155 | batch_start = 0 156 | for data in data_loader.make_batch(shuffle=False): 157 | features = data['features'] 158 | paths_indices = data['paths'] 159 | n_paths = data['n_paths'] 160 | # print(n_paths) 161 | n_nodes = data['n_nodes'] 162 | anonymous_walks = data['anonymous_walks'] 163 | 164 | size = len(n_nodes) 165 | if use_cuda: 166 | features = features.cuda() 167 | if isinstance(n_paths, list): 168 | paths_indices = [p.cuda() for p in paths_indices] 169 | n_paths = [p.cuda() for p in n_paths] 170 | else: 171 | paths_indices = paths_indices.cuda() 172 | n_paths = n_paths.cuda() 173 | anonymous_walks = anonymous_walks.cuda() 174 | n_nodes = n_nodes.cuda() 175 | with torch.no_grad(): 176 | features = features.to(device) 177 | paths_indices = [path_index.to(device) for path_index in paths_indices] 178 | batch_out = self.forward(features, paths_indices, anonymous_walks, {'n_paths': n_paths, 'n_nodes': n_nodes}) 179 | output[batch_start: batch_start + size] = batch_out 180 | batch_start += size 181 | return output, data_loader.labels 182 | -------------------------------------------------------------------------------- /gckn/ops.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import torch 3 | 4 | 5 | class MatrixInverseSqrt(torch.autograd.Function): 6 | """Matrix inverse square root for a symmetric definite positive matrix 7 | """ 8 | @staticmethod 9 | def forward(ctx, input, eps=1e-2): 10 | dim = input.dim() 11 | ctx.dim = dim 12 | use_cuda = input.is_cuda 13 | if input.size(0) < 300: 14 | input = input.cpu() 15 | e, v = torch.symeig(input, eigenvectors=True) 16 | if use_cuda and input.size(0) < 300: 17 | e = e.cuda() 18 | v = v.cuda() 19 | e = e.clamp(min=0) 20 | e_sqrt = e.sqrt_().add_(eps) 21 | ctx.save_for_backward(e_sqrt, v) 22 | e_rsqrt = e_sqrt.reciprocal() 23 | 24 | if dim > 2: 25 | output = v.bmm(v.permute(0, 2, 1) * e_rsqrt.unsqueeze(-1)) 26 | else: 27 | output = v.mm(v.t() * e_rsqrt.view(-1, 1)) 28 | return output 29 | 30 | @staticmethod 31 | def backward(ctx, grad_output): 32 | e_sqrt, v = ctx.saved_variables 33 | if ctx.dim > 2: 34 | ei = e_sqrt.unsqueeze(1).expand_as(v) 35 | ej = e_sqrt.unsqueeze(-1).expand_as(v) 36 | else: 37 | ei = e_sqrt.expand_as(v) 38 | ej = e_sqrt.view(-1, 1).expand_as(v) 39 | f = torch.reciprocal((ei + ej) * ei * ej) 40 | if ctx.dim > 2: 41 | vt = v.permute(0, 2, 1) 42 | grad_input = -v.bmm((f*(vt.bmm(grad_output.bmm(v)))).bmm(vt)) 43 | else: 44 | grad_input = -v.mm((f*(v.t().mm(grad_output.mm(v)))).mm(v.t())) 45 | return grad_input, None 46 | 47 | 48 | def matrix_inverse_sqrt(input, eps=1e-2): 49 | """Wrapper for MatrixInverseSqrt""" 50 | return MatrixInverseSqrt.apply(input, eps) 51 | 52 | 53 | if __name__ == "__main__": 54 | torch.manual_seed(0) 55 | # x = torch.rand(1, 3, 3) 56 | # x = torch.bmm(x, x.permute(0, 2, 1)) 57 | x = torch.rand(3, 3) 58 | x = torch.mm(x, x.t()) 59 | x.unsqueeze_(0) 60 | x = x.repeat(5, 1, 1) 61 | x.requires_grad_() 62 | out = matrix_inverse_sqrt(x) 63 | print(out) 64 | out = out.sum() 65 | out.backward() 66 | print(x.grad) 67 | -------------------------------------------------------------------------------- /gckn/path_conv_agg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import torch 3 | from gckn.gckn_fast.gckn_fast import path_conv_forward, path_conv_backward 4 | from gckn.dynamic_pooling.pooling import dpooling_forward, dpooling_backward 5 | 6 | 7 | MAXRAM = int(5e9) 8 | # MAXRAM = int(100000) 9 | 10 | def get_batch_indices(array, batch_size): 11 | indices = [0] 12 | s = 0 13 | for i, v in enumerate(array): 14 | s += v.item() 15 | if s > batch_size: 16 | indices.append(i) 17 | s = v.item() 18 | indices.append(len(array)) 19 | return indices 20 | 21 | 22 | from gckn.dynamic_pooling.pooling import dpooling_torch, dpooling 23 | from gckn.gckn_fast.gckn_fast import path_conv, PathConv 24 | 25 | 26 | def path_conv_agg(features, path_indices, kernel_size, pooling='sum', kappa=torch.exp, d_kappa=torch.exp, mask=None): 27 | ram_saving = MAXRAM <= (2 * path_indices.shape[0] * features.shape[-1] * features.element_size()) 28 | if ram_saving and mask is None: 29 | return PathConvAggregation.apply( 30 | features, path_indices, kernel_size, pooling, kappa, d_kappa) 31 | embeded = PathConv.apply(path_indices, features) 32 | embeded = kappa(embeded) 33 | if mask is not None: 34 | embeded = embeded * mask.view(-1, 1) 35 | embeded = dpooling(embeded, kernel_size, pooling) 36 | return embeded 37 | -------------------------------------------------------------------------------- /gckn/setup.py: -------------------------------------------------------------------------------- 1 | def configuration(parent_package='', top_path=None): 2 | from numpy.distutils.misc_util import Configuration 3 | 4 | config = Configuration('gckn', parent_package, top_path) 5 | 6 | config.add_subpackage('graphs') 7 | 8 | return config 9 | 10 | 11 | if __name__ == '__main__': 12 | from numpy.distutils.core import setup 13 | setup(**configuration(top_path='').todict()) 14 | -------------------------------------------------------------------------------- /gckn/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import math 3 | import numpy as np 4 | import torch 5 | from sklearn.base import BaseEstimator, TransformerMixin 6 | from sklearn.utils.validation import check_is_fitted 7 | 8 | EPS = 1e-4 9 | 10 | 11 | def normalize_(x, p=2, dim=-1, c2=1.): 12 | norm = x.norm(p=p, dim=dim, keepdim=True) 13 | x.div_(norm.clamp(min=EPS) / math.sqrt(c2)) 14 | return x 15 | 16 | def init_kmeans(x, n_clusters, norm=1., n_local_trials=None, use_cuda=False): 17 | n_samples, n_features = x.size() 18 | clusters = torch.Tensor(n_clusters, n_features) 19 | if use_cuda: 20 | clusters = clusters.cuda() 21 | 22 | if n_local_trials is None: 23 | n_local_trials = 2 + int(np.log(n_clusters)) 24 | clusters[0] = x[np.random.randint(n_samples)] 25 | 26 | closest_dist_sq = 2 * (norm - clusters[[0]].mm(x.t())) 27 | closest_dist_sq = closest_dist_sq.view(-1) 28 | current_pot = closest_dist_sq.sum().item() 29 | 30 | for c in range(1, n_clusters): 31 | rand_vals = np.random.random_sample(n_local_trials).astype('float32') * current_pot 32 | rand_vals = np.minimum(rand_vals, current_pot * (1.0 - EPS)) 33 | candidate_ids = np.searchsorted(closest_dist_sq.cumsum(-1).cpu(), rand_vals) 34 | distance_to_candidates = 2 * (norm - x[candidate_ids].mm(x.t())) 35 | 36 | best_candidate = None 37 | best_pot = None 38 | best_dist_sq = None 39 | for trial in range(n_local_trials): 40 | # Compute potential when including center candidate 41 | new_dist_sq = torch.min(closest_dist_sq, 42 | distance_to_candidates[trial]) 43 | new_pot = new_dist_sq.sum().item() 44 | 45 | # Store result if it is the best local trial so far 46 | if (best_candidate is None) or (new_pot < best_pot): 47 | best_candidate = candidate_ids[trial] 48 | best_pot = new_pot 49 | best_dist_sq = new_dist_sq 50 | 51 | clusters[c] = x[best_candidate] 52 | current_pot = best_pot 53 | closest_dist_sq = best_dist_sq 54 | 55 | return clusters 56 | 57 | 58 | def spherical_kmeans(x, n_clusters, max_iters=100, verbose=True, init=None, eps=1e-4): 59 | """Spherical kmeans 60 | Args: 61 | x (Tensor n_samples x kmer_size x n_features): data points 62 | n_clusters (int): number of clusters 63 | """ 64 | use_cuda = x.is_cuda 65 | if x.ndim == 3: 66 | n_samples, kmer_size, n_features = x.size() 67 | else: 68 | n_samples, n_features = x.size() 69 | if init == "kmeans++": 70 | print(init) 71 | if x.ndim == 3: 72 | clusters = init_kmeans(x.view(n_samples, -1), n_clusters, norm=kmer_size, use_cuda=use_cuda) 73 | clusters = clusters.view(n_clusters, kmer_size, n_features) 74 | else: 75 | clusters = init_kmeans(x, n_clusters, use_cuda=use_cuda) 76 | else: 77 | indices = torch.randperm(n_samples)[:n_clusters] 78 | if use_cuda: 79 | indices = indices.cuda() 80 | clusters = x[indices] 81 | 82 | prev_sim = np.inf 83 | 84 | for n_iter in range(max_iters): 85 | # assign data points to clusters 86 | cos_sim = x.view(n_samples, -1).mm(clusters.view(n_clusters, -1).t()) 87 | tmp, assign = cos_sim.max(dim=-1) 88 | sim = tmp.mean() 89 | if (n_iter + 1) % 10 == 0 and verbose: 90 | print("Spherical kmeans iter {}, objective value {}".format( 91 | n_iter + 1, sim)) 92 | 93 | # update clusters 94 | for j in range(n_clusters): 95 | index = assign == j 96 | if index.sum() == 0: 97 | # clusters[j] = x[random.randrange(n_samples)] 98 | idx = tmp.argmin() 99 | clusters[j] = x[idx] 100 | tmp[idx] = 1 101 | else: 102 | xj = x[index] 103 | c = xj.mean(0) 104 | clusters[j] = c / c.norm(dim=-1, keepdim=True).clamp(min=EPS) 105 | 106 | if torch.abs(prev_sim - sim)/(torch.abs(sim)+1e-20) < 1e-6: 107 | break 108 | prev_sim = sim 109 | return clusters 110 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import torch 5 | import numpy as np 6 | import pandas as pd 7 | import argparse 8 | from sklearn.model_selection import GridSearchCV 9 | from sklearn.pipeline import make_pipeline 10 | from sklearn.preprocessing import StandardScaler 11 | from cyanure import LinearSVC 12 | import datetime 13 | 14 | from gckn.models import GCKNetFeature 15 | from gckn.data import load_data, PathLoader 16 | 17 | def eval(args, features, labels): 18 | features, labels = features.numpy(), labels.numpy() 19 | 20 | if not os.path.exists("./result/"): 21 | try: 22 | os.makedirs("./result/") 23 | except: 24 | pass 25 | 26 | np.savetxt("./result/gskn_" + args.dataset + ".txt", features) 27 | np.savetxt("./result/labels.txt", labels) 28 | 29 | print('Cross validation') 30 | train_fold_idx = [np.loadtxt('./dataset/{}/10fold_idx/train_idx-{}.txt'.format( 31 | args.dataset, i)).astype(int) for i in range(1, 11)] 32 | test_fold_idx = [np.loadtxt('./dataset/{}/10fold_idx/test_idx-{}.txt'.format( 33 | args.dataset, i)).astype(int) for i in range(1, 11)] 34 | cv_idx = zip(train_fold_idx, test_fold_idx) 35 | 36 | C_list = np.logspace(-4, 4, 60) 37 | svc = LinearSVC(C=1.0) 38 | clf = GridSearchCV(make_pipeline(StandardScaler(), svc), 39 | {'linearsvc__C' : C_list}, 40 | cv=cv_idx, 41 | n_jobs=4, verbose=0, return_train_score=True) 42 | 43 | clf.fit(features, labels) 44 | df = pd.DataFrame({'C': C_list, 45 | 'train': clf.cv_results_['mean_train_score'], 46 | 'test': clf.cv_results_['mean_test_score'], 47 | 'test_std': clf.cv_results_['std_test_score']}, 48 | columns=['C', 'train', 'test', 'test_std']) 49 | print(df) 50 | 51 | print("best test acc: %f" % (df['test'].max())) 52 | 53 | 54 | def load_args(): 55 | parser = argparse.ArgumentParser( 56 | description='Unsupervised GCKN', 57 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 58 | parser.add_argument('--seed', type=int, default=0, 59 | help='random seed') 60 | parser.add_argument('--dataset', type=str, default="MUTAG", 61 | help='name of dataset') 62 | parser.add_argument('--batch-size', type=int, default=16, 63 | help='batch size') 64 | parser.add_argument('--path-size', type=int, nargs='+', default=[2], 65 | help='path sizes for layers') 66 | parser.add_argument('--hidden_size', type=int, nargs='+', default=[32], 67 | help='number of filters for layers') 68 | 69 | parser.add_argument('--anonymous_walk_length', type=int, default=10, help=' ') 70 | parser.add_argument('--anonymous_walks_per_node', type=int, default=30, help=' ') 71 | 72 | parser.add_argument('--pooling', type=str, default='sum', 73 | help='local path pooling for each node') 74 | parser.add_argument('--global-pooling', type=str, default='sum', 75 | help='global node pooling for each graph') 76 | parser.add_argument('--aggregation', action='store_true', 77 | help='aggregate all path features until path size') 78 | parser.add_argument('--sigma', type=float, nargs='+', default=[0.5], 79 | help='sigma of exponential (Gaussian) kernels for layers') 80 | parser.add_argument('--sampling-paths', type=int, default=300000, 81 | help='number of paths to sample for unsupervised training') 82 | parser.add_argument('--walk', action='store_true', 83 | help='use walk instead of path') 84 | parser.add_argument('--outdir', type=str, default='/nas/user/qk/GCKN/res', 85 | help='output path') 86 | args = parser.parse_args() 87 | args.continuous = False 88 | if args.dataset in ['IMDBBINARY', 'IMDBMULTI', 'COLLAB']: 89 | # social network 90 | degree_as_tag = True 91 | elif args.dataset in ['MUTAG', 'PROTEINS', 'PTC', 'NCI1', 'sys']: 92 | # bioinformatics 93 | degree_as_tag = False 94 | elif args.dataset in ['BZR', 'COX2', 'ENZYMES', 'PROTEINS_full']: 95 | degree_as_tag = False 96 | args.continuous = True 97 | else: 98 | raise ValueError("Unrecognized dataset!") 99 | args.degree_as_tag = degree_as_tag 100 | 101 | args.save_logs = False 102 | return args 103 | 104 | 105 | def main(): 106 | args = load_args() 107 | torch.manual_seed(args.seed) 108 | np.random.seed(args.seed) 109 | print(args) 110 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 111 | 112 | start_time = datetime.datetime.now() 113 | graphs, n_class = load_data(args.dataset, './dataset', degree_as_tag=args.degree_as_tag) 114 | if args.continuous: 115 | print("Dataset with continuous node attributes") 116 | node_features = np.concatenate([g.node_features for g in graphs], axis=0) 117 | sc = StandardScaler() 118 | sc.fit(node_features) 119 | for g in graphs: 120 | node_features = sc.transform(g.node_features) 121 | g.node_features = node_features / np.linalg.norm(node_features, axis=-1, keepdims=True).clip(min=1e-06) 122 | 123 | data_loader = PathLoader(graphs, max(args.path_size), args.batch_size, args.anonymous_walk_length, 124 | args.anonymous_walks_per_node, 125 | True, dataset=args.dataset, walk=args.walk) 126 | print('Computing paths...') 127 | if args.dataset != 'COLLAB' or max(args.path_size) <= 2: 128 | data_loader.get_all_paths() 129 | 130 | 131 | input_size = data_loader.input_size 132 | 133 | print('Unsupervised training...') 134 | model = GCKNetFeature(input_size, args.hidden_size, args.path_size, args.anonymous_walk_length, 135 | kernel_args_list=args.sigma, pooling=args.pooling, 136 | global_pooling=args.global_pooling, 137 | aggregation=args.aggregation) 138 | 139 | model = torch.nn.DataParallel(model, device_ids=[0, 1]) 140 | model = model.to(device) 141 | model.module.unsup_train(data_loader, n_sampling_paths=args.sampling_paths) 142 | 143 | print('Encoding...') 144 | features, labels = model.module.predict(data_loader) 145 | 146 | print("Evaluating...") 147 | eval(args, features, labels) 148 | 149 | if __name__ == "__main__": 150 | main() 151 | -------------------------------------------------------------------------------- /result/labels.txt: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 2 | 0.000000000000000000e+00 3 | 0.000000000000000000e+00 4 | 0.000000000000000000e+00 5 | 0.000000000000000000e+00 6 | 0.000000000000000000e+00 7 | 0.000000000000000000e+00 8 | 0.000000000000000000e+00 9 | 0.000000000000000000e+00 10 | 0.000000000000000000e+00 11 | 0.000000000000000000e+00 12 | 0.000000000000000000e+00 13 | 0.000000000000000000e+00 14 | 0.000000000000000000e+00 15 | 0.000000000000000000e+00 16 | 0.000000000000000000e+00 17 | 0.000000000000000000e+00 18 | 0.000000000000000000e+00 19 | 0.000000000000000000e+00 20 | 0.000000000000000000e+00 21 | 0.000000000000000000e+00 22 | 0.000000000000000000e+00 23 | 0.000000000000000000e+00 24 | 0.000000000000000000e+00 25 | 0.000000000000000000e+00 26 | 0.000000000000000000e+00 27 | 0.000000000000000000e+00 28 | 0.000000000000000000e+00 29 | 0.000000000000000000e+00 30 | 0.000000000000000000e+00 31 | 0.000000000000000000e+00 32 | 0.000000000000000000e+00 33 | 0.000000000000000000e+00 34 | 0.000000000000000000e+00 35 | 0.000000000000000000e+00 36 | 0.000000000000000000e+00 37 | 0.000000000000000000e+00 38 | 0.000000000000000000e+00 39 | 0.000000000000000000e+00 40 | 0.000000000000000000e+00 41 | 0.000000000000000000e+00 42 | 0.000000000000000000e+00 43 | 0.000000000000000000e+00 44 | 0.000000000000000000e+00 45 | 0.000000000000000000e+00 46 | 0.000000000000000000e+00 47 | 0.000000000000000000e+00 48 | 0.000000000000000000e+00 49 | 0.000000000000000000e+00 50 | 0.000000000000000000e+00 51 | 0.000000000000000000e+00 52 | 0.000000000000000000e+00 53 | 0.000000000000000000e+00 54 | 0.000000000000000000e+00 55 | 0.000000000000000000e+00 56 | 0.000000000000000000e+00 57 | 0.000000000000000000e+00 58 | 0.000000000000000000e+00 59 | 0.000000000000000000e+00 60 | 0.000000000000000000e+00 61 | 0.000000000000000000e+00 62 | 0.000000000000000000e+00 63 | 0.000000000000000000e+00 64 | 0.000000000000000000e+00 65 | 0.000000000000000000e+00 66 | 0.000000000000000000e+00 67 | 0.000000000000000000e+00 68 | 0.000000000000000000e+00 69 | 0.000000000000000000e+00 70 | 0.000000000000000000e+00 71 | 0.000000000000000000e+00 72 | 0.000000000000000000e+00 73 | 0.000000000000000000e+00 74 | 0.000000000000000000e+00 75 | 0.000000000000000000e+00 76 | 0.000000000000000000e+00 77 | 0.000000000000000000e+00 78 | 0.000000000000000000e+00 79 | 0.000000000000000000e+00 80 | 0.000000000000000000e+00 81 | 0.000000000000000000e+00 82 | 0.000000000000000000e+00 83 | 0.000000000000000000e+00 84 | 0.000000000000000000e+00 85 | 0.000000000000000000e+00 86 | 0.000000000000000000e+00 87 | 0.000000000000000000e+00 88 | 0.000000000000000000e+00 89 | 0.000000000000000000e+00 90 | 0.000000000000000000e+00 91 | 0.000000000000000000e+00 92 | 0.000000000000000000e+00 93 | 0.000000000000000000e+00 94 | 0.000000000000000000e+00 95 | 0.000000000000000000e+00 96 | 0.000000000000000000e+00 97 | 0.000000000000000000e+00 98 | 0.000000000000000000e+00 99 | 0.000000000000000000e+00 100 | 0.000000000000000000e+00 101 | 0.000000000000000000e+00 102 | 0.000000000000000000e+00 103 | 0.000000000000000000e+00 104 | 0.000000000000000000e+00 105 | 0.000000000000000000e+00 106 | 0.000000000000000000e+00 107 | 0.000000000000000000e+00 108 | 0.000000000000000000e+00 109 | 0.000000000000000000e+00 110 | 0.000000000000000000e+00 111 | 0.000000000000000000e+00 112 | 0.000000000000000000e+00 113 | 0.000000000000000000e+00 114 | 0.000000000000000000e+00 115 | 0.000000000000000000e+00 116 | 0.000000000000000000e+00 117 | 0.000000000000000000e+00 118 | 0.000000000000000000e+00 119 | 0.000000000000000000e+00 120 | 0.000000000000000000e+00 121 | 0.000000000000000000e+00 122 | 0.000000000000000000e+00 123 | 0.000000000000000000e+00 124 | 0.000000000000000000e+00 125 | 0.000000000000000000e+00 126 | 1.000000000000000000e+00 127 | 1.000000000000000000e+00 128 | 1.000000000000000000e+00 129 | 1.000000000000000000e+00 130 | 1.000000000000000000e+00 131 | 1.000000000000000000e+00 132 | 1.000000000000000000e+00 133 | 1.000000000000000000e+00 134 | 1.000000000000000000e+00 135 | 1.000000000000000000e+00 136 | 1.000000000000000000e+00 137 | 1.000000000000000000e+00 138 | 1.000000000000000000e+00 139 | 1.000000000000000000e+00 140 | 1.000000000000000000e+00 141 | 1.000000000000000000e+00 142 | 1.000000000000000000e+00 143 | 1.000000000000000000e+00 144 | 1.000000000000000000e+00 145 | 1.000000000000000000e+00 146 | 1.000000000000000000e+00 147 | 1.000000000000000000e+00 148 | 1.000000000000000000e+00 149 | 1.000000000000000000e+00 150 | 1.000000000000000000e+00 151 | 1.000000000000000000e+00 152 | 1.000000000000000000e+00 153 | 1.000000000000000000e+00 154 | 1.000000000000000000e+00 155 | 1.000000000000000000e+00 156 | 1.000000000000000000e+00 157 | 1.000000000000000000e+00 158 | 1.000000000000000000e+00 159 | 1.000000000000000000e+00 160 | 1.000000000000000000e+00 161 | 1.000000000000000000e+00 162 | 1.000000000000000000e+00 163 | 1.000000000000000000e+00 164 | 1.000000000000000000e+00 165 | 1.000000000000000000e+00 166 | 1.000000000000000000e+00 167 | 1.000000000000000000e+00 168 | 1.000000000000000000e+00 169 | 1.000000000000000000e+00 170 | 1.000000000000000000e+00 171 | 1.000000000000000000e+00 172 | 1.000000000000000000e+00 173 | 1.000000000000000000e+00 174 | 1.000000000000000000e+00 175 | 1.000000000000000000e+00 176 | 1.000000000000000000e+00 177 | 1.000000000000000000e+00 178 | 1.000000000000000000e+00 179 | 1.000000000000000000e+00 180 | 1.000000000000000000e+00 181 | 1.000000000000000000e+00 182 | 1.000000000000000000e+00 183 | 1.000000000000000000e+00 184 | 1.000000000000000000e+00 185 | 1.000000000000000000e+00 186 | 1.000000000000000000e+00 187 | 1.000000000000000000e+00 188 | 1.000000000000000000e+00 189 | --------------------------------------------------------------------------------