gpe-framework 1.0.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.
- baselines/__init__.py +18 -0
- baselines/anchors_wrapper.py +257 -0
- baselines/lime_wrapper.py +266 -0
- baselines/shap_wrapper.py +261 -0
- gpe/__init__.py +144 -0
- gpe/core.py +455 -0
- gpe/explanation.py +366 -0
- gpe/metrics.py +380 -0
- gpe/novel_methods.py +866 -0
- gpe/tree_utils.py +428 -0
- gpe/variants.py +523 -0
- gpe/visualization.py +535 -0
- gpe_framework-1.0.0.dist-info/LICENSE +22 -0
- gpe_framework-1.0.0.dist-info/METADATA +259 -0
- gpe_framework-1.0.0.dist-info/RECORD +17 -0
- gpe_framework-1.0.0.dist-info/WHEEL +5 -0
- gpe_framework-1.0.0.dist-info/top_level.txt +2 -0
baselines/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Baseline Explainer Wrappers.
|
|
3
|
+
|
|
4
|
+
This module provides unified wrappers for comparing GPE with other
|
|
5
|
+
explanation methods:
|
|
6
|
+
- LIME (Local Interpretable Model-agnostic Explanations)
|
|
7
|
+
- SHAP (SHapley Additive exPlanations)
|
|
8
|
+
- Anchors
|
|
9
|
+
|
|
10
|
+
Author: Vladyslav Dehtiarov
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .lime_wrapper import LIMEWrapper
|
|
14
|
+
from .shap_wrapper import SHAPWrapper
|
|
15
|
+
from .anchors_wrapper import AnchorsWrapper
|
|
16
|
+
|
|
17
|
+
__all__ = ['LIMEWrapper', 'SHAPWrapper', 'AnchorsWrapper']
|
|
18
|
+
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Anchors Wrapper for GPE Benchmark.
|
|
3
|
+
|
|
4
|
+
Provides a unified interface for Anchors explanations that matches
|
|
5
|
+
the GPE Explanation format.
|
|
6
|
+
|
|
7
|
+
Author: Vladyslav Dehtiarov
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import time
|
|
12
|
+
from typing import List, Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
sys.path.insert(0, '..')
|
|
16
|
+
from gpe.explanation import Condition, Rule, Explanation
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AnchorsWrapper:
|
|
20
|
+
"""
|
|
21
|
+
Wrapper for Anchors explanations.
|
|
22
|
+
|
|
23
|
+
Anchors generate rule-based explanations that "anchor" the prediction,
|
|
24
|
+
providing sufficient conditions for the prediction to hold.
|
|
25
|
+
|
|
26
|
+
Parameters:
|
|
27
|
+
model: The model to explain
|
|
28
|
+
X_train: Training data
|
|
29
|
+
feature_names: Feature names
|
|
30
|
+
categorical_names: Dict mapping categorical feature indices to value names
|
|
31
|
+
threshold: Precision threshold for anchors (default: 0.95)
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> wrapper = AnchorsWrapper(model, X_train, feature_names)
|
|
35
|
+
>>> explanation = wrapper.explain(x)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
model,
|
|
41
|
+
X_train: np.ndarray,
|
|
42
|
+
feature_names: Optional[List[str]] = None,
|
|
43
|
+
categorical_names: Optional[Dict[int, List[str]]] = None,
|
|
44
|
+
threshold: float = 0.95
|
|
45
|
+
):
|
|
46
|
+
self.model = model
|
|
47
|
+
self.X_train = X_train
|
|
48
|
+
self.threshold = threshold
|
|
49
|
+
self.categorical_names = categorical_names or {}
|
|
50
|
+
|
|
51
|
+
# Set feature names
|
|
52
|
+
if feature_names is None:
|
|
53
|
+
self.feature_names = [f"feature_{i}" for i in range(X_train.shape[1])]
|
|
54
|
+
else:
|
|
55
|
+
self.feature_names = feature_names
|
|
56
|
+
|
|
57
|
+
# Initialize Anchors
|
|
58
|
+
self._init_anchors()
|
|
59
|
+
|
|
60
|
+
def _init_anchors(self):
|
|
61
|
+
"""Initialize the Anchors explainer."""
|
|
62
|
+
try:
|
|
63
|
+
from anchor import anchor_tabular
|
|
64
|
+
except ImportError:
|
|
65
|
+
raise ImportError(
|
|
66
|
+
"anchor-exp is not installed. Install with: pip install anchor-exp"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.explainer = anchor_tabular.AnchorTabularExplainer(
|
|
70
|
+
class_names=['0', '1'], # Will be updated based on model
|
|
71
|
+
feature_names=self.feature_names,
|
|
72
|
+
train_data=self.X_train,
|
|
73
|
+
categorical_names=self.categorical_names
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def explain(
|
|
77
|
+
self,
|
|
78
|
+
x: np.ndarray,
|
|
79
|
+
**kwargs
|
|
80
|
+
) -> Explanation:
|
|
81
|
+
"""
|
|
82
|
+
Generate an explanation using Anchors.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
x: Instance to explain
|
|
86
|
+
**kwargs: Additional arguments for Anchors
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Explanation object in GPE format
|
|
90
|
+
"""
|
|
91
|
+
start_time = time.time()
|
|
92
|
+
|
|
93
|
+
x = np.asarray(x).flatten()
|
|
94
|
+
|
|
95
|
+
# Get Anchors explanation
|
|
96
|
+
anchor_exp = self.explainer.explain_instance(
|
|
97
|
+
x,
|
|
98
|
+
self.model.predict,
|
|
99
|
+
threshold=self.threshold,
|
|
100
|
+
**kwargs
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
prediction = self.model.predict(x.reshape(1, -1))[0]
|
|
104
|
+
|
|
105
|
+
# Convert Anchors to GPE format
|
|
106
|
+
conditions = self._parse_anchor_rules(anchor_exp, x)
|
|
107
|
+
|
|
108
|
+
# Create rule
|
|
109
|
+
rule = Rule(
|
|
110
|
+
conditions=conditions,
|
|
111
|
+
prediction=prediction,
|
|
112
|
+
confidence=anchor_exp.precision() if hasattr(anchor_exp, 'precision') else None
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
computation_time = time.time() - start_time
|
|
116
|
+
|
|
117
|
+
# Get metrics from Anchors
|
|
118
|
+
precision = anchor_exp.precision() if hasattr(anchor_exp, 'precision') else 1.0
|
|
119
|
+
coverage = anchor_exp.coverage() if hasattr(anchor_exp, 'coverage') else 0.0
|
|
120
|
+
|
|
121
|
+
return Explanation(
|
|
122
|
+
instance=x,
|
|
123
|
+
rule=rule,
|
|
124
|
+
prediction=prediction,
|
|
125
|
+
precision=precision,
|
|
126
|
+
coverage=coverage,
|
|
127
|
+
method="Anchors",
|
|
128
|
+
computation_time=computation_time,
|
|
129
|
+
metadata={
|
|
130
|
+
'anchor_names': anchor_exp.names() if hasattr(anchor_exp, 'names') else [],
|
|
131
|
+
'threshold': self.threshold
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _parse_anchor_rules(
|
|
136
|
+
self,
|
|
137
|
+
anchor_exp,
|
|
138
|
+
x: np.ndarray
|
|
139
|
+
) -> List[Condition]:
|
|
140
|
+
"""
|
|
141
|
+
Parse Anchors explanation into Conditions.
|
|
142
|
+
|
|
143
|
+
Anchors format examples:
|
|
144
|
+
- "feature_0 > 3.50"
|
|
145
|
+
- "feature_1 <= 2.00"
|
|
146
|
+
- "3.50 < feature_0 <= 5.00"
|
|
147
|
+
"""
|
|
148
|
+
import re
|
|
149
|
+
|
|
150
|
+
conditions = []
|
|
151
|
+
|
|
152
|
+
if not hasattr(anchor_exp, 'names'):
|
|
153
|
+
return conditions
|
|
154
|
+
|
|
155
|
+
for rule_str in anchor_exp.names():
|
|
156
|
+
# Try different patterns
|
|
157
|
+
|
|
158
|
+
# Pattern: "value < feature <= value"
|
|
159
|
+
match = re.match(r"([\d.-]+)\s*<\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*<=\s*([\d.-]+)", rule_str)
|
|
160
|
+
if match:
|
|
161
|
+
low = float(match.group(1))
|
|
162
|
+
feature_name = match.group(2)
|
|
163
|
+
high = float(match.group(3))
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
idx = self.feature_names.index(feature_name)
|
|
167
|
+
except ValueError:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# Add both bounds
|
|
171
|
+
conditions.append(Condition(
|
|
172
|
+
feature_index=idx,
|
|
173
|
+
feature_name=feature_name,
|
|
174
|
+
operator='>',
|
|
175
|
+
threshold=low
|
|
176
|
+
))
|
|
177
|
+
conditions.append(Condition(
|
|
178
|
+
feature_index=idx,
|
|
179
|
+
feature_name=feature_name,
|
|
180
|
+
operator='<=',
|
|
181
|
+
threshold=high
|
|
182
|
+
))
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# Pattern: "feature <= value" or "feature > value"
|
|
186
|
+
match = re.match(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(<=|<|>=|>|=)\s*([\d.-]+)", rule_str)
|
|
187
|
+
if match:
|
|
188
|
+
feature_name = match.group(1)
|
|
189
|
+
operator = match.group(2)
|
|
190
|
+
threshold = float(match.group(3))
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
idx = self.feature_names.index(feature_name)
|
|
194
|
+
except ValueError:
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# Normalize operator
|
|
198
|
+
if operator == '=':
|
|
199
|
+
operator = '=='
|
|
200
|
+
|
|
201
|
+
conditions.append(Condition(
|
|
202
|
+
feature_index=idx,
|
|
203
|
+
feature_name=feature_name,
|
|
204
|
+
operator=operator,
|
|
205
|
+
threshold=threshold
|
|
206
|
+
))
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Categorical pattern: "feature = category_name"
|
|
210
|
+
match = re.match(r"([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*([a-zA-Z_][a-zA-Z0-9_]*)", rule_str)
|
|
211
|
+
if match:
|
|
212
|
+
feature_name = match.group(1)
|
|
213
|
+
category = match.group(2)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
idx = self.feature_names.index(feature_name)
|
|
217
|
+
except ValueError:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
conditions.append(Condition(
|
|
221
|
+
feature_index=idx,
|
|
222
|
+
feature_name=feature_name,
|
|
223
|
+
operator='==',
|
|
224
|
+
threshold=category,
|
|
225
|
+
is_categorical=True
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
return conditions
|
|
229
|
+
|
|
230
|
+
def explain_batch(
|
|
231
|
+
self,
|
|
232
|
+
X: np.ndarray,
|
|
233
|
+
verbose: bool = False
|
|
234
|
+
) -> List[Explanation]:
|
|
235
|
+
"""Generate explanations for multiple instances."""
|
|
236
|
+
explanations = []
|
|
237
|
+
|
|
238
|
+
for i, x in enumerate(X):
|
|
239
|
+
if verbose and (i + 1) % 10 == 0:
|
|
240
|
+
print(f"Anchors: Explaining instance {i + 1}/{X.shape[0]}")
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
explanation = self.explain(x)
|
|
244
|
+
explanations.append(explanation)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
# Anchors can fail for some instances
|
|
247
|
+
print(f"Warning: Anchors failed for instance {i}: {e}")
|
|
248
|
+
# Create empty explanation
|
|
249
|
+
explanations.append(Explanation(
|
|
250
|
+
instance=x,
|
|
251
|
+
rule=Rule(conditions=[], prediction=self.model.predict(x.reshape(1, -1))[0]),
|
|
252
|
+
prediction=self.model.predict(x.reshape(1, -1))[0],
|
|
253
|
+
method="Anchors-failed"
|
|
254
|
+
))
|
|
255
|
+
|
|
256
|
+
return explanations
|
|
257
|
+
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LIME Wrapper for GPE Benchmark.
|
|
3
|
+
|
|
4
|
+
Provides a unified interface for LIME explanations that matches
|
|
5
|
+
the GPE Explanation format.
|
|
6
|
+
|
|
7
|
+
Author: Vladyslav Dehtiarov
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import time
|
|
12
|
+
from typing import List, Optional, Dict, Any
|
|
13
|
+
|
|
14
|
+
# Import GPE structures
|
|
15
|
+
import sys
|
|
16
|
+
sys.path.insert(0, '..')
|
|
17
|
+
from gpe.explanation import Condition, Rule, Explanation
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LIMEWrapper:
|
|
21
|
+
"""
|
|
22
|
+
Wrapper for LIME (Local Interpretable Model-agnostic Explanations).
|
|
23
|
+
|
|
24
|
+
Provides a unified interface that converts LIME explanations to
|
|
25
|
+
the GPE Explanation format for comparison.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
model: The model to explain
|
|
29
|
+
X_train: Training data for LIME
|
|
30
|
+
feature_names: Feature names
|
|
31
|
+
mode: 'classification' or 'regression'
|
|
32
|
+
n_features: Number of features to include in explanation
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
>>> wrapper = LIMEWrapper(model, X_train, feature_names)
|
|
36
|
+
>>> explanation = wrapper.explain(x)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
model,
|
|
42
|
+
X_train: np.ndarray,
|
|
43
|
+
feature_names: Optional[List[str]] = None,
|
|
44
|
+
mode: str = 'classification',
|
|
45
|
+
n_features: int = 10,
|
|
46
|
+
kernel_width: Optional[float] = None
|
|
47
|
+
):
|
|
48
|
+
self.model = model
|
|
49
|
+
self.X_train = X_train
|
|
50
|
+
self.mode = mode
|
|
51
|
+
self.n_features = n_features
|
|
52
|
+
self.kernel_width = kernel_width
|
|
53
|
+
|
|
54
|
+
# Set feature names
|
|
55
|
+
if feature_names is None:
|
|
56
|
+
self.feature_names = [f"feature_{i}" for i in range(X_train.shape[1])]
|
|
57
|
+
else:
|
|
58
|
+
self.feature_names = feature_names
|
|
59
|
+
|
|
60
|
+
# Initialize LIME
|
|
61
|
+
self._init_lime()
|
|
62
|
+
|
|
63
|
+
def _init_lime(self):
|
|
64
|
+
"""Initialize the LIME explainer."""
|
|
65
|
+
try:
|
|
66
|
+
import lime
|
|
67
|
+
import lime.lime_tabular
|
|
68
|
+
except ImportError:
|
|
69
|
+
raise ImportError(
|
|
70
|
+
"LIME is not installed. Install with: pip install lime"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if self.mode == 'classification':
|
|
74
|
+
self.explainer = lime.lime_tabular.LimeTabularExplainer(
|
|
75
|
+
self.X_train,
|
|
76
|
+
feature_names=self.feature_names,
|
|
77
|
+
mode='classification',
|
|
78
|
+
kernel_width=self.kernel_width
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
self.explainer = lime.lime_tabular.LimeTabularExplainer(
|
|
82
|
+
self.X_train,
|
|
83
|
+
feature_names=self.feature_names,
|
|
84
|
+
mode='regression',
|
|
85
|
+
kernel_width=self.kernel_width
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def explain(
|
|
89
|
+
self,
|
|
90
|
+
x: np.ndarray,
|
|
91
|
+
**kwargs
|
|
92
|
+
) -> Explanation:
|
|
93
|
+
"""
|
|
94
|
+
Generate an explanation using LIME.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
x: Instance to explain
|
|
98
|
+
**kwargs: Additional arguments passed to LIME
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Explanation object in GPE format
|
|
102
|
+
"""
|
|
103
|
+
start_time = time.time()
|
|
104
|
+
|
|
105
|
+
x = np.asarray(x).flatten()
|
|
106
|
+
|
|
107
|
+
# Get LIME explanation
|
|
108
|
+
if self.mode == 'classification':
|
|
109
|
+
lime_exp = self.explainer.explain_instance(
|
|
110
|
+
x,
|
|
111
|
+
self.model.predict_proba,
|
|
112
|
+
num_features=self.n_features,
|
|
113
|
+
**kwargs
|
|
114
|
+
)
|
|
115
|
+
prediction = self.model.predict(x.reshape(1, -1))[0]
|
|
116
|
+
else:
|
|
117
|
+
lime_exp = self.explainer.explain_instance(
|
|
118
|
+
x,
|
|
119
|
+
self.model.predict,
|
|
120
|
+
num_features=self.n_features,
|
|
121
|
+
**kwargs
|
|
122
|
+
)
|
|
123
|
+
prediction = self.model.predict(x.reshape(1, -1))[0]
|
|
124
|
+
|
|
125
|
+
# Convert LIME explanation to GPE format
|
|
126
|
+
conditions = []
|
|
127
|
+
|
|
128
|
+
for feature_weight in lime_exp.as_list():
|
|
129
|
+
# Parse LIME feature string (e.g., "3.50 < feature_0 <= 5.00")
|
|
130
|
+
feature_str = feature_weight[0]
|
|
131
|
+
weight = feature_weight[1]
|
|
132
|
+
|
|
133
|
+
condition = self._parse_lime_condition(feature_str, x)
|
|
134
|
+
if condition is not None:
|
|
135
|
+
conditions.append(condition)
|
|
136
|
+
|
|
137
|
+
# Create rule
|
|
138
|
+
rule = Rule(
|
|
139
|
+
conditions=conditions,
|
|
140
|
+
prediction=prediction
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
computation_time = time.time() - start_time
|
|
144
|
+
|
|
145
|
+
# Calculate approximate precision/coverage
|
|
146
|
+
precision, coverage = self._estimate_metrics(rule, prediction)
|
|
147
|
+
|
|
148
|
+
return Explanation(
|
|
149
|
+
instance=x,
|
|
150
|
+
rule=rule,
|
|
151
|
+
prediction=prediction,
|
|
152
|
+
precision=precision,
|
|
153
|
+
coverage=coverage,
|
|
154
|
+
method="LIME",
|
|
155
|
+
computation_time=computation_time,
|
|
156
|
+
metadata={
|
|
157
|
+
'lime_score': lime_exp.score if hasattr(lime_exp, 'score') else None,
|
|
158
|
+
'n_features': self.n_features
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def _parse_lime_condition(
|
|
163
|
+
self,
|
|
164
|
+
feature_str: str,
|
|
165
|
+
x: np.ndarray
|
|
166
|
+
) -> Optional[Condition]:
|
|
167
|
+
"""
|
|
168
|
+
Parse a LIME feature string into a Condition.
|
|
169
|
+
|
|
170
|
+
LIME format examples:
|
|
171
|
+
- "feature_0 <= 3.50"
|
|
172
|
+
- "3.50 < feature_0 <= 5.00"
|
|
173
|
+
- "feature_1 > 2.00"
|
|
174
|
+
"""
|
|
175
|
+
import re
|
|
176
|
+
|
|
177
|
+
# Try to match patterns
|
|
178
|
+
# Pattern 1: "feature <= value" or "feature > value"
|
|
179
|
+
pattern1 = r"([a-zA-Z_][a-zA-Z0-9_]*)\s*(<=|<|>=|>)\s*([\d.-]+)"
|
|
180
|
+
# Pattern 2: "value < feature <= value"
|
|
181
|
+
pattern2 = r"([\d.-]+)\s*<\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*<=\s*([\d.-]+)"
|
|
182
|
+
|
|
183
|
+
match2 = re.match(pattern2, feature_str)
|
|
184
|
+
if match2:
|
|
185
|
+
low = float(match2.group(1))
|
|
186
|
+
feature_name = match2.group(2)
|
|
187
|
+
high = float(match2.group(3))
|
|
188
|
+
|
|
189
|
+
# Find feature index
|
|
190
|
+
try:
|
|
191
|
+
feature_idx = self.feature_names.index(feature_name)
|
|
192
|
+
except ValueError:
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
# Return the more restrictive condition based on instance value
|
|
196
|
+
value = x[feature_idx]
|
|
197
|
+
if abs(value - low) < abs(value - high):
|
|
198
|
+
return Condition(
|
|
199
|
+
feature_index=feature_idx,
|
|
200
|
+
feature_name=feature_name,
|
|
201
|
+
operator='>',
|
|
202
|
+
threshold=low
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
return Condition(
|
|
206
|
+
feature_index=feature_idx,
|
|
207
|
+
feature_name=feature_name,
|
|
208
|
+
operator='<=',
|
|
209
|
+
threshold=high
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
match1 = re.match(pattern1, feature_str)
|
|
213
|
+
if match1:
|
|
214
|
+
feature_name = match1.group(1)
|
|
215
|
+
operator = match1.group(2)
|
|
216
|
+
threshold = float(match1.group(3))
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
feature_idx = self.feature_names.index(feature_name)
|
|
220
|
+
except ValueError:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
return Condition(
|
|
224
|
+
feature_index=feature_idx,
|
|
225
|
+
feature_name=feature_name,
|
|
226
|
+
operator=operator,
|
|
227
|
+
threshold=threshold
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def _estimate_metrics(
|
|
233
|
+
self,
|
|
234
|
+
rule: Rule,
|
|
235
|
+
prediction: Any
|
|
236
|
+
) -> tuple:
|
|
237
|
+
"""Estimate precision and coverage from training data."""
|
|
238
|
+
mask = rule.evaluate_batch(self.X_train)
|
|
239
|
+
|
|
240
|
+
if mask.sum() == 0:
|
|
241
|
+
return 1.0, 0.0
|
|
242
|
+
|
|
243
|
+
coverage = mask.mean()
|
|
244
|
+
|
|
245
|
+
y_pred = self.model.predict(self.X_train[mask])
|
|
246
|
+
precision = (y_pred == prediction).mean()
|
|
247
|
+
|
|
248
|
+
return precision, coverage
|
|
249
|
+
|
|
250
|
+
def explain_batch(
|
|
251
|
+
self,
|
|
252
|
+
X: np.ndarray,
|
|
253
|
+
verbose: bool = False
|
|
254
|
+
) -> List[Explanation]:
|
|
255
|
+
"""Generate explanations for multiple instances."""
|
|
256
|
+
explanations = []
|
|
257
|
+
|
|
258
|
+
for i, x in enumerate(X):
|
|
259
|
+
if verbose and (i + 1) % 10 == 0:
|
|
260
|
+
print(f"LIME: Explaining instance {i + 1}/{X.shape[0]}")
|
|
261
|
+
|
|
262
|
+
explanation = self.explain(x)
|
|
263
|
+
explanations.append(explanation)
|
|
264
|
+
|
|
265
|
+
return explanations
|
|
266
|
+
|