explainiverse 0.7.0__tar.gz → 0.8.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.
- {explainiverse-0.7.0 → explainiverse-0.8.0}/PKG-INFO +76 -13
- {explainiverse-0.7.0 → explainiverse-0.8.0}/README.md +74 -11
- {explainiverse-0.7.0 → explainiverse-0.8.0}/pyproject.toml +2 -2
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/__init__.py +5 -4
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/adapters/pytorch_adapter.py +88 -25
- explainiverse-0.8.0/src/explainiverse/core/explanation.py +179 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/core/registry.py +18 -0
- explainiverse-0.8.0/src/explainiverse/engine/suite.py +252 -0
- explainiverse-0.8.0/src/explainiverse/evaluation/metrics.py +314 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/attribution/lime_wrapper.py +90 -7
- explainiverse-0.8.0/src/explainiverse/explainers/attribution/shap_wrapper.py +185 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/__init__.py +3 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/integrated_gradients.py +189 -76
- explainiverse-0.8.0/src/explainiverse/explainers/gradient/lrp.py +1206 -0
- explainiverse-0.7.0/src/explainiverse/core/explanation.py +0 -24
- explainiverse-0.7.0/src/explainiverse/engine/suite.py +0 -143
- explainiverse-0.7.0/src/explainiverse/evaluation/metrics.py +0 -233
- explainiverse-0.7.0/src/explainiverse/explainers/attribution/shap_wrapper.py +0 -89
- {explainiverse-0.7.0 → explainiverse-0.8.0}/LICENSE +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/adapters/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/adapters/base_adapter.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/adapters/sklearn_adapter.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/core/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/core/explainer.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/engine/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/evaluation/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/evaluation/_utils.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/evaluation/faithfulness.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/evaluation/stability.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/attribution/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/attribution/treeshap_wrapper.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/counterfactual/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/counterfactual/dice_wrapper.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/example_based/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/example_based/protodash.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/global_explainers/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/global_explainers/ale.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/global_explainers/partial_dependence.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/global_explainers/permutation_importance.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/global_explainers/sage.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/deeplift.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/gradcam.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/saliency.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/smoothgrad.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/gradient/tcav.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/rule_based/__init__.py +0 -0
- {explainiverse-0.7.0 → explainiverse-0.8.0}/src/explainiverse/explainers/rule_based/anchors_wrapper.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: explainiverse
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Unified, extensible explainability framework supporting LIME, SHAP,
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Unified, extensible explainability framework supporting 18 XAI methods including LIME, SHAP, LRP, TCAV, GradCAM, and more
|
|
5
5
|
Home-page: https://github.com/jemsbhai/explainiverse
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: xai,explainability,interpretability,machine-learning,lime,shap,anchors
|
|
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
[](https://www.python.org/downloads/)
|
|
36
36
|
[](https://opensource.org/licenses/MIT)
|
|
37
37
|
|
|
38
|
-
**Explainiverse** is a unified, extensible Python framework for Explainable AI (XAI). It provides a standardized interface for **
|
|
38
|
+
**Explainiverse** is a unified, extensible Python framework for Explainable AI (XAI). It provides a standardized interface for **18 state-of-the-art explanation methods** across local, global, gradient-based, concept-based, and example-based paradigms, along with **comprehensive evaluation metrics** for assessing explanation quality.
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
|
|
44
44
|
| Feature | Description |
|
|
45
45
|
|---------|-------------|
|
|
46
|
-
| **
|
|
46
|
+
| **18 Explainers** | LIME, KernelSHAP, TreeSHAP, Integrated Gradients, DeepLIFT, DeepSHAP, SmoothGrad, Saliency Maps, GradCAM/GradCAM++, LRP, TCAV, Anchors, Counterfactual, Permutation Importance, PDP, ALE, SAGE, ProtoDash |
|
|
47
47
|
| **8 Evaluation Metrics** | Faithfulness (PGI, PGU, Comprehensiveness, Sufficiency, Correlation) and Stability (RIS, ROS, Lipschitz) |
|
|
48
48
|
| **Unified API** | Consistent `BaseExplainer` interface with standardized `Explanation` output |
|
|
49
49
|
| **Plugin Registry** | Filter explainers by scope, model type, data type; automatic recommendations |
|
|
@@ -66,6 +66,7 @@ Description-Content-Type: text/markdown
|
|
|
66
66
|
| **SmoothGrad** | Gradient | [Smilkov et al., 2017](https://arxiv.org/abs/1706.03825) |
|
|
67
67
|
| **Saliency Maps** | Gradient | [Simonyan et al., 2014](https://arxiv.org/abs/1312.6034) |
|
|
68
68
|
| **GradCAM / GradCAM++** | Gradient (CNN) | [Selvaraju et al., 2017](https://arxiv.org/abs/1610.02391) |
|
|
69
|
+
| **LRP** | Decomposition | [Bach et al., 2015](https://doi.org/10.1371/journal.pone.0130140) |
|
|
69
70
|
| **TCAV** | Concept-Based | [Kim et al., 2018](https://arxiv.org/abs/1711.11279) |
|
|
70
71
|
| **Anchors** | Rule-Based | [Ribeiro et al., 2018](https://ojs.aaai.org/index.php/AAAI/article/view/11491) |
|
|
71
72
|
| **Counterfactual** | Contrastive | [Mothilal et al., 2020](https://arxiv.org/abs/1905.07697) |
|
|
@@ -143,7 +144,7 @@ adapter = SklearnAdapter(model, class_names=iris.target_names.tolist())
|
|
|
143
144
|
# List all available explainers
|
|
144
145
|
print(default_registry.list_explainers())
|
|
145
146
|
# ['lime', 'shap', 'treeshap', 'integrated_gradients', 'deeplift', 'deepshap',
|
|
146
|
-
# 'smoothgrad', 'saliency', 'gradcam', 'tcav', 'anchors', 'counterfactual',
|
|
147
|
+
# 'smoothgrad', 'saliency', 'gradcam', 'lrp', 'tcav', 'anchors', 'counterfactual',
|
|
147
148
|
# 'protodash', 'permutation_importance', 'partial_dependence', 'ale', 'sage']
|
|
148
149
|
|
|
149
150
|
# Create an explainer via registry
|
|
@@ -211,6 +212,70 @@ print(f"Attributions: {explanation.explanation_data['feature_attributions']}")
|
|
|
211
212
|
print(f"Convergence δ: {explanation.explanation_data['convergence_delta']:.6f}")
|
|
212
213
|
```
|
|
213
214
|
|
|
215
|
+
### Layer-wise Relevance Propagation (LRP)
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from explainiverse.explainers.gradient import LRPExplainer
|
|
219
|
+
|
|
220
|
+
# LRP - Decomposition-based attribution with conservation property
|
|
221
|
+
explainer = LRPExplainer(
|
|
222
|
+
model=adapter,
|
|
223
|
+
feature_names=feature_names,
|
|
224
|
+
class_names=class_names,
|
|
225
|
+
rule="epsilon", # Propagation rule: epsilon, gamma, alpha_beta, z_plus, composite
|
|
226
|
+
epsilon=1e-6 # Stabilization constant
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Basic explanation
|
|
230
|
+
explanation = explainer.explain(X[0], target_class=0)
|
|
231
|
+
print(explanation.explanation_data["feature_attributions"])
|
|
232
|
+
|
|
233
|
+
# Verify conservation property (sum of attributions ≈ target output)
|
|
234
|
+
explanation = explainer.explain(X[0], return_convergence_delta=True)
|
|
235
|
+
print(f"Conservation delta: {explanation.explanation_data['convergence_delta']:.6f}")
|
|
236
|
+
|
|
237
|
+
# Compare different LRP rules
|
|
238
|
+
comparison = explainer.compare_rules(X[0], rules=["epsilon", "gamma", "z_plus"])
|
|
239
|
+
for rule, result in comparison.items():
|
|
240
|
+
print(f"{rule}: top feature = {result['top_feature']}")
|
|
241
|
+
|
|
242
|
+
# Layer-wise relevance analysis
|
|
243
|
+
layer_result = explainer.explain_with_layer_relevances(X[0])
|
|
244
|
+
for layer, relevances in layer_result["layer_relevances"].items():
|
|
245
|
+
print(f"{layer}: sum = {sum(relevances):.4f}")
|
|
246
|
+
|
|
247
|
+
# Composite rules: different rules for different layers
|
|
248
|
+
explainer_composite = LRPExplainer(
|
|
249
|
+
model=adapter,
|
|
250
|
+
feature_names=feature_names,
|
|
251
|
+
class_names=class_names,
|
|
252
|
+
rule="composite"
|
|
253
|
+
)
|
|
254
|
+
explainer_composite.set_composite_rule({
|
|
255
|
+
0: "z_plus", # Input layer: focus on what's present
|
|
256
|
+
2: "epsilon", # Middle layers: balanced
|
|
257
|
+
4: "epsilon" # Output layer
|
|
258
|
+
})
|
|
259
|
+
explanation = explainer_composite.explain(X[0])
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**LRP Propagation Rules:**
|
|
263
|
+
|
|
264
|
+
| Rule | Description | Use Case |
|
|
265
|
+
|------|-------------|----------|
|
|
266
|
+
| `epsilon` | Adds stabilization constant | General purpose (default) |
|
|
267
|
+
| `gamma` | Enhances positive contributions | Image classification |
|
|
268
|
+
| `alpha_beta` | Separates pos/neg (α-β=1) | Fine-grained control |
|
|
269
|
+
| `z_plus` | Only positive weights | Input layers, what's present |
|
|
270
|
+
| `composite` | Different rules per layer | Best practice for deep nets |
|
|
271
|
+
|
|
272
|
+
**Supported Layers:**
|
|
273
|
+
- Linear, Conv2d
|
|
274
|
+
- BatchNorm1d, BatchNorm2d
|
|
275
|
+
- ReLU, LeakyReLU, ELU, Tanh, Sigmoid, GELU
|
|
276
|
+
- MaxPool2d, AvgPool2d, AdaptiveAvgPool2d
|
|
277
|
+
- Flatten, Dropout
|
|
278
|
+
|
|
214
279
|
### DeepLIFT and DeepSHAP
|
|
215
280
|
|
|
216
281
|
```python
|
|
@@ -602,7 +667,7 @@ explainiverse/
|
|
|
602
667
|
│ └── pytorch_adapter.py # With gradient support
|
|
603
668
|
├── explainers/
|
|
604
669
|
│ ├── attribution/ # LIME, SHAP, TreeSHAP
|
|
605
|
-
│ ├── gradient/ # IG, DeepLIFT, DeepSHAP, SmoothGrad, Saliency, GradCAM, TCAV
|
|
670
|
+
│ ├── gradient/ # IG, DeepLIFT, DeepSHAP, SmoothGrad, Saliency, GradCAM, LRP, TCAV
|
|
606
671
|
│ ├── rule_based/ # Anchors
|
|
607
672
|
│ ├── counterfactual/ # DiCE-style
|
|
608
673
|
│ ├── global_explainers/ # Permutation, PDP, ALE, SAGE
|
|
@@ -626,10 +691,10 @@ poetry run pytest
|
|
|
626
691
|
poetry run pytest --cov=explainiverse --cov-report=html
|
|
627
692
|
|
|
628
693
|
# Run specific test file
|
|
629
|
-
poetry run pytest tests/
|
|
694
|
+
poetry run pytest tests/test_lrp.py -v
|
|
630
695
|
|
|
631
696
|
# Run specific test class
|
|
632
|
-
poetry run pytest tests/
|
|
697
|
+
poetry run pytest tests/test_lrp.py::TestLRPConv2d -v
|
|
633
698
|
```
|
|
634
699
|
|
|
635
700
|
---
|
|
@@ -640,6 +705,7 @@ poetry run pytest tests/test_smoothgrad.py::TestSmoothGradBasic -v
|
|
|
640
705
|
- [x] Core framework (BaseExplainer, Explanation, Registry)
|
|
641
706
|
- [x] Perturbation methods: LIME, KernelSHAP, TreeSHAP
|
|
642
707
|
- [x] Gradient methods: Integrated Gradients, DeepLIFT, DeepSHAP, SmoothGrad, Saliency Maps, GradCAM/GradCAM++
|
|
708
|
+
- [x] Decomposition methods: Layer-wise Relevance Propagation (LRP) with ε, γ, αβ, z⁺, composite rules
|
|
643
709
|
- [x] Concept-based: TCAV (Testing with Concept Activation Vectors)
|
|
644
710
|
- [x] Rule-based: Anchors
|
|
645
711
|
- [x] Counterfactual: DiCE-style
|
|
@@ -649,9 +715,6 @@ poetry run pytest tests/test_smoothgrad.py::TestSmoothGradBasic -v
|
|
|
649
715
|
- [x] Evaluation: Stability metrics (RIS, ROS, Lipschitz)
|
|
650
716
|
- [x] PyTorch adapter with gradient support
|
|
651
717
|
|
|
652
|
-
### In Progress 🚧
|
|
653
|
-
- [ ] Layer-wise Relevance Propagation (LRP)
|
|
654
|
-
|
|
655
718
|
### Planned 📋
|
|
656
719
|
- [ ] Attention-based explanations (for Transformers)
|
|
657
720
|
- [ ] TensorFlow/Keras adapter
|
|
@@ -671,7 +734,7 @@ If you use Explainiverse in your research, please cite:
|
|
|
671
734
|
author = {Syed, Muntaser},
|
|
672
735
|
year = {2025},
|
|
673
736
|
url = {https://github.com/jemsbhai/explainiverse},
|
|
674
|
-
version = {0.
|
|
737
|
+
version = {0.8.0}
|
|
675
738
|
}
|
|
676
739
|
```
|
|
677
740
|
|
|
@@ -699,5 +762,5 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
699
762
|
|
|
700
763
|
## Acknowledgments
|
|
701
764
|
|
|
702
|
-
Explainiverse builds upon the foundational work of many researchers in the XAI community. We thank the authors of LIME, SHAP, Integrated Gradients, DeepLIFT, GradCAM, TCAV, Anchors, DiCE, ALE, SAGE, and ProtoDash for their contributions to interpretable machine learning.
|
|
765
|
+
Explainiverse builds upon the foundational work of many researchers in the XAI community. We thank the authors of LIME, SHAP, Integrated Gradients, DeepLIFT, LRP, GradCAM, TCAV, Anchors, DiCE, ALE, SAGE, and ProtoDash for their contributions to interpretable machine learning.
|
|
703
766
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
**Explainiverse** is a unified, extensible Python framework for Explainable AI (XAI). It provides a standardized interface for **
|
|
7
|
+
**Explainiverse** is a unified, extensible Python framework for Explainable AI (XAI). It provides a standardized interface for **18 state-of-the-art explanation methods** across local, global, gradient-based, concept-based, and example-based paradigms, along with **comprehensive evaluation metrics** for assessing explanation quality.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
| Feature | Description |
|
|
14
14
|
|---------|-------------|
|
|
15
|
-
| **
|
|
15
|
+
| **18 Explainers** | LIME, KernelSHAP, TreeSHAP, Integrated Gradients, DeepLIFT, DeepSHAP, SmoothGrad, Saliency Maps, GradCAM/GradCAM++, LRP, TCAV, Anchors, Counterfactual, Permutation Importance, PDP, ALE, SAGE, ProtoDash |
|
|
16
16
|
| **8 Evaluation Metrics** | Faithfulness (PGI, PGU, Comprehensiveness, Sufficiency, Correlation) and Stability (RIS, ROS, Lipschitz) |
|
|
17
17
|
| **Unified API** | Consistent `BaseExplainer` interface with standardized `Explanation` output |
|
|
18
18
|
| **Plugin Registry** | Filter explainers by scope, model type, data type; automatic recommendations |
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
| **SmoothGrad** | Gradient | [Smilkov et al., 2017](https://arxiv.org/abs/1706.03825) |
|
|
36
36
|
| **Saliency Maps** | Gradient | [Simonyan et al., 2014](https://arxiv.org/abs/1312.6034) |
|
|
37
37
|
| **GradCAM / GradCAM++** | Gradient (CNN) | [Selvaraju et al., 2017](https://arxiv.org/abs/1610.02391) |
|
|
38
|
+
| **LRP** | Decomposition | [Bach et al., 2015](https://doi.org/10.1371/journal.pone.0130140) |
|
|
38
39
|
| **TCAV** | Concept-Based | [Kim et al., 2018](https://arxiv.org/abs/1711.11279) |
|
|
39
40
|
| **Anchors** | Rule-Based | [Ribeiro et al., 2018](https://ojs.aaai.org/index.php/AAAI/article/view/11491) |
|
|
40
41
|
| **Counterfactual** | Contrastive | [Mothilal et al., 2020](https://arxiv.org/abs/1905.07697) |
|
|
@@ -112,7 +113,7 @@ adapter = SklearnAdapter(model, class_names=iris.target_names.tolist())
|
|
|
112
113
|
# List all available explainers
|
|
113
114
|
print(default_registry.list_explainers())
|
|
114
115
|
# ['lime', 'shap', 'treeshap', 'integrated_gradients', 'deeplift', 'deepshap',
|
|
115
|
-
# 'smoothgrad', 'saliency', 'gradcam', 'tcav', 'anchors', 'counterfactual',
|
|
116
|
+
# 'smoothgrad', 'saliency', 'gradcam', 'lrp', 'tcav', 'anchors', 'counterfactual',
|
|
116
117
|
# 'protodash', 'permutation_importance', 'partial_dependence', 'ale', 'sage']
|
|
117
118
|
|
|
118
119
|
# Create an explainer via registry
|
|
@@ -180,6 +181,70 @@ print(f"Attributions: {explanation.explanation_data['feature_attributions']}")
|
|
|
180
181
|
print(f"Convergence δ: {explanation.explanation_data['convergence_delta']:.6f}")
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
### Layer-wise Relevance Propagation (LRP)
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from explainiverse.explainers.gradient import LRPExplainer
|
|
188
|
+
|
|
189
|
+
# LRP - Decomposition-based attribution with conservation property
|
|
190
|
+
explainer = LRPExplainer(
|
|
191
|
+
model=adapter,
|
|
192
|
+
feature_names=feature_names,
|
|
193
|
+
class_names=class_names,
|
|
194
|
+
rule="epsilon", # Propagation rule: epsilon, gamma, alpha_beta, z_plus, composite
|
|
195
|
+
epsilon=1e-6 # Stabilization constant
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Basic explanation
|
|
199
|
+
explanation = explainer.explain(X[0], target_class=0)
|
|
200
|
+
print(explanation.explanation_data["feature_attributions"])
|
|
201
|
+
|
|
202
|
+
# Verify conservation property (sum of attributions ≈ target output)
|
|
203
|
+
explanation = explainer.explain(X[0], return_convergence_delta=True)
|
|
204
|
+
print(f"Conservation delta: {explanation.explanation_data['convergence_delta']:.6f}")
|
|
205
|
+
|
|
206
|
+
# Compare different LRP rules
|
|
207
|
+
comparison = explainer.compare_rules(X[0], rules=["epsilon", "gamma", "z_plus"])
|
|
208
|
+
for rule, result in comparison.items():
|
|
209
|
+
print(f"{rule}: top feature = {result['top_feature']}")
|
|
210
|
+
|
|
211
|
+
# Layer-wise relevance analysis
|
|
212
|
+
layer_result = explainer.explain_with_layer_relevances(X[0])
|
|
213
|
+
for layer, relevances in layer_result["layer_relevances"].items():
|
|
214
|
+
print(f"{layer}: sum = {sum(relevances):.4f}")
|
|
215
|
+
|
|
216
|
+
# Composite rules: different rules for different layers
|
|
217
|
+
explainer_composite = LRPExplainer(
|
|
218
|
+
model=adapter,
|
|
219
|
+
feature_names=feature_names,
|
|
220
|
+
class_names=class_names,
|
|
221
|
+
rule="composite"
|
|
222
|
+
)
|
|
223
|
+
explainer_composite.set_composite_rule({
|
|
224
|
+
0: "z_plus", # Input layer: focus on what's present
|
|
225
|
+
2: "epsilon", # Middle layers: balanced
|
|
226
|
+
4: "epsilon" # Output layer
|
|
227
|
+
})
|
|
228
|
+
explanation = explainer_composite.explain(X[0])
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**LRP Propagation Rules:**
|
|
232
|
+
|
|
233
|
+
| Rule | Description | Use Case |
|
|
234
|
+
|------|-------------|----------|
|
|
235
|
+
| `epsilon` | Adds stabilization constant | General purpose (default) |
|
|
236
|
+
| `gamma` | Enhances positive contributions | Image classification |
|
|
237
|
+
| `alpha_beta` | Separates pos/neg (α-β=1) | Fine-grained control |
|
|
238
|
+
| `z_plus` | Only positive weights | Input layers, what's present |
|
|
239
|
+
| `composite` | Different rules per layer | Best practice for deep nets |
|
|
240
|
+
|
|
241
|
+
**Supported Layers:**
|
|
242
|
+
- Linear, Conv2d
|
|
243
|
+
- BatchNorm1d, BatchNorm2d
|
|
244
|
+
- ReLU, LeakyReLU, ELU, Tanh, Sigmoid, GELU
|
|
245
|
+
- MaxPool2d, AvgPool2d, AdaptiveAvgPool2d
|
|
246
|
+
- Flatten, Dropout
|
|
247
|
+
|
|
183
248
|
### DeepLIFT and DeepSHAP
|
|
184
249
|
|
|
185
250
|
```python
|
|
@@ -571,7 +636,7 @@ explainiverse/
|
|
|
571
636
|
│ └── pytorch_adapter.py # With gradient support
|
|
572
637
|
├── explainers/
|
|
573
638
|
│ ├── attribution/ # LIME, SHAP, TreeSHAP
|
|
574
|
-
│ ├── gradient/ # IG, DeepLIFT, DeepSHAP, SmoothGrad, Saliency, GradCAM, TCAV
|
|
639
|
+
│ ├── gradient/ # IG, DeepLIFT, DeepSHAP, SmoothGrad, Saliency, GradCAM, LRP, TCAV
|
|
575
640
|
│ ├── rule_based/ # Anchors
|
|
576
641
|
│ ├── counterfactual/ # DiCE-style
|
|
577
642
|
│ ├── global_explainers/ # Permutation, PDP, ALE, SAGE
|
|
@@ -595,10 +660,10 @@ poetry run pytest
|
|
|
595
660
|
poetry run pytest --cov=explainiverse --cov-report=html
|
|
596
661
|
|
|
597
662
|
# Run specific test file
|
|
598
|
-
poetry run pytest tests/
|
|
663
|
+
poetry run pytest tests/test_lrp.py -v
|
|
599
664
|
|
|
600
665
|
# Run specific test class
|
|
601
|
-
poetry run pytest tests/
|
|
666
|
+
poetry run pytest tests/test_lrp.py::TestLRPConv2d -v
|
|
602
667
|
```
|
|
603
668
|
|
|
604
669
|
---
|
|
@@ -609,6 +674,7 @@ poetry run pytest tests/test_smoothgrad.py::TestSmoothGradBasic -v
|
|
|
609
674
|
- [x] Core framework (BaseExplainer, Explanation, Registry)
|
|
610
675
|
- [x] Perturbation methods: LIME, KernelSHAP, TreeSHAP
|
|
611
676
|
- [x] Gradient methods: Integrated Gradients, DeepLIFT, DeepSHAP, SmoothGrad, Saliency Maps, GradCAM/GradCAM++
|
|
677
|
+
- [x] Decomposition methods: Layer-wise Relevance Propagation (LRP) with ε, γ, αβ, z⁺, composite rules
|
|
612
678
|
- [x] Concept-based: TCAV (Testing with Concept Activation Vectors)
|
|
613
679
|
- [x] Rule-based: Anchors
|
|
614
680
|
- [x] Counterfactual: DiCE-style
|
|
@@ -618,9 +684,6 @@ poetry run pytest tests/test_smoothgrad.py::TestSmoothGradBasic -v
|
|
|
618
684
|
- [x] Evaluation: Stability metrics (RIS, ROS, Lipschitz)
|
|
619
685
|
- [x] PyTorch adapter with gradient support
|
|
620
686
|
|
|
621
|
-
### In Progress 🚧
|
|
622
|
-
- [ ] Layer-wise Relevance Propagation (LRP)
|
|
623
|
-
|
|
624
687
|
### Planned 📋
|
|
625
688
|
- [ ] Attention-based explanations (for Transformers)
|
|
626
689
|
- [ ] TensorFlow/Keras adapter
|
|
@@ -640,7 +703,7 @@ If you use Explainiverse in your research, please cite:
|
|
|
640
703
|
author = {Syed, Muntaser},
|
|
641
704
|
year = {2025},
|
|
642
705
|
url = {https://github.com/jemsbhai/explainiverse},
|
|
643
|
-
version = {0.
|
|
706
|
+
version = {0.8.0}
|
|
644
707
|
}
|
|
645
708
|
```
|
|
646
709
|
|
|
@@ -668,4 +731,4 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
668
731
|
|
|
669
732
|
## Acknowledgments
|
|
670
733
|
|
|
671
|
-
Explainiverse builds upon the foundational work of many researchers in the XAI community. We thank the authors of LIME, SHAP, Integrated Gradients, DeepLIFT, GradCAM, TCAV, Anchors, DiCE, ALE, SAGE, and ProtoDash for their contributions to interpretable machine learning.
|
|
734
|
+
Explainiverse builds upon the foundational work of many researchers in the XAI community. We thank the authors of LIME, SHAP, Integrated Gradients, DeepLIFT, LRP, GradCAM, TCAV, Anchors, DiCE, ALE, SAGE, and ProtoDash for their contributions to interpretable machine learning.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "explainiverse"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "Unified, extensible explainability framework supporting LIME, SHAP,
|
|
3
|
+
version = "0.8.0"
|
|
4
|
+
description = "Unified, extensible explainability framework supporting 18 XAI methods including LIME, SHAP, LRP, TCAV, GradCAM, and more"
|
|
5
5
|
authors = ["Muntaser Syed <jemsbhai@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Explainiverse - A unified, extensible explainability framework.
|
|
4
4
|
|
|
5
|
-
Supports
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Supports 18 state-of-the-art XAI methods including LIME, SHAP, TreeSHAP,
|
|
6
|
+
Integrated Gradients, DeepLIFT, DeepSHAP, LRP, GradCAM, TCAV, Anchors,
|
|
7
|
+
Counterfactuals, Permutation Importance, PDP, ALE, SAGE, and ProtoDash
|
|
8
|
+
through a consistent interface.
|
|
8
9
|
|
|
9
10
|
Quick Start:
|
|
10
11
|
from explainiverse import default_registry
|
|
@@ -33,7 +34,7 @@ from explainiverse.adapters.sklearn_adapter import SklearnAdapter
|
|
|
33
34
|
from explainiverse.adapters import TORCH_AVAILABLE
|
|
34
35
|
from explainiverse.engine.suite import ExplanationSuite
|
|
35
36
|
|
|
36
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.8.0"
|
|
37
38
|
|
|
38
39
|
__all__ = [
|
|
39
40
|
# Core
|
|
@@ -25,7 +25,7 @@ Example:
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
import numpy as np
|
|
28
|
-
from typing import List, Optional, Union,
|
|
28
|
+
from typing import List, Optional, Union, Tuple
|
|
29
29
|
|
|
30
30
|
from .base_adapter import BaseModelAdapter
|
|
31
31
|
|
|
@@ -57,6 +57,11 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
57
57
|
explainability methods. Handles device management, tensor/numpy
|
|
58
58
|
conversions, and supports both classification and regression tasks.
|
|
59
59
|
|
|
60
|
+
Supports:
|
|
61
|
+
- Multi-class classification (output shape: [batch, n_classes])
|
|
62
|
+
- Binary classification (output shape: [batch, 1] or [batch])
|
|
63
|
+
- Regression (output shape: [batch, n_outputs] or [batch])
|
|
64
|
+
|
|
60
65
|
Attributes:
|
|
61
66
|
model: The PyTorch model (nn.Module)
|
|
62
67
|
task: "classification" or "regression"
|
|
@@ -150,11 +155,27 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
150
155
|
def _apply_activation(self, output: "torch.Tensor") -> "torch.Tensor":
|
|
151
156
|
"""Apply output activation function."""
|
|
152
157
|
if self.output_activation == "softmax":
|
|
158
|
+
# Handle different output shapes
|
|
159
|
+
if output.dim() == 1 or (output.dim() == 2 and output.shape[1] == 1):
|
|
160
|
+
# Binary: apply sigmoid instead of softmax
|
|
161
|
+
return torch.sigmoid(output)
|
|
153
162
|
return torch.softmax(output, dim=-1)
|
|
154
163
|
elif self.output_activation == "sigmoid":
|
|
155
164
|
return torch.sigmoid(output)
|
|
156
165
|
return output
|
|
157
166
|
|
|
167
|
+
def _normalize_output_shape(self, output: "torch.Tensor") -> "torch.Tensor":
|
|
168
|
+
"""
|
|
169
|
+
Normalize output to consistent 2D shape (batch, outputs).
|
|
170
|
+
|
|
171
|
+
Handles:
|
|
172
|
+
- (batch,) -> (batch, 1)
|
|
173
|
+
- (batch, n) -> (batch, n)
|
|
174
|
+
"""
|
|
175
|
+
if output.dim() == 1:
|
|
176
|
+
return output.unsqueeze(-1)
|
|
177
|
+
return output
|
|
178
|
+
|
|
158
179
|
def predict(self, data: np.ndarray) -> np.ndarray:
|
|
159
180
|
"""
|
|
160
181
|
Generate predictions for input data.
|
|
@@ -183,16 +204,66 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
183
204
|
tensor_batch = self._to_tensor(batch)
|
|
184
205
|
|
|
185
206
|
output = self.model(tensor_batch)
|
|
207
|
+
output = self._normalize_output_shape(output)
|
|
186
208
|
output = self._apply_activation(output)
|
|
187
209
|
outputs.append(self._to_numpy(output))
|
|
188
210
|
|
|
189
211
|
return np.vstack(outputs)
|
|
190
212
|
|
|
213
|
+
def _get_target_scores(
|
|
214
|
+
self,
|
|
215
|
+
output: "torch.Tensor",
|
|
216
|
+
target_class: Optional[Union[int, "torch.Tensor"]] = None
|
|
217
|
+
) -> "torch.Tensor":
|
|
218
|
+
"""
|
|
219
|
+
Extract target scores for gradient computation.
|
|
220
|
+
|
|
221
|
+
Handles both multi-class and binary classification outputs.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
output: Raw model output (logits)
|
|
225
|
+
target_class: Target class index or tensor of indices
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Target scores tensor for backpropagation
|
|
229
|
+
"""
|
|
230
|
+
batch_size = output.shape[0]
|
|
231
|
+
|
|
232
|
+
# Normalize to 2D
|
|
233
|
+
if output.dim() == 1:
|
|
234
|
+
output = output.unsqueeze(-1)
|
|
235
|
+
|
|
236
|
+
n_outputs = output.shape[1]
|
|
237
|
+
|
|
238
|
+
if self.task == "classification":
|
|
239
|
+
if n_outputs == 1:
|
|
240
|
+
# Binary classification with single logit
|
|
241
|
+
# Score is the logit itself (positive class score)
|
|
242
|
+
return output.squeeze(-1)
|
|
243
|
+
else:
|
|
244
|
+
# Multi-class classification
|
|
245
|
+
if target_class is None:
|
|
246
|
+
target_class = output.argmax(dim=-1)
|
|
247
|
+
elif isinstance(target_class, int):
|
|
248
|
+
target_class = torch.tensor(
|
|
249
|
+
[target_class] * batch_size,
|
|
250
|
+
device=self.device
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Gather scores for target class
|
|
254
|
+
return output.gather(1, target_class.view(-1, 1)).squeeze(-1)
|
|
255
|
+
else:
|
|
256
|
+
# Regression: use first output or sum of outputs
|
|
257
|
+
if n_outputs == 1:
|
|
258
|
+
return output.squeeze(-1)
|
|
259
|
+
else:
|
|
260
|
+
return output.sum(dim=-1)
|
|
261
|
+
|
|
191
262
|
def predict_with_gradients(
|
|
192
263
|
self,
|
|
193
264
|
data: np.ndarray,
|
|
194
265
|
target_class: Optional[int] = None
|
|
195
|
-
) ->
|
|
266
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
196
267
|
"""
|
|
197
268
|
Generate predictions and compute gradients w.r.t. inputs.
|
|
198
269
|
|
|
@@ -203,11 +274,17 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
203
274
|
data: Input data as numpy array.
|
|
204
275
|
target_class: Class index for gradient computation.
|
|
205
276
|
If None, uses the predicted class.
|
|
277
|
+
For binary classification with single output,
|
|
278
|
+
this is ignored (gradient w.r.t. the single logit).
|
|
206
279
|
|
|
207
280
|
Returns:
|
|
208
281
|
Tuple of (predictions, gradients) as numpy arrays.
|
|
282
|
+
- predictions: (batch, n_classes) probabilities
|
|
283
|
+
- gradients: same shape as input data
|
|
209
284
|
"""
|
|
210
285
|
data = np.array(data)
|
|
286
|
+
original_shape = data.shape
|
|
287
|
+
|
|
211
288
|
if data.ndim == 1:
|
|
212
289
|
data = data.reshape(1, -1)
|
|
213
290
|
|
|
@@ -217,20 +294,13 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
217
294
|
|
|
218
295
|
# Forward pass
|
|
219
296
|
output = self.model(tensor_data)
|
|
220
|
-
activated_output = self._apply_activation(output)
|
|
221
297
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
# Select target class scores for gradient
|
|
230
|
-
target_scores = output.gather(1, target_class.view(-1, 1)).squeeze()
|
|
231
|
-
else:
|
|
232
|
-
# Regression: gradient w.r.t. output
|
|
233
|
-
target_scores = output.squeeze()
|
|
298
|
+
# Get activated output for return
|
|
299
|
+
output_normalized = self._normalize_output_shape(output)
|
|
300
|
+
activated_output = self._apply_activation(output_normalized)
|
|
301
|
+
|
|
302
|
+
# Get target scores for gradient computation
|
|
303
|
+
target_scores = self._get_target_scores(output, target_class)
|
|
234
304
|
|
|
235
305
|
# Backward pass
|
|
236
306
|
if target_scores.dim() == 0:
|
|
@@ -295,7 +365,7 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
295
365
|
data: np.ndarray,
|
|
296
366
|
layer_name: str,
|
|
297
367
|
target_class: Optional[int] = None
|
|
298
|
-
) ->
|
|
368
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
299
369
|
"""
|
|
300
370
|
Get gradients of output w.r.t. a specific layer's activations.
|
|
301
371
|
|
|
@@ -339,15 +409,8 @@ class PyTorchAdapter(BaseModelAdapter):
|
|
|
339
409
|
|
|
340
410
|
output = self.model(tensor_data)
|
|
341
411
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
target_class = output.argmax(dim=-1)
|
|
345
|
-
elif isinstance(target_class, int):
|
|
346
|
-
target_class = torch.tensor([target_class] * data.shape[0], device=self.device)
|
|
347
|
-
|
|
348
|
-
target_scores = output.gather(1, target_class.view(-1, 1)).squeeze()
|
|
349
|
-
else:
|
|
350
|
-
target_scores = output.squeeze()
|
|
412
|
+
# Get target scores using the new method
|
|
413
|
+
target_scores = self._get_target_scores(output, target_class)
|
|
351
414
|
|
|
352
415
|
if target_scores.dim() == 0:
|
|
353
416
|
target_scores.backward()
|