graph-explain 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graph_explain-0.7.0/PKG-INFO +332 -0
- graph_explain-0.7.0/README.md +287 -0
- graph_explain-0.7.0/pyproject.toml +59 -0
- graph_explain-0.7.0/setup.cfg +4 -0
- graph_explain-0.7.0/src/graph_explain/__init__.py +79 -0
- graph_explain-0.7.0/src/graph_explain/backends/__init__.py +4 -0
- graph_explain-0.7.0/src/graph_explain/backends/base.py +103 -0
- graph_explain-0.7.0/src/graph_explain/backends/dgl.py +121 -0
- graph_explain-0.7.0/src/graph_explain/benchmarks/__init__.py +3 -0
- graph_explain-0.7.0/src/graph_explain/benchmarks/synthetic.py +246 -0
- graph_explain-0.7.0/src/graph_explain/cli.py +459 -0
- graph_explain-0.7.0/src/graph_explain/core/__init__.py +14 -0
- graph_explain-0.7.0/src/graph_explain/core/benchmark.py +284 -0
- graph_explain-0.7.0/src/graph_explain/core/evaluation.py +391 -0
- graph_explain-0.7.0/src/graph_explain/core/explainer.py +83 -0
- graph_explain-0.7.0/src/graph_explain/core/explanation.py +72 -0
- graph_explain-0.7.0/src/graph_explain/core/model_utils.py +44 -0
- graph_explain-0.7.0/src/graph_explain/core/registry.py +55 -0
- graph_explain-0.7.0/src/graph_explain/methods/__init__.py +39 -0
- graph_explain-0.7.0/src/graph_explain/methods/attention/attention.py +147 -0
- graph_explain-0.7.0/src/graph_explain/methods/base.py +25 -0
- graph_explain-0.7.0/src/graph_explain/methods/baseline/random_baseline.py +78 -0
- graph_explain-0.7.0/src/graph_explain/methods/counterfactual/counterfactual.py +304 -0
- graph_explain-0.7.0/src/graph_explain/methods/feature/graph_lime.py +141 -0
- graph_explain-0.7.0/src/graph_explain/methods/gradient/__init__.py +0 -0
- graph_explain-0.7.0/src/graph_explain/methods/gradient/grad_x_input.py +110 -0
- graph_explain-0.7.0/src/graph_explain/methods/gradient/guided_backprop.py +117 -0
- graph_explain-0.7.0/src/graph_explain/methods/gradient/integrated_gradients.py +115 -0
- graph_explain-0.7.0/src/graph_explain/methods/gradient/saliency.py +93 -0
- graph_explain-0.7.0/src/graph_explain/methods/perturbation/__init__.py +0 -0
- graph_explain-0.7.0/src/graph_explain/methods/perturbation/gnn_explainer.py +265 -0
- graph_explain-0.7.0/src/graph_explain/methods/perturbation/node_mask.py +136 -0
- graph_explain-0.7.0/src/graph_explain/methods/perturbation/pg_explainer.py +162 -0
- graph_explain-0.7.0/src/graph_explain/methods/perturbation/subgraphx.py +393 -0
- graph_explain-0.7.0/src/graph_explain/methods/relevance/deeplift.py +262 -0
- graph_explain-0.7.0/src/graph_explain/methods/relevance/gnn_lrp.py +219 -0
- graph_explain-0.7.0/src/graph_explain/narration/__init__.py +3 -0
- graph_explain-0.7.0/src/graph_explain/narration/narrator.py +185 -0
- graph_explain-0.7.0/src/graph_explain/visualization/__init__.py +4 -0
- graph_explain-0.7.0/src/graph_explain/visualization/interactive.py +73 -0
- graph_explain-0.7.0/src/graph_explain/visualization/static.py +90 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/PKG-INFO +332 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/SOURCES.txt +56 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/dependency_links.txt +1 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/entry_points.txt +2 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/requires.txt +33 -0
- graph_explain-0.7.0/src/graph_explain.egg-info/top_level.txt +1 -0
- graph_explain-0.7.0/tests/test_cli.py +265 -0
- graph_explain-0.7.0/tests/test_core.py +103 -0
- graph_explain-0.7.0/tests/test_counterfactual.py +69 -0
- graph_explain-0.7.0/tests/test_dgl_integration.py +107 -0
- graph_explain-0.7.0/tests/test_narration.py +76 -0
- graph_explain-0.7.0/tests/test_phase10.py +193 -0
- graph_explain-0.7.0/tests/test_phase2.py +55 -0
- graph_explain-0.7.0/tests/test_phase3.py +154 -0
- graph_explain-0.7.0/tests/test_phase4.py +51 -0
- graph_explain-0.7.0/tests/test_phase6.py +142 -0
- graph_explain-0.7.0/tests/test_phase8.py +173 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: graph-explain
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Explainability library for graph-based models (GNN)
|
|
5
|
+
Author: graph-explain contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: gnn,explainability,xai,graph,neural-networks,interpretability
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: numpy>=1.24
|
|
20
|
+
Requires-Dist: networkx>=3.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.6
|
|
22
|
+
Provides-Extra: pyg
|
|
23
|
+
Requires-Dist: torch>=2.0; extra == "pyg"
|
|
24
|
+
Requires-Dist: torch-geometric>=2.5; extra == "pyg"
|
|
25
|
+
Provides-Extra: dgl
|
|
26
|
+
Requires-Dist: torch>=2.0; extra == "dgl"
|
|
27
|
+
Requires-Dist: dgl>=2.0; extra == "dgl"
|
|
28
|
+
Provides-Extra: interactive
|
|
29
|
+
Requires-Dist: plotly>=5.15; extra == "interactive"
|
|
30
|
+
Requires-Dist: pyvis>=0.3; extra == "interactive"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: torch>=2.0; extra == "all"
|
|
33
|
+
Requires-Dist: torch-geometric>=2.5; extra == "all"
|
|
34
|
+
Requires-Dist: plotly>=5.15; extra == "all"
|
|
35
|
+
Requires-Dist: pyvis>=0.3; extra == "all"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
39
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: sphinx>=7.2; extra == "docs"
|
|
42
|
+
Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
|
|
43
|
+
Provides-Extra: publish
|
|
44
|
+
Requires-Dist: twine>=5.0; extra == "publish"
|
|
45
|
+
|
|
46
|
+
# graph-explain
|
|
47
|
+
|
|
48
|
+
Explainability library for graph-based models (Graph Neural Networks).
|
|
49
|
+
Explains a GNN's predictions in terms of **important nodes, edges and subgraphs**,
|
|
50
|
+
with built-in metrics and visualization.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Unified API**: a single `Explainer` object for every method.
|
|
55
|
+
- **Node-level and graph-level**: `explain_node(...)` explains a node's
|
|
56
|
+
prediction; `explain_graph(...)` (or CLI without `--node`) explains a whole
|
|
57
|
+
graph with graph-level models (`task_level = "graph"`), including GEA
|
|
58
|
+
graph-level metrics and comparative benchmarking.
|
|
59
|
+
- **Explanation methods**:
|
|
60
|
+
- `GNNExplainer` — soft masks over nodes/edges (perturbation).
|
|
61
|
+
- `PGExplainer` — MLP generating edge masks (inductive, fast at inference).
|
|
62
|
+
- `SubgraphX` — MCTS search for subgraphs that maximize the prediction (high fidelity).
|
|
63
|
+
- `Saliency` — gradient-based importance.
|
|
64
|
+
- `Integrated Gradients` — gradient accumulation vs. a baseline (attribution paths).
|
|
65
|
+
- `GNNGatedLRP` — layer-wise relevance propagation (LRP-0/z+) over GCNs;
|
|
66
|
+
distributes relevance between nodes and edges from the positive contributions
|
|
67
|
+
of each conv/linear layer; supports `GCNConv` + `ReLU` + `Linear`.
|
|
68
|
+
- `DeepLift` — additive rescale rule vs. a (zero) baseline: each feature gets a
|
|
69
|
+
contribution proportional to its effect on the target class; conservative
|
|
70
|
+
(contributions sum ≈ Δ logits); supports `GCNConv` + `ReLU` + `Linear`.
|
|
71
|
+
- `AttentionExplainer` — node/edge importance from a `GATConv` model's
|
|
72
|
+
attention weights (softmax per neighbor, averaged over heads and layers).
|
|
73
|
+
- `GradXInput` — gradient × activation (zero baseline) for nodes and edges.
|
|
74
|
+
- `GraphLIME` — local linear (ridge) regression over the k-hop neighbors'
|
|
75
|
+
features, weighted by similarity to the target node; gives directly
|
|
76
|
+
interpretable feature importance without training.
|
|
77
|
+
- `NodeMask` — node mask learned by optimization (tracking the prediction)
|
|
78
|
+
over the k-hop subgraph, regularized toward sparsity.
|
|
79
|
+
- `GuidedBackprop` — gradients guided by the ReLU mask (positive activations
|
|
80
|
+
only); falls back to standard gradients if the model uses functional ReLUs.
|
|
81
|
+
- `Random` — uniformly random importance baseline (seed-able) for benchmarks.
|
|
82
|
+
- `Counterfactual` — minimal perturbation (edges or features) that changes a
|
|
83
|
+
node's prediction (deterministic greedy search); returns the modified
|
|
84
|
+
elements as importance plus the logits after the change.
|
|
85
|
+
- **Narration**: `describe(expl)` builds a template-based natural-language
|
|
86
|
+
explanation (Spanish by default), and `narrate(expl, llm=...)` lets you plug
|
|
87
|
+
in a generative model (a `prompt -> text` callable) for free-form text.
|
|
88
|
+
- **Metrics**:
|
|
89
|
+
- `evaluate_sparsity` — global or local sparsity (`local=True`, over the k-hop subgraph).
|
|
90
|
+
- `evaluate_fidelity_plus` — **necessity**: drop in `P(c)` when removing the top-k elements.
|
|
91
|
+
- `evaluate_fidelity_minus` — **sufficiency**: `P(c)` preserved when keeping ONLY the top-k.
|
|
92
|
+
- `evaluate_stability` — mean similarity between explanations under feature/edge perturbations.
|
|
93
|
+
- `evaluate_gea` — **Graph Explanation Accuracy**: overlap of the top-k with the ground-truth subgraph (BA-Shapes).
|
|
94
|
+
- **Built-in benchmarks**: BA-Shapes synthetic generator with ground truth and
|
|
95
|
+
`ground_truth_nodes` / `ground_truth_edge_ids` helpers; in addition,
|
|
96
|
+
`build_graph_classification` builds a **graph classification** dataset (house
|
|
97
|
+
motif) with per-graph `gt_edge_mask` for graph-level GEA
|
|
98
|
+
(`evaluate_gea_graph`).
|
|
99
|
+
- **Visualization**: static (matplotlib + networkx) and interactive (pyvis → HTML).
|
|
100
|
+
- **Backends**: PyTorch Geometric and DGL (through an adapter; DGL requires a
|
|
101
|
+
PyTorch version with pre-built graphbolt libraries).
|
|
102
|
+
- **CLI** to explain saved models without writing code, plus a **comparative
|
|
103
|
+
benchmark** of all methods over a node (table, JSON and HTML).
|
|
104
|
+
- **Programmatic comparison**: `compare(...)` to evaluate and compare methods.
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m venv .venv && source .venv/bin/activate
|
|
110
|
+
pip install -e .[all]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Optional extras: `pyg` (PyTorch Geometric), `dgl` (DGL backend),
|
|
114
|
+
`interactive` (plotly/pyvis).
|
|
115
|
+
|
|
116
|
+
## Quick start
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from graph_explain import Explainer, GNNExplainer, Saliency
|
|
120
|
+
from graph_explain.benchmarks.synthetic import build_data
|
|
121
|
+
from graph_explain.visualization import show
|
|
122
|
+
|
|
123
|
+
data = build_data(base_nodes=300, num_houses=80) # BA-Shapes with ground truth
|
|
124
|
+
model = GCN(in_channels=data.x.size(1)) # your trained GNN
|
|
125
|
+
model.eval()
|
|
126
|
+
|
|
127
|
+
explainer = Explainer(algorithm=GNNExplainer(epochs=150))
|
|
128
|
+
expl = explainer.explain_node(data, model, node_idx=42)
|
|
129
|
+
|
|
130
|
+
print(expl.evaluate(metrics=["fidelity", "sparsity"]))
|
|
131
|
+
print(expl.evaluate(metrics=["sparsity"], local=True)) # sparsity over the node's k-hop subgraph
|
|
132
|
+
show(expl, show_labels=True) # highlight the explanatory subgraph
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Sparsity tuning notes
|
|
136
|
+
|
|
137
|
+
- **Structural models**: perturbation-based explainers (GNNExplainer,
|
|
138
|
+
PGExplainer, SubgraphX) assume the prediction depends on the neighborhood
|
|
139
|
+
structure. A `GCNConv` with `add_self_loops=True` and `bias=True` can predict
|
|
140
|
+
the class from biases/self-loops alone; in that case edge masks collapse to
|
|
141
|
+
zero because edges do not matter. For meaningful demos use
|
|
142
|
+
`GCNConv(..., add_self_loops=False, bias=False)` (see `examples/model.py`).
|
|
143
|
+
- **Benchmark split**: `build_data` splits train/test across **all** nodes
|
|
144
|
+
(including motifs). If the model is trained on class 0 only, it learns to
|
|
145
|
+
ignore structure.
|
|
146
|
+
- **`PGExplainer(temp=...)`**: with `temp=5` the Gumbel-sigmoid sampling
|
|
147
|
+
gradient flattens (~0.05) and the mask collapses to zero. The default is `temp=1.0`.
|
|
148
|
+
- **Local sparsity**: `evaluate_sparsity(expl, local=True)` measures sparsity
|
|
149
|
+
over the explained node's `k-hop` subgraph instead of the whole graph; when
|
|
150
|
+
the mask is counted over the full graph, local explanations get diluted
|
|
151
|
+
(sparsity appears near 1).
|
|
152
|
+
|
|
153
|
+
## CLI
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Save model and data first:
|
|
157
|
+
torch.save(model, "model.pt"); torch.save(data, "data.pt")
|
|
158
|
+
|
|
159
|
+
graph-explain explain \
|
|
160
|
+
--model model.pt --data data.pt \
|
|
161
|
+
--method gnn_explainer --node 42 \
|
|
162
|
+
--plot explicacion.png
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## The `Explanation` object
|
|
166
|
+
|
|
167
|
+
- `node_importance`: importance per node `(num_nodes,)`.
|
|
168
|
+
- `edge_importance`: importance per edge.
|
|
169
|
+
- `feature_importance`: importance per feature (method-dependent).
|
|
170
|
+
- `prediction_original` / `prediction_explanation`: logits for fidelity evaluation.
|
|
171
|
+
- Methods: `evaluate(metrics=[...])`, `to_networkx(threshold=...)`, `visualize_static(...)`.
|
|
172
|
+
|
|
173
|
+
## Structure
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
src/graph_explain/
|
|
177
|
+
├── core/ # Explainer, Explanation, registry, evaluation
|
|
178
|
+
├── methods/ # gnn_explainer, subgraphx, pg_explainer, saliency, integrated_gradients
|
|
179
|
+
├── backends/ # Backend API + PyGAdapter + DGLAdapter
|
|
180
|
+
├── benchmarks/ # BA-Shapes synthetic generator + ground-truth helpers
|
|
181
|
+
└── visualization/ # static plots
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`get_backend(name)` returns `PyGAdapter` or `DGLAdapter`. For DGL, features go
|
|
186
|
+
in `ndata['feat']`, labels in `ndata['label']` and edge weights in `edata['w']`;
|
|
187
|
+
the model must read `g.ndata['feat']` and `g.edata['w']`.
|
|
188
|
+
|
|
189
|
+
**DGL validation against the real library**: DGL 2.1.0 only ships graphbolt C++
|
|
190
|
+
libraries for torch ≤ 2.2.1, so the real integration is tested in an isolated
|
|
191
|
+
virtual machine (`tests/test_dgl_integration.py`, skipped when dgl is not
|
|
192
|
+
available):
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
python3.12 -m venv /tmp/dgl-venv
|
|
196
|
+
/tmp/dgl-venv/bin/pip install torch==2.2.1 --index-url https://download.pytorch.org/whl/cpu \
|
|
197
|
+
dgl==2.1.0 "numpy<2" "scipy<1.14" "pandas" "torchdata==0.7.1" \
|
|
198
|
+
"torch-geometric==2.6.1" setuptools packaging
|
|
199
|
+
cd graph-explain && PYTHONPATH=. /tmp/dgl-venv/bin/python -m pytest tests -q
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Metrics (phase 3)
|
|
203
|
+
|
|
204
|
+
````python
|
|
205
|
+
from graph_explain.core.evaluation import (
|
|
206
|
+
evaluate_fidelity_plus, evaluate_fidelity_minus,
|
|
207
|
+
evaluate_stability, evaluate_gea,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
fp = evaluate_fidelity_plus(model, expl) # necessity: remove top-k elements → P(c) drops
|
|
211
|
+
fm = evaluate_fidelity_minus(model, expl) # sufficiency: keep only top-k → P(c) is preserved
|
|
212
|
+
stab = evaluate_stability(
|
|
213
|
+
lambda d: Explainer(algorithm=GNNExplainer(epochs=40)).explain_node(d, model, node_idx=42),
|
|
214
|
+
data, num_perturbations=5, noise_std=0.02,
|
|
215
|
+
)
|
|
216
|
+
gea = evaluate_gea(expl, data=data) # overlap with the BA-Shapes motif
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Example in `examples/example.py`, benchmark with `num_houses=30`: GNNExplainer →
|
|
220
|
+
`fid+ 0.74 / fid- 0.99 / GEA 0.92 / stab 0.85`.
|
|
221
|
+
````
|
|
222
|
+
|
|
223
|
+
## CLI (phase 5)
|
|
224
|
+
|
|
225
|
+
The command-line interface covers all methods (including the `lrp`/`gnn_lrp`
|
|
226
|
+
and `cf`/`counterfactual` aliases), metrics, narration and JSON reports:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
graph-explain --version
|
|
230
|
+
|
|
231
|
+
# Counterfactual explanation for node 42 + narration + JSON report
|
|
232
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
233
|
+
--method counterfactual --node 42 --mode feature \
|
|
234
|
+
--hops 2 --max-steps 10 --describe --json report.json
|
|
235
|
+
|
|
236
|
+
# Normalized GNN-LRP with metrics
|
|
237
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
238
|
+
--method lrp --node 42 --normalize \
|
|
239
|
+
--metrics fidelity_plus,fidelity_minus,gea,stability \
|
|
240
|
+
--top-k 5 --num-perturbations 5
|
|
241
|
+
|
|
242
|
+
# GNNExplainer + static and interactive visualizations
|
|
243
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
244
|
+
--method gnn_explainer --node 42 --epochs 200 \
|
|
245
|
+
--threshold 0.5 --plot expl.png --html expl.html
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Main options: `--method`, `--node`, `--target-class`, `--epochs`, `--lr`,
|
|
249
|
+
`--mode` (edge/feature), `--hops`, `--max-steps`, `--eps`, `--steps`,
|
|
250
|
+
`--normalize`, `--backend` (pyg/dgl), `--threshold`, `--top-k`, `--metrics`,
|
|
251
|
+
`--num-perturbations`, `--noise-std`, `--describe`, `--json`, `--output`,
|
|
252
|
+
`--plot`, `--html`. The JSON report includes method, predictions, metrics and
|
|
253
|
+
the structured summary (`summarize`) with top-k nodes/edges.
|
|
254
|
+
|
|
255
|
+
## Comparative benchmark (phase 7)
|
|
256
|
+
|
|
257
|
+
`compare(data, model, node=...)` runs every method on a node, computes the
|
|
258
|
+
metric battery (fid+ / fid- / GEA / sparsity / stability) and returns a
|
|
259
|
+
structured dict; non-applicable methods (e.g. Attention without `GATConv`) and
|
|
260
|
+
meaningless metrics are marked as `skipped`/`None` without aborting the rest:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from graph_explain import compare, report_html
|
|
264
|
+
|
|
265
|
+
results = compare(data, model, node=42, methods=None, # None = all
|
|
266
|
+
num_perturbations=5, epochs=200)
|
|
267
|
+
report_html(results, "bench.html") # self-contained HTML report
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The CLI ships an equivalent subcommand:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
graph-explain bench --model model.pt --data data.pt --node 42 \
|
|
274
|
+
--methods all --num-perturbations 5 \
|
|
275
|
+
--json bench.json --html bench.html
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Note: `gea` is only defined when the node belongs to a ground-truth subgraph of
|
|
279
|
+
the benchmark (BA-Shapes); otherwise it shows up empty in the table.
|
|
280
|
+
|
|
281
|
+
## Graph-level (phase 10)
|
|
282
|
+
|
|
283
|
+
Models that predict over whole graphs (`task_level = "graph"`, e.g. GCN +
|
|
284
|
+
global pooling). Without `--node`, the CLI explains the whole graph; methods
|
|
285
|
+
marked with `graph_level`:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
# Explain a whole graph (graph-level model) + GEA over the motif
|
|
289
|
+
graph-explain explain --model model.pt --data graph.pt \
|
|
290
|
+
--method grad_x_input --metrics fidelity_plus,gea
|
|
291
|
+
|
|
292
|
+
# Graph-level bench (shows skipped methods and only runs applicable ones)
|
|
293
|
+
graph-explain bench --model model.pt --data graph.pt \
|
|
294
|
+
--methods all --no-stability --json bench_graph.json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
In Python:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from graph_explain import Explainer, evaluate_gea_graph
|
|
301
|
+
from graph_explain.benchmarks.synthetic import build_graph_classification
|
|
302
|
+
|
|
303
|
+
graphs = build_graph_classification(num_pos=8, num_neg=8, seed=0) # binary y, gt_edge_mask
|
|
304
|
+
model = ... # GraphGCN (task_level="graph")
|
|
305
|
+
|
|
306
|
+
expl = Explainer(algorithm=GradXInput()).explain_graph(graphs[0], model)
|
|
307
|
+
print(evaluate_gea_graph(expl, data=graphs[0], top_k=13))
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Node-only methods (`GraphLIME`, `NodeMask`, `Attention`, `GNNGatedLRP`,
|
|
311
|
+
`Counterfactual`, `DeepLift`, `PGExplainer`, `SubgraphX`) are marked as
|
|
312
|
+
`skipped` at graph-level.
|
|
313
|
+
|
|
314
|
+
## Roadmap
|
|
315
|
+
|
|
316
|
+
- [x] Phase 2: PGExplainer, SubgraphX, Integrated Gradients
|
|
317
|
+
- [x] Phase 2: interactive visualization (pyvis → HTML)
|
|
318
|
+
- [x] Phase 3: full metrics (fidelity±, stability, GEA)
|
|
319
|
+
- [x] Phase 3: DGL backend (adapter; integration validated with DGL 2.1 + torch 2.2.1)
|
|
320
|
+
- [x] Phase 4: GNN-LRP (layer-wise relevance for GCNs; validates the house motif in BA-Shapes)
|
|
321
|
+
- [x] Phase 4: counterfactual explanations (minimal edge/feature removal that changes the class)
|
|
322
|
+
- [x] Phase 4: LLM narration (`describe` deterministic + pluggable `narrate` LLM)
|
|
323
|
+
- [x] Phase 5: full CLI (all methods, metrics, narration and JSON export)
|
|
324
|
+
- [x] Phase 6: more methods (DeepLIFT rescale, Attention/GAT, Gradient×Input)
|
|
325
|
+
- [x] Phase 7: comparative benchmark (`compare` + CLI `bench` subcommand, table and JSON/HTML reports)
|
|
326
|
+
- [x] Phase 8: more methods (GraphLIME, NodeMask, GuidedBackprop and Random baseline)
|
|
327
|
+
- [x] Phase 10: graph-level explanations (graph-classification dataset with house
|
|
328
|
+
motif, graph-level GEA, CLI/bench without `--node` and `graph_level` flag)
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# graph-explain
|
|
2
|
+
|
|
3
|
+
Explainability library for graph-based models (Graph Neural Networks).
|
|
4
|
+
Explains a GNN's predictions in terms of **important nodes, edges and subgraphs**,
|
|
5
|
+
with built-in metrics and visualization.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Unified API**: a single `Explainer` object for every method.
|
|
10
|
+
- **Node-level and graph-level**: `explain_node(...)` explains a node's
|
|
11
|
+
prediction; `explain_graph(...)` (or CLI without `--node`) explains a whole
|
|
12
|
+
graph with graph-level models (`task_level = "graph"`), including GEA
|
|
13
|
+
graph-level metrics and comparative benchmarking.
|
|
14
|
+
- **Explanation methods**:
|
|
15
|
+
- `GNNExplainer` — soft masks over nodes/edges (perturbation).
|
|
16
|
+
- `PGExplainer` — MLP generating edge masks (inductive, fast at inference).
|
|
17
|
+
- `SubgraphX` — MCTS search for subgraphs that maximize the prediction (high fidelity).
|
|
18
|
+
- `Saliency` — gradient-based importance.
|
|
19
|
+
- `Integrated Gradients` — gradient accumulation vs. a baseline (attribution paths).
|
|
20
|
+
- `GNNGatedLRP` — layer-wise relevance propagation (LRP-0/z+) over GCNs;
|
|
21
|
+
distributes relevance between nodes and edges from the positive contributions
|
|
22
|
+
of each conv/linear layer; supports `GCNConv` + `ReLU` + `Linear`.
|
|
23
|
+
- `DeepLift` — additive rescale rule vs. a (zero) baseline: each feature gets a
|
|
24
|
+
contribution proportional to its effect on the target class; conservative
|
|
25
|
+
(contributions sum ≈ Δ logits); supports `GCNConv` + `ReLU` + `Linear`.
|
|
26
|
+
- `AttentionExplainer` — node/edge importance from a `GATConv` model's
|
|
27
|
+
attention weights (softmax per neighbor, averaged over heads and layers).
|
|
28
|
+
- `GradXInput` — gradient × activation (zero baseline) for nodes and edges.
|
|
29
|
+
- `GraphLIME` — local linear (ridge) regression over the k-hop neighbors'
|
|
30
|
+
features, weighted by similarity to the target node; gives directly
|
|
31
|
+
interpretable feature importance without training.
|
|
32
|
+
- `NodeMask` — node mask learned by optimization (tracking the prediction)
|
|
33
|
+
over the k-hop subgraph, regularized toward sparsity.
|
|
34
|
+
- `GuidedBackprop` — gradients guided by the ReLU mask (positive activations
|
|
35
|
+
only); falls back to standard gradients if the model uses functional ReLUs.
|
|
36
|
+
- `Random` — uniformly random importance baseline (seed-able) for benchmarks.
|
|
37
|
+
- `Counterfactual` — minimal perturbation (edges or features) that changes a
|
|
38
|
+
node's prediction (deterministic greedy search); returns the modified
|
|
39
|
+
elements as importance plus the logits after the change.
|
|
40
|
+
- **Narration**: `describe(expl)` builds a template-based natural-language
|
|
41
|
+
explanation (Spanish by default), and `narrate(expl, llm=...)` lets you plug
|
|
42
|
+
in a generative model (a `prompt -> text` callable) for free-form text.
|
|
43
|
+
- **Metrics**:
|
|
44
|
+
- `evaluate_sparsity` — global or local sparsity (`local=True`, over the k-hop subgraph).
|
|
45
|
+
- `evaluate_fidelity_plus` — **necessity**: drop in `P(c)` when removing the top-k elements.
|
|
46
|
+
- `evaluate_fidelity_minus` — **sufficiency**: `P(c)` preserved when keeping ONLY the top-k.
|
|
47
|
+
- `evaluate_stability` — mean similarity between explanations under feature/edge perturbations.
|
|
48
|
+
- `evaluate_gea` — **Graph Explanation Accuracy**: overlap of the top-k with the ground-truth subgraph (BA-Shapes).
|
|
49
|
+
- **Built-in benchmarks**: BA-Shapes synthetic generator with ground truth and
|
|
50
|
+
`ground_truth_nodes` / `ground_truth_edge_ids` helpers; in addition,
|
|
51
|
+
`build_graph_classification` builds a **graph classification** dataset (house
|
|
52
|
+
motif) with per-graph `gt_edge_mask` for graph-level GEA
|
|
53
|
+
(`evaluate_gea_graph`).
|
|
54
|
+
- **Visualization**: static (matplotlib + networkx) and interactive (pyvis → HTML).
|
|
55
|
+
- **Backends**: PyTorch Geometric and DGL (through an adapter; DGL requires a
|
|
56
|
+
PyTorch version with pre-built graphbolt libraries).
|
|
57
|
+
- **CLI** to explain saved models without writing code, plus a **comparative
|
|
58
|
+
benchmark** of all methods over a node (table, JSON and HTML).
|
|
59
|
+
- **Programmatic comparison**: `compare(...)` to evaluate and compare methods.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python -m venv .venv && source .venv/bin/activate
|
|
65
|
+
pip install -e .[all]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Optional extras: `pyg` (PyTorch Geometric), `dgl` (DGL backend),
|
|
69
|
+
`interactive` (plotly/pyvis).
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from graph_explain import Explainer, GNNExplainer, Saliency
|
|
75
|
+
from graph_explain.benchmarks.synthetic import build_data
|
|
76
|
+
from graph_explain.visualization import show
|
|
77
|
+
|
|
78
|
+
data = build_data(base_nodes=300, num_houses=80) # BA-Shapes with ground truth
|
|
79
|
+
model = GCN(in_channels=data.x.size(1)) # your trained GNN
|
|
80
|
+
model.eval()
|
|
81
|
+
|
|
82
|
+
explainer = Explainer(algorithm=GNNExplainer(epochs=150))
|
|
83
|
+
expl = explainer.explain_node(data, model, node_idx=42)
|
|
84
|
+
|
|
85
|
+
print(expl.evaluate(metrics=["fidelity", "sparsity"]))
|
|
86
|
+
print(expl.evaluate(metrics=["sparsity"], local=True)) # sparsity over the node's k-hop subgraph
|
|
87
|
+
show(expl, show_labels=True) # highlight the explanatory subgraph
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Sparsity tuning notes
|
|
91
|
+
|
|
92
|
+
- **Structural models**: perturbation-based explainers (GNNExplainer,
|
|
93
|
+
PGExplainer, SubgraphX) assume the prediction depends on the neighborhood
|
|
94
|
+
structure. A `GCNConv` with `add_self_loops=True` and `bias=True` can predict
|
|
95
|
+
the class from biases/self-loops alone; in that case edge masks collapse to
|
|
96
|
+
zero because edges do not matter. For meaningful demos use
|
|
97
|
+
`GCNConv(..., add_self_loops=False, bias=False)` (see `examples/model.py`).
|
|
98
|
+
- **Benchmark split**: `build_data` splits train/test across **all** nodes
|
|
99
|
+
(including motifs). If the model is trained on class 0 only, it learns to
|
|
100
|
+
ignore structure.
|
|
101
|
+
- **`PGExplainer(temp=...)`**: with `temp=5` the Gumbel-sigmoid sampling
|
|
102
|
+
gradient flattens (~0.05) and the mask collapses to zero. The default is `temp=1.0`.
|
|
103
|
+
- **Local sparsity**: `evaluate_sparsity(expl, local=True)` measures sparsity
|
|
104
|
+
over the explained node's `k-hop` subgraph instead of the whole graph; when
|
|
105
|
+
the mask is counted over the full graph, local explanations get diluted
|
|
106
|
+
(sparsity appears near 1).
|
|
107
|
+
|
|
108
|
+
## CLI
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Save model and data first:
|
|
112
|
+
torch.save(model, "model.pt"); torch.save(data, "data.pt")
|
|
113
|
+
|
|
114
|
+
graph-explain explain \
|
|
115
|
+
--model model.pt --data data.pt \
|
|
116
|
+
--method gnn_explainer --node 42 \
|
|
117
|
+
--plot explicacion.png
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## The `Explanation` object
|
|
121
|
+
|
|
122
|
+
- `node_importance`: importance per node `(num_nodes,)`.
|
|
123
|
+
- `edge_importance`: importance per edge.
|
|
124
|
+
- `feature_importance`: importance per feature (method-dependent).
|
|
125
|
+
- `prediction_original` / `prediction_explanation`: logits for fidelity evaluation.
|
|
126
|
+
- Methods: `evaluate(metrics=[...])`, `to_networkx(threshold=...)`, `visualize_static(...)`.
|
|
127
|
+
|
|
128
|
+
## Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
src/graph_explain/
|
|
132
|
+
├── core/ # Explainer, Explanation, registry, evaluation
|
|
133
|
+
├── methods/ # gnn_explainer, subgraphx, pg_explainer, saliency, integrated_gradients
|
|
134
|
+
├── backends/ # Backend API + PyGAdapter + DGLAdapter
|
|
135
|
+
├── benchmarks/ # BA-Shapes synthetic generator + ground-truth helpers
|
|
136
|
+
└── visualization/ # static plots
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`get_backend(name)` returns `PyGAdapter` or `DGLAdapter`. For DGL, features go
|
|
141
|
+
in `ndata['feat']`, labels in `ndata['label']` and edge weights in `edata['w']`;
|
|
142
|
+
the model must read `g.ndata['feat']` and `g.edata['w']`.
|
|
143
|
+
|
|
144
|
+
**DGL validation against the real library**: DGL 2.1.0 only ships graphbolt C++
|
|
145
|
+
libraries for torch ≤ 2.2.1, so the real integration is tested in an isolated
|
|
146
|
+
virtual machine (`tests/test_dgl_integration.py`, skipped when dgl is not
|
|
147
|
+
available):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
python3.12 -m venv /tmp/dgl-venv
|
|
151
|
+
/tmp/dgl-venv/bin/pip install torch==2.2.1 --index-url https://download.pytorch.org/whl/cpu \
|
|
152
|
+
dgl==2.1.0 "numpy<2" "scipy<1.14" "pandas" "torchdata==0.7.1" \
|
|
153
|
+
"torch-geometric==2.6.1" setuptools packaging
|
|
154
|
+
cd graph-explain && PYTHONPATH=. /tmp/dgl-venv/bin/python -m pytest tests -q
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Metrics (phase 3)
|
|
158
|
+
|
|
159
|
+
````python
|
|
160
|
+
from graph_explain.core.evaluation import (
|
|
161
|
+
evaluate_fidelity_plus, evaluate_fidelity_minus,
|
|
162
|
+
evaluate_stability, evaluate_gea,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
fp = evaluate_fidelity_plus(model, expl) # necessity: remove top-k elements → P(c) drops
|
|
166
|
+
fm = evaluate_fidelity_minus(model, expl) # sufficiency: keep only top-k → P(c) is preserved
|
|
167
|
+
stab = evaluate_stability(
|
|
168
|
+
lambda d: Explainer(algorithm=GNNExplainer(epochs=40)).explain_node(d, model, node_idx=42),
|
|
169
|
+
data, num_perturbations=5, noise_std=0.02,
|
|
170
|
+
)
|
|
171
|
+
gea = evaluate_gea(expl, data=data) # overlap with the BA-Shapes motif
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Example in `examples/example.py`, benchmark with `num_houses=30`: GNNExplainer →
|
|
175
|
+
`fid+ 0.74 / fid- 0.99 / GEA 0.92 / stab 0.85`.
|
|
176
|
+
````
|
|
177
|
+
|
|
178
|
+
## CLI (phase 5)
|
|
179
|
+
|
|
180
|
+
The command-line interface covers all methods (including the `lrp`/`gnn_lrp`
|
|
181
|
+
and `cf`/`counterfactual` aliases), metrics, narration and JSON reports:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
graph-explain --version
|
|
185
|
+
|
|
186
|
+
# Counterfactual explanation for node 42 + narration + JSON report
|
|
187
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
188
|
+
--method counterfactual --node 42 --mode feature \
|
|
189
|
+
--hops 2 --max-steps 10 --describe --json report.json
|
|
190
|
+
|
|
191
|
+
# Normalized GNN-LRP with metrics
|
|
192
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
193
|
+
--method lrp --node 42 --normalize \
|
|
194
|
+
--metrics fidelity_plus,fidelity_minus,gea,stability \
|
|
195
|
+
--top-k 5 --num-perturbations 5
|
|
196
|
+
|
|
197
|
+
# GNNExplainer + static and interactive visualizations
|
|
198
|
+
graph-explain explain --model model.pt --data data.pt \
|
|
199
|
+
--method gnn_explainer --node 42 --epochs 200 \
|
|
200
|
+
--threshold 0.5 --plot expl.png --html expl.html
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Main options: `--method`, `--node`, `--target-class`, `--epochs`, `--lr`,
|
|
204
|
+
`--mode` (edge/feature), `--hops`, `--max-steps`, `--eps`, `--steps`,
|
|
205
|
+
`--normalize`, `--backend` (pyg/dgl), `--threshold`, `--top-k`, `--metrics`,
|
|
206
|
+
`--num-perturbations`, `--noise-std`, `--describe`, `--json`, `--output`,
|
|
207
|
+
`--plot`, `--html`. The JSON report includes method, predictions, metrics and
|
|
208
|
+
the structured summary (`summarize`) with top-k nodes/edges.
|
|
209
|
+
|
|
210
|
+
## Comparative benchmark (phase 7)
|
|
211
|
+
|
|
212
|
+
`compare(data, model, node=...)` runs every method on a node, computes the
|
|
213
|
+
metric battery (fid+ / fid- / GEA / sparsity / stability) and returns a
|
|
214
|
+
structured dict; non-applicable methods (e.g. Attention without `GATConv`) and
|
|
215
|
+
meaningless metrics are marked as `skipped`/`None` without aborting the rest:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from graph_explain import compare, report_html
|
|
219
|
+
|
|
220
|
+
results = compare(data, model, node=42, methods=None, # None = all
|
|
221
|
+
num_perturbations=5, epochs=200)
|
|
222
|
+
report_html(results, "bench.html") # self-contained HTML report
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The CLI ships an equivalent subcommand:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
graph-explain bench --model model.pt --data data.pt --node 42 \
|
|
229
|
+
--methods all --num-perturbations 5 \
|
|
230
|
+
--json bench.json --html bench.html
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Note: `gea` is only defined when the node belongs to a ground-truth subgraph of
|
|
234
|
+
the benchmark (BA-Shapes); otherwise it shows up empty in the table.
|
|
235
|
+
|
|
236
|
+
## Graph-level (phase 10)
|
|
237
|
+
|
|
238
|
+
Models that predict over whole graphs (`task_level = "graph"`, e.g. GCN +
|
|
239
|
+
global pooling). Without `--node`, the CLI explains the whole graph; methods
|
|
240
|
+
marked with `graph_level`:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Explain a whole graph (graph-level model) + GEA over the motif
|
|
244
|
+
graph-explain explain --model model.pt --data graph.pt \
|
|
245
|
+
--method grad_x_input --metrics fidelity_plus,gea
|
|
246
|
+
|
|
247
|
+
# Graph-level bench (shows skipped methods and only runs applicable ones)
|
|
248
|
+
graph-explain bench --model model.pt --data graph.pt \
|
|
249
|
+
--methods all --no-stability --json bench_graph.json
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
In Python:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from graph_explain import Explainer, evaluate_gea_graph
|
|
256
|
+
from graph_explain.benchmarks.synthetic import build_graph_classification
|
|
257
|
+
|
|
258
|
+
graphs = build_graph_classification(num_pos=8, num_neg=8, seed=0) # binary y, gt_edge_mask
|
|
259
|
+
model = ... # GraphGCN (task_level="graph")
|
|
260
|
+
|
|
261
|
+
expl = Explainer(algorithm=GradXInput()).explain_graph(graphs[0], model)
|
|
262
|
+
print(evaluate_gea_graph(expl, data=graphs[0], top_k=13))
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Node-only methods (`GraphLIME`, `NodeMask`, `Attention`, `GNNGatedLRP`,
|
|
266
|
+
`Counterfactual`, `DeepLift`, `PGExplainer`, `SubgraphX`) are marked as
|
|
267
|
+
`skipped` at graph-level.
|
|
268
|
+
|
|
269
|
+
## Roadmap
|
|
270
|
+
|
|
271
|
+
- [x] Phase 2: PGExplainer, SubgraphX, Integrated Gradients
|
|
272
|
+
- [x] Phase 2: interactive visualization (pyvis → HTML)
|
|
273
|
+
- [x] Phase 3: full metrics (fidelity±, stability, GEA)
|
|
274
|
+
- [x] Phase 3: DGL backend (adapter; integration validated with DGL 2.1 + torch 2.2.1)
|
|
275
|
+
- [x] Phase 4: GNN-LRP (layer-wise relevance for GCNs; validates the house motif in BA-Shapes)
|
|
276
|
+
- [x] Phase 4: counterfactual explanations (minimal edge/feature removal that changes the class)
|
|
277
|
+
- [x] Phase 4: LLM narration (`describe` deterministic + pluggable `narrate` LLM)
|
|
278
|
+
- [x] Phase 5: full CLI (all methods, metrics, narration and JSON export)
|
|
279
|
+
- [x] Phase 6: more methods (DeepLIFT rescale, Attention/GAT, Gradient×Input)
|
|
280
|
+
- [x] Phase 7: comparative benchmark (`compare` + CLI `bench` subcommand, table and JSON/HTML reports)
|
|
281
|
+
- [x] Phase 8: more methods (GraphLIME, NodeMask, GuidedBackprop and Random baseline)
|
|
282
|
+
- [x] Phase 10: graph-level explanations (graph-classification dataset with house
|
|
283
|
+
motif, graph-level GEA, CLI/bench without `--node` and `graph_level` flag)
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "graph-explain"
|
|
7
|
+
version = "0.7.0"
|
|
8
|
+
description = "Explainability library for graph-based models (GNN)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "graph-explain contributors" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"gnn",
|
|
15
|
+
"explainability",
|
|
16
|
+
"xai",
|
|
17
|
+
"graph",
|
|
18
|
+
"neural-networks",
|
|
19
|
+
"interpretability",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
dependencies = [
|
|
34
|
+
"numpy>=1.24",
|
|
35
|
+
"networkx>=3.0",
|
|
36
|
+
"matplotlib>=3.6",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
pyg = ["torch>=2.0", "torch-geometric>=2.5"]
|
|
41
|
+
dgl = ["torch>=2.0", "dgl>=2.0"]
|
|
42
|
+
interactive = ["plotly>=5.15", "pyvis>=0.3"]
|
|
43
|
+
all = ["torch>=2.0", "torch-geometric>=2.5", "plotly>=5.15", "pyvis>=0.3"]
|
|
44
|
+
dev = ["pytest>=7.0", "ruff>=0.5", "build>=1.0"]
|
|
45
|
+
docs = ["sphinx>=7.2", "sphinx-rtd-theme>=2.0"]
|
|
46
|
+
publish = ["twine>=5.0"]
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
graph-explain = "graph_explain.cli:main"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff]
|
|
58
|
+
line-length = 88
|
|
59
|
+
target-version = "py310"
|