oodeel 0.1.1__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 (47) 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/eval/plots/features.py +2 -2
  13. oodeel/eval/plots/plotly.py +2 -2
  14. oodeel/extractor/feature_extractor.py +30 -9
  15. oodeel/extractor/keras_feature_extractor.py +70 -13
  16. oodeel/extractor/torch_feature_extractor.py +120 -33
  17. oodeel/methods/__init__.py +17 -1
  18. oodeel/methods/base.py +103 -17
  19. oodeel/methods/dknn.py +22 -9
  20. oodeel/methods/energy.py +8 -0
  21. oodeel/methods/entropy.py +8 -0
  22. oodeel/methods/gen.py +118 -0
  23. oodeel/methods/gram.py +307 -0
  24. oodeel/methods/mahalanobis.py +14 -12
  25. oodeel/methods/mls.py +8 -0
  26. oodeel/methods/odin.py +8 -0
  27. oodeel/methods/rmds.py +122 -0
  28. oodeel/methods/she.py +197 -0
  29. oodeel/methods/vim.py +5 -5
  30. oodeel/preprocess/__init__.py +31 -0
  31. oodeel/preprocess/tf_preprocess.py +95 -0
  32. oodeel/preprocess/torch_preprocess.py +97 -0
  33. oodeel/utils/operator.py +72 -2
  34. oodeel/utils/tf_operator.py +72 -4
  35. oodeel/utils/tf_training_tools.py +26 -3
  36. oodeel/utils/torch_operator.py +75 -4
  37. oodeel/utils/torch_training_tools.py +31 -2
  38. {oodeel-0.1.1.dist-info → oodeel-0.3.0.dist-info}/METADATA +141 -107
  39. oodeel-0.3.0.dist-info/RECORD +57 -0
  40. {oodeel-0.1.1.dist-info → oodeel-0.3.0.dist-info}/WHEEL +1 -1
  41. tests/tests_tensorflow/tf_methods_utils.py +2 -1
  42. tests/tests_torch/tools_torch.py +9 -9
  43. tests/tests_torch/torch_methods_utils.py +34 -27
  44. tests/tools_operator.py +10 -1
  45. oodeel-0.1.1.dist-info/RECORD +0 -46
  46. {oodeel-0.1.1.dist-info → oodeel-0.3.0.dist-info/licenses}/LICENSE +0 -0
  47. {oodeel-0.1.1.dist-info → oodeel-0.3.0.dist-info}/top_level.txt +0 -0
oodeel/methods/base.py CHANGED
@@ -20,11 +20,13 @@
20
20
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  # SOFTWARE.
23
+ import inspect
23
24
  from abc import ABC
24
25
  from abc import abstractmethod
25
26
  from typing import get_args
26
27
 
27
28
  import numpy as np
29
+ from tqdm import tqdm
28
30
 
29
31
  from ..extractor.feature_extractor import FeatureExtractor
30
32
  from ..types import Callable
@@ -38,25 +40,65 @@ from ..utils import import_backend_specific_stuff
38
40
 
39
41
 
40
42
  class OODBaseDetector(ABC):
41
- """Base Class for methods that assign a score to unseen samples.
42
-
43
- Args:
44
- use_react (bool): if true, apply ReAct method by clipping penultimate
45
- activations under a threshold value.
46
- react_quantile (Optional[float]): q value in the range [0, 1] used to compute
47
- the react clipping threshold defined as the q-th quantile penultimate layer
48
- activations. Defaults to 0.8.
43
+ """OODBaseDetector is an abstract base class for Out-of-Distribution (OOD)
44
+ detection.
45
+
46
+ Attributes:
47
+ feature_extractor (FeatureExtractor): The feature extractor instance.
48
+ use_react (bool): Flag to indicate if ReAct method is used.
49
+ use_scale (bool): Flag to indicate if scaling method is used.
50
+ use_ash (bool): Flag to indicate if ASH method is used.
51
+ react_quantile (float): Quantile value for ReAct threshold.
52
+ scale_percentile (float): Percentile value for scaling.
53
+ ash_percentile (float): Percentile value for ASH.
54
+ react_threshold (float): Threshold value for ReAct.
55
+ postproc_fns (List[Callable]): List of post-processing functions.
56
+
57
+ Methods:
58
+ __init__: Initializes the OODBaseDetector with specified parameters.
59
+ _score_tensor: Abstract method to compute OOD score for input samples.
60
+ _sanitize_posproc_fns: Sanitizes post-processing functions used at each layer
61
+ output.
62
+ fit: Prepares the detector for scoring by constructing the feature extractor
63
+ and calibrating on ID data.
64
+ _load_feature_extractor: Loads the feature extractor based on the model and
65
+ specified layers.
66
+ _fit_to_dataset: Abstract method to fit the OOD detector to a dataset.
67
+ score: Computes an OOD score for input samples.
68
+ compute_react_threshold: Computes the ReAct threshold using the fit dataset.
69
+ __call__: Convenience wrapper for the score method.
70
+ requires_to_fit_dataset: Property indicating if the detector needs a fit
71
+ dataset.
72
+ requires_internal_features: Property indicating if the detector acts on
73
+ internal model features.
49
74
  """
50
75
 
51
76
  def __init__(
52
77
  self,
53
- use_react: bool = False,
54
- react_quantile: float = 0.8,
78
+ use_react: Optional[bool] = False,
79
+ use_scale: Optional[bool] = False,
80
+ use_ash: Optional[bool] = False,
81
+ react_quantile: Optional[float] = None,
82
+ scale_percentile: Optional[float] = None,
83
+ ash_percentile: Optional[float] = None,
84
+ postproc_fns: Optional[List[Callable]] = None,
55
85
  ):
56
86
  self.feature_extractor: FeatureExtractor = None
57
87
  self.use_react = use_react
88
+ self.use_scale = use_scale
89
+ self.use_ash = use_ash
58
90
  self.react_quantile = react_quantile
91
+ self.scale_percentile = scale_percentile
92
+ self.ash_percentile = ash_percentile
59
93
  self.react_threshold = None
94
+ self.postproc_fns = self._sanitize_posproc_fns(postproc_fns)
95
+
96
+ if use_scale and use_react:
97
+ raise ValueError("Cannot use both ReAct and scale at the same time")
98
+ if use_scale and use_ash:
99
+ raise ValueError("Cannot use both ASH and scale at the same time")
100
+ if use_ash and use_react:
101
+ raise ValueError("Cannot use both ReAct and ASH at the same time")
60
102
 
61
103
  @abstractmethod
62
104
  def _score_tensor(self, inputs: TensorType) -> np.ndarray:
@@ -66,18 +108,44 @@ class OODBaseDetector(ABC):
66
108
 
67
109
  Args:
68
110
  inputs (TensorType): tensor to score
69
-
70
111
  Returns:
71
112
  Tuple[TensorType]: OOD scores, predicted logits
72
113
  """
73
114
  raise NotImplementedError()
74
115
 
116
+ def _sanitize_posproc_fns(
117
+ self,
118
+ postproc_fns: Union[List[Callable], None],
119
+ ) -> List[Callable]:
120
+ """Sanitize postproc fns used at each layer output of the feature extractor.
121
+
122
+ Args:
123
+ postproc_fns (Optional[List[Callable]], optional): List of postproc
124
+ functions, one per output layer. Defaults to None.
125
+
126
+ Returns:
127
+ List[Callable]: Sanitized postproc_fns list
128
+ """
129
+ if postproc_fns is not None:
130
+ assert len(postproc_fns) == len(
131
+ self.output_layers_id
132
+ ), "len of postproc_fns and output_layers_id must match"
133
+
134
+ def identity(x):
135
+ return x
136
+
137
+ postproc_fns = [identity if fn is None else fn for fn in postproc_fns]
138
+
139
+ return postproc_fns
140
+
75
141
  def fit(
76
142
  self,
77
143
  model: Callable,
78
144
  fit_dataset: Optional[Union[ItemType, DatasetType]] = None,
79
145
  feature_layers_id: List[Union[int, str]] = [],
80
146
  input_layer_id: Optional[Union[int, str]] = None,
147
+ verbose: bool = False,
148
+ **kwargs,
81
149
  ) -> None:
82
150
  """Prepare the detector for scoring:
83
151
  * Constructs the feature extractor based on the model
@@ -95,6 +163,7 @@ class OODBaseDetector(ABC):
95
163
  layer of the feature extractor.
96
164
  If int, the rank of the layer in the layer list
97
165
  If str, the name of the layer. Defaults to None.
166
+ verbose (bool): if True, display a progress bar. Defaults to False.
98
167
  """
99
168
  (
100
169
  self.backend,
@@ -117,7 +186,7 @@ class OODBaseDetector(ABC):
117
186
  " provided to compute react activation threshold"
118
187
  )
119
188
  else:
120
- self.compute_react_threshold(model, fit_dataset)
189
+ self.compute_react_threshold(model, fit_dataset, verbose=verbose)
121
190
 
122
191
  if (feature_layers_id == []) and (self.requires_internal_features):
123
192
  raise ValueError(
@@ -133,7 +202,9 @@ class OODBaseDetector(ABC):
133
202
  )
134
203
 
135
204
  if fit_dataset is not None:
136
- self._fit_to_dataset(fit_dataset)
205
+ if "verbose" in inspect.signature(self._fit_to_dataset).parameters.keys():
206
+ kwargs.update({"verbose": verbose})
207
+ self._fit_to_dataset(fit_dataset, **kwargs)
137
208
 
138
209
  def _load_feature_extractor(
139
210
  self,
@@ -158,11 +229,18 @@ class OODBaseDetector(ABC):
158
229
  Returns:
159
230
  FeatureExtractor: a feature extractor instance
160
231
  """
232
+ if not self.use_ash:
233
+ self.ash_percentile = None
234
+ if not self.use_scale:
235
+ self.scale_percentile = None
236
+
161
237
  feature_extractor = self.FeatureExtractorClass(
162
238
  model,
163
239
  feature_layers_id=feature_layers_id,
164
240
  input_layer_id=input_layer_id,
165
241
  react_threshold=self.react_threshold,
242
+ scale_percentile=self.scale_percentile,
243
+ ash_percentile=self.ash_percentile,
166
244
  )
167
245
  return feature_extractor
168
246
 
@@ -180,12 +258,14 @@ class OODBaseDetector(ABC):
180
258
  def score(
181
259
  self,
182
260
  dataset: Union[ItemType, DatasetType],
261
+ verbose: bool = False,
183
262
  ) -> np.ndarray:
184
263
  """
185
264
  Computes an OOD score for input samples "inputs".
186
265
 
187
266
  Args:
188
267
  dataset (Union[ItemType, DatasetType]): dataset or tensors to score
268
+ verbose (bool): if True, display a progress bar. Defaults to False.
189
269
 
190
270
  Returns:
191
271
  tuple: scores or list of scores (depending on the input) and a dictionary
@@ -209,7 +289,7 @@ class OODBaseDetector(ABC):
209
289
  scores = np.array([])
210
290
  logits = None
211
291
 
212
- for item in dataset:
292
+ for item in tqdm(dataset, desc="Scoring", disable=not verbose):
213
293
  tensor = self.data_handler.get_input_from_dataset_item(item)
214
294
  score_batch = self._score_tensor(tensor)
215
295
  logits_batch = self.op.convert_to_numpy(
@@ -240,10 +320,16 @@ class OODBaseDetector(ABC):
240
320
  info = dict(labels=labels, logits=logits)
241
321
  return scores, info
242
322
 
243
- def compute_react_threshold(self, model: Callable, fit_dataset: DatasetType):
323
+ def compute_react_threshold(
324
+ self, model: Callable, fit_dataset: DatasetType, verbose: bool = False
325
+ ):
244
326
  penult_feat_extractor = self._load_feature_extractor(model, [-2])
245
- unclipped_features, _ = penult_feat_extractor.predict(fit_dataset)
246
- self.react_threshold = self.op.quantile(unclipped_features, self.react_quantile)
327
+ unclipped_features, _ = penult_feat_extractor.predict(
328
+ fit_dataset, verbose=verbose
329
+ )
330
+ self.react_threshold = self.op.quantile(
331
+ unclipped_features[0], self.react_quantile
332
+ )
247
333
 
248
334
  def __call__(self, inputs: Union[ItemType, DatasetType]) -> np.ndarray:
249
335
  """
oodeel/methods/dknn.py CHANGED
@@ -38,30 +38,43 @@ class DKNN(OODBaseDetector):
38
38
  Args:
39
39
  nearest: number of nearest neighbors to consider.
40
40
  Defaults to 1.
41
+ use_gpu (bool): Flag to enable GPU acceleration for FAISS. Defaults to False.
41
42
  """
42
43
 
43
- def __init__(
44
- self,
45
- nearest: int = 1,
46
- ):
44
+ def __init__(self, nearest: int = 50, use_gpu: bool = False):
47
45
  super().__init__()
48
-
49
46
  self.index = None
50
47
  self.nearest = nearest
48
+ self.use_gpu = use_gpu
49
+
50
+ if self.use_gpu:
51
+ try:
52
+ self.res = faiss.StandardGpuResources()
53
+ except AttributeError as e:
54
+ raise ImportError(
55
+ "faiss-gpu is not installed, but use_gpu was set to True."
56
+ + "Please install faiss-gpu or set use_gpu to False."
57
+ ) from e
51
58
 
52
59
  def _fit_to_dataset(self, fit_dataset: Union[TensorType, DatasetType]) -> None:
53
60
  """
54
61
  Constructs the index from ID data "fit_dataset", which will be used for
55
- nearest neighbor search.
62
+ nearest neighbor search. Can operate on CPU or GPU based on the `use_gpu` flag.
56
63
 
57
64
  Args:
58
65
  fit_dataset: input dataset (ID) to construct the index with.
59
66
  """
60
67
  fit_projected, _ = self.feature_extractor.predict(fit_dataset)
61
- fit_projected = self.op.convert_to_numpy(fit_projected)
68
+ fit_projected = self.op.convert_to_numpy(fit_projected[0])
62
69
  fit_projected = fit_projected.reshape(fit_projected.shape[0], -1)
63
70
  norm_fit_projected = self._l2_normalization(fit_projected)
64
- self.index = faiss.IndexFlatL2(norm_fit_projected.shape[1])
71
+
72
+ if self.use_gpu:
73
+ cpu_index = faiss.IndexFlatL2(norm_fit_projected.shape[1])
74
+ self.index = faiss.index_cpu_to_gpu(self.res, 0, cpu_index)
75
+ else:
76
+ self.index = faiss.IndexFlatL2(norm_fit_projected.shape[1])
77
+
65
78
  self.index.add(norm_fit_projected)
66
79
 
67
80
  def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
@@ -77,7 +90,7 @@ class DKNN(OODBaseDetector):
77
90
  """
78
91
 
79
92
  input_projected, _ = self.feature_extractor.predict_tensor(inputs)
80
- input_projected = self.op.convert_to_numpy(input_projected)
93
+ input_projected = self.op.convert_to_numpy(input_projected[0])
81
94
  input_projected = input_projected.reshape(input_projected.shape[0], -1)
82
95
  norm_input_projected = self._l2_normalization(input_projected)
83
96
  scores, _ = self.index.search(norm_input_projected, self.nearest)
oodeel/methods/energy.py CHANGED
@@ -59,11 +59,19 @@ class Energy(OODBaseDetector):
59
59
  def __init__(
60
60
  self,
61
61
  use_react: bool = False,
62
+ use_scale: bool = False,
63
+ use_ash: bool = False,
62
64
  react_quantile: float = 0.8,
65
+ scale_percentile: float = 0.85,
66
+ ash_percentile: float = 0.90,
63
67
  ):
64
68
  super().__init__(
65
69
  use_react=use_react,
70
+ use_scale=use_scale,
71
+ use_ash=use_ash,
66
72
  react_quantile=react_quantile,
73
+ scale_percentile=scale_percentile,
74
+ ash_percentile=ash_percentile,
67
75
  )
68
76
 
69
77
  def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
oodeel/methods/entropy.py CHANGED
@@ -52,11 +52,19 @@ class Entropy(OODBaseDetector):
52
52
  def __init__(
53
53
  self,
54
54
  use_react: bool = False,
55
+ use_scale: bool = False,
56
+ use_ash: bool = False,
55
57
  react_quantile: float = 0.8,
58
+ scale_percentile: float = 0.85,
59
+ ash_percentile: float = 0.90,
56
60
  ):
57
61
  super().__init__(
58
62
  use_react=use_react,
63
+ use_scale=use_scale,
64
+ use_ash=use_ash,
59
65
  react_quantile=react_quantile,
66
+ scale_percentile=scale_percentile,
67
+ ash_percentile=ash_percentile,
60
68
  )
61
69
 
62
70
  def _score_tensor(self, inputs: TensorType) -> Tuple[np.ndarray]:
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