explainiverse 0.1.1a1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. explainiverse/__init__.py +45 -1
  2. explainiverse/adapters/__init__.py +9 -0
  3. explainiverse/adapters/base_adapter.py +25 -25
  4. explainiverse/adapters/sklearn_adapter.py +32 -32
  5. explainiverse/core/__init__.py +22 -0
  6. explainiverse/core/explainer.py +31 -31
  7. explainiverse/core/explanation.py +24 -24
  8. explainiverse/core/registry.py +545 -0
  9. explainiverse/engine/__init__.py +8 -0
  10. explainiverse/engine/suite.py +142 -142
  11. explainiverse/evaluation/__init__.py +8 -0
  12. explainiverse/evaluation/metrics.py +232 -232
  13. explainiverse/explainers/__init__.py +38 -0
  14. explainiverse/explainers/attribution/__init__.py +9 -0
  15. explainiverse/explainers/attribution/lime_wrapper.py +90 -63
  16. explainiverse/explainers/attribution/shap_wrapper.py +89 -66
  17. explainiverse/explainers/counterfactual/__init__.py +8 -0
  18. explainiverse/explainers/counterfactual/dice_wrapper.py +302 -0
  19. explainiverse/explainers/global_explainers/__init__.py +23 -0
  20. explainiverse/explainers/global_explainers/ale.py +191 -0
  21. explainiverse/explainers/global_explainers/partial_dependence.py +192 -0
  22. explainiverse/explainers/global_explainers/permutation_importance.py +123 -0
  23. explainiverse/explainers/global_explainers/sage.py +164 -0
  24. explainiverse/explainers/rule_based/__init__.py +8 -0
  25. explainiverse/explainers/rule_based/anchors_wrapper.py +350 -0
  26. explainiverse-0.2.0.dist-info/METADATA +264 -0
  27. explainiverse-0.2.0.dist-info/RECORD +29 -0
  28. explainiverse-0.1.1a1.dist-info/METADATA +0 -128
  29. explainiverse-0.1.1a1.dist-info/RECORD +0 -19
  30. {explainiverse-0.1.1a1.dist-info → explainiverse-0.2.0.dist-info}/LICENSE +0 -0
  31. {explainiverse-0.1.1a1.dist-info → explainiverse-0.2.0.dist-info}/WHEEL +0 -0
@@ -1,143 +1,143 @@
1
- # src/explainiverse/engine/suite.py
2
-
3
- from explainiverse.core.explanation import Explanation
4
- from explainiverse.explainers.attribution.lime_wrapper import LimeExplainer
5
- from explainiverse.explainers.attribution.shap_wrapper import ShapExplainer
6
- from explainiverse.evaluation.metrics import compute_roar
7
- from sklearn.metrics import accuracy_score
8
- from sklearn.linear_model import LogisticRegression
9
-
10
- class ExplanationSuite:
11
- """
12
- Runs multiple explainers on a single instance and compares their outputs.
13
- """
14
-
15
- def __init__(self, model, explainer_configs, data_meta=None):
16
- """
17
- Args:
18
- model: a model adapter (e.g., SklearnAdapter)
19
- explainer_configs: list of (name, kwargs) tuples for explainers
20
- data_meta: optional metadata about the task, scope, or preference
21
- """
22
- self.model = model
23
- self.configs = explainer_configs
24
- self.data_meta = data_meta or {}
25
- self.explanations = {}
26
-
27
- def run(self, instance):
28
- """
29
- Run all configured explainers on a single instance.
30
- """
31
- for name, params in self.configs:
32
- explainer = self._load_explainer(name, **params)
33
- explanation = explainer.explain(instance)
34
- self.explanations[name] = explanation
35
- return self.explanations
36
-
37
- def compare(self):
38
- """
39
- Print attribution scores side-by-side.
40
- """
41
- keys = set()
42
- for explanation in self.explanations.values():
43
- keys.update(explanation.explanation_data.get("feature_attributions", {}).keys())
44
-
45
- print("\nSide-by-Side Comparison:")
46
- for key in sorted(keys):
47
- row = [f"{key}"]
48
- for name in self.explanations:
49
- value = self.explanations[name].explanation_data.get("feature_attributions", {}).get(key, "—")
50
- row.append(f"{name}: {value:.4f}" if isinstance(value, float) else f"{name}: {value}")
51
- print(" | ".join(row))
52
-
53
- def suggest_best(self):
54
- """
55
- Suggest the best explainer based on model type, output structure, and task metadata.
56
- """
57
- if "task" in self.data_meta:
58
- task = self.data_meta["task"]
59
- else:
60
- task = "unknown"
61
-
62
- model = self.model.model
63
-
64
- # 1. Regression: SHAP preferred due to consistent output
65
- if task == "regression":
66
- return "shap"
67
-
68
- # 2. Model with `predict_proba` → SHAP handles probabilistic outputs well
69
- if hasattr(model, "predict_proba"):
70
- try:
71
- output = self.model.predict([[0] * model.n_features_in_])
72
- if output.shape[1] > 2:
73
- return "shap" # Multi-class, SHAP more stable
74
- else:
75
- return "lime" # Binary, both are okay
76
- except Exception:
77
- return "shap"
78
-
79
- # 3. Tree-based models → prefer SHAP (TreeSHAP if available)
80
- if "tree" in str(type(model)).lower():
81
- return "shap"
82
-
83
- # 4. Default fallback
84
- return "lime"
85
-
86
- def _load_explainer(self, name, **kwargs):
87
- if name == "lime":
88
- return LimeExplainer(model=self.model, **kwargs)
89
- elif name == "shap":
90
- return ShapExplainer(model=self.model, **kwargs)
91
- else:
92
- raise ValueError(f"Unknown explainer: {name}")
93
-
94
-
95
-
96
- def evaluate_roar(
97
- self,
98
- X_train,
99
- y_train,
100
- X_test,
101
- y_test,
102
- top_k: int = 2,
103
- model_class=None,
104
- model_kwargs: dict = None
105
- ):
106
- """
107
- Evaluate each explainer using ROAR (Remove And Retrain).
108
-
109
- Args:
110
- X_train, y_train: training data
111
- X_test, y_test: test data
112
- top_k: number of features to mask
113
- model_class: model constructor with .fit() and .predict() (default: same as current model)
114
- model_kwargs: optional keyword args for new model instance
115
-
116
- Returns:
117
- Dict of {explainer_name: accuracy drop (baseline - retrained)}
118
- """
119
- from explainiverse.evaluation.metrics import compute_roar
120
-
121
- model_kwargs = model_kwargs or {}
122
-
123
- # Default to type(self.model.model) if not provided
124
- if model_class is None:
125
- model_class = type(self.model.model)
126
-
127
- roar_scores = {}
128
-
129
- for name, explanation in self.explanations.items():
130
- print(f"[ROAR] Evaluating explainer: {name}")
131
- roar = compute_roar(
132
- model_class=model_class,
133
- X_train=X_train,
134
- y_train=y_train,
135
- X_test=X_test,
136
- y_test=y_test,
137
- explanations=[explanation], # single-instance for now
138
- top_k=top_k,
139
- model_kwargs=model_kwargs
140
- )
141
- roar_scores[name] = roar
142
-
1
+ # src/explainiverse/engine/suite.py
2
+
3
+ from explainiverse.core.explanation import Explanation
4
+ from explainiverse.explainers.attribution.lime_wrapper import LimeExplainer
5
+ from explainiverse.explainers.attribution.shap_wrapper import ShapExplainer
6
+ from explainiverse.evaluation.metrics import compute_roar
7
+ from sklearn.metrics import accuracy_score
8
+ from sklearn.linear_model import LogisticRegression
9
+
10
+ class ExplanationSuite:
11
+ """
12
+ Runs multiple explainers on a single instance and compares their outputs.
13
+ """
14
+
15
+ def __init__(self, model, explainer_configs, data_meta=None):
16
+ """
17
+ Args:
18
+ model: a model adapter (e.g., SklearnAdapter)
19
+ explainer_configs: list of (name, kwargs) tuples for explainers
20
+ data_meta: optional metadata about the task, scope, or preference
21
+ """
22
+ self.model = model
23
+ self.configs = explainer_configs
24
+ self.data_meta = data_meta or {}
25
+ self.explanations = {}
26
+
27
+ def run(self, instance):
28
+ """
29
+ Run all configured explainers on a single instance.
30
+ """
31
+ for name, params in self.configs:
32
+ explainer = self._load_explainer(name, **params)
33
+ explanation = explainer.explain(instance)
34
+ self.explanations[name] = explanation
35
+ return self.explanations
36
+
37
+ def compare(self):
38
+ """
39
+ Print attribution scores side-by-side.
40
+ """
41
+ keys = set()
42
+ for explanation in self.explanations.values():
43
+ keys.update(explanation.explanation_data.get("feature_attributions", {}).keys())
44
+
45
+ print("\nSide-by-Side Comparison:")
46
+ for key in sorted(keys):
47
+ row = [f"{key}"]
48
+ for name in self.explanations:
49
+ value = self.explanations[name].explanation_data.get("feature_attributions", {}).get(key, "—")
50
+ row.append(f"{name}: {value:.4f}" if isinstance(value, float) else f"{name}: {value}")
51
+ print(" | ".join(row))
52
+
53
+ def suggest_best(self):
54
+ """
55
+ Suggest the best explainer based on model type, output structure, and task metadata.
56
+ """
57
+ if "task" in self.data_meta:
58
+ task = self.data_meta["task"]
59
+ else:
60
+ task = "unknown"
61
+
62
+ model = self.model.model
63
+
64
+ # 1. Regression: SHAP preferred due to consistent output
65
+ if task == "regression":
66
+ return "shap"
67
+
68
+ # 2. Model with `predict_proba` → SHAP handles probabilistic outputs well
69
+ if hasattr(model, "predict_proba"):
70
+ try:
71
+ output = self.model.predict([[0] * model.n_features_in_])
72
+ if output.shape[1] > 2:
73
+ return "shap" # Multi-class, SHAP more stable
74
+ else:
75
+ return "lime" # Binary, both are okay
76
+ except Exception:
77
+ return "shap"
78
+
79
+ # 3. Tree-based models → prefer SHAP (TreeSHAP if available)
80
+ if "tree" in str(type(model)).lower():
81
+ return "shap"
82
+
83
+ # 4. Default fallback
84
+ return "lime"
85
+
86
+ def _load_explainer(self, name, **kwargs):
87
+ if name == "lime":
88
+ return LimeExplainer(model=self.model, **kwargs)
89
+ elif name == "shap":
90
+ return ShapExplainer(model=self.model, **kwargs)
91
+ else:
92
+ raise ValueError(f"Unknown explainer: {name}")
93
+
94
+
95
+
96
+ def evaluate_roar(
97
+ self,
98
+ X_train,
99
+ y_train,
100
+ X_test,
101
+ y_test,
102
+ top_k: int = 2,
103
+ model_class=None,
104
+ model_kwargs: dict = None
105
+ ):
106
+ """
107
+ Evaluate each explainer using ROAR (Remove And Retrain).
108
+
109
+ Args:
110
+ X_train, y_train: training data
111
+ X_test, y_test: test data
112
+ top_k: number of features to mask
113
+ model_class: model constructor with .fit() and .predict() (default: same as current model)
114
+ model_kwargs: optional keyword args for new model instance
115
+
116
+ Returns:
117
+ Dict of {explainer_name: accuracy drop (baseline - retrained)}
118
+ """
119
+ from explainiverse.evaluation.metrics import compute_roar
120
+
121
+ model_kwargs = model_kwargs or {}
122
+
123
+ # Default to type(self.model.model) if not provided
124
+ if model_class is None:
125
+ model_class = type(self.model.model)
126
+
127
+ roar_scores = {}
128
+
129
+ for name, explanation in self.explanations.items():
130
+ print(f"[ROAR] Evaluating explainer: {name}")
131
+ roar = compute_roar(
132
+ model_class=model_class,
133
+ X_train=X_train,
134
+ y_train=y_train,
135
+ X_test=X_test,
136
+ y_test=y_test,
137
+ explanations=[explanation], # single-instance for now
138
+ top_k=top_k,
139
+ model_kwargs=model_kwargs
140
+ )
141
+ roar_scores[name] = roar
142
+
143
143
  return roar_scores
@@ -0,0 +1,8 @@
1
+ # src/explainiverse/evaluation/__init__.py
2
+ """
3
+ Evaluation metrics for explanation quality.
4
+ """
5
+
6
+ from explainiverse.evaluation.metrics import compute_aopc, compute_roar
7
+
8
+ __all__ = ["compute_aopc", "compute_roar"]