interpreto 0.4.9__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.
- interpreto/__init__.py +80 -0
- interpreto/attributions/__init__.py +50 -0
- interpreto/attributions/aggregations/__init__.py +36 -0
- interpreto/attributions/aggregations/base.py +159 -0
- interpreto/attributions/aggregations/linear_regression_aggregation.py +194 -0
- interpreto/attributions/aggregations/sobol_aggregation.py +116 -0
- interpreto/attributions/base.py +775 -0
- interpreto/attributions/methods/__init__.py +47 -0
- interpreto/attributions/methods/gradient_shap.py +119 -0
- interpreto/attributions/methods/integrated_gradients.py +114 -0
- interpreto/attributions/methods/kernel_shap.py +128 -0
- interpreto/attributions/methods/lime.py +137 -0
- interpreto/attributions/methods/occlusion.py +115 -0
- interpreto/attributions/methods/saliency.py +107 -0
- interpreto/attributions/methods/smooth_grad.py +118 -0
- interpreto/attributions/methods/sobol_attribution.py +134 -0
- interpreto/attributions/methods/square_grad.py +118 -0
- interpreto/attributions/methods/var_grad.py +122 -0
- interpreto/attributions/metrics/__init__.py +25 -0
- interpreto/attributions/metrics/insertion_deletion.py +279 -0
- interpreto/attributions/perturbations/__init__.py +33 -0
- interpreto/attributions/perturbations/base.py +312 -0
- interpreto/attributions/perturbations/gaussian_noise_perturbation.py +81 -0
- interpreto/attributions/perturbations/gradient_shap_perturbation.py +81 -0
- interpreto/attributions/perturbations/insertion_deletion_perturbation.py +163 -0
- interpreto/attributions/perturbations/linear_interpolation_perturbation.py +134 -0
- interpreto/attributions/perturbations/occlusion_perturbation.py +82 -0
- interpreto/attributions/perturbations/random_perturbation.py +91 -0
- interpreto/attributions/perturbations/shap_perturbation.py +122 -0
- interpreto/attributions/perturbations/sobol_perturbation.py +137 -0
- interpreto/attributions/plots/__init__.py +0 -0
- interpreto/commons/__init__.py +28 -0
- interpreto/commons/distances.py +271 -0
- interpreto/commons/generator_tools.py +168 -0
- interpreto/commons/granularity.py +653 -0
- interpreto/concepts/__init__.py +67 -0
- interpreto/concepts/base.py +495 -0
- interpreto/concepts/interpretations/__init__.py +29 -0
- interpreto/concepts/interpretations/base.py +554 -0
- interpreto/concepts/interpretations/llm_labels.py +508 -0
- interpreto/concepts/interpretations/topk_inputs.py +335 -0
- interpreto/concepts/methods/__init__.py +71 -0
- interpreto/concepts/methods/cockatiel.py +91 -0
- interpreto/concepts/methods/neurons_as_concepts.py +152 -0
- interpreto/concepts/methods/overcomplete.py +722 -0
- interpreto/concepts/methods/sklearn_wrappers.py +420 -0
- interpreto/concepts/metrics/__init__.py +45 -0
- interpreto/concepts/metrics/consim.py +1335 -0
- interpreto/concepts/metrics/dictionary_metrics.py +198 -0
- interpreto/concepts/metrics/reconstruction_metrics.py +161 -0
- interpreto/concepts/metrics/sparsity_metrics.py +96 -0
- interpreto/concepts/plots/__init__.py +0 -0
- interpreto/model_wrapping/__init__.py +27 -0
- interpreto/model_wrapping/classification_inference_wrapper.py +227 -0
- interpreto/model_wrapping/generation_inference_wrapper.py +214 -0
- interpreto/model_wrapping/inference_wrapper.py +693 -0
- interpreto/model_wrapping/llm_interface.py +149 -0
- interpreto/model_wrapping/model_with_split_points.py +1408 -0
- interpreto/model_wrapping/splitting_utils.py +125 -0
- interpreto/model_wrapping/transformers_classes.py +88 -0
- interpreto/typing.py +98 -0
- interpreto/visualizations/__init__.py +0 -0
- interpreto/visualizations/attributions/__init__.py +5 -0
- interpreto/visualizations/attributions/attribution_highlight.py +323 -0
- interpreto/visualizations/base.py +237 -0
- interpreto/visualizations/colormap_helpers.py +59 -0
- interpreto/visualizations/concepts/__init__.py +5 -0
- interpreto/visualizations/concepts/concepts_highlight.py +176 -0
- interpreto/visualizations/visualization.css +129 -0
- interpreto/visualizations/visualization.js +606 -0
- interpreto/visualizations/visualization_attribution.js +591 -0
- interpreto-0.4.9.dist-info/METADATA +279 -0
- interpreto-0.4.9.dist-info/RECORD +76 -0
- interpreto-0.4.9.dist-info/WHEEL +5 -0
- interpreto-0.4.9.dist-info/licenses/LICENSE +23 -0
- interpreto-0.4.9.dist-info/top_level.txt +1 -0
interpreto/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
# Copyright IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
26
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
27
|
+
# CRIAQ and ANITI - https://www.deel.ai/
|
|
28
|
+
# =====================================================================================
|
|
29
|
+
|
|
30
|
+
from .attributions import (
|
|
31
|
+
GradientShap,
|
|
32
|
+
IntegratedGradients,
|
|
33
|
+
KernelShap,
|
|
34
|
+
Lime,
|
|
35
|
+
Occlusion,
|
|
36
|
+
Saliency,
|
|
37
|
+
SmoothGrad,
|
|
38
|
+
Sobol,
|
|
39
|
+
SquareGrad,
|
|
40
|
+
VarGrad,
|
|
41
|
+
)
|
|
42
|
+
from .commons import (
|
|
43
|
+
Granularity,
|
|
44
|
+
)
|
|
45
|
+
from .model_wrapping import ModelWithSplitPoints
|
|
46
|
+
from .visualizations.attributions import (
|
|
47
|
+
AttributionVisualization,
|
|
48
|
+
)
|
|
49
|
+
from .visualizations.concepts import (
|
|
50
|
+
ConceptHighlightVisualization,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_version() -> str:
|
|
55
|
+
"""Returns the current version of the Interpreto library."""
|
|
56
|
+
import pkg_resources # noqa: PLC0415 # ruff: disable=import-outside-toplevel
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
return pkg_resources.get_distribution("interpreto").version
|
|
60
|
+
except pkg_resources.DistributionNotFound:
|
|
61
|
+
return "unknown"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"AttributionVisualization",
|
|
66
|
+
"ConceptHighlightVisualization",
|
|
67
|
+
"GradientShap",
|
|
68
|
+
"Granularity",
|
|
69
|
+
"IntegratedGradients",
|
|
70
|
+
"KernelShap",
|
|
71
|
+
"Lime",
|
|
72
|
+
"ModelWithSplitPoints",
|
|
73
|
+
"Occlusion",
|
|
74
|
+
"SquareGrad",
|
|
75
|
+
"Saliency",
|
|
76
|
+
"SmoothGrad",
|
|
77
|
+
"Sobol",
|
|
78
|
+
"VarGrad",
|
|
79
|
+
"get_version",
|
|
80
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
from .base import InferenceModes
|
|
26
|
+
from .methods import (
|
|
27
|
+
GradientShap,
|
|
28
|
+
IntegratedGradients,
|
|
29
|
+
KernelShap,
|
|
30
|
+
Lime,
|
|
31
|
+
Occlusion,
|
|
32
|
+
Saliency,
|
|
33
|
+
SmoothGrad,
|
|
34
|
+
Sobol,
|
|
35
|
+
SquareGrad,
|
|
36
|
+
VarGrad,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"GradientShap",
|
|
41
|
+
"IntegratedGradients",
|
|
42
|
+
"KernelShap",
|
|
43
|
+
"Lime",
|
|
44
|
+
"Occlusion",
|
|
45
|
+
"Saliency",
|
|
46
|
+
"SmoothGrad",
|
|
47
|
+
"Sobol",
|
|
48
|
+
"SquareGrad",
|
|
49
|
+
"VarGrad",
|
|
50
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
from .base import (
|
|
26
|
+
Aggregator,
|
|
27
|
+
MaskwiseMeanAggregator,
|
|
28
|
+
MeanAggregator,
|
|
29
|
+
OcclusionAggregator,
|
|
30
|
+
SquaredMeanAggregator,
|
|
31
|
+
SumAggregator,
|
|
32
|
+
TrapezoidalMeanAggregator,
|
|
33
|
+
VarianceAggregator,
|
|
34
|
+
)
|
|
35
|
+
from .linear_regression_aggregation import LinearRegressionAggregator
|
|
36
|
+
from .sobol_aggregation import SobolAggregator
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
Aggregations used at the end of an attribution method
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
|
|
33
|
+
import torch
|
|
34
|
+
from beartype import beartype
|
|
35
|
+
from jaxtyping import Float, jaxtyped
|
|
36
|
+
from torch import Tensor
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Aggregator:
|
|
40
|
+
"""
|
|
41
|
+
Abstract class for aggregation made at the end of attribution methods
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def aggregate(self, results: torch.Tensor, mask) -> torch.Tensor:
|
|
45
|
+
"""
|
|
46
|
+
Get results from multiple "Inference wrappers", aggregate results and gives an explanation
|
|
47
|
+
"""
|
|
48
|
+
return results.squeeze(0)
|
|
49
|
+
|
|
50
|
+
def __call__(self, results: torch.Tensor, mask: torch.Tensor | None) -> torch.Tensor:
|
|
51
|
+
return self.aggregate(results, mask)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TorchAggregator(Aggregator):
|
|
55
|
+
"""
|
|
56
|
+
Basic aggregator using built-in torch methods to perform aggregation
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
_method: Callable[[torch.Tensor, int], torch.Tensor]
|
|
60
|
+
|
|
61
|
+
@jaxtyped(typechecker=beartype)
|
|
62
|
+
def aggregate(
|
|
63
|
+
self,
|
|
64
|
+
results: Float[Tensor, "p t l"],
|
|
65
|
+
mask: torch.Tensor | None = None,
|
|
66
|
+
) -> Float[Tensor, "t l"]:
|
|
67
|
+
return self._method(results, dim=0) # type: ignore
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class MeanAggregator(TorchAggregator):
|
|
71
|
+
"""
|
|
72
|
+
Mean of attributions
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
_method = torch.mean
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SquaredMeanAggregator(TorchAggregator):
|
|
79
|
+
"""
|
|
80
|
+
Mean of squares of attributions
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _method(x: torch.Tensor, dim: int = 0) -> torch.Tensor: # type: ignore
|
|
85
|
+
return torch.mean(torch.square(x), dim=dim)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SumAggregator(TorchAggregator):
|
|
89
|
+
"""
|
|
90
|
+
Sum of attributions
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
_method = torch.sum
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class VarianceAggregator(TorchAggregator):
|
|
97
|
+
"""
|
|
98
|
+
Variance of attributions
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
_method = torch.var
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MaskwiseMeanAggregator(Aggregator):
|
|
105
|
+
"""Average scores for each target weighted by their perturbation mask.
|
|
106
|
+
|
|
107
|
+
This aggregator assumes that ``results`` contains one score per
|
|
108
|
+
perturbation and target and that ``mask`` indicates which input elements were
|
|
109
|
+
perturbed. The returned tensor has one importance score per target and input
|
|
110
|
+
element.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
@jaxtyped(typechecker=beartype)
|
|
114
|
+
def aggregate(
|
|
115
|
+
self,
|
|
116
|
+
results: Float[Tensor, "p t"],
|
|
117
|
+
mask: Float[Tensor, "p l"],
|
|
118
|
+
) -> Float[Tensor, "t l"]:
|
|
119
|
+
return torch.einsum("pt,pl->tl", results, mask) / mask.sum(dim=0)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class OcclusionAggregator(Aggregator):
|
|
123
|
+
"""Aggregate occlusion scores using a reference prediction.
|
|
124
|
+
|
|
125
|
+
The first row in ``results`` must correspond to the unperturbed prediction.
|
|
126
|
+
Subsequent rows represent the prediction for each perturbation. The returned
|
|
127
|
+
tensor contains the mean difference between the reference and each
|
|
128
|
+
perturbation weighted by the corresponding mask.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
@jaxtyped(typechecker=beartype)
|
|
132
|
+
def aggregate(
|
|
133
|
+
self,
|
|
134
|
+
results: Float[Tensor, "p t"],
|
|
135
|
+
mask: Float[Tensor, "p l"],
|
|
136
|
+
) -> Float[Tensor, "t l"]:
|
|
137
|
+
# first prediction is reference, unmodified input
|
|
138
|
+
scores = results[..., 0, :] - results[..., 1:, :]
|
|
139
|
+
mask = mask[..., 1:, :]
|
|
140
|
+
return torch.einsum("pt,pl->tl", scores, mask) / mask.sum(dim=0)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class TrapezoidalMeanAggregator(Aggregator):
|
|
144
|
+
"""
|
|
145
|
+
Weighted mean using the trapezoidal rule.
|
|
146
|
+
|
|
147
|
+
This aggregator performs a weighted average across the first axis of the input tensor,
|
|
148
|
+
assigning a weight of 0.5 to the first and last elements, and 1.0 to all intermediate ones.
|
|
149
|
+
|
|
150
|
+
This reflects trapezoidal numerical integration over uniformly spaced samples.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
@jaxtyped(typechecker=beartype)
|
|
154
|
+
def aggregate(
|
|
155
|
+
self,
|
|
156
|
+
results: Float[Tensor, "p t l"],
|
|
157
|
+
mask: torch.Tensor | None = None,
|
|
158
|
+
) -> Float[Tensor, "t l"]:
|
|
159
|
+
return torch.trapezoid(results, dim=0) / (results.shape[0] - 1)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
Aggregator for LIME and KernelSHAP
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
from enum import Enum
|
|
33
|
+
|
|
34
|
+
import torch
|
|
35
|
+
from beartype import beartype
|
|
36
|
+
from jaxtyping import Float, jaxtyped
|
|
37
|
+
from torch import Tensor
|
|
38
|
+
from torch.nn import functional as F
|
|
39
|
+
|
|
40
|
+
from interpreto.attributions.aggregations.base import Aggregator
|
|
41
|
+
|
|
42
|
+
DistancesFromMaskProtocol = Callable[[Float[Tensor, "p l"]], Float[Tensor, "p"]]
|
|
43
|
+
SimilarityKernelProtocol = Callable[[Float[Tensor, "p"], float], Float[Tensor, "p"]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def hamming_distance(mask: torch.Tensor) -> torch.Tensor:
|
|
47
|
+
"""
|
|
48
|
+
Compute the Hamming distance between a mask and the original input
|
|
49
|
+
"""
|
|
50
|
+
return torch.sum(mask, dim=1).float()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def euclidean_distance(mask: torch.Tensor) -> torch.Tensor:
|
|
54
|
+
"""
|
|
55
|
+
Compute the Euclidean distance between a mask and the original input
|
|
56
|
+
"""
|
|
57
|
+
return torch.norm(mask, dim=1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cosine_distance(mask: torch.Tensor) -> torch.Tensor:
|
|
61
|
+
"""
|
|
62
|
+
Compute the cosine distance between a mask and the original input
|
|
63
|
+
"""
|
|
64
|
+
return 1 - F.cosine_similarity(torch.ones_like(mask), mask, dim=1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DistancesFromMask(Enum):
|
|
68
|
+
"""
|
|
69
|
+
Enumeration of available distance functions.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
HAMMING = staticmethod(hamming_distance)
|
|
73
|
+
EUCLIDEAN = staticmethod(euclidean_distance)
|
|
74
|
+
COSINE = staticmethod(cosine_distance)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def exponential_kernel(distances: torch.Tensor, kernel_width: float) -> torch.Tensor:
|
|
78
|
+
"""
|
|
79
|
+
Compute the exponential kernel for a given distance matrix
|
|
80
|
+
"""
|
|
81
|
+
return torch.exp(-(distances**2) / (kernel_width**2))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def ones_kernel(distances: torch.Tensor, kernel_width: float) -> torch.Tensor:
|
|
85
|
+
"""
|
|
86
|
+
Compute the ones kernel for a given distance matrix
|
|
87
|
+
"""
|
|
88
|
+
return torch.ones_like(distances)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Kernels(Enum):
|
|
92
|
+
"""
|
|
93
|
+
Enumeration of available kernels.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
EXPONENTIAL = staticmethod(exponential_kernel)
|
|
97
|
+
ONES = staticmethod(ones_kernel)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def default_kernel_width_fn(mask: torch.Tensor) -> float:
|
|
101
|
+
"""
|
|
102
|
+
Compute the default kernel width for a given mask
|
|
103
|
+
"""
|
|
104
|
+
return (mask.shape[1] ** 0.5) * 0.75 # kernel parameter inspired by LIME
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class LinearRegressionAggregator(Aggregator):
|
|
108
|
+
"""
|
|
109
|
+
Aggregator for masked perturbations using a linear model
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
distance_function: DistancesFromMaskProtocol | None = None, # noqa: UP036
|
|
115
|
+
similarity_kernel: SimilarityKernelProtocol = exponential_kernel,
|
|
116
|
+
kernel_width: float | Callable | None = None,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Initialize the aggregator.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
distance_function (DistancesFromMaskProtocol): distance function used to compute the similarity between perturbations and the original input from the mask.
|
|
123
|
+
similarity_kernel (Callable): similarity kernel used to compute the similarity between perturbations and the original input
|
|
124
|
+
kernel_width (float | Callable): kernel width used to compute the similarity between perturbations and the original input
|
|
125
|
+
"""
|
|
126
|
+
self.distance_function = distance_function
|
|
127
|
+
self.similarity_kernel = similarity_kernel
|
|
128
|
+
self.kernel_width = kernel_width if kernel_width is not None else default_kernel_width_fn
|
|
129
|
+
|
|
130
|
+
@jaxtyped(typechecker=beartype)
|
|
131
|
+
def aggregate(
|
|
132
|
+
self,
|
|
133
|
+
results: Float[Tensor, "p t"],
|
|
134
|
+
mask: Float[Tensor, "p l"],
|
|
135
|
+
) -> Float[Tensor, "t l"]:
|
|
136
|
+
"""
|
|
137
|
+
Aggregate the results of the perturbations using a linear model.
|
|
138
|
+
We use the closed form solution for the weighted least squares problem.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
results (torch.Tensor): The results of the perturbations. Shape: (p, t).
|
|
142
|
+
p the number of perturbations
|
|
143
|
+
t the number of target classes/tokens
|
|
144
|
+
mask (torch.Tensor): The mask used for the perturbations. Shape: (p, l).
|
|
145
|
+
l the length of the sequence
|
|
146
|
+
Returns:
|
|
147
|
+
torch.Tensor: The aggregated results. Shape: (t, l).
|
|
148
|
+
"""
|
|
149
|
+
# Simplify typing
|
|
150
|
+
p, l = mask.shape
|
|
151
|
+
t = results.shape[1]
|
|
152
|
+
|
|
153
|
+
if results.shape[0] != p:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
"The number of perturbations must match the number of results."
|
|
156
|
+
+ f"Got {results.shape} perturbations and {results.shape} results."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Define the weights for the linear model
|
|
160
|
+
if self.distance_function is not None: # LIME
|
|
161
|
+
# Compute distance between perturbations and original input using the mask
|
|
162
|
+
distances: Float[Tensor, p] = self.distance_function(mask)
|
|
163
|
+
|
|
164
|
+
# Compute the similarities between perturbations and original input using the distances
|
|
165
|
+
if isinstance(self.kernel_width, Callable):
|
|
166
|
+
kernel_width: float = self.kernel_width(mask)
|
|
167
|
+
else:
|
|
168
|
+
assert isinstance(self.kernel_width, float) or isinstance(self.kernel_width, int)
|
|
169
|
+
kernel_width: float = self.kernel_width
|
|
170
|
+
weights: Float[Tensor, p] = self.similarity_kernel(distances, kernel_width)
|
|
171
|
+
else: # Kernel SHAP
|
|
172
|
+
weights: Float[Tensor, p] = self.similarity_kernel(mask[:, 0], 1.0)
|
|
173
|
+
|
|
174
|
+
# We take the opposite of the mask
|
|
175
|
+
# Otherwise the linear treat the perturbation of a feature as passing from 0 to 1 for this feature
|
|
176
|
+
# We want exactly the opposite
|
|
177
|
+
mask = 1 - mask
|
|
178
|
+
|
|
179
|
+
# Compute closed form solution for the linear model
|
|
180
|
+
# Add a constant column for the bias term
|
|
181
|
+
X: Float[Tensor, p, l + 1] = torch.cat([torch.ones(results.shape[0], 1, device=mask.device), mask], dim=1)
|
|
182
|
+
|
|
183
|
+
# formula from wikipedia: https://en.wikipedia.org/wiki/Weighted_least_squares
|
|
184
|
+
# \theta =(X^T w X)^{-1} (X^T w y)
|
|
185
|
+
XT_W: Float[Tensor, l + 1, p] = X.T * weights
|
|
186
|
+
epsilon: Float[Tensor, l + 1, l + 1] = (
|
|
187
|
+
torch.eye(XT_W.shape[0], device=XT_W.device) * 1e-6
|
|
188
|
+
) # To prevent inversion errors
|
|
189
|
+
theta: Float[Tensor, l + 1, t] = torch.inverse(XT_W @ X + epsilon) @ (XT_W @ results)
|
|
190
|
+
|
|
191
|
+
# exclude the bias term
|
|
192
|
+
token_importance: Float[Tensor, t, l] = theta[1:].T
|
|
193
|
+
|
|
194
|
+
return token_importance
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2025 IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
|
|
4
|
+
# rights reserved. DEEL and FOR are research programs operated by IVADO, IRT Saint Exupéry,
|
|
5
|
+
# CRIAQ and ANITI - https://www.deel.ai/.
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
# furnished to do so, subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
# SOFTWARE.
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from enum import Enum
|
|
28
|
+
|
|
29
|
+
import torch
|
|
30
|
+
from beartype import beartype
|
|
31
|
+
from jaxtyping import Float, jaxtyped
|
|
32
|
+
|
|
33
|
+
from interpreto.attributions.aggregations.base import Aggregator
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SobolIndicesOrders(Enum):
|
|
37
|
+
"""
|
|
38
|
+
Enumeration of available Sobol indices orders.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
FIRST_ORDER = "first order"
|
|
42
|
+
TOTAL_ORDER = "total order"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SobolAggregator(Aggregator):
|
|
46
|
+
"""
|
|
47
|
+
Aggregates Sobol indices from model outputs.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
n_token_perturbations: int,
|
|
53
|
+
sobol_indices_order: SobolIndicesOrders = SobolIndicesOrders.FIRST_ORDER,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the aggregator.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
n_token_perturbations (int): Number of token perturbations.
|
|
60
|
+
sobol_indices_order (SobolIndicesOrders): Sobol indices order, either `FIRST_ORDER` or `TOTAL_ORDER`.
|
|
61
|
+
"""
|
|
62
|
+
self.n_token_perturbations = n_token_perturbations
|
|
63
|
+
self.sobol_indices_order = sobol_indices_order.value
|
|
64
|
+
|
|
65
|
+
@jaxtyped(typechecker=beartype)
|
|
66
|
+
def aggregate(self, results: Float[torch.Tensor, "p t"], mask: torch.Tensor) -> Float[torch.Tensor, "t l"]: # noqa: UP037
|
|
67
|
+
"""
|
|
68
|
+
Compute the Sobol indices from the model outputs perturbed inputs.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
results (torch.Tensor): The model outputs on perturbed inputs. Shape: (p, t) with p = (l + 2) * k
|
|
72
|
+
- l is the number of elements perturbed.
|
|
73
|
+
- k is the number of perturbations per element.
|
|
74
|
+
- t is the targets dimensions, i.e. the number of classes or number of generated tokens.
|
|
75
|
+
mask (torch.Tensor): Ignored.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
token_importance (torch.Tensor): The Sobol attribution indices for each token. Shape: (l,)
|
|
79
|
+
"""
|
|
80
|
+
# simplify typing
|
|
81
|
+
k = self.n_token_perturbations
|
|
82
|
+
l = (results.shape[0] // k) - 2
|
|
83
|
+
t = results.shape[1]
|
|
84
|
+
|
|
85
|
+
# Extract initial matrices
|
|
86
|
+
fA: Float[torch.Tensor, k, t] = results[:k]
|
|
87
|
+
fB: Float[torch.Tensor, k, t] = results[k : 2 * k]
|
|
88
|
+
fC: Float[torch.Tensor, l, k, t] = results[2 * k :].view(l, k, t)
|
|
89
|
+
|
|
90
|
+
var_fA: Float[torch.Tensor, 1, t] = torch.var(fA, dim=0, keepdim=True)
|
|
91
|
+
|
|
92
|
+
token_importance: Float[torch.Tensor, l, t]
|
|
93
|
+
if self.sobol_indices_order == SobolIndicesOrders.FIRST_ORDER.value:
|
|
94
|
+
# (var(f(A)) - 0.5*mean(f(B) - f(C))^2) / var(f(A))
|
|
95
|
+
fBfC_diff: Float[torch.Tensor, l, k, t] = fB.unsqueeze(0) - fC
|
|
96
|
+
fBfC_mean_square_diff: Float[torch.Tensor, l, t] = torch.mean(fBfC_diff**2, dim=1) / 2
|
|
97
|
+
token_importance: Float[torch.Tensor, l, t] = (var_fA - fBfC_mean_square_diff) / (var_fA + 1e-6)
|
|
98
|
+
elif self.sobol_indices_order == SobolIndicesOrders.TOTAL_ORDER.value:
|
|
99
|
+
# mean(f(A) - f(C))^2) / 2var(f(A))
|
|
100
|
+
fAfC_diff: Float[torch.Tensor, l, k, t] = fA.unsqueeze(0) - fC
|
|
101
|
+
fAfC_mean_square_diff: Float[torch.Tensor, l, t] = torch.mean(fAfC_diff**2, dim=1)
|
|
102
|
+
token_importance: Float[torch.Tensor, l, t] = fAfC_mean_square_diff / (2 * var_fA + 1e-6)
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(f"Unknown Sobol indices order: {self.sobol_indices_order}")
|
|
105
|
+
|
|
106
|
+
return token_importance.T
|
|
107
|
+
|
|
108
|
+
# # Compute token-wise variance on the initial mask
|
|
109
|
+
# initial_results: Float[torch.Tensor, t, k] = results[:k].T
|
|
110
|
+
# initial_var: Float[torch.Tensor, t, 1] = torch.var(initial_results, dim=1, keepdim=True)
|
|
111
|
+
|
|
112
|
+
# # Compute token-wise sobol attribution indices
|
|
113
|
+
# token_results: Float[torch.Tensor, t, l, k] = results[k:].view(t, l, k)
|
|
114
|
+
# difference: Float[torch.Tensor, t, l, k] = token_results - initial_results.unsqueeze(1)
|
|
115
|
+
# token_importance: Float[torch.Tensor, t, l] = torch.mean(difference**2, dim=-1) / (initial_var + 1e-6)
|
|
116
|
+
# return token_importance
|