-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXAI_Metrics.py
More file actions
142 lines (108 loc) · 5 KB
/
Copy pathXAI_Metrics.py
File metadata and controls
142 lines (108 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import numpy as np
import cv2
from scipy.stats import pearsonr
from sklearn.metrics import auc
from pathlib import Path
EPS = 1e-12
def normalize_to_probability_map(saliency):
"""Normalize a map so all pixel values sum to 1."""
saliency = np.maximum(saliency - saliency.min(), 0)
total = saliency.sum()
return saliency / (total + EPS)
def pearson_correlation(map1, map2):
"""Pearson correlation between two flattened maps."""
a, b = map1.flatten(), map2.flatten()
if a.std() < EPS or b.std() < EPS:
return 0.0
return float(pearsonr(a, b)[0])
def similarity_score(map1, map2):
"""SIM metric (histogram intersection between normalized maps)."""
a, b = normalize_to_probability_map(map1), normalize_to_probability_map(map2)
return float(np.sum(np.minimum(a, b)))
def resize_to_match(image, target_shape):
"""Resize a saliency map to match image resolution."""
return cv2.resize(image, (target_shape[1], target_shape[0]), interpolation=cv2.INTER_LINEAR)
def get_pixel_ranking_descending(saliency_map):
"""Return indices of pixels sorted from most to least important."""
return np.argsort(-saliency_map.flatten())
def run_deletion_test(image, predict_fn, saliency, class_index, steps=100, replacement_value=0,
image_name=None, save_debug=False, debug_root="check_deletion"):
"""
Remove most important pixels progressively and record class score.
Lower AUC means a better explanation.
"""
image = np.array(image, dtype=np.float32)
image_to_plot = image.copy()
# only for checking the masking is true - visualization
if image.max() > 1.5: # normalize if in [0,255]
image_to_plot /= 255.0
h, w = saliency.shape
sorted_pixels = get_pixel_ranking_descending(saliency)
total_pixels = h * w
scores = []
# Debug folder (if saving)
if save_debug and image_name is not None:
save_dir = Path(debug_root) / Path(image_name).stem
save_dir.mkdir(parents=True, exist_ok=True)
for step in range(steps + 1):
num_remove = int(total_pixels * step / steps)
mask = np.ones(total_pixels, bool)
mask[sorted_pixels[:num_remove]] = False
mask = mask.reshape(h, w)
modified = image.copy()
modified[~mask] = replacement_value
modified_to_plot = image_to_plot.copy()
modified_to_plot[~mask] = replacement_value
score = predict_fn(modified, class_index)
scores.append(score)
# Save every 10th step for inspection
if save_debug and (step % 10 == 0) and image_name is not None:
save_img = np.clip(modified_to_plot, 0, 1)
save_img = (save_img * 255).astype(np.uint8)
step_path = save_dir / f"step_{step:03d}.png"
cv2.imwrite(str(step_path), cv2.cvtColor(save_img, cv2.COLOR_RGB2BGR))
x_axis = np.linspace(0, 1, steps + 1)
return x_axis, np.array(scores)
def run_insertion_test(image, predict_fn, saliency, class_index, steps=100,
image_name=None, save_debug=False, debug_root="check_insertion"):
"""
Start from a blurred baseline and insert important pixels gradually.
Higher AUC means a better explanation.
"""
image = np.array(image, dtype=np.float32)
image_to_plot = image.copy()
# only for checking the masking is true
if image.max() > 1.5: # normalize if in [0,255]
image_to_plot /= 255.0
h, w = saliency.shape
sorted_pixels = get_pixel_ranking_descending(saliency)
total_pixels = h * w
blurred = cv2.GaussianBlur(image, (21, 21), 0)
blurred_to_plot = cv2.GaussianBlur(image_to_plot, (21, 21), 0)
scores = []
# Debug folder (if saving)
if save_debug and image_name is not None:
save_dir = Path(debug_root) / Path(image_name).stem
save_dir.mkdir(parents=True, exist_ok=True)
for step in range(steps + 1):
num_add = int(total_pixels * step / steps)
mask = np.zeros(total_pixels, bool)
mask[sorted_pixels[:num_add]] = True
mask = mask.reshape(h, w)
modified = blurred.copy()
modified[mask] = image[mask]
modified_to_plot = blurred_to_plot.copy()
modified_to_plot[mask] = image_to_plot[mask]
score = predict_fn(modified, class_index)
scores.append(score)
# Save every 10th step for inspection
if save_debug and (step % 10 == 0) and image_name is not None:
save_img = np.clip(modified_to_plot, 0, 1)
save_img = (save_img * 255).astype(np.uint8)
step_path = save_dir / f"step_{step:03d}.png"
cv2.imwrite(str(step_path), cv2.cvtColor(save_img, cv2.COLOR_RGB2BGR))
x_axis = np.linspace(0, 1, steps + 1)
return x_axis, np.array(scores)
def compute_auc(x_values, y_values):
"""Simple AUC helper."""
return float(auc(x_values, y_values))