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.
Files changed (58) hide show
  1. graph_explain-0.7.0/PKG-INFO +332 -0
  2. graph_explain-0.7.0/README.md +287 -0
  3. graph_explain-0.7.0/pyproject.toml +59 -0
  4. graph_explain-0.7.0/setup.cfg +4 -0
  5. graph_explain-0.7.0/src/graph_explain/__init__.py +79 -0
  6. graph_explain-0.7.0/src/graph_explain/backends/__init__.py +4 -0
  7. graph_explain-0.7.0/src/graph_explain/backends/base.py +103 -0
  8. graph_explain-0.7.0/src/graph_explain/backends/dgl.py +121 -0
  9. graph_explain-0.7.0/src/graph_explain/benchmarks/__init__.py +3 -0
  10. graph_explain-0.7.0/src/graph_explain/benchmarks/synthetic.py +246 -0
  11. graph_explain-0.7.0/src/graph_explain/cli.py +459 -0
  12. graph_explain-0.7.0/src/graph_explain/core/__init__.py +14 -0
  13. graph_explain-0.7.0/src/graph_explain/core/benchmark.py +284 -0
  14. graph_explain-0.7.0/src/graph_explain/core/evaluation.py +391 -0
  15. graph_explain-0.7.0/src/graph_explain/core/explainer.py +83 -0
  16. graph_explain-0.7.0/src/graph_explain/core/explanation.py +72 -0
  17. graph_explain-0.7.0/src/graph_explain/core/model_utils.py +44 -0
  18. graph_explain-0.7.0/src/graph_explain/core/registry.py +55 -0
  19. graph_explain-0.7.0/src/graph_explain/methods/__init__.py +39 -0
  20. graph_explain-0.7.0/src/graph_explain/methods/attention/attention.py +147 -0
  21. graph_explain-0.7.0/src/graph_explain/methods/base.py +25 -0
  22. graph_explain-0.7.0/src/graph_explain/methods/baseline/random_baseline.py +78 -0
  23. graph_explain-0.7.0/src/graph_explain/methods/counterfactual/counterfactual.py +304 -0
  24. graph_explain-0.7.0/src/graph_explain/methods/feature/graph_lime.py +141 -0
  25. graph_explain-0.7.0/src/graph_explain/methods/gradient/__init__.py +0 -0
  26. graph_explain-0.7.0/src/graph_explain/methods/gradient/grad_x_input.py +110 -0
  27. graph_explain-0.7.0/src/graph_explain/methods/gradient/guided_backprop.py +117 -0
  28. graph_explain-0.7.0/src/graph_explain/methods/gradient/integrated_gradients.py +115 -0
  29. graph_explain-0.7.0/src/graph_explain/methods/gradient/saliency.py +93 -0
  30. graph_explain-0.7.0/src/graph_explain/methods/perturbation/__init__.py +0 -0
  31. graph_explain-0.7.0/src/graph_explain/methods/perturbation/gnn_explainer.py +265 -0
  32. graph_explain-0.7.0/src/graph_explain/methods/perturbation/node_mask.py +136 -0
  33. graph_explain-0.7.0/src/graph_explain/methods/perturbation/pg_explainer.py +162 -0
  34. graph_explain-0.7.0/src/graph_explain/methods/perturbation/subgraphx.py +393 -0
  35. graph_explain-0.7.0/src/graph_explain/methods/relevance/deeplift.py +262 -0
  36. graph_explain-0.7.0/src/graph_explain/methods/relevance/gnn_lrp.py +219 -0
  37. graph_explain-0.7.0/src/graph_explain/narration/__init__.py +3 -0
  38. graph_explain-0.7.0/src/graph_explain/narration/narrator.py +185 -0
  39. graph_explain-0.7.0/src/graph_explain/visualization/__init__.py +4 -0
  40. graph_explain-0.7.0/src/graph_explain/visualization/interactive.py +73 -0
  41. graph_explain-0.7.0/src/graph_explain/visualization/static.py +90 -0
  42. graph_explain-0.7.0/src/graph_explain.egg-info/PKG-INFO +332 -0
  43. graph_explain-0.7.0/src/graph_explain.egg-info/SOURCES.txt +56 -0
  44. graph_explain-0.7.0/src/graph_explain.egg-info/dependency_links.txt +1 -0
  45. graph_explain-0.7.0/src/graph_explain.egg-info/entry_points.txt +2 -0
  46. graph_explain-0.7.0/src/graph_explain.egg-info/requires.txt +33 -0
  47. graph_explain-0.7.0/src/graph_explain.egg-info/top_level.txt +1 -0
  48. graph_explain-0.7.0/tests/test_cli.py +265 -0
  49. graph_explain-0.7.0/tests/test_core.py +103 -0
  50. graph_explain-0.7.0/tests/test_counterfactual.py +69 -0
  51. graph_explain-0.7.0/tests/test_dgl_integration.py +107 -0
  52. graph_explain-0.7.0/tests/test_narration.py +76 -0
  53. graph_explain-0.7.0/tests/test_phase10.py +193 -0
  54. graph_explain-0.7.0/tests/test_phase2.py +55 -0
  55. graph_explain-0.7.0/tests/test_phase3.py +154 -0
  56. graph_explain-0.7.0/tests/test_phase4.py +51 -0
  57. graph_explain-0.7.0/tests/test_phase6.py +142 -0
  58. 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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+