oodeel 0.2.0__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of oodeel might be problematic. Click here for more details.

Files changed (42) hide show
  1. oodeel/__init__.py +1 -1
  2. oodeel/datasets/__init__.py +2 -1
  3. oodeel/datasets/data_handler.py +162 -94
  4. oodeel/datasets/deprecated/DEPRECATED_data_handler.py +236 -0
  5. oodeel/datasets/{ooddataset.py → deprecated/DEPRECATED_ooddataset.py} +14 -13
  6. oodeel/datasets/deprecated/DEPRECATED_tf_data_handler.py +671 -0
  7. oodeel/datasets/deprecated/DEPRECATED_torch_data_handler.py +769 -0
  8. oodeel/datasets/deprecated/__init__.py +31 -0
  9. oodeel/datasets/tf_data_handler.py +105 -167
  10. oodeel/datasets/torch_data_handler.py +109 -181
  11. oodeel/eval/metrics.py +7 -2
  12. oodeel/extractor/feature_extractor.py +11 -0
  13. oodeel/extractor/keras_feature_extractor.py +51 -1
  14. oodeel/extractor/torch_feature_extractor.py +103 -21
  15. oodeel/methods/__init__.py +16 -1
  16. oodeel/methods/base.py +72 -15
  17. oodeel/methods/dknn.py +20 -7
  18. oodeel/methods/energy.py +8 -0
  19. oodeel/methods/entropy.py +8 -0
  20. oodeel/methods/gen.py +118 -0
  21. oodeel/methods/gram.py +15 -4
  22. oodeel/methods/mahalanobis.py +9 -7
  23. oodeel/methods/mls.py +8 -0
  24. oodeel/methods/odin.py +8 -0
  25. oodeel/methods/rmds.py +122 -0
  26. oodeel/methods/she.py +197 -0
  27. oodeel/methods/vim.py +1 -1
  28. oodeel/preprocess/__init__.py +31 -0
  29. oodeel/preprocess/tf_preprocess.py +95 -0
  30. oodeel/preprocess/torch_preprocess.py +97 -0
  31. oodeel/utils/operator.py +17 -0
  32. oodeel/utils/tf_operator.py +15 -0
  33. oodeel/utils/tf_training_tools.py +2 -2
  34. oodeel/utils/torch_operator.py +19 -0
  35. {oodeel-0.2.0.dist-info → oodeel-0.3.0.dist-info}/METADATA +139 -105
  36. oodeel-0.3.0.dist-info/RECORD +57 -0
  37. {oodeel-0.2.0.dist-info → oodeel-0.3.0.dist-info}/WHEEL +1 -1
  38. tests/tests_tensorflow/tf_methods_utils.py +2 -1
  39. tests/tests_torch/torch_methods_utils.py +34 -27
  40. oodeel-0.2.0.dist-info/RECORD +0 -47
  41. {oodeel-0.2.0.dist-info → oodeel-0.3.0.dist-info/licenses}/LICENSE +0 -0
  42. {oodeel-0.2.0.dist-info → oodeel-0.3.0.dist-info}/top_level.txt +0 -0
oodeel/methods/gen.py ADDED
@@ -0,0 +1,118 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
3
+ # rights reserved. DEEL is a research program operated by IVADO, IRT Saint Exupéry,
4
+ # CRIAQ and ANITI - https://www.deel.ai/
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ import numpy as np
24
+
25
+ from ..types import DatasetType
26
+ from ..types import TensorType
27
+ from ..types import Tuple
28
+ from .base import OODBaseDetector
29
+
30
+
31
+ class GEN(OODBaseDetector):
32
+ """
33
+ Generalized Entropy method for OOD detection.
34
+ "GEN: Pushing the Limits of Softmax-Based Out-of-Distribution Detection"
35
+ https://openaccess.thecvf.com/content/CVPR2023/html/Liu_GEN_Pushing_the_Limits_of_Softmax-Based_Out-of-Distribution_Detection_CVPR_2023_paper.html,
36
+
37
+ Args:
38
+ gamma (float): parameter for the generalized entropy. Must be between 0 and 1.
39
+ Defaults to 0.1.
40
+ k (int): number of softmax values to keep for the entropy computation. Only the
41
+ top-k softmax probabilities will be used. Defaults to 100.
42
+ use_react (bool): if true, apply ReAct method by clipping penultimate
43
+ activations under a threshold value.
44
+ react_quantile (Optional[float]): q value in the range [0, 1] used to compute
45
+ the react clipping threshold defined as the q-th quantile penultimate layer
46
+ activations. Defaults to 0.8.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ gamma: float = 0.1,
52
+ k: int = 100,
53
+ use_react: bool = False,
54
+ use_scale: bool = False,
55
+ use_ash: bool = False,
56
+ react_quantile: float = 0.8,
57
+ scale_percentile: float = 0.85,
58
+ ash_percentile: float = 0.90,
59
+ ):
60
+ super().__init__(
61
+ use_react=use_react,
62
+ use_scale=use_scale,
63
+ use_ash=use_ash,
64
+ react_quantile=react_quantile,
65
+ scale_percentile=scale_percentile,
66
+ ash_percentile=ash_percentile,
67
+ )
68
+ self.gamma = gamma
69
+ self.k = k
70
+
71
+ def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
72
+ """
73
+ Computes an OOD score for input samples "inputs" based on
74
+ the distance to nearest neighbors in the feature space of self.model
75
+
76
+ Args:
77
+ inputs: input samples to score
78
+
79
+ Returns:
80
+ Tuple[np.ndarray]: scores, logits
81
+ """
82
+
83
+ _, logits = self.feature_extractor.predict_tensor(inputs)
84
+ probs = self.op.softmax(logits)
85
+ probs = self.op.convert_to_numpy(probs)
86
+ probs = np.sort(probs)[:, -self.k :] # Keep the k largest probabilities
87
+ scores = np.sum(probs**self.gamma * (1 - probs) ** (self.gamma), axis=-1)
88
+ return scores
89
+
90
+ def _fit_to_dataset(self, fit_dataset: DatasetType) -> None:
91
+ """
92
+ Fits the OOD detector to fit_dataset.
93
+
94
+ Args:
95
+ fit_dataset: dataset to fit the OOD detector on
96
+ """
97
+ pass
98
+
99
+ @property
100
+ def requires_to_fit_dataset(self) -> bool:
101
+ """
102
+ Whether an OOD detector needs a `fit_dataset` argument in the fit function.
103
+
104
+ Returns:
105
+ bool: True if `fit_dataset` is required else False.
106
+ """
107
+ return False
108
+
109
+ @property
110
+ def requires_internal_features(self) -> bool:
111
+ """
112
+ Whether an OOD detector acts on internal model features.
113
+
114
+ Returns:
115
+ bool: True if the detector perform computations on an intermediate layer
116
+ else False.
117
+ """
118
+ return False
oodeel/methods/gram.py CHANGED
@@ -76,7 +76,7 @@ class Gram(OODBaseDetector):
76
76
 
77
77
  def __init__(
78
78
  self,
79
- orders: List[int] = [i for i in range(1, 11)],
79
+ orders: List[int] = [i for i in range(1, 6)],
80
80
  quantile: float = 0.01,
81
81
  ):
82
82
  super().__init__()
@@ -90,6 +90,7 @@ class Gram(OODBaseDetector):
90
90
  self,
91
91
  fit_dataset: Union[TensorType, DatasetType],
92
92
  val_split: float = 0.2,
93
+ verbose: bool = False,
93
94
  ) -> None:
94
95
  """
95
96
  Compute the quantiles of channelwise correlations for each layer, power of
@@ -102,13 +103,19 @@ class Gram(OODBaseDetector):
102
103
  construct the index with.
103
104
  val_split (float): The percentage of fit data to use as validation data for
104
105
  normalization. Default to 0.2.
106
+ verbose (bool): Whether to print information during the fitting process.
107
+ Default to False.
105
108
  """
106
109
  self.postproc_fns = [
107
110
  self._stat for i in range(len(self.feature_extractor.feature_layers_id))
108
111
  ]
109
112
 
113
+ # fit_stats shape: [n_features, n_samples, n_orders, n_channels]
110
114
  fit_stats, info = self.feature_extractor.predict(
111
- fit_dataset, postproc_fns=self.postproc_fns, return_labels=True
115
+ fit_dataset,
116
+ postproc_fns=self.postproc_fns,
117
+ return_labels=True,
118
+ verbose=verbose,
112
119
  )
113
120
  labels = info["labels"]
114
121
  self._classes = np.sort(np.unique(self.op.convert_to_numpy(labels)))
@@ -256,21 +263,25 @@ class Gram(OODBaseDetector):
256
263
  (fm_s[0], fm_s[-1], -1),
257
264
  )
258
265
  else:
266
+ # batch, channel, spatial
259
267
  feature_map_p = self.op.reshape(
260
268
  feature_map_p, (fm_s[0], fm_s[1], -1)
261
269
  )
270
+ # batch, channel, channel
262
271
  feature_map_p = self.op.matmul(
263
272
  feature_map_p, self.op.permute(feature_map_p, (0, 2, 1))
264
273
  )
274
+ # normalize the Gram matrix
265
275
  feature_map_p = self.op.sign(feature_map_p) * (
266
276
  self.op.abs(feature_map_p) ** (1 / p)
267
277
  )
268
278
  # get the lower triangular part of the matrix
269
279
  feature_map_p = self.op.tril(feature_map_p)
270
- # directly sum row-wise (to limit computational burden)
280
+ # directly sum row-wise (to limit computational burden) -> batch, channel
271
281
  feature_map_p = self.op.sum(feature_map_p, dim=2)
272
282
  # stat.append(self.op.t(feature_map_p))
273
283
  stat.append(feature_map_p)
284
+ # batch, n_orders, channel
274
285
  stat = self.op.stack(stat, 1)
275
286
  return stat
276
287
 
@@ -293,4 +304,4 @@ class Gram(OODBaseDetector):
293
304
  bool: True if the detector perform computations on an intermediate layer
294
305
  else False.
295
306
  """
296
- return False
307
+ return True
@@ -55,7 +55,7 @@ class Mahalanobis(OODBaseDetector):
55
55
  fit_dataset (Union[TensorType, DatasetType]): input dataset (ID)
56
56
  """
57
57
  # extract features and labels
58
- features, infos = self.feature_extractor.predict(fit_dataset)
58
+ features, infos = self.feature_extractor.predict(fit_dataset, detach=True)
59
59
  labels = infos["labels"]
60
60
 
61
61
  # unique sorted classes
@@ -63,22 +63,24 @@ class Mahalanobis(OODBaseDetector):
63
63
 
64
64
  # compute mus and covs
65
65
  mus = dict()
66
- covs = dict()
66
+ mean_cov = None
67
67
  for cls in self._classes:
68
68
  indexes = self.op.equal(labels, cls)
69
69
  _features_cls = self.op.flatten(features[0][indexes])
70
70
  mus[cls] = self.op.mean(_features_cls, dim=0)
71
71
  _zero_f_cls = _features_cls - mus[cls]
72
- covs[cls] = (
72
+ cov_cls = (
73
73
  self.op.matmul(self.op.t(_zero_f_cls), _zero_f_cls)
74
74
  / _zero_f_cls.shape[0]
75
75
  )
76
+ if mean_cov is None:
77
+ mean_cov = (len(_features_cls) / len(features[0])) * cov_cls
78
+ else:
79
+ mean_cov += (len(_features_cls) / len(features[0])) * cov_cls
76
80
 
77
- # mean cov and its inverse
78
- mean_cov = self.op.mean(self.op.stack(list(covs.values())), dim=0)
79
-
80
- self._mus = mus
81
+ # pseudo-inverse of the mean covariance matrix
81
82
  self._pinv_cov = self.op.pinv(mean_cov)
83
+ self._mus = mus
82
84
 
83
85
  def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
84
86
  """
oodeel/methods/mls.py CHANGED
@@ -53,11 +53,19 @@ class MLS(OODBaseDetector):
53
53
  self,
54
54
  output_activation: str = "linear",
55
55
  use_react: bool = False,
56
+ use_scale: bool = False,
57
+ use_ash: bool = False,
56
58
  react_quantile: float = 0.8,
59
+ scale_percentile: float = 0.85,
60
+ ash_percentile: float = 0.90,
57
61
  ):
58
62
  super().__init__(
59
63
  use_react=use_react,
64
+ use_scale=use_scale,
65
+ use_ash=use_ash,
60
66
  react_quantile=react_quantile,
67
+ scale_percentile=scale_percentile,
68
+ ash_percentile=ash_percentile,
61
69
  )
62
70
  self.output_activation = output_activation
63
71
 
oodeel/methods/odin.py CHANGED
@@ -48,12 +48,20 @@ class ODIN(OODBaseDetector):
48
48
  temperature: float = 1000,
49
49
  noise: float = 0.014,
50
50
  use_react: bool = False,
51
+ use_scale: bool = False,
52
+ use_ash: bool = False,
51
53
  react_quantile: float = 0.8,
54
+ scale_percentile: float = 0.85,
55
+ ash_percentile: float = 0.90,
52
56
  ):
53
57
  self.temperature = temperature
54
58
  super().__init__(
55
59
  use_react=use_react,
60
+ use_scale=use_scale,
61
+ use_ash=use_ash,
56
62
  react_quantile=react_quantile,
63
+ scale_percentile=scale_percentile,
64
+ ash_percentile=ash_percentile,
57
65
  )
58
66
  self.noise = noise
59
67
 
oodeel/methods/rmds.py ADDED
@@ -0,0 +1,122 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
3
+ # rights reserved. DEEL is a research program operated by IVADO, IRT Saint Exupéry,
4
+ # CRIAQ and ANITI - https://www.deel.ai/
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ import numpy as np
24
+
25
+ from ..types import DatasetType
26
+ from ..types import TensorType
27
+ from ..types import Tuple
28
+ from oodeel.methods.mahalanobis import Mahalanobis
29
+
30
+
31
+ class RMDS(Mahalanobis):
32
+ """
33
+ "A Simple Fix to Mahalanobis Distance for Improving Near-OOD Detection"
34
+ https://arxiv.org/abs/2106.09022
35
+
36
+ Args:
37
+ eps (float): magnitude for gradient based input perturbation.
38
+ Defaults to 0.02.
39
+ """
40
+
41
+ def __init__(self, eps: float = 0.002):
42
+ super().__init__(eps=eps)
43
+
44
+ def _fit_to_dataset(self, fit_dataset: DatasetType) -> None:
45
+ """
46
+ Constructs the per class means and the covariance matrix,
47
+ as well as the background mean and covariance matrix,
48
+ from ID data "fit_dataset".
49
+ The means and pseudo-inverses of the covariance matrices
50
+ will be used for RMDS score computation.
51
+
52
+ Args:
53
+ fit_dataset (Union[TensorType, DatasetType]): input dataset (ID)
54
+ """
55
+ # means and pseudo-inverse of the mean convariance matrix from Mahalanobis
56
+ super()._fit_to_dataset(fit_dataset)
57
+
58
+ # extract features
59
+ features, _ = self.feature_extractor.predict(fit_dataset)
60
+
61
+ # compute background mu and cov
62
+ _features_bg = self.op.flatten(features[0])
63
+ mu_bg = self.op.mean(_features_bg, dim=0)
64
+ _zero_f_bg = _features_bg - mu_bg
65
+ cov_bg = self.op.matmul(self.op.t(_zero_f_bg), _zero_f_bg) / _zero_f_bg.shape[0]
66
+
67
+ # background mu and pseudo-inverse of the mean covariance matrices
68
+ self._mu_bg = mu_bg
69
+ self._pinv_cov_bg = self.op.pinv(cov_bg)
70
+
71
+ def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
72
+ """
73
+ Computes an OOD score for input samples "inputs" based on the RMDS
74
+ distance with respect to the closest class-conditional Gaussian distribution,
75
+ and the background distribution.
76
+
77
+ Args:
78
+ inputs (TensorType): input samples
79
+
80
+ Returns:
81
+ Tuple[np.ndarray]: scores, logits
82
+ """
83
+ # input preprocessing (perturbation)
84
+ if self.eps > 0:
85
+ inputs_p = self._input_perturbation(inputs)
86
+ else:
87
+ inputs_p = inputs
88
+
89
+ # mahalanobis score on perturbed inputs
90
+ features_p, _ = self.feature_extractor.predict_tensor(inputs_p)
91
+ features_p = self.op.flatten(features_p[0])
92
+ gaussian_score_p = self._mahalanobis_score(features_p)
93
+
94
+ # background score on perturbed inputs
95
+ gaussian_score_bg = self._background_score(features_p)
96
+
97
+ # take the highest score for each sample
98
+ gaussian_score_corrected = self.op.max(
99
+ gaussian_score_p - gaussian_score_bg, dim=1
100
+ )
101
+ return -self.op.convert_to_numpy(gaussian_score_corrected)
102
+
103
+ def _background_score(self, out_features: TensorType) -> TensorType:
104
+ """
105
+ Mahalanobis distance-based background score. For each test sample, it computes
106
+ the log of the probability densities of some observations (assuming a
107
+ normal distribution) using the mahalanobis distance with respect to the
108
+ background distribution.
109
+
110
+ Args:
111
+ out_features (TensorType): test samples features
112
+
113
+ Returns:
114
+ TensorType: confidence scores (with respect to the background distribution)
115
+ """
116
+ zero_f = out_features - self._mu_bg
117
+ # gaussian log prob density (mahalanobis)
118
+ log_probs_f = -0.5 * self.op.diag(
119
+ self.op.matmul(self.op.matmul(zero_f, self._pinv_cov_bg), self.op.t(zero_f))
120
+ )
121
+ gaussian_score = self.op.reshape(log_probs_f, (-1, 1))
122
+ return gaussian_score
oodeel/methods/she.py ADDED
@@ -0,0 +1,197 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
3
+ # rights reserved. DEEL is a research program operated by IVADO, IRT Saint Exupéry,
4
+ # CRIAQ and ANITI - https://www.deel.ai/
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ import numpy as np
24
+
25
+ from ..types import DatasetType
26
+ from ..types import TensorType
27
+ from ..types import Union
28
+ from .base import OODBaseDetector
29
+
30
+
31
+ class SHE(OODBaseDetector):
32
+ """
33
+ "Out-of-Distribution Detection based on In-Distribution Data Patterns Memorization
34
+ with Modern Hopfield Energy"
35
+ [link](https://openreview.net/forum?id=KkazG4lgKL)
36
+
37
+ This method first computes the mean of the internal layer representation of ID data
38
+ for each ID class. This mean is seen as the average of the ID activation patterns
39
+ as defined in the original paper.
40
+ The method then returns the maximum value of the dot product between the internal
41
+ layer representation of the input and the average patterns, which is a simplified
42
+ version of Hopfield energy as defined in the original paper.
43
+
44
+ Remarks:
45
+ * An input perturbation is applied in the same way as in Mahalanobis score
46
+ * The original paper only considers the penultimate layer of the neural
47
+ network, while we aggregate the results of multiple layers after normalizing by
48
+ the dimension of each vector (the activation vector for dense layers, and the
49
+ average pooling of the feature map for convolutional layers).
50
+
51
+ Args:
52
+ eps (float): magnitude for gradient based input perturbation.
53
+ Defaults to 0.0014.
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ eps: float = 0.0014,
59
+ ):
60
+ super().__init__()
61
+ self.eps = eps
62
+ self.postproc_fns = None
63
+
64
+ def _postproc_feature_maps(self, feature_map):
65
+ if len(feature_map.shape) > 2:
66
+ feature_map = self.op.avg_pool_2d(feature_map)
67
+ return self.op.flatten(feature_map)
68
+
69
+ def _fit_to_dataset(
70
+ self,
71
+ fit_dataset: Union[TensorType, DatasetType],
72
+ ) -> None:
73
+ """
74
+ Compute the means of the input dataset in the activation space of the selected
75
+ layers. The means are computed for each class in the dataset.
76
+
77
+ Args:
78
+ fit_dataset (Union[TensorType, DatasetType]): input dataset (ID) to
79
+ construct the index with.
80
+ ood_dataset (Union[TensorType, DatasetType]): OOD dataset to tune the
81
+ aggregation coefficients.
82
+ """
83
+ self.postproc_fns = [
84
+ self._postproc_feature_maps
85
+ for i in range(len(self.feature_extractor.feature_layers_id))
86
+ ]
87
+
88
+ features, infos = self.feature_extractor.predict(
89
+ fit_dataset, postproc_fns=self.postproc_fns
90
+ )
91
+
92
+ labels = infos["labels"]
93
+ preds = self.op.argmax(infos["logits"], dim=-1)
94
+ preds = self.op.convert_to_numpy(preds)
95
+
96
+ # unique sorted classes
97
+ self._classes = np.sort(np.unique(self.op.convert_to_numpy(labels)))
98
+ labels = self.op.convert_to_numpy(labels)
99
+
100
+ self._mus = list()
101
+ for feature in features:
102
+ mus_f = list()
103
+ for cls in self._classes:
104
+ indexes = np.equal(labels, cls) & np.equal(preds, cls)
105
+ _features_cls = feature[indexes]
106
+ mus_f.append(
107
+ self.op.unsqueeze(self.op.mean(_features_cls, dim=0), dim=0)
108
+ )
109
+ self._mus.append(self.op.permute(self.op.cat(mus_f), (1, 0)))
110
+
111
+ def _score_tensor(self, inputs: TensorType) -> np.ndarray:
112
+ """
113
+ Computes an OOD score for input samples "inputs" based on
114
+ the aggregation of neural mean discrepancies from different layers.
115
+
116
+ Args:
117
+ inputs: input samples to score
118
+
119
+ Returns:
120
+ scores
121
+ """
122
+
123
+ inputs_p = self._input_perturbation(inputs)
124
+ features, logits = self.feature_extractor.predict_tensor(
125
+ inputs_p, postproc_fns=self.postproc_fns
126
+ )
127
+
128
+ scores = self._get_she_output(features)
129
+
130
+ return -self.op.convert_to_numpy(scores)
131
+
132
+ def _get_she_output(self, features):
133
+ scores = None
134
+ for feature, mus_f in zip(features, self._mus):
135
+ she = self.op.matmul(self.op.squeeze(feature), mus_f) / feature.shape[1]
136
+ she = self.op.max(she, dim=1)
137
+ scores = she if scores is None else she + scores
138
+ return scores
139
+
140
+ def _input_perturbation(self, inputs: TensorType) -> TensorType:
141
+ """
142
+ Apply small perturbation on inputs to make the in- and out- distribution
143
+ samples more separable.
144
+
145
+ Args:
146
+ inputs (TensorType): input samples
147
+
148
+ Returns:
149
+ TensorType: Perturbed inputs
150
+ """
151
+
152
+ def __loss_fn(inputs: TensorType) -> TensorType:
153
+ """
154
+ Loss function for the input perturbation.
155
+
156
+ Args:
157
+ inputs (TensorType): input samples
158
+
159
+ Returns:
160
+ TensorType: loss value
161
+ """
162
+ # extract features
163
+ out_features, _ = self.feature_extractor.predict(
164
+ inputs, detach=False, postproc_fns=self.postproc_fns
165
+ )
166
+ # get mahalanobis score for the class maximizing it
167
+ she_score = self._get_she_output(out_features)
168
+ log_probs_f = self.op.log(she_score)
169
+ return self.op.mean(log_probs_f)
170
+
171
+ # compute gradient
172
+ gradient = self.op.gradient(__loss_fn, inputs)
173
+ gradient = self.op.sign(gradient)
174
+
175
+ inputs_p = inputs - self.eps * gradient
176
+ return inputs_p
177
+
178
+ @property
179
+ def requires_to_fit_dataset(self) -> bool:
180
+ """
181
+ Whether an OOD detector needs a `fit_dataset` argument in the fit function.
182
+
183
+ Returns:
184
+ bool: True if `fit_dataset` is required else False.
185
+ """
186
+ return True
187
+
188
+ @property
189
+ def requires_internal_features(self) -> bool:
190
+ """
191
+ Whether an OOD detector acts on internal model features.
192
+
193
+ Returns:
194
+ bool: True if the detector perform computations on an intermediate layer
195
+ else False.
196
+ """
197
+ return True
oodeel/methods/vim.py CHANGED
@@ -61,7 +61,7 @@ class VIM(OODBaseDetector):
61
61
  pca_origin (str): either "pseudo" for using $W^{-1}b$ where $W^{-1}$ is
62
62
  the pseudo inverse of the final linear layer applied to bias term
63
63
  (as in the VIM paper), or "center" for using the mean of the data in
64
- feature space. Defaults to "center".
64
+ feature space. Defaults to "pseudo".
65
65
  """
66
66
 
67
67
  def __init__(
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright IRT Antoine de Saint Exupéry et Université Paul Sabatier Toulouse III - All
3
+ # rights reserved. DEEL is a research program operated by IVADO, IRT Saint Exupéry,
4
+ # CRIAQ and ANITI - https://www.deel.ai/
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ try:
24
+ from .tf_preprocess import TFRandomPatchPermutation
25
+ except ImportError:
26
+ pass
27
+
28
+ try:
29
+ from .torch_preprocess import TorchRandomPatchPermutation
30
+ except ImportError:
31
+ pass