├── .gitignore ├── README.md └── gnnexplainer_cora.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GNNExplainer Tutorial 2 | 3 | 4 | 5 | ## 1. Reference Paper 6 | 7 | Ying, Zhitao and Bourgeois, Dylan and You, Jiaxuan and Zitnik, Marinka and Leskovec, Jure. __"GNNExplainer: Generating Explanations for Graph Neural Networks"__. Advances in Neural Information Processing Systems 32. 2019. 8 | 9 | Link: https://arxiv.org/abs/1903.03894 10 | 11 | # 2. Reference Code 12 | 13 | This tutorial is based on the example provided by the official Pytorch-Geometric repository. 14 | 15 | Link: 16 | 17 | ## 3. Requirements 18 | 19 | * numpy 20 | * scipy 21 | * matplotlib 22 | * pytorch 23 | * pytorch-geometric 24 | 25 | ## 4. Dataset 26 | 27 | Cora dataset from [1]. 28 | 29 | [1] Yang, Zhilin, William Cohen, and Ruslan Salakhudinov. "Revisiting semi-supervised learning with graph embeddings." International conference on machine learning. 2016. 30 | 31 | ## 5. Contacts 32 | 33 | Please contact Juneyong Yang(laoconeth@kaist.ac.kr) or raise an issue in this repo. 34 | 35 | 36 | 37 | # XAI Project 38 | 39 | **This work was supported by Institute for Information & Communications Technology Promotion (IITP) grant funded by the Korea government (MSIT) (No.2017-0-01779, A machine learning and statistical inference framework for explainable artificial intelligence)** 40 | 41 | + Project Name : A machine learning and statistical inference framework for explainable artificial intelligence (의사결정 이유를 설명할 수 있는 인간 수준의 학습·추론 프레임워크 개발) 42 | 43 | + Managed by Ministry of Science and ICT/XAIC 44 | 45 | + Participated Affiliation : UNIST, Korea Univ., Yonsei Univ., KAIST, AItrics 46 | 47 | + Web Site : 48 | -------------------------------------------------------------------------------- /gnnexplainer_cora.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# __GNNExplainer Tutorial__\n", 8 | "\n", 9 | "Disclaimer: This tutorial assumes that you are familiar with Pytorch.\n", 10 | "\n", 11 | "In this tutorial, We will use Pytorch-Geometric(https://pytorch-geometric.readthedocs.io/en/latest/)[[1]], which is a GNN library built on top of Pytorch.\n", 12 | "\n", 13 | "This tutorial is based on the example provided by Pytorch-Geometric(https://github.com/rusty1s/pytorch_geometric/blob/master/examples/gnn_explainer.py).\n", 14 | "\n", 15 | "\n", 16 | "[[1]] Fey, Matthias and Lenssen, Jan E. \"Fast Graph Representation Learning with PyTorch Geometric.\" ICLR Workshop on Representation Learning on Graphs and Manifolds. 2019." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": { 23 | "tags": [] 24 | }, 25 | "outputs": [ 26 | { 27 | "output_type": "stream", 28 | "name": "stderr", 29 | "text": "RDKit WARNING: [22:48:10] Enabling RDKit 2019.09.3 jupyter extensions\n" 30 | } 31 | ], 32 | "source": [ 33 | "%matplotlib inline\n", 34 | "\n", 35 | "from torch_geometric.data import Data, DataLoader\n", 36 | "from torch_geometric.datasets import TUDataset, Planetoid\n", 37 | "from torch_geometric.nn import GCNConv, Set2Set, GNNExplainer\n", 38 | "import torch_geometric.transforms as T\n", 39 | "import torch\n", 40 | "import torch.nn.functional as F\n", 41 | "import os\n", 42 | "from tqdm import tqdm, trange\n", 43 | "\n", 44 | "import matplotlib.pyplot as plt\n", 45 | "\n", 46 | "\n", 47 | "\n" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "# 1. Training a GNN with Pytorch-Geometric" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "## 1. Dataset\n", 62 | "\n", 63 | "Here, we load our dataset. We use Cora as in Yang et al.[[1]], which is readily provided by Pytorch-Geometric(https://pytorch-geometric.readthedocs.io/en/latest/modules/datasets.html?highlight=cora#torch_geometric.datasets.Planetoid). This dataset is a node classification dataset: that is, given nodes and their features, the GNN must predict the correct label for the node. For each node, there is an input feature vector $x$ and label $y$. \n", 64 | "\n", 65 | "*Currently, the GNNExplainer provided by Pytorch-Geometric can only handle networks that are designed for __node classification__ tasks. Other tasks(node regression, graph classifiication, etc.) will result in an error.\n", 66 | "\n", 67 | "### Reference Paper\n", 68 | "[[1]] Yang, Zhilin, William Cohen, and Ruslan Salakhudinov. \"Revisiting semi-supervised learning with graph embeddings.\" International conference on machine learning. 2016.\n" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 2, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "#Load the dataset\n", 78 | "dataset = 'cora'\n", 79 | "path = os.path.join(os.getcwd(), 'data', 'Planetoid')\n", 80 | "train_dataset = Planetoid(path, dataset, transform=T.NormalizeFeatures())\n", 81 | "\n", 82 | "# Since the dataset is comprised of a single huge graph, we extract that graph by indexing 0.\n", 83 | "data = train_dataset[0]\n", 84 | "\n", 85 | "# Since there is only 1 graph, the train/test split is done by masking regions of the graph. We split the last 500+500 nodes as val and test, and use the rest as the training data.\n", 86 | "data.train_mask = torch.zeros(data.num_nodes, dtype=torch.bool)\n", 87 | "data.train_mask[:data.num_nodes - 1000] = 1\n", 88 | "data.val_mask = None\n", 89 | "data.test_mask = torch.zeros(data.num_nodes, dtype=torch.bool)\n", 90 | "data.test_mask[data.num_nodes - 500:] = 1" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "## 2. Network\n", 98 | "We define a network that is to be trained and explained by GNNExplainer.\n", 99 | "We construct a simple graph neural network composed of 2 graph convolution layers(Kipf et al.)." 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 3, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "class Net(torch.nn.Module):\n", 109 | " def __init__(self, num_features, dim=16, num_classes=1):\n", 110 | " super(Net, self).__init__()\n", 111 | " self.conv1 = GCNConv(num_features, dim)\n", 112 | " self.conv2 = GCNConv(dim, num_classes)\n", 113 | "\n", 114 | " def forward(self, x, edge_index, data=None):\n", 115 | " x = F.relu(self.conv1(x, edge_index))\n", 116 | " x = F.dropout(x, training=self.training)\n", 117 | " x = self.conv2(x, edge_index)\n", 118 | " return F.log_softmax(x, dim=1)" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "## 3. Training\n", 126 | "\n", 127 | "Below is a standard Pytorch network training procedure." 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 9, 133 | "metadata": { 134 | "tags": [] 135 | }, 136 | "outputs": [], 137 | "source": [ 138 | "epochs = 200\n", 139 | "dim = 16\n" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 10, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", 149 | "model = Net(num_features=train_dataset.num_features, dim=dim, num_classes=train_dataset.num_classes).to(device)\n", 150 | "optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-3)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 11, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "def test(model, data):\n", 160 | " model.eval()\n", 161 | " logits, accs = model(data.x, data.edge_index, data), []\n", 162 | " for _, mask in data('train_mask', 'test_mask'):\n", 163 | " pred = logits[mask].max(1)[1]\n", 164 | " acc = pred.eq(data.y[mask]).sum().item() / mask.sum().item()\n", 165 | " accs.append(acc)\n", 166 | " return accs" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 12, 172 | "metadata": { 173 | "tags": [] 174 | }, 175 | "outputs": [ 176 | { 177 | "output_type": "stream", 178 | "name": "stderr", 179 | "text": "[Train_loss:1.505047 Train_acc: 0.4403, Test_acc: 0.4480]: 100%|██████████| 1000/1000 [00:09<00:00, 100.05it/s]\n" 180 | } 181 | ], 182 | "source": [ 183 | "loss = 999.0\n", 184 | "train_acc = 0.0\n", 185 | "test_acc = 0.0\n", 186 | "\n", 187 | "t = trange(epochs, desc=\"Stats: \", position=0)\n", 188 | "\n", 189 | "for epoch in t:\n", 190 | "\n", 191 | " model.train()\n", 192 | " \n", 193 | " loss = 0\n", 194 | "\n", 195 | " data = data.to(device)\n", 196 | " optimizer.zero_grad()\n", 197 | " log_logits = model(data.x, data.edge_index, data)\n", 198 | "\n", 199 | " # Since the data is a single huge graph, training on the training set is done by masking the nodes that are not in the training set.\n", 200 | " loss = F.nll_loss(log_logits[data.train_mask], data.y[data.train_mask])\n", 201 | " loss.backward()\n", 202 | " optimizer.step()\n", 203 | "\n", 204 | " # validate\n", 205 | " train_acc, test_acc = test(model, data)\n", 206 | " train_loss = loss\n", 207 | " \n", 208 | " t.set_description('[Train_loss:{:.6f} Train_acc: {:.4f}, Test_acc: {:.4f}]'.format(loss, train_acc, test_acc))" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "# 2. GNNExplainer" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "## 1. Instantiation\n", 223 | "\n", 224 | "Now we use GNNExplainer on the network trained above by wrapping the model with the GNNExplainer wrapper class(https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html?highlight=gnnexplainer#torch_geometric.nn.models.GNNExplainer). With explainer.explain_node() method, we cam fit GNNExplainer to explain a target node. This method must be supplied with a target node index(node_idx) and the graph(x, edge_index). The method then produces a node mask and a feature mask that reveals nodes/features that were important in producing such a prediction.\n", 225 | "\n" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 13, 231 | "metadata": { 232 | "tags": [] 233 | }, 234 | "outputs": [ 235 | { 236 | "output_type": "stream", 237 | "name": "stderr", 238 | "text": "Explain node 10: 100%|██████████| 200/200 [00:01<00:00, 115.71it/s]\n" 239 | } 240 | ], 241 | "source": [ 242 | "node_idx = 10\n", 243 | "x, edge_index = data.x, data.edge_index\n", 244 | "explainer = GNNExplainer(model, epochs=200)\n", 245 | "node_feat_mask, edge_mask = explainer.explain_node(node_idx, x, edge_index)\n" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "## 2. Visualization\n", 253 | "GNNExplainer supports visualization via Matplotlib. you can also supply the function with a threshold value deciding whether to obscure/reveal nodes and edges of certain importance values. The function returns the axes to the Matplotlib plot and a Networkx graph for additional control." 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 9, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "output_type": "display_data", 263 | "data": { 264 | "text/plain": "
", 265 | "image/svg+xml": "\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", 266 | "image/png": "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\n" 267 | }, 268 | "metadata": {} 269 | } 270 | ], 271 | "source": [ 272 | "ax, G = explainer.visualize_subgraph(node_idx, edge_index, edge_mask, y=data.y)\n", 273 | "plt.show()\n" 274 | ] 275 | } 276 | ], 277 | "metadata": { 278 | "language_info": { 279 | "codemirror_mode": { 280 | "name": "ipython", 281 | "version": 3 282 | }, 283 | "file_extension": ".py", 284 | "mimetype": "text/x-python", 285 | "name": "python", 286 | "nbconvert_exporter": "python", 287 | "pygments_lexer": "ipython3", 288 | "version": "3.7.6-final" 289 | }, 290 | "orig_nbformat": 2, 291 | "kernelspec": { 292 | "name": "python3", 293 | "display_name": "Python 3" 294 | } 295 | }, 296 | "nbformat": 4, 297 | "nbformat_minor": 2 298 | } --------------------------------------------------------------------------------