├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── configs
├── cogsl_config.yaml
├── dagnn_config.yaml
├── gcn_config.yaml
├── gen_config.yaml
├── gprgnn_config.yaml
├── grcn_config.yaml
├── gsr_config.yaml
├── gtn_config.yaml
├── han_config.yaml
├── hesgsl_config.yaml
├── hgpsl_config.yaml
├── hgsl_config.yaml
├── idgl_config.yaml
├── lds_config.yaml
├── mlp_config.yaml
├── neuralsparse_config.yaml
├── nodeformer_config.yaml
├── prognn_config.yaml
├── ptdnet_config.yaml
├── slaps_config.yaml
├── stable_config.yaml
├── sublime_config.yaml
└── vibgsl_config.yaml
├── data
├── __init__.py
├── dataset.py
├── graph_dataset.py
└── hetero_dataset.py
├── encoder
├── __init__.py
├── dagnn.py
├── gat.py
├── gcn.py
├── gin.py
├── gprgnn.py
├── graphsage.py
├── metamodule.py
├── mlp.py
└── nodeformer_conv.py
├── eval
├── __init__.py
└── eval_cls.py
├── experiment.py
├── generate_attack.py
├── hyperparams
└── idgl_hyper.yaml
├── learner
├── __init__.py
├── attention_learner.py
├── base_learner.py
├── full_param_learner.py
├── gnn_learner.py
├── hadamard_prod_learner.py
├── mlp_learner.py
└── sparsification_learner.py
├── logo.jpeg
├── main.py
├── metric.py
├── model
├── __init__.py
├── base_gsl.py
├── baselines.py
├── cogsl.py
├── gen.py
├── grcn.py
├── gsr.py
├── gtn.py
├── han.py
├── hesgsl.py
├── hgpsl.py
├── hgsl.py
├── idgl.py
├── lds.py
├── neuralsparse.py
├── nodeformer.py
├── prognn.py
├── ptdnet.py
├── slaps.py
├── stable.py
├── sublime.py
└── vibgsl.py
├── pipeline.png
├── processor.py
├── pyproject.toml
├── setup.cfg
├── setup.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | *__pycache__
3 | .vscode/
4 | .idea/
5 | test*
6 | results*
7 | dist/
8 | *.egg-info/
9 | temp*
10 | *.pkl
11 | *.npz
12 | *.npy
13 | *.allx
14 | *.graph
15 | *.index
16 | *.ally
17 | *.tx
18 | *.ty
19 | *.x
20 | *.y
21 | gslb_data/IMDB-BINARY_3673667b/tu_IMDB-BINARY.bin
22 | gslb_data/IMDB-BINARY_3673667b/IMDB-BINARY/IMDB-BINARY_graph_labels.txt
23 | gslb_data/IMDB-BINARY_3673667b/IMDB-BINARY/IMDB-BINARY_graph_indicator.txt
24 | gslb_data/IMDB-BINARY_3673667b/IMDB-BINARY/IMDB-BINARY_A.txt
25 | gslb_data/IMDB-BINARY.zip
26 | gslb_data/ENZYMES.zip
27 | gslb_data/ENZYMES_67bfdeff/ENZYMES/ENZYMES_A.txt
28 | gslb_data/ENZYMES_67bfdeff/ENZYMES/ENZYMES_graph_labels.txt
29 | gslb_data/ENZYMES_67bfdeff/ENZYMES/ENZYMES_graph_indicator.txt
30 | gslb_data/ENZYMES_67bfdeff/ENZYMES/ENZYMES_node_attributes.txt
31 | gslb_data/ENZYMES_67bfdeff/ENZYMES/ENZYMES_node_labels.txt
32 | gslb_data/ENZYMES_67bfdeff/ENZYMES/README.txt
33 | gslb_data/ENZYMES_67bfdeff/tu_ENZYMES.bin
34 | gslb_data/Mutagenicity.zip
35 | gslb_data/Mutagenicity_0de0f646/Mutagenicity/Mutagenicity_A.txt
36 | gslb_data/Mutagenicity_0de0f646/Mutagenicity/Mutagenicity_edge_labels.txt
37 | gslb_data/Mutagenicity_0de0f646/Mutagenicity/Mutagenicity_graph_indicator.txt
38 | gslb_data/Mutagenicity_0de0f646/Mutagenicity/Mutagenicity_graph_labels.txt
39 | *.txt
40 | *.bin
41 | *.zip
42 | *.txt~
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Graph Structure Learning Benchmark (GSLB)
6 |
7 | GSLB is a Graph Structure Learning (GSL) library and benchmark based on [DGL](https://github.com/dmlc/dgl) and [PyTorch](https://pytorch.org/). We integrate diverse datasets and state-of-the-art GSL models.
8 |
9 | ## 📔 What is Graph Structure Learning?
10 |
11 |
12 |
13 |
14 |
15 | Graph Structure Learning (GSL) aims to optimize both the parameters of Graph Neural Networks (GNNs) and the computation graph structure simultaneously. GSL methods start with input features and an optimal initial graph structure. Its corresponding computation graph is iteratively refined through a structure learning module. With the refined computation graph ,GNNs are used to generate graph representations. Then parameters of the GNNs and the structure modeling module are jointly updated, either simultaneously or alternatively, util a preset stopping condition is satisfied.
16 |
17 | If you want to explore more information about GSL, please refer to our [Paper](https://arxiv.org/abs/2310.05174), [survey](https://www.researchgate.net/profile/Yanqiao-Zhu/publication/349787551_Deep_Graph_Structure_Learning_for_Robust_Representations_A_Survey/links/6137188538818c2eaf885a3f/Deep-Graph-Structure-Learning-for-Robust-Representations-A-Survey.pdf), and [paper collection](https://github.com/GSL-Benchmark/Awesome-Graph-Structure-Learning).
18 |
19 | ## 🚀 Get Start
20 |
21 | ### Requirements
22 |
23 | GSLB needs the following requirements to be satisfied beforehand:
24 |
25 | * Python 3.8+
26 | * PyTorch 1.13
27 | * DGL 1.1+
28 | * Scipy 1.9+
29 | * Scikit-learn
30 | * Numpy
31 | * NetworkX
32 | * ogb
33 | * tqdm
34 | * easydict
35 | * PyYAML
36 | * DeepRobust
37 |
38 | ### Installation via PyPI
39 |
40 | To install GSLB with [`pip`](https://pip.pypa.io/en/stable/), simply run:
41 |
42 | ```
43 | pip install GSLB
44 | ```
45 |
46 | Then, you can import `GSL` from your current environment.
47 |
48 | ## Usage
49 |
50 | If you want to quickly run an existing GSL model on a graph dataset:
51 |
52 | ```python
53 | python main.py --dataset dataset_name --model model_name --num_trails --gpu_num 0 --use_knn --k 5 --use_mettack --sparse --metric acc --ptb_rate 0. --drop_rate 0. --add_rate 0.
54 | ```
55 |
56 | Optional arguments:
57 |
58 | ``--dataset`` : the name of graph dataset
59 |
60 | ``--model`` : the name of GSL model
61 |
62 | ``--ntrail`` : repetition count of experiments
63 |
64 | ``--use_knn`` : whether to use knn graph instead of the original graph
65 |
66 | ``--k`` : the number of the nearest neighbors
67 |
68 | ``--drop_rate`` : the probability of randomly edge deletion
69 |
70 | ``--add_rate`` : the probability of randomly edge addition
71 |
72 | ``--mask_feat_rate`` : the probability of randomly mask features
73 |
74 | ``--use_mettack`` : whether to use the structure after being attacked by mettack
75 |
76 | ``--ptb_rate`` : the perturbation rate
77 |
78 | ``--metric`` : the evaluation metric
79 |
80 | ``--gpu_num`` : the selected GPU number
81 |
82 | *Example: Train GRCN on Cora dataset, with the evaluation metric is accuracy.*
83 |
84 | ```
85 | python main.py --dataset cora --model GRCN --metric acc
86 | ```
87 |
88 | *If you want to quickly generate a perturbed graph by Mettack:*
89 |
90 | ```
91 | python generate_attack.py --dataset cora --ptb_rate 0.05
92 | ```
93 |
94 | Step 1: Load datasets
95 |
96 | ```python
97 | from GSL.data import *
98 |
99 | # load a homophilic or heterophilic graph dataset
100 | data = Dataset(root='/tmp/', name='cora')
101 |
102 | # load a perturbed graph dataset
103 | data = Dataset(root='/tmp/', name='cora', use_mettack=True, ptb_rate=0.05)
104 |
105 | # load a heterogeneous graph dataset
106 | data = HeteroDataset(root='/tmp/', name='acm')
107 |
108 | # load a graph-level dataset
109 | data = GraphDataset(root='/tmp/', name='IMDB-BINARY', model='GCN')
110 | ```
111 |
112 | Step 2: Initialize the GSL model
113 |
114 | ```python
115 | from GSL.model import *
116 | from GSL.utils import accuracy, macro_f1, micro_f1
117 |
118 | model_name = 'GRCN'
119 | metric = 'acc'
120 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
121 |
122 | # the hyper-parameters are recorded in config
123 | config_path = './configs/{}_config.yaml'.format(model_name.lower())
124 |
125 | # select a evaluation metric
126 | eval_metric = {
127 | 'acc': accuracy,
128 | 'macro-f1': macro_f1,
129 | 'micro-f1': micro_f1
130 | }[metric]
131 |
132 | model = GRCN(data.num_feat, data.num_class, evel_metric,
133 | config_path, dataset_name, device)
134 | ```
135 |
136 | Step 3: Train GSL model
137 |
138 | ```python
139 | model.fit(data)
140 | ```
141 |
142 | ## 🧩 Implementation Algorithms
143 |
144 | Currently, we have implemented the following GSL algorithms:
145 |
146 | | Algorithm | Conference/Journal | Category | Paper | Code |
147 | | --------- | ------------------ | -------- | ----- | ---- |
148 | | LDS | ICML 2019 | Homogeneous GSL | [Learning Discrete Structures for Graph Neural Networks](http://proceedings.mlr.press/v97/franceschi19a/franceschi19a.pdf) | [Link](https://github.com/lucfra/LDS-GNN) |
149 | | GRCN | ECML PKDD 2020 | Homogeneous GSL | [Graph-Revised Convolutional Network](https://arxiv.org/pdf/1911.07123.pdf) | [Link](https://github.com/PlusRoss/GRCN) |
150 | | ProGNN | KDD 202 | Homogeneous GSL | [Graph structure learning for robust graph neural networks](https://dl.acm.org/doi/pdf/10.1145/3394486.3403049) | [Link](https://github.com/ChandlerBang/Pro-GNN) |
151 | | IDGL | NeurIPS 2020 | Homogeneous GSL | [Iterative Deep Graph Learning for Graph Neural Networks: Better and Robust Node Embeddings](https://proceedings.neurips.cc/paper/2020/file/e05c7ba4e087beea9410929698dc41a6-Paper.pdf) | [Link](https://github.com/hugochan/IDGL) |
152 | | GEN | WWW 2021 | Homogeneous GSL | [Graph Structure Estimation Neural Networks](https://dl.acm.org/doi/pdf/10.1145/3442381.3449952?casa_token=Ac8pftvrgv0AAAAA:Ka_mklQVpQmYfhNVB-r66cf6fFsCdy8jyVKGFvzC1q5Ko5DbQQqci_3vopigN0jzTDlWiL8L8Q) | [Link](https://github.com/BUPT-GAMMA/Graph-Structure-Estimation-Neural-Networks) |
153 | | CoGSL | WWW 2022 | Homogeneous GSL | [Compact Graph Structure Learning via Mutual Information Compression](https://dl.acm.org/doi/pdf/10.1145/3485447.3512206?casa_token=lyWPk8kyFzwAAAAA:HLmbgpzrKe17LbnQqNh2zI_6WOvNgm_VqfNAgEoqLSXR7rRm_Bzro1oNzETTQb63W9vcVlijNw) | [Link](https://github.com/liun-online/CoGSL) |
154 | | SLAPS | NeurIPS 2021 | Homogeneous GSL | [SLAPS: Self-Supervision Improves Structure Learning for Graph Neural Networks](https://proceedings.neurips.cc/paper/2021/file/bf499a12e998d178afd964adf64a60cb-Paper.pdf) | [Link](https://github.com/BorealisAI/SLAPS-GNN) |
155 | | SUBLIME | WWW 2022 | Homogeneous GSL | [Towards Unsupervised Deep Graph Structure Learning](https://dl.acm.org/doi/pdf/10.1145/3485447.3512186?casa_token=445ECOqpVh4AAAAA:3ZBlGDSLFxrhwN0zEUdGFMpB4DslsI4h-rFLvI3cWHNzNsx6k-4m2t-NiDLubRvw1tBLaziISw) | [Link](https://github.com/GRAND-Lab/SUBLIME) |
156 | | STABLE | KDD 2022 | Homogeneous GSL | [Reliable Representations Make A Stronger Defender: Unsupervised Structure Refinement for Robust GNN](https://dl.acm.org/doi/abs/10.1145/3534678.3539484) | [Link](https://github.com/likuanppd/STABLE) |
157 | | NodeFormer | NeurIPS 2022 | Homogeneous GSL | [NodeFormer: A Scalable Graph Structure Learning Transformer for Node Classification](https://openreview.net/forum?id=sMezXGG5So) | [Link](https://github.com/qitianwu/NodeFormer) |
158 | | HES-GSL | TNNLS 2023 | Homogeneous GSL | [Homophily-Enhanced Self-Supervision for Graph Structure Learning: Insights and Directions](https://ieeexplore.ieee.org/abstract/document/10106110) | [Link](https://github.com/LirongWu/Homophily-Enhanced-Self-supervision) |
159 | | GSR | WSDM 2023 | Homogeneous GSL | [Self-Supervised Graph Structure Refinement for Graph Neural Networks](https://dl.acm.org/doi/abs/10.1145/3539597.3570455) | [Link](https://github.com/andyjzhao/WSDM23-GSR) |
160 | | GTN | NeurIPS 2020 | Heterogeneous GSL | [Graph Transformer Networks](https://proceedings.neurips.cc/paper_files/paper/2019/file/9d63484abb477c97640154d40595a3bb-Paper.pdf) | [Link](https://github.com/seongjunyun/Graph_Transformer_Networks) |
161 | | HGSL | AAAI 2021 | Heterogeneous GSL | [Heterogeneous Graph Structure Learning for Graph Neural Networks](https://ojs.aaai.org/index.php/AAAI/article/view/16600) | [Link](https://github.com/AndyJZhao/HGSL) |
162 | | HGP-SL | AAAI 2020 | Graph-level GSL | [Hierarchical Graph Pooling with Structure Learning](https://arxiv.org/abs/1911.05954) | [Link](https://github.com/cszhangzhen/HGP-SL) |
163 | | VIB-GSL | AAAI 2022 | Graph-level GSL | [Graph Structure Learning with Variational Information Bottleneck](https://ojs.aaai.org/index.php/AAAI/article/view/20335) | [Link](https://github.com/RingBDStack/VIB-GSL) |
164 |
165 |
166 | ## Cite Us
167 |
168 | Feel free to cite this work if you find it useful to you!
169 | ```
170 | @article{li2023gslb,
171 | title={GSLB: The Graph Structure Learning Benchmark},
172 | author={Li, Zhixun and Wang, Liang and Sun, Xin and Luo, Yifan and Zhu, Yanqiao and Chen, Dingshuo and Luo, Yingtao and Zhou, Xiangxin and Liu, Qiang and Wu, Shu and others},
173 | journal={arXiv preprint arXiv:2310.05174},
174 | year={2023}
175 | }
176 | ```
177 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | import GSL.learner
2 | import GSL.metric
3 | import GSL.processor
4 | import GSL.utils
5 |
6 | __all__ = [
7 | 'learner',
8 | 'metric',
9 | 'processor',
10 | 'utils'
11 | ]
--------------------------------------------------------------------------------
/configs/cogsl_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 0
3 | adj_hop: 1
4 |
5 | cora:
6 | big: False
7 | dataset: cora
8 | batch: 0
9 | gpu: 2
10 | seed: 3
11 | name_view1: v1_adj
12 | name_view2: v2_diff
13 | indice_view1: v1_2
14 | indice_view2: v2_40
15 | cls_hid_1: 16
16 | com_lambda_v1: 0.1
17 | com_lambda_v2: 0.1
18 | gen_hid: 32
19 | lam: 0.5
20 | alpha: 0.1
21 | mi_hid_1: 128
22 | cls_lr: 0.01
23 | cls_weight_decay: 0.0005
24 | cls_dropout: 0.5
25 | ve_lr: 0.001
26 | ve_weight_decay: 0.0
27 | ve_dropout: 0.2
28 | mi_lr: 0.01
29 | mi_weight_decay: 0
30 | main_epoch: 200
31 | inner_ne_epoch: 5
32 | inner_cls_epoch: 5
33 | inner_mi_epoch: 10
34 | temp_r: 0.0001
35 | cls_coe: 0.3
36 | mi_coe: 0.3
37 | tau: 0.8
38 | add: False
39 | dele: False
40 | ptb_feat: False
41 | ratio: 0.0
42 | flag: 1
43 |
44 | citeseer:
45 | big: False
46 | dataset: citeseer
47 | batch: 0
48 | gpu: 2
49 | seed: 3
50 | name_view1: v1_adj
51 | name_view2: v2_diff
52 | indice_view1: v1_2
53 | indice_view2: v2_40
54 | cls_hid_1: 16
55 | com_lambda_v1: 0.1
56 | com_lambda_v2: 0.1
57 | gen_hid: 32
58 | lam: 0.5
59 | alpha: 0.1
60 | mi_hid_1: 128
61 | cls_lr: 0.01
62 | cls_weight_decay: 0.0005
63 | cls_dropout: 0.5
64 | ve_lr: 0.001
65 | ve_weight_decay: 0.0
66 | ve_dropout: 0.2
67 | mi_lr: 0.01
68 | mi_weight_decay: 0
69 | main_epoch: 200
70 | inner_ne_epoch: 5
71 | inner_cls_epoch: 5
72 | inner_mi_epoch: 10
73 | temp_r: 0.0001
74 | cls_coe: 0.3
75 | mi_coe: 0.3
76 | tau: 0.8
77 | add: False
78 | dele: False
79 | ptb_feat: False
80 | ratio: 0.0
81 | flag: 1
82 |
83 | pubmed:
84 | big: False
85 | dataset: pubmed
86 | batch: 0
87 | gpu: 2
88 | seed: 3
89 | name_view1: v1_adj
90 | name_view2: v2_diff
91 | indice_view1: v1_2
92 | indice_view2: v2_10
93 | cls_hid_1: 16
94 | com_lambda_v1: 0.1
95 | com_lambda_v2: 0.1
96 | gen_hid: 32
97 | lam: 0.5
98 | alpha: 0.1
99 | mi_hid_1: 128
100 | cls_lr: 0.01
101 | cls_weight_decay: 0.0005
102 | cls_dropout: 0.5
103 | ve_lr: 0.001
104 | ve_weight_decay: 0.0
105 | ve_dropout: 0.2
106 | mi_lr: 0.01
107 | mi_weight_decay: 0
108 | main_epoch: 200
109 | inner_ne_epoch: 5
110 | inner_cls_epoch: 5
111 | inner_mi_epoch: 10
112 | temp_r: 0.0001
113 | cls_coe: 0.3
114 | mi_coe: 0.3
115 | tau: 0.8
116 | add: False
117 | dele: False
118 | ptb_feat: False
119 | ratio: 0.0
120 | flag: 1
121 |
122 | cornell:
123 | big: False
124 | dataset: cornell
125 | batch: 0
126 | gpu: 2
127 | seed: 3
128 | name_view1: v1_adj
129 | name_view2: v2_diff
130 | indice_view1: v1_2
131 | indice_view2: v2_40
132 | cls_hid_1: 16
133 | com_lambda_v1: 0.1
134 | com_lambda_v2: 0.1
135 | gen_hid: 32
136 | lam: 0.5
137 | alpha: 0.1
138 | mi_hid_1: 128
139 | cls_lr: 0.01
140 | cls_weight_decay: 0.0005
141 | cls_dropout: 0.5
142 | ve_lr: 0.001
143 | ve_weight_decay: 0.0
144 | ve_dropout: 0.2
145 | mi_lr: 0.01
146 | mi_weight_decay: 0
147 | main_epoch: 200
148 | inner_ne_epoch: 5
149 | inner_cls_epoch: 5
150 | inner_mi_epoch: 10
151 | temp_r: 0.0001
152 | cls_coe: 0.3
153 | mi_coe: 0.3
154 | tau: 0.8
155 | add: False
156 | dele: False
157 | ptb_feat: False
158 | ratio: 0.0
159 | flag: 1
160 |
161 | actor:
162 | big: False
163 | dataset: actor
164 | batch: 0
165 | gpu: 2
166 | seed: 3
167 | name_view1: v1_adj
168 | name_view2: v2_diff
169 | indice_view1: v1_2
170 | indice_view2: v2_40
171 | cls_hid_1: 16
172 | com_lambda_v1: 0.1
173 | com_lambda_v2: 0.1
174 | gen_hid: 32
175 | lam: 0.5
176 | alpha: 0.1
177 | mi_hid_1: 128
178 | cls_lr: 0.01
179 | cls_weight_decay: 0.0005
180 | cls_dropout: 0.5
181 | ve_lr: 0.001
182 | ve_weight_decay: 0.0
183 | ve_dropout: 0.2
184 | mi_lr: 0.01
185 | mi_weight_decay: 0
186 | main_epoch: 200
187 | inner_ne_epoch: 5
188 | inner_cls_epoch: 5
189 | inner_mi_epoch: 10
190 | temp_r: 0.0001
191 | cls_coe: 0.3
192 | mi_coe: 0.3
193 | tau: 0.8
194 | add: False
195 | dele: False
196 | ptb_feat: False
197 | ratio: 0.0
198 | flag: 1
199 |
200 | texas:
201 | big: False
202 | dataset: texas
203 | batch: 0
204 | gpu: 2
205 | seed: 3
206 | name_view1: v1_adj
207 | name_view2: v2_diff
208 | indice_view1: v1_2
209 | indice_view2: v2_40
210 | cls_hid_1: 16
211 | com_lambda_v1: 0.1
212 | com_lambda_v2: 0.1
213 | gen_hid: 32
214 | lam: 0.5
215 | alpha: 0.1
216 | mi_hid_1: 128
217 | cls_lr: 0.01
218 | cls_weight_decay: 0.0005
219 | cls_dropout: 0.5
220 | ve_lr: 0.001
221 | ve_weight_decay: 0.0
222 | ve_dropout: 0.2
223 | mi_lr: 0.01
224 | mi_weight_decay: 0
225 | main_epoch: 200
226 | inner_ne_epoch: 5
227 | inner_cls_epoch: 5
228 | inner_mi_epoch: 10
229 | temp_r: 0.0001
230 | cls_coe: 0.3
231 | mi_coe: 0.3
232 | tau: 0.8
233 | add: False
234 | dele: False
235 | ptb_feat: False
236 | ratio: 0.0
237 | flag: 1
238 |
239 | wisconsin:
240 | big: False
241 | dataset: wisconsin
242 | batch: 0
243 | gpu: 2
244 | seed: 3
245 | name_view1: v1_adj
246 | name_view2: v2_diff
247 | indice_view1: v1_2
248 | indice_view2: v2_40
249 | cls_hid_1: 16
250 | com_lambda_v1: 0.1
251 | com_lambda_v2: 0.1
252 | gen_hid: 32
253 | lam: 0.5
254 | alpha: 0.1
255 | mi_hid_1: 128
256 | cls_lr: 0.01
257 | cls_weight_decay: 0.0005
258 | cls_dropout: 0.5
259 | ve_lr: 0.001
260 | ve_weight_decay: 0.0
261 | ve_dropout: 0.2
262 | mi_lr: 0.01
263 | mi_weight_decay: 0
264 | main_epoch: 200
265 | inner_ne_epoch: 5
266 | inner_cls_epoch: 5
267 | inner_mi_epoch: 10
268 | temp_r: 0.0001
269 | cls_coe: 0.3
270 | mi_coe: 0.3
271 | tau: 0.8
272 | add: False
273 | dele: False
274 | ptb_feat: False
275 | ratio: 0.0
276 | flag: 1
277 |
278 | chameleon:
279 | big: False
280 | dataset: chameleon
281 | batch: 0
282 | gpu: 2
283 | seed: 3
284 | name_view1: v1_adj
285 | name_view2: v2_diff
286 | indice_view1: v1_2
287 | indice_view2: v2_40
288 | cls_hid_1: 16
289 | com_lambda_v1: 0.1
290 | com_lambda_v2: 0.1
291 | gen_hid: 32
292 | lam: 0.5
293 | alpha: 0.1
294 | mi_hid_1: 128
295 | cls_lr: 0.01
296 | cls_weight_decay: 0.0005
297 | cls_dropout: 0.5
298 | ve_lr: 0.001
299 | ve_weight_decay: 0.0
300 | ve_dropout: 0.2
301 | mi_lr: 0.01
302 | mi_weight_decay: 0
303 | main_epoch: 200
304 | inner_ne_epoch: 5
305 | inner_cls_epoch: 5
306 | inner_mi_epoch: 10
307 | temp_r: 0.0001
308 | cls_coe: 0.3
309 | mi_coe: 0.3
310 | tau: 0.8
311 | add: False
312 | dele: False
313 | ptb_feat: False
314 | ratio: 0.0
315 | flag: 1
316 |
317 | squirrel:
318 | big: False
319 | dataset: squirrel
320 | batch: 0
321 | gpu: 2
322 | seed: 3
323 | name_view1: v1_adj
324 | name_view2: v2_diff
325 | indice_view1: v1_2
326 | indice_view2: v2_40
327 | cls_hid_1: 16
328 | com_lambda_v1: 0.1
329 | com_lambda_v2: 0.1
330 | gen_hid: 32
331 | lam: 0.5
332 | alpha: 0.1
333 | mi_hid_1: 128
334 | cls_lr: 0.01
335 | cls_weight_decay: 0.0005
336 | cls_dropout: 0.5
337 | ve_lr: 0.001
338 | ve_weight_decay: 0.0
339 | ve_dropout: 0.2
340 | mi_lr: 0.01
341 | mi_weight_decay: 0
342 | main_epoch: 200
343 | inner_ne_epoch: 5
344 | inner_cls_epoch: 5
345 | inner_mi_epoch: 10
346 | temp_r: 0.0001
347 | cls_coe: 0.3
348 | mi_coe: 0.3
349 | tau: 0.8
350 | add: False
351 | dele: False
352 | ptb_feat: False
353 | ratio: 0.0
354 | flag: 1
--------------------------------------------------------------------------------
/configs/dagnn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | epochs: 1000
3 | lr: 0.01
4 | weight_decay: 0.005
5 | hidden: 64
6 | dropout: 0.5
7 | K: 10
8 |
9 | cora:
10 | dropout: 0.8
11 |
12 | citeseer:
13 | weight_decay: 0.02
14 |
15 | pubmed:
16 | dropout: 0.8
--------------------------------------------------------------------------------
/configs/gcn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 0
3 | hidden: 32
4 | sparse: False
5 | lr: 0.001
6 | weight_decay: 0
7 | epochs: 300
8 | dropout: 0.5
9 |
10 | cora:
11 | seed: 0
12 | hidden: 32
13 | sparse: False
14 | lr: 0.005
15 | weight_decay: 0.001
16 | epochs: 300
17 | dropout: 0.5
18 |
19 | citeseer:
20 | seed: 0
21 | hidden: 32
22 | sparse: False
23 | lr: 0.005
24 | weight_decay: 0.001
25 | epochs: 300
26 | dropout: 0.5
27 |
28 | pubmed:
29 | seed: 0
30 | hidden: 32
31 | sparse: False
32 | lr: 0.005
33 | weight_decay: 0.0005
34 | epochs: 300
35 | dropout: 0.5
36 |
37 | ogbn-arxiv:
38 | seed: 0
39 | hidden: 256
40 | sparse: True
41 | lr: 0.01
42 | weight_decay: 0
43 | epochs: 500
44 | dropout: 0.5
--------------------------------------------------------------------------------
/configs/gen_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | base: 'gcn'
3 | seed: 42
4 | lr: 0.01
5 | weight_decay: 5.0e-4
6 | hidden: 32
7 | dropout: 0.5
8 | activation: 'relu'
9 | epoch: 200
10 | iter: 30
11 | k: 9
12 | threshold: 0.6
13 | tolerance: 0.01
14 | debug: True
15 | cora:
16 | base: 'gcn'
17 | seed: 42
18 | lr: 0.01
19 | weight_decay: 5.0e-4
20 | hidden: 16
21 | dropout: 0.5
22 | activation: 'relu'
23 | epoch: 200
24 | iter: 30
25 | k: 9
26 | threshold: 0.5
27 | tolerance: 0.01
28 | debug: True
29 | citeseer:
30 | base: 'gcn'
31 | seed: 9
32 | lr: 0.01
33 | weight_decay: 5.0e-4
34 | hidden: 16
35 | dropout: 0.5
36 | activation: 'relu'
37 | epoch: 200
38 | iter: 30
39 | k: 9
40 | threshold: 0.5
41 | tolerance: 0.01
42 | debug: True
--------------------------------------------------------------------------------
/configs/gprgnn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | epochs: 1000
3 | lr: 0.002
4 | weight_decay: 0.0005
5 | hidden: 64
6 | dropout: 0.5
7 | K: 10
8 | alpha: 0.1
9 | dprate: 0.5
10 | Init: PPR
11 | ppnp: GPR_prop
12 | Gamma: None
13 |
14 | cora:
15 | lr: 0.01
16 | alpha: 0.01
17 |
18 | citeseer:
19 | lr: 0.01
20 | alpha: 0.1
21 |
22 | pubmed:
23 | lr: 0.05
24 | alpha: 0.2
25 |
26 | cornell:
27 | lr: 0.05
28 | alpha: 0.9
29 |
30 | texas:
31 | lr: 0.05
32 | alpha: 1.0
--------------------------------------------------------------------------------
/configs/grcn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | sparse: True
3 | hid_graph: "100:10"
4 | nhid: 32
5 | F: relu
6 | F_graph: tanh
7 | dropout: 0.5
8 |
9 | cora:
10 | compl_param: "150"
11 | normalize: True
12 | reduce: knn
13 | lr: 0.005
14 | weight_decay: 0.005
15 | lr_graph: 0.001
16 | weight_decay_graph: 0.
17 | num_epochs: 300
18 |
19 | citeseer:
20 | compl_param: "300"
21 | normalize: True
22 | reduce: knn
23 | lr: 0.005
24 | weight_decay: 0.005
25 | lr_graph: 0.001
26 | weight_decay_graph: 0.
27 | num_epochs: 200
28 |
29 | pubmed:
30 | compl_param: "1"
31 | normalize: False
32 | reduce: knn
33 | lr: 5.0e-3
34 | weight_decay: 5.0e-3
35 | lr_graph: 0.
36 | weight_decay_graph: 0.
37 | num_epochs: 300
38 | layertype: dense
--------------------------------------------------------------------------------
/configs/gsr_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | gnn_model: GCN
3 | activation: Relu
4 | semb: dw
5 | prt_lr: 0.001
6 | p_epochs: 50
7 | p_batch_size: 512
8 | p_schedule_step: 500
9 | poly_decay_lr: True
10 | fan_out: '20_40'
11 | num_workers: 0
12 | decoder_layer: 2
13 | decoder_n_hidden: 48
14 | intra_weight: 0.75
15 | seed: 0
16 |
17 | momentum_factor: 0.999
18 | nce_k: 16382
19 | nce_t: 0.07
20 | pre_dropout: 0.0
21 |
22 |
23 | cos_batch_size: 10000
24 |
25 | fsim_norm: True
26 | fsim_weight: 0.0
27 | rm_ratio: 0.0
28 | add_ratio: 0.25
29 |
30 | cla_batch_size: 5000
31 | lr: 0.01
32 | dropout: 0.5
33 | n_hidden: 256
34 |
35 | weight_decay: 5.e-4
36 | early_stop: 100
37 | epochs: 1000
38 |
39 | train_percentage: 0
40 | gpu: 0
41 |
--------------------------------------------------------------------------------
/configs/gtn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | early_stop: 20
3 | model: 'GTN'
4 | weight_decay: 0.001
5 | num_layers: 1
6 | is_norm: 1
7 | identity: 1
8 |
9 | acm:
10 | dataset: 'acm'
11 | num_layers: 2
12 | epochs: 100
13 | lr: 0.005
14 | num_channels: 2
15 | hid_dim: 128
16 | identity: 1
17 |
18 | dblp:
19 | dataset: 'dblp'
20 | num_layers: 3
21 | epochs: 100
22 | lr: 0.005
23 | num_channels: 2
24 | hid_dim: 128
25 | identity: 1
26 |
27 | yelp:
28 | dataset: 'yelp'
29 | num_layers: 1
30 | epochs: 100
31 | lr: 0.005
32 | num_channels: 2
33 | hid_dim: 128
34 | identity: 1
35 |
36 | imdb:
37 | dataset: 'imdb'
38 | num_layers: 2
39 | epochs: 150
40 | lr: 0.005
41 | num_channels: 2
42 | hid_dim: 128
43 | identity: 1
--------------------------------------------------------------------------------
/configs/han_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | early_stop: 100
3 | model: 'HAN'
4 | weight_decay: 0.001
5 |
6 | acm:
7 | dataset: 'acm'
8 | dropout: 0.46
9 | hid_dim: 128
10 | lr: 0.01
11 | epochs: 200
12 | early_stop: 100
13 | weight_decay: 0.001
14 |
15 | #dblp:
16 | # dataset: 'dblp'
17 | # dropout: 0.46
18 | # hid_dim: 128
19 | # lr: 0.01
20 | # epochs: 200
21 | # early_stop: 100
22 | # weight_decay: 0.001
23 | #
24 | #
25 | #yelp:
26 | # dataset: 'yelp'
27 | # dropout: 0.46
28 | # hid_dim: 128
29 | # lr: 0.01
30 | # epochs: 200
31 | # early_stop: 100
32 | # weight_decay: 0.001
33 | #
34 | #
35 | #imdb:
36 | # dataset: 'imdb'
37 | # dropout: 0.46
38 | # hid_dim: 128
39 | # lr: 0.01
40 | # epochs: 200
41 | # early_stop: 100
42 | # weight_decay: 0.001
43 |
--------------------------------------------------------------------------------
/configs/hesgsl_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | dataset: cora
3 | seed: 0
4 | nlayers: 2
5 | hid_dim_cla: 32
6 | hid_dim_dae: 1024
7 | mlp_dim: 1433
8 | k: 30
9 | ratio: 10
10 | scale: 15
11 | num_hop: 10
12 | alpha: 8
13 | beta: 1
14 | num_epochs: 2000
15 | epochs_pre: 400
16 | epochs_hom: 100
17 | lr: 0.001
18 | weight_decay: 0.0005
19 | dropout_cla: 0.6
20 | dropout_adj: 0.25
21 | sparse: False
22 | lr_cla: 0.01
23 | patience: 200
24 | eval_step: 10
25 |
26 | cora:
27 | dataset: cora
28 |
29 | citeseer:
30 | dataset: citeseer
31 | nlayers: 2
32 | hid_dim_cla: 32
33 | hid_dim_dae: 1024
34 | mlp_dim: 3703
35 | k: 30
36 | ratio: 10
37 | scale: 10
38 | num_hop: 1
39 | alpha: 8
40 | beta: 1
41 | num_epochs: 2000
42 | epochs_pre: 400
43 | epochs_hom: 100
44 | lr: 0.001
45 | weight_decay: 0.0005
46 | dropout_cla: 0.5
47 | dropout_adj: 0.2
48 | sparse: False
49 | lr_cla: 0.01
50 | patience: 200
51 | eval_step: 10
52 |
53 | pubmed:
54 | dataset: pubmed
55 | nlayers: 2
56 | hid_dim_cla: 64
57 | hid_dim_dae: 512
58 | mlp_dim: 500
59 | k: 20
60 | ratio: 5
61 | scale: 5
62 | num_hop: 10
63 | alpha: 5
64 | beta: 1
65 | num_epochs: 2000
66 | epochs_pre: 400
67 | epochs_hom: 100
68 | lr: 0.01
69 | weight_decay: 0.0005
70 | dropout_cla: 0.4
71 | dropout_adj: 0.3
72 | sparse: True
73 | lr_cla: 0.01
74 | patience: 200
75 | eval_step: 10
76 |
77 | ogbn-arxiv:
78 | dataset: ogbn-arxiv
79 | nlayers: 2
80 | hid_dim_cla: 256
81 | hid_dim_dae: 512
82 | mlp_dim: 128
83 | k: 20
84 | ratio: 100
85 | scale: 3
86 | num_hop: 3
87 | alpha: 10
88 | beta: 1
89 | num_epochs: 2000
90 | epochs_pre: 0
91 | epochs_hom: 300
92 | lr: 0.001
93 | weight_decay: 0.
94 | dropout_cla: 0.4
95 | dropout_adj: 0.5
96 | sparse: True
97 | lr_cla: 0.01
98 | patience: 200
99 | eval_step: 10
100 |
101 | cornell:
102 | dataset: cornell
103 | nlayers: 2
104 | hid_dim_cla: 128
105 | hid_dim_dae: 1024
106 | mlp_dim: 1703
107 | k: 20
108 | ratio: 20
109 | scale: 15
110 | num_hop: 8
111 | alpha: 8
112 | beta: 1
113 | num_epochs: 2000
114 | epochs_pre: 200
115 | epochs_hom: 100
116 | lr: 0.001
117 | weight_decay: 0.0005
118 | dropout_cla: 0.6
119 | dropout_adj: 0.3
120 | sparse: False
121 | lr_cla: 0.01
122 | patience: 200
123 | eval_step: 10
124 |
125 | texas:
126 | dataset: texas
127 | nlayers: 2
128 | hid_dim_cla: 16
129 | hid_dim_dae: 1024
130 | mlp_dim: 1703
131 | k: 5
132 | ratio: 10
133 | scale: 5
134 | num_hop: 5
135 | alpha: 1
136 | beta: 1
137 | num_epochs: 2000
138 | epochs_pre: 400
139 | epochs_hom: 100
140 | lr: 0.001
141 | weight_decay: 0.0005
142 | dropout_cla: 0.6
143 | dropout_adj: 0.1
144 | sparse: False
145 | lr_cla: 0.01
146 | patience: 200
147 | eval_step: 10
148 |
149 | wisconsin:
150 | dataset: wisconsin
151 | nlayers: 2
152 | hid_dim_cla: 32
153 | hid_dim_dae: 1024
154 | mlp_dim: 1703
155 | k: 3
156 | ratio: 5
157 | scale: 8
158 | num_hop: 3
159 | alpha: 1
160 | beta: 1
161 | num_epochs: 2000
162 | epochs_pre: 400
163 | epochs_hom: 100
164 | lr: 0.001
165 | weight_decay: 0.0005
166 | dropout_cla: 0.6
167 | dropout_adj: 0.25
168 | sparse: False
169 | lr_cla: 0.01
170 | patience: 200
171 | eval_step: 10
172 |
173 | actor:
174 | dataset: actor
175 | nlayers: 2
176 | hid_dim_cla: 32
177 | hid_dim_dae: 256
178 | mlp_dim: 932
179 | k: 30
180 | ratio: 5
181 | scale: 10
182 | num_hop: 3
183 | alpha: 10
184 | beta: 1
185 | num_epochs: 2000
186 | epochs_pre: 400
187 | epochs_hom: 100
188 | lr: 0.005
189 | weight_decay: 0.0005
190 | dropout_cla: 0.6
191 | dropout_adj: 0.2
192 | sparse: False
193 | lr_cla: 0.01
194 | patience: 200
195 | eval_step: 10
196 |
--------------------------------------------------------------------------------
/configs/hgpsl_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 0
3 | folds: 10
4 | epochs: 200
5 | batch_size: 80
6 | test_batch_size: 60
7 | sample: True
8 | lr: 1.0e-3
9 | weight_decay: 1.0e-3
10 | pool_ratio: 0.5
11 | num_layers: 3
12 | dropout: 0
13 | lamb: 1.0
14 | patience: 100
15 | eval_step: 1
16 | hidden_dim: 512
17 | sparse_attn: True
18 | sl: True
19 | backbone: 'GIN'
20 | use_log_soft: True
21 | loss_type: 'Nll'
22 |
23 | IMDB-BINARY:
24 | batch_size: 256
25 | hidden_dim: 64
26 | lr: 1.0e-3
27 | dropout: 0
28 | weight_decay: 0
29 |
30 | REDDIT-BINARY:
31 | batch_size: 256
32 | hidden_dim: 64
33 | lr: 1.0e-3
34 | dropout: 0
35 | weight_decay: 0
36 |
37 | COLLAB:
38 | batch_size: 1024
39 | hidden_dim: 64
40 | lr: 1.0e-3
41 | dropout: 0
42 | weight_decay: 0
--------------------------------------------------------------------------------
/configs/hgsl_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 0
3 | dataset: 'acm'
4 | lr: 0.01
5 | conv_method: 'gcn'
6 | num_head: 2
7 | early_stop: 80
8 | adj_norm_order: 1
9 | feat_norm: -1
10 | emb_dim: 64
11 | com_feat_dim: 16
12 | weight_decay: 5e-4
13 | model: 'HGSL'
14 | epochs: 200
15 | exp_name: 'debug'
16 | save_weights: False
17 |
18 | acm:
19 | alpha: 1
20 | dropout: 0
21 | fgd_th: 0.8
22 | fgh_th: 0.2
23 | sem_th: 0.6
24 | mp_list: ['psp', 'pap', 'pspap']
25 | data_type: 'pas'
26 | relation_list: 'p-a+a-p+p-s+s-p'
27 |
28 | dblp:
29 | alpha: 4.5
30 | dropout: 0.2
31 | fgd_th: 0.99
32 | fgh_th: 0.99
33 | sem_th: 0.4
34 | mp_list: ['apcpa']
35 | data_type: 'apc'
36 | relation_list: 'p-a+a-p+p-c+c-p'
37 |
38 | yelp:
39 | alpha: 0.5
40 | dropout: 0.2
41 | fgd_th: 0.8
42 | fgh_th: 0.1
43 | sem_th: 0.2
44 | mp_list: ['bub', 'bsb', 'bublb', 'bubsb']
45 | data_type: 'busl'
46 | relation_list: 'b-u+u-b+b-s+s-b+b-l+l-b'
47 |
48 | imdb:
49 | alpha: 1
50 | dropout: 0
51 | fgd_th: 0.8
52 | fgh_th: 0.2
53 | sem_th: 0.6
54 | mp_list: []
55 | data_type: 'mda'
56 | relation_list: 'm-d+d-m+m-a+a-m'
--------------------------------------------------------------------------------
/configs/lds_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 0
3 | patience: 20
4 | outer_loop_max_epochs: 400
5 | inner_loop_max_epochs: 400
6 | hyper_gradient_interval: 5
7 | hidden_size: 16
8 | dropout: 0.5
9 | gcn_optimizer_learning_rate: 0.01
10 | gcn_weight_decay: 0.0005
11 | graph_model: "lds"
12 | smoothness_factor: 0.0
13 | disconnection_factor: 0.0
14 | sparsity_factor: 0.0
15 | n_samples_empirical_mean: 16
16 | activation_last: 'log_softmax'
17 | normalize_adj: True
18 |
19 |
20 | cora:
21 | seed: 0
22 | patience: 20
23 | outer_loop_max_epochs: 400
24 | inner_loop_max_epochs: 400
25 | hyper_gradient_interval: 5
26 | hidden_size: 128
27 | dropout: 0.5
28 | gcn_optimizer_learning_rate: 0.01
29 | gcn_weight_decay: 0.0005
30 | graph_model: "lds"
31 | smoothness_factor: 0.0
32 | disconnection_factor: 0.0
33 | sparsity_factor: 0.0
34 | n_samples_empirical_mean: 16
35 | activation_last: 'log_softmax'
36 | normalize_adj: True
37 |
38 |
39 | citeseer:
40 | seed: 0
41 | patience: 20
42 | outer_loop_max_epochs: 400
43 | inner_loop_max_epochs: 400
44 | hyper_gradient_interval: 5
45 | hidden_size: 64
46 | dropout: 0.5
47 | gcn_optimizer_learning_rate: 0.01
48 | gcn_weight_decay: 0.0005
49 | graph_model: "lds"
50 | smoothness_factor: 0.0
51 | disconnection_factor: 0.0
52 | sparsity_factor: 0.0
53 | n_samples_empirical_mean: 16
54 | activation_last: 'log_softmax'
55 | normalize_adj: True
56 |
57 |
58 | cornell:
59 | seed: 0
60 | patience: 20
61 | outer_loop_max_epochs: 400
62 | inner_loop_max_epochs: 400
63 | hyper_gradient_interval: 5
64 | hidden_size: 128
65 | dropout: 0.5
66 | gcn_optimizer_learning_rate: 0.01
67 | gcn_weight_decay: 0.0005
68 | graph_model: "lds"
69 | smoothness_factor: 0.0
70 | disconnection_factor: 0.0
71 | sparsity_factor: 0.0
72 | n_samples_empirical_mean: 16
73 | activation_last: 'log_softmax'
74 | normalize_adj: True
75 |
76 |
77 | texas:
78 | seed: 0
79 | patience: 20
80 | outer_loop_max_epochs: 400
81 | inner_loop_max_epochs: 400
82 | hyper_gradient_interval: 5
83 | hidden_size: 64
84 | dropout: 0.5
85 | gcn_optimizer_learning_rate: 0.01
86 | gcn_weight_decay: 0.0005
87 | graph_model: "lds"
88 | smoothness_factor: 0.0
89 | disconnection_factor: 0.0
90 | sparsity_factor: 0.0
91 | n_samples_empirical_mean: 16
92 | activation_last: 'log_softmax'
93 | normalize_adj: True
94 |
95 | wisconsin:
96 | seed: 0
97 | patience: 20
98 | outer_loop_max_epochs: 400
99 | inner_loop_max_epochs: 400
100 | hyper_gradient_interval: 5
101 | hidden_size: 64
102 | dropout: 0.5
103 | gcn_optimizer_learning_rate: 0.01
104 | gcn_weight_decay: 0.0005
105 | graph_model: "lds"
106 | smoothness_factor: 0.0
107 | disconnection_factor: 0.0
108 | sparsity_factor: 0.0
109 | n_samples_empirical_mean: 16
110 | activation_last: 'log_softmax'
111 | normalize_adj: True
112 |
113 | actor:
114 | seed: 0
115 | patience: 20
116 | outer_loop_max_epochs: 400
117 | inner_loop_max_epochs: 400
118 | hyper_gradient_interval: 5
119 | hidden_size: 64
120 | dropout: 0.5
121 | gcn_optimizer_learning_rate: 0.01
122 | gcn_weight_decay: 0.0005
123 | graph_model: "lds"
124 | smoothness_factor: 0.0
125 | disconnection_factor: 0.0
126 | sparsity_factor: 0.0
127 | n_samples_empirical_mean: 16
128 | activation_last: 'log_softmax'
129 | normalize_adj: True
130 |
--------------------------------------------------------------------------------
/configs/mlp_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | hidden: 32
3 | lr: 0.01
4 | weight_decay: 0.0001
5 | epochs: 500
6 |
7 | cora:
8 | hideen: 32
9 | lr: 0.01
10 | weight_decay: 0.001
11 | epochs: 500
--------------------------------------------------------------------------------
/configs/neuralsparse_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | dataset: cora
3 |
4 | Cora:
5 | dataset: cora
6 | seed: 0
7 | hidden: 32
8 | epochs: 3000
9 | temp_N: 50
10 | temp_r: 0.001
11 | k: 5
12 | hidden_channel: 128
13 | dropout: 0.5
14 | num_layers: 2
15 | lr: 0.001
16 | weight_decay: 0.001
17 | patience: 100
--------------------------------------------------------------------------------
/configs/nodeformer_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 9
3 | num_layers: 2
4 | num_heads: 4
5 | kernel_transformation: softmax
6 | nb_random_features: 30
7 | rb_trans: sigmoid
8 | use_residual: True
9 | use_gumbel: True
10 | use_bn: True
11 | use_jk: False
12 | use_edge_loss: True
13 | use_act: False
14 | tau: 0.25
15 |
16 | cora:
17 | sparse: False
18 | hidden_dim: 32
19 | dropout: 0.0
20 | epochs: 500
21 | lr: 0.01
22 | weight_decay: 5.0e-4
23 | eval_step: 1
24 | lamda: 1.0
25 | rb_order: 3
26 | nb_gumbel_sample: 10
27 |
28 | citeseer:
29 | sparse: False
30 | hidden_dim: 32
31 | dropout: 0.5
32 | epochs: 500
33 | lr: 0.01
34 | weight_decay: 5.0e-3
35 | eval_step: 1
36 | lamda: 1.0
37 | rb_order: 3
38 | tau: 0.25
39 | nb_gumbel_sample: 10
40 |
41 | pubmed:
42 | sparse: False
43 | hidden_dim: 32
44 | dropout: 0.3
45 | epochs: 1000
46 | lr: 0.001
47 | weight_decay: 5.0e-4
48 | eval_step: 1
49 | lamda: 1.0
50 | rb_order: 2
51 | tau: 0.25
52 | nb_gumbel_sample: 10
53 |
54 | amazon-computers:
55 | sparse: False
56 | hidden_dim: 64
57 | dropout: 0.2
58 | epochs: 5000
59 | lr: 0.0005
60 | weight_decay: 5.0e-4
61 | eval_step: 10
62 | lamda: 0.5
63 | rb_order: 0
64 | tau: 0.25
65 | nb_gumbel_sample: 10
66 | nb_random_features: 30
67 | use_jk: True
68 | use_act: False
69 |
70 | amazon-photo:
71 | sparse: False
72 | hidden_dim: 64
73 | dropout: 0.2
74 | epochs: 500
75 | lr: 0.003
76 | weight_decay: 0.0005
77 | eval_step: 1
78 | lamda: 0.5
79 | rb_order: 1
80 | tau: 0.25
81 | nb_gumbel_sample: 10
82 |
83 | cornell:
84 | sparse: False
85 | hidden_dim: 128
86 | dropout: 0.5
87 | epochs: 100
88 | lr: 0.01
89 | weight_decay: 5.0e-4
90 | eval_step: 1
91 | lamda: 0.01
92 | rb_order: 1
93 | nb_gumbel_sample: 10
94 |
95 | texas:
96 | sparse: False
97 | hidden_dim: 128
98 | dropout: 0.5
99 | epochs: 50
100 | lr: 0.01
101 | weight_decay: 5.0e-4
102 | eval_step: 1
103 | lamda: 1
104 | rb_order: 1
105 | nb_gumbel_sample: 10
106 |
107 | wisconsin:
108 | sparse: False
109 | hidden_dim: 128
110 | dropout: 0.5
111 | epochs: 500
112 | lr: 0.01
113 | weight_decay: 5.0e-4
114 | eval_step: 1
115 | lamda: 1
116 | rb_order: 1
117 | nb_gumbel_sample: 10
118 |
119 | actor:
120 | sparse: False
121 | hidden_dim: 128
122 | dropout: 0.5
123 | epochs: 500
124 | lr: 0.01
125 | weight_decay: 5.0e-4
126 | eval_step: 1
127 | lamda: 1
128 | rb_order: 1
129 | nb_gumbel_sample: 10
130 |
131 | chameleon:
132 | sparse: False
133 | hidden_dim: 128
134 | dropout: 0.5
135 | epochs: 150
136 | lr: 0.005
137 | weight_decay: 5.0e-4
138 | eval_step: 1
139 | lamda: 1
140 | rb_order: 1
141 | nb_gumbel_sample: 10
142 |
143 | squirrel:
144 | sparse: False
145 | hidden_dim: 128
146 | dropout: 0.5
147 | epochs: 100
148 | lr: 0.005
149 | weight_decay: 5.0e-4
150 | eval_step: 1
151 | lamda: 1
152 | rb_order: 1
153 | nb_gumbel_sample: 10
154 |
155 | acm:
156 | sparse: False
157 | hidden_dim: 32
158 | dropout: 0.0
159 | epochs: 500
160 | lr: 0.01
161 | weight_decay: 5.0e-4
162 | eval_step: 1
163 | lamda: 1.0
164 | rb_order: 2
165 | nb_gumbel_sample: 10
--------------------------------------------------------------------------------
/configs/prognn_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | alpha: 0.0005
3 | beta: 1.5
4 | cuda: true
5 | debug: false
6 | dropout: 0.5
7 | epochs: 400
8 | gamma: 1
9 | hidden: 32
10 | inner_steps: 2
11 | lambda_: 0
12 | lr: 0.01
13 | lr_adj: 0.01
14 | outer_steps: 1
15 | phi: 0
16 | seed: 42
17 | symmetric: false
18 | weight_decay: 0.0005
19 | cora:
20 | alpha: 0.0005
21 | beta: 1.5
22 | cuda: true
23 | debug: false
24 | dropout: 0.5
25 | epochs: 400
26 | gamma: 1
27 | hidden: 16
28 | inner_steps: 2
29 | lambda_: 0
30 | lr: 0.01
31 | lr_adj: 0.01
32 | outer_steps: 1
33 | phi: 0
34 | seed: 42
35 | symmetric: false
36 | weight_decay: 0.0005
37 | Pubmed:
38 | alpha: 0.0005
39 | beta: 1.5
40 | cuda: true
41 | debug: false
42 | dropout: 0.5
43 | epochs: 400
44 | gamma: 1
45 | hidden: 32
46 | inner_steps: 2
47 | lambda_: 0
48 | lr: 0.01
49 | lr_adj: 0.01
50 | outer_steps: 1
51 | phi: 0
52 | seed: 42
53 | symmetric: false
54 | weight_decay: 0.0005
--------------------------------------------------------------------------------
/configs/ptdnet_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | dataset: cora
3 | seed: 0
4 | sparse: False
5 | hiddens: 256
6 | conv_bias: False
7 | denoise_hidden_1: 16
8 | denoise_hidden_2: 0
9 | weight_decay: 5.0e-4
10 | lr: 0.001
11 | epochs: 1000
12 | init_temperature: 2.0
13 | temperature_decay: 0.99
14 | outL: 3
15 | L: 1
16 | dropout: 0.5
17 | gamma: -0.0
18 | zeta: 1.01
19 | lambda1: 0.1
20 | lambda3: 0.01
21 | coff_consis: 0.01
22 | k_svd: 1
23 | patience: 100
24 |
25 | Cora:
26 | dataset: cora
27 | hiddens: 128
28 |
29 | Citeseer:
30 | dataset: citeseer
31 | hiddens: 128
32 | weight_decay: 1.0e-3
--------------------------------------------------------------------------------
/configs/slaps_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | epochs: 200
3 | epochs_adj: 2000
4 | lr: 0.001
5 | lr_adj: 0.01
6 | w_decay: 0.0005
7 | w_decay_adj: 0.0
8 | hidden: 32
9 | hidden_adj: 512
10 | dropout1: 0.5
11 | dropout2: 0.5
12 | dropout_adj1: 0.25
13 | dropout_adj2: 0.25
14 | dataset: cora
15 | nlayers: 2
16 | nlayers_adj: 2
17 | patience: 10
18 | k: 20
19 | ratio: 20
20 | epoch_d: 5
21 | lambda_: 0.1
22 | nr: 5
23 | knn_metric: cosine
24 | model: end2end
25 | i: 6
26 | non_linearity: elu
27 | mlp_act: relu
28 | normalization: sym
29 | mlp_h: 50
30 | learner: MLP # Graph Structure Learner, choices: ['FP', 'MLP']
31 | sparse: 0
32 | noise: mask
33 | loss: mse
34 |
35 | cora:
36 | dataset: cora
37 | epochs_adj: 2000
38 | lr: 0.01
39 | lr_adj: 0.001
40 | w_decay: 0.0005
41 | nlayers: 2
42 | nlayers_adj: 2
43 | hidden: 32
44 | hidden_adj: 512
45 | dropout1: 0.5
46 | dropout2: 0.5
47 | dropout_adj1: 0.25
48 | dropout_adj2: 0.5
49 | k: 20
50 | lambda_: 10.0
51 | nr: 5
52 | ratio: 10
53 | model: end2end
54 | sparse: 0
55 | learner: MLP
56 | non_linearity: relu
57 | mlp_h: 1433
58 | mlp_act: relu
59 | epoch_d: 5
60 |
61 | citeseer:
62 | dataset: citeseer
63 | epochs_adj: 2000
64 | lr: 0.01
65 | lr_adj: 0.001
66 | w_decay: 0.0005
67 | nlayers: 2
68 | nlayers_adj: 2
69 | hidden: 32
70 | hidden_adj: 1024
71 | dropout1: 0.5
72 | dropout2: 0.5
73 | dropout_adj1: 0.25
74 | dropout_adj2: 0.5
75 | k: 30
76 | lambda_: 10.0
77 | nr: 5
78 | ratio: 10
79 | model: end2end
80 | sparse: 0
81 | learner: MLP
82 | non_linearity: relu
83 | mlp_act: relu
84 | mlp_h: 3703
85 | epoch_d: 5
86 |
87 | pubmed:
88 | dataset: pubmed
89 | epochs_adj: 2000
90 | lr: 0.01
91 | lr_adj: 0.01
92 | w_decay: 0.0005
93 | nlayers: 2
94 | nlayers_adj: 2
95 | hidden: 32
96 | hidden_adj: 128
97 | dropout1: 0.5
98 | dropout2: 0.5
99 | dropout_adj1: 0.5
100 | dropout_adj2: 0.5
101 | k: 15
102 | lambda_: 10.0
103 | nr: 5
104 | ratio: 20
105 | model: end2end
106 | learner: MLP
107 | non_linearity: relu
108 | mlp_h: 500
109 | mlp_act: relu
110 | epoch_d: 5
111 | sparse: 1
112 |
113 | ogbn-arxiv:
114 | dataset: ogbn-arxiv
115 | epochs_adj: 2000
116 | lr: 0.01
117 | lr_adj: 0.001
118 | w_decay: 0.0
119 | nlayers: 2
120 | nlayers_adj: 2
121 | hidden: 256
122 | hidden_adj: 256
123 | dropout1: 0.5
124 | dropout2: 0.5
125 | dropout_adj1: 0.25
126 | dropout_adj2: 0.5
127 | k: 15
128 | lambda_: 10.0
129 | nr: 5
130 | ratio: 100
131 | model: end2end
132 | learner: MLP
133 | non_linearity: relu
134 | mlp_h: 128
135 | mlp_act: relu
136 | epoch_d: 2001
137 | sparse: 1
138 | loss: mse
139 | noise: mask
140 |
141 | cornell:
142 | dataset: cornell
143 | epochs_adj: 2000
144 | lr: 0.01
145 | lr_adj: 0.01
146 | w_decay: 0.0005
147 | nlayers: 2
148 | nlayers_adj: 2
149 | hidden: 32
150 | hidden_adj: 512
151 | dropout1: 0.5
152 | dropout2: 0.5
153 | dropout_adj1: 0.5
154 | dropout_adj2: 0.25
155 | k: 30
156 | lambda_: 10.0
157 | nr: 5
158 | ratio: 10
159 | model: end2end
160 | sparse: 0
161 | learner: FP
162 | non_linearity: elu
163 | epoch_d: 5
164 |
165 | actor:
166 | dataset: actor
167 | epochs_adj: 2000
168 | lr: 0.01
169 | lr_adj: 0.001
170 | w_decay: 0.0005
171 | nlayers: 2
172 | nlayers_adj: 2
173 | hidden: 32
174 | hidden_adj: 512
175 | dropout1: 0.5
176 | dropout2: 0.5
177 | dropout_adj1: 0.25
178 | dropout_adj2: 0.5
179 | k: 20
180 | lambda_: 10.0
181 | nr: 5
182 | ratio: 10
183 | model: end2end
184 | sparse: 0
185 | learner: MLP
186 | non_linearity: relu
187 | mlp_h: 932
188 | mlp_act: relu
189 | epoch_d: 5
190 |
191 | texas:
192 | dataset: texas
193 | epochs_adj: 2000
194 | lr: 0.01
195 | lr_adj: 0.01
196 | w_decay: 0.0005
197 | nlayers: 2
198 | nlayers_adj: 2
199 | hidden: 32
200 | hidden_adj: 512
201 | dropout1: 0.5
202 | dropout2: 0.5
203 | dropout_adj1: 0.5
204 | dropout_adj2: 0.25
205 | k: 30
206 | lambda_: 10.0
207 | nr: 5
208 | ratio: 10
209 | model: end2end
210 | sparse: 0
211 | learner: FP
212 | non_linearity: elu
213 | epoch_d: 5
214 |
215 | wisconsin:
216 | dataset: wisconsin
217 | epochs_adj: 2000
218 | lr: 0.01
219 | lr_adj: 0.01
220 | w_decay: 0.0005
221 | nlayers: 2
222 | nlayers_adj: 2
223 | hidden: 32
224 | hidden_adj: 512
225 | dropout1: 0.5
226 | dropout2: 0.5
227 | dropout_adj1: 0.5
228 | dropout_adj2: 0.25
229 | k: 30
230 | lambda_: 10.0
231 | nr: 5
232 | ratio: 10
233 | model: end2end
234 | sparse: 0
235 | learner: FP
236 | non_linearity: elu
237 | epoch_d: 5
238 |
239 | chameleon:
240 | dataset: chameleon
241 | epochs_adj: 2000
242 | lr: 0.01
243 | lr_adj: 0.001
244 | w_decay: 0.0005
245 | nlayers: 2
246 | nlayers_adj: 2
247 | hidden: 32
248 | hidden_adj: 512
249 | dropout1: 0.5
250 | dropout2: 0.5
251 | dropout_adj1: 0.25
252 | dropout_adj2: 0.5
253 | k: 20
254 | lambda_: 10.0
255 | nr: 5
256 | ratio: 10
257 | model: end2end
258 | sparse: 0
259 | learner: MLP
260 | non_linearity: relu
261 | mlp_h: 1433
262 | mlp_act: relu
263 | epoch_d: 5
264 |
265 | squirrel:
266 | dataset: squirrel
267 | epochs_adj: 2000
268 | lr: 0.01
269 | lr_adj: 0.001
270 | w_decay: 0.0005
271 | nlayers: 2
272 | nlayers_adj: 2
273 | hidden: 32
274 | hidden_adj: 512
275 | dropout1: 0.5
276 | dropout2: 0.5
277 | dropout_adj1: 0.25
278 | dropout_adj2: 0.5
279 | k: 20
280 | lambda_: 10.0
281 | nr: 5
282 | ratio: 10
283 | model: end2end
284 | sparse: 0
285 | learner: MLP
286 | non_linearity: relu
287 | mlp_h: 1433
288 | mlp_act: relu
289 | epoch_d: 5
--------------------------------------------------------------------------------
/configs/stable_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | dataset: cora
3 | jt: 0.03
4 | threshold: 1
5 | recover_percent: 0.2
6 | beta: 2
7 | nb_epochs: 10000
8 | cos: 0.1
9 | k: 5
10 | alpha: -0.3
11 | sparse: False
12 | num_hidden_eval: 16
13 | num_layers_eval: 2
14 | dropout_eval: 0.5
15 | dropedge_rate_eval: 0
16 | lr_cls: 0.01
17 | weight_decay_cls: 0.0005
18 | num_epochs_cls: 200
19 | seed: 1
20 |
21 | Cora-FP:
22 | dataset: cora
23 | jt: 0.03
24 | threshold: 1
25 | recover_percent: 0.2
26 | beta: 2
27 | nb_epochs: 10000
28 | cos: 0.1
29 | k: 5
30 | alpha: -0.3
31 | sparse: False
32 | num_hidden_eval: 16
33 | num_layers_eval: 2
34 | dropout_eval: 0.5
35 | dropedge_rate_eval: 0
36 | lr_cls: 0.01
37 | weight_decay_cls: 0.0005
38 | num_epochs_cls: 200
39 | seed: 1
40 | lr: 0.01
41 | weight_decay: 0.0005
--------------------------------------------------------------------------------
/configs/sublime_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 1
3 | dataset: cora
4 | lr: 0.01
5 | lr_cls: 0.001
6 | num_hidden: 512
7 | num_hidden_eval: 32
8 | num_rep_dim: 256
9 | num_proj_dim: 256
10 | activation: relu
11 | base_model: GCNConv
12 | num_layers: 2
13 | num_layers_eval: 2
14 | num_epochs: 4000
15 | num_epochs_cls: 200
16 | weight_decay: 0
17 | weight_decay_cls: 0.0005
18 | mode: structure_refinement
19 | sparse: False
20 | k: 30
21 | dropout: 0.5
22 | dropout_eval: 0.5
23 | dropedge_rate: 0.5
24 | dropedge_rate_eval: 0.75
25 | tau: 0.9999
26 | c: 0
27 | learner: mlp
28 | maskfeat_rate_anchor: 0.6
29 | maskfeat_rate_learner: 0.7
30 | contrast_batch_size: 0
31 |
32 | cora:
33 | lr: 0.01
34 | lr_cls: 0.001
35 | num_hidden: 512
36 | num_hidden_eval: 32
37 | num_rep_dim: 256
38 | num_proj_dim: 256
39 | activation: relu
40 | base_model: GCNConv
41 | num_layers: 2
42 | num_layers_eval: 2
43 | num_epochs: 4000
44 | num_epochs_cls: 200
45 | weight_decay: 0
46 | weight_decay_cls: 0.0005
47 | mode: structure_refinement
48 | sparse: False
49 | k: 30
50 | dropout: 0.5
51 | dropout_eval: 0.5
52 | dropedge_rate: 0.5
53 | dropedge_rate_eval: 0.75
54 | tau: 0.9999
55 | c: 0
56 | learner: full
57 | maskfeat_rate_anchor: 0.6
58 | maskfeat_rate_learner: 0.7
59 | contrast_batch_size: 0
60 | eval_step: 50
61 |
62 | citeseer:
63 | seed: 1
64 | dataset: citeseer
65 | lr: 0.001
66 | lr_cls: 0.001
67 | num_hidden: 512
68 | num_hidden_eval: 32
69 | num_rep_dim: 256
70 | num_proj_dim: 256
71 | activation: tanh
72 | base_model: GCNConv
73 | num_layers: 2
74 | num_layers_eval: 2
75 | num_epochs: 1000
76 | num_epochs_cls: 200
77 | weight_decay: 0
78 | weight_decay_cls: 0.05
79 | mode: structure_refinement
80 | sparse: False
81 | k: 20
82 | dropout: 0.5
83 | dropout_eval: 0.5
84 | dropedge_rate: 0.25
85 | dropedge_rate_eval: 0.5
86 | tau: 0.9999
87 | c: 0
88 | learner: att
89 | maskfeat_rate_anchor: 0.8
90 | maskfeat_rate_learner: 0.6
91 | contrast_batch_size: 0
92 |
93 | pubmed:
94 | seed: 1
95 | dataset: pubmed
96 | lr: 0.001
97 | lr_cls: 0.01
98 | num_hidden: 128
99 | num_hidden_eval: 32
100 | num_rep_dim: 64
101 | num_proj_dim: 64
102 | activation: relu
103 | base_model: GCNConv
104 | num_layers: 2
105 | num_layers_eval: 2
106 | num_epochs: 1500
107 | num_epochs_cls: 200
108 | weight_decay: 0
109 | weight_decay_cls: 0.0005
110 | mode: structure_refinement
111 | sparse: True
112 | k: 10
113 | dropout: 0.5
114 | dropout_eval: 0.5
115 | dropedge_rate: 0.5
116 | dropedge_rate_eval: 0.25
117 | tau: 0.9999
118 | c: 50
119 | learner: mlp
120 | maskfeat_rate_anchor: 0.4
121 | maskfeat_rate_learner: 0.4
122 | contrast_batch_size: 2000
--------------------------------------------------------------------------------
/configs/vibgsl_config.yaml:
--------------------------------------------------------------------------------
1 | Default:
2 | seed: 12345
3 | backbone: 'GCN'
4 | graph_type: 'prob'
5 | graph_metric_type: 'mlp'
6 | repar: True
7 | num_layers: 2
8 | hidden_dim: 16
9 | folds: 10
10 | epochs: 200
11 | lr: 0.001
12 | lr_decay_factor: 0.5
13 | lr_decay_step_size: 50
14 | weight_decay: 5*10**-5
15 | batch_size: 100
16 | test_batch_size: 100
17 | beta: 0.00001
18 | IB_size: 16
19 | num_per: 16
20 | feature_denoise: False
21 | top_k: 10
22 | epsilon: 0.3
23 | graph_skip_conn: 0.0
24 | graph_include_self: True
25 |
26 | reddit-b:
27 | epochs: 200
28 | feature_denoise: False
29 |
30 | imdb-b:
31 | epochs: 200
32 | feature_denoise: False
33 |
34 | imdb-m:
35 | epochs: 200
36 | feature_denoise: False
37 |
38 | collab:
39 | epochs: 200
40 | feature_denoise: False
--------------------------------------------------------------------------------
/data/__init__.py:
--------------------------------------------------------------------------------
1 | from .dataset import Dataset
2 | from .hetero_dataset import HeteroDataset
3 | from .graph_dataset import GraphDataset
4 |
5 | __all__ = ['Dataset', 'HeteroDataset', 'GraphDataset']
--------------------------------------------------------------------------------
/data/graph_dataset.py:
--------------------------------------------------------------------------------
1 | import os.path as osp
2 | import numpy as np
3 | import dgl
4 | import torch
5 | import torch.nn.functional as F
6 | from dgl.data import TUDataset
7 |
8 |
9 | class GraphDataset:
10 | def __init__(self, root, name):
11 | self.root = osp.expanduser(osp.normpath(root))
12 | dict_dataset_name = {
13 | 'imdb-b': 'IMDB-BINARY',
14 | 'imdb-m': 'IMDB-MULTI',
15 | 'collab': 'COLLAB',
16 | 'reddit-b': 'REDDIT-BINARY',
17 | }
18 | self.name = dict_dataset_name[name]
19 |
20 | assert self.name in [
21 | "IMDB-BINARY", "REDDIT-BINARY", "COLLAB", "IMDB-MULTI",
22 | ], (
23 | "Currently only support IMDB-BINARY, REDDIT-BINARY, COLLAB, IMDB-MULTI"
24 | )
25 |
26 | self.dgl_dataset = self.load_data()
27 | self.graphs, self.labels = zip(*[self.dgl_dataset[i] for i in range(len(self.dgl_dataset))])
28 | self.labels = torch.tensor(self.labels)
29 | self.num_feat = self.dgl_dataset[0][0].ndata['feat'].shape[1]
30 | self.num_class = self.dgl_dataset.num_labels
31 |
32 | def __len__(self):
33 | return len(self.dgl_dataset)
34 |
35 | def load_data(self):
36 | # TODO: to_simple will be conducted before every access, which results in
37 | # the computed features being overwritten. Maybe we could abstract this
38 | # function to a dgl.transform class.
39 | dataset = TUDataset(self.name, raw_dir=self.root)
40 | # dataset = TUDataset(self.name, raw_dir=self.root, transform=dgl.to_simple)
41 | graph, _ = dataset[0]
42 |
43 | if "feat" not in graph.ndata:
44 | max_degree = 0
45 | degs = []
46 | for g, _ in dataset:
47 | degs += [g.in_degrees().to(torch.long)]
48 | max_degree = max(max_degree, degs[-1].max().item())
49 |
50 | if max_degree < 1000:
51 | # use one-hot degree embedding as node features
52 | for g, _ in dataset:
53 | deg = g.in_degrees()
54 | g.ndata['feat'] = F.one_hot(deg, num_classes=max_degree+1).to(torch.float)
55 | else:
56 | deg = torch.cat(degs, dim=0).to(torch.float)
57 | mean, std = deg.mean().item(), deg.std().item()
58 | for g, _ in dataset:
59 | deg = g.in_degrees().to(torch.float)
60 | deg = (deg - mean) / std
61 | g.ndata['feat'] = deg.view(-1, 1)
62 |
63 | return dataset
64 |
65 |
66 |
67 | if __name__ == "__main__":
68 |
69 | # graph classification dataset
70 | data_path = osp.join(osp.expanduser('~'), 'datasets')
71 | dataset = GraphDataset(root=data_path, name="IMDB-BINARY")
72 |
--------------------------------------------------------------------------------
/encoder/__init__.py:
--------------------------------------------------------------------------------
1 | from .gat import GAT, GAT_dgl
2 | from .gcn import GCN, GCNConv, GCNConv_dgl, SparseDropout, GraphEncoder, GCNConv_diag, GCN_dgl, MetaDenseGCN
3 | from .gin import GIN
4 | from .graphsage import GraphSAGE
5 | from .mlp import MLP
6 | from .nodeformer_conv import NodeFormerConv
7 | from .gprgnn import GPRGNN, GPR_prop
8 | from .dagnn import DAGNN
9 |
10 | __all__ = [
11 | "GAT",
12 | "GAT_dgl",
13 | "GCN",
14 | "GCNConv",
15 | "GCNConv_dgl",
16 | "SparseDropout",
17 | "GraphEncoder",
18 | "GCNConv_diag",
19 | "GCN_dgl",
20 | "GIN",
21 | "GraphSAGE",
22 | "MLP",
23 | "NodeFormerConv",
24 | "GPR_prop",
25 | "GPRGNN",
26 | "DAGNN",
27 | "MetaDenseGCN"
28 | ]
29 |
30 | classes = __all__
--------------------------------------------------------------------------------
/encoder/dagnn.py:
--------------------------------------------------------------------------------
1 | import torch
2 | from torch import nn
3 | from torch.nn import functional as F, Parameter
4 | import dgl.function as fn
5 |
6 |
7 | class DAGNNConv(nn.Module):
8 | def __init__(self, in_dim, k):
9 | super(DAGNNConv, self).__init__()
10 |
11 | self.s = Parameter(torch.FloatTensor(in_dim, 1))
12 | self.k = k
13 |
14 | self.reset_parameters()
15 |
16 | def reset_parameters(self):
17 | gain = nn.init.calculate_gain("sigmoid")
18 | nn.init.xavier_uniform_(self.s, gain=gain)
19 |
20 | def forward(self, graph, feats):
21 | with graph.local_scope():
22 | results = [feats]
23 |
24 | degs = graph.in_degrees().float()
25 | norm = torch.pow(degs, -0.5)
26 | norm = norm.to(feats.device).unsqueeze(1)
27 |
28 | for _ in range(self.k):
29 | feats = feats * norm
30 | graph.ndata["h"] = feats
31 | graph.update_all(fn.copy_u("h", "m"), fn.sum("m", "h"))
32 | feats = graph.ndata["h"]
33 | feats = feats * norm
34 | results.append(feats)
35 |
36 | H = torch.stack(results, dim=1)
37 | S = F.sigmoid(torch.matmul(H, self.s))
38 | S = S.permute(0, 2, 1)
39 | H = torch.matmul(S, H).squeeze()
40 |
41 | return H
42 |
43 |
44 | class MLPLayer(nn.Module):
45 | def __init__(self, in_dim, out_dim, bias=True, activation=None, dropout=0):
46 | super(MLPLayer, self).__init__()
47 |
48 | self.linear = nn.Linear(in_dim, out_dim, bias=bias)
49 | self.activation = activation
50 | self.dropout = nn.Dropout(dropout)
51 | self.reset_parameters()
52 |
53 | def reset_parameters(self):
54 | gain = 1.0
55 | if self.activation is F.relu:
56 | gain = nn.init.calculate_gain("relu")
57 | nn.init.xavier_uniform_(self.linear.weight, gain=gain)
58 | if self.linear.bias is not None:
59 | nn.init.zeros_(self.linear.bias)
60 |
61 | def forward(self, feats):
62 | feats = self.dropout(feats)
63 | feats = self.linear(feats)
64 | if self.activation:
65 | feats = self.activation(feats)
66 |
67 | return feats
68 |
69 |
70 | class DAGNN(nn.Module):
71 | def __init__(
72 | self,
73 | k,
74 | in_dim,
75 | hid_dim,
76 | out_dim,
77 | bias=True,
78 | activation=F.relu,
79 | dropout=0,
80 | ):
81 | super(DAGNN, self).__init__()
82 | self.mlp = nn.ModuleList()
83 | self.mlp.append(
84 | MLPLayer(
85 | in_dim=in_dim,
86 | out_dim=hid_dim,
87 | bias=bias,
88 | activation=activation,
89 | dropout=dropout,
90 | )
91 | )
92 | self.mlp.append(
93 | MLPLayer(
94 | in_dim=hid_dim,
95 | out_dim=out_dim,
96 | bias=bias,
97 | activation=None,
98 | dropout=dropout,
99 | )
100 | )
101 | self.dagnn = DAGNNConv(in_dim=out_dim, k=k)
102 |
103 | def forward(self, graph, feats):
104 | for layer in self.mlp:
105 | feats = layer(feats)
106 | feats = self.dagnn(graph, feats)
107 | return feats
--------------------------------------------------------------------------------
/encoder/gat.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | from dgl.nn.pytorch.conv import GATConv
5 |
6 |
7 | class GraphAttentionLayer(nn.Module):
8 | """
9 | Simple GAT layer, similar to https://arxiv.org/abs/1710.10903
10 | """
11 |
12 | def __init__(self, in_features, out_features, dropout, alpha=0.2, concat=True):
13 | super(GraphAttentionLayer, self).__init__()
14 | self.dropout = dropout
15 | self.in_features = in_features
16 | self.out_features = out_features
17 | self.alpha = alpha
18 | self.concat = concat
19 |
20 | self.W = nn.Parameter(torch.zeros(size=(in_features, out_features)))
21 | nn.init.xavier_uniform_(self.W.data, gain=1.414)
22 |
23 | self.a1 = nn.Parameter(torch.zeros(size=(out_features, 1)))
24 | nn.init.xavier_uniform_(self.a1.data, gain=1.414)
25 | self.a2 = nn.Parameter(torch.zeros(size=(out_features, 1)))
26 | nn.init.xavier_uniform_(self.a2.data, gain=1.414)
27 |
28 | self.leakyrelu = nn.LeakyReLU(self.alpha)
29 |
30 | def forward(self, input, adj):
31 | h = torch.mm(input, self.W)
32 | N = h.size()[0]
33 |
34 | a_input1 = torch.matmul(h, self.a1)
35 | a_input2 = torch.matmul(h, self.a2)
36 | e = self.leakyrelu(a_input1 + a_input2.transpose(-1, -2))
37 |
38 | zero_vec = -9e15 * torch.ones_like(e)
39 | attention = torch.where(adj > 0, e, zero_vec)
40 | attention = F.softmax(attention, dim=1)
41 | attention = F.dropout(attention, self.dropout, training=self.training)
42 | h_prime = torch.matmul(attention, h)
43 |
44 | if self.concat:
45 | return F.elu(h_prime)
46 | else:
47 | return h_prime
48 |
49 | def __repr__(self):
50 | return (
51 | self.__class__.__name__
52 | + " ("
53 | + str(self.in_features)
54 | + " -> "
55 | + str(self.out_features)
56 | + ")"
57 | )
58 |
59 |
60 | class SpecialSpmmFunction(torch.autograd.Function):
61 | """Special function for only sparse region backpropataion layer."""
62 | @staticmethod
63 | def forward(ctx, indices, values, shape, b):
64 | assert indices.requires_grad == False
65 | a = torch.sparse_coo_tensor(indices, values, shape)
66 | ctx.save_for_backward(a, b)
67 | ctx.N = shape[0]
68 | return torch.matmul(a, b)
69 |
70 | @staticmethod
71 | def backward(ctx, grad_output):
72 | a, b = ctx.saved_tensors
73 | grad_values = grad_b = None
74 | if ctx.needs_input_grad[1]:
75 | grad_a_dense = grad_output.matmul(b.t())
76 | edge_idx = a._indices()[0, :] * ctx.N + a._indices()[1, :]
77 | grad_values = grad_a_dense.view(-1)[edge_idx]
78 | if ctx.needs_input_grad[3]:
79 | grad_b = a.t().matmul(grad_output)
80 | return None, grad_values, None, grad_b
81 |
82 |
83 | class SpecialSpmm(nn.Module):
84 | def forward(self, indices, values, shape, b):
85 | return SpecialSpmmFunction.apply(indices, values, shape, b)
86 |
87 |
88 | class SpGraphAttentionLayer(nn.Module):
89 | """
90 | Sparse version GAT layer, similar to https://arxiv.org/abs/1710.10903
91 | """
92 |
93 | def __init__(self, in_features, out_features, dropout, alpha, concat=True):
94 | super(SpGraphAttentionLayer, self).__init__()
95 | self.in_features = in_features
96 | self.out_features = out_features
97 | self.alpha = alpha
98 | self.concat = concat
99 |
100 | self.W = nn.Parameter(torch.zeros(size=(in_features, out_features)))
101 | nn.init.xavier_normal_(self.W.data, gain=1.414)
102 |
103 | self.a = nn.Parameter(torch.zeros(size=(1, 2*out_features)))
104 | nn.init.xavier_normal_(self.a.data, gain=1.414)
105 |
106 | self.dropout = nn.Dropout(dropout)
107 | self.leakyrelu = nn.LeakyReLU(self.alpha)
108 | self.special_spmm = SpecialSpmm()
109 |
110 | def forward(self, input, adj):
111 | dv = 'cuda' if input.is_cuda else 'cpu'
112 |
113 | N = input.size()[0]
114 | edge = adj.coalesce().indices()
115 |
116 | h = torch.mm(input, self.W)
117 | # h: N x out
118 | assert not torch.isnan(h).any()
119 |
120 | # Self-attention on the nodes - Shared attention mechanism
121 | edge_h = torch.cat((h[edge[0, :], :], h[edge[1, :], :]), dim=1).t()
122 | # edge: 2*D x E
123 |
124 | edge_e = torch.exp(-self.leakyrelu(self.a.mm(edge_h).squeeze()))
125 | assert not torch.isnan(edge_e).any()
126 | # edge_e: E
127 |
128 | e_rowsum = self.special_spmm(edge, edge_e, torch.Size([N, N]), torch.ones(size=(N,1), device=dv))
129 | # e_rowsum: N x 1
130 |
131 | edge_e = self.dropout(edge_e)
132 | # edge_e: E
133 |
134 | h_prime = self.special_spmm(edge, edge_e, torch.Size([N, N]), h)
135 | assert not torch.isnan(h_prime).any()
136 | # h_prime: N x out
137 |
138 | h_prime = h_prime.div(e_rowsum)
139 | # h_prime: N x out
140 | assert not torch.isnan(h_prime).any()
141 |
142 | if self.concat:
143 | # if this layer is not last layer,
144 | return F.elu(h_prime)
145 | else:
146 | # if this layer is last layer,
147 | return h_prime
148 |
149 | def __repr__(self):
150 | return self.__class__.__name__ + ' (' + str(self.in_features) + ' -> ' + str(self.out_features) + ')'
151 |
152 |
153 | class GAT(nn.Module):
154 | def __init__(self, nfeat, nhid, nclass, dropout, alpha, nheads, sparse=False):
155 | super(GAT, self).__init__()
156 | self.dropout = dropout
157 | self.sparse = sparse
158 |
159 | # def __init__(self, in_features, out_features, dropout, alpha, concat=True):
160 |
161 | if sparse:
162 | self.attentions = [
163 | SpGraphAttentionLayer(nfeat, nhid, dropout=dropout, alpha=alpha, concat=True)
164 | for _ in range(nheads)
165 | ]
166 | for i, attention in enumerate(self.attentions):
167 | self.add_module('attention_{}'.format(i), attention)
168 |
169 | self.out_att = SpGraphAttentionLayer(
170 | nhid * nheads, nclass, dropout=dropout, alpha=alpha, concat=False
171 | )
172 | else:
173 | self.attentions = [
174 | GraphAttentionLayer(nfeat, nhid, dropout=dropout, alpha=alpha, concat=True)
175 | for _ in range(nheads)
176 | ]
177 | for i, attention in enumerate(self.attentions):
178 | self.add_module("attention_{}".format(i), attention)
179 | self.out_att = GraphAttentionLayer(
180 | nhid * nheads, nclass, dropout=dropout, alpha=alpha, concat=False
181 | )
182 |
183 | def forward(self, x, adj):
184 | x = F.dropout(x, self.dropout, training=self.training)
185 | x = torch.cat([att(x, adj) for att in self.attentions], dim=1)
186 | x = F.dropout(x, self.dropout, training=self.training)
187 | x = F.elu(self.out_att(x, adj))
188 | return x
189 |
190 |
191 | class GAT_dgl(nn.Module):
192 | def __init__(self, nfeat, nhid, nclass, dropout, alpha, nheads):
193 | super(GAT_dgl, self).__init__()
194 |
195 | self.dropout = dropout
196 | self.gat_layers = nn.ModuleList()
197 | # two-layer GAT
198 | self.gat_layers.append(
199 | GATConv(nfeat, nhid, nheads, feat_drop=dropout, attn_drop=dropout, activation=torch.nn.LeakyReLU(alpha))
200 | )
201 | self.gat_layers.append(
202 | GATConv(nhid * nheads, nclass, 1, feat_drop=dropout, attn_drop=dropout, activation=None)
203 | )
204 |
205 | def forward(self, x, adj):
206 | for i, layer in enumerate(self.gat_layers):
207 | x = layer(adj, x)
208 | if i == 1: # last layer
209 | x = x.mean(1)
210 | else: # other layer(s)
211 | x = x.flatten(1)
212 | return x
213 |
214 | def reset_parameters(self):
215 | for layer in self.gat_layers:
216 | layer.reset_parameters()
--------------------------------------------------------------------------------
/encoder/gin.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | from dgl.nn.pytorch.conv import GINConv
5 |
6 |
7 | class MLP(nn.Module):
8 | """Construct two-layer MLP-type aggreator for GIN model"""
9 |
10 | def __init__(self, input_dim, hidden_dim, output_dim):
11 | super().__init__()
12 | self.linears = nn.ModuleList()
13 | # two-layer MLP
14 | self.linears.append(nn.Linear(input_dim, hidden_dim, bias=False))
15 | self.linears.append(nn.Linear(hidden_dim, output_dim, bias=False))
16 | self.batch_norm = nn.BatchNorm1d(output_dim)
17 |
18 | def forward(self, x):
19 | h = F.relu(self.linears[0](x))
20 | h = F.relu(self.linears[1](h))
21 | h = self.batch_norm(h)
22 | return h
23 |
24 | def reset_parameters(self):
25 | for linear in self.linears:
26 | nn.init.xavier_normal_(linear.weight)
27 | self.batch_norm.reset_parameters()
28 |
29 |
30 | class GIN(nn.Module):
31 | def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
32 | super().__init__()
33 | self.ginlayers = nn.ModuleList()
34 | for layer in range(num_layers):
35 | if layer == 0:
36 | mlp = MLP(input_dim, hidden_dim, hidden_dim)
37 | elif layer == num_layers - 1:
38 | mlp = MLP(hidden_dim, hidden_dim, output_dim)
39 | else:
40 | mlp = MLP(hidden_dim, hidden_dim, hidden_dim)
41 | self.ginlayers.append(
42 | GINConv(mlp, learn_eps=True)
43 | )
44 | # self.batch_norms = nn.ModuleList()
45 | # self.batch_norms.append(nn.BatchNorm1d(hidden_dim))
46 |
47 | def reset_parameters(self):
48 | for layer in self.ginlayers:
49 | layer.apply_func.reset_parameters()
50 | layer.eps.data.fill_(0.)
51 |
52 | def forward(self, x, adj):
53 | for layer in self.ginlayers:
54 | x = layer(adj, x)
55 | # h = self.batch_norms[i](h)
56 | # x = F.relu(x)
57 | return x
58 |
--------------------------------------------------------------------------------
/encoder/gprgnn.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn.functional as F
3 | import numpy as np
4 | from torch.nn import Parameter
5 |
6 | from torch.nn import Linear
7 | import dgl.function as fn
8 |
9 |
10 | class GPR_prop(torch.nn.Module):
11 | '''
12 | propagation class for GPR_GNN
13 | '''
14 | def __init__(self, K, alpha, Init, Gamma=None, bias=True, **kwargs):
15 | super(GPR_prop, self).__init__(**kwargs)
16 | self.K = K
17 | self.Init = Init
18 | self.alpha = alpha
19 | self.Gamma = Gamma
20 |
21 | assert Init in ['SGC', 'PPR', 'NPPR', 'Random', 'WS']
22 | if Init == 'SGC':
23 | # SGC-like, note that in this case, alpha has to be a integer. It means where the peak at when initializing GPR weights.
24 | TEMP = 0.0*np.ones(K+1)
25 | TEMP[alpha] = 1.0
26 | elif Init == 'PPR':
27 | # PPR-like
28 | TEMP = alpha*(1-alpha)**np.arange(K+1)
29 | TEMP[-1] = (1-alpha)**K
30 | elif Init == 'NPPR':
31 | # Negative PPR
32 | TEMP = (alpha)**np.arange(K+1)
33 | TEMP = TEMP/np.sum(np.abs(TEMP))
34 | elif Init == 'Random':
35 | # Random
36 | bound = np.sqrt(3/(K+1))
37 | TEMP = np.random.uniform(-bound, bound, K+1)
38 | TEMP = TEMP/np.sum(np.abs(TEMP))
39 | elif Init == 'WS':
40 | # Specify Gamma
41 | TEMP = Gamma
42 |
43 | self.temp = Parameter(torch.tensor(TEMP))
44 |
45 | def reset_parameters(self):
46 | torch.nn.init.zeros_(self.temp)
47 | if self.Init == 'SGC':
48 | self.temp.data[self.alpha]= 1.0
49 | elif self.Init == 'PPR':
50 | for k in range(self.K+1):
51 | self.temp.data[k] = self.alpha*(1-self.alpha)**k
52 | self.temp.data[-1] = (1-self.alpha)**self.K
53 | elif self.Init == 'NPPR':
54 | for k in range(self.K+1):
55 | self.temp.data[k] = self.alpha**k
56 | self.temp.data = self.temp.data/torch.sum(torch.abs(self.temp.data))
57 | elif self.Init == 'Random':
58 | bound = np.sqrt(3/(self.K+1))
59 | torch.nn.init.uniform_(self.temp,-bound,bound)
60 | self.temp.data = self.temp.data/torch.sum(torch.abs(self.temp.data))
61 | elif self.Init == 'WS':
62 | self.temp.data = self.Gamma
63 |
64 | def forward(self, graph, feats):
65 | with graph.local_scope():
66 | degs = graph.in_degrees().float()
67 | norm = torch.pow(degs, -0.5)
68 | norm = norm.to(feats.device).unsqueeze(1)
69 |
70 | hidden = feats*(self.temp[0])
71 | for k in range(self.K):
72 | feats = feats * norm
73 | graph.ndata["h"] = feats
74 | graph.update_all(fn.copy_u("h", "m"), fn.sum("m", "h"))
75 | feats = graph.ndata["h"]
76 | feats = feats * norm
77 | gamma = self.temp[k+1]
78 | hidden = hidden + gamma*feats
79 |
80 | return hidden
81 |
82 | def __repr__(self):
83 | return '{}(K={}, temp={})'.format(self.__class__.__name__, self.K,
84 | self.temp)
85 |
86 |
87 | class GPRGNN(torch.nn.Module):
88 | def __init__(self, num_feat, num_class, hidden, ppnp, K, alpha, Init, Gamma, dprate, dropout):
89 | super(GPRGNN, self).__init__()
90 | self.lin1 = Linear(num_feat, hidden)
91 | self.lin2 = Linear(hidden, num_class)
92 |
93 | if ppnp == 'GPR_prop':
94 | self.prop1 = GPR_prop(K, alpha, Init, Gamma)
95 |
96 | self.Init = Init
97 | self.dprate = dprate
98 | self.dropout = dropout
99 |
100 | def reset_parameters(self):
101 | self.prop1.reset_parameters()
102 |
103 | def forward(self, graph, features):
104 |
105 | x = F.dropout(features, p=self.dropout, training=self.training)
106 | x = F.relu(self.lin1(x))
107 | x = F.dropout(x, p=self.dropout, training=self.training)
108 | x = self.lin2(x)
109 |
110 | if self.dprate == 0.0:
111 | x = self.prop1(graph, x)
112 | return F.log_softmax(x, dim=1)
113 | else:
114 | x = F.dropout(x, p=self.dprate, training=self.training)
115 | x = self.prop1(graph, x)
116 | return F.log_softmax(x, dim=1)
--------------------------------------------------------------------------------
/encoder/graphsage.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 |
6 | class GraphSAGE(nn.Module):
7 | """https://github.com/dmlc/dgl/blob/master/examples/pytorch/graphsage/train_full.py"""
8 |
9 | def __init__(
10 | self,
11 | in_feats,
12 | n_hidden,
13 | n_classes,
14 | n_layers,
15 | activation,
16 | dropout,
17 | aggregator_type,
18 | ):
19 | super(GraphSAGE, self).__init__()
20 | from dgl.nn.pytorch.conv import SAGEConv
21 |
22 | self.layers = nn.ModuleList()
23 | self.dropout = nn.Dropout(dropout)
24 | self.activation = activation
25 |
26 | # input layer
27 | self.layers.append(SAGEConv(in_feats, n_hidden, aggregator_type))
28 | # hidden layers
29 | for i in range(n_layers - 1):
30 | self.layers.append(SAGEConv(n_hidden, n_hidden, aggregator_type))
31 | # output layer
32 | self.layers.append(
33 | SAGEConv(n_hidden, n_classes, aggregator_type)
34 | ) # activation None
35 |
36 | def forward(self, graph, inputs):
37 | h = self.dropout(inputs)
38 | for l, layer in enumerate(self.layers):
39 | h = layer(graph, h)
40 | if l != len(self.layers) - 1:
41 | h = self.activation(h)
42 | h = self.dropout(h)
43 | return h
44 |
--------------------------------------------------------------------------------
/encoder/metamodule.py:
--------------------------------------------------------------------------------
1 | import torch
2 | from collections import OrderedDict
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 |
6 | class MetaModule(nn.Module):
7 | """
8 | Base class for PyTorch meta-learning modules. These modules accept an
9 | additional argument `params` in their `forward` method.
10 |
11 | Notes
12 | -----
13 | Objects inherited from `MetaModule` are fully compatible with PyTorch
14 | modules from `torch.nn.Module`. The argument `params` is a dictionary of
15 | tensors, with full support of the computation graph (for differentiation).
16 | """
17 | def meta_named_parameters(self, prefix='', recurse=True):
18 | gen = self._named_members(
19 | lambda module: module._parameters.items()
20 | if isinstance(module, MetaModule) else [],
21 | prefix=prefix, recurse=recurse)
22 | for elem in gen:
23 | yield elem
24 |
25 | def meta_parameters(self, recurse=True):
26 | for name, param in self.meta_named_parameters(recurse=recurse):
27 | yield param
28 |
29 |
30 | class MetaLinear(nn.Linear, MetaModule):
31 | __doc__ = nn.Linear.__doc__
32 |
33 | def forward(self, input, params=None):
34 | if params is None:
35 | params = OrderedDict(self.named_parameters())
36 | bias = params.get('bias', None)
37 | return F.linear(input, params['weight'], bias)
38 |
39 |
40 | import re
41 |
42 | def get_subdict(dictionary, key=None):
43 | if dictionary is None:
44 | return None
45 | if (key is None) or (key == ''):
46 | return dictionary
47 | key_re = re.compile(r'^{0}\.(.+)'.format(re.escape(key)))
48 | return OrderedDict((key_re.sub(r'\1', k), value) for (k, value)
49 | in dictionary.items() if key_re.match(k) is not None)
--------------------------------------------------------------------------------
/encoder/mlp.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 |
4 |
5 | class MLP(nn.Module):
6 | def __init__(self, n_layer, input_dim, output_dim, n_hidden, dropout=0.5, activation=torch.nn.ReLU()):
7 | super(MLP, self).__init__()
8 | self.layers = nn.ModuleList()
9 | # input layer
10 | self.layers.append(nn.Linear(input_dim, n_hidden))
11 | # hidden layers
12 | for i in range(n_layer - 2):
13 | self.layers.append(nn.Linear(n_hidden, n_hidden))
14 | # output layer
15 | self.layers.append(nn.Linear(n_hidden, output_dim))
16 | self.dropout = nn.Dropout(p=dropout)
17 | self.activation = activation
18 | return
19 |
20 | def forward(self, input):
21 | h = input
22 | for i, layer in enumerate(self.layers):
23 | if i != 0:
24 | h = self.dropout(h)
25 | # h = F.relu(layer(h))
26 | h = self.activation(layer(h))
27 | return h
--------------------------------------------------------------------------------
/eval/__init__.py:
--------------------------------------------------------------------------------
1 | from .eval_cls import ClsEvaluator
--------------------------------------------------------------------------------
/eval/eval_cls.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import copy
3 | import torch.nn.functional as F
4 | from GSL.encoder import GCN
5 | from GSL.utils import accuracy
6 |
7 |
8 | class ClsEvaluator:
9 | def __init__(self, model, config, nfeat, nclass, device):
10 | self.config = config
11 |
12 | if model == "GCN":
13 | self.model = GCN(nfeat, self.config.num_hidden_eval, nclass, self.config.num_layers_eval,
14 | self.config.dropout_eval, self.config.dropedge_rate_eval, self.config.sparse)
15 | self.model = self.model.to(device)
16 | else:
17 | raise Exception("We don't support the GNN model")
18 |
19 | def compute_loss_acc(self, model, features, adj, mask, labels):
20 | logits = model(features, adj)
21 | logp = F.log_softmax(logits, 1)
22 | loss = F.nll_loss(logp[mask], labels[mask], reduction='mean')
23 | acc = accuracy(logits[mask], labels[mask])
24 | return loss, acc
25 |
26 |
27 | def __call__(self, features, adj, train_mask, val_mask, test_mask, labels):
28 | if self.config.sparse:
29 | f_adj = copy.deepcopy(adj)
30 | f_adj.edata['w'] = adj.edata['w'].detach()
31 | else:
32 | f_adj = adj.detach()
33 | # self.model.init_para()
34 | optimizer = torch.optim.Adam(self.model.parameters(), lr=self.config.lr_cls, weight_decay=self.config.weight_decay_cls)
35 |
36 | best_val = 0
37 | best_model = None
38 |
39 | for epoch in range(1, self.config.num_epochs_cls+1):
40 | self.model.train()
41 | loss_train, _ = self.compute_loss_acc(self.model, features, f_adj, train_mask, labels)
42 |
43 | optimizer.zero_grad()
44 | loss_train.backward()
45 | optimizer.step()
46 |
47 | if epoch % 10 == 0:
48 | self.model.eval()
49 | loss_val, acc_val = self.compute_loss_acc(self.model, features, f_adj, val_mask, labels)
50 |
51 | if acc_val > best_val:
52 | best_val = acc_val
53 | best_model = copy.deepcopy(self.model)
54 |
55 | best_model.eval()
56 | _, acc_test = self.compute_loss_acc(best_model, features, f_adj, test_mask, labels)
57 |
58 | return {
59 | 'Acc_val': best_val.item(),
60 | 'Acc_test': acc_test.item()
61 | }
62 |
--------------------------------------------------------------------------------
/experiment.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pickle
3 | from contextlib import redirect_stdout
4 |
5 | import numpy as np
6 | import torch
7 | import yaml
8 | from easydict import EasyDict as edict
9 | # from hyperopt import Trials, fmin, hp, tpe
10 |
11 | from GSL.data import *
12 | from GSL.encoder import *
13 | from GSL.model import *
14 | from GSL.utils import (accuracy, get_knn_graph, macro_f1, micro_f1,
15 | random_add_edge, random_drop_edge, seed_everything,
16 | sparse_mx_to_torch_sparse_tensor, feature_mask, apply_feature_mask)
17 |
18 |
19 | class Experiment(object):
20 | def __init__(self, model_name,
21 | dataset, ntrail,
22 | data_path, config_path,
23 | sparse: bool = False,
24 | metric: str = 'acc',
25 | gpu_num: int = 0,
26 | use_knn: bool = False,
27 | k: int = 5,
28 | drop_rate: float = 0.,
29 | add_rate: float = 0.,
30 | mask_feat_rate: float = 0.,
31 | use_mettack: bool = False,
32 | ptb_rate: float = 0.05
33 | ):
34 |
35 | self.sparse = sparse
36 | self.ntrail = ntrail
37 | self.metric = metric
38 | self.config_path = config_path
39 | self.device = torch.device("cuda:"+str(gpu_num) if torch.cuda.is_available() else "cpu")
40 |
41 | self.model_name = model_name
42 | self.dataset_name = dataset.lower()
43 | self.model_dict = {
44 | 'GCN': GCN_Trainer, 'MLP': MLP_Trainer, 'GPRGNN': GPRGNN_Trainer, 'DAGNN': DAGNN_Trainer, 'GRCN': GRCN, 'ProGNN': ProGNN, 'IDGL': IDGL,
45 | 'SLAPS': SLAPS, 'SUBLIME': SUBLIME, 'STABLE': STABLE, 'GTN': GTN, 'HGSL': HGSL, 'CoGSL': CoGSL, 'GEN': GEN,
46 | 'HGPSL': HGPSL, 'VIBGSL': VIBGSL, 'HESGSL': HESGSL, 'GSR': GSR, 'NodeFormer': NodeFormer, 'HAN': HAN, 'LDS': LDS
47 | }
48 |
49 | # Load graph datasets
50 | if self.dataset_name in ['cora', 'citeseer', 'pubmed', 'ogbn-arxiv',
51 | 'cornell', 'texas', 'wisconsin', 'actor']:
52 | self.data = Dataset(root=data_path, name=self.dataset_name, use_mettack=use_mettack, ptb_rate=ptb_rate)
53 | elif self.dataset_name in ['acm', 'dblp', 'yelp', 'imdb']:
54 | self.data = HeteroDataset(root=data_path, name=self.dataset_name)
55 | elif self.dataset_name in ['imdb-b', 'imdb-m', 'collab', 'reddit-b', 'mutag',
56 | 'proteins', 'peptides-func', 'peptides-struct']:
57 | self.data = GraphDataset(root=data_path, name=self.dataset_name)
58 |
59 | if isinstance(self.data, Dataset):
60 | # Modify graph structures
61 | adj = self.data.adj
62 | features = self.data.features
63 |
64 | mask = feature_mask(features, mask_feat_rate)
65 | apply_feature_mask(features, mask)
66 |
67 | # Randomly drop edges
68 | if drop_rate > 0:
69 | adj = random_drop_edge(adj, drop_rate)
70 |
71 | # Randomly add edges
72 | if add_rate > 0:
73 | adj = random_add_edge(adj, add_rate)
74 |
75 | # Use knn graph instead of the original structure
76 | if use_knn:
77 | adj = get_knn_graph(features, k, self.dataset_name)
78 |
79 | # Sparse or notyao
80 | if not self.sparse:
81 | self.data.adj = torch.from_numpy(adj.todense()).to(torch.float)
82 | else:
83 | self.data.adj = sparse_mx_to_torch_sparse_tensor(adj)
84 |
85 | if isinstance(self.data, GraphDataset):
86 | self.data = self.data.dgl_dataset
87 | else:
88 | self.data = self.data.to(self.device)
89 |
90 | # Select the metric of evaluation
91 | self.eval_metric = {
92 | 'acc': accuracy,
93 | 'macro-f1': macro_f1,
94 | 'micro-f1': micro_f1,
95 | }[metric]
96 |
97 | def run(self, params=None):
98 | """
99 | Run the experiment
100 | """
101 | test_results = []
102 | num_feat, num_class = self.data.num_feat, self.data.num_class
103 |
104 | for i in range(self.ntrail):
105 |
106 | seed_everything(i)
107 |
108 | # Initialize the GSL model
109 | if self.model_name in ['SLAPS', 'CoGSL', 'HGSL', 'GTN', 'HAN']:
110 | model = self.model_dict[self.model_name](num_feat, num_class, self.eval_metric,
111 | self.config_path, self.dataset_name, self.device, self.data) # TODO modify the config according to the search space
112 | else:
113 | model = self.model_dict[self.model_name](num_feat, num_class, self.eval_metric,
114 | self.config_path, self.dataset_name, self.device, params)
115 | self.model = model.to(self.device)
116 |
117 |
118 | # Structure Learning
119 | self.model.fit(self.data, split_num=i)
120 |
121 | result = self.model.best_result
122 | test_results.append(result)
123 | print('Run: {} | Test result: {}'.format(i+1, result))
124 |
125 | # TODO: support multiple metrics
126 | exp_info = '------------------------------------------------------------------------------\n' \
127 | 'Experimental settings: \n' \
128 | 'Model: {} | Dataset: {} | Metric: {} \n' \
129 | 'Result: {} \n' \
130 | 'Final test result: {} +- {} \n' \
131 | '------------------------------------------------------------------------------'.\
132 | format(self.model_name, self.dataset_name, self.metric,
133 | test_results, np.mean(test_results), np.std(test_results))
134 | print(exp_info)
135 | return np.mean(test_results)
136 |
137 | def objective(self, params):
138 | with redirect_stdout(open(os.devnull, "w")):
139 | return {'loss': -self.run(params), 'status': 'ok'}
140 |
141 | def hp_search(self, hyperspace_path):
142 | with open(hyperspace_path, "r") as fin:
143 | raw_text = fin.read()
144 | raw_space = edict(yaml.safe_load(raw_text))
145 |
146 | space_hyperopt = {}
147 | for key, config in raw_space.items():
148 | if config.type == 'choice':
149 | space_hyperopt[key] = hp.choice(key, config.values)
150 |
151 | elif config.type == 'uniform':
152 | space_hyperopt[key] = hp.uniform(key, config.bounds[0], config.bounds[1])
153 |
154 | # 可以扩展其他类型
155 | print(space_hyperopt)
156 | trials = Trials()
157 | best = fmin(self.objective, space_hyperopt, algo=tpe.suggest, max_evals=100, trials=trials)
158 | print(trials.best_trial)
159 |
160 | with open('trails.pkl', 'wb') as f:
161 | pickle.dump(trials, f)
162 |
--------------------------------------------------------------------------------
/generate_attack.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import numpy as np
3 | import torch.nn.functional as F
4 | import torch.optim as optim
5 | from deeprobust.graph.defense import GCN
6 | from deeprobust.graph.global_attack import MetaApprox, Metattack
7 | from deeprobust.graph.utils import *
8 | from deeprobust.graph.data import Dataset
9 | import argparse
10 | import time
11 | import os.path as osp
12 |
13 | parser = argparse.ArgumentParser()
14 | parser.add_argument('--no_cuda', action='store_true',
15 | help='Disables CUDA training.')
16 | parser.add_argument('--gpu_num', type=int, default=0,
17 | help='The selected GPU number.')
18 | parser.add_argument('--seed', type=int, default=int(time.time()), help='Random seed.')
19 | parser.add_argument('--epochs', type=int, default=200,
20 | help='Number of epochs to train.')
21 | parser.add_argument('--lr', type=float, default=0.01,
22 | help='Initial learning rate.')
23 | parser.add_argument('--weight_decay', type=float, default=5e-4,
24 | help='Weight decay (L2 loss on parameters).')
25 | parser.add_argument('--hidden', type=int, default=16,
26 | help='Number of hidden units.')
27 | parser.add_argument('--dropout', type=float, default=0.5,
28 | help='Dropout rate (1 - keep probability).')
29 | parser.add_argument('--dataset', type=str, default='cora', choices=['cora', 'citeseer', 'polblogs'], help='dataset')
30 | parser.add_argument('--ptb_rate', type=float, default=0.05, help='pertubation rate')
31 | parser.add_argument('--model', type=str, default='Meta-Self',
32 | choices=['Meta-Self', 'A-Meta-Self', 'Meta-Train', 'A-Meta-Train'], help='model variant')
33 |
34 | args = parser.parse_args()
35 |
36 | device = torch.device("cuda:{}".format(args.gpu_num) if not args.no_cuda else "cpu")
37 |
38 | np.random.seed(args.seed)
39 | torch.manual_seed(args.seed)
40 | if device != 'cpu':
41 | torch.cuda.manual_seed(args.seed)
42 |
43 | data_path = osp.join(osp.expanduser('~'), 'datasets')
44 |
45 | data = Dataset(root='/tmp/', name=args.dataset, setting='nettack')
46 | adj, features, labels = data.adj, data.features, data.labels
47 | idx_train, idx_val, idx_test = data.idx_train, data.idx_val, data.idx_test
48 | idx_unlabeled = np.union1d(idx_val, idx_test)
49 |
50 | perturbations = int(args.ptb_rate * (adj.sum()//2))
51 | adj, features, labels = preprocess(adj, features, labels, preprocess_adj=False)
52 |
53 | # Setup Surrogate Model
54 | surrogate = GCN(nfeat=features.shape[1], nclass=labels.max().item()+1, nhid=16,
55 | dropout=0.5, with_relu=False, with_bias=True, weight_decay=5e-4, device=device)
56 |
57 | surrogate = surrogate.to(device)
58 | surrogate.fit(features, adj, labels, idx_train)
59 |
60 | # Setup Attack Model
61 | if 'Self' in args.model:
62 | lambda_ = 0
63 | if 'Train' in args.model:
64 | lambda_ = 1
65 | if 'Both' in args.model:
66 | lambda_ = 0.5
67 |
68 | if 'A' in args.model:
69 | model = MetaApprox(model=surrogate, nnodes=adj.shape[0], feature_shape=features.shape, attack_structure=True, attack_features=False, device=device, lambda_=lambda_)
70 |
71 | else:
72 | model = Metattack(model=surrogate, nnodes=adj.shape[0], feature_shape=features.shape, attack_structure=True, attack_features=False, device=device, lambda_=lambda_)
73 |
74 | model = model.to(device)
75 |
76 | def test(adj):
77 | ''' test on GCN '''
78 |
79 | # adj = normalize_adj_tensor(adj)
80 | gcn = GCN(nfeat=features.shape[1],
81 | nhid=args.hidden,
82 | nclass=labels.max().item() + 1,
83 | dropout=args.dropout, device=device)
84 | gcn = gcn.to(device)
85 | # gcn.fit(features, adj, labels, idx_train) # train without model picking
86 | gcn.fit(features, adj, labels, idx_train, idx_val) # train with validation model picking
87 | output = gcn.output.cpu()
88 | loss_test = F.nll_loss(output[idx_test], labels[idx_test])
89 | acc_test = accuracy(output[idx_test], labels[idx_test])
90 | print("Test set results:",
91 | "loss= {:.4f}".format(loss_test.item()),
92 | "accuracy= {:.4f}".format(acc_test.item()))
93 |
94 | return acc_test.item()
95 |
96 | def main():
97 | model.attack(features, adj, labels, idx_train, idx_unlabeled, perturbations, ll_constraint=False)
98 | print('=== testing GCN on modified graph ===')
99 | modified_adj = model.modified_adj
100 | torch.save(modified_adj.cpu().to_sparse(), osp.join(data_path, "%s_%s_%s.pt" % ('mettack', args.dataset, args.ptb_rate)))
101 | np.save(osp.join(data_path, "%s_%s_%s_idx_train.npy" % ('mettack', args.dataset, args.ptb_rate)), idx_train)
102 | np.save(osp.join(data_path, "%s_%s_%s_idx_val.npy" % ('mettack', args.dataset, args.ptb_rate)), idx_val)
103 | np.save(osp.join(data_path, "%s_%s_%s_idx_test.npy" % ('mettack', args.dataset, args.ptb_rate)), idx_test)
104 |
105 | # test(modified_adj)
106 |
107 |
108 | if __name__ == '__main__':
109 | main()
110 |
--------------------------------------------------------------------------------
/hyperparams/idgl_hyper.yaml:
--------------------------------------------------------------------------------
1 | learning_rate:
2 | type: choice
3 | values: [1.e-4, 1.e-3, 1.e-2]
4 |
5 | graph_skip_conn:
6 | type: uniform
7 | bounds: [0.5, 0.9]
8 |
--------------------------------------------------------------------------------
/learner/__init__.py:
--------------------------------------------------------------------------------
1 | from .base_learner import BaseLearner
2 | from .attention_learner import AttLearner
3 | from .full_param_learner import FullParam
4 | from .gnn_learner import GNNLearner
5 | from .hadamard_prod_learner import KHeadHPLearner
6 | from .mlp_learner import MLPLearner
7 | from .sparsification_learner import SpLearner
8 |
9 | __all__ = [
10 | "BaseLearner",
11 | "FullParam",
12 | "AttLearner",
13 | "MLPLearner",
14 | "KHeadHPLearner",
15 | "GNNLearner",
16 | "SpLearner"
17 | ]
18 |
19 | classes = __all__
20 |
--------------------------------------------------------------------------------
/learner/attention_learner.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import dgl
4 |
5 | from GSL.learner import BaseLearner
6 | from GSL.metric import *
7 | from GSL.processor import *
8 | from GSL.utils import knn_fast
9 |
10 |
11 | class Attentive(nn.Module):
12 | def __init__(self, size):
13 | super(Attentive, self).__init__()
14 | self.w = nn.Parameter(torch.ones(size))
15 |
16 | def forward(self, x):
17 | return x @ torch.diag(self.w)
18 |
19 |
20 | class AttLearner(BaseLearner):
21 | """Attentive Learner"""
22 | def __init__(self, metric, processors, nlayers, size, activation, sparse):
23 | """
24 | nlayers: int
25 | Number of attention layers
26 | act: str
27 | Activation Function
28 | """
29 | super(AttLearner, self).__init__(metric, processors)
30 | self.nlayers = nlayers
31 | self.layers = nn.ModuleList()
32 | for _ in range(self.nlayers):
33 | self.layers.append(Attentive(size))
34 | self.activation = activation
35 | self.sparse = sparse
36 |
37 | def internal_forward(self, x):
38 | for i, layer in enumerate(self.layers):
39 | x = layer(x)
40 | if i != (self.nlayers - 1):
41 | x = self.activation(x)
42 | return x
43 |
44 | def forward(self, features):
45 | if self.sparse:
46 | z = self.internal_forward(features)
47 | # TODO: knn should be moved to processor
48 | rows, cols, values = knn_fast(z, self.k, 1000)
49 | rows_ = torch.cat((rows, cols))
50 | cols_ = torch.cat((cols, rows))
51 | values_ = torch.cat((values, values))
52 | values_ = self.activation(values_)
53 | adj = dgl.graph((rows_, cols_), num_nodes=features.shape[0], device='cuda')
54 | adj.edata['w'] = values_
55 | return adj
56 | else:
57 | z = self.internal_forward(features)
58 | z = F.normalize(z, dim=1, p=2)
59 | similarities = self.metric(z)
60 | for processor in self.processors:
61 | similarities = processor(similarities)
62 | similarities = F.relu(similarities)
63 | return similarities
64 |
65 |
66 | if __name__ == '__main__':
67 | metric = CosineSimilarity()
68 | processors = [KNNSparsify(3), NonLinearize(non_linearity='relu'), Symmetrize(), Normalize(mode='sym')]
69 | activation = nn.ReLU
70 | att_learner = AttLearner(metric, processors, 1, 16, activation)
71 | x = torch.rand(8, 16)
72 | adj = att_learner(x)
73 | print(adj)
--------------------------------------------------------------------------------
/learner/base_learner.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 |
4 | class BaseLearner(nn.Module):
5 | """Abstract base class for graph learner"""
6 | def __init__(self, metric, processors):
7 | super(BaseLearner, self).__init__()
8 |
9 | self.metric = metric
10 | self.processors = processors
--------------------------------------------------------------------------------
/learner/full_param_learner.py:
--------------------------------------------------------------------------------
1 | import torch.nn as nn
2 | import torch.nn.functional as F
3 |
4 | from GSL.learner import BaseLearner
5 |
6 |
7 | class FullParam(BaseLearner):
8 | """Full graph parameterization learner"""
9 |
10 | def __init__(
11 | self,
12 | metric=None,
13 | processors=None,
14 | features=None,
15 | sparse=False,
16 | non_linearize=None,
17 | adj=None,
18 | ):
19 | super(FullParam, self).__init__(metric, processors)
20 | self.sparse = sparse
21 | self.non_linearize = non_linearize
22 | if adj is None:
23 | adj = self.metric(features)
24 | if processors:
25 | for processor in self.processors:
26 | adj = processor(adj)
27 | self.adj = nn.Parameter(adj)
28 |
29 | def forward(self, h):
30 | # note: non_linearize should be conducted in every forward
31 | adj = self.non_linearize(self.adj)
32 | return adj
33 |
--------------------------------------------------------------------------------
/learner/gnn_learner.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import dgl
3 | import os.path as osp
4 | import torch.nn as nn
5 | import torch.nn.functional as F
6 | from GSL.encoder import GCNConv_dgl, GCNConv
7 | from GSL.learner import BaseLearner
8 | from GSL.utils import knn_fast, top_k
9 | from GSL.metric import *
10 | from GSL.processor import *
11 | from GSL.data import Dataset
12 |
13 | class GNNLearner(BaseLearner):
14 | def __init__(self, metric, processors, adj, nlayers, num_feature, hidden,
15 | activation, sparse, k, base_model='GCN', bias=False):
16 | super(GNNLearner, self).__init__(metric, processors)
17 |
18 | self.nlayers = nlayers
19 | if sparse:
20 | self.base_model = {'GCN': GCNConv_dgl}[base_model]
21 | self.layers = nn.ModuleList()
22 | if nlayers == 1:
23 | self.layers.append(self.base_model(num_feature, hidden))
24 | else:
25 | self.layers.append(self.base_model(num_feature, hidden))
26 | for _ in range(nlayers - 2):
27 | self.layers.append(self.base_model(hidden, hidden))
28 | self.layers.append(self.base_model(hidden, hidden))
29 | else:
30 | self.base_model = {'GCN': GCNConv}[base_model]
31 | self.layers = nn.ModuleList()
32 | if nlayers == 1:
33 | self.layers.append(self.base_model(num_feature, hidden, bias=bias, activation=activation))
34 | else:
35 | self.layers.append(self.base_model(num_feature, hidden, bias=bias, activation=activation))
36 | for _ in range(nlayers - 2):
37 | self.layers.append(self.base_model(hidden, hidden, bias=bias, activation=activation))
38 | self.layers.append(self.base_model(hidden, hidden, bias=bias, activation=activation))
39 |
40 | self.activation = activation
41 | self.dim = hidden
42 | self.sparse = sparse
43 | self.k = k
44 | self.adj = adj
45 | self.param_init()
46 |
47 | def param_init(self):
48 | for layer in self.layers:
49 | layer.weight = nn.Parameter(torch.eye(self.dim))
50 |
51 | def internal_forward(self, h):
52 | for i, layer in enumerate(self.layers):
53 | if self.sparse:
54 | h = layer(self.adj, h)
55 | else:
56 | h = layer(h, self.adj)
57 | if i != (len(self.layers) - 1):
58 | h = self.activation(h)
59 | return h
60 |
61 | def forward(self, features, v_indices=None):
62 | # TODO: knn sparsify should be a post-processor
63 | if self.sparse:
64 | embeddings = self.internal_forward(features)
65 | rows, cols, values = knn_fast(embeddings, self.k, 1000)
66 | rows_ = torch.cat((rows, cols))
67 | cols_ = torch.cat((cols, rows))
68 | values_ = torch.cat((values, values))
69 | values_ = self.processors(values_)
70 | adj = dgl.graph((rows_, cols_), num_nodes=features.shape[0], device='cuda')
71 | adj.edata['w'] = values_
72 | return adj
73 | else:
74 | embeddings = self.internal_forward(features)
75 | # embeddings = F.normalize(embeddings, dim=1, p=2)
76 | if v_indices is not None:
77 | similarities = self.metric(embeddings, v_indices)
78 | else:
79 | similarities = self.metric(embeddings)
80 | for processor in self.processors:
81 | similarities = processor(similarities)
82 | return similarities
83 |
84 |
85 | if __name__ == "__main__":
86 | data_path = osp.join(osp.expanduser('~'), 'datasets')
87 | data = Dataset(root=data_path, name='cora')
88 | adj = data.adj
89 |
90 | metric = CosineSimilarity()
91 | processors = [NonLinearize(non_linearity='relu', alpha=1)]
92 | graph_learner = GNNLearner(metric, processors, adj, 2, 32, F.relu, False, 30, 'GCN')
93 |
--------------------------------------------------------------------------------
/learner/hadamard_prod_learner.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | from GSL.utils import knn_fast
4 | import torch.nn.functional as F
5 | import dgl
6 | from GSL.learner import BaseLearner
7 |
8 |
9 | class HadamardProduct(nn.Module):
10 | def __init__(self, dim):
11 | super(HadamardProduct, self).__init__()
12 | self.weight = nn.Parameter(torch.FloatTensor(1, dim))
13 | nn.init.xavier_uniform_(self.weight)
14 |
15 | def forward(self, h):
16 | return h * self.weight
17 |
18 |
19 | class KHeadHPLearner(BaseLearner):
20 | def __init__(self, metric, processors, dim, num_heads, sparse=False):
21 | super().__init__(metric=metric, processors=processors)
22 |
23 | self.dim = dim
24 | self.num_heads = num_heads
25 | self.sparse = sparse
26 |
27 | self.hp_heads = nn.ModuleList()
28 | for i in range(num_heads):
29 | self.hp_heads.append(HadamardProduct(dim))
30 |
31 | def internal_forward(self, x):
32 | for i, layer in enumerate(self.layers):
33 | x = layer(x)
34 | if i != self.nlayers - 1:
35 | x = self.activation(x)
36 | return x
37 |
38 | def forward(self, left_features, right_feature=None):
39 | """
40 | Note: The right_feature is used to computer a non-square adjacent matrix, such as relation subgraph
41 | of heterogeneous graph.
42 | """
43 | if self.sparse:
44 | raise NameError('We dont support the sparse version yet')
45 | else:
46 | if torch.sum(left_features) == 0 or torch.sum(right_feature) == 0:
47 | return torch.zeros((left_features.shape[0], right_feature.shape[0]))
48 | s = torch.zeros((left_features.shape[0], right_feature.shape[0])).to(left_features.device)
49 | zero_lines = torch.nonzero(torch.sum(left_features, 1) == 0)
50 | # The ReLU function will generate zero lines, which lead to the nan (devided by zero) problem.
51 | if len(zero_lines) > 0:
52 | left_features[zero_lines, :] += 1e-8
53 |
54 | # metric
55 | for i in range(self.num_heads):
56 | weighted_left_h = self.hp_heads[i](left_features)
57 | weighted_right_h = self.hp_heads[i](right_feature)
58 | s += self.metric(weighted_left_h, weighted_right_h)
59 | s /= self.num_heads
60 |
61 | # processor
62 | for processor in self.processors:
63 | s = processor(s)
64 |
65 | return s
--------------------------------------------------------------------------------
/learner/mlp_learner.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | from GSL.utils import knn_fast
4 | import torch.nn.functional as F
5 | import dgl
6 |
7 | from GSL.learner import BaseLearner
8 |
9 | class MLPLearner(BaseLearner):
10 | """Multi-Layer Perception learner"""
11 | def __init__(self, metric, processors, nlayers, in_dim, hidden_dim, activation, sparse, k=None):
12 | super().__init__(metric=metric, processors=processors)
13 |
14 | self.nlayers = nlayers
15 | self.layers = nn.ModuleList()
16 | self.layers.append(nn.Linear(in_dim, hidden_dim))
17 | for _ in range(1, nlayers):
18 | self.layers.append(nn.Linear(hidden_dim, hidden_dim))
19 |
20 | self.in_dim = in_dim
21 | self.hidden_dim = hidden_dim
22 | self.param_init()
23 | self.activation = activation
24 | self.sparse = sparse
25 | self.k = k
26 |
27 | def param_init(self):
28 | for layer in self.layers:
29 | # layer.reset_parameters()
30 | layer.weight = nn.Parameter(torch.eye(self.in_dim))
31 |
32 | def internal_forward(self, x):
33 | for i, layer in enumerate(self.layers):
34 | x = layer(x)
35 | if i != self.nlayers - 1:
36 | x = self.activation(x)
37 | return x
38 |
39 | def forward(self, features):
40 | if self.sparse:
41 | z = self.internal_forward(features)
42 | # TODO: knn should be moved to processor
43 | rows, cols, values = knn_fast(z, self.k, 1000)
44 | rows_ = torch.cat((rows, cols))
45 | cols_ = torch.cat((cols, rows))
46 | values_ = torch.cat((values, values))
47 | values_ = self.activation(values_)
48 | adj = dgl.graph((rows_, cols_), num_nodes=features.shape[0], device=features.device)
49 | adj.edata['w'] = values_
50 | return adj
51 | else:
52 | z = self.internal_forward(features)
53 | z = F.normalize(z, dim=1, p=2)
54 | similarities = self.metric(z)
55 | for processor in self.processors:
56 | similarities = processor(similarities)
57 | similarities = F.relu(similarities)
58 | return similarities
--------------------------------------------------------------------------------
/learner/sparsification_learner.py:
--------------------------------------------------------------------------------
1 | from GSL.learner import BaseLearner
2 | import torch
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 |
6 | eps = 1e-8
7 |
8 |
9 | class SpLearner(BaseLearner):
10 | """Sparsification learner"""
11 | def __init__(self, nlayers, in_dim, hidden, activation, k, weight=True, metric=None, processors=None):
12 | super().__init__(metric=metric, processors=processors)
13 |
14 | self.nlayers = nlayers
15 | self.layers = nn.ModuleList()
16 | self.layers.append(nn.Linear(in_dim, hidden))
17 | for _ in range(nlayers - 2):
18 | self.layers.append(nn.Linear(hidden, hidden))
19 | self.layers.append(nn.Linear(hidden, 1))
20 |
21 | self.param_init()
22 | self.activation = activation
23 | self.k = k
24 | self.weight = weight
25 |
26 | def param_init(self):
27 | for layer in self.layers:
28 | nn.init.xavier_uniform_(layer.weight)
29 |
30 | def internal_forward(self, x):
31 | for i, layer in enumerate(self.layers):
32 | x = layer(x)
33 | if i != self.nlayers - 1:
34 | x = self.activation(x)
35 | return x
36 |
37 | def gumbel_softmax_sample(self, logits, temperature, adj, training):
38 | """Draw a sample from the Gumbel-Softmax distribution"""
39 | r = self.sample_gumble(logits.coalesce().values().shape)
40 | if training is not None:
41 | values = torch.log(logits.coalesce().values()) + r.to(logits.device)
42 | else:
43 | values = torch.log(logits.coalesce().values())
44 | values /= temperature
45 | y = torch.sparse_coo_tensor(indices=adj.coalesce().indices(), values=values, size=adj.shape)
46 | return torch.sparse.softmax(y, dim=1)
47 |
48 | def sample_gumble(self, shape):
49 | """Sample from Gumbel(0, 1)"""
50 | U = torch.rand(shape)
51 | return -torch.log(-torch.log(U + eps) + eps)
52 |
53 | def forward(self, features, adj, temperature, training=None):
54 | indices = adj.coalesce().indices().t()
55 | values = adj.coalesce().values()
56 |
57 | f1_features = torch.index_select(features, 0, indices[:, 0])
58 | f2_features = torch.index_select(features, 0, indices[:, 1])
59 | auv = torch.unsqueeze(values, -1)
60 | temp = torch.cat([f1_features, f2_features, auv], -1)
61 |
62 | temp = self.internal_forward(temp)
63 | z = torch.reshape(temp, [-1])
64 |
65 | z_matrix = torch.sparse_coo_tensor(indices=indices.t(), values=z, size=adj.shape)
66 | pi = torch.sparse.softmax(z_matrix, dim=1)
67 |
68 | y = self.gumbel_softmax_sample(pi, temperature, adj, training)
69 | y_dense = y.to_dense()
70 |
71 | top_k_v, top_k_i = torch.topk(y_dense, self.k)
72 | kth = torch.min(top_k_v, -1)[0] + eps
73 | kth = kth.unsqueeze(-1)
74 | kth = torch.tile(kth, [1, kth.shape[0]])
75 | mask2 = y_dense >= kth
76 | mask2 = mask2.to(torch.float32)
77 | row_sum = mask2.sum(-1)
78 |
79 | dense_support = mask2
80 |
81 | if self.weight:
82 | dense_support = torch.mul(y_dense, mask2)
83 | else:
84 | print('no gradient bug here!')
85 | exit()
86 |
87 | # norm
88 | dense_support = dense_support + torch.eye(adj.shape[0]).to(dense_support.device)
89 |
90 | rowsum = torch.sum(dense_support, -1) + 1e-6
91 | d_inv_sqrt = torch.reshape(torch.pow(rowsum, -0.5), [-1])
92 | d_mat_inv_sqer = torch.diag(d_inv_sqrt)
93 | ad = torch.matmul(dense_support, d_mat_inv_sqer)
94 | adt = ad.t()
95 | dadt = torch.matmul(adt, d_mat_inv_sqer)
96 | support = dadt
97 |
98 | return support
--------------------------------------------------------------------------------
/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSL-Benchmark/GSLB/375cc1c20314360a927527f8d8528773fe1b0abc/logo.jpeg
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os.path as osp
3 |
4 | from GSL.experiment import Experiment
5 |
6 | if __name__ == '__main__':
7 |
8 | parser = argparse.ArgumentParser()
9 | parser.add_argument('--dataset', type=str, default='cora',
10 | help='the name of graph dataset')
11 | parser.add_argument('--model', type=str, default='GRCN',
12 | help='the name of GSL model')
13 | parser.add_argument('--ntrail', type=int, default=1,
14 | help='repetition count of experiments')
15 | parser.add_argument('--use_knn', action='store_true',
16 | help='whether to use knn graph instead of the original structure')
17 | parser.add_argument('--k', type=int, default=5, help='the number of nearest neighbors')
18 | parser.add_argument('--drop_rate', type=float, default=0.,
19 | help='the probability of randomly drop edges')
20 | parser.add_argument('--add_rate', type=float, default=0.,
21 | help='the probability of randomly add edges')
22 | parser.add_argument('--mask_feat_rate', type=float, default=0.,
23 | help='the probability of randomly mask features')
24 | parser.add_argument('--use_mettack', action='store_true',
25 | help='whether to use the structure after being attacked by mettack')
26 | parser.add_argument('--ptb_rate', type=float, default=0.,
27 | help='the perturbation rate')
28 | parser.add_argument('--metric', type=str, default='acc',
29 | help='the evaluation metric')
30 | parser.add_argument('--sparse', action='store_true',
31 | help='whether to use sparse version')
32 | parser.add_argument('--gpu_num', type=int, default=0,
33 | help='the selected GPU number')
34 | args = parser.parse_args()
35 |
36 | data_path = osp.join(osp.expanduser('~'), 'datasets')
37 | config_path = './configs/{}_config.yaml'.format(args.model.lower())
38 |
39 | exp = Experiment(model_name=args.model, dataset=args.dataset, ntrail=args.ntrail,
40 | data_path=data_path, config_path=config_path, metric=args.metric, sparse=args.sparse,
41 | use_knn=args.use_knn, k=args.k, drop_rate=args.drop_rate, gpu_num=args.gpu_num,
42 | add_rate=args.add_rate, use_mettack=args.use_mettack, ptb_rate=args.ptb_rate, mask_feat_rate=args.mask_feat_rate)
43 | exp.run()
44 | # exp.hp_search("hyperparams/idgl_hyper.yaml")
--------------------------------------------------------------------------------
/metric.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 |
6 | def cal_similarity_graph(node_embeddings, right_node_embedding=None):
7 | if right_node_embedding is None:
8 | similarity_graph = torch.mm(node_embeddings, node_embeddings.t())
9 | else:
10 | similarity_graph = torch.mm(node_embeddings, right_node_embedding.t())
11 | return similarity_graph
12 |
13 |
14 | class InnerProductSimilarity:
15 | def __init__(self):
16 | super(InnerProductSimilarity, self).__init__()
17 |
18 | def __call__(self, embeddings, right_embeddings=None):
19 | similarities = cal_similarity_graph(embeddings, right_embeddings)
20 | return similarities
21 |
22 |
23 | class CosineSimilarity:
24 | def __init__(self):
25 | super(CosineSimilarity, self).__init__()
26 |
27 | def __call__(self, embeddings, right_embeddings=None):
28 | if right_embeddings is None:
29 | embeddings = F.normalize(embeddings, dim=1, p=2)
30 | similarities = cal_similarity_graph(embeddings)
31 | else:
32 | embeddings = F.normalize(embeddings, dim=1, p=2)
33 | right_embeddings = F.normalize(right_embeddings, dim=1, p=2)
34 | similarities = cal_similarity_graph(embeddings, right_embeddings)
35 | return similarities
36 |
37 |
38 | class WeightedCosine(nn.Module):
39 | def __init__(self, input_size, num_pers):
40 | super().__init__()
41 | self.weight_tensor = torch.Tensor(num_pers, input_size)
42 | self.weight_tensor = nn.Parameter(nn.init.xavier_uniform_(self.weight_tensor))
43 |
44 | def __call__(self, embeddings):
45 | expand_weight_tensor = self.weight_tensor.unsqueeze(1)
46 | if len(embeddings.shape) == 3:
47 | expand_weight_tensor = expand_weight_tensor.unsqueeze(1)
48 |
49 | embeddings_fc = embeddings.unsqueeze(0) * expand_weight_tensor
50 | embeddings_norm = F.normalize(embeddings_fc, p=2, dim=-1)
51 | attention = torch.matmul(
52 | embeddings_norm, embeddings_norm.transpose(-1, -2)
53 | ).mean(0)
54 | return attention
55 |
56 |
57 | class MLPRefineSimilarity(nn.Module):
58 | def __init__(self, hid, dropout):
59 | super(MLPRefineSimilarity, self).__init__()
60 | self.gen_mlp = nn.Linear(2 * hid, 1)
61 | self.dropout = nn.Dropout(dropout)
62 |
63 | def __call__(self, embeddings, v_indices):
64 | num_node = embeddings.shape[0]
65 | f1 = embeddings[v_indices[0]]
66 | f2 = embeddings[v_indices[1]]
67 | ff = torch.cat([f1, f2], dim=-1)
68 | temp = self.gen_mlp(self.dropout(ff)).reshape(-1)
69 | z_matrix = torch.sparse.FloatTensor(v_indices, temp, (num_node, num_node)).to_dense()
70 | return z_matrix
71 |
72 | # class Minkowski:
73 |
--------------------------------------------------------------------------------
/model/__init__.py:
--------------------------------------------------------------------------------
1 | from .base_gsl import BaseModel
2 | from .gen import GEN
3 | from .idgl import IDGL
4 | from .prognn import ProGNN
5 | from .ptdnet import PTDNet
6 | from .slaps import SLAPS
7 | from .stable import STABLE
8 | from .sublime import SUBLIME
9 | from .vibgsl import VIBGSL
10 | from .cogsl import CoGSL
11 | from .neuralsparse import NeuralSparse
12 | from .grcn import GRCN
13 | from .hgpsl import HGPSL
14 | from .gtn import GTN
15 | from .hgsl import HGSL
16 | from .hesgsl import HESGSL
17 | from .baselines import GCN_Trainer, MLP_Trainer, GPRGNN_Trainer, DAGNN_Trainer
18 | from .gsr import GSR
19 | from .nodeformer import NodeFormer
20 | from .han import HAN
21 | from .lds import LDS
22 | from .lds import LDS
23 |
24 | __all__ = [
25 | 'BaseModel',
26 | 'IDGL',
27 | 'SLAPS',
28 | 'SUBLIME',
29 | 'STABLE',
30 | 'VIBGSL',
31 | 'PTDNet',
32 | 'GEN',
33 | 'ProGNN',
34 | 'CoGSL',
35 | 'NeuralSparse',
36 | 'GRCN',
37 | 'HGPSL',
38 | 'GTN',
39 | 'HGSL',
40 | 'HESGSL',
41 | 'GCN_Trainer',
42 | 'MLP_Trainer',
43 | 'GPRGNN_Trainer',
44 | 'DAGNN_Trainer',
45 | 'GSR',
46 | 'NodeFormer',
47 | 'HAN',
48 | 'LDS'
49 | ]
50 |
--------------------------------------------------------------------------------
/model/base_gsl.py:
--------------------------------------------------------------------------------
1 | import easydict
2 | import numpy as np
3 | import torch.nn as nn
4 | import yaml
5 |
6 | from GSL.learner import *
7 |
8 |
9 | class BaseModel(nn.Module):
10 | '''
11 | Abstract base class for graph structure learning models.
12 | '''
13 | def __init__(self, num_feat, num_class, metric, config_path, dataset, device, params=None):
14 | super(BaseModel, self).__init__()
15 | self.num_feat = num_feat
16 | self.num_class = num_class
17 | self.metric = metric
18 | self.device = device
19 | self.load_config(config_path, dataset, params)
20 | self.config.device = device
21 |
22 | def check_adj(self, adj):
23 | """
24 | Check if the modified adjacency is symmetric and unweighted.
25 | """
26 | assert np.abs(adj - adj.T).sum() == 0, "Input graph is not symmetric"
27 | assert adj.tocsr().max() == 1, "Max value should be 1!"
28 | assert adj.tocsr().min() == 0, "Min value should be 0!"
29 |
30 | def load_config(self, config_path, dataset, params):
31 | """
32 | Load the hyper-parameters required for the models.
33 | """
34 | with open(config_path, "r") as fin:
35 | raw_text = fin.read()
36 | configs = [easydict.EasyDict(yaml.safe_load(raw_text))]
37 |
38 | all_config = configs[0]
39 | config = all_config.Default
40 | dataset_config = all_config.get(dataset, None)
41 | if dataset_config is not None:
42 | config.update(dataset_config)
43 | self.config = config
44 | self.config.dataset = dataset
45 | self.load_hypers(params)
46 |
47 | def load_hypers(self, params):
48 | if params is None:
49 | return
50 | for key, value in params.items():
51 | self.config[key] = value
52 |
--------------------------------------------------------------------------------
/model/gen.py:
--------------------------------------------------------------------------------
1 | import time
2 | from collections import Counter
3 | from copy import deepcopy
4 |
5 | import numpy as np
6 | import torch
7 | import torch.nn.functional as F
8 | import torch.optim as optim
9 | from sklearn.metrics.pairwise import cosine_similarity as cos
10 |
11 | from GSL.encoder import GCN
12 | from GSL.metric import CosineSimilarity
13 | from GSL.model import BaseModel
14 | from GSL.processor import KNNSparsify
15 | from GSL.utils import accuracy, get_homophily, prob_to_adj
16 |
17 |
18 | class GEN(BaseModel):
19 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device):
20 | super(GEN, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
21 | # self.knn = KNNGraphFromFeature(
22 | # k=self.config["k"], metric="minkowski", i=self.config["i"]
23 | # )
24 | self.config.update({"num_feat": num_features, "num_class": num_classes})
25 | self.base_model = GCN(
26 | self.config.num_feat,
27 | self.config.hidden,
28 | self.config.num_class,
29 | num_layers=2,
30 | dropout=self.config.dropout,
31 | dropout_adj=0.0,
32 | sparse=False,
33 | activation_last="log_softmax",
34 | )
35 | self.best_acc_val = 0.0
36 | self.iter = 0
37 | self.k = self.config.k
38 |
39 | def knn(self, feature):
40 | adj = np.zeros((self.num_node, self.num_node), dtype=np.int64)
41 | dist = cos(feature.detach().cpu().numpy())
42 | col = np.argpartition(dist, -(self.config.k + 1), axis=1)[:,-(self.config.k + 1):].flatten()
43 | adj[np.arange(self.num_node).repeat(self.config.k + 1), col] = 1
44 | return adj
45 | # metric = CosineSimilarity()
46 | # processor = KNNSparsify(self.k)
47 | # dist = metric(feature)
48 | # adj_float = processor(dist).detach().cpu().numpy()
49 | # return (adj_float > 0).astype(np.int64)
50 |
51 | def fit(self, dataset, split_num=0):
52 | adj, features, labels = dataset.adj.clone(), dataset.features.clone(), dataset.labels
53 | if dataset.name in ['cornell', 'texas', 'wisconsin', 'actor']:
54 | train_mask = dataset.train_masks[split_num % 10]
55 | val_mask = dataset.val_masks[split_num % 10]
56 | test_mask = dataset.test_masks[split_num % 10]
57 | else:
58 | train_mask, val_mask, test_mask = dataset.train_mask, dataset.val_mask, dataset.test_mask
59 |
60 | if adj.is_sparse:
61 | indices = adj.coalesce().indices()
62 | values = adj.coalesce().values()
63 | shape = adj.coalesce().shape
64 | num_nodes = features.shape[0]
65 | loop_edge_index = torch.stack([torch.arange(num_nodes), torch.arange(num_nodes)]).to(adj.device)
66 | loop_edge_index = torch.cat([indices, loop_edge_index], dim=1)
67 | loop_values = torch.ones(num_nodes).to(adj.device)
68 | loop_values = torch.cat([values, loop_values], dim=0)
69 | adj = torch.sparse_coo_tensor(indices=loop_edge_index, values=loop_values, size=shape)
70 | else:
71 | adj += torch.eye(adj.shape[0]).to(self.device)
72 | # adj = adj.to_sparse()
73 |
74 | self.num_class = self.config["num_class"]
75 | self.num_node = features.shape[0]
76 |
77 | homophily = get_homophily(label=labels, adj=adj)
78 | estimator = EstimateAdj(
79 | features,
80 | labels,
81 | adj,
82 | torch.where(train_mask)[0],
83 | self.num_class,
84 | self.num_node,
85 | homophily,
86 | )
87 |
88 | t_total = time.time()
89 | for iter in range(self.config["iter"]):
90 | self.train_base_model(features, adj, labels, train_mask, val_mask, iter)
91 |
92 | estimator.reset_obs()
93 | estimator.update_obs(self.knn(features))
94 | estimator.update_obs(self.knn(self.hidden_output))
95 | estimator.update_obs(self.knn(self.output))
96 | self.iter += 1
97 | alpha, beta, O, Q, iterations = estimator.EM(
98 | self.output.max(1)[1].detach().cpu().numpy(), self.config["tolerance"]
99 | )
100 | adj = prob_to_adj(Q, self.config["threshold"]).to(self.device)
101 |
102 | print(
103 | "***********************************************************************************************"
104 | )
105 | print("Optimization Finished!")
106 | print(
107 | "Total time:{:.4f}s".format(time.time() - t_total),
108 | "Best validation accuracy:{:.4f}".format(self.best_acc_val),
109 | "EM iterations:{:04d}\n".format(iterations),
110 | )
111 | self.best_result = self.test(features, labels, test_mask)
112 |
113 | def train_base_model(self, features, adj, labels, train_mask, val_mask, iter):
114 | best_acc_val = 0
115 | optimizer = optim.Adam(
116 | self.base_model.parameters(),
117 | lr=self.config["lr"],
118 | weight_decay=self.config["weight_decay"],
119 | )
120 |
121 | t = time.time()
122 | for epoch in range(self.config["epoch"]):
123 | self.base_model.train()
124 | optimizer.zero_grad()
125 |
126 | hidden_output, output = self.base_model(features, adj, return_hidden=True)
127 | loss_train = F.nll_loss(output[train_mask], labels[train_mask])
128 | acc_train = accuracy(output[train_mask], labels[train_mask])
129 | loss_train.backward()
130 | optimizer.step()
131 |
132 | # Evaluate valset performance (deactivate dropout)
133 | self.base_model.eval()
134 | hidden_output, output = self.base_model(features, adj, return_hidden=True)
135 |
136 | loss_val = F.nll_loss(output[val_mask], labels[val_mask])
137 | acc_val = accuracy(output[val_mask], labels[val_mask])
138 |
139 | if acc_val > best_acc_val:
140 | best_acc_val = acc_val
141 | if acc_val > self.best_acc_val:
142 | self.best_acc_val = acc_val
143 | self.best_graph = adj
144 | self.hidden_output = hidden_output
145 | self.output = output
146 | self.weights = deepcopy(self.base_model.state_dict())
147 | if self.config["debug"]:
148 | print(
149 | "=== Saving current graph/base_model, best_acc_val:%.4f"
150 | % self.best_acc_val.item()
151 | )
152 |
153 | if self.config["debug"]:
154 | if epoch % 1 == 0:
155 | print(
156 | "Epoch {:04d}".format(epoch + 1),
157 | "loss_train:{:.4f}".format(loss_train.item()),
158 | "acc_train:{:.4f}".format(acc_train.item()),
159 | "loss_val:{:.4f}".format(loss_val.item()),
160 | "acc_val:{:.4f}".format(acc_val.item()),
161 | "time:{:.4f}s".format(time.time() - t),
162 | )
163 |
164 | print(
165 | "Iteration {:04d}".format(iter),
166 | "acc_val:{:.4f}".format(best_acc_val.item()),
167 | )
168 |
169 | def test(self, features, labels, mask_test, hetero=False):
170 | """Evaluate the performance on testset."""
171 | print("=== Testing ===")
172 | print("Picking the best model according to validation performance")
173 |
174 | self.base_model.load_state_dict(self.weights)
175 | self.base_model.eval()
176 | hidden_output, output = self.base_model(
177 | features, self.best_graph, return_hidden=True
178 | )
179 | loss_test = F.nll_loss(output[mask_test], labels[mask_test])
180 | if hetero:
181 | pred = torch.argmax(output, dim=1)
182 | return torch_f1_score(pred[mask_test], labels[mask_test])
183 | else:
184 | acc_test = accuracy(output[mask_test], labels[mask_test])
185 | print(
186 | "Testset results: ",
187 | "loss={:.4f}".format(loss_test.item()),
188 | "accuracy={:.4f}".format(acc_test.item()),
189 | )
190 | return acc_test.item()
191 |
192 |
193 | class EstimateAdj:
194 | def __init__(
195 | self, features, labels, adj, idx_train, num_class, num_node, homophily
196 | ):
197 | self.num_class = num_class
198 | self.num_node = num_node
199 | self.idx_train = idx_train.cpu().numpy()
200 | self.label = labels.cpu().numpy()
201 | self.adj = adj.cpu().numpy()
202 |
203 | self.output = None
204 | self.iterations = 0
205 |
206 | self.homophily = homophily
207 |
208 | def reset_obs(self):
209 | self.N = 0
210 | self.E = np.zeros((self.num_node, self.num_node), dtype=np.int64)
211 |
212 | def update_obs(self, output):
213 | self.E += output
214 | self.N += 1
215 |
216 | def revise_pred(self):
217 | for j in range(len(self.idx_train)):
218 | self.output[self.idx_train[j]] = self.label[self.idx_train[j]]
219 |
220 | def E_step(self, Q):
221 | """Run the Expectation(E) step of the EM algorithm.
222 | Parameters
223 | ----------
224 | Q:
225 | The current estimation that each edge is actually present (numpy.array)
226 |
227 | Returns
228 | ----------
229 | alpha:
230 | The estimation of true-positive rate (float)
231 | beta:
232 | The estimation of false-positive rate (float)
233 | O:
234 | The estimation of network model parameters (numpy.array)
235 | """
236 | # Temporary variables to hold the numerators and denominators of alpha and beta
237 | an = Q * self.E
238 | an = np.triu(an, 1).sum()
239 | bn = (1 - Q) * self.E
240 | bn = np.triu(bn, 1).sum()
241 | ad = Q * self.N
242 | ad = np.triu(ad, 1).sum()
243 | bd = (1 - Q) * self.N
244 | bd = np.triu(bd, 1).sum()
245 |
246 | # Calculate alpha, beta
247 | alpha = an * 1.0 / (ad)
248 | beta = bn * 1.0 / (bd)
249 |
250 | O = np.zeros((self.num_class, self.num_class))
251 |
252 | n = []
253 | counter = Counter(self.output)
254 | for i in range(self.num_class):
255 | n.append(counter[i])
256 |
257 | a = self.output.repeat(self.num_node).reshape(self.num_node, -1)
258 | for j in range(self.num_class):
259 | c = a == j
260 | for i in range(j + 1):
261 | b = a == i
262 | O[i, j] = np.triu((b & c.T) * Q, 1).sum()
263 | if i == j:
264 | O[j, j] = 2.0 / (n[j] * (n[j] - 1)) * O[j, j]
265 | else:
266 | O[i, j] = 1.0 / (n[i] * n[j]) * O[i, j]
267 | return (alpha, beta, O)
268 |
269 | def M_step(self, alpha, beta, O):
270 | """Run the Maximization(M) step of the EM algorithm."""
271 | O += O.T - np.diag(O.diagonal())
272 |
273 | row = self.output.repeat(self.num_node)
274 | col = np.tile(self.output, self.num_node)
275 | tmp = O[row, col].reshape(self.num_node, -1)
276 |
277 | p1 = tmp * np.power(alpha, self.E) * np.power(1 - alpha, self.N - self.E)
278 | p2 = (1 - tmp) * np.power(beta, self.E) * np.power(1 - beta, self.N - self.E)
279 | Q = p1 * 1.0 / (p1 + p2 * 1.0)
280 | return Q
281 |
282 | def EM(self, output, tolerance=0.000001):
283 | """Run the complete EM algorithm.
284 | Parameters
285 | ----------
286 | tolerance:
287 | Determine the tolerance in the variantions of alpha, beta and O, which is acceptable to stop iterating (float)
288 | seed:
289 | seed for np.random.seed (int)
290 |
291 | Returns
292 | ----------
293 | iterations:
294 | The number of iterations to achieve the tolerance on the parameters (int)
295 | """
296 | # Record previous values to confirm convergence
297 | alpha_p = 0
298 | beta_p = 0
299 |
300 | self.output = output
301 | self.revise_pred()
302 |
303 | # Do an initial E-step with random alpha, beta and O
304 | # Beta must be smaller than alpha
305 | beta, alpha = np.sort(np.random.rand(2))
306 | O = np.triu(np.random.rand(self.num_class, self.num_class))
307 |
308 | # Calculate initial Q
309 | Q = self.M_step(alpha, beta, O)
310 |
311 | while abs(alpha_p - alpha) > tolerance or abs(beta_p - beta) > tolerance:
312 | alpha_p = alpha
313 | beta_p = beta
314 | alpha, beta, O = self.E_step(Q)
315 | Q = self.M_step(alpha, beta, O)
316 | self.iterations += 1
317 |
318 | if self.homophily > 0.5:
319 | Q += self.adj
320 | return (alpha, beta, O, Q, self.iterations)
321 |
--------------------------------------------------------------------------------
/model/grcn.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.encoder.gcn import GCNConv, GCNConv_diag
3 | from GSL.metric import InnerProductSimilarity
4 | from GSL.utils import accuracy
5 |
6 | import numpy as np
7 | from scipy.sparse import coo_matrix
8 | import torch
9 | import torch.nn.functional as F
10 | from tqdm import tqdm
11 |
12 | EOS = 1e-10
13 |
14 |
15 | class GRCN(BaseModel):
16 |
17 | """Graph-Revised Convolutional Network (ECML-PKDD 2020)"""
18 |
19 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device):
20 | super(GRCN, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
21 | self.device = device
22 | self.num_features = num_features
23 | self.graph_nhid = int(self.config.hid_graph.split(":")[0])
24 | self.graph_nhid2 = int(self.config.hid_graph.split(":")[1])
25 | self.nhid = self.config.nhid
26 | self.conv1 = GCNConv(self.num_features, self.nhid)
27 | self.conv2 = GCNConv(self.nhid, num_classes)
28 |
29 | if self.config.sparse:
30 | self.conv_graph = GCNConv_diag(self.num_features, self.device)
31 | self.conv_graph2 = GCNConv_diag(self.num_features, self.device)
32 | else:
33 | self.conv_graph = GCNConv(self.num_features, self.graph_nhid)
34 | self.conv_graph2 = GCNConv(self.graph_nhid, self.graph_nhid2)
35 |
36 | self.F = ({'relu': F.relu, 'prelu': F.prelu, 'tanh': torch.tanh})[self.config.F]
37 | self.F_graph = ({'relu': F.relu, 'prelu': F.prelu, 'tanh': torch.tanh})[self.config.F_graph]
38 | self.dropout = self.config.dropout
39 | self.K = self.config.compl_param.split(":")[0]
40 | self.mask = None
41 | self.Adj_new = None
42 | self._normalize = self.config.normalize
43 | self.reduce = self.config.reduce
44 | self.sparse = self.config.sparse
45 | self.norm_mode = "sym"
46 | self.config = self.config
47 |
48 | def init_para(self):
49 | self.conv1.init_para()
50 | self.conv2.init_para()
51 | self.conv_graph.init_para()
52 | self.conv_graph2.init_para()
53 |
54 | def graph_parameters(self):
55 | return list(self.conv_graph.parameters()) + list(self.conv_graph2.parameters())
56 |
57 | def base_parameters(self):
58 | return list(self.conv1.parameters()) + list(self.conv2.parameters())
59 |
60 | def normalize(self, adj, mode="sym"):
61 | if not self.sparse:
62 | if mode == "sym":
63 | inv_sqrt_degree = 1. / (torch.sqrt(adj.sum(dim=1, keepdim=False)) + EOS)
64 | return inv_sqrt_degree[:, None] * adj * inv_sqrt_degree[None, :]
65 | elif mode == "row":
66 | inv_degree = 1. / (adj.sum(dim=1, keepdim=False) + EOS)
67 | return inv_degree[:, None] * adj
68 | else:
69 | exit("wrong norm mode")
70 | else:
71 | adj = adj.coalesce()
72 | if mode == "sym":
73 | inv_sqrt_degree = 1. / (torch.sqrt(torch.sparse.sum(adj, dim=1).values()) + EOS)
74 | D_value = inv_sqrt_degree[adj.indices()[0]] * inv_sqrt_degree[adj.indices()[1]]
75 | elif mode == "row":
76 | inv_degree = 1. / (torch.sparse.sum(adj, dim=1).values() + EOS)
77 | D_value = inv_degree[adj.indices()[0]]
78 | else:
79 | exit("wrong norm mode")
80 | new_values = adj.values() * D_value
81 | return torch.sparse_coo_tensor(adj.indices(), new_values, adj.size()).to(self.device)
82 |
83 | def _node_embeddings(self, features, adj):
84 | norm_adj = self.normalize(adj, self.norm_mode)
85 | node_embeddings = self.F_graph(self.conv_graph(features, norm_adj, self.sparse))
86 | node_embeddings = self.conv_graph2(node_embeddings, norm_adj, self.sparse)
87 | if self._normalize:
88 | node_embeddings = F.normalize(node_embeddings, dim=1, p=2)
89 | return node_embeddings
90 |
91 | def _sparse_graph(self, raw_graph, K):
92 | if self.reduce == 'knn':
93 | values, indices = raw_graph.topk(k=int(K), dim=-1)
94 | assert torch.sum(torch.isnan(values)) == 0
95 | assert torch.max(indices) < raw_graph.shape[1]
96 | if not self.sparse:
97 | self.mask = torch.zeros(raw_graph.shape).to(self.device)
98 | self.mask[torch.arange(raw_graph.shape[0]).view(-1,1), indices] = 1.
99 | self.mask[indices, torch.arange(raw_graph.shape[1]).view(-1,1)] = 1.
100 | else:
101 | inds = torch.stack([torch.arange(raw_graph.shape[0]).view(-1,1).expand(-1,int(K)).contiguous().view(1,-1)[0].to(self.device),
102 | indices.view(1,-1)[0]])
103 | inds = torch.cat([inds, torch.stack([inds[1], inds[0]])], dim=1)
104 | values = torch.cat([values.view(1,-1)[0], values.view(1,-1)[0]])
105 | return inds, values
106 | else:
107 | exit("wrong sparsification method")
108 | self.mask.requires_grad = False
109 | sparse_graph = raw_graph * self.mask
110 | return sparse_graph
111 |
112 | def feedforward(self, features, adj):
113 | adj.requires_grad = False
114 | node_embeddings = self._node_embeddings(features, adj)
115 |
116 | metric = InnerProductSimilarity()
117 | adj_new = metric(node_embeddings[:, :int(self.num_features/2)], node_embeddings[:, :int(self.num_features/2)])
118 | adj_new += metric(node_embeddings[:, int(self.num_features/2):], node_embeddings[:, int(self.num_features/2):])
119 |
120 | if not self.sparse:
121 | adj_new = self._sparse_graph(adj_new, self.K)
122 | adj_new = self.normalize(adj + adj_new, self.norm_mode)
123 | else:
124 | adj_new_indices, adj_new_values = self._sparse_graph(adj_new, self.K)
125 | new_inds = torch.cat([adj.coalesce().indices(), adj_new_indices], dim=1)
126 | new_values = torch.cat([adj.coalesce().values(), adj_new_values])
127 | adj_new = torch.sparse.FloatTensor(new_inds, new_values, adj.size()).to(self.device)
128 | adj_new = self.normalize(adj_new, self.norm_mode)
129 |
130 | x = self.conv1(features, adj_new, sparse=self.sparse)
131 | x = F.dropout(self.F(x), training=self.training, p=self.dropout)
132 | x = self.conv2(x, adj_new, sparse=self.sparse)
133 |
134 | return F.log_softmax(x, dim=1)
135 |
136 | def test(self, features, adj, labels, mask):
137 | with torch.no_grad():
138 | output = self.feedforward(features, adj)
139 | result = self.metric(output[mask], labels[mask])
140 | return result.item()
141 |
142 | def fit(self, dataset, split_num=0):
143 | adj, features, labels = dataset.adj.copy(), dataset.features.clone().to(self.device), dataset.labels.to(self.device)
144 | if dataset.name in ['cornell', 'texas', 'wisconsin', 'actor']:
145 | train_mask = dataset.train_masks[split_num % 10]
146 | val_mask = dataset.val_masks[split_num % 10]
147 | test_mask = dataset.test_masks[split_num % 10]
148 | else:
149 | train_mask, val_mask, test_mask = dataset.train_mask, dataset.val_mask, dataset.test_mask
150 |
151 | # if self.sparse: ??
152 |
153 | # if adj.is_sparse:
154 | # indices = adj.coalesce().indices()
155 | # values = adj.coalesce().values()
156 | # shape = adj.coalesce().shape
157 | # num_nodes = features.shape[0]
158 | # loop_edge_index = torch.stack([torch.arange(num_nodes), torch.arange(num_nodes)]).to(adj.device)
159 | # loop_edge_index = torch.cat([indices, loop_edge_index], dim=1)
160 | # loop_values = torch.ones(num_nodes).to(adj.device)
161 | # loop_values = torch.cat([values, loop_values], dim=0)
162 | # adj = torch.sparse_coo_tensor(indices=loop_edge_index, values=loop_values, size=shape)
163 | # else:
164 | # adj += torch.eye(adj.shape[0]).to(self.device)
165 | # adj = adj.to_sparse()
166 |
167 | coo = coo_matrix(adj)
168 | values = coo.data
169 | indices = np.vstack((coo.row, coo.col))
170 | i = torch.LongTensor(indices)
171 | v = torch.FloatTensor(values)
172 | shape = coo.shape
173 | adj = torch.sparse_coo_tensor(i, v, torch.Size(shape)).to(self.device)
174 |
175 | optimizer_base = torch.optim.Adam(self.base_parameters(), lr=self.config.lr, weight_decay=self.config.weight_decay)
176 | optimizer_graph = torch.optim.Adam(self.graph_parameters(), lr=self.config.lr_graph, weight_decay=self.config.weight_decay_graph)
177 |
178 | best_val_result = 0
179 | for epoch in range(self.config.num_epochs):
180 | optimizer_base.zero_grad()
181 | optimizer_graph.zero_grad()
182 |
183 | output = self.feedforward(features, adj)
184 |
185 | loss = F.nll_loss(output[train_mask], labels[train_mask])
186 | loss.backward(retain_graph=False)
187 |
188 | optimizer_base.step()
189 | optimizer_graph.step()
190 |
191 | if epoch % 10 == 0:
192 | train_result = self.test(features, adj, labels, train_mask)
193 | val_result = self.test(features, adj, labels, val_mask)
194 | test_result = self.test(features, adj, labels, test_mask)
195 | if val_result > best_val_result:
196 | best_val_result = val_result
197 | self.best_result = test_result
198 |
199 | print(f'Epoch: {epoch: 02d}, '
200 | f'Loss: {loss:.4f}, '
201 | f'Train: {100 * train_result:.2f}%, '
202 | f'Valid: {100 * val_result:.2f}%, '
203 | f'Test: {100 * test_result:.2f}%')
--------------------------------------------------------------------------------
/model/hesgsl.py:
--------------------------------------------------------------------------------
1 | import math
2 | import torch
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 | import dgl
6 | import dgl.function as fn
7 |
8 | from GSL.model import BaseModel
9 | from GSL.utils import knn_fast, top_k
10 | from GSL.encoder import GCNConv_dgl, GCNConv, GCN, GCN_dgl
11 |
12 | def get_random_mask(features, r, scale, dataset):
13 |
14 | if dataset == 'ogbn-arxiv' or dataset == 'minist' or dataset == 'cifar10' or dataset == 'fashionmnist':
15 | probs = torch.full(features.shape, 1 / r)
16 | mask = torch.bernoulli(probs)
17 | return mask
18 |
19 | nones = torch.sum(features > 0.0).float()
20 | nzeros = features.shape[0] * features.shape[1] - nones
21 | pzeros = nones / nzeros / r * scale
22 |
23 | probs = torch.zeros(features.shape).to(features.device)
24 | probs[features == 0.0] = pzeros
25 | probs[features > 0.0] = 1 / r
26 |
27 | mask = torch.bernoulli(probs)
28 |
29 | return mask
30 |
31 |
32 | def get_homophily(adj, labels, dgl_=False):
33 | if dgl_:
34 | src, dst = adj.edges()
35 | else:
36 | src, dst = adj.detach().nonzero().t()
37 | homophily_ratio = 1.0 * torch.sum((labels[src] == labels[dst])) / src.shape[0]
38 |
39 | return homophily_ratio
40 |
41 |
42 | class HESGSL(BaseModel):
43 | """
44 | Homophily-Enhanced Self-Supervision for Graph Structure Learning: Insights and Directions (TNNLS 2023')
45 | """
46 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device):
47 | super(HESGSL, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
48 | self.nclass = num_classes
49 | self.hid_dim_dae = self.config.hid_dim_dae
50 | self.hid_dim_cla = self.config.hid_dim_cla
51 | self.nlayers = self.config.nlayers
52 | self.dropout_cla = self.config.dropout_cla
53 | self.dropout_adj = self.config.dropout_adj
54 | self.mlp_dim = self.config.mlp_dim
55 | self.k = self.config.k
56 | self.sparse = self.config.sparse
57 | self.lr = self.config.lr
58 | self.lr_cla = self.config.lr_cla
59 | self.weight_decay = self.config.weight_decay
60 | self.num_epochs = self.config.num_epochs
61 | self.epochs_pre = self.config.epochs_pre
62 | self.epochs_hom = self.config.epochs_hom
63 | self.dataset = self.config.dataset
64 | self.ratio = self.config.ratio
65 | self.scale = self.config.scale
66 | self.num_hop = self.config.num_hop
67 | self.alpha = self.config.alpha
68 | self.beta = self.config.beta
69 | self.patience = self.config.patience
70 | self.eval_step = self.config.eval_step
71 |
72 | self.model_dae = GCN_DAE(num_features, self.hid_dim_dae, num_features, self.nlayers, self.dropout_cla, self.dropout_adj, self.mlp_dim, self.k, self.sparse).to(device)
73 | if self.sparse:
74 | self.model_cla = GCN_dgl(num_features, self.hid_dim_cla, num_classes, self.nlayers, self.dropout_cla, self.dropout_adj)
75 | else:
76 | self.model_cla = GCN(num_features, self.hid_dim_cla, num_classes, self.nlayers, self.dropout_cla, self.dropout_adj, self.sparse)
77 |
78 | self.optimizer_dat = torch.optim.Adam(self.model_dae.parameters(), lr=self.lr, weight_decay=float(0.0))
79 | self.optimizer_cla = torch.optim.Adam(self.model_cla.parameters(), lr=self.lr_cla, weight_decay=self.weight_decay)
80 |
81 | def get_loss_reconstruction(self, features, mask):
82 | if self.dataset == 'ogbn-arxiv' or self.dataset == 'minist' or self.dataset == 'cifar10' or self.dataset == 'fashionmnist':
83 | masked_features = features * (1 - mask)
84 | logits, adj = self.model_dae(features, masked_features)
85 |
86 | indices = mask > 0
87 | loss = F.mse_loss(logits[indices], features[indices], reduction='mean')
88 |
89 | return loss, adj
90 |
91 | logits, adj = self.model_dae(features, features)
92 |
93 | indices = mask > 0
94 | loss = F.binary_cross_entropy_with_logits(logits[indices], features[indices], reduction='mean')
95 |
96 | return loss, adj
97 |
98 | def get_loss_classification(self, features, adj, mask, labels):
99 |
100 | logits = self.model_cla(features, adj)
101 | logits = F.log_softmax(logits, 1)
102 |
103 | loss = F.nll_loss(logits[mask], labels[mask], reduction='mean')
104 | return loss
105 |
106 | def fit(self, dataset, split_num=0):
107 | adj, features, labels = dataset.adj.clone(), dataset.features.clone(), dataset.labels
108 | if dataset.name in ['cornell', 'texas', 'wisconsin', 'actor']:
109 | train_mask = dataset.train_masks[split_num % 10]
110 | val_mask = dataset.val_masks[split_num % 10]
111 | test_mask = dataset.test_masks[split_num % 10]
112 | else:
113 | train_mask, val_mask, test_mask = dataset.train_mask, dataset.val_mask, dataset.test_mask
114 |
115 | best_val, best_test = float('-inf'), 0
116 | hom_ratio_val = 0
117 | p = 0
118 | for epoch in range(1, self.num_epochs + 1):
119 | self.model_dae.train()
120 | self.model_cla.train()
121 |
122 | self.optimizer_dat.zero_grad()
123 | self.optimizer_cla.zero_grad()
124 |
125 | mask = get_random_mask(features, self.ratio, self.scale, self.dataset).to(self.device)
126 |
127 | if epoch < self.epochs_pre:
128 | loss_dae, adj = self.get_loss_reconstruction(features, mask)
129 | loss_cla = torch.tensor(0).to(self.device)
130 | loss_hom = torch.tensor(0).to(self.device)
131 | elif epoch < self.epochs_pre + self.epochs_hom:
132 | loss_dae, adj = self.get_loss_reconstruction(features, mask)
133 | loss_cla = self.get_loss_classification(features, adj, train_mask, labels)
134 | loss_hom = torch.tensor(0).to(self.device)
135 | else:
136 | loss_dae, adj = self.get_loss_reconstruction(features, mask)
137 | loss_cla = self.get_loss_classification(features, adj, train_mask, labels)
138 | loss_hom = self.model_dae.get_loss_homophily(adj, logits, labels, train_mask, self.nclass, self.num_hop, self.sparse)
139 |
140 | loss = loss_dae * self.alpha + loss_cla + loss_hom * self.beta
141 | loss.backward()
142 |
143 | self.optimizer_dat.step()
144 | self.optimizer_cla.step()
145 |
146 | self.model_dae.eval()
147 | self.model_cla.eval()
148 | adj = self.model_dae.get_adj(features)
149 | unnorm_adj = self.model_dae.get_unnorm_adj(features)
150 | logits = self.model_cla(features, adj)
151 |
152 | train_result = self.metric(logits[train_mask], labels[train_mask])
153 | val_result = self.metric(logits[val_mask], labels[val_mask])
154 | test_result = self.metric(logits[test_mask], labels[test_mask])
155 | hom_ratio = get_homophily(adj, labels, self.sparse).item()
156 |
157 | if epoch >= self.epochs_pre:
158 | if val_result > best_val:
159 | best_val = val_result
160 | best_test = test_result
161 | hom_ratio_val = hom_ratio
162 | p = 0
163 | torch.save(unnorm_adj, 'HESGSL_Cora.pt')
164 | else:
165 | p += 1
166 | if p >= self.patience:
167 | print("Early stopping!")
168 | break
169 |
170 | if epoch % self.eval_step == 0:
171 | print(f'Epoch: {epoch: 02d}, '
172 | f'Loss: {loss:.4f}, '
173 | f'Homophily: {hom_ratio:.2f}',
174 | f'Train: {100 * train_result:.2f}%, '
175 | f'Valid: {100 * val_result:.2f}%, '
176 | f'Test: {100 * test_result:.2f}%')
177 |
178 | print('Best Test Result: ', best_test.item())
179 | self.best_result = best_test.item()
180 |
181 | # GSL.learner.mlp_learner.py
182 | class GSL(nn.Module):
183 | def __init__(self, in_dim, hid_dim, out_dim, nlayers, k, sparse):
184 | super(GSL, self).__init__()
185 |
186 | self.layers = nn.ModuleList()
187 | self.layers.append(nn.Linear(in_dim, hid_dim))
188 | for _ in range(nlayers - 2):
189 | self.layers.append(nn.Linear(hid_dim, hid_dim))
190 | self.layers.append(nn.Linear(hid_dim, out_dim))
191 |
192 | self.k = k
193 | self.sparse = sparse
194 | self.in_dim = in_dim
195 | self.mlp_knn_init()
196 |
197 | def mlp_knn_init(self):
198 | for layer in self.layers:
199 | layer.weight = nn.Parameter(torch.eye(self.in_dim))
200 |
201 | def forward(self, h):
202 | for i, layer in enumerate(self.layers):
203 | h = layer(h)
204 | if i != (len(self.layers) - 1):
205 | h = F.relu(h)
206 |
207 | if self.sparse == 1:
208 | rows, cols, values = knn_fast(h, self.k, 1000)
209 |
210 | rows_ = torch.cat((rows, cols))
211 | cols_ = torch.cat((cols, rows))
212 | values_ = F.relu(torch.cat((values, values)))
213 |
214 | adj = dgl.graph((rows_, cols_), num_nodes=h.shape[0], device=h.device)
215 | adj.edata['w'] = values_
216 | else:
217 | embeddings = F.normalize(h, dim=1, p=2)
218 | adj = torch.mm(embeddings, embeddings.t())
219 |
220 | adj = top_k(adj, self.k + 1)
221 | adj = F.relu(adj)
222 |
223 | return adj
224 |
225 |
226 | class GCN_DAE(nn.Module):
227 | def __init__(self, in_dim, hid_dim, out_dim, nlayers, dropout_cla, dropout_adj, mlp_dim, k, sparse):
228 | super(GCN_DAE, self).__init__()
229 |
230 | self.layers = nn.ModuleList()
231 | if sparse == 1:
232 | self.layers.append(GCNConv_dgl(in_dim, hid_dim))
233 | for i in range(nlayers - 2):
234 | self.layers.append(GCNConv_dgl(hid_dim, hid_dim))
235 | self.layers.append(GCNConv_dgl(hid_dim, out_dim))
236 |
237 | self.dropout_cla = dropout_cla
238 | self.dropout_adj = dropout_adj
239 |
240 | else:
241 | self.layers = nn.ModuleList()
242 | self.layers.append(GCNConv(in_dim, hid_dim))
243 | for i in range(nlayers - 2):
244 | self.layers.append(GCNConv(hid_dim, hid_dim))
245 | self.layers.append(GCNConv(hid_dim, out_dim))
246 |
247 | self.dropout_cla = dropout_cla
248 | self.dropout_adj = nn.Dropout(p=dropout_adj)
249 |
250 | self.sparse = sparse
251 | self.graph_generator = GSL(in_dim, math.floor(math.sqrt(in_dim * mlp_dim)), mlp_dim, nlayers, k, sparse)
252 |
253 | def get_adj(self, features):
254 | if self.sparse == 1:
255 | return self.graph_generator(features)
256 | else:
257 | adj = self.graph_generator(features)
258 | adj = (adj + adj.T) / 2
259 |
260 | inv_sqrt_degree = 1. / (torch.sqrt(adj.sum(dim=1, keepdim=False)) + 1e-10)
261 | return inv_sqrt_degree[:, None] * adj * inv_sqrt_degree[None, :]
262 |
263 | def get_unnorm_adj(self, features):
264 | if self.sparse == 1:
265 | return self.graph_generator(features)
266 | else:
267 | adj = self.graph_generator(features)
268 | adj = (adj + adj.T) / 2
269 | return adj
270 |
271 | def forward(self, features, x):
272 | adj = self.get_adj(features)
273 | if self.sparse == 1:
274 | adj_dropout = adj
275 | adj_dropout.edata['w'] = F.dropout(adj_dropout.edata['w'], p=self.dropout_adj, training=self.training)
276 | else:
277 | adj_dropout = self.dropout_adj(adj)
278 |
279 | for i, conv in enumerate(self.layers[:-1]):
280 | x = conv(x, adj_dropout)
281 | x = F.relu(x)
282 | x = F.dropout(x, p=self.dropout_cla, training=self.training)
283 | x = self.layers[-1](x, adj_dropout)
284 |
285 | return x, adj
286 |
287 | def get_loss_homophily(self, g, logits, labels, train_mask, nclasses, num_hop, sparse):
288 |
289 | logits = torch.argmax(logits, dim=-1, keepdim=True)
290 | logits[train_mask, 0] = labels[train_mask]
291 |
292 | preds = torch.zeros(logits.shape[0], nclasses).to(labels.device)
293 | preds = preds.scatter(1, logits, 1).detach()
294 |
295 | if sparse == 1:
296 | g.ndata['l'] = preds
297 | for _ in range(num_hop):
298 | g.update_all(fn.u_mul_e('l', 'w', 'm'), fn.sum(msg='m', out='l'))
299 | q_dist = F.log_softmax(g.ndata['l'], dim=-1)
300 | else:
301 | q_dist = preds
302 | for _ in range(num_hop):
303 | q_dist = torch.matmul(g, q_dist)
304 | q_dist = F.log_softmax(q_dist, dim=-1)
305 |
306 | loss_hom = F.kl_div(q_dist, preds)
307 |
308 | return loss_hom
--------------------------------------------------------------------------------
/model/hgsl.py:
--------------------------------------------------------------------------------
1 | import time
2 | from GSL.model import BaseModel
3 | from GSL.utils import *
4 | from GSL.learner import *
5 | from GSL.encoder import *
6 | from GSL.metric import *
7 | from GSL.processor import *
8 | import torch
9 | import torch.nn as nn
10 |
11 |
12 | class HGSL(BaseModel):
13 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device, data):
14 | super(HGSL, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
15 | metadata = data.metadata
16 | self.ti, self.ri, self.types, self.ud_rels = metadata['t_info'], metadata['r_info']\
17 | , metadata['types'], metadata['undirected_relations']
18 | feat_dim, mp_emb_dim, n_class = metadata['n_feat'], metadata['n_meta_path_emb'], metadata['n_class']
19 | self.non_linear = nn.ReLU()
20 | # ! Graph Structure Learning
21 | MD = nn.ModuleDict
22 | self.fgg_direct, self.fgg_left, self.fgg_right, self.fg_agg, self.sgg_gen, self.sg_agg, self.overall_g_agg = \
23 | MD({}), MD({}), MD({}), MD({}), MD({}), MD({}), MD({})
24 | # Feature encoder
25 | self.encoder = MD(dict(zip(metadata['types'], [nn.Linear(feat_dim, self.config.com_feat_dim)
26 | for _ in metadata['types']])))
27 |
28 | for r in metadata['undirected_relations']:
29 | # ! Feature Graph Generator
30 | self.fgg_direct[r] = KHeadHPLearner(CosineSimilarity(), [ThresholdSparsify(self.config.fgd_th)], self.config.com_feat_dim, self.config.num_head)
31 | self.fgg_left[r] = KHeadHPLearner(CosineSimilarity(), [ThresholdSparsify(self.config.fgh_th)], feat_dim, self.config.num_head)
32 | self.fgg_right[r] = KHeadHPLearner(CosineSimilarity(), [ThresholdSparsify(self.config.fgh_th)], feat_dim, self.config.num_head)
33 | self.fg_agg[r] = GraphChannelAttLayer(3) # 3 = 1 (first-order/direct) + 2 (second-order)
34 |
35 | # ! Semantic Graph Generator
36 | self.sgg_gen[r] = MD(dict(
37 | zip(self.config.mp_list, [KHeadHPLearner(CosineSimilarity(), [ThresholdSparsify(self.config.sem_th)], mp_emb_dim, self.config.num_head) for _ in self.config.mp_list])))
38 | self.sg_agg[r] = GraphChannelAttLayer(len(self.config.mp_list))
39 |
40 | # ! Overall Graph Generator
41 | if len(data.meta_path_emb) > 0:
42 | self.overall_g_agg[r] = GraphChannelAttLayer(3, [1, 1, 10]) # 3 = feat-graph + sem-graph + ori_graph
43 | else:
44 | self.overall_g_agg[r] = GraphChannelAttLayer(2, [1, 1, 10]) # 2 = feat-graph + ori_graph
45 |
46 | # ! Graph Convolution
47 | if self.config.conv_method == 'gcn':
48 | self.GCN = GCN(in_channels=feat_dim, hidden_channels=self.config.emb_dim, out_channels=n_class, num_layers=2,
49 | dropout=self.config.dropout, dropout_adj=0., sparse=False, activation_last='log_softmax',
50 | conv_bias=True)
51 | self.norm_order = self.config.adj_norm_order
52 | self.stopper = None
53 |
54 | def fit(self, data):
55 | adj, features, labels = data.adj, data.features, data.labels
56 | train_idx, val_idx, test_idx = data.train_idx, data.val_idx, data.test_idx
57 | meta_path_emb = data.meta_path_emb
58 | optimizer = torch.optim.Adam(
59 | self.parameters(), lr=self.config.lr, weight_decay=float(self.config.weight_decay))
60 | self.stopper = EarlyStopping(patience=self.config.early_stop)
61 | cla_loss = torch.nn.NLLLoss()
62 |
63 | dur = []
64 | # w_list = []
65 | for epoch in range(self.config.epochs):
66 | # ! Train
67 | t0 = time.time()
68 | self.train()
69 | logits, adj_new = self.forward(features, adj, meta_path_emb)
70 | train_f1, train_mif1 = self.eval_logits(logits, train_idx, labels[train_idx])
71 |
72 | l_pred = cla_loss(logits[train_idx], labels[train_idx])
73 | l_reg = self.config.alpha * torch.norm(adj, 1)
74 | loss = l_pred + l_reg
75 | optimizer.zero_grad()
76 | loss.backward()
77 | optimizer.step()
78 |
79 | # ! Valid
80 | self.eval()
81 | with torch.no_grad():
82 | logits = self.GCN(features, adj_new)
83 | val_f1, val_mif1 = self.eval_logits(logits, val_idx, labels[val_idx])
84 | dur.append(time.time() - t0)
85 | print(
86 | f"Epoch {epoch:05d} | Time(s) {np.mean(dur):.4f} | Loss {loss.item():.4f} | TrainF1 {train_f1:.4f} | ValF1 {val_f1:.4f}")
87 |
88 | if self.config.early_stop > 0:
89 | if self.stopper.step(val_f1, self, epoch):
90 | print(f'Early stopped, loading model from epoch-{self.stopper.best_epoch}')
91 | break
92 |
93 | if self.config.early_stop > 0:
94 | self.load_state_dict(self.stopper.best_weight)
95 | self.test(adj, features, labels, val_idx, test_idx, meta_path_emb)
96 |
97 | def test(self, adj, features, labels, val_idx, test_idx, meta_path_emb):
98 | with torch.no_grad():
99 | logits, _ = self.forward(features, adj, meta_path_emb)
100 | test_f1, test_mif1 = self.eval_logits(logits, test_idx, labels[test_idx])
101 | val_f1, val_mif1 = self.eval_logits(logits, val_idx, labels[val_idx])
102 | res = {}
103 | if self.stopper != None:
104 | res.update({'test_f1': f'{test_f1:.4f}', 'test_mif1': f'{test_mif1:.4f}',
105 | 'val_f1': f'{val_f1:.4f}', 'val_mif1': f'{val_mif1:.4f}',
106 | 'best_epoch': self.stopper.best_epoch})
107 | else:
108 | res.update({'test_f1': f'{test_f1:.4f}', 'test_mif1': f'{test_mif1:.4f}',
109 | 'val_f1': f'{val_f1:.4f}', 'val_mif1': f'{val_mif1:.4f}'})
110 | res_dict = {'res': res}
111 | print(f'results:{res_dict}')
112 | self.best_result = test_f1.item()
113 |
114 | def eval_logits(self, logits, target_x, target_y):
115 | pred_y = torch.argmax(logits[target_x], dim=1)
116 | return macro_f1(pred_y, target_y, n_class=logits.shape[1]), micro_f1(pred_y, target_y, n_class=logits.shape[1])
117 |
118 | def forward(self, features, adj_ori, mp_emb):
119 | def get_rel_mat(mat, r):
120 | return mat[self.ri[r][0]:self.ri[r][1], self.ri[r][2]:self.ri[r][3]]
121 |
122 | def get_type_rows(mat, type):
123 | return mat[self.ti[type]['ind'], :]
124 |
125 | def gen_g_via_feat(graph_gen_func, mat, r):
126 | return graph_gen_func(get_type_rows(mat, r[0]), get_type_rows(mat, r[-1]))
127 |
128 | # ! Heterogeneous Feature Mapping
129 | com_feat_mat = torch.cat([self.non_linear(
130 | self.encoder[t](features[self.ti[t]['ind']])) for t in self.types])
131 |
132 | # ! Heterogeneous Graph Generation
133 | new_adj = torch.zeros_like(adj_ori).to(self.device)
134 | for r in self.ud_rels:
135 | ori_g = get_rel_mat(adj_ori, r)
136 | # ! Feature Graph Generation
137 | fg_direct = gen_g_via_feat(self.fgg_direct[r], com_feat_mat, r)
138 |
139 | fmat_l, fmat_r = features[self.ti[r[0]]['ind']], features[self.ti[r[-1]]['ind']]
140 | sim_l, sim_r = self.fgg_left[r](fmat_l, fmat_l), self.fgg_right[r](fmat_r, fmat_r)
141 | fg_left, fg_right = sim_l.mm(ori_g), sim_r.mm(ori_g.t()).t()
142 |
143 | feat_g = self.fg_agg[r]([fg_direct, fg_left, fg_right])
144 |
145 | # ! Semantic Graph Generation
146 | sem_g_list = [gen_g_via_feat(self.sgg_gen[r][mp], mp_emb[mp], r) for mp in mp_emb]
147 | if len(sem_g_list) > 0:
148 | sem_g = self.sg_agg[r](sem_g_list)
149 | # ! Overall Graph
150 | # Update relation sub-matixs
151 | new_adj[self.ri[r][0]:self.ri[r][1], self.ri[r][2]:self.ri[r][3]] = \
152 | self.overall_g_agg[r]([feat_g, sem_g, ori_g]) # update edge e.g. AP
153 | else:
154 | new_adj[self.ri[r][0]:self.ri[r][1], self.ri[r][2]:self.ri[r][3]] = \
155 | self.overall_g_agg[r]([feat_g, ori_g]) # update edge e.g. AP
156 |
157 |
158 | new_adj += new_adj.clone().t() # sysmetric
159 | # ! Aggregate
160 | new_adj = F.normalize(new_adj, dim=0, p=self.norm_order)
161 | logits = self.GCN(features, new_adj)
162 | return logits, new_adj
163 |
164 |
165 | class GraphChannelAttLayer(nn.Module):
166 | """
167 | Fuse a multi-channel graph to a single-channel graph with attention.
168 | """
169 | def __init__(self, num_channel, weights=None):
170 | super(GraphChannelAttLayer, self).__init__()
171 | self.weight = nn.Parameter(torch.Tensor(num_channel, 1, 1))
172 | nn.init.constant_(self.weight, 0.1) # equal weight
173 |
174 | def forward(self, adj_list):
175 | adj_list = torch.stack(adj_list)
176 | # Row normalization of all graphs generated
177 | adj_list = F.normalize(adj_list, dim=1, p=1)
178 | # Hadamard product + summation -> Conv
179 | return torch.sum(adj_list * F.softmax(self.weight, dim=0), dim=0)
180 |
--------------------------------------------------------------------------------
/model/neuralsparse.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.learner import SpLearner
3 | from GSL.encoder import GCN
4 | from GSL.utils import accuracy
5 | import torch
6 | import torch.nn as nn
7 | import torch.nn.functional as F
8 | import scipy.sparse as sp
9 | import numpy as np
10 | from tqdm import tqdm
11 |
12 |
13 | class NeuralSparse(BaseModel):
14 | """
15 | Robust Graph Representation Learning via Neural Sparsification (ICML 2020)
16 | """
17 | def __init__(self, config, device, num_features, num_classes):
18 | super(NeuralSparse, self).__init__(device=device)
19 | self.config = config
20 | self.learner = SpLearner(nlayers=2, in_dim=2*num_features+1, hidden=config.hidden, activation=nn.ReLU(), k=config.k)
21 | self.gnn = GCN(in_channels=num_features,
22 | hidden_channels=config.hidden_channel,
23 | out_channels=num_classes,
24 | num_layers=config.num_layers,
25 | dropout=config.dropout,
26 | dropout_adj=0,
27 | sparse=False)
28 | self.optimizer = torch.optim.Adam(list(self.learner.parameters())+list(self.gnn.parameters()), lr=config.lr, weight_decay=config.weight_decay)
29 |
30 | def test(self, features, adj, labels, mask):
31 | self.learner.eval()
32 | self.gnn.eval()
33 | with torch.no_grad():
34 | support = self.learner(features, adj, 1.0, False)
35 | output = self.gnn(features, support)
36 | acc = accuracy(output[mask], labels[mask]).item()
37 | return acc
38 |
39 |
40 | def fit(self, features, adj, labels, train_mask, val_mask, test_mask):
41 | features = features.to(self.device)
42 | labels = labels.to(self.device)
43 | train_mask = train_mask.to(self.device)
44 | val_mask = val_mask.to(self.device)
45 | test_mask = test_mask.to(self.device)
46 | adj = torch.tensor(adj).to(self.device)
47 |
48 | indices = torch.nonzero(adj)
49 | values = adj[indices[:, 0], indices[:, 1]]
50 | shape = adj.shape
51 |
52 | adj = torch.sparse_coo_tensor(indices.t(), values, torch.Size(shape)).to(torch.float32)
53 |
54 | best_val_acc, best_test_acc = 0, 0
55 | with tqdm(total=self.config.epochs, desc='(NeuralSparse)',
56 | bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}{postfix}]') as pbar:
57 | for epoch in range(self.config.epochs):
58 | self.learner.train()
59 | self.gnn.train()
60 | if epoch % self.config.temp_N == 0:
61 | decay_temp = np.exp(-1*self.config.temp_r*epoch)
62 | temp = max(0.05, decay_temp)
63 | support = self.learner(features, adj, temp, True)
64 | output = self.gnn(features, support)
65 | loss_train = F.cross_entropy(output[train_mask], labels[train_mask])
66 |
67 | self.optimizer.zero_grad()
68 | loss_train.backward()
69 | self.optimizer.step()
70 |
71 | val_acc = self.test(features, adj, labels, val_mask)
72 | test_acc = self.test(features, adj, labels, test_mask)
73 | if val_acc > best_val_acc:
74 | best_val_acc = val_acc
75 | best_test_acc = test_acc
76 | patience = 0
77 | else:
78 | patience += 1
79 |
80 | if patience > self.config.patience:
81 | print("Early stopping...")
82 | break
83 |
84 | pbar.set_postfix({'Epoch': epoch+1, 'Loss': loss_train.item(), 'Acc_val': val_acc, 'Acc_test': test_acc})
85 | pbar.update(1)
86 | print("Best Test Accuracy: ", best_test_acc)
--------------------------------------------------------------------------------
/model/nodeformer.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.processor import AddEye
3 | from GSL.encoder import NodeFormerConv
4 | from GSL.utils import dense_adj_to_edge_index
5 |
6 | import torch
7 | import torch.nn.functional as F
8 | import torch.nn as nn
9 |
10 |
11 | class NodeFormer(BaseModel):
12 | """NodeFormer: A Scalable Graph Structure Learning Transformer for Node Classification (NeurIPS 2022)"""
13 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device, params):
14 | super(NodeFormer, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device, params)
15 |
16 | # hyper-parameters of model initialization
17 | self.hidden_dim = self.config.hidden_dim
18 | self.num_layers = self.config.num_layers
19 | self.use_jk = self.config.use_jk
20 | self.dropout = self.config.dropout
21 | self.activation = F.elu
22 | self.use_bn = self.config.use_bn
23 | self.use_residual = self.config.use_residual
24 | self.use_act = self.config.use_act
25 | self.use_edge_loss = self.config.use_edge_loss
26 | self.num_heads = self.config.num_heads
27 | self.kernel_transformation = self.config.kernel_transformation
28 | self.nb_random_features = self.config.nb_random_features
29 | self.use_gumbel = self.config.use_gumbel
30 | self.nb_gumbel_sample = self.config.nb_gumbel_sample
31 | self.rb_trans = self.config.rb_trans
32 |
33 | # hyper-parameters of model training
34 | self.epochs = self.config.epochs
35 | self.lr = self.config.lr
36 | self.weight_decay = self.config.weight_decay
37 | self.eval_step = self.config.eval_step
38 | self.lamda = self.config.lamda
39 | self.rb_order = self.config.rb_order
40 | self.tau = self.config.tau
41 |
42 | self.convs = nn.ModuleList()
43 | self.fcs = nn.ModuleList()
44 | self.fcs.append(nn.Linear(num_features, self.hidden_dim))
45 | self.bns = nn.ModuleList()
46 | self.bns.append(nn.LayerNorm(self.hidden_dim))
47 | for i in range(self.num_layers):
48 | self.convs.append(NodeFormerConv(self.hidden_dim, self.hidden_dim, num_heads=self.num_heads,
49 | kernel_transformation=self.kernel_transformation,
50 | nb_random_features=self.nb_random_features,
51 | use_gumbel=self.use_gumbel,
52 | nb_gumbel_sample=self.nb_gumbel_sample,
53 | rb_order=self.rb_order,
54 | rb_trans=self.rb_trans,
55 | use_edge_loss=self.use_edge_loss))
56 | self.bns.append(nn.LayerNorm(self.hidden_dim))
57 |
58 | if self.use_jk:
59 | self.fcs.append(nn.Linear(self.hidden_dim * self.num_layers + self.hidden_dim, num_classes))
60 | else:
61 | self.fcs.append(nn.Linear(self.hidden_dim, num_classes))
62 |
63 | def reset_parameters(self):
64 | for conv in self.convs:
65 | conv.reset_parameters()
66 | for bn in self.bns:
67 | bn.reset_parameters()
68 | for fc in self.fcs:
69 | fc.reset_parameters()
70 |
71 | def model_parameters(self):
72 | params = list()
73 | params += list(self.convs.parameters())
74 | params += list(self.bns.parameters())
75 | params += list(self.fcs.parameters())
76 | return params
77 |
78 | def feedforward(self, x, adjs, tau=1.0):
79 | x = x.unsqueeze(0) # [B, N, H, D], B=1 denotes number of graph
80 | layer_ = []
81 | link_loss_ = []
82 | z = self.fcs[0](x)
83 | if self.use_bn:
84 | z = self.bns[0](z)
85 | z = self.activation(z)
86 | z = F.dropout(z, p=self.dropout, training=self.training)
87 | layer_.append(z)
88 |
89 | for i, conv in enumerate(self.convs):
90 | if self.use_edge_loss:
91 | z, link_loss = conv(z, adjs, tau)
92 | link_loss_.append(link_loss)
93 | else:
94 | z = conv(z, adjs, tau)
95 | if self.use_residual:
96 | z += layer_[i]
97 | if self.use_bn:
98 | z = self.bns[i+1](z)
99 | if self.use_act:
100 | z = self.activation(z)
101 | z = F.dropout(z, p=self.dropout, training=self.training)
102 | layer_.append(z)
103 |
104 | if self.use_jk: # use jk connection for each layer
105 | z = torch.cat(layer_, dim=-1)
106 |
107 | x_out = self.fcs[-1](z).squeeze(0)
108 |
109 | if self.use_edge_loss:
110 | return x_out, link_loss_
111 | else:
112 | return x_out
113 |
114 | def test(self, features, adjs, labels, mask):
115 | self.eval()
116 | with torch.no_grad():
117 | out, _ = self.feedforward(features, adjs, self.tau)
118 | acc = self.metric(out[mask], labels[mask], self.num_class)
119 | return acc
120 |
121 | @staticmethod
122 | def adj_mul(adj_i, adj, N):
123 | adj_i_sp = torch.sparse_coo_tensor(adj_i, torch.ones(adj_i.shape[1], dtype=torch.float).to(adj.device), (N, N))
124 | adj_sp = torch.sparse_coo_tensor(adj, torch.ones(adj.shape[1], dtype=torch.float).to(adj.device), (N, N))
125 | adj_j = torch.sparse.mm(adj_i_sp, adj_sp)
126 | adj_j = adj_j.coalesce().indices()
127 | return adj_j
128 |
129 | def to(self, device):
130 | self.convs.to(device)
131 | self.fcs.to(device)
132 | self.bns.to(device)
133 | return self
134 |
135 | def fit(self, dataset, split_num=0):
136 | adj, features, labels = dataset.adj.clone(), dataset.features.clone(), dataset.labels
137 | if dataset.name in ['cornell', 'texas', 'wisconsin', 'actor']:
138 | train_mask = dataset.train_masks[split_num % 10]
139 | val_mask = dataset.val_masks[split_num % 10]
140 | test_mask = dataset.test_masks[split_num % 10]
141 | else:
142 | train_mask, val_mask, test_mask = dataset.train_mask, dataset.val_mask, dataset.test_mask
143 |
144 |
145 | num_nodes = features.shape[0]
146 | adjs = []
147 | adj = AddEye()(adj)
148 | adj = dense_adj_to_edge_index(adj)
149 | adjs.append(adj)
150 | for i in range(self.rb_order - 1):
151 | adj = self.adj_mul(adj, adj, num_nodes)
152 | adjs.append(adj)
153 |
154 | criterion = nn.NLLLoss()
155 | optimizer = torch.optim.Adam(self.model_parameters(), lr=self.lr, weight_decay=self.weight_decay)
156 |
157 | best_test, best_val = 0, float('-inf')
158 |
159 | for epoch in range(self.epochs):
160 | self.train()
161 | optimizer.zero_grad()
162 |
163 | out, link_loss_ = self.feedforward(features, adjs, self.tau)
164 | out = F.log_softmax(out, dim=1)
165 | loss = criterion(out[train_mask], labels[train_mask])
166 | loss -= self.lamda * sum(link_loss_) / len(link_loss_)
167 |
168 | loss.backward()
169 | optimizer.step()
170 |
171 | if epoch % self.eval_step == 0:
172 | train_result = self.test(features, adjs, labels, train_mask)
173 | val_result = self.test(features, adjs, labels, val_mask)
174 | test_result = self.test(features, adjs, labels, test_mask)
175 |
176 | if val_result > best_val:
177 | best_val = val_result
178 | best_test = test_result
179 |
180 | print(f'Epoch: {epoch: 02d}, '
181 | f'Loss: {loss:.4f}, '
182 | f'Train: {100 * train_result:.2f}%, '
183 | f'Valid: {100 * val_result:.2f}%, '
184 | f'Test: {100 * test_result:.2f}%')
185 | self.best_result = best_test.item()
186 |
--------------------------------------------------------------------------------
/model/ptdnet.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.encoder import GCNConv, GCNConv_dgl
3 | from GSL.utils import accuracy
4 | import numpy as np
5 | import torch.nn as nn
6 | import torch.nn.functional as F
7 | import torch
8 | from scipy.sparse import csc_matrix
9 | from scipy.sparse.linalg import svds
10 |
11 | eps = 1e-7
12 | SVD_PI = True
13 |
14 |
15 | class PTDNet(BaseModel):
16 | """
17 | Learning to Drop: Robust Graph Neural Network via Topological Denoising (WSDM 2021)
18 | """
19 | def __init__(self, config, device, in_dim, out_dim):
20 | super(PTDNet, self).__init__(device=device)
21 | self.config = config
22 | self.sparse = config.sparse
23 | self.in_dim = in_dim
24 | self.out_dim = out_dim
25 | hiddens = config.hiddens
26 |
27 | self.layers = nn.ModuleList()
28 |
29 | try:
30 | hiddens = [int(s) for s in str(hiddens).split('-')]
31 | except:
32 | hiddens = [config.hidden1]
33 |
34 | nhiddens = len(hiddens)
35 |
36 | if self.sparse:
37 | self.layers.append(GCNConv_dgl(self.in_dim, hiddens[0]))
38 | for _ in range(1, nhiddens):
39 | self.layers.append(GCNConv_dgl(hiddens[-1], hiddens[_]))
40 | self.layers.append(GCNConv_dgl(hiddens[-1], self.out_dim))
41 | else:
42 | self.layers.append(GCNConv(self.in_dim, hiddens[0]))
43 | for i in range(1, nhiddens):
44 | self.layers.append(GCNConv(hiddens[-1], hiddens[_]))
45 | self.layers.append(GCNConv(hiddens[-1], self.out_dim))
46 |
47 | hidden_1 = config.denoise_hidden_1
48 | hidden_2 = config.denoise_hidden_2
49 | self.edge_weights = []
50 | self.nblayers = nn.ModuleList()
51 | self.selflayers = nn.ModuleList()
52 |
53 | self.attentions = nn.ModuleList()
54 | self.attentions.append(nn.ModuleList())
55 | for _ in hiddens:
56 | self.attentions.append(nn.ModuleList())
57 |
58 | self.nblayers.append(nn.Linear(self.in_dim, hidden_1))
59 | self.selflayers.append(nn.Linear(self.in_dim, hidden_1))
60 |
61 | if hidden_2 > 0:
62 | self.attentions[0].append(nn.Linear(hidden_1*2, hidden_2))
63 |
64 | self.attentions[0].append(nn.Linear(hidden_1*2, 1))
65 |
66 | for i in range(1, len(self.attentions)):
67 | self.nblayers.append(nn.Linear(hiddens[i-1], hidden_1))
68 | self.selflayers.append(nn.Linear(hiddens[i-1], hidden_1))
69 |
70 | if hidden_2>0:
71 | self.attentions[i].append(nn.Linear(hidden_1*2, hidden_2))
72 |
73 | self.attentions[i].append(nn.Linear(hidden_1*2, 1))
74 |
75 | def get_attention(self, input1, input2, l=0, training=False):
76 |
77 | nb_layer = self.nblayers[l]
78 | selflayer = self.selflayers[l]
79 | net = self.attentions[l]
80 |
81 | input1 = F.relu(nb_layer(input1))
82 | if training:
83 | input1 = F.dropout(input1, p=self.config.dropout)
84 | input2 = F.relu(selflayer(input2))
85 | if training:
86 | input2 = F.dropout(input2, p=self.config.dropout)
87 |
88 | input10 = torch.cat([input1, input2], dim=1)
89 | for _layer in net:
90 | input10 = _layer(input10)
91 | if training:
92 | input10 = F.dropout(input10, p=self.config.dropout)
93 | weight10 = input10
94 | return weight10
95 |
96 | def hard_concrete_sample(self, log_alpha, beta=1.0, training=True):
97 | gamma = self.config.gamma
98 | zeta = self.config.zeta
99 |
100 | if training:
101 | debug_var = eps
102 | bias = 0.0
103 | random_noise = bias+torch.empty_like(log_alpha).uniform_(debug_var, 1.0 - debug_var).to(torch.float32)
104 | gate_inputs = torch.log(random_noise) - torch.log(1.0 - random_noise)
105 | gate_inputs = (gate_inputs + log_alpha) / beta
106 | gate_inputs = torch.sigmoid(gate_inputs)
107 | else:
108 | gate_inputs = torch.sigmoid(log_alpha)
109 |
110 | stretched_values = gate_inputs * (zeta - gamma) + gamma
111 | cliped = torch.clamp(stretched_values, min=0.0, max=1.0)
112 | return cliped
113 |
114 | def l0_norm(self, log_alpha, beta):
115 | gamma = self.config.gamma
116 | zeta = self.config.zeta
117 | temp = torch.tensor(-gamma / zeta).to(self.device)
118 | reg_per_weight = torch.sigmoid(log_alpha - beta * torch.log(temp).to(torch.float32))
119 | return torch.mean(reg_per_weight)
120 |
121 | def lossl0(self, temperature):
122 | l0_loss = torch.zeros([], dtype=torch.float32).to(self.device)
123 | for weight in self.edge_weights:
124 | l0_loss += self.l0_norm(weight, temperature)
125 | self.edge_weights = []
126 | return l0_loss
127 |
128 | def nuclear(self):
129 | nuclear_loss = torch.zeros([], dtype=torch.float32)
130 | values = []
131 | if self.config.lambda3 == 0:
132 | return 0
133 | for mask in self.maskes:
134 | mask = torch.squeeze(mask)
135 | support = torch.sparse_coo_tensor(indices=self.indices.t(),
136 | values=mask,
137 | size=self.adj_mat.shape)
138 | support_dense = support.to_dense()
139 | support_trans = support_dense.t()
140 |
141 | AA = torch.matmul(support_trans, support_dense)
142 | if SVD_PI:
143 | row_ind = self.indices[:, 0].cpu().numpy()
144 | col_ind = self.indices[:, 1].cpu().numpy()
145 | support_csc = csc_matrix((mask.detach().cpu().numpy(), (row_ind, col_ind)))
146 | k = self.config.k_svd
147 | u, s, vh = svds(support_csc, k=k)
148 |
149 | u = torch.tensor(u.copy())
150 | s = torch.tensor(s.copy())
151 | vh = torch.tensor(vh.copy())
152 |
153 | for i in range(k):
154 | vi = torch.unsqueeze(vh[i], -1).to(self.device)
155 | for ite in range(1):
156 | vi = torch.matmul(AA, vi)
157 | vi_norm = torch.linalg.norm(vi)
158 | vi = vi / vi_norm
159 |
160 | vmv = torch.matmul(vi.t(), torch.matmul(AA, vi))
161 | vv = torch.matmul(vi.t(), vi)
162 |
163 | t_vi = torch.sqrt(torch.abs(vmv / vv))
164 | values.append(t_vi)
165 |
166 | if k > 1:
167 | AA_minus = torch.matmul(AA, torch.matmul(vi, torch.transpose(vi)))
168 | AA = AA - AA_minus
169 | else:
170 | trace = torch.linalg.trace(AA)
171 | values.append(torch.sum(trace))
172 |
173 | nuclear_loss = torch.sum(torch.stack(values, dim=0), dim=0)
174 |
175 | return nuclear_loss
176 |
177 | def feedforward(self, inputs, training=None):
178 | if training:
179 | temperature = inputs
180 | else:
181 | temperature = 1.0
182 |
183 | self.edge_maskes = []
184 | self.maskes = []
185 | layer_index = 0
186 | x = self.features
187 |
188 | for i in range(len(self.layers)):
189 | xs = []
190 | layer = self.layers[i]
191 | for l in range(self.config.L):
192 | f1_features = torch.index_select(x, 0, self.row)
193 | f2_features = torch.index_select(x, 0, self.col)
194 | weight = self.get_attention(f1_features, f2_features, l=layer_index, training=True)
195 | mask = self.hard_concrete_sample(weight, temperature, True)
196 | self.edge_weights.append(weight)
197 | self.maskes.append(mask)
198 | mask = torch.squeeze(mask)
199 | adj = torch.sparse_coo_tensor(indices=self.indices.t(),
200 | values=mask,
201 | size=self.adj_mat.shape)
202 | # norm
203 | adj = torch.eye(self.node_num, dtype=torch.float32).to_sparse().to(self.device) + adj
204 |
205 | row = adj.coalesce().indices().t()[:, 0]
206 | col = adj.coalesce().indices().t()[:, 1]
207 |
208 | rowsum = torch.sparse.sum(adj, dim=-1).to_dense()
209 | d_inv_sqrt = torch.reshape(torch.pow(rowsum, -0.5), [-1])
210 | d_inv_sqrt = torch.clamp(d_inv_sqrt, 0, 10.0)
211 | row_inv_sqrt = torch.index_select(d_inv_sqrt, 0, row)
212 | col_inv_sqrt = torch.index_select(d_inv_sqrt, 0, col)
213 | values = torch.mul(adj.coalesce().values(), row_inv_sqrt)
214 | values = torch.mul(values, col_inv_sqrt)
215 |
216 | support = torch.sparse_coo_tensor(indices=adj.coalesce().indices(),
217 | values = values,
218 | size=adj.shape).to_dense().to(torch.float32)
219 | nextx = layer(x, support)
220 | if i != len(self.layers)-1:
221 | nextx = F.relu(nextx)
222 | xs.append(nextx)
223 | x = torch.mean(torch.stack(xs, dim=0), dim=0)
224 | layer_index += 1
225 | return x
226 |
227 | def compute_loss(self, preds, temperature, labels, train_mask):
228 | all_preds = torch.cat(preds, dim=0)
229 | mean_preds = torch.mean(torch.stack(preds, dim=0), dim=0)
230 | mean_preds = torch.squeeze(mean_preds, dim=0)
231 | diff = mean_preds - all_preds
232 |
233 | consistency_loss = F.mse_loss(diff, torch.zeros_like(diff))
234 |
235 | cross_loss = self.criterion(mean_preds[train_mask], labels[train_mask])
236 | lossl0 = self.lossl0(temperature)
237 | nuclear = self.nuclear()
238 | loss = cross_loss + self.config.lambda1*lossl0 + self.config.lambda3*nuclear + self.config.coff_consis*consistency_loss
239 | return loss
240 |
241 | def test(self, labels, mask):
242 | with torch.no_grad():
243 | output = self.feedforward(None, False)
244 | acc = accuracy(output[mask], labels[mask]).item()
245 | return acc
246 |
247 |
248 | def fit(self, features, adj, labels, train_mask, val_mask, test_mask):
249 | self.optimizer = torch.optim.Adam(self.parameters(), weight_decay=self.config.weight_decay, lr=self.config.lr)
250 | self.criterion = nn.CrossEntropyLoss()
251 | labels = labels.to(self.device)
252 |
253 | self.adj = adj.to(self.device)
254 | self.features = features.to(self.device)
255 | self.indices = adj.coalesce().indices().t().to(self.device)
256 | self.node_num = features.shape[0]
257 | self.values = adj.coalesce().values()
258 | self.shape = adj.shape
259 | self.row = self.indices[:, 0]
260 | self.col = self.indices[:, 1]
261 | self.adj_mat = adj
262 |
263 | best_val_result, best_test_result = float('-inf'), 0
264 | patience = 0
265 | for epoch in range(self.config.epochs):
266 | temperature = max(0.05, self.config.init_temperature * pow(self.config.temperature_decay, epoch))
267 | preds = []
268 |
269 | for _ in range(self.config.outL):
270 | output = self.feedforward(temperature, True)
271 | preds.append(torch.unsqueeze(output, 0))
272 |
273 | loss = self.compute_loss(preds, temperature, labels, train_mask)
274 |
275 | self.optimizer.zero_grad()
276 | loss.backward()
277 | self.optimizer.step()
278 |
279 | train_result = self.test(labels, train_mask)
280 | val_result = self.test(labels, val_mask)
281 | test_result = self.test(labels, test_mask)
282 | if val_result > best_val_result:
283 | best_val_result = val_result
284 | best_test_result = test_result
285 | patience = 0
286 | else:
287 | patience += 1
288 |
289 | print(f'Epoch: {epoch: 02d}, '
290 | f'Loss: {loss.item():.4f}, '
291 | f'Train: {100 * train_result:.2f}%, '
292 | f'Valid: {100 * val_result:.2f}%, '
293 | f'Test: {100 * test_result:.2f}%')
294 |
295 | if patience > self.config.patience:
296 | print("Early stopping...")
297 | break
298 |
299 | print("Best Test Accuracy: ", best_test_result)
--------------------------------------------------------------------------------
/model/slaps.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.utils import *
3 | from GSL.learner import *
4 | from GSL.encoder import *
5 | from GSL.metric import *
6 | from GSL.processor import *
7 | import math
8 | import torch
9 | import torch.nn as nn
10 |
11 | class SLAPS(BaseModel):
12 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device, data):
13 | super(SLAPS, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
14 |
15 | self.features = data.features
16 | #self.features = features
17 | self.Adj = None
18 | self.device = device
19 |
20 | self.model1 = GCN_DAE(config=self.config, nlayers=self.config.nlayers_adj, in_dim=num_features, hidden_dim=self.config.hidden_adj,
21 | num_classes=num_features,
22 | dropout=self.config.dropout1, dropout_adj=self.config.dropout_adj1,
23 | features=self.features.cpu(), k=self.config.k, knn_metric=self.config.knn_metric, i_=self.config.i,
24 | non_linearity=self.config.non_linearity, normalization=self.config.normalization,
25 | mlp_h=self.config.mlp_h,
26 | learner=self.config.learner, sparse=self.config.sparse,
27 | mlp_act=self.config.mlp_act).to(self.device)
28 | self.model2 = GCN_C(in_channels=num_features, hidden_channels=self.config.hidden, out_channels=num_classes,
29 | num_layers=self.config.nlayers, dropout=self.config.dropout2,
30 | dropout_adj=self.config.dropout_adj2,
31 | sparse=self.config.sparse).to(self.device)
32 |
33 | def get_loss_masked_features(self, model, features, mask, ogb, noise, loss_t):
34 |
35 | features = features.to(self.device)
36 |
37 | if ogb:
38 | if noise == 'mask':
39 | masked_features = features * (1 - mask)
40 | elif noise == "normal":
41 | noise = torch.normal(0.0, 1.0, size=features.shape).to(self.device)
42 | masked_features = features + (noise * mask)
43 |
44 | logits, Adj = model(features, masked_features)
45 | indices = mask > 0
46 |
47 | if loss_t == 'bce':
48 | features_sign = torch.sign(features).to(self.device) * 0.5 + 0.5
49 | loss = F.binary_cross_entropy_with_logits(logits[indices], features_sign[indices], reduction='mean')
50 | elif loss_t == 'mse':
51 | loss = F.mse_loss(logits[indices], features[indices], reduction='mean')
52 | else:
53 | masked_features = features * (1 - mask)
54 | logits, Adj = model(features, masked_features)
55 | indices = mask > 0
56 | loss = F.binary_cross_entropy_with_logits(logits[indices], features[indices], reduction='mean')
57 | return loss, Adj
58 |
59 | def get_loss_learnable_adj(self, model, mask, features, labels, Adj):
60 |
61 | features = features.to(self.device)
62 | mask = mask.to(self.device)
63 | labels = labels.to(self.device)
64 |
65 | logits = model(features, Adj)
66 | logp = F.log_softmax(logits, 1)
67 | loss = F.nll_loss(logp[mask], labels[mask], reduction='mean')
68 | accu = accuracy(logp[mask], labels[mask])
69 | return loss, accu
70 |
71 | def fit(self, data, split_num=0):
72 | features, labels = data.features, data.labels
73 | if data.name in ['cornell', 'texas', 'wisconsin', 'actor']:
74 | train_mask = data.train_masks[split_num % 10]
75 | val_mask = data.val_masks[split_num % 10]
76 | test_mask = data.test_masks[split_num % 10]
77 | else:
78 | train_mask, val_mask, test_mask = data.train_mask, data.val_mask, data.test_mask
79 |
80 | optimizer1 = torch.optim.Adam(self.model1.parameters(), lr=self.config.lr_adj, weight_decay=self.config.w_decay_adj)
81 | optimizer2 = torch.optim.Adam(self.model2.parameters(), lr=self.config.lr, weight_decay=self.config.w_decay)
82 |
83 | best_val_result = 0.0
84 |
85 | for epoch in range(1, self.config.epochs_adj + 1):
86 | self.model1.train()
87 | self.model2.train()
88 |
89 | optimizer1.zero_grad()
90 | optimizer2.zero_grad()
91 |
92 | if self.config.dataset.startswith('ogb') or self.config.dataset in ["wine", "digits", "breast_cancer"]:
93 | mask = get_random_mask_ogb(features, self.config.ratio).to(self.device)
94 | ogb = True
95 | elif self.config.dataset == "20news10":
96 | mask = get_random_mask(features, self.config.ratio, self.config.nr).to(self.device)
97 | ogb = True
98 | else:
99 | mask = get_random_mask(features, self.config.ratio, self.config.nr).to(self.device)
100 | ogb = False
101 |
102 | if epoch < self.config.epochs_adj // self.config.epoch_d:
103 | self.model2.eval()
104 | loss1, Adj = self.get_loss_masked_features(self.model1, features, mask, ogb, self.config.noise, self.config.loss)
105 | loss2 = torch.tensor(0).to(self.device)
106 | else:
107 | loss1, Adj = self.get_loss_masked_features(self.model1, features, mask, ogb, self.config.noise, self.config.loss)
108 | loss2, train_result = self.get_loss_learnable_adj(self.model2, train_mask, features, labels, Adj)
109 |
110 | loss = loss1 * self.config.lambda_ + loss2
111 | loss.backward()
112 | optimizer1.step()
113 | optimizer2.step()
114 |
115 | if epoch >= self.config.epochs_adj // self.config.epoch_d and epoch % 1 == 0:
116 | val_result = self.test(val_mask, features, labels, Adj)
117 | test_result = self.test(test_mask, features, labels, Adj)
118 | if val_result > best_val_result:
119 | # best_weight = deepcopy(self.state_dict())
120 | best_val_result = val_result
121 | self.best_result = test_result
122 | self.Adj = Adj
123 | print(f'Epoch: {epoch: 02d}, '
124 | f'Loss: {loss:.4f}, '
125 | f'Train: {100 * train_result.item():.2f}%, '
126 | f'Valid: {100 * val_result:.2f}%, '
127 | f'Test: {100 * test_result:.2f}%')
128 |
129 | def test(self, test_mask, features, labels, adj):
130 | with torch.no_grad():
131 | self.model1.eval()
132 | self.model2.eval()
133 | test_loss_, test_accu_ = self.get_loss_learnable_adj(self.model2, test_mask, features, labels, adj)
134 | return test_accu_.item()
135 |
136 | class GCN_DAE(nn.Module):
137 | def __init__(self, config, nlayers, in_dim, hidden_dim, num_classes, dropout, dropout_adj, features, k, knn_metric, i_,
138 | non_linearity, normalization, mlp_h, learner, sparse, mlp_act):
139 | super(GCN_DAE, self).__init__()
140 |
141 | self.layers = nn.ModuleList()
142 |
143 | if sparse:
144 | self.layers.append(GCNConv_dgl(in_dim, hidden_dim))
145 | for _ in range(nlayers - 2):
146 | self.layers.append(GCNConv_dgl(hidden_dim, hidden_dim))
147 | self.layers.append(GCNConv_dgl(hidden_dim, num_classes))
148 |
149 | else:
150 | self.layers.append(GCNConv(in_dim, hidden_dim))
151 | for i in range(nlayers - 2):
152 | self.layers.append(GCNConv(hidden_dim, hidden_dim))
153 | self.layers.append(GCNConv(hidden_dim, num_classes))
154 |
155 | self.dropout = dropout
156 | self.dropout_adj = nn.Dropout(p=dropout_adj)
157 | self.dropout_adj_p = dropout_adj
158 | self.k = k
159 | self.knn_metric = knn_metric
160 | self.i = i_
161 | self.non_linearity = non_linearity
162 | self.normalization = normalization
163 | self.nnodes = features.shape[0]
164 | self.mlp_h = mlp_h
165 | self.sparse = sparse
166 |
167 | print('Graph Structure Learner: {}'.format(learner))
168 | if learner == 'FP':
169 | metric = CosineSimilarity()
170 | processors = [KNNSparsify(k), Discretize(), LinearTransform(config.i)]
171 | non_linearize = NonLinearize(config.non_linearity, alpha=config.i)
172 | self.graph_gen = FullParam(metric, processors, features, sparse, non_linearize)
173 | elif learner == 'MLP':
174 | metric = CosineSimilarity()
175 | processors = [KNNSparsify(k)]
176 | activation = ({'relu': F.relu, 'prelu': F.prelu, 'tanh': F.tanh})[mlp_act]
177 | self.graph_gen = MLPLearner(metric, processors, 2, features.shape[1],
178 | math.floor(math.sqrt(features.shape[1] * self.mlp_h)), activation, sparse, k=k)
179 |
180 | def get_adj(self, h):
181 | Adj_ = self.graph_gen(h)
182 | if not self.sparse:
183 | Adj_ = symmetrize(Adj_)
184 | Adj_ = normalize(Adj_, self.normalization, self.sparse)
185 | return Adj_
186 |
187 | def forward(self, features, x): # x corresponds to masked_fearures
188 | Adj_ = self.get_adj(features)
189 | if self.sparse:
190 | Adj = Adj_
191 | Adj.edata['w'] = F.dropout(Adj.edata['w'], p=self.dropout_adj_p, training=self.training)
192 | else:
193 | Adj = self.dropout_adj(Adj_)
194 | for i, conv in enumerate(self.layers[:-1]):
195 | x = conv(x, Adj)
196 | x = F.relu(x)
197 | x = F.dropout(x, p=self.dropout, training=self.training)
198 | x = self.layers[-1](x, Adj)
199 | return x, Adj_
200 |
201 |
202 |
203 | class GCN_C(nn.Module):
204 |
205 | def __init__(self, in_channels, hidden_channels, out_channels, num_layers, dropout, dropout_adj, sparse):
206 | super(GCN_C, self).__init__()
207 |
208 | self.layers = nn.ModuleList()
209 |
210 | if sparse:
211 | self.layers.append(GCNConv_dgl(in_channels, hidden_channels))
212 | for _ in range(num_layers - 2):
213 | self.layers.append(GCNConv_dgl(hidden_channels, hidden_channels))
214 | self.layers.append(GCNConv_dgl(hidden_channels, out_channels))
215 | else:
216 | self.layers.append(GCNConv(in_channels, hidden_channels))
217 | for i in range(num_layers - 2):
218 | self.layers.append(GCNConv(hidden_channels, hidden_channels))
219 | self.layers.append(GCNConv(hidden_channels, out_channels))
220 |
221 | self.dropout = dropout
222 | self.dropout_adj = nn.Dropout(p=dropout_adj)
223 | self.dropout_adj_p = dropout_adj
224 | self.sparse = sparse
225 |
226 | def forward(self, x, adj_t):
227 |
228 | if self.sparse:
229 | Adj = adj_t
230 | Adj.edata['w'] = F.dropout(Adj.edata['w'], p=self.dropout_adj_p, training=self.training)
231 | else:
232 | Adj = self.dropout_adj(adj_t)
233 |
234 | for i, conv in enumerate(self.layers[:-1]):
235 | x = conv(x, Adj)
236 | x = F.relu(x)
237 | x = F.dropout(x, p=self.dropout, training=self.training)
238 | x = self.layers[-1](x, Adj)
239 | return x
--------------------------------------------------------------------------------
/model/sublime.py:
--------------------------------------------------------------------------------
1 | from GSL.model import BaseModel
2 | from GSL.utils import *
3 | from GSL.learner import *
4 | from GSL.encoder import *
5 | from GSL.metric import *
6 | from GSL.processor import *
7 | from GSL.eval import ClsEvaluator
8 | import torch
9 | import copy
10 | import torch.nn as nn
11 | import torch.nn.functional as F
12 | from scipy.sparse import coo_matrix
13 |
14 | #test git push
15 |
16 | class SUBLIME(BaseModel):
17 | '''
18 | Towards Unsupervised Deep Graph Structure Learning (WWW 2022')
19 | '''
20 | # def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device, params):
21 | # super(SUBLIME, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device, params)
22 | def __init__(self, num_features, num_classes, metric, config_path, dataset_name, device):
23 | super(SUBLIME, self).__init__(num_features, num_classes, metric, config_path, dataset_name, device)
24 | self.num_features = num_features
25 | self.num_classes = num_classes
26 | self.device = device
27 |
28 | def fit(self, dataset, split_num=0):
29 | adj, features, labels = dataset.adj.copy(), dataset.features.clone(), dataset.labels
30 | if dataset.name in ['cornell', 'texas', 'wisconsin', 'actor']:
31 | train_mask = dataset.train_masks[split_num % 10]
32 | val_mask = dataset.val_masks[split_num % 10]
33 | test_mask = dataset.test_masks[split_num % 10]
34 | else:
35 | train_mask, val_mask, test_mask = dataset.train_mask, dataset.val_mask, dataset.test_mask
36 |
37 |
38 | if self.config.mode == 'structure_inference':
39 | if self.config.sparse:
40 | anchor_adj_raw = torch_sparse_eye(features.shape[0])
41 | else:
42 | anchor_adj_raw = torch.eye(features.shape[0])
43 | elif self.config.mode == 'structure_refinement':
44 | # if self.config.sparse:
45 | # anchor_adj_raw = adj
46 | # else:
47 | # anchor_adj_raw = torch.from_numpy(adj)
48 | #anchor_adj_raw = adj
49 | coo = coo_matrix(adj)
50 | values = coo.data
51 | indices = np.vstack((coo.row, coo.col))
52 | i = torch.LongTensor(indices)
53 | v = torch.FloatTensor(values)
54 | shape = coo.shape
55 | adj = torch.sparse_coo_tensor(i, v, torch.Size(shape)).to(self.device)
56 | anchor_adj_raw = adj.to_dense()
57 |
58 | anchor_adj = normalize(anchor_adj_raw, 'sym', self.config['sparse'])
59 |
60 | if self.config.sparse:
61 | anchor_adj_torch_sparse = copy.deepcopy(anchor_adj)
62 | anchor_adj = torch_sparse_to_dgl_graph(anchor_adj)
63 |
64 | activation = ({'relu': F.relu, 'prelu': F.prelu, 'tanh': F.tanh})[self.config.activation]
65 |
66 | #TODO: Graph Learner Initialize
67 | metric = CosineSimilarity()
68 | knnsparsify = KNNSparsify(self.config.k, discrete=True, self_loop=False)
69 | linear_trans = LinearTransform(6)
70 | processors = [knnsparsify, linear_trans]
71 | nonlinear = NonLinearize(non_linearity='elu')
72 | if self.config.learner == 'mlp':
73 | graph_learner = MLPLearner(metric, processors, 2, features.shape[1], features.shape[1], activation, self.config.sparse, self.config.k)
74 | elif self.config.learner == 'full':
75 | graph_learner = FullParam(metric, processors, features.cpu(), self.config.sparse, nonlinear)
76 | elif self.config.learner == 'gnn':
77 | graph_learner = GNNLearner(metric, processors, 2, features.shape[1], features.shape[1], activation)
78 | elif self.config.learner == 'att':
79 | graph_learner = AttLearner(metric, processors, 2, features.shape[1], activation, self.config.sparse)
80 |
81 | model = GCL(nlayers=self.config.num_layers, in_dim=self.num_features, hidden_dim=self.config.num_hidden,
82 | emb_dim=self.config.num_rep_dim, proj_dim=self.config.num_proj_dim,
83 | dropout=self.config.dropout, dropout_adj=self.config.dropedge_rate, sparse=self.config.sparse)
84 |
85 | optimizer_cl = torch.optim.Adam(model.parameters(), lr=self.config.lr, weight_decay=self.config.weight_decay)
86 | optimizer_learner = torch.optim.Adam(graph_learner.parameters(), lr=self.config.lr, weight_decay=self.config.weight_decay)
87 |
88 | model = model.to(self.device)
89 | graph_learner = graph_learner.to(self.device)
90 | train_mask = train_mask.to(self.device)
91 | val_mask = val_mask.to(self.device)
92 | test_mask = test_mask.to(self.device)
93 | features = features.to(self.device)
94 | labels = labels.to(self.device)
95 | if not self.config.sparse:
96 | anchor_adj = anchor_adj.to(self.device)
97 |
98 | best_val_result = 0
99 | for epoch in range(self.config.num_epochs):
100 | # Training
101 | model.train()
102 | graph_learner.train()
103 | loss, Adj = self.loss_gcl(model, graph_learner, features, anchor_adj)
104 |
105 | optimizer_cl.zero_grad()
106 | optimizer_learner.zero_grad()
107 | loss.backward()
108 | optimizer_cl.step()
109 | optimizer_learner.step()
110 |
111 | # Structure Bootstrapping
112 | if (1 - self.config.tau) and (self.config.c == 0 or epoch % self.config.c == 0):
113 | if self.config.sparse:
114 | learned_adj_torch_sparse = dgl_graph_to_torch_sparse(Adj)
115 | anchor_adj_torch_sparse = anchor_adj_torch_sparse * self.config.tau \
116 | + learned_adj_torch_sparse * (1 - self.config.tau)
117 | anchor_adj = torch_sparse_to_dgl_graph(anchor_adj_torch_sparse)
118 | else:
119 | anchor_adj = anchor_adj * self.config.tau + Adj.detach() * (1 - self.config.tau)
120 |
121 | # Evaluate
122 | if epoch % self.config.eval_step == 0:
123 | model.eval()
124 | graph_learner.eval()
125 | f_adj = Adj
126 |
127 | ClsEval = ClsEvaluator('GCN', self.config, self.num_features, self.num_classes, self.device)
128 | result = ClsEval(features, f_adj, train_mask, \
129 | val_mask, test_mask, labels)
130 | val_result, test_result = result['Acc_val'], result['Acc_test']
131 | if val_result > best_val_result:
132 | self.best_result = test_result
133 | best_val_result = val_result
134 | self.Adj = anchor_adj
135 |
136 | print(f'Epoch: {epoch: 02d}, '
137 | f'Loss: {loss:.4f}, '
138 | f'Valid: {100 * val_result:.2f}%, '
139 | f'Test: {100 * test_result:.2f}%')
140 |
141 |
142 |
143 | def loss_gcl(self, model, graph_learner, features, anchor_adj):
144 |
145 | # view 1: anchor graph
146 | if self.config.maskfeat_rate_anchor:
147 | mask_v1, _ = get_feat_mask(features, self.config.maskfeat_rate_anchor)
148 | features_v1 = features * (1 - mask_v1)
149 | else:
150 | features_v1 = copy.deepcopy(features)
151 |
152 | z1, _ = model(features_v1, anchor_adj, 'anchor')
153 |
154 | # view 2: learned graph
155 | if self.config.maskfeat_rate_learner:
156 | mask, _ = get_feat_mask(features, self.config.maskfeat_rate_learner)
157 | features_v2 = features * (1 - mask)
158 | else:
159 | features_v2 = copy.deepcopy(features)
160 |
161 | learned_adj = graph_learner(features)
162 | if not self.config.sparse:
163 | learned_adj = symmetrize(learned_adj)
164 | learned_adj = normalize(learned_adj, 'sym', self.config.sparse)
165 |
166 | z2, _ = model(features_v2, learned_adj, 'learner')
167 |
168 | # compute loss
169 | if self.config.contrast_batch_size:
170 | node_idxs = list(range(features.shape[0]))
171 | # random.shuffle(node_idxs)
172 | batches = split_batch(node_idxs, self.config.contrast_batch_size)
173 | loss = 0
174 | for batch in batches:
175 | weight = len(batch) / features.shape[0]
176 | loss += model.calc_loss(z1[batch], z2[batch]) * weight
177 | else:
178 | loss = model.calc_loss(z1, z2)
179 |
180 | return loss, learned_adj
181 |
182 |
183 | class GCL(nn.Module):
184 | def __init__(self, nlayers, in_dim, hidden_dim, emb_dim, proj_dim, dropout, dropout_adj, sparse):
185 | super(GCL, self).__init__()
186 |
187 | self.encoder = GraphEncoder(nlayers, in_dim, hidden_dim, emb_dim, proj_dim, dropout, dropout_adj, sparse)
188 |
189 | def forward(self, x, Adj_, branch=None):
190 | z, embedding = self.encoder(x, Adj_, branch)
191 | return z, embedding
192 |
193 | @staticmethod
194 | def calc_loss(x, x_aug, temperature=0.2, sym=True):
195 | batch_size, _ = x.size()
196 | x_abs = x.norm(dim=1)
197 | x_aug_abs = x_aug.norm(dim=1)
198 |
199 | sim_matrix = torch.einsum('ik,jk->ij', x, x_aug) / torch.einsum('i,j->ij', x_abs, x_aug_abs)
200 | sim_matrix = torch.exp(sim_matrix / temperature)
201 | pos_sim = sim_matrix[range(batch_size), range(batch_size)]
202 | if sym:
203 | loss_0 = pos_sim / (sim_matrix.sum(dim=0) - pos_sim)
204 | loss_1 = pos_sim / (sim_matrix.sum(dim=1) - pos_sim)
205 |
206 | loss_0 = - torch.log(loss_0).mean()
207 | loss_1 = - torch.log(loss_1).mean()
208 | loss = (loss_0 + loss_1) / 2.0
209 | return loss
210 | else:
211 | loss_1 = pos_sim / (sim_matrix.sum(dim=1) - pos_sim)
212 | loss_1 = - torch.log(loss_1).mean()
213 | return loss_1
--------------------------------------------------------------------------------
/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSL-Benchmark/GSLB/375cc1c20314360a927527f8d8528773fe1b0abc/pipeline.png
--------------------------------------------------------------------------------
/processor.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import torch
3 | import torch.nn.functional as F
4 | from sklearn.neighbors import kneighbors_graph
5 | from torch.distributions.relaxed_bernoulli import RelaxedBernoulli
6 |
7 |
8 | class KNNSparsify:
9 | def __init__(self, k, discrete=False, self_loop=True):
10 | super(KNNSparsify, self).__init__()
11 | self.k = k
12 | self.discrete = discrete
13 | self.self_loop = self_loop
14 |
15 | def __call__(self, adj):
16 | _, indices = adj.topk(k=int(self.k+1), dim=-1)
17 | assert torch.max(indices) < adj.shape[1]
18 | mask = torch.zeros(adj.shape).to(adj.device)
19 | mask[torch.arange(adj.shape[0]).view(-1, 1), indices] = 1.
20 |
21 | mask.requires_grad = False
22 | if self.discrete:
23 | sparse_adj = mask.to(torch.float)
24 | else:
25 | sparse_adj = adj * mask
26 |
27 | if not self.self_loop:
28 | sparse_adj.fill_diagonal_(0)
29 | return sparse_adj
30 |
31 |
32 | class ThresholdSparsify:
33 | def __init__(self, threshold):
34 | super(ThresholdSparsify, self).__init__()
35 | self.threshold = threshold
36 |
37 | def __call__(self, adj):
38 | return torch.where(adj < self.threshold, torch.zeros_like(adj), adj)
39 |
40 |
41 | class ProbabilitySparsify:
42 | def __init__(self, temperature=0.1):
43 | self.temperature = temperature
44 |
45 | def __call__(self, prob):
46 | prob = torch.clamp(prob, 0.01, 0.99)
47 | adj = RelaxedBernoulli(temperature=torch.Tensor([self.temperature]).to(prob.device),
48 | probs=prob).rsample()
49 | eps = 0.5
50 | mask = (adj > eps).detach().float()
51 | adj = adj * mask + 0.0 * (1 - mask)
52 | return adj
53 |
54 |
55 | class Discretize:
56 | def __init__(self):
57 | super(Discretize, self).__init__()
58 |
59 | def __call__(self, adj):
60 | adj[adj != 0] = 1.0
61 | return adj
62 |
63 |
64 | class AddEye:
65 | def __init__(self):
66 | super(AddEye, self).__init__()
67 |
68 | def __call__(self, adj):
69 | adj += torch.eye(adj.shape[0]).to(adj.device)
70 | return adj
71 |
72 |
73 | class LinearTransform:
74 | def __init__(self, alpha):
75 | super(LinearTransform, self).__init__()
76 | self.alpha = alpha
77 |
78 | def __call__(self, adj):
79 | adj = adj * self.alpha - self.alpha
80 | return adj
81 |
82 |
83 | class NonLinearize:
84 | def __init__(self, non_linearity='relu', alpha=1.0):
85 | super(NonLinearize, self).__init__()
86 | self.non_linearity = non_linearity
87 | self.alpha = alpha
88 |
89 | def __call__(self, adj):
90 | if self.non_linearity == 'elu':
91 | return F.elu(adj) + 1
92 | elif self.non_linearity == 'relu':
93 | return F.relu(adj)
94 | elif self.non_linearity == 'none':
95 | return adj
96 | else:
97 | raise NameError('We dont support the non-linearity yet')
98 |
99 |
100 | class Symmetrize:
101 | def __init__(self):
102 | super(Symmetrize, self).__init__()
103 |
104 | def __call__(self, adj):
105 | return (adj + adj.T) / 2
106 |
107 |
108 | class Normalize:
109 | def __init__(self, mode='sym', eos=1e-10):
110 | super(Normalize, self).__init__()
111 | self.mode = mode
112 | self.EOS = eos
113 |
114 | def __call__(self, adj):
115 | if self.mode == "sym":
116 | inv_sqrt_degree = 1. / (torch.sqrt(adj.sum(dim=1, keepdim=False)) + self.EOS)
117 | return inv_sqrt_degree[:, None] * adj * inv_sqrt_degree[None, :]
118 | elif self.mode == "row":
119 | inv_degree = 1. / (adj.sum(dim=1, keepdim=False) + self.EOS)
120 | return inv_degree[:, None] * adj
121 | elif self.mode == "row_softmax":
122 | return F.softmax(adj, dim=1)
123 | elif self.mode == "row_softmax_sparse":
124 | return torch.sparse.softmax(adj.to_sparse(), dim=1).to_dense()
125 | else:
126 | raise Exception('We dont support the normalization mode')
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel"]
3 | build-backend = "setuptools.build_meta"
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = GSLB
3 | version = 0.1.0
4 | author = Liang Wang
5 | author_email = wangliang.leon20@gmail.com
6 | description = GSLB: Graph Structure Learning Benchmark
7 | long_description = file: README.md
8 | long_description_content_type = text/markdown
9 | url = https://github.com/GSL-Benchmark/GSLB
10 | project_urls =
11 | Bug Tracker = https://github.com/GSL-Benchmark/GSLB/issues
12 | classifiers =
13 | Programming Language :: Python :: 3
14 | License :: OSI Approved :: MIT License
15 | Operating System :: OS Independent
16 | keywords =
17 | PyTorch
18 | Graph-Neural-Networks
19 | Graph-Structure-Learning
20 |
21 | [options]
22 | package_dir =
23 | = .
24 | packages = find:
25 | python_requires = >=3.8
26 | install_requires =
27 | torch >= 1.13
28 | dgl >= 1.1
29 | numpy
30 | scipy
31 | networkx
32 | scikit-learn
33 | tqdm
34 | ogb
35 | easydict
36 | PyYAML
37 |
38 | [options.packages.find]
39 | where = .
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup()
--------------------------------------------------------------------------------