exai67 0.1.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.
Potentially problematic release.
This version of exai67 might be problematic. Click here for more details.
- exai67-0.1.0/LICENSE +21 -0
- exai67-0.1.0/PKG-INFO +143 -0
- exai67-0.1.0/README.md +114 -0
- exai67-0.1.0/pyproject.toml +57 -0
- exai67-0.1.0/setup.cfg +8 -0
- exai67-0.1.0/setup.py +29 -0
- exai67-0.1.0/src/exai/__init__.py +195 -0
- exai67-0.1.0/src/exai/cbow_gradient.py +37 -0
- exai67-0.1.0/src/exai/cli.py +94 -0
- exai67-0.1.0/src/exai/gradcam.py +16 -0
- exai67-0.1.0/src/exai/ice.py +16 -0
- exai67-0.1.0/src/exai/index.py +68 -0
- exai67-0.1.0/src/exai/lime.py +19 -0
- exai67-0.1.0/src/exai/linear_logistic.py +16 -0
- exai67-0.1.0/src/exai/lrp.py +22 -0
- exai67-0.1.0/src/exai/pdp.py +19 -0
- exai67-0.1.0/src/exai/shapley.py +19 -0
- exai67-0.1.0/src/exai/tree_knn.py +22 -0
- exai67-0.1.0/src/exai/xrai.py +16 -0
- exai67-0.1.0/src/exai67.egg-info/PKG-INFO +143 -0
- exai67-0.1.0/src/exai67.egg-info/SOURCES.txt +25 -0
- exai67-0.1.0/src/exai67.egg-info/dependency_links.txt +1 -0
- exai67-0.1.0/src/exai67.egg-info/entry_points.txt +2 -0
- exai67-0.1.0/src/exai67.egg-info/requires.txt +3 -0
- exai67-0.1.0/src/exai67.egg-info/top_level.txt +1 -0
- exai67-0.1.0/tests/test_library.py +72 -0
exai67-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EXAI Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
exai67-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: exai67
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight, dependency-minimal Explainable AI (XAI) algorithms and laboratory experiments implemented from scratch.
|
|
5
|
+
Author: EXAI Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/exai-toolkit/exai-toolkit
|
|
8
|
+
Project-URL: Repository, https://github.com/exai-toolkit/exai-toolkit
|
|
9
|
+
Keywords: xai,explainable-ai,interpretability,shapley,lime,gradcam,lrp,ice,pdp,xrai,machine-learning
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.20.0
|
|
27
|
+
Requires-Dist: pandas>=1.3.0
|
|
28
|
+
Requires-Dist: scikit-learn>=1.0.0
|
|
29
|
+
|
|
30
|
+
# EXAI (`exai67`)
|
|
31
|
+
|
|
32
|
+
[](https://opensource.org/licenses/MIT)
|
|
33
|
+
[](https://www.python.org/downloads/)
|
|
34
|
+
|
|
35
|
+
A lightweight, dependency-minimal Python library containing complete implementations of **10 foundational Explainable AI (XAI)** laboratory experiments from scratch. Calling any function prints the full Python code for that experiment directly to the screen.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Instant Code Display**: Calling any function or experiment displays the complete, working Python source code.
|
|
42
|
+
- **Pure / Minimal Dependencies**: Built with standard Python, `numpy`, `pandas`, and `scikit-learn`. Zero deep learning frameworks.
|
|
43
|
+
- **Exact Formulations**: Follows published literature (Greenwell et al., Ribeiro et al. LIME, Selvaraju et al. Grad-CAM, Bach et al. LRP).
|
|
44
|
+
- **Interactive CLI**: Display experiment codes directly from terminal with `exai run <1-10>` or `exai list`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Included XAI Algorithms
|
|
49
|
+
|
|
50
|
+
| Experiment | Module / Function | Description |
|
|
51
|
+
| :--- | :--- | :--- |
|
|
52
|
+
| **Index** | `exai.index()` | Index of all 10 experiments with descriptions and commands |
|
|
53
|
+
| **Exp 1** | `exai.exp1()`, `exai.compute_linear_regression_metrics()` | Linear ($R^2$, Adj $R^2$, $t$-stat, 95% CI) & Logistic Regression (Odds Ratio, % change) |
|
|
54
|
+
| **Exp 2** | `exai.exp2()`, `exai.extract_if_then_rules()` | Decision Tree IF-THEN rules, MDI vs Permutation Importance; KNN local neighbor explanations |
|
|
55
|
+
| **Exp 3** | `exai.exp3()`, `exai.approximate_shapley_sampling()` | Approximate Shapley Values via permutation sampling from scratch |
|
|
56
|
+
| **Exp 4** | `exai.exp4()`, `exai.compute_pdp()` | Partial Dependence Plot (PDP) Feature Importance (Greenwell et al., 2018) |
|
|
57
|
+
| **Exp 5** | `exai.exp5()`, `exai.compute_ice_scratch()` | Individual Conditional Expectation (ICE) curves and PDP average computation |
|
|
58
|
+
| **Exp 6** | `exai.exp6()`, `exai.compute_xrai_fixed_regions()` | XRAI Region-based image attribution and visual intensity scoring |
|
|
59
|
+
| **Exp 7** | `exai.exp7()`, `exai.compute_gradcam()` | Grad-CAM (Gradient-Weighted Class Activation Mapping) with ReLU heatmap generation |
|
|
60
|
+
| **Exp 8** | `exai.exp8()`, `exai.fit_lime_surrogate()` | LIME from scratch via Weighted Least Squares (WLS) and exponential kernels |
|
|
61
|
+
| **Exp 9** | `exai.exp9()`, `exai.compute_gradient_x_input()` | CBOW Word Embeddings from scratch & Gradient × Input sentiment attribution |
|
|
62
|
+
| **Exp 10** | `exai.exp10()`, `exai.layerwise_relevance_propagation()` | Layer-Wise Relevance Propagation (LRP) for multi-layer neural networks |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### From PyPI
|
|
69
|
+
```bash
|
|
70
|
+
pip install exai67
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Local Development Install
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Python API Usage
|
|
81
|
+
|
|
82
|
+
Calling any function displays the clean, complete, ready-to-run Python code for that experiment:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import exai
|
|
86
|
+
|
|
87
|
+
# 1. Print code by experiment number
|
|
88
|
+
exai.get_code(1) # Displays Experiment 1 code
|
|
89
|
+
exai.get_code(8) # Displays Experiment 8 code
|
|
90
|
+
|
|
91
|
+
# 2. Print code using shortcut functions
|
|
92
|
+
exai.exp1() # Linear & Logistic Regression
|
|
93
|
+
exai.exp2() # Decision Trees & KNN
|
|
94
|
+
exai.exp3() # Shapley Values
|
|
95
|
+
exai.exp4() # PDP Feature Importance
|
|
96
|
+
exai.exp5() # ICE Plot
|
|
97
|
+
exai.exp6() # XRAI Attribution
|
|
98
|
+
exai.exp7() # Grad-CAM
|
|
99
|
+
exai.exp8() # LIME
|
|
100
|
+
exai.exp9() # CBOW & Gradient x Input
|
|
101
|
+
exai.exp10() # LRP
|
|
102
|
+
|
|
103
|
+
# 3. Print code using algorithm names
|
|
104
|
+
exai.compute_gradcam()
|
|
105
|
+
exai.fit_lime_surrogate()
|
|
106
|
+
exai.layerwise_relevance_propagation()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Command-Line Interface (CLI)
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# List all 10 available experiments
|
|
115
|
+
exai list
|
|
116
|
+
|
|
117
|
+
# Display code for a specific experiment
|
|
118
|
+
exai run 8
|
|
119
|
+
|
|
120
|
+
# Display code for all experiments
|
|
121
|
+
exai run all
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Publishing to PyPI
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 1. Install build tools
|
|
130
|
+
pip install --upgrade build twine
|
|
131
|
+
|
|
132
|
+
# 2. Build the package
|
|
133
|
+
python3 setup.py sdist
|
|
134
|
+
|
|
135
|
+
# 3. Upload to PyPI
|
|
136
|
+
twine upload dist/*
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
This project is licensed under the [MIT License](LICENSE).
|
exai67-0.1.0/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# EXAI (`exai67`)
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
|
|
6
|
+
A lightweight, dependency-minimal Python library containing complete implementations of **10 foundational Explainable AI (XAI)** laboratory experiments from scratch. Calling any function prints the full Python code for that experiment directly to the screen.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Instant Code Display**: Calling any function or experiment displays the complete, working Python source code.
|
|
13
|
+
- **Pure / Minimal Dependencies**: Built with standard Python, `numpy`, `pandas`, and `scikit-learn`. Zero deep learning frameworks.
|
|
14
|
+
- **Exact Formulations**: Follows published literature (Greenwell et al., Ribeiro et al. LIME, Selvaraju et al. Grad-CAM, Bach et al. LRP).
|
|
15
|
+
- **Interactive CLI**: Display experiment codes directly from terminal with `exai run <1-10>` or `exai list`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Included XAI Algorithms
|
|
20
|
+
|
|
21
|
+
| Experiment | Module / Function | Description |
|
|
22
|
+
| :--- | :--- | :--- |
|
|
23
|
+
| **Index** | `exai.index()` | Index of all 10 experiments with descriptions and commands |
|
|
24
|
+
| **Exp 1** | `exai.exp1()`, `exai.compute_linear_regression_metrics()` | Linear ($R^2$, Adj $R^2$, $t$-stat, 95% CI) & Logistic Regression (Odds Ratio, % change) |
|
|
25
|
+
| **Exp 2** | `exai.exp2()`, `exai.extract_if_then_rules()` | Decision Tree IF-THEN rules, MDI vs Permutation Importance; KNN local neighbor explanations |
|
|
26
|
+
| **Exp 3** | `exai.exp3()`, `exai.approximate_shapley_sampling()` | Approximate Shapley Values via permutation sampling from scratch |
|
|
27
|
+
| **Exp 4** | `exai.exp4()`, `exai.compute_pdp()` | Partial Dependence Plot (PDP) Feature Importance (Greenwell et al., 2018) |
|
|
28
|
+
| **Exp 5** | `exai.exp5()`, `exai.compute_ice_scratch()` | Individual Conditional Expectation (ICE) curves and PDP average computation |
|
|
29
|
+
| **Exp 6** | `exai.exp6()`, `exai.compute_xrai_fixed_regions()` | XRAI Region-based image attribution and visual intensity scoring |
|
|
30
|
+
| **Exp 7** | `exai.exp7()`, `exai.compute_gradcam()` | Grad-CAM (Gradient-Weighted Class Activation Mapping) with ReLU heatmap generation |
|
|
31
|
+
| **Exp 8** | `exai.exp8()`, `exai.fit_lime_surrogate()` | LIME from scratch via Weighted Least Squares (WLS) and exponential kernels |
|
|
32
|
+
| **Exp 9** | `exai.exp9()`, `exai.compute_gradient_x_input()` | CBOW Word Embeddings from scratch & Gradient × Input sentiment attribution |
|
|
33
|
+
| **Exp 10** | `exai.exp10()`, `exai.layerwise_relevance_propagation()` | Layer-Wise Relevance Propagation (LRP) for multi-layer neural networks |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### From PyPI
|
|
40
|
+
```bash
|
|
41
|
+
pip install exai67
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Local Development Install
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Python API Usage
|
|
52
|
+
|
|
53
|
+
Calling any function displays the clean, complete, ready-to-run Python code for that experiment:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import exai
|
|
57
|
+
|
|
58
|
+
# 1. Print code by experiment number
|
|
59
|
+
exai.get_code(1) # Displays Experiment 1 code
|
|
60
|
+
exai.get_code(8) # Displays Experiment 8 code
|
|
61
|
+
|
|
62
|
+
# 2. Print code using shortcut functions
|
|
63
|
+
exai.exp1() # Linear & Logistic Regression
|
|
64
|
+
exai.exp2() # Decision Trees & KNN
|
|
65
|
+
exai.exp3() # Shapley Values
|
|
66
|
+
exai.exp4() # PDP Feature Importance
|
|
67
|
+
exai.exp5() # ICE Plot
|
|
68
|
+
exai.exp6() # XRAI Attribution
|
|
69
|
+
exai.exp7() # Grad-CAM
|
|
70
|
+
exai.exp8() # LIME
|
|
71
|
+
exai.exp9() # CBOW & Gradient x Input
|
|
72
|
+
exai.exp10() # LRP
|
|
73
|
+
|
|
74
|
+
# 3. Print code using algorithm names
|
|
75
|
+
exai.compute_gradcam()
|
|
76
|
+
exai.fit_lime_surrogate()
|
|
77
|
+
exai.layerwise_relevance_propagation()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Command-Line Interface (CLI)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# List all 10 available experiments
|
|
86
|
+
exai list
|
|
87
|
+
|
|
88
|
+
# Display code for a specific experiment
|
|
89
|
+
exai run 8
|
|
90
|
+
|
|
91
|
+
# Display code for all experiments
|
|
92
|
+
exai run all
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Publishing to PyPI
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 1. Install build tools
|
|
101
|
+
pip install --upgrade build twine
|
|
102
|
+
|
|
103
|
+
# 2. Build the package
|
|
104
|
+
python3 setup.py sdist
|
|
105
|
+
|
|
106
|
+
# 3. Upload to PyPI
|
|
107
|
+
twine upload dist/*
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "exai67"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight, dependency-minimal Explainable AI (XAI) algorithms and laboratory experiments implemented from scratch."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "EXAI Team" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"xai",
|
|
17
|
+
"explainable-ai",
|
|
18
|
+
"interpretability",
|
|
19
|
+
"shapley",
|
|
20
|
+
"lime",
|
|
21
|
+
"gradcam",
|
|
22
|
+
"lrp",
|
|
23
|
+
"ice",
|
|
24
|
+
"pdp",
|
|
25
|
+
"xrai",
|
|
26
|
+
"machine-learning"
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Intended Audience :: Education",
|
|
32
|
+
"Intended Audience :: Science/Research",
|
|
33
|
+
"License :: OSI Approved :: MIT License",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.8",
|
|
36
|
+
"Programming Language :: Python :: 3.9",
|
|
37
|
+
"Programming Language :: Python :: 3.10",
|
|
38
|
+
"Programming Language :: Python :: 3.11",
|
|
39
|
+
"Programming Language :: Python :: 3.12",
|
|
40
|
+
"Programming Language :: Python :: 3.13",
|
|
41
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
42
|
+
]
|
|
43
|
+
dependencies = [
|
|
44
|
+
"numpy>=1.20.0",
|
|
45
|
+
"pandas>=1.3.0",
|
|
46
|
+
"scikit-learn>=1.0.0"
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
exai = "exai.cli:main"
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/exai-toolkit/exai-toolkit"
|
|
54
|
+
Repository = "https://github.com/exai-toolkit/exai-toolkit"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
exai67-0.1.0/setup.cfg
ADDED
exai67-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="exai67",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Lightweight Explainable AI (XAI) algorithms from scratch without deep learning libraries.",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="EXAI Team",
|
|
10
|
+
license="MIT",
|
|
11
|
+
package_dir={"": "src"},
|
|
12
|
+
packages=find_packages(where="src"),
|
|
13
|
+
python_requires=">=3.8",
|
|
14
|
+
install_requires=[
|
|
15
|
+
"numpy>=1.20.0",
|
|
16
|
+
"pandas>=1.3.0",
|
|
17
|
+
"scikit-learn>=1.0.0"
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
"console_scripts": [
|
|
21
|
+
"exai=exai.cli:main",
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
classifiers=[
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
+
],
|
|
29
|
+
)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EXAI: Explainable AI Toolkit
|
|
3
|
+
============================
|
|
4
|
+
A Python library providing complete implementations of 10 foundational Explainable AI (XAI)
|
|
5
|
+
laboratory experiments. Calling any function displays the complete Python code for that experiment.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
import exai
|
|
9
|
+
exai.exp1() # Prints code for Experiment 1
|
|
10
|
+
exai.experiment_8() # Prints code for Experiment 8 (LIME)
|
|
11
|
+
exai.get_code(7) # Prints code for Experiment 7 (Grad-CAM)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
from exai.linear_logistic import (
|
|
17
|
+
experiment_1,
|
|
18
|
+
run_experiment_1,
|
|
19
|
+
exp1,
|
|
20
|
+
compute_linear_regression_metrics,
|
|
21
|
+
interpret_logistic_regression,
|
|
22
|
+
CODE_EXPERIMENT_1,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from exai.tree_knn import (
|
|
26
|
+
experiment_2,
|
|
27
|
+
run_experiment_2,
|
|
28
|
+
exp2,
|
|
29
|
+
extract_if_then_rules,
|
|
30
|
+
compute_permutation_importance,
|
|
31
|
+
explain_knn_classification,
|
|
32
|
+
explain_knn_regression,
|
|
33
|
+
CODE_EXPERIMENT_2,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from exai.shapley import (
|
|
37
|
+
experiment_3,
|
|
38
|
+
run_experiment_3,
|
|
39
|
+
exp3,
|
|
40
|
+
linear_model,
|
|
41
|
+
compute_single_permutation_marginal_contribution,
|
|
42
|
+
approximate_shapley_sampling,
|
|
43
|
+
CODE_EXPERIMENT_3,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
from exai.pdp import (
|
|
47
|
+
experiment_4,
|
|
48
|
+
run_experiment_4,
|
|
49
|
+
exp4,
|
|
50
|
+
compute_pdp,
|
|
51
|
+
compute_numerical_pdp_importance,
|
|
52
|
+
compute_categorical_pdp_importance,
|
|
53
|
+
CODE_EXPERIMENT_4,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from exai.ice import (
|
|
57
|
+
experiment_5,
|
|
58
|
+
run_experiment_5,
|
|
59
|
+
exp5,
|
|
60
|
+
compute_ice_scratch,
|
|
61
|
+
ascii_ice_plot,
|
|
62
|
+
CODE_EXPERIMENT_5,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
from exai.xrai import (
|
|
66
|
+
experiment_6,
|
|
67
|
+
run_experiment_6,
|
|
68
|
+
exp6,
|
|
69
|
+
get_visual_intensity_bar,
|
|
70
|
+
compute_xrai_fixed_regions,
|
|
71
|
+
CODE_EXPERIMENT_6,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
from exai.gradcam import (
|
|
75
|
+
experiment_7,
|
|
76
|
+
run_experiment_7,
|
|
77
|
+
exp7,
|
|
78
|
+
compute_gradcam,
|
|
79
|
+
print_ascii_heatmap,
|
|
80
|
+
CODE_EXPERIMENT_7,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
from exai.lime import (
|
|
84
|
+
experiment_8,
|
|
85
|
+
run_experiment_8,
|
|
86
|
+
exp8,
|
|
87
|
+
compute_euclidean_distance,
|
|
88
|
+
exponential_kernel,
|
|
89
|
+
fit_lime_surrogate,
|
|
90
|
+
CODE_EXPERIMENT_8,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
from exai.cbow_gradient import (
|
|
94
|
+
experiment_9,
|
|
95
|
+
run_experiment_9,
|
|
96
|
+
exp9,
|
|
97
|
+
tokenize,
|
|
98
|
+
build_vocabulary,
|
|
99
|
+
create_cbow_dataset,
|
|
100
|
+
softmax,
|
|
101
|
+
cosine_similarity,
|
|
102
|
+
SimpleCBOW,
|
|
103
|
+
sigmoid,
|
|
104
|
+
predict_sentiment,
|
|
105
|
+
compute_gradient_x_input,
|
|
106
|
+
CODE_EXPERIMENT_9,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
from exai.lrp import (
|
|
110
|
+
experiment_10,
|
|
111
|
+
run_experiment_10,
|
|
112
|
+
exp10,
|
|
113
|
+
relu,
|
|
114
|
+
forward_propagation,
|
|
115
|
+
layerwise_relevance_propagation,
|
|
116
|
+
CODE_EXPERIMENT_10,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
from exai.index import (
|
|
120
|
+
index,
|
|
121
|
+
show_index,
|
|
122
|
+
list_all,
|
|
123
|
+
INDEX_TEXT,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
EXPERIMENTS = {
|
|
127
|
+
1: CODE_EXPERIMENT_1,
|
|
128
|
+
2: CODE_EXPERIMENT_2,
|
|
129
|
+
3: CODE_EXPERIMENT_3,
|
|
130
|
+
4: CODE_EXPERIMENT_4,
|
|
131
|
+
5: CODE_EXPERIMENT_5,
|
|
132
|
+
6: CODE_EXPERIMENT_6,
|
|
133
|
+
7: CODE_EXPERIMENT_7,
|
|
134
|
+
8: CODE_EXPERIMENT_8,
|
|
135
|
+
9: CODE_EXPERIMENT_9,
|
|
136
|
+
10: CODE_EXPERIMENT_10,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_code(experiment_number):
|
|
141
|
+
"""
|
|
142
|
+
Displays the Python code for a given experiment number (1 to 10).
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
num = int(experiment_number)
|
|
146
|
+
if num in EXPERIMENTS:
|
|
147
|
+
print(EXPERIMENTS[num])
|
|
148
|
+
else:
|
|
149
|
+
print(f"Error: Experiment {num} not found. Please choose between 1 and 10.")
|
|
150
|
+
except (ValueError, TypeError):
|
|
151
|
+
print(f"Error: Invalid experiment number '{experiment_number}'. Use an integer 1 to 10.")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
__all__ = [
|
|
155
|
+
"__version__",
|
|
156
|
+
"get_code",
|
|
157
|
+
"index",
|
|
158
|
+
"show_index",
|
|
159
|
+
"list_all",
|
|
160
|
+
"INDEX_TEXT",
|
|
161
|
+
# Exp 1
|
|
162
|
+
"experiment_1", "run_experiment_1", "exp1",
|
|
163
|
+
"compute_linear_regression_metrics", "interpret_logistic_regression",
|
|
164
|
+
# Exp 2
|
|
165
|
+
"experiment_2", "run_experiment_2", "exp2",
|
|
166
|
+
"extract_if_then_rules", "compute_permutation_importance",
|
|
167
|
+
"explain_knn_classification", "explain_knn_regression",
|
|
168
|
+
# Exp 3
|
|
169
|
+
"experiment_3", "run_experiment_3", "exp3",
|
|
170
|
+
"linear_model", "compute_single_permutation_marginal_contribution",
|
|
171
|
+
"approximate_shapley_sampling",
|
|
172
|
+
# Exp 4
|
|
173
|
+
"experiment_4", "run_experiment_4", "exp4",
|
|
174
|
+
"compute_pdp", "compute_numerical_pdp_importance", "compute_categorical_pdp_importance",
|
|
175
|
+
# Exp 5
|
|
176
|
+
"experiment_5", "run_experiment_5", "exp5",
|
|
177
|
+
"compute_ice_scratch", "ascii_ice_plot",
|
|
178
|
+
# Exp 6
|
|
179
|
+
"experiment_6", "run_experiment_6", "exp6",
|
|
180
|
+
"get_visual_intensity_bar", "compute_xrai_fixed_regions",
|
|
181
|
+
# Exp 7
|
|
182
|
+
"experiment_7", "run_experiment_7", "exp7",
|
|
183
|
+
"compute_gradcam", "print_ascii_heatmap",
|
|
184
|
+
# Exp 8
|
|
185
|
+
"experiment_8", "run_experiment_8", "exp8",
|
|
186
|
+
"compute_euclidean_distance", "exponential_kernel", "fit_lime_surrogate",
|
|
187
|
+
# Exp 9
|
|
188
|
+
"experiment_9", "run_experiment_9", "exp9",
|
|
189
|
+
"tokenize", "build_vocabulary", "create_cbow_dataset", "softmax",
|
|
190
|
+
"cosine_similarity", "SimpleCBOW", "sigmoid", "predict_sentiment",
|
|
191
|
+
"compute_gradient_x_input",
|
|
192
|
+
# Exp 10
|
|
193
|
+
"experiment_10", "run_experiment_10", "exp10",
|
|
194
|
+
"relu", "forward_propagation", "layerwise_relevance_propagation",
|
|
195
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Experiment 9: CBOW & Gradient x Input Explainability"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_9 = '"""\nCBOW Word Embeddings & Gradient × Input Attribution Module\n==========================================================\nPart A: Continuous Bag of Words (CBOW) embeddings trained from scratch.\nPart B: Gradient × Input attribution for sentiment neural networks.\n"""\n\nimport math\nimport numpy as np\nimport pandas as pd\n\n\ndef tokenize(text):\n """Simple whitespace and lowercasing unigram tokenizer."""\n return text.lower().strip().split()\n\n\ndef build_vocabulary(corpus):\n """Builds a sorted unique vocabulary and index maps."""\n words = []\n for sentence in corpus:\n words.extend(tokenize(sentence))\n vocab = sorted(list(set(words)))\n word2idx = {w: i for i, w in enumerate(vocab)}\n idx2word = {i: w for i, w in enumerate(vocab)}\n return vocab, word2idx, idx2word\n\n\ndef create_cbow_dataset(corpus, word2idx, window_size=1):\n """Creates (context, target) pairs for CBOW."""\n data = []\n for sentence in corpus:\n tokens = tokenize(sentence)\n for t in range(len(tokens)):\n context_indices = []\n for delta in range(-window_size, window_size + 1):\n if delta != 0 and 0 <= t + delta < len(tokens):\n context_indices.append(word2idx[tokens[t + delta]])\n if context_indices:\n data.append((context_indices, word2idx[tokens[t]]))\n return data\n\n\ndef softmax(x):\n """Numerically stable softmax."""\n e_x = np.exp(x - np.max(x))\n return e_x / np.sum(e_x)\n\n\ndef cosine_similarity(v1, v2):\n """Computes cosine similarity between two vectors from scratch."""\n dot = np.dot(v1, v2)\n norm1 = np.linalg.norm(v1)\n norm2 = np.linalg.norm(v2)\n if norm1 == 0 or norm2 == 0:\n return 0.0\n return dot / (norm1 * norm2)\n\n\nclass SimpleCBOW:\n """CBOW model implemented in pure NumPy without DL libraries."""\n def __init__(self, vocab_size, embedding_dim=2, learning_rate=0.05, seed=42):\n np.random.seed(seed)\n self.vocab_size = vocab_size\n self.embedding_dim = embedding_dim\n self.lr = learning_rate\n self.W_in = np.random.uniform(-0.5, 0.5, (vocab_size, embedding_dim))\n self.W_out = np.random.uniform(-0.5, 0.5, (embedding_dim, vocab_size))\n \n def train(self, cbow_data, epochs=300):\n losses = []\n for _ in range(epochs):\n total_loss = 0.0\n for context_indices, target_idx in cbow_data:\n h = np.mean(self.W_in[context_indices], axis=0)\n u = np.dot(h, self.W_out)\n y_hat = softmax(u)\n loss = -np.log(y_hat[target_idx] + 1e-12)\n total_loss += loss\n \n e = y_hat.copy()\n e[target_idx] -= 1.0\n grad_W_out = np.outer(h, e)\n grad_h = np.dot(self.W_out, e)\n \n self.W_out -= self.lr * grad_W_out\n for idx in context_indices:\n self.W_in[idx] -= (self.lr / len(context_indices)) * grad_h\n losses.append(total_loss)\n return losses\n\n\ndef sigmoid(z):\n """Sigmoid activation function."""\n return 1.0 / (1.0 + math.exp(-z))\n\n\ndef predict_sentiment(x, weights, bias=0.0):\n """Single-neuron sentiment prediction."""\n z = sum(x[i] * weights[i] for i in range(len(x))) + bias\n return z, sigmoid(z)\n\n\ndef compute_gradient_x_input(x, weights, bias=0.0):\n """Computes Gradient × Input attribution: A_i = x_i * [d(y_hat)/d(x_i)]."""\n z, y_hat = predict_sentiment(x, weights, bias)\n sigmoid_gradient = y_hat * (1.0 - y_hat)\n gradients = [sigmoid_gradient * w for w in weights]\n attributions = [x[i] * gradients[i] for i in range(len(x))]\n return z, y_hat, sigmoid_gradient, gradients, attributions\n\n\ndef run_experiment_9():\n print("=" * 80)\n print("EXPERIMENT 9: CBOW EMBEDDINGS & GRADIENT × INPUT EXPLAINABILITY")\n print("=" * 80)\n vocab = ["the", "movie", "was", "good", "bad"]\n weights = [0.1, 0.2, 0.1, 2.0, -2.0]\n x = [1, 1, 1, 1, 0]\n z, y_hat, sig_grad, grads, attrs = compute_gradient_x_input(x, weights)\n print(f"Logit: {z:.2f}, Pred: {y_hat:.4f}")\n df = pd.DataFrame({\'Token\': vocab, \'Weight\': weights, \'Attribution\': attrs})\n print(df.to_string(index=False))\n'
|
|
4
|
+
|
|
5
|
+
def experiment_9(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 9."""
|
|
7
|
+
print(CODE_EXPERIMENT_9)
|
|
8
|
+
|
|
9
|
+
def tokenize(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_9)
|
|
11
|
+
|
|
12
|
+
def build_vocabulary(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_9)
|
|
14
|
+
|
|
15
|
+
def create_cbow_dataset(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_9)
|
|
17
|
+
|
|
18
|
+
def softmax(*args, **kwargs):
|
|
19
|
+
print(CODE_EXPERIMENT_9)
|
|
20
|
+
|
|
21
|
+
def cosine_similarity(*args, **kwargs):
|
|
22
|
+
print(CODE_EXPERIMENT_9)
|
|
23
|
+
|
|
24
|
+
def SimpleCBOW(*args, **kwargs):
|
|
25
|
+
print(CODE_EXPERIMENT_9)
|
|
26
|
+
|
|
27
|
+
def sigmoid(*args, **kwargs):
|
|
28
|
+
print(CODE_EXPERIMENT_9)
|
|
29
|
+
|
|
30
|
+
def predict_sentiment(*args, **kwargs):
|
|
31
|
+
print(CODE_EXPERIMENT_9)
|
|
32
|
+
|
|
33
|
+
def compute_gradient_x_input(*args, **kwargs):
|
|
34
|
+
print(CODE_EXPERIMENT_9)
|
|
35
|
+
|
|
36
|
+
run_experiment_9 = experiment_9
|
|
37
|
+
exp9 = experiment_9
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EXAI Command-Line Interface (CLI)
|
|
3
|
+
=================================
|
|
4
|
+
Allows running any of the 10 XAI laboratory experiments directly from terminal:
|
|
5
|
+
exai list
|
|
6
|
+
exai run 1
|
|
7
|
+
exai run all
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
from exai.linear_logistic import run_experiment_1
|
|
13
|
+
from exai.tree_knn import run_experiment_2
|
|
14
|
+
from exai.shapley import run_experiment_3
|
|
15
|
+
from exai.pdp import run_experiment_4
|
|
16
|
+
from exai.ice import run_experiment_5
|
|
17
|
+
from exai.xrai import run_experiment_6
|
|
18
|
+
from exai.gradcam import run_experiment_7
|
|
19
|
+
from exai.lime import run_experiment_8
|
|
20
|
+
from exai.cbow_gradient import run_experiment_9
|
|
21
|
+
from exai.lrp import run_experiment_10
|
|
22
|
+
|
|
23
|
+
EXPERIMENTS = {
|
|
24
|
+
1: ("Linear & Logistic Regression Explainability", run_experiment_1),
|
|
25
|
+
2: ("Explainability of Decision Trees & KNN", run_experiment_2),
|
|
26
|
+
3: ("Approximate Shapley Value for Explainable AI", run_experiment_3),
|
|
27
|
+
4: ("PDP-Based Feature Importance", run_experiment_4),
|
|
28
|
+
5: ("ICE (Individual Conditional Expectation) from Scratch", run_experiment_5),
|
|
29
|
+
6: ("XRAI: Region-Based Image Attribution", run_experiment_6),
|
|
30
|
+
7: ("Grad-CAM (Gradient-Weighted Class Activation Mapping)", run_experiment_7),
|
|
31
|
+
8: ("LIME from Scratch using Weighted Least Squares", run_experiment_8),
|
|
32
|
+
9: ("CBOW Word Embeddings & Gradient × Input Explainability", run_experiment_9),
|
|
33
|
+
10: ("Layer-Wise Relevance Propagation (LRP) from Scratch", run_experiment_10),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def list_experiments():
|
|
38
|
+
print("=" * 80)
|
|
39
|
+
print("EXAI Toolkit: 10 Core Explainable AI (XAI) Laboratory Experiments")
|
|
40
|
+
print("=" * 80)
|
|
41
|
+
for num, (title, _) in EXPERIMENTS.items():
|
|
42
|
+
print(f" [{num:2d}] {title}")
|
|
43
|
+
print("=" * 80)
|
|
44
|
+
print("Run a specific experiment: exai run <1-10>")
|
|
45
|
+
print("Run all experiments : exai run all")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv=None):
|
|
49
|
+
if argv is None:
|
|
50
|
+
argv = sys.argv[1:]
|
|
51
|
+
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
prog="exai",
|
|
54
|
+
description="EXAI Toolkit: Core Explainable AI Algorithms & Experiments from Scratch"
|
|
55
|
+
)
|
|
56
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
57
|
+
|
|
58
|
+
# Subcommand: list
|
|
59
|
+
subparsers.add_parser("list", help="List all available XAI experiments")
|
|
60
|
+
|
|
61
|
+
# Subcommand: run
|
|
62
|
+
run_parser = subparsers.add_parser("run", help="Run one or all experiments")
|
|
63
|
+
run_parser.add_argument("target", help="Experiment number (1-10) or 'all'")
|
|
64
|
+
|
|
65
|
+
args = parser.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
if args.command == "list" or (args.command is None and len(argv) == 0):
|
|
68
|
+
list_experiments()
|
|
69
|
+
elif args.command == "run":
|
|
70
|
+
target = args.target.strip().lower()
|
|
71
|
+
if target == "all":
|
|
72
|
+
print("Running all 10 XAI experiments...")
|
|
73
|
+
for num in sorted(EXPERIMENTS.keys()):
|
|
74
|
+
title, fn = EXPERIMENTS[num]
|
|
75
|
+
fn()
|
|
76
|
+
print("\n")
|
|
77
|
+
else:
|
|
78
|
+
try:
|
|
79
|
+
exp_num = int(target)
|
|
80
|
+
if exp_num in EXPERIMENTS:
|
|
81
|
+
title, fn = EXPERIMENTS[exp_num]
|
|
82
|
+
fn()
|
|
83
|
+
else:
|
|
84
|
+
print(f"Error: Experiment {exp_num} not found. Choose between 1 and 10.")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
except ValueError:
|
|
87
|
+
print(f"Invalid experiment target: '{args.target}'. Use an integer 1-10 or 'all'.")
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
else:
|
|
90
|
+
parser.print_help()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
main()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Experiment 7: Grad-CAM"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_7 = '"""\nGrad-CAM: Gradient-Weighted Class Activation Mapping Module\n===========================================================\nGenerates class-discriminative localization heatmaps from convolutional feature maps.\n"""\n\nimport numpy as np\n\n\ndef compute_gradcam(feature_maps, gradients):\n """\n Computes Grad-CAM heatmap from scratch given feature maps and gradients.\n \n Parameters:\n - feature_maps: numpy array of shape (K, H, W)\n - gradients: numpy array of shape (K, H, W) representing d(y^c)/d(A)\n \n Returns:\n - alpha_weights: 1D array of shape (K,) with importance weights\n - weighted_sum: 2D array of shape (H, W) before ReLU\n - gradcam_heatmap: 2D array of shape (H, W) after ReLU\n - normalized_heatmap: 2D array of shape (H, W) scaled to [0, 1]\n """\n K, H, W = feature_maps.shape\n Z = H * W\n alpha_weights = np.sum(gradients, axis=(1, 2)) / Z\n \n weighted_sum = np.zeros((H, W), dtype=float)\n for k in range(K):\n weighted_sum += alpha_weights[k] * feature_maps[k]\n \n gradcam_heatmap = np.maximum(0.0, weighted_sum)\n max_val = np.max(gradcam_heatmap)\n min_val = np.min(gradcam_heatmap)\n if max_val > min_val:\n normalized_heatmap = (gradcam_heatmap - min_val) / (max_val - min_val)\n else:\n normalized_heatmap = np.zeros_like(gradcam_heatmap)\n \n return alpha_weights, weighted_sum, gradcam_heatmap, normalized_heatmap\n\n\ndef print_ascii_heatmap(heatmap, title="Grad-CAM Heatmap"):\n """Renders a 2D heatmap in ASCII blocks for visual inspection."""\n print(f"\\n{title}:")\n H, W = heatmap.shape\n chars = [\' \', \'░░\', \'▒▒\', \'▓▓\', \'██\']\n for r in range(H):\n row_str = ""\n for c in range(W):\n val = heatmap[r, c]\n idx = min(int(val * len(chars)), len(chars) - 1)\n row_str += chars[idx] + " "\n num_str = " ".join(f"{heatmap[r, c]:.2f}" for c in range(W))\n print(f" [{row_str.strip()}] | Values: [ {num_str} ]")\n\n\ndef run_experiment_7():\n print("=" * 80)\n print("EXPERIMENT 7: GRAD-CAM (GRADIENT-WEIGHTED CLASS ACTIVATION MAPPING)")\n print("=" * 80)\n K, H, W = 2, 4, 4\n feature_maps = np.zeros((K, H, W))\n feature_maps[0, :, :2] = 1.0 # Left object\n feature_maps[1, :, 2:] = 1.0 # Right object\n \n # Target Class 1 (Left object)\n grads_left = np.zeros((K, H, W))\n grads_left[0] = 1.0\n _, _, _, norm_hm = compute_gradcam(feature_maps, grads_left)\n print_ascii_heatmap(norm_hm, "Heatmap for Left Object Target")\n'
|
|
4
|
+
|
|
5
|
+
def experiment_7(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 7."""
|
|
7
|
+
print(CODE_EXPERIMENT_7)
|
|
8
|
+
|
|
9
|
+
def compute_gradcam(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_7)
|
|
11
|
+
|
|
12
|
+
def print_ascii_heatmap(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_7)
|
|
14
|
+
|
|
15
|
+
run_experiment_7 = experiment_7
|
|
16
|
+
exp7 = experiment_7
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Experiment 5: Individual Conditional Expectation (ICE) Plot"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_5 = '"""\nIndividual Conditional Expectation (ICE) Module\n===============================================\nImplements ICE curves and ASCII curve visualization from scratch.\n"""\n\nimport numpy as np\nimport pandas as pd\n\n\ndef compute_ice_scratch(dataset, target_feature_idx, grid_values, predict_fn):\n """\n Computes Individual Conditional Expectation (ICE) matrix and PDP average.\n Returns:\n - ice_matrix: shape (len(grid_values), len(dataset))\n - pdp_curve: shape (len(grid_values),)\n """\n n_samples = len(dataset)\n n_grid = len(grid_values)\n ice_matrix = np.zeros((n_grid, n_samples))\n \n for row_idx, val in enumerate(grid_values):\n for col_idx, sample in enumerate(dataset):\n modified_sample = list(sample)\n modified_sample[target_feature_idx] = val\n pred = predict_fn(*modified_sample)\n ice_matrix[row_idx, col_idx] = pred\n \n pdp_curve = np.mean(ice_matrix, axis=1)\n return ice_matrix, pdp_curve\n\n\ndef ascii_ice_plot(grid_values, ice_matrix, pdp_curve, feature_name="X1"):\n """Draws an ASCII representation of ICE curves and PDP average."""\n print(f"\\n--- VISUALIZATION: ICE Plot for {feature_name} ---")\n print(f"{\'Feature \' + feature_name:<10} | {\'Sample 1\':<10} | {\'Sample 2\':<10} | {\'Sample 3\':<10} | {\'PDP (Average)\':<14} | Curve Visualization")\n print("-" * 80)\n for i, g in enumerate(grid_values):\n s1 = ice_matrix[i, 0]\n s2 = ice_matrix[i, 1]\n s3 = ice_matrix[i, 2]\n pdp = pdp_curve[i]\n bar = "█" * max(1, int((pdp - 20) * 1.5))\n print(f"{g:<10.1f} | {s1:<10.1f} | {s2:<10.1f} | {s3:<10.1f} | {pdp:<14.1f} | {bar}")\n\n\ndef run_experiment_5():\n print("=" * 80)\n print("EXPERIMENT 5: IMPLEMENTING AN ICE PLOT FROM SCRATCH")\n print("=" * 80)\n dataset = [[5, 2], [6, 3], [7, 4]]\n grid = [5, 6, 7]\n def model(x1, x2): return 3.5 + 3.0 * x1 + 4.0 * x2\n ice_mat, pdp = compute_ice_scratch(dataset, 0, grid, model)\n ascii_ice_plot(grid, ice_mat, pdp)\n'
|
|
4
|
+
|
|
5
|
+
def experiment_5(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 5."""
|
|
7
|
+
print(CODE_EXPERIMENT_5)
|
|
8
|
+
|
|
9
|
+
def compute_ice_scratch(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_5)
|
|
11
|
+
|
|
12
|
+
def ascii_ice_plot(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_5)
|
|
14
|
+
|
|
15
|
+
run_experiment_5 = experiment_5
|
|
16
|
+
exp5 = experiment_5
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EXAI Toolkit: Index of Experiments
|
|
3
|
+
==================================
|
|
4
|
+
Calling index() or running this file displays the index of all 10 XAI experiments.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
INDEX_TEXT = r"""================================================================================
|
|
8
|
+
EXAI Toolkit: Index of All 10 Laboratory Experiments
|
|
9
|
+
================================================================================
|
|
10
|
+
|
|
11
|
+
[ 1] Linear & Logistic Regression Explainability
|
|
12
|
+
Topics : R², Adjusted R², t-statistic, 95% CI, Odds Ratio, Feature Ranking
|
|
13
|
+
Function : exai.exp1() or exai.get_code(1) or python experiment_1.py
|
|
14
|
+
|
|
15
|
+
[ 2] Explainability of Decision Trees & K-Nearest Neighbors (KNN)
|
|
16
|
+
Topics : Tree Rules (IF-THEN), Impurity (MDI), Permutation Importance, KNN
|
|
17
|
+
Function : exai.exp2() or exai.get_code(2) or python experiment_2.py
|
|
18
|
+
|
|
19
|
+
[ 3] Approximate Shapley Value for Explainable AI
|
|
20
|
+
Topics : Permutation Sampling from Scratch, Marginal Contributions, Efficiency
|
|
21
|
+
Function : exai.exp3() or exai.get_code(3) or python experiment_3.py
|
|
22
|
+
|
|
23
|
+
[ 4] Partial Dependence Plot (PDP) Feature Importance
|
|
24
|
+
Topics : Greenwell et al. Formulation, Sample Std Dev, Range/4 Categorical
|
|
25
|
+
Function : exai.exp4() or exai.get_code(4) or python experiment_4.py
|
|
26
|
+
|
|
27
|
+
[ 5] Individual Conditional Expectation (ICE) Plot from Scratch
|
|
28
|
+
Topics : Individual Prediction Curves, PDP Average Relationship, Slopes
|
|
29
|
+
Function : exai.exp5() or exai.get_code(5) or python experiment_5.py
|
|
30
|
+
|
|
31
|
+
[ 6] XRAI — Region-Based Image Attribution
|
|
32
|
+
Topics : Fixed Region Quadrants, Pixel Attribution Aggregation, Ranking
|
|
33
|
+
Function : exai.exp6() or exai.get_code(6) or python experiment_6.py
|
|
34
|
+
|
|
35
|
+
[ 7] Grad-CAM (Gradient-Weighted Class Activation Mapping)
|
|
36
|
+
Topics : Convolutional Feature Maps, Gradient Global Pooling, ReLU Heatmap
|
|
37
|
+
Function : exai.exp7() or exai.get_code(7) or python experiment_7.py
|
|
38
|
+
|
|
39
|
+
[ 8] LIME from Scratch using Weighted Least Squares (WLS)
|
|
40
|
+
Topics : Local Perturbations, Exponential Kernel Weights, Normal Equations
|
|
41
|
+
Function : exai.exp8() or exai.get_code(8) or python experiment_8.py
|
|
42
|
+
|
|
43
|
+
[ 9] CBOW Word Embeddings & Gradient × Input Text Attribution
|
|
44
|
+
Topics : CBOW from Scratch, Softmax, Cosine Similarity, Sigmoid Attribution
|
|
45
|
+
Function : exai.exp9() or exai.get_code(9) or python experiment_9.py
|
|
46
|
+
|
|
47
|
+
[10] Layer-Wise Relevance Propagation (LRP) from Scratch
|
|
48
|
+
Topics : Two-Layer ANN (ReLU, Sigmoid), LRP-epsilon Rule, Relevance Conservation
|
|
49
|
+
Function : exai.exp10() or exai.get_code(10) or python experiment_10.py
|
|
50
|
+
|
|
51
|
+
================================================================================
|
|
52
|
+
Quick Usage:
|
|
53
|
+
import exai
|
|
54
|
+
exai.index() # Displays this index
|
|
55
|
+
exai.get_code(8) # Displays Experiment 8 code
|
|
56
|
+
exai.exp8() # Displays Experiment 8 code
|
|
57
|
+
================================================================================
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def index(*args, **kwargs):
|
|
61
|
+
"""Displays the index of all 10 XAI experiments."""
|
|
62
|
+
print(INDEX_TEXT)
|
|
63
|
+
|
|
64
|
+
show_index = index
|
|
65
|
+
list_all = index
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
index()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Experiment 8: LIME from Scratch using Weighted Least Squares"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_8 = '"""\nLocal Interpretable Model-Agnostic Explanations (LIME) Module\n=============================================================\nImplements LIME from scratch using Weighted Least Squares (WLS) and exponential kernel.\n"""\n\nimport numpy as np\nimport pandas as pd\n\n\ndef compute_euclidean_distance(x, z):\n """Euclidean distance between original instance x and perturbation z."""\n return np.linalg.norm(np.array(x) - np.array(z))\n\n\ndef exponential_kernel(distance, sigma=1.0):\n """Exponential similarity kernel: w = exp(-d^2 / sigma^2)."""\n return np.exp(- (distance ** 2) / (sigma ** 2))\n\n\ndef fit_lime_surrogate(perturbations, predictions, original_instance, sigma=1.0, feature_names=None):\n """\n Fits a local linear surrogate model via Weighted Least Squares (WLS).\n Solves: beta = (X^T * W * X)^(-1) * X^T * W * y\n """\n N = len(perturbations)\n p = len(original_instance)\n if feature_names is None:\n feature_names = [f"Feature_{j+1}" for j in range(p)]\n \n distances = np.array([compute_euclidean_distance(original_instance, z) for z in perturbations])\n weights = np.array([exponential_kernel(d, sigma=sigma) for d in distances])\n \n X = np.hstack([np.ones((N, 1)), np.array(perturbations)])\n W = np.diag(weights)\n y = np.array(predictions).reshape(-1, 1)\n \n XTW = X.T @ W\n XTWX = XTW @ X\n XTWy = XTW @ y\n \n beta = np.linalg.solve(XTWX, XTWy).flatten()\n intercept = beta[0]\n coefficients = beta[1:]\n \n coef_df = pd.DataFrame({\n \'Feature\': feature_names,\n \'Coefficient (Local Importance)\': coefficients,\n \'Abs Value\': np.abs(coefficients),\n \'Sign\': [\'Positive (+)\' if c > 0 else \'Negative (-)\' for c in coefficients]\n })\n coef_df[\'Local Rank\'] = coef_df[\'Abs Value\'].rank(ascending=False, method=\'min\').astype(int)\n coef_df = coef_df.sort_values(by=\'Local Rank\').reset_index(drop=True)\n \n perturbations_df = pd.DataFrame(perturbations, columns=feature_names)\n perturbations_df[\'f(z) Prediction\'] = predictions\n perturbations_df[\'Distance\'] = distances\n perturbations_df[\'Similarity Weight\'] = weights\n \n return {\n \'intercept\': intercept,\n \'coefficients\': coefficients,\n \'beta\': beta,\n \'XTWX\': XTWX,\n \'XTWy\': XTWy,\n \'explanation_df\': coef_df,\n \'perturbations_df\': perturbations_df\n }\n\n\ndef run_experiment_8():\n print("=" * 80)\n print("EXPERIMENT 8: LIME FROM SCRATCH USING WEIGHTED LEAST SQUARES")\n print("=" * 80)\n x_orig = [1, 1]\n perturbations = [[1, 1], [1, 0], [0, 1], [0, 0]]\n predictions = [0.90, 0.70, 0.60, 0.20]\n names = [\'Blood Pressure\', \'Cholesterol\']\n res = fit_lime_surrogate(perturbations, predictions, x_orig, sigma=1.0, feature_names=names)\n print(res[\'explanation_df\'].to_string(index=False))\n'
|
|
4
|
+
|
|
5
|
+
def experiment_8(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 8."""
|
|
7
|
+
print(CODE_EXPERIMENT_8)
|
|
8
|
+
|
|
9
|
+
def compute_euclidean_distance(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_8)
|
|
11
|
+
|
|
12
|
+
def exponential_kernel(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_8)
|
|
14
|
+
|
|
15
|
+
def fit_lime_surrogate(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_8)
|
|
17
|
+
|
|
18
|
+
run_experiment_8 = experiment_8
|
|
19
|
+
exp8 = experiment_8
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Experiment 1: Linear and Logistic Regression Explainability"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_1 = '"""\nLinear and Logistic Regression Explainability Module\n====================================================\nImplements:\n- Linear Regression evaluation: R², Adjusted R², t-statistic, 95% Confidence Intervals\n- Logistic Regression coefficient interpretation: Odds Ratio, % change in odds, ranking\n"""\n\nimport math\nimport numpy as np\nimport pandas as pd\n\n\ndef compute_linear_regression_metrics(y_true, y_pred, coefficients, standard_errors, feature_names, intercept=0.0):\n """\n Computes Linear Regression evaluation metrics:\n - SSE, SST, R², Adjusted R²\n - t-statistic for each coefficient (t = coef / SE)\n - 95% Confidence Interval for each coefficient (coef +/- 1.96 * SE)\n - Feature importance ranking based on absolute t-statistic\n """\n y_true = np.array(y_true, dtype=float)\n y_pred = np.array(y_pred, dtype=float)\n coefficients = np.array(coefficients, dtype=float)\n standard_errors = np.array(standard_errors, dtype=float)\n \n n = len(y_true)\n p = len(coefficients)\n \n sse = np.sum((y_true - y_pred) ** 2)\n y_mean = np.mean(y_true)\n sst = np.sum((y_true - y_mean) ** 2)\n r_squared = 1.0 - (sse / sst)\n \n if n - p - 1 > 0:\n adj_r_squared = 1.0 - ((1.0 - r_squared) * (n - 1) / (n - p - 1))\n else:\n adj_r_squared = float(\'nan\')\n \n t_statistics = coefficients / standard_errors\n z_critical = 1.96\n ci_lower = coefficients - (z_critical * standard_errors)\n ci_upper = coefficients + (z_critical * standard_errors)\n \n results_df = pd.DataFrame({\n \'Feature\': feature_names,\n \'Coefficient\': coefficients,\n \'Std Error\': standard_errors,\n \'t-Statistic\': t_statistics,\n \'95% CI Lower\': ci_lower,\n \'95% CI Upper\': ci_upper,\n \'Significant (95%)\': [not (low <= 0 <= high) for low, high in zip(ci_lower, ci_upper)],\n \'Absolute t-stat\': np.abs(t_statistics)\n })\n \n results_df[\'Importance Rank\'] = results_df[\'Absolute t-stat\'].rank(ascending=False, method=\'min\').astype(int)\n results_df = results_df.sort_values(by=\'Importance Rank\').reset_index(drop=True)\n \n return {\n \'n_observations\': n,\n \'n_predictors\': p,\n \'SSE\': sse,\n \'SST\': sst,\n \'R_squared\': r_squared,\n \'Adjusted_R_squared\': adj_r_squared,\n \'coefficients_table\': results_df\n }\n\n\ndef interpret_logistic_regression(features, coefficients, intercept=0.0):\n """\n Interprets Logistic Regression coefficients:\n - Sign: positive increases odds of positive class, negative decreases odds\n - Odds Ratio: exp(beta)\n - Percentage change in odds: (OR - 1) * 100%\n - Ranking based on coefficient magnitude\n """\n rows = []\n for feat, beta in zip(features, coefficients):\n odds_ratio = math.exp(beta)\n pct_change = (odds_ratio - 1.0) * 100.0\n direction = "Positive" if beta > 0 else ("Negative" if beta < 0 else "Neutral")\n \n if beta > 0:\n interpretation = (f"Every 1-unit increase in \'{feat}\' multiplies odds by {odds_ratio:.2f} "\n f"(increases odds by {pct_change:.1f}%).")\n elif beta < 0:\n interpretation = (f"Every 1-unit increase in \'{feat}\' multiplies odds by {odds_ratio:.2f} "\n f"(decreases odds by {abs(pct_change):.1f}%).")\n else:\n interpretation = f"\'{feat}\' has no effect on the odds (OR = 1.0)."\n \n rows.append({\n \'Feature\': feat,\n \'Coefficient (Beta)\': beta,\n \'Abs(Beta)\': abs(beta),\n \'Direction\': direction,\n \'Odds Ratio\': odds_ratio,\n \'Pct Change in Odds (%)\': pct_change,\n \'Interpretation\': interpretation\n })\n \n df = pd.DataFrame(rows)\n df[\'Influence Rank\'] = df[\'Abs(Beta)\'].rank(ascending=False, method=\'min\').astype(int)\n df = df.sort_values(by=\'Influence Rank\').reset_index(drop=True)\n return df\n\n\ndef run_experiment_1():\n print("=" * 80)\n print("EXPERIMENT 1: LINEAR & LOGISTIC REGRESSION EXPLAINABILITY")\n print("=" * 80)\n feature_names = [\'Size (100 sqft)\', \'Bedrooms\', \'Age (years)\']\n coefficients = [18.5, 7.2, -1.8]\n standard_errors = [2.1, 4.0, 1.2]\n y_true = [250, 310, 190, 400, 220, 280, 350, 175, 420, 300]\n y_pred = [245, 305, 198, 390, 228, 272, 342, 180, 412, 308]\n \n lin_reg = compute_linear_regression_metrics(y_true, y_pred, coefficients, standard_errors, feature_names)\n print(f"R²: {lin_reg[\'R_squared\']:.4f}, Adj R²: {lin_reg[\'Adjusted_R_squared\']:.4f}")\n print(lin_reg[\'coefficients_table\'].to_string(index=False))\n \n print("\\nLogistic Regression Interpretation:")\n log_features = [\'Age\', \'Income\', \'Loan Amount\', \'Credit Score\']\n log_coefficients = [0.06, 0.85, -0.40, 1.25]\n log_df = interpret_logistic_regression(log_features, log_coefficients)\n print(log_df[[\'Feature\', \'Coefficient (Beta)\', \'Odds Ratio\', \'Pct Change in Odds (%)\', \'Influence Rank\']].to_string(index=False))\n'
|
|
4
|
+
|
|
5
|
+
def experiment_1(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 1."""
|
|
7
|
+
print(CODE_EXPERIMENT_1)
|
|
8
|
+
|
|
9
|
+
def compute_linear_regression_metrics(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_1)
|
|
11
|
+
|
|
12
|
+
def interpret_logistic_regression(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_1)
|
|
14
|
+
|
|
15
|
+
run_experiment_1 = experiment_1
|
|
16
|
+
exp1 = experiment_1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Experiment 10: Layer-Wise Relevance Propagation (LRP)"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_10 = '"""\nLayer-Wise Relevance Propagation (LRP) Module\n=============================================\nImplements LRP for a two-layer ANN with ReLU and sigmoid from scratch.\n"""\n\nimport math\nimport pandas as pd\n\n\ndef relu(a):\n """ReLU activation function: max(0, a)."""\n return max(0.0, float(a))\n\n\ndef sigmoid(s):\n """Sigmoid activation function: 1 / (1 + exp(-s))."""\n return 1.0 / (1.0 + math.exp(-s))\n\n\ndef forward_propagation(x1, x2, w11=1, w21=0, w12=0, w22=2, v1=1, v2=1, bias_h1=0, bias_h2=0, bias_o=0):\n """Forward Propagation through 2-layer ANN."""\n z1 = x1 * w11 + x2 * w21 + bias_h1\n h1 = relu(z1)\n \n z2 = x1 * w12 + x2 * w22 + bias_h2\n h2 = relu(z2)\n \n s = h1 * v1 + h2 * v2 + bias_o\n prob = sigmoid(s)\n \n return {\n \'x1\': x1, \'x2\': x2,\n \'z1\': z1, \'h1\': h1,\n \'z2\': z2, \'h2\': h2,\n \'s\': s, \'prob\': prob\n }\n\n\ndef layerwise_relevance_propagation(fwd, w11=1, w21=0, w12=0, w22=2, v1=1, v2=1):\n """LRP Backward propagation from output logit down to input features."""\n x1, x2 = fwd[\'x1\'], fwd[\'x2\']\n h1, h2 = fwd[\'h1\'], fwd[\'h2\']\n s = fwd[\'s\']\n R_output = s\n \n denom_output = h1 * v1 + h2 * v2\n if denom_output != 0:\n R_h1 = (h1 * v1 / denom_output) * R_output\n R_h2 = (h2 * v2 / denom_output) * R_output\n else:\n R_h1, R_h2 = 0.0, 0.0\n \n denom_h1 = x1 * w11 + x2 * w21\n if denom_h1 != 0 and R_h1 != 0:\n R_x1_from_h1 = (x1 * w11 / denom_h1) * R_h1\n R_x2_from_h1 = (x2 * w21 / denom_h1) * R_h1\n else:\n R_x1_from_h1, R_x2_from_h1 = 0.0, 0.0\n \n denom_h2 = x1 * w12 + x2 * w22\n if denom_h2 != 0 and R_h2 != 0:\n R_x1_from_h2 = (x1 * w12 / denom_h2) * R_h2\n R_x2_from_h2 = (x2 * w22 / denom_h2) * R_h2\n else:\n R_x1_from_h2, R_x2_from_h2 = 0.0, 0.0\n \n R_x1 = R_x1_from_h1 + R_x1_from_h2\n R_x2 = R_x2_from_h1 + R_x2_from_h2\n \n conserved_hidden = math.isclose(R_h1 + R_h2, R_output, abs_tol=1e-5)\n conserved_input = math.isclose(R_x1 + R_x2, R_output, abs_tol=1e-5)\n \n return {\n \'R_output\': R_output,\n \'R_h1\': R_h1,\n \'R_h2\': R_h2,\n \'R_x1\': R_x1,\n \'R_x2\': R_x2,\n \'conserved_hidden\': conserved_hidden,\n \'conserved_input\': conserved_input\n }\n\n\ndef run_experiment_10():\n print("=" * 80)\n print("EXPERIMENT 10: LAYER-WISE RELEVANCE PROPAGATION (LRP) FROM SCRATCH")\n print("=" * 80)\n fwd = forward_propagation(x1=1, x2=1)\n lrp = layerwise_relevance_propagation(fwd)\n print(f"Logit s: {fwd[\'s\']:.1f}, Prob: {fwd[\'prob\']:.4f}")\n print(f"Input Relevance: R_x1 = {lrp[\'R_x1\']:.1f}, R_x2 = {lrp[\'R_x2\']:.1f}")\n print(f"Relevance Conserved: {lrp[\'conserved_input\']}")\n'
|
|
4
|
+
|
|
5
|
+
def experiment_10(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 10."""
|
|
7
|
+
print(CODE_EXPERIMENT_10)
|
|
8
|
+
|
|
9
|
+
def relu(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_10)
|
|
11
|
+
|
|
12
|
+
def sigmoid(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_10)
|
|
14
|
+
|
|
15
|
+
def forward_propagation(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_10)
|
|
17
|
+
|
|
18
|
+
def layerwise_relevance_propagation(*args, **kwargs):
|
|
19
|
+
print(CODE_EXPERIMENT_10)
|
|
20
|
+
|
|
21
|
+
run_experiment_10 = experiment_10
|
|
22
|
+
exp10 = experiment_10
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Experiment 4: PDP-Based Feature Importance"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_4 = '"""\nPartial Dependence Plot (PDP) Feature Importance Module\n=======================================================\nImplements PDP and PDP-based importance following Greenwell, Boehmke, & McCarthy (2018).\n"""\n\nimport math\n\n\ndef compute_pdp(dataset, feature_index, grid_values, predict_fn):\n """Computes Partial Dependence Plot (PDP) values from scratch."""\n n = len(dataset)\n pdp_values = []\n \n for val in grid_values:\n total_pred = 0.0\n for sample in dataset:\n modified_sample = list(sample)\n modified_sample[feature_index] = val\n total_pred += predict_fn(modified_sample)\n pdp_values.append(total_pred / n)\n \n return pdp_values\n\n\ndef compute_numerical_pdp_importance(pdp_values):\n """Computes sample standard deviation across K PDP values."""\n K = len(pdp_values)\n if K <= 1:\n return "Numerical PDP importance cannot be calculated when there is only one unique value."\n mean_pdp = sum(pdp_values) / K\n sum_sq_diff = sum((val - mean_pdp) ** 2 for val in pdp_values)\n variance = sum_sq_diff / (K - 1)\n return math.sqrt(variance)\n\n\ndef compute_categorical_pdp_importance(pdp_values):\n """Computes categorical importance: (max(PDP) - min(PDP)) / 4."""\n if len(pdp_values) == 0:\n return 0.0\n return (max(pdp_values) - min(pdp_values)) / 4.0\n\n\ndef run_experiment_4():\n print("=" * 80)\n print("EXPERIMENT 4: PDP-BASED FEATURE IMPORTANCE")\n print("=" * 80)\n tc2 = [0.20, 0.30, 0.45, 0.60, 0.75]\n tc3 = [0.25, 0.40, 0.60, 0.70]\n print(f"Numerical Test Case: PDP = {tc2} -> Importance = {compute_numerical_pdp_importance(tc2):.4f}")\n print(f"Categorical Test Case: PDP = {tc3} -> Importance = {compute_categorical_pdp_importance(tc3):.4f}")\n'
|
|
4
|
+
|
|
5
|
+
def experiment_4(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 4."""
|
|
7
|
+
print(CODE_EXPERIMENT_4)
|
|
8
|
+
|
|
9
|
+
def compute_pdp(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_4)
|
|
11
|
+
|
|
12
|
+
def compute_numerical_pdp_importance(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_4)
|
|
14
|
+
|
|
15
|
+
def compute_categorical_pdp_importance(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_4)
|
|
17
|
+
|
|
18
|
+
run_experiment_4 = experiment_4
|
|
19
|
+
exp4 = experiment_4
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Experiment 3: Approximate Shapley Value for Explainable AI"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_3 = '"""\nApproximate Shapley Value Module\n================================\nImplements permutation-sampling approximation of the Shapley value from scratch.\n"""\n\nimport random\n\n\ndef linear_model(x):\n """Additive linear model from manual: f(x1, x2, x3) = 2*x1 + 3*x2 + 5*x3."""\n return 2 * x[0] + 3 * x[1] + 5 * x[2]\n\n\ndef compute_single_permutation_marginal_contribution(x, z, permutation, target_feature_idx, model):\n """Computes marginal contribution for a specific permutation ordering."""\n current_vector = list(z)\n before_vector = None\n after_vector = None\n \n for feat_idx in permutation:\n if feat_idx == target_feature_idx:\n before_vector = list(current_vector)\n current_vector[feat_idx] = x[feat_idx]\n after_vector = list(current_vector)\n break\n else:\n current_vector[feat_idx] = x[feat_idx]\n \n pred_before = model(before_vector)\n pred_after = model(after_vector)\n marginal_contrib = pred_after - pred_before\n \n return before_vector, after_vector, pred_before, pred_after, marginal_contrib\n\n\ndef approximate_shapley_sampling(x, background_dataset, model, M=1000, random_seed=42):\n """Approximates Shapley values for all features using permutation sampling over M iterations."""\n random.seed(random_seed)\n p = len(x)\n marginal_contributions = {i: [] for i in range(p)}\n first_10_iterations = []\n \n for m in range(M):\n z = random.choice(background_dataset)\n perm = list(range(p))\n random.shuffle(perm)\n \n iter_row = {\'iteration\': m + 1, \'z\': tuple(z), \'perm\': [f"X{k+1}" for k in perm]}\n \n for feat_idx in range(p):\n _, _, _, _, contrib = compute_single_permutation_marginal_contribution(\n x, z, perm, feat_idx, model\n )\n marginal_contributions[feat_idx].append(contrib)\n iter_row[f"contrib_X{feat_idx+1}"] = contrib\n \n if m < 10:\n first_10_iterations.append(iter_row)\n \n approx_shapley = {feat_idx: sum(vals) / len(vals) for feat_idx, vals in marginal_contributions.items()}\n return approx_shapley, first_10_iterations\n\n\ndef run_experiment_3():\n print("=" * 80)\n print("EXPERIMENT 3: APPROXIMATE SHAPLEY VALUE FOR EXPLAINABLE AI")\n print("=" * 80)\n x = [4, 2, 3]\n bg = [[5, 1, 4], [2, 5, 1], [3, 3, 2], [4, 1, 5], [1, 4, 3]]\n shap_vals, _ = approximate_shapley_sampling(x, bg, linear_model, M=1000)\n print("Estimated Shapley Values (M=1000):")\n for feat_idx, val in shap_vals.items():\n print(f" X{feat_idx+1}: {val:.4f}")\n'
|
|
4
|
+
|
|
5
|
+
def experiment_3(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 3."""
|
|
7
|
+
print(CODE_EXPERIMENT_3)
|
|
8
|
+
|
|
9
|
+
def linear_model(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_3)
|
|
11
|
+
|
|
12
|
+
def compute_single_permutation_marginal_contribution(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_3)
|
|
14
|
+
|
|
15
|
+
def approximate_shapley_sampling(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_3)
|
|
17
|
+
|
|
18
|
+
run_experiment_3 = experiment_3
|
|
19
|
+
exp3 = experiment_3
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Experiment 2: Decision Tree and KNN Explainability"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_2 = '"""\nDecision Tree and K-Nearest Neighbors Explainability Module\n===========================================================\nImplements:\n- Decision Tree rule extraction, MDI importance, permutation importance\n- KNN classification explanation (distances, neighbors, majority voting)\n- KNN regression explanation (distances, neighbors, local manual averaging)\n"""\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.datasets import load_iris\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.tree import DecisionTreeClassifier, export_text\nfrom sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor\nfrom sklearn.metrics import accuracy_score\n\n\ndef extract_if_then_rules(tree_classifier, feature_names, class_names):\n """Traverses tree structure and extracts human-readable IF-THEN rules."""\n tree = tree_classifier.tree_\n rules = []\n \n def recurse(node_id, current_rule):\n if tree.children_left[node_id] == -1 and tree.children_right[node_id] == -1:\n class_idx = np.argmax(tree.value[node_id][0])\n predicted_class = class_names[class_idx]\n samples = tree.n_node_samples[node_id]\n gini = tree.impurity[node_id]\n rule_str = " AND ".join(current_rule) if current_rule else "TRUE"\n rules.append({\n \'Rule\': f"IF {rule_str} THEN Predict: {predicted_class}",\n \'Predicted Class\': predicted_class,\n \'Samples\': samples,\n \'Gini Impurity\': round(gini, 4)\n })\n return\n \n feature_idx = tree.feature[node_id]\n threshold = tree.threshold[node_id]\n feat_name = feature_names[feature_idx]\n \n left_cond = f"{feat_name} <= {threshold:.2f}"\n recurse(tree.children_left[node_id], current_rule + [left_cond])\n right_cond = f"{feat_name} > {threshold:.2f}"\n recurse(tree.children_right[node_id], current_rule + [right_cond])\n \n recurse(0, [])\n return pd.DataFrame(rules)\n\n\ndef compute_permutation_importance(model, X_val, y_val, feature_names, n_repeats=10, random_state=42):\n """Computes Permutation Feature Importance by measuring drop in accuracy."""\n rng = np.random.RandomState(random_state)\n baseline_acc = accuracy_score(y_val, model.predict(X_val))\n \n perm_importances = []\n for col_idx, col_name in enumerate(feature_names):\n drops = []\n for _ in range(n_repeats):\n X_permuted = X_val.copy()\n shuffled_col = X_permuted[:, col_idx].copy()\n rng.shuffle(shuffled_col)\n X_permuted[:, col_idx] = shuffled_col\n \n permuted_acc = accuracy_score(y_val, model.predict(X_permuted))\n drop = baseline_acc - permuted_acc\n drops.append(drop)\n \n perm_importances.append({\n \'Feature\': col_name,\n \'Permutation Importance (Mean Drop)\': np.mean(drops),\n \'Std Dev\': np.std(drops)\n })\n \n df = pd.DataFrame(perm_importances)\n df = df.sort_values(by=\'Permutation Importance (Mean Drop)\', ascending=False).reset_index(drop=True)\n return baseline_acc, df\n\n\ndef explain_knn_classification(X_train, y_train, test_instance, class_names, k=3):\n """Step-by-step local explanation of KNN Classification."""\n distances = np.linalg.norm(X_train - test_instance, axis=1)\n nearest_indices = np.argsort(distances)[:k]\n \n neighbor_records = []\n for rank, idx in enumerate(nearest_indices, start=1):\n neighbor_records.append({\n \'Rank\': rank,\n \'Train Index\': idx,\n \'Distance\': distances[idx],\n \'Class Label\': y_train[idx],\n \'Class Name\': class_names[y_train[idx]],\n \'Features\': X_train[idx]\n })\n df_neighbors = pd.DataFrame(neighbor_records)\n votes = df_neighbors[\'Class Name\'].value_counts()\n predicted_class = votes.index[0]\n \n return {\n \'test_instance\': test_instance,\n \'k\': k,\n \'neighbors_df\': df_neighbors,\n \'vote_counts\': votes.to_dict(),\n \'predicted_class\': predicted_class\n }\n\n\ndef explain_knn_regression(X_train, y_train, test_instance, k=3):\n """Step-by-step local explanation of KNN Regression."""\n distances = np.linalg.norm(X_train - test_instance, axis=1)\n nearest_indices = np.argsort(distances)[:k]\n \n neighbor_records = []\n for rank, idx in enumerate(nearest_indices, start=1):\n neighbor_records.append({\n \'Rank\': rank,\n \'Train Index\': idx,\n \'Distance\': distances[idx],\n \'Target Value\': y_train[idx],\n \'Features\': X_train[idx]\n })\n df_neighbors = pd.DataFrame(neighbor_records)\n manual_avg = np.mean(df_neighbors[\'Target Value\'])\n \n return {\n \'test_instance\': test_instance,\n \'k\': k,\n \'neighbors_df\': df_neighbors,\n \'manual_average\': manual_avg\n }\n\n\ndef run_experiment_2():\n print("=" * 80)\n print("EXPERIMENT 2: EXPLAINABILITY OF DECISION TREES & KNN")\n print("=" * 80)\n iris = load_iris()\n X, y = iris.data, iris.target\n X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\n \n tree_clf = DecisionTreeClassifier(max_depth=3, random_state=42)\n tree_clf.fit(X_train, y_train)\n rules_df = extract_if_then_rules(tree_clf, iris.feature_names, iris.target_names)\n print("Extracted IF-THEN Rules:")\n print(rules_df[[\'Rule\', \'Samples\', \'Gini Impurity\']].to_string(index=False))\n \n knn_clf = KNeighborsClassifier(n_neighbors=3)\n knn_clf.fit(X_train, y_train)\n clf_exp = explain_knn_classification(X_train, y_train, X_test[0], iris.target_names, k=3)\n print("\\nKNN Local Explanation:")\n print(clf_exp[\'neighbors_df\'][[\'Rank\', \'Train Index\', \'Distance\', \'Class Name\']].to_string(index=False))\n'
|
|
4
|
+
|
|
5
|
+
def experiment_2(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 2."""
|
|
7
|
+
print(CODE_EXPERIMENT_2)
|
|
8
|
+
|
|
9
|
+
def extract_if_then_rules(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_2)
|
|
11
|
+
|
|
12
|
+
def compute_permutation_importance(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_2)
|
|
14
|
+
|
|
15
|
+
def explain_knn_classification(*args, **kwargs):
|
|
16
|
+
print(CODE_EXPERIMENT_2)
|
|
17
|
+
|
|
18
|
+
def explain_knn_regression(*args, **kwargs):
|
|
19
|
+
print(CODE_EXPERIMENT_2)
|
|
20
|
+
|
|
21
|
+
run_experiment_2 = experiment_2
|
|
22
|
+
exp2 = experiment_2
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Experiment 6: XRAI — Region-Based Image Attribution"""
|
|
2
|
+
|
|
3
|
+
CODE_EXPERIMENT_6 = '"""\nXRAI: Region-Based Image Attribution Module\n===========================================\nAggregates pixel-level attributions into predefined spatial regions and ranks them.\n"""\n\nimport numpy as np\nimport pandas as pd\n\n\ndef get_visual_intensity_bar(score, min_score=0.0, max_score=1.0):\n """Returns visual ASCII intensity bars matching the lab manual scale."""\n if max_score == min_score:\n norm = 0.5\n else:\n norm = (score - min_score) / (max_score - min_score)\n \n if norm >= 0.8:\n return "█████ (Very High)"\n elif norm >= 0.6:\n return "████ (High)"\n elif norm >= 0.4:\n return "███ (Medium)"\n elif norm >= 0.2:\n return "██ (Low)"\n else:\n return "█ (Very Low)"\n\n\ndef compute_xrai_fixed_regions(attribution_matrix, region_definitions):\n """\n Aggregates pixel attributions into regions and ranks them.\n Parameters:\n - attribution_matrix: 2D numpy array of pixel attributions\n - region_definitions: dict of region_name -> list of (row, col) coordinates\n """\n results = []\n for r_name, coords in region_definitions.items():\n pixels = [attribution_matrix[r, c] for r, c in coords]\n total_sum = sum(pixels)\n region_size = len(coords)\n score = total_sum / region_size\n results.append({\n \'Region\': r_name,\n \'Pixels\': [round(p, 2) for p in pixels],\n \'Pixel Sum\': total_sum,\n \'Size\': region_size,\n \'Score\': score\n })\n \n df = pd.DataFrame(results)\n df[\'Rank\'] = df[\'Score\'].rank(ascending=False, method=\'min\').astype(int)\n df = df.sort_values(by=[\'Rank\', \'Region\']).reset_index(drop=True)\n \n scores = df[\'Score\'].values\n min_s, max_s = min(scores), max(scores)\n df[\'Intensity\'] = [get_visual_intensity_bar(s, min_s, max_s) for s in scores]\n return df\n\n\ndef run_experiment_6():\n print("=" * 80)\n print("EXPERIMENT 6: XRAI — REGION-BASED IMAGE ATTRIBUTION WITHOUT LIBRARIES")\n print("=" * 80)\n attribution_matrix = np.array([\n [0.1, 0.2, 0.1, 0.0],\n [0.2, 0.3, 0.2, 0.1],\n [0.0, 0.1, 0.8, 0.7],\n [0.1, 0.2, 0.9, 0.8]\n ])\n region_defs = {\n \'R1\': [(0, 0), (0, 1), (1, 0), (1, 1)],\n \'R2\': [(0, 2), (0, 3), (1, 2), (1, 3)],\n \'R3\': [(2, 0), (2, 1), (3, 0), (3, 1)],\n \'R4\': [(2, 2), (2, 3), (3, 2), (3, 3)]\n }\n df = compute_xrai_fixed_regions(attribution_matrix, region_defs)\n print(df[[\'Rank\', \'Region\', \'Score\', \'Intensity\']].to_string(index=False))\n'
|
|
4
|
+
|
|
5
|
+
def experiment_6(*args, **kwargs):
|
|
6
|
+
"""Displays the complete Python code for Experiment 6."""
|
|
7
|
+
print(CODE_EXPERIMENT_6)
|
|
8
|
+
|
|
9
|
+
def get_visual_intensity_bar(*args, **kwargs):
|
|
10
|
+
print(CODE_EXPERIMENT_6)
|
|
11
|
+
|
|
12
|
+
def compute_xrai_fixed_regions(*args, **kwargs):
|
|
13
|
+
print(CODE_EXPERIMENT_6)
|
|
14
|
+
|
|
15
|
+
run_experiment_6 = experiment_6
|
|
16
|
+
exp6 = experiment_6
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: exai67
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight, dependency-minimal Explainable AI (XAI) algorithms and laboratory experiments implemented from scratch.
|
|
5
|
+
Author: EXAI Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/exai-toolkit/exai-toolkit
|
|
8
|
+
Project-URL: Repository, https://github.com/exai-toolkit/exai-toolkit
|
|
9
|
+
Keywords: xai,explainable-ai,interpretability,shapley,lime,gradcam,lrp,ice,pdp,xrai,machine-learning
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.20.0
|
|
27
|
+
Requires-Dist: pandas>=1.3.0
|
|
28
|
+
Requires-Dist: scikit-learn>=1.0.0
|
|
29
|
+
|
|
30
|
+
# EXAI (`exai67`)
|
|
31
|
+
|
|
32
|
+
[](https://opensource.org/licenses/MIT)
|
|
33
|
+
[](https://www.python.org/downloads/)
|
|
34
|
+
|
|
35
|
+
A lightweight, dependency-minimal Python library containing complete implementations of **10 foundational Explainable AI (XAI)** laboratory experiments from scratch. Calling any function prints the full Python code for that experiment directly to the screen.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Instant Code Display**: Calling any function or experiment displays the complete, working Python source code.
|
|
42
|
+
- **Pure / Minimal Dependencies**: Built with standard Python, `numpy`, `pandas`, and `scikit-learn`. Zero deep learning frameworks.
|
|
43
|
+
- **Exact Formulations**: Follows published literature (Greenwell et al., Ribeiro et al. LIME, Selvaraju et al. Grad-CAM, Bach et al. LRP).
|
|
44
|
+
- **Interactive CLI**: Display experiment codes directly from terminal with `exai run <1-10>` or `exai list`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Included XAI Algorithms
|
|
49
|
+
|
|
50
|
+
| Experiment | Module / Function | Description |
|
|
51
|
+
| :--- | :--- | :--- |
|
|
52
|
+
| **Index** | `exai.index()` | Index of all 10 experiments with descriptions and commands |
|
|
53
|
+
| **Exp 1** | `exai.exp1()`, `exai.compute_linear_regression_metrics()` | Linear ($R^2$, Adj $R^2$, $t$-stat, 95% CI) & Logistic Regression (Odds Ratio, % change) |
|
|
54
|
+
| **Exp 2** | `exai.exp2()`, `exai.extract_if_then_rules()` | Decision Tree IF-THEN rules, MDI vs Permutation Importance; KNN local neighbor explanations |
|
|
55
|
+
| **Exp 3** | `exai.exp3()`, `exai.approximate_shapley_sampling()` | Approximate Shapley Values via permutation sampling from scratch |
|
|
56
|
+
| **Exp 4** | `exai.exp4()`, `exai.compute_pdp()` | Partial Dependence Plot (PDP) Feature Importance (Greenwell et al., 2018) |
|
|
57
|
+
| **Exp 5** | `exai.exp5()`, `exai.compute_ice_scratch()` | Individual Conditional Expectation (ICE) curves and PDP average computation |
|
|
58
|
+
| **Exp 6** | `exai.exp6()`, `exai.compute_xrai_fixed_regions()` | XRAI Region-based image attribution and visual intensity scoring |
|
|
59
|
+
| **Exp 7** | `exai.exp7()`, `exai.compute_gradcam()` | Grad-CAM (Gradient-Weighted Class Activation Mapping) with ReLU heatmap generation |
|
|
60
|
+
| **Exp 8** | `exai.exp8()`, `exai.fit_lime_surrogate()` | LIME from scratch via Weighted Least Squares (WLS) and exponential kernels |
|
|
61
|
+
| **Exp 9** | `exai.exp9()`, `exai.compute_gradient_x_input()` | CBOW Word Embeddings from scratch & Gradient × Input sentiment attribution |
|
|
62
|
+
| **Exp 10** | `exai.exp10()`, `exai.layerwise_relevance_propagation()` | Layer-Wise Relevance Propagation (LRP) for multi-layer neural networks |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### From PyPI
|
|
69
|
+
```bash
|
|
70
|
+
pip install exai67
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Local Development Install
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Python API Usage
|
|
81
|
+
|
|
82
|
+
Calling any function displays the clean, complete, ready-to-run Python code for that experiment:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import exai
|
|
86
|
+
|
|
87
|
+
# 1. Print code by experiment number
|
|
88
|
+
exai.get_code(1) # Displays Experiment 1 code
|
|
89
|
+
exai.get_code(8) # Displays Experiment 8 code
|
|
90
|
+
|
|
91
|
+
# 2. Print code using shortcut functions
|
|
92
|
+
exai.exp1() # Linear & Logistic Regression
|
|
93
|
+
exai.exp2() # Decision Trees & KNN
|
|
94
|
+
exai.exp3() # Shapley Values
|
|
95
|
+
exai.exp4() # PDP Feature Importance
|
|
96
|
+
exai.exp5() # ICE Plot
|
|
97
|
+
exai.exp6() # XRAI Attribution
|
|
98
|
+
exai.exp7() # Grad-CAM
|
|
99
|
+
exai.exp8() # LIME
|
|
100
|
+
exai.exp9() # CBOW & Gradient x Input
|
|
101
|
+
exai.exp10() # LRP
|
|
102
|
+
|
|
103
|
+
# 3. Print code using algorithm names
|
|
104
|
+
exai.compute_gradcam()
|
|
105
|
+
exai.fit_lime_surrogate()
|
|
106
|
+
exai.layerwise_relevance_propagation()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Command-Line Interface (CLI)
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# List all 10 available experiments
|
|
115
|
+
exai list
|
|
116
|
+
|
|
117
|
+
# Display code for a specific experiment
|
|
118
|
+
exai run 8
|
|
119
|
+
|
|
120
|
+
# Display code for all experiments
|
|
121
|
+
exai run all
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Publishing to PyPI
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 1. Install build tools
|
|
130
|
+
pip install --upgrade build twine
|
|
131
|
+
|
|
132
|
+
# 2. Build the package
|
|
133
|
+
python3 setup.py sdist
|
|
134
|
+
|
|
135
|
+
# 3. Upload to PyPI
|
|
136
|
+
twine upload dist/*
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.cfg
|
|
5
|
+
setup.py
|
|
6
|
+
src/exai/__init__.py
|
|
7
|
+
src/exai/cbow_gradient.py
|
|
8
|
+
src/exai/cli.py
|
|
9
|
+
src/exai/gradcam.py
|
|
10
|
+
src/exai/ice.py
|
|
11
|
+
src/exai/index.py
|
|
12
|
+
src/exai/lime.py
|
|
13
|
+
src/exai/linear_logistic.py
|
|
14
|
+
src/exai/lrp.py
|
|
15
|
+
src/exai/pdp.py
|
|
16
|
+
src/exai/shapley.py
|
|
17
|
+
src/exai/tree_knn.py
|
|
18
|
+
src/exai/xrai.py
|
|
19
|
+
src/exai67.egg-info/PKG-INFO
|
|
20
|
+
src/exai67.egg-info/SOURCES.txt
|
|
21
|
+
src/exai67.egg-info/dependency_links.txt
|
|
22
|
+
src/exai67.egg-info/entry_points.txt
|
|
23
|
+
src/exai67.egg-info/requires.txt
|
|
24
|
+
src/exai67.egg-info/top_level.txt
|
|
25
|
+
tests/test_library.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
exai
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit Test Suite for EXAI Library
|
|
3
|
+
Verifies that calling any experiment or function outputs the code cleanly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import unittest
|
|
7
|
+
import io
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
# Ensure src is in sys.path
|
|
12
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
13
|
+
|
|
14
|
+
import exai
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestEXAILibrary(unittest.TestCase):
|
|
18
|
+
def test_version(self):
|
|
19
|
+
self.assertEqual(exai.__version__, "0.1.0")
|
|
20
|
+
|
|
21
|
+
def test_index(self):
|
|
22
|
+
captured = io.StringIO()
|
|
23
|
+
sys.stdout = captured
|
|
24
|
+
try:
|
|
25
|
+
exai.index()
|
|
26
|
+
finally:
|
|
27
|
+
sys.stdout = sys.__stdout__
|
|
28
|
+
output = captured.getvalue()
|
|
29
|
+
self.assertIn("Index of All 10 Laboratory Experiments", output)
|
|
30
|
+
self.assertIn("Linear & Logistic Regression", output)
|
|
31
|
+
self.assertIn("Layer-Wise Relevance Propagation", output)
|
|
32
|
+
|
|
33
|
+
def test_get_code_all_experiments(self):
|
|
34
|
+
for i in range(1, 11):
|
|
35
|
+
captured = io.StringIO()
|
|
36
|
+
sys.stdout = captured
|
|
37
|
+
try:
|
|
38
|
+
exai.get_code(i)
|
|
39
|
+
finally:
|
|
40
|
+
sys.stdout = sys.__stdout__
|
|
41
|
+
output = captured.getvalue()
|
|
42
|
+
self.assertTrue(len(output) > 100, f"Exp {i} output too short!")
|
|
43
|
+
self.assertIn("def ", output)
|
|
44
|
+
|
|
45
|
+
def test_individual_function_calls_print_code(self):
|
|
46
|
+
functions_to_test = [
|
|
47
|
+
exai.exp1,
|
|
48
|
+
exai.compute_linear_regression_metrics,
|
|
49
|
+
exai.extract_if_then_rules,
|
|
50
|
+
exai.approximate_shapley_sampling,
|
|
51
|
+
exai.compute_pdp,
|
|
52
|
+
exai.compute_ice_scratch,
|
|
53
|
+
exai.compute_xrai_fixed_regions,
|
|
54
|
+
exai.compute_gradcam,
|
|
55
|
+
exai.fit_lime_surrogate,
|
|
56
|
+
exai.compute_gradient_x_input,
|
|
57
|
+
exai.layerwise_relevance_propagation
|
|
58
|
+
]
|
|
59
|
+
for fn in functions_to_test:
|
|
60
|
+
captured = io.StringIO()
|
|
61
|
+
sys.stdout = captured
|
|
62
|
+
try:
|
|
63
|
+
fn()
|
|
64
|
+
finally:
|
|
65
|
+
sys.stdout = sys.__stdout__
|
|
66
|
+
output = captured.getvalue()
|
|
67
|
+
self.assertTrue(len(output) > 100, f"{fn.__name__} did not print code!")
|
|
68
|
+
self.assertIn("def ", output)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
unittest.main()
|