From c54e92fe693ad38c10a4cc695e2406a5a9cd4b24 Mon Sep 17 00:00:00 2001 From: martinjingyu <1098549362@qq.com> Date: Tue, 2 Jul 2024 20:54:25 +0800 Subject: [PATCH 1/3] Add HGAT --- examples/hgat/hgat_trainer.py | 263 +++++++++++++++++++++ examples/hgat/readme.md | 29 +++ gammagl/datasets/__init__.py | 8 +- gammagl/datasets/agnews.py | 81 +++++++ gammagl/datasets/ohsumed.py | 82 +++++++ gammagl/datasets/twitter.py | 82 +++++++ gammagl/layers/conv/__init__.py | 6 +- gammagl/layers/conv/hgat_conv.py | 120 ++++++++++ gammagl/layers/conv/hin_conv.py | 35 +++ gammagl/models/__init__.py | 4 +- gammagl/models/hgat.py | 62 +++++ gammagl/utils/__init__.py | 7 +- gammagl/utils/homo_heter_mutual_convert.py | 203 ++++++++++++++++ 13 files changed, 978 insertions(+), 4 deletions(-) create mode 100644 examples/hgat/hgat_trainer.py create mode 100644 examples/hgat/readme.md create mode 100644 gammagl/datasets/agnews.py create mode 100644 gammagl/datasets/ohsumed.py create mode 100644 gammagl/datasets/twitter.py create mode 100644 gammagl/layers/conv/hgat_conv.py create mode 100644 gammagl/layers/conv/hin_conv.py create mode 100644 gammagl/models/hgat.py create mode 100644 gammagl/utils/homo_heter_mutual_convert.py diff --git a/examples/hgat/hgat_trainer.py b/examples/hgat/hgat_trainer.py new file mode 100644 index 00000000..30345221 --- /dev/null +++ b/examples/hgat/hgat_trainer.py @@ -0,0 +1,263 @@ +# !/usr/bin/env python3 +# -*- coding:utf-8 -*- + +# @Time : 2022/04/16 25:16 +# @Author : Jingyu Huang +# @FileName: hgat_trainer.py +import os +os.environ['CUDA_VISIBLE_DEVICES'] = '2' +os.environ['TL_BACKEND'] = 'tensorflow' +# 0:Output all; 1:Filter out INFO; 2:Filter out INFO and WARNING; 3:Filter out INFO, WARNING, and ERROR +import numpy as np +import argparse +import tensorlayerx as tlx +import gammagl.transforms as T +from gammagl.datasets import AGNews,IMDB, OHSUMED, Twitter + + +from gammagl.models import HGATModel; +from gammagl.utils import mask_to_index, set_device +from tensorlayerx.model import TrainOneStep, WithLoss + +class SemiSpvzLoss(WithLoss): + def __init__(self, net, loss_fn): + super(SemiSpvzLoss, self).__init__(backbone=net, loss_fn=loss_fn) + + def forward(self, data, y, node_tpye): + logits = self.backbone_network(data['x_dict'], data['edge_index_dict'], data['num_nodes_dict']) + train_logits = tlx.gather(logits[node_tpye], data['train_idx']) + train_y = tlx.gather(data['y'], data['train_idx']) + loss = self._loss_fn(train_logits, train_y) + return loss + + +def calculate_acc(logits, y, metrics): + """ + Args: + logits: node logits + y: node labels + metrics: tensorlayerx.metrics + + Returns: + rst + """ + + metrics.update(logits, y) + rst = metrics.result() + metrics.reset() + return rst + + +def main(args): + # NOTE: ONLY IMDB DATASET + # If you want to execute HAN on other dataset (e.g. ACM), + # you will be needed to init `metepaths` + # and set `movie` string with proper values. + # path = osp.join(osp.dirname(osp.realpath(__file__)), '../IMDB') + if(args.dataset=="IMDB"): + dataset = IMDB(args.dataset_path) + graph = dataset[0] + print(graph) + y = graph['movie'].y + node_type = 'movie' + print(len(np.unique(graph['movie'].y.cpu()))) + # for mindspore, it should be passed into node indices + train_idx = mask_to_index(graph['movie'].train_mask) + test_idx = mask_to_index(graph['movie'].test_mask) + val_idx = mask_to_index(graph['movie'].val_mask) + + in_channel = {'movie':3066, 'director':3066, 'actor': 3066} + num_nodes_dict = {'movie':4278,'actor':5257,'director':2081} + print(len(np.unique(graph['movie'].y.cpu()))) + net = HGATModel( + in_channels=in_channel, + out_channels=len(np.unique(graph['movie'].y.cpu())), # graph.num_classes, + metadata=graph.metadata(), + drop_rate=0.5, + hidden_channels=512, + name = 'hgat', + ) + + + if(args.dataset=="agnews"): + dataset = AGNews(args.dataset_path) + graph = dataset[0] + print(graph) + y = graph['text'].y + node_type = 'text' + print(len(np.unique(graph['text'].y.cpu()))) + # for mindspore, it should be passed into node indices + train_idx = mask_to_index(graph['text'].train_mask,) + test_idx = mask_to_index(graph['text'].test_mask) + val_idx = mask_to_index(graph['text'].val_mask) + + in_channel = {'text':5126, 'topic':4962, 'entity': 4378} + num_nodes_dict = {'text': 3200, 'topic': 15, 'entity': 5680} + + net = HGATModel( + in_channels=in_channel, + out_channels=len(np.unique(graph['text'].y.cpu())), # graph.num_classes, + metadata=graph.metadata(), + drop_rate=args.drop_rate, + hidden_channels=args.hidden_dim, + name='hgat', + ) + + if(args.dataset=="ohsumed"): + dataset = OHSUMED(args.dataset_path) + graph = dataset[0] + print(graph) + y = graph['documents'].y + node_type = 'documents' + + # for mindspore, it should be passed into node indices + train_idx = mask_to_index(graph['documents'].train_mask,) + test_idx = mask_to_index(graph['documents'].test_mask) + val_idx = mask_to_index(graph['documents'].val_mask) + + + num_nodes_dict = {'documents': 7400, 'topics': 15, 'words': 5420} + in_channel = {'documents':2471, 'topics':2472, 'words': 3197} + + + net = HGATModel( + in_channels=in_channel, + out_channels=len(np.unique(graph['documents'].y.cpu())), # graph.num_classes, + metadata=graph.metadata(), + drop_rate=args.drop_rate, + hidden_channels=256, + name='hgat', + ) + if(args.dataset=="twitter"): + dataset = Twitter(args.dataset_path) + graph = dataset[0] + print(graph) + y = graph['twitter'].y + node_type = 'twitter' + + # for mindspore, it should be passed into node indices + train_idx = mask_to_index(graph['twitter'].train_mask,) + test_idx = mask_to_index(graph['twitter'].test_mask) + val_idx = mask_to_index(graph['twitter'].val_mask) + + + num_nodes_dict = {'twitter': 10000, 'topics': 15, 'entity': 4698} + in_channel = {'twitter':1515, 'topics':1543, 'entity': 2787} + + + net = HGATModel( + in_channels=in_channel, + out_channels=len(np.unique(graph['twitter'].y.cpu())), # graph.num_classes, + metadata=graph.metadata(), + drop_rate=args.drop_rate, + hidden_channels=64, + name='hgat', + ) + + + optimizer = tlx.optimizers.Adam(lr=args.lr, weight_decay=args.l2_coef) + metrics = tlx.metrics.Accuracy() + train_weights = net.trainable_weights + + loss_func = tlx.losses.softmax_cross_entropy_with_logits + semi_spvz_loss = SemiSpvzLoss(net, loss_func) + train_one_step = TrainOneStep(semi_spvz_loss, optimizer, train_weights) + + data = { + "x_dict": graph.x_dict, + "y": y, + "edge_index_dict": graph.edge_index_dict, + "train_idx": train_idx, + "test_idx": test_idx, + "val_idx": val_idx, + "num_nodes_dict": num_nodes_dict, + } + print(np.unique(y.cpu())) + best_val_acc = 0 + + + + # dataset1 = AGNews(args.dataset_path) + # graph1 = dataset1[0] + # print(graph1) + # y1 = graph1['text'].y + # print(len(np.unique(graph1['text'].y.cpu()))) + # # for mindspore, it should be passed into node indices + # train_idx = mask_to_index(graph1['text'].train_mask,) + # test_idx = mask_to_index(graph1['text'].test_mask) + # val_idx = mask_to_index(graph1['text'].val_mask) + + # in_channel1 = {'text':5126, 'topic':4962, 'entity': 4378} + # num_nodes_dict1 = {'text': 3200, 'topic': 15, 'entity': 5680} + + + # dataset2 = OHSUMED(args.dataset_path) + # graph2 = dataset2[0] + # print(graph2) + # y = graph2['documents'].y + # node_type2 = 'documents' + + # # for mindspore, it should be passed into node indices + # train_idx2 = mask_to_index(graph2['documents'].train_mask,) + # test_idx2 = mask_to_index(graph2['documents'].test_mask) + # val_idx2 = mask_to_index(graph2['documents'].val_mask) + + + # num_nodes_dict = {'documents': 10000, 'topics': 15, 'words': 4698} + # in_channel = {'documents':1515, 'topics':1543, 'words': 2787} + + + + + + + for epoch in range(args.n_epoch): + net.set_train() + train_loss = train_one_step(data, y, node_type) + net.set_eval() + + logits = net(data['x_dict'], data['edge_index_dict'], data['num_nodes_dict']) + val_logits = tlx.gather(logits[node_type], data['val_idx']) + val_y = tlx.gather(data['y'], data['val_idx']) + val_acc = calculate_acc(val_logits, val_y, metrics) + + print("Epoch [{:0>3d}] ".format(epoch + 1) + + " train_loss: {:.4f}".format(train_loss.item()) + # + " train_acc: {:.4f}".format(train_acc) + + " val_acc: {:.4f}".format(val_acc)) + + # save best model on evaluation set + if val_acc > best_val_acc: + best_val_acc = val_acc + net.save_weights(args.best_model_path + net.name + ".npz", format='npz_dict') + + net.load_weights(args.best_model_path + net.name + ".npz", format='npz_dict') + net.set_eval() + logits = net(data['x_dict'], data['edge_index_dict'], data['num_nodes_dict']) + test_logits = tlx.gather(logits[node_type], data['test_idx']) + test_y = tlx.gather(data['y'], data['test_idx']) + test_acc = calculate_acc(test_logits, test_y, metrics) + print("Test acc: {:.4f}".format(test_acc)) + + +if __name__ == '__main__': + # parameters setting + parser = argparse.ArgumentParser() + parser.add_argument("--lr", type=float, default=0.005, help="learnin rate") + parser.add_argument("--n_epoch", type=int, default=100, help="number of epoch") + parser.add_argument("--hidden_dim", type=int, default=64, help="dimention of hidden layers") + parser.add_argument("--l2_coef", type=float, default=1e-3, help="l2 loss coeficient") + parser.add_argument("--heads", type=int, default=8, help="number of heads for stablization") + parser.add_argument("--drop_rate", type=float, default=0.6, help="drop_rate") + parser.add_argument("--gpu", type=int, default=0, help="gpu id") + parser.add_argument("--dataset_path", type=str, default=r'', help="path to save dataset") + parser.add_argument('--dataset', type=str, default='IMDB', help='dataset') + parser.add_argument("--best_model_path", type=str, default=r'./', help="path to save best model") + + args = parser.parse_args() + if args.gpu >= 0: + tlx.set_device("GPU", args.gpu) + else: + tlx.set_device("CPU") + + main(args) diff --git a/examples/hgat/readme.md b/examples/hgat/readme.md new file mode 100644 index 00000000..9044b357 --- /dev/null +++ b/examples/hgat/readme.md @@ -0,0 +1,29 @@ +# Heterogeneous Graph Attention Network (HGAT) + +This is an implementation of `HAN` for heterogeneous graphs. + +- Paper link: [https://aclanthology.org/D19-1488/](https://aclanthology.org/D19-1488/) +- Author's code repo: [https://github.com/BUPT-GAMMA/HGAT](https://github.com/BUPT-GAMMA/HGAT). Note that the original code is + implemented with Tensorflow for the paper. + +## Usage + +`python hgat_trainer.py` for reproducing HGAT's work on IMDB. + + + +## Performance + + + +| Dataset |Paper(80% training) | Our(tf) | Our(th) | Our(pd) | +| ------- | ------------------ | ------- | ------- |-------- | +| AGNews | 72.10 | 63.80 | | | +| Ohsumed | 42.68 | 25.82 | | | +| Twitter | 63.21 | 61.06 | | | +| IMDB | | 57.71 | | | + +```bash +TL_BACKEND="tensorflow" python3 hgat_trainer.py --n_epoch 100 --lr 0.01 --l2_coef 0.0001 --drop_rate 0.8 + +``` \ No newline at end of file diff --git a/gammagl/datasets/__init__.py b/gammagl/datasets/__init__.py index 2dc598bb..bd64fd92 100644 --- a/gammagl/datasets/__init__.py +++ b/gammagl/datasets/__init__.py @@ -22,6 +22,9 @@ from .molecule_net import MoleculeNet from .acm4heco import ACM4HeCo from .yelp import Yelp +from .agnews import AGNews +from .ohsumed import OHSUMED +from .twitter import Twitter __all__ = [ 'ACM4HeCo', @@ -46,7 +49,10 @@ 'WikiCS', 'MoleculeNet', 'NGSIM_US_101', - 'Yelp' + 'Yelp', + 'AGNews', + 'OHSUMED', + 'Twitter' ] classes = __all__ diff --git a/gammagl/datasets/agnews.py b/gammagl/datasets/agnews.py new file mode 100644 index 00000000..bbd96938 --- /dev/null +++ b/gammagl/datasets/agnews.py @@ -0,0 +1,81 @@ +import os +import os.path as osp +from itertools import product +from typing import Callable, List, Optional + +import numpy as np +import scipy.sparse as sp +import tensorlayerx as tlx + +from gammagl.data import (HeteroGraph, InMemoryDataset, download_url, + extract_zip) + +class AGNews(InMemoryDataset): + r"""AGNews dataset processed for use in GNN models.""" + + url = 'https://www.dropbox.com/scl/fi/m809k1xdqzf0rhdmb83jf/agnews.zip?rlkey=wrz4by7f4tvtsdte2scuiec5k&st=s3ty36oi&dl=1' + + def __init__(self, root: str = None, transform: Optional[Callable] = None, + pre_transform: Optional[Callable] = None, force_reload: bool = False): + super().__init__(root, transform, pre_transform, force_reload=force_reload) + self.data, self.slices = self.load_data(self.processed_paths[0]) + + @property + def raw_file_names(self) -> List[str]: + return [ + 'adjM.npz', 'features_0.npz', 'features_1.npz', 'features_2.npz', + 'labels.npy', 'train_val_test_idx.npz' + ] + + @property + def processed_file_names(self) -> str: + return tlx.BACKEND + 'data.pt' + + def download(self): + path = download_url(self.url, self.raw_dir) + extract_zip(path, self.raw_dir) + os.remove(path) + + def process(self): + data = HeteroGraph() + + node_types = ['text', 'topic', 'entity'] + for i, node_type in enumerate(node_types): + x = sp.load_npz(osp.join(self.raw_dir, f'features_{i}.npz')) + data[node_type].x = tlx.convert_to_tensor(x.todense(), dtype=tlx.float32) + y = np.load(osp.join(self.raw_dir, 'labels.npy')) + y = np.argmax(y,axis=1) + data['text'].y = tlx.convert_to_tensor(y, dtype=tlx.int64) + + split = np.load(osp.join(self.raw_dir, 'train_val_test_idx.npz')) + for name in ['train', 'val', 'test']: + idx = split[f'{name}_idx'] + mask = np.zeros(data['text'].num_nodes, dtype=np.bool_) + mask[idx] = True + data['text'][f'{name}_mask'] = tlx.convert_to_tensor(mask, dtype=tlx.bool) + + + s = {} + N_m = data['text'].num_nodes + N_d = data['topic'].num_nodes + N_a = data['entity'].num_nodes + s['text'] = (0, N_m) + s['topic'] = (N_m, N_m + N_d) + s['entity'] = (N_m + N_d, N_m + N_d + N_a) + + A = sp.load_npz(osp.join(self.raw_dir, 'adjM.npz')).tocsr() + for src, dst in product(node_types, node_types): + A_sub = A[s[src][0]:s[src][1], s[dst][0]:s[dst][1]].tocoo() + if A_sub.nnz > 0: + row = tlx.convert_to_tensor(A_sub.row, dtype=tlx.int64) + col = tlx.convert_to_tensor(A_sub.col, dtype=tlx.int64) + data[src, dst].edge_index = tlx.stack([row, col], axis=0) + print(src+"____"+dst) + + if self.pre_transform is not None: + data = self.pre_transform(data) + + self.save_data(self.collate([data]), self.processed_paths[0]) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}()' \ No newline at end of file diff --git a/gammagl/datasets/ohsumed.py b/gammagl/datasets/ohsumed.py new file mode 100644 index 00000000..ed90e211 --- /dev/null +++ b/gammagl/datasets/ohsumed.py @@ -0,0 +1,82 @@ +import os +import os.path as osp +from itertools import product +from typing import Callable, List, Optional + +import numpy as np +import scipy.sparse as sp +import tensorlayerx as tlx + +from gammagl.data import (HeteroGraph, InMemoryDataset, download_url, + extract_zip) + +class OHSUMED(InMemoryDataset): + r"""AGNews dataset processed for use in GNN models.""" + + url = 'https://www.dropbox.com/scl/fi/di4u2apxat4v6oibq8j0q/ohsumed.zip?rlkey=hkbleedkz8bqw9p40y5zws425&st=yxj4kyzr&dl=1' + + def __init__(self, root: str = None, transform: Optional[Callable] = None, + pre_transform: Optional[Callable] = None, force_reload: bool = False): + super().__init__(root, transform, pre_transform, force_reload=force_reload) + self.data, self.slices = self.load_data(self.processed_paths[0]) + + @property + def raw_file_names(self) -> List[str]: + return [ + 'adjM.npz', 'features_0.npz', 'features_1.npz', 'features_2.npz', + 'labels.npy', 'train_val_test_idx.npz' + ] + + @property + def processed_file_names(self) -> str: + return tlx.BACKEND + 'data.pt' + + def download(self): + path = download_url(self.url, self.raw_dir) + extract_zip(path, self.raw_dir) + os.remove(path) + + def process(self): + data = HeteroGraph() + + node_types = ['documents', 'topics', 'words'] + for i, node_type in enumerate(node_types): + x = sp.load_npz(osp.join(self.raw_dir, f'features_{i}.npz')) + data[node_type].x = tlx.convert_to_tensor(x.todense(), dtype=tlx.float32) + + y = np.load(osp.join(self.raw_dir, 'labels.npy')) + y = np.argmax(y,axis=1) + data['documents'].y = tlx.convert_to_tensor(y, dtype=tlx.int64) + + split = np.load(osp.join(self.raw_dir, 'train_val_test_idx.npz')) + for name in ['train', 'val', 'test']: + idx = split[f'{name}_idx'] + mask = np.zeros(data['documents'].num_nodes, dtype=np.bool_) + mask[idx] = True + data['documents'][f'{name}_mask'] = tlx.convert_to_tensor(mask, dtype=tlx.bool) + + + s = {} + N_m = data['documents'].num_nodes + N_d = data['topics'].num_nodes + N_a = data['words'].num_nodes + s['documents'] = (0, N_m) + s['topics'] = (N_m, N_m + N_d) + s['words'] = (N_m + N_d, N_m + N_d + N_a) + + A = sp.load_npz(osp.join(self.raw_dir, 'adjM.npz')).tocsr() + for src, dst in product(node_types, node_types): + A_sub = A[s[src][0]:s[src][1], s[dst][0]:s[dst][1]].tocoo() + if A_sub.nnz > 0: + row = tlx.convert_to_tensor(A_sub.row, dtype=tlx.int64) + col = tlx.convert_to_tensor(A_sub.col, dtype=tlx.int64) + data[src, dst].edge_index = tlx.stack([row, col], axis=0) + print(src+"____"+dst) + + if self.pre_transform is not None: + data = self.pre_transform(data) + + self.save_data(self.collate([data]), self.processed_paths[0]) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}()' \ No newline at end of file diff --git a/gammagl/datasets/twitter.py b/gammagl/datasets/twitter.py new file mode 100644 index 00000000..704d5d80 --- /dev/null +++ b/gammagl/datasets/twitter.py @@ -0,0 +1,82 @@ +import os +import os.path as osp +from itertools import product +from typing import Callable, List, Optional + +import numpy as np +import scipy.sparse as sp +import tensorlayerx as tlx + +from gammagl.data import (HeteroGraph, InMemoryDataset, download_url, + extract_zip) + +class Twitter(InMemoryDataset): + r"""AGNews dataset processed for use in GNN models.""" + + url = 'https://www.dropbox.com/scl/fi/uqiglprqpz6rytaoc4k5p/twitter.zip?rlkey=b8f9cltuus36hr0haqt0988o8&st=0cqjc9qx&dl=1' + + def __init__(self, root: str = None, transform: Optional[Callable] = None, + pre_transform: Optional[Callable] = None, force_reload: bool = False): + super().__init__(root, transform, pre_transform, force_reload=force_reload) + self.data, self.slices = self.load_data(self.processed_paths[0]) + + @property + def raw_file_names(self) -> List[str]: + return [ + 'adjM.npz', 'features_0.npz', 'features_1.npz', 'features_2.npz', + 'labels.npy', 'train_val_test_idx.npz' + ] + + @property + def processed_file_names(self) -> str: + return tlx.BACKEND + 'data.pt' + + def download(self): + path = download_url(self.url, self.raw_dir) + extract_zip(path, self.raw_dir) + os.remove(path) + + def process(self): + data = HeteroGraph() + + node_types = ['twitter', 'topics', 'entity'] + for i, node_type in enumerate(node_types): + x = sp.load_npz(osp.join(self.raw_dir, f'features_{i}.npz')) + data[node_type].x = tlx.convert_to_tensor(x.todense(), dtype=tlx.float32) + + y = np.load(osp.join(self.raw_dir, 'labels.npy')) + y = np.argmax(y,axis=1) + data['twitter'].y = tlx.convert_to_tensor(y, dtype=tlx.int64) + + split = np.load(osp.join(self.raw_dir, 'train_val_test_idx.npz')) + for name in ['train', 'val', 'test']: + idx = split[f'{name}_idx'] + mask = np.zeros(data['twitter'].num_nodes, dtype=np.bool_) + mask[idx] = True + data['twitter'][f'{name}_mask'] = tlx.convert_to_tensor(mask, dtype=tlx.bool) + + + s = {} + N_m = data['twitter'].num_nodes + N_d = data['topics'].num_nodes + N_a = data['entity'].num_nodes + s['twitter'] = (0, N_m) + s['topics'] = (N_m, N_m + N_d) + s['entity'] = (N_m + N_d, N_m + N_d + N_a) + + A = sp.load_npz(osp.join(self.raw_dir, 'adjM.npz')).tocsr() + for src, dst in product(node_types, node_types): + A_sub = A[s[src][0]:s[src][1], s[dst][0]:s[dst][1]].tocoo() + if A_sub.nnz > 0: + row = tlx.convert_to_tensor(A_sub.row, dtype=tlx.int64) + col = tlx.convert_to_tensor(A_sub.col, dtype=tlx.int64) + data[src, dst].edge_index = tlx.stack([row, col], axis=0) + print(src+"____"+dst) + + if self.pre_transform is not None: + data = self.pre_transform(data) + + self.save_data(self.collate([data]), self.processed_paths[0]) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}()' \ No newline at end of file diff --git a/gammagl/layers/conv/__init__.py b/gammagl/layers/conv/__init__.py index 0162a6cb..8cbefea9 100644 --- a/gammagl/layers/conv/__init__.py +++ b/gammagl/layers/conv/__init__.py @@ -33,6 +33,8 @@ from .magcl_conv import MAGCLConv from .fusedgat_conv import FusedGATConv from .hid_conv import Hid_conv +from .hgat_conv import HGATConv +from .hin_conv import HINConv __all__ = [ 'MessagePassing', 'GCNConv', @@ -68,7 +70,9 @@ 'MAGCLConv', 'FusedGATConv', 'Hid_conv', - 'HEATlayer' + 'HEATlayer', + 'HIN_conv', + 'HGAT_conv' ] classes = __all__ diff --git a/gammagl/layers/conv/hgat_conv.py b/gammagl/layers/conv/hgat_conv.py new file mode 100644 index 00000000..fd0ffb39 --- /dev/null +++ b/gammagl/layers/conv/hgat_conv.py @@ -0,0 +1,120 @@ +import tensorlayerx as tlx +from tensorlayerx.nn import ModuleDict +from gammagl.layers.conv import MessagePassing +from gammagl.utils import segment_softmax, to_homograph, to_heterograph +from gammagl.mpops import unsorted_segment_sum +class HGATConv(MessagePassing): + def __init__(self, + in_channels, + out_channels, + metadata, + negative_slope=0.2, + drop_rate=0.5): + super().__init__() + + self.in_channels = in_channels + self.out_channels = out_channels + self.metadata = metadata + self.negetive_slop = negative_slope + self.dropout = tlx.layers.Dropout(drop_rate) + + + self.Linear_dict_l = ModuleDict({}) + self.Linear_dict_r = ModuleDict({}) + + l_list = [] + r_list = [] + + for edge_type in metadata[1]: + src_type,_, dst_type = edge_type + if src_type not in r_list: + r_list.append(src_type) + if dst_type not in l_list: + l_list.append(dst_type) + + for dst_type in l_list: + print(dst_type) + self.Linear_dict_l[dst_type] = tlx.layers.Linear(out_features=1, + in_features=in_channels, + W_init='xavier_uniform') + for src_type in r_list: + print(src_type) + self.Linear_dict_r[src_type]= tlx.layers.Linear(out_features=1, + in_features=in_channels, + W_init='xavier_uniform') + + + self.nodeAttention = tlx.layers.Linear(out_features=1, + in_features=in_channels*2, + W_init='xavier_uniform') + + + self.leakyReLu = tlx.LeakyReLU(negative_slope=negative_slope) + + + def forward(self, x_dict, edge_index_dict, num_nodes_dict): + edge_pattern_dict={} + for node_type, value in x_dict.items(): + edge_pattern_dict[node_type]={} + for edge_type, edge_index in edge_index_dict.items(): + src_type, _, dst_type = edge_type + if(dst_type==node_type): + edge_pattern_dict[node_type][edge_type]=edge_index + + + + # 每一个pattern 中有若干edge_type,它们的scr_type 是一样的 + alpha_pattern_dict={} + beta_pattern_dict = {} + for node_type, pattern_dict in edge_pattern_dict.items(): + alpha_pattern_dict[node_type]={} + beta_pattern_dict[node_type]={} + Attention_value_dict = {} + for edge_type, edge_index in pattern_dict.items(): + src_type, _, dst_type = edge_type + src = edge_index[0,:] + dst = edge_index[1,:] + + message = unsorted_segment_sum(tlx.gather(x_dict[src_type],src),dst,x_dict[dst_type].shape[0]) + + h_l = self.Linear_dict_l[dst_type](x_dict[dst_type]) + h_r = self.Linear_dict_r[dst_type](message) + Type_Attention_Value = h_l + h_r # N 个值, N等于边的数量 + Type_Attention_Value = self.leakyReLu(Type_Attention_Value) + + Type_Attention_Value = tlx.exp(Type_Attention_Value) + Attention_value_dict[edge_type]=Type_Attention_Value + Attention_value_list = [value for value in Attention_value_dict.values()] + + Summation = tlx.reduce_sum(tlx.stack(Attention_value_list,axis=0),axis=0) + + + for edge_type, edge_index in pattern_dict.items(): + alpha_pattern_dict[node_type][edge_type] = Attention_value_dict[edge_type]/Summation # N个值, N等于边的数量 + + out_dict={} + for node_type, pattern_dict in edge_pattern_dict.items(): + message_list= [] + for edge_type, edge_index in pattern_dict.items(): + alpha = alpha_pattern_dict[node_type][edge_type] + src_type, _, dst_type = edge_type + src = edge_index[0,:] + dst = edge_index[1,:] + # 运用广播机制 alpha(N,1), 后面是(N,hidden_dim*2),N表示边的数量。 + # print("asdfasdfasdfasdfaefwa") + # print(alpha) + # print(tlx.gather(x_dict[dst_type],dst)) + # print(tlx.gather(x_dict[src_type],src)) + value = self.nodeAttention(tlx.gather(alpha,dst)*tlx.concat([tlx.gather(x_dict[dst_type],dst),tlx.gather(x_dict[src_type],src)],axis=1)) + value = self.leakyReLu(value) + value = segment_softmax(value,dst,num_segments=None) + beta_pattern_dict[node_type][edge_type] = value + # print(dst_type) + # print(dst) + # print(tlx.gather(x_dict[dst_type],dst)) + # print(x_dict[dst_type].shape[0]) + message_list.append(unsorted_segment_sum(value*tlx.gather(x_dict[dst_type],dst),dst,x_dict[dst_type].shape[0])) + # print(message_list[-1]) + out_dict[node_type]=tlx.reduce_sum(tlx.stack(message_list,axis=0),axis=0) + + return out_dict \ No newline at end of file diff --git a/gammagl/layers/conv/hin_conv.py b/gammagl/layers/conv/hin_conv.py new file mode 100644 index 00000000..d0e8d7a2 --- /dev/null +++ b/gammagl/layers/conv/hin_conv.py @@ -0,0 +1,35 @@ +from tensorlayerx.nn import ModuleDict +from gammagl.layers.conv import MessagePassing +from gammagl.utils import segment_softmax, to_homograph, to_heterograph, degree +import tensorlayerx as tlx + +class HINConv(MessagePassing): + def __init__(self, + in_channels, + out_channels, + metadata, + negative_slope=0.2, + drop_rate=0.5): + super().__init__() + if not isinstance(in_channels, dict): + in_channels = {node_type: in_channels for node_type in metadata[0]} + self.in_channels = in_channels + self.out_channels = out_channels + self.metadata = metadata + self.negetive_slop = negative_slope + self.drop_rate = drop_rate + self.dropout = tlx.layers.Dropout(drop_rate) + + self.heterlinear = ModuleDict({}) + for node_type in self.metadata[0]: + self.heterlinear[node_type] = tlx.layers.Linear(out_features=out_channels, + in_features=in_channels[node_type], + W_init='xavier_uniform') + + + def forward(self, x_dict, edge_index_dict, num_nodes_dict): + out_dict = {} + for node_type, x_node in x_dict.items(): + out_dict[node_type]= self.dropout(self.heterlinear[node_type](x_node)) + return out_dict + diff --git a/gammagl/models/__init__.py b/gammagl/models/__init__.py index c08b85c0..49037dd1 100644 --- a/gammagl/models/__init__.py +++ b/gammagl/models/__init__.py @@ -57,6 +57,7 @@ from .fusedgat import FusedGATModel from .hid_net import Hid_net from .gnnlfhf import GNNLFHFModel +from .hgat import HGATModel __all__ = [ 'HeCo', @@ -117,7 +118,8 @@ 'FusedGATModel', 'hid_net', 'HEAT', - 'GNNLFHFModel' + 'GNNLFHFModel', + 'HGATModel' ] classes = __all__ diff --git a/gammagl/models/hgat.py b/gammagl/models/hgat.py new file mode 100644 index 00000000..dad9e576 --- /dev/null +++ b/gammagl/models/hgat.py @@ -0,0 +1,62 @@ +import tensorlayerx as tlx +from tensorlayerx.nn import ModuleDict +from gammagl.layers.conv import MessagePassing, GCNConv, HINConv, HGATConv,GATConv +from gammagl.utils import segment_softmax, to_homograph, to_heterograph +from gammagl.mpops import unsorted_segment_sum + +class HGATModel(tlx.nn.Module): + def __init__(self, + in_channels, + out_channels, + metadata, + drop_rate, + hidden_channels=128, + name=None): + + + super().__init__(name=name) + self.hin_conv1 = HINConv(in_channels, + hidden_channels, + metadata, + drop_rate=drop_rate) + self.hgat_conv1 = HGATConv(in_channels=hidden_channels, + out_channels=hidden_channels, + metadata=metadata, + drop_rate=drop_rate) + + self.hin_conv2 = GCNConv(hidden_channels, + hidden_channels) + self.hgat_conv2 = GATConv(in_channels=hidden_channels, + out_channels=hidden_channels, + dropout_rate=drop_rate) + self.linear = tlx.nn.Linear(out_features=out_channels, + in_features=hidden_channels) + self.softmax = tlx.nn.activation.Softmax() + + def forward(self, x_dict, edge_index_dict, num_nodes_dict): + out = self.hin_conv1(x_dict, edge_index_dict, num_nodes_dict) + + out = self.hgat_conv1(out, edge_index_dict, num_nodes_dict) + out, edge_index, edge_value = to_homograph(out,edge_index_dict,num_nodes_dict,None) + out = self.hin_conv2(out, edge_index) + + out = self.hgat_conv2(out, edge_index) + + out_dict = to_heterograph(out,edge_index,num_nodes_dict) + for node_type, _ in x_dict.items(): + out_dict[node_type] = self.softmax(self.linear(out_dict[node_type])) + + + return out_dict + # out_dict={} + # for node_type, _ in x_dict.items(): + # out_dict[node_type]=[] + # for edge_type, edge_index in edge_index_dict.items(): + # src_type, _, dst_type = edge_type + # src = edge_index[0,:] + # dst = edge_index[1,:] + # message = unsorted_segment_sum(tlx.gather(x_dict[src_type],src),dst,num_nodes_dict[dst_type]) + # out_dict[dst_type].append(message) + # for node_type, outs in out_dict.items(): + # aggr_out = tlx.reduce_sum(outs,axis=0) + # out_dict[node_type]=tlx.relu(aggr_out) \ No newline at end of file diff --git a/gammagl/utils/__init__.py b/gammagl/utils/__init__.py index f57d8d09..251f4e5d 100644 --- a/gammagl/utils/__init__.py +++ b/gammagl/utils/__init__.py @@ -20,6 +20,8 @@ from .shortest_path import shortest_path_distance, batched_shortest_path_distance from .get_split import get_train_val_test_split from .get_laplacian import get_laplacian +from .homo_heter_mutual_convert import to_homograph, to_heterograph, add_num + __all__ = [ 'calc_A_norm_hat', @@ -46,7 +48,10 @@ 'shortest_path_distance', 'batched_shortest_path_distance', 'get_train_val_test_split', - 'get_laplacian' + 'get_laplacian', + 'to_homograph', + 'to_heterograph', + 'add_num' ] diff --git a/gammagl/utils/homo_heter_mutual_convert.py b/gammagl/utils/homo_heter_mutual_convert.py new file mode 100644 index 00000000..d038e0ed --- /dev/null +++ b/gammagl/utils/homo_heter_mutual_convert.py @@ -0,0 +1,203 @@ +import tensorlayerx as tlx + +def to_homograph(x_dict ,edge_index_dict, num_nodes_dict, edge_value_dict): + + node_type_num = len(num_nodes_dict) + + x_list = list(x_dict) + + node_type_list = list(num_nodes_dict.keys()) + + node_num_list = list(num_nodes_dict.values()) + + add_num_list = add_num(node_num_list) + + # print(add_num_list) + + x_feature = x_dict[node_type_list[0]] + + if(tlx.backend=="tensorflow"): + + x_feature = tlx.stack(x_feature) + + + + + for i in node_type_list[1:]: + + + + if(tlx.backend=="tensorflow"): + x_feature = tlx.concat((x_feature,tlx.stack(x_dict[i])), axis=0) + else: + x_feature = tlx.concat((x_feature,x_dict[i]), axis=0) + + + + + + + edge_type_list = list(edge_index_dict.keys()) + + + + + edge_index = edge_index_dict[edge_type_list[0]] + + + + + _,num = edge_index.shape + + + + + node_src = tlx.slice(edge_index,[0,0],[1,num]) + + node_dst = tlx.slice(edge_index,[1,0],[1,num]) + + + + + node_src = tlx.add(node_src,add_num_list[node_type_list.index(edge_type_list[0][0])]) + + node_dst = tlx.add(node_dst,add_num_list[node_type_list.index(edge_type_list[0][2])]) + + + + if(tlx.backend=="tensorflow"): + node_src = tlx.stack(node_src) + node_dst = tlx.stack(node_dst) + edge_index = tlx.concat((node_src,node_dst),axis=0) + + + if(edge_value_dict!=None): + edge_value = edge_value_dict[edge_type_list[0]] + if(tlx.backend=="tensorflow"): + edge_value[0]=tlx.stack(edge_value[0]) + edge_value = edge_value[0] + + + + + + + + + + for i in edge_type_list[1:]: + + edge_index_tem = edge_index_dict[i] + + _,num = edge_index_tem.shape + + + + + node_src = tlx.slice(edge_index_tem,[0,0],[1,num]) + + node_dst = tlx.slice(edge_index_tem,[1,0],[1,num]) + + + + + node_src = tlx.add(node_src,add_num_list[node_type_list.index(i[0])]) + + node_dst = tlx.add(node_dst,add_num_list[node_type_list.index(i[2])]) + + if(tlx.backend=="tensorflow"): + node_src = tlx.stack(node_src) + node_dst = tlx.stack(node_dst) + + + edge_index_tem = tlx.concat((node_src,node_dst),axis=0) + + if(tlx.backend=="tensorflow"): + edge_index_tem = tlx.stack(edge_index_tem) + + edge_index = tlx.concat((edge_index,edge_index_tem),axis=1) + + # print('-----') + + # print(edge_value) + + # print(edge_value_dict[i]) + if(edge_value_dict!= None): + if(tlx.backend=="tensorflow"): + edge_value = tlx.concat((edge_value,tlx.stack(edge_value_dict[i][0])),axis=0) + else: + + edge_value = tlx.concat((edge_value,edge_value_dict[i][0]),axis=0) + + else: + edge_value = None + + + + + + + + + + + return [x_feature, edge_index, edge_value] + + + + +def to_heterograph(x_value, edge_index, num_node_dict): + + + + + x_dict = {} + + node_type_list = list(num_node_dict.keys()) + + node_num_list = list(num_node_dict.values()) + + node_index_list = add_num(node_num_list) + + type_num = len(node_type_list) + + + + + for node_type in node_type_list: + + index = node_type_list.index(node_type) + + if(index == type_num-1): + + x_dict[node_type] = x_value[node_index_list[index]:,:] + + else: + + x_dict[node_type] = x_value[node_index_list[index]:node_index_list[index+1],:] + + return x_dict + + + + + # print(x_feature) + + # for node_type, x_node in x_dict.items(): + + + + +def add_num(int_list): + + out = [] + + num = len(int_list) + + out.append(0) + + for i in range(num-1): + + out.append(out[-1]+int_list[i]) + + return out \ No newline at end of file From f806326c512c9cff596420f6509401fc38455215 Mon Sep 17 00:00:00 2001 From: martinjingyu <1098549362@qq.com> Date: Thu, 4 Jul 2024 11:11:23 +0800 Subject: [PATCH 2/3] Improve the code --- examples/hgat/hgat_trainer.py | 120 ++++---------------- gammagl/layers/conv/__init__.py | 2 - gammagl/layers/conv/hgat_conv.py | 19 +--- gammagl/layers/conv/hin_conv.py | 35 ------ gammagl/models/hgat.py | 45 +++++--- gammagl/utils/homo_heter_mutual_convert.py | 125 --------------------- 6 files changed, 56 insertions(+), 290 deletions(-) delete mode 100644 gammagl/layers/conv/hin_conv.py diff --git a/examples/hgat/hgat_trainer.py b/examples/hgat/hgat_trainer.py index 30345221..bfe5cf55 100644 --- a/examples/hgat/hgat_trainer.py +++ b/examples/hgat/hgat_trainer.py @@ -60,23 +60,7 @@ def main(args): print(graph) y = graph['movie'].y node_type = 'movie' - print(len(np.unique(graph['movie'].y.cpu()))) - # for mindspore, it should be passed into node indices - train_idx = mask_to_index(graph['movie'].train_mask) - test_idx = mask_to_index(graph['movie'].test_mask) - val_idx = mask_to_index(graph['movie'].val_mask) - - in_channel = {'movie':3066, 'director':3066, 'actor': 3066} - num_nodes_dict = {'movie':4278,'actor':5257,'director':2081} - print(len(np.unique(graph['movie'].y.cpu()))) - net = HGATModel( - in_channels=in_channel, - out_channels=len(np.unique(graph['movie'].y.cpu())), # graph.num_classes, - metadata=graph.metadata(), - drop_rate=0.5, - hidden_channels=512, - name = 'hgat', - ) + if(args.dataset=="agnews"): @@ -85,23 +69,7 @@ def main(args): print(graph) y = graph['text'].y node_type = 'text' - print(len(np.unique(graph['text'].y.cpu()))) - # for mindspore, it should be passed into node indices - train_idx = mask_to_index(graph['text'].train_mask,) - test_idx = mask_to_index(graph['text'].test_mask) - val_idx = mask_to_index(graph['text'].val_mask) - - in_channel = {'text':5126, 'topic':4962, 'entity': 4378} - num_nodes_dict = {'text': 3200, 'topic': 15, 'entity': 5680} - - net = HGATModel( - in_channels=in_channel, - out_channels=len(np.unique(graph['text'].y.cpu())), # graph.num_classes, - metadata=graph.metadata(), - drop_rate=args.drop_rate, - hidden_channels=args.hidden_dim, - name='hgat', - ) + if(args.dataset=="ohsumed"): dataset = OHSUMED(args.dataset_path) @@ -110,24 +78,8 @@ def main(args): y = graph['documents'].y node_type = 'documents' - # for mindspore, it should be passed into node indices - train_idx = mask_to_index(graph['documents'].train_mask,) - test_idx = mask_to_index(graph['documents'].test_mask) - val_idx = mask_to_index(graph['documents'].val_mask) - - num_nodes_dict = {'documents': 7400, 'topics': 15, 'words': 5420} - in_channel = {'documents':2471, 'topics':2472, 'words': 3197} - - net = HGATModel( - in_channels=in_channel, - out_channels=len(np.unique(graph['documents'].y.cpu())), # graph.num_classes, - metadata=graph.metadata(), - drop_rate=args.drop_rate, - hidden_channels=256, - name='hgat', - ) if(args.dataset=="twitter"): dataset = Twitter(args.dataset_path) graph = dataset[0] @@ -135,24 +87,28 @@ def main(args): y = graph['twitter'].y node_type = 'twitter' - # for mindspore, it should be passed into node indices - train_idx = mask_to_index(graph['twitter'].train_mask,) - test_idx = mask_to_index(graph['twitter'].test_mask) - val_idx = mask_to_index(graph['twitter'].val_mask) - num_nodes_dict = {'twitter': 10000, 'topics': 15, 'entity': 4698} - in_channel = {'twitter':1515, 'topics':1543, 'entity': 2787} + # for mindspore, it should be passed into node indices + train_idx = mask_to_index(graph[node_type].train_mask) + test_idx = mask_to_index(graph[node_type].test_mask) + val_idx = mask_to_index(graph[node_type].val_mask) + node_type_list = graph.metadata()[0] + in_channel = {} + num_nodes_dict = {} + for node_type in node_type_list: + in_channel[node_type]=graph.x_dict[node_type].shape[1] + num_nodes_dict[node_type]=graph.x_dict[node_type].shape[0] - net = HGATModel( - in_channels=in_channel, - out_channels=len(np.unique(graph['twitter'].y.cpu())), # graph.num_classes, - metadata=graph.metadata(), - drop_rate=args.drop_rate, - hidden_channels=64, - name='hgat', - ) + net = HGATModel( + in_channels=in_channel, + out_channels=len(np.unique(graph.y.cpu())), # graph.num_classes, + metadata=graph.metadata(), + drop_rate=0.5, + hidden_channels=args.hidden_dim, + name = 'hgat', + ) optimizer = tlx.optimizers.Adam(lr=args.lr, weight_decay=args.l2_coef) @@ -175,42 +131,6 @@ def main(args): print(np.unique(y.cpu())) best_val_acc = 0 - - - # dataset1 = AGNews(args.dataset_path) - # graph1 = dataset1[0] - # print(graph1) - # y1 = graph1['text'].y - # print(len(np.unique(graph1['text'].y.cpu()))) - # # for mindspore, it should be passed into node indices - # train_idx = mask_to_index(graph1['text'].train_mask,) - # test_idx = mask_to_index(graph1['text'].test_mask) - # val_idx = mask_to_index(graph1['text'].val_mask) - - # in_channel1 = {'text':5126, 'topic':4962, 'entity': 4378} - # num_nodes_dict1 = {'text': 3200, 'topic': 15, 'entity': 5680} - - - # dataset2 = OHSUMED(args.dataset_path) - # graph2 = dataset2[0] - # print(graph2) - # y = graph2['documents'].y - # node_type2 = 'documents' - - # # for mindspore, it should be passed into node indices - # train_idx2 = mask_to_index(graph2['documents'].train_mask,) - # test_idx2 = mask_to_index(graph2['documents'].test_mask) - # val_idx2 = mask_to_index(graph2['documents'].val_mask) - - - # num_nodes_dict = {'documents': 10000, 'topics': 15, 'words': 4698} - # in_channel = {'documents':1515, 'topics':1543, 'words': 2787} - - - - - - for epoch in range(args.n_epoch): net.set_train() train_loss = train_one_step(data, y, node_type) diff --git a/gammagl/layers/conv/__init__.py b/gammagl/layers/conv/__init__.py index 8cbefea9..4efb5167 100644 --- a/gammagl/layers/conv/__init__.py +++ b/gammagl/layers/conv/__init__.py @@ -34,7 +34,6 @@ from .fusedgat_conv import FusedGATConv from .hid_conv import Hid_conv from .hgat_conv import HGATConv -from .hin_conv import HINConv __all__ = [ 'MessagePassing', 'GCNConv', @@ -71,7 +70,6 @@ 'FusedGATConv', 'Hid_conv', 'HEATlayer', - 'HIN_conv', 'HGAT_conv' ] diff --git a/gammagl/layers/conv/hgat_conv.py b/gammagl/layers/conv/hgat_conv.py index fd0ffb39..3210443c 100644 --- a/gammagl/layers/conv/hgat_conv.py +++ b/gammagl/layers/conv/hgat_conv.py @@ -1,7 +1,7 @@ import tensorlayerx as tlx from tensorlayerx.nn import ModuleDict from gammagl.layers.conv import MessagePassing -from gammagl.utils import segment_softmax, to_homograph, to_heterograph +from gammagl.utils import segment_softmax from gammagl.mpops import unsorted_segment_sum class HGATConv(MessagePassing): def __init__(self, @@ -63,7 +63,7 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): - # 每一个pattern 中有若干edge_type,它们的scr_type 是一样的 + # There are several edge_type in each pattern, and their scr_type are the same alpha_pattern_dict={} beta_pattern_dict = {} for node_type, pattern_dict in edge_pattern_dict.items(): @@ -79,7 +79,7 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): h_l = self.Linear_dict_l[dst_type](x_dict[dst_type]) h_r = self.Linear_dict_r[dst_type](message) - Type_Attention_Value = h_l + h_r # N 个值, N等于边的数量 + Type_Attention_Value = h_l + h_r # N values, N equals the number of edges Type_Attention_Value = self.leakyReLu(Type_Attention_Value) Type_Attention_Value = tlx.exp(Type_Attention_Value) @@ -90,7 +90,7 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): for edge_type, edge_index in pattern_dict.items(): - alpha_pattern_dict[node_type][edge_type] = Attention_value_dict[edge_type]/Summation # N个值, N等于边的数量 + alpha_pattern_dict[node_type][edge_type] = Attention_value_dict[edge_type]/Summation # N values, N equals the number of edges out_dict={} for node_type, pattern_dict in edge_pattern_dict.items(): @@ -100,21 +100,12 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): src_type, _, dst_type = edge_type src = edge_index[0,:] dst = edge_index[1,:] - # 运用广播机制 alpha(N,1), 后面是(N,hidden_dim*2),N表示边的数量。 - # print("asdfasdfasdfasdfaefwa") - # print(alpha) - # print(tlx.gather(x_dict[dst_type],dst)) - # print(tlx.gather(x_dict[src_type],src)) + # Use the broadcast mechanism alpha(N,1), followed by (N,hidden_dim*2), where N denotes the number of edges. value = self.nodeAttention(tlx.gather(alpha,dst)*tlx.concat([tlx.gather(x_dict[dst_type],dst),tlx.gather(x_dict[src_type],src)],axis=1)) value = self.leakyReLu(value) value = segment_softmax(value,dst,num_segments=None) beta_pattern_dict[node_type][edge_type] = value - # print(dst_type) - # print(dst) - # print(tlx.gather(x_dict[dst_type],dst)) - # print(x_dict[dst_type].shape[0]) message_list.append(unsorted_segment_sum(value*tlx.gather(x_dict[dst_type],dst),dst,x_dict[dst_type].shape[0])) - # print(message_list[-1]) out_dict[node_type]=tlx.reduce_sum(tlx.stack(message_list,axis=0),axis=0) return out_dict \ No newline at end of file diff --git a/gammagl/layers/conv/hin_conv.py b/gammagl/layers/conv/hin_conv.py deleted file mode 100644 index d0e8d7a2..00000000 --- a/gammagl/layers/conv/hin_conv.py +++ /dev/null @@ -1,35 +0,0 @@ -from tensorlayerx.nn import ModuleDict -from gammagl.layers.conv import MessagePassing -from gammagl.utils import segment_softmax, to_homograph, to_heterograph, degree -import tensorlayerx as tlx - -class HINConv(MessagePassing): - def __init__(self, - in_channels, - out_channels, - metadata, - negative_slope=0.2, - drop_rate=0.5): - super().__init__() - if not isinstance(in_channels, dict): - in_channels = {node_type: in_channels for node_type in metadata[0]} - self.in_channels = in_channels - self.out_channels = out_channels - self.metadata = metadata - self.negetive_slop = negative_slope - self.drop_rate = drop_rate - self.dropout = tlx.layers.Dropout(drop_rate) - - self.heterlinear = ModuleDict({}) - for node_type in self.metadata[0]: - self.heterlinear[node_type] = tlx.layers.Linear(out_features=out_channels, - in_features=in_channels[node_type], - W_init='xavier_uniform') - - - def forward(self, x_dict, edge_index_dict, num_nodes_dict): - out_dict = {} - for node_type, x_node in x_dict.items(): - out_dict[node_type]= self.dropout(self.heterlinear[node_type](x_node)) - return out_dict - diff --git a/gammagl/models/hgat.py b/gammagl/models/hgat.py index dad9e576..481cc1c0 100644 --- a/gammagl/models/hgat.py +++ b/gammagl/models/hgat.py @@ -1,6 +1,6 @@ import tensorlayerx as tlx from tensorlayerx.nn import ModuleDict -from gammagl.layers.conv import MessagePassing, GCNConv, HINConv, HGATConv,GATConv +from gammagl.layers.conv import MessagePassing, GCNConv, HGATConv,GATConv from gammagl.utils import segment_softmax, to_homograph, to_heterograph from gammagl.mpops import unsorted_segment_sum @@ -46,17 +46,34 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): for node_type, _ in x_dict.items(): out_dict[node_type] = self.softmax(self.linear(out_dict[node_type])) - return out_dict - # out_dict={} - # for node_type, _ in x_dict.items(): - # out_dict[node_type]=[] - # for edge_type, edge_index in edge_index_dict.items(): - # src_type, _, dst_type = edge_type - # src = edge_index[0,:] - # dst = edge_index[1,:] - # message = unsorted_segment_sum(tlx.gather(x_dict[src_type],src),dst,num_nodes_dict[dst_type]) - # out_dict[dst_type].append(message) - # for node_type, outs in out_dict.items(): - # aggr_out = tlx.reduce_sum(outs,axis=0) - # out_dict[node_type]=tlx.relu(aggr_out) \ No newline at end of file + +class HINConv(MessagePassing): + def __init__(self, + in_channels, + out_channels, + metadata, + negative_slope=0.2, + drop_rate=0.5): + super().__init__() + if not isinstance(in_channels, dict): + in_channels = {node_type: in_channels for node_type in metadata[0]} + self.in_channels = in_channels + self.out_channels = out_channels + self.metadata = metadata + self.negetive_slop = negative_slope + self.drop_rate = drop_rate + self.dropout = tlx.layers.Dropout(drop_rate) + + self.heterlinear = ModuleDict({}) + for node_type in self.metadata[0]: + self.heterlinear[node_type] = tlx.layers.Linear(out_features=out_channels, + in_features=in_channels[node_type], + W_init='xavier_uniform') + + + def forward(self, x_dict, edge_index_dict, num_nodes_dict): + out_dict = {} + for node_type, x_node in x_dict.items(): + out_dict[node_type]= self.dropout(self.heterlinear[node_type](x_node)) + return out_dict \ No newline at end of file diff --git a/gammagl/utils/homo_heter_mutual_convert.py b/gammagl/utils/homo_heter_mutual_convert.py index d038e0ed..348d24ad 100644 --- a/gammagl/utils/homo_heter_mutual_convert.py +++ b/gammagl/utils/homo_heter_mutual_convert.py @@ -3,201 +3,76 @@ def to_homograph(x_dict ,edge_index_dict, num_nodes_dict, edge_value_dict): node_type_num = len(num_nodes_dict) - x_list = list(x_dict) - node_type_list = list(num_nodes_dict.keys()) - node_num_list = list(num_nodes_dict.values()) - add_num_list = add_num(node_num_list) - - # print(add_num_list) - x_feature = x_dict[node_type_list[0]] - if(tlx.backend=="tensorflow"): - x_feature = tlx.stack(x_feature) - - - - for i in node_type_list[1:]: - - - if(tlx.backend=="tensorflow"): x_feature = tlx.concat((x_feature,tlx.stack(x_dict[i])), axis=0) else: x_feature = tlx.concat((x_feature,x_dict[i]), axis=0) - - - - - - edge_type_list = list(edge_index_dict.keys()) - - - - edge_index = edge_index_dict[edge_type_list[0]] - - - - _,num = edge_index.shape - - - - node_src = tlx.slice(edge_index,[0,0],[1,num]) - node_dst = tlx.slice(edge_index,[1,0],[1,num]) - - - - node_src = tlx.add(node_src,add_num_list[node_type_list.index(edge_type_list[0][0])]) - node_dst = tlx.add(node_dst,add_num_list[node_type_list.index(edge_type_list[0][2])]) - - - if(tlx.backend=="tensorflow"): node_src = tlx.stack(node_src) node_dst = tlx.stack(node_dst) edge_index = tlx.concat((node_src,node_dst),axis=0) - - if(edge_value_dict!=None): edge_value = edge_value_dict[edge_type_list[0]] if(tlx.backend=="tensorflow"): edge_value[0]=tlx.stack(edge_value[0]) edge_value = edge_value[0] - - - - - - - - - for i in edge_type_list[1:]: - edge_index_tem = edge_index_dict[i] - _,num = edge_index_tem.shape - - - - node_src = tlx.slice(edge_index_tem,[0,0],[1,num]) - node_dst = tlx.slice(edge_index_tem,[1,0],[1,num]) - - - - node_src = tlx.add(node_src,add_num_list[node_type_list.index(i[0])]) - node_dst = tlx.add(node_dst,add_num_list[node_type_list.index(i[2])]) - if(tlx.backend=="tensorflow"): node_src = tlx.stack(node_src) node_dst = tlx.stack(node_dst) - - edge_index_tem = tlx.concat((node_src,node_dst),axis=0) - if(tlx.backend=="tensorflow"): edge_index_tem = tlx.stack(edge_index_tem) - edge_index = tlx.concat((edge_index,edge_index_tem),axis=1) - - # print('-----') - - # print(edge_value) - - # print(edge_value_dict[i]) if(edge_value_dict!= None): if(tlx.backend=="tensorflow"): edge_value = tlx.concat((edge_value,tlx.stack(edge_value_dict[i][0])),axis=0) else: - edge_value = tlx.concat((edge_value,edge_value_dict[i][0]),axis=0) - else: edge_value = None - - - - - - - - - - return [x_feature, edge_index, edge_value] - - - def to_heterograph(x_value, edge_index, num_node_dict): - - - x_dict = {} - node_type_list = list(num_node_dict.keys()) - node_num_list = list(num_node_dict.values()) - node_index_list = add_num(node_num_list) - type_num = len(node_type_list) - - - - for node_type in node_type_list: - index = node_type_list.index(node_type) - if(index == type_num-1): - x_dict[node_type] = x_value[node_index_list[index]:,:] - else: - x_dict[node_type] = x_value[node_index_list[index]:node_index_list[index+1],:] - return x_dict - - - - # print(x_feature) - - # for node_type, x_node in x_dict.items(): - - - - def add_num(int_list): - out = [] - num = len(int_list) - out.append(0) - for i in range(num-1): - out.append(out[-1]+int_list[i]) - return out \ No newline at end of file From 6f4a281ca3ea56304785ae34546d12c13a9882aa Mon Sep 17 00:00:00 2001 From: martinjingyu <1098549362@qq.com> Date: Thu, 4 Jul 2024 11:45:15 +0800 Subject: [PATCH 3/3] Improve the code --- examples/hgat/hgat_trainer.py | 2 +- gammagl/layers/conv/hgat_conv.py | 12 +++++++- gammagl/models/hgat.py | 53 ++++++++------------------------ 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/examples/hgat/hgat_trainer.py b/examples/hgat/hgat_trainer.py index bfe5cf55..7e4f351b 100644 --- a/examples/hgat/hgat_trainer.py +++ b/examples/hgat/hgat_trainer.py @@ -105,7 +105,7 @@ def main(args): in_channels=in_channel, out_channels=len(np.unique(graph.y.cpu())), # graph.num_classes, metadata=graph.metadata(), - drop_rate=0.5, + drop_rate=args.drop_rate, hidden_channels=args.hidden_dim, name = 'hgat', ) diff --git a/gammagl/layers/conv/hgat_conv.py b/gammagl/layers/conv/hgat_conv.py index 3210443c..e89bd946 100644 --- a/gammagl/layers/conv/hgat_conv.py +++ b/gammagl/layers/conv/hgat_conv.py @@ -18,6 +18,11 @@ def __init__(self, self.negetive_slop = negative_slope self.dropout = tlx.layers.Dropout(drop_rate) + for node_type in self.metadata[0]: + self.heterlinear[node_type] = tlx.layers.Linear(out_features=out_channels, + in_features=in_channels[node_type], + W_init='xavier_uniform') + self.Linear_dict_l = ModuleDict({}) self.Linear_dict_r = ModuleDict({}) @@ -53,6 +58,10 @@ def __init__(self, def forward(self, x_dict, edge_index_dict, num_nodes_dict): + + for node_type, x_node in x_dict.items(): + x_dict[node_type]= self.dropout(self.heterlinear[node_type](x_node)) + edge_pattern_dict={} for node_type, value in x_dict.items(): edge_pattern_dict[node_type]={} @@ -108,4 +117,5 @@ def forward(self, x_dict, edge_index_dict, num_nodes_dict): message_list.append(unsorted_segment_sum(value*tlx.gather(x_dict[dst_type],dst),dst,x_dict[dst_type].shape[0])) out_dict[node_type]=tlx.reduce_sum(tlx.stack(message_list,axis=0),axis=0) - return out_dict \ No newline at end of file + return out_dict + diff --git a/gammagl/models/hgat.py b/gammagl/models/hgat.py index 481cc1c0..77d8cfb9 100644 --- a/gammagl/models/hgat.py +++ b/gammagl/models/hgat.py @@ -15,18 +15,19 @@ def __init__(self, super().__init__(name=name) - self.hin_conv1 = HINConv(in_channels, - hidden_channels, - metadata, - drop_rate=drop_rate) - self.hgat_conv1 = HGATConv(in_channels=hidden_channels, + + self.hgat_conv = HGATConv(in_channels=hidden_channels, out_channels=hidden_channels, metadata=metadata, drop_rate=drop_rate) - self.hin_conv2 = GCNConv(hidden_channels, + # This two layers is equal to another HGAT Layer, as I followed the code of the paper. + # It seems like after the first HGAT layer, the node can be considered as a same type + # If we use another HGATConv again, there will be error, indicating: Grad is none + # This may because the design of the HGAT layer, if it is placed in the last layer, some of the parameter won't influence the output + self.gcn_conv = GCNConv(hidden_channels, hidden_channels) - self.hgat_conv2 = GATConv(in_channels=hidden_channels, + self.gat_conv = GATConv(in_channels=hidden_channels, out_channels=hidden_channels, dropout_rate=drop_rate) self.linear = tlx.nn.Linear(out_features=out_channels, @@ -34,46 +35,16 @@ def __init__(self, self.softmax = tlx.nn.activation.Softmax() def forward(self, x_dict, edge_index_dict, num_nodes_dict): - out = self.hin_conv1(x_dict, edge_index_dict, num_nodes_dict) + out = self.hgat_conv(x_dict, edge_index_dict, num_nodes_dict) - out = self.hgat_conv1(out, edge_index_dict, num_nodes_dict) out, edge_index, edge_value = to_homograph(out,edge_index_dict,num_nodes_dict,None) - out = self.hin_conv2(out, edge_index) + out = self.gcn_conv(out, edge_index) - out = self.hgat_conv2(out, edge_index) + out = self.gat_conv(out, edge_index) out_dict = to_heterograph(out,edge_index,num_nodes_dict) for node_type, _ in x_dict.items(): out_dict[node_type] = self.softmax(self.linear(out_dict[node_type])) return out_dict - -class HINConv(MessagePassing): - def __init__(self, - in_channels, - out_channels, - metadata, - negative_slope=0.2, - drop_rate=0.5): - super().__init__() - if not isinstance(in_channels, dict): - in_channels = {node_type: in_channels for node_type in metadata[0]} - self.in_channels = in_channels - self.out_channels = out_channels - self.metadata = metadata - self.negetive_slop = negative_slope - self.drop_rate = drop_rate - self.dropout = tlx.layers.Dropout(drop_rate) - - self.heterlinear = ModuleDict({}) - for node_type in self.metadata[0]: - self.heterlinear[node_type] = tlx.layers.Linear(out_features=out_channels, - in_features=in_channels[node_type], - W_init='xavier_uniform') - - - def forward(self, x_dict, edge_index_dict, num_nodes_dict): - out_dict = {} - for node_type, x_node in x_dict.items(): - out_dict[node_type]= self.dropout(self.heterlinear[node_type](x_node)) - return out_dict \ No newline at end of file + \ No newline at end of file