dataeval 0.74.2__py3-none-any.whl → 0.75.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.
Files changed (66) hide show
  1. dataeval/__init__.py +27 -23
  2. dataeval/detectors/__init__.py +2 -2
  3. dataeval/detectors/drift/__init__.py +14 -12
  4. dataeval/detectors/drift/base.py +1 -1
  5. dataeval/detectors/drift/cvm.py +1 -1
  6. dataeval/detectors/drift/ks.py +1 -1
  7. dataeval/detectors/drift/mmd.py +6 -5
  8. dataeval/detectors/drift/torch.py +12 -12
  9. dataeval/detectors/drift/uncertainty.py +3 -2
  10. dataeval/detectors/linters/__init__.py +4 -4
  11. dataeval/detectors/linters/clusterer.py +2 -7
  12. dataeval/detectors/linters/duplicates.py +6 -10
  13. dataeval/detectors/linters/outliers.py +4 -2
  14. dataeval/detectors/ood/__init__.py +3 -10
  15. dataeval/detectors/ood/{ae_torch.py → ae.py} +6 -4
  16. dataeval/detectors/ood/base.py +64 -161
  17. dataeval/detectors/ood/metadata_ks_compare.py +34 -42
  18. dataeval/detectors/ood/metadata_least_likely.py +3 -3
  19. dataeval/detectors/ood/metadata_ood_mi.py +6 -5
  20. dataeval/detectors/ood/mixin.py +146 -0
  21. dataeval/detectors/ood/output.py +63 -0
  22. dataeval/interop.py +6 -5
  23. dataeval/{logging.py → log.py} +2 -0
  24. dataeval/metrics/__init__.py +2 -2
  25. dataeval/metrics/bias/__init__.py +9 -12
  26. dataeval/metrics/bias/balance.py +10 -8
  27. dataeval/metrics/bias/coverage.py +52 -4
  28. dataeval/metrics/bias/diversity.py +42 -14
  29. dataeval/metrics/bias/parity.py +15 -12
  30. dataeval/metrics/estimators/__init__.py +2 -2
  31. dataeval/metrics/estimators/ber.py +3 -1
  32. dataeval/metrics/estimators/divergence.py +1 -1
  33. dataeval/metrics/estimators/uap.py +1 -1
  34. dataeval/metrics/stats/__init__.py +18 -18
  35. dataeval/metrics/stats/base.py +4 -4
  36. dataeval/metrics/stats/boxratiostats.py +8 -9
  37. dataeval/metrics/stats/datasetstats.py +10 -14
  38. dataeval/metrics/stats/dimensionstats.py +4 -4
  39. dataeval/metrics/stats/hashstats.py +12 -8
  40. dataeval/metrics/stats/labelstats.py +5 -5
  41. dataeval/metrics/stats/pixelstats.py +4 -9
  42. dataeval/metrics/stats/visualstats.py +4 -9
  43. dataeval/utils/__init__.py +4 -13
  44. dataeval/utils/dataset/__init__.py +7 -0
  45. dataeval/utils/{torch → dataset}/datasets.py +2 -0
  46. dataeval/utils/dataset/read.py +63 -0
  47. dataeval/utils/{split_dataset.py → dataset/split.py} +38 -30
  48. dataeval/utils/image.py +2 -2
  49. dataeval/utils/metadata.py +310 -5
  50. dataeval/{metrics/bias/metadata_utils.py → utils/plot.py} +1 -104
  51. dataeval/utils/torch/__init__.py +2 -17
  52. dataeval/utils/torch/gmm.py +29 -6
  53. dataeval/utils/torch/{utils.py → internal.py} +82 -58
  54. dataeval/utils/torch/models.py +10 -8
  55. dataeval/utils/torch/trainer.py +6 -85
  56. dataeval/workflows/__init__.py +2 -5
  57. dataeval/workflows/sufficiency.py +16 -6
  58. dataeval-0.75.0.dist-info/METADATA +136 -0
  59. dataeval-0.75.0.dist-info/RECORD +67 -0
  60. dataeval/detectors/ood/base_torch.py +0 -109
  61. dataeval/metrics/bias/metadata_preprocessing.py +0 -285
  62. dataeval/utils/gmm.py +0 -26
  63. dataeval-0.74.2.dist-info/METADATA +0 -120
  64. dataeval-0.74.2.dist-info/RECORD +0 -66
  65. {dataeval-0.74.2.dist-info → dataeval-0.75.0.dist-info}/LICENSE.txt +0 -0
  66. {dataeval-0.74.2.dist-info → dataeval-0.75.0.dist-info}/WHEEL +0 -0
@@ -8,94 +8,30 @@ Licensed under Apache Software License (Apache 2.0)
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- __all__ = ["OODOutput", "OODScoreOutput"]
11
+ __all__ = []
12
12
 
13
- from abc import ABC, abstractmethod
14
- from dataclasses import dataclass
15
- from typing import Callable, Generic, Literal, TypeVar
13
+ from typing import Callable, cast
16
14
 
17
- import numpy as np
18
- from numpy.typing import ArrayLike, NDArray
15
+ import torch
16
+ from numpy.typing import ArrayLike
19
17
 
18
+ from dataeval.detectors.ood.mixin import OODBaseMixin, OODFitMixin, OODGMMMixin
20
19
  from dataeval.interop import to_numpy
21
- from dataeval.output import Output, set_metadata
22
- from dataeval.utils.gmm import GaussianMixtureModelParams
23
-
24
-
25
- @dataclass(frozen=True)
26
- class OODOutput(Output):
27
- """
28
- Output class for predictions from :class:`OOD_AE`, :class:`OOD_AEGMM`, :class:`OOD_LLR`,
29
- :class:`OOD_VAE`, and :class:`OOD_VAEGMM` out-of-distribution detectors
30
-
31
- Attributes
32
- ----------
33
- is_ood : NDArray
34
- Array of images that are detected as :term:Out-of-Distribution (OOD)`
35
- instance_score : NDArray
36
- Instance score of the evaluated dataset
37
- feature_score : NDArray | None
38
- Feature score, if available, of the evaluated dataset
39
- """
40
-
41
- is_ood: NDArray[np.bool_]
42
- instance_score: NDArray[np.float32]
43
- feature_score: NDArray[np.float32] | None
44
-
45
-
46
- @dataclass(frozen=True)
47
- class OODScoreOutput(Output):
48
- """
49
- Output class for instance and feature scores from :class:`OOD_AE`, :class:`OOD_AEGMM`,
50
- :class:`OOD_LLR`, :class:`OOD_VAE`, and :class:`OOD_VAEGMM` out-of-distribution detectors
51
-
52
- Parameters
53
- ----------
54
- instance_score : NDArray
55
- Instance score of the evaluated dataset.
56
- feature_score : NDArray | None, default None
57
- Feature score, if available, of the evaluated dataset.
58
- """
59
-
60
- instance_score: NDArray[np.float32]
61
- feature_score: NDArray[np.float32] | None = None
62
-
63
- def get(self, ood_type: Literal["instance", "feature"]) -> NDArray[np.float32]:
64
- """
65
- Returns either the instance or feature score
66
-
67
- Parameters
68
- ----------
69
- ood_type : "instance" | "feature"
70
-
71
- Returns
72
- -------
73
- NDArray
74
- Either the instance or feature score based on input selection
75
- """
76
- return self.instance_score if ood_type == "instance" or self.feature_score is None else self.feature_score
77
-
78
-
79
- TGMMData = TypeVar("TGMMData")
20
+ from dataeval.utils.torch.gmm import GaussianMixtureModelParams, gmm_params
21
+ from dataeval.utils.torch.internal import get_device, trainer
80
22
 
81
23
 
82
- class OODGMMMixin(Generic[TGMMData]):
83
- _gmm_params: GaussianMixtureModelParams[TGMMData]
24
+ class OODBase(OODBaseMixin[torch.nn.Module], OODFitMixin[Callable[..., torch.nn.Module], torch.optim.Optimizer]):
25
+ def __init__(self, model: torch.nn.Module, device: str | torch.device | None = None) -> None:
26
+ self.device: torch.device = get_device(device)
27
+ super().__init__(model)
84
28
 
85
-
86
- TModel = TypeVar("TModel", bound=Callable)
87
- TLossFn = TypeVar("TLossFn", bound=Callable)
88
- TOptimizer = TypeVar("TOptimizer")
89
-
90
-
91
- class OODFitMixin(Generic[TLossFn, TOptimizer], ABC):
92
- @abstractmethod
93
29
  def fit(
94
30
  self,
95
31
  x_ref: ArrayLike,
96
32
  threshold_perc: float,
97
- loss_fn: TLossFn | None,
98
- optimizer: TOptimizer | None,
33
+ loss_fn: Callable[..., torch.nn.Module] | None,
34
+ optimizer: torch.optim.Optimizer | None,
99
35
  epochs: int,
100
36
  batch_size: int,
101
37
  verbose: bool,
@@ -109,9 +45,9 @@ class OODFitMixin(Generic[TLossFn, TOptimizer], ABC):
109
45
  Training data.
110
46
  threshold_perc : float, default 100.0
111
47
  Percentage of reference data that is normal.
112
- loss_fn : TLossFn
48
+ loss_fn : Callable | None, default None
113
49
  Loss function used for training.
114
- optimizer : TOptimizer
50
+ optimizer : Optimizer, default keras.optimizers.Adam
115
51
  Optimizer used for training.
116
52
  epochs : int, default 20
117
53
  Number of training epochs.
@@ -121,87 +57,54 @@ class OODFitMixin(Generic[TLossFn, TOptimizer], ABC):
121
57
  Whether to print training progress.
122
58
  """
123
59
 
124
-
125
- class OODBaseMixin(Generic[TModel], ABC):
126
- _ref_score: OODScoreOutput
127
- _threshold_perc: float
128
- _data_info: tuple[tuple, type] | None = None
129
-
130
- def __init__(
60
+ # Train the model
61
+ trainer(
62
+ model=self.model,
63
+ x_train=to_numpy(x_ref),
64
+ y_train=None,
65
+ loss_fn=loss_fn,
66
+ optimizer=optimizer,
67
+ preprocess_fn=None,
68
+ epochs=epochs,
69
+ batch_size=batch_size,
70
+ device=self.device,
71
+ verbose=verbose,
72
+ )
73
+
74
+ # Infer the threshold values
75
+ self._ref_score = self.score(x_ref, batch_size)
76
+ self._threshold_perc = threshold_perc
77
+
78
+
79
+ class OODBaseGMM(OODBase, OODGMMMixin[GaussianMixtureModelParams]):
80
+ def fit(
131
81
  self,
132
- model: TModel,
82
+ x_ref: ArrayLike,
83
+ threshold_perc: float,
84
+ loss_fn: Callable[..., torch.nn.Module] | None,
85
+ optimizer: torch.optim.Optimizer | None,
86
+ epochs: int,
87
+ batch_size: int,
88
+ verbose: bool,
133
89
  ) -> None:
134
- self.model = model
135
-
136
- def _get_data_info(self, X: NDArray) -> tuple[tuple, type]:
137
- if not isinstance(X, np.ndarray):
138
- raise TypeError("Dataset should of type: `NDArray`.")
139
- return X.shape[1:], X.dtype.type
140
-
141
- def _validate(self, X: NDArray) -> None:
142
- check_data_info = self._get_data_info(X)
143
- if self._data_info is not None and check_data_info != self._data_info:
144
- raise RuntimeError(f"Expect data of type: {self._data_info[1]} and shape: {self._data_info[0]}. \
145
- Provided data is type: {check_data_info[1]} and shape: {check_data_info[0]}.")
146
-
147
- def _validate_state(self, X: NDArray) -> None:
148
- attrs = [k for c in self.__class__.mro()[:-1][::-1] if hasattr(c, "__annotations__") for k in c.__annotations__]
149
- if not all(hasattr(self, attr) for attr in attrs) or any(getattr(self, attr) for attr in attrs) is None:
150
- raise RuntimeError("Metric needs to be `fit` before method call.")
151
- self._validate(X)
152
-
153
- @abstractmethod
154
- def _score(self, X: ArrayLike, batch_size: int = int(1e10)) -> OODScoreOutput: ...
155
-
156
- @set_metadata
157
- def score(self, X: ArrayLike, batch_size: int = int(1e10)) -> OODScoreOutput:
158
- """
159
- Compute the :term:`out of distribution<Out-of-distribution (OOD)>` scores for a given dataset.
160
-
161
- Parameters
162
- ----------
163
- X : ArrayLike
164
- Input data to score.
165
- batch_size : int, default 1e10
166
- Number of instances to process in each batch.
167
- Use a smaller batch size if your dataset is large or if you encounter memory issues.
168
-
169
- Returns
170
- -------
171
- OODScoreOutput
172
- An object containing the instance-level and feature-level OOD scores.
173
- """
174
- return self._score(X, batch_size)
175
-
176
- def _threshold_score(self, ood_type: Literal["feature", "instance"] = "instance") -> np.floating:
177
- return np.percentile(self._ref_score.get(ood_type), self._threshold_perc)
178
-
179
- @set_metadata
180
- def predict(
181
- self,
182
- X: ArrayLike,
183
- batch_size: int = int(1e10),
184
- ood_type: Literal["feature", "instance"] = "instance",
185
- ) -> OODOutput:
186
- """
187
- Predict whether instances are :term:`out of distribution<Out-of-distribution (OOD)>` or not.
188
-
189
- Parameters
190
- ----------
191
- X : ArrayLike
192
- Input data for out-of-distribution prediction.
193
- batch_size : int, default 1e10
194
- Number of instances to process in each batch.
195
- ood_type : "feature" | "instance", default "instance"
196
- Predict out-of-distribution at the 'feature' or 'instance' level.
197
-
198
- Returns
199
- -------
200
- Dictionary containing the outlier predictions for the selected level,
201
- and the OOD scores for the data including both 'instance' and 'feature' (if present) level scores.
202
- """
203
- self._validate_state(X := to_numpy(X))
204
- # compute outlier scores
205
- score = self.score(X, batch_size=batch_size)
206
- ood_pred = score.get(ood_type) > self._threshold_score(ood_type)
207
- return OODOutput(is_ood=ood_pred, **score.dict())
90
+ # Train the model
91
+ trainer(
92
+ model=self.model,
93
+ x_train=to_numpy(x_ref),
94
+ y_train=None,
95
+ loss_fn=loss_fn,
96
+ optimizer=optimizer,
97
+ preprocess_fn=None,
98
+ epochs=epochs,
99
+ batch_size=batch_size,
100
+ device=self.device,
101
+ verbose=verbose,
102
+ )
103
+
104
+ # Calculate the GMM parameters
105
+ _, z, gamma = cast(tuple[torch.Tensor, torch.Tensor, torch.Tensor], self.model(x_ref))
106
+ self._gmm_params = gmm_params(z, gamma)
107
+
108
+ # Infer the threshold values
109
+ self._ref_score = self.score(x_ref, batch_size)
110
+ self._threshold_perc = threshold_perc
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __all__ = []
4
+
3
5
  import numbers
4
6
  import warnings
5
7
  from typing import Any, Mapping, NamedTuple
@@ -40,51 +42,41 @@ class KSOutput(MappingOutput[str, MetadataKSResult]):
40
42
  def meta_distribution_compare(
41
43
  md0: Mapping[str, list[Any] | NDArray[Any]], md1: Mapping[str, list[Any] | NDArray[Any]]
42
44
  ) -> KSOutput:
43
- """Measures the featurewise distance between two metadata distributions, and computes a p-value to evaluate its
44
- significance.
45
-
46
- Uses the Earth Mover's Distance and the Kolmogorov-Smirnov two-sample test, featurewise.
47
-
48
- Parameters
49
- ----------
50
- md0 : Mapping[str, list[Any] | NDArray[Any]]
51
- A set of arrays of values, indexed by metadata feature names, with one value per data example per feature.
52
- md1 : Mapping[str, list[Any] | NDArray[Any]]
53
- Another set of arrays of values, indexed by metadata feature names, with one value per data example per
54
- feature.
55
-
56
- Returns
57
- -------
58
- dict[str, KstestResult]
59
- A dictionary with keys corresponding to metadata feature names, and values that are KstestResult objects, as
60
- defined by scipy.stats.ks_2samp. These values also have two additional attributes: shift_magnitude and
61
- statistic_location. The first is the Earth Mover's Distance normalized by the interquartile range (IQR) of
62
- the reference, while the second is the value at which the KS statistic has its maximum, measured in
63
- IQR-normalized units relative to the median of the reference distribution.
64
-
65
- Examples
66
- --------
67
- Imagine we have 3 data examples, and that the corresponding metadata contains 2 features called time and
68
- altitude.
69
-
70
- >>> import numpy
45
+ """
46
+ Measures the featurewise distance between two metadata distributions, and computes a p-value to evaluate its
47
+ significance.
48
+
49
+ Uses the Earth Mover's Distance and the Kolmogorov-Smirnov two-sample test, featurewise.
50
+
51
+ Parameters
52
+ ----------
53
+ md0 : Mapping[str, list[Any] | NDArray[Any]]
54
+ A set of arrays of values, indexed by metadata feature names, with one value per data example per feature.
55
+ md1 : Mapping[str, list[Any] | NDArray[Any]]
56
+ Another set of arrays of values, indexed by metadata feature names, with one value per data example per
57
+ feature.
58
+
59
+ Returns
60
+ -------
61
+ dict[str, KstestResult]
62
+ A dictionary with keys corresponding to metadata feature names, and values that are KstestResult objects, as
63
+ defined by scipy.stats.ks_2samp. These values also have two additional attributes: shift_magnitude and
64
+ statistic_location. The first is the Earth Mover's Distance normalized by the interquartile range (IQR) of
65
+ the reference, while the second is the value at which the KS statistic has its maximum, measured in
66
+ IQR-normalized units relative to the median of the reference distribution.
67
+
68
+ Examples
69
+ --------
70
+ Imagine we have 3 data examples, and that the corresponding metadata contains 2 features called time and
71
+ altitude.
72
+
71
73
  >>> md0 = {"time": [1.2, 3.4, 5.6], "altitude": [235, 6789, 101112]}
72
74
  >>> md1 = {"time": [7.8, 9.10, 11.12], "altitude": [532, 9876, 211101]}
73
- >>> md_out = meta_distribution_compare(md0, md1).mdc
75
+ >>> md_out = meta_distribution_compare(md0, md1)
74
76
  >>> for k, v in md_out.items():
75
- >>> print(k)
76
- >>> for kv in v:
77
- >>> print("\t", f"{kv}: {v[kv]:.3f}")
78
- time
79
- statistic: 1.000
80
- statistic_location: 0.444
81
- shift_magnitude: 2.700
82
- pvalue: 0.000
83
- altitude
84
- statistic: 0.333
85
- statistic_location: 0.478
86
- shift_magnitude: 0.749
87
- pvalue: 0.944
77
+ ... print(f"{k}: { {kv: round(vv, 3) for kv, vv in v._asdict().items()} }")
78
+ time: {'statistic': 1.0, 'statistic_location': 0.444, 'shift_magnitude': 2.7, 'pvalue': 0.0}
79
+ altitude: {'statistic': 0.333, 'statistic_location': 0.478, 'shift_magnitude': 0.749, 'pvalue': 0.944}
88
80
  """
89
81
 
90
82
  if (metadata_keys := md0.keys()) != md1.keys():
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __all__ = []
4
+
3
5
  import numbers
4
6
  import warnings
5
7
  from typing import Any
@@ -41,11 +43,9 @@ def get_least_likely_features(
41
43
  Imagine we have 3 data examples, and that the corresponding metadata contains 2 features called time and
42
44
  altitude, as shown below.
43
45
 
44
- >>> from dataeval._internal.metrics.metadata_least_likely import get_least_likely_features
45
- >>> import numpy
46
46
  >>> metadata = {"time": [1.2, 3.4, 5.6], "altitude": [235, 6789, 101112]}
47
47
  >>> new_metadata = {"time": [7.8, 11.12], "altitude": [532, -211101]}
48
- >>> is_ood = numpy.array([True, True])
48
+ >>> is_ood = np.array([True, True])
49
49
  >>> get_least_likely_features(metadata, new_metadata, is_ood)
50
50
  [('time', 2.0), ('altitude', 33.245346)]
51
51
  """
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ __all__ = []
4
+
3
5
  import numbers
4
6
  import warnings
5
7
  from typing import Any
@@ -51,11 +53,10 @@ def get_metadata_ood_mi(
51
53
  --------
52
54
  Imagine we have 3 data examples, and that the corresponding metadata contains 2 features called time and altitude.
53
55
 
54
- >>> import numpy
55
- >>> metadata = {"time": numpy.linspace(0, 10, 100), "altitude": numpy.linspace(0, 16, 100) ** 2}
56
- >>> is_ood = metadata["altitude"] > 100
57
- >>> print(get_metadata_ood_mi(metadata, is_ood, discrete_features=False))
58
- {'time': 0.933074285817367, 'altitude': 0.9407686591507002}
56
+ >>> metadata = {"time": np.linspace(0, 10, 100), "altitude": np.linspace(0, 16, 100) ** 2}
57
+ >>> is_ood = metadata["altitude"] > 100
58
+ >>> get_metadata_ood_mi(metadata, is_ood, discrete_features=False, random_state=0)
59
+ {'time': 0.9359596758173668, 'altitude': 0.9407686591507002}
59
60
  """
60
61
  numerical_keys = [k for k, v in metadata.items() if all(isinstance(vi, numbers.Number) for vi in v)]
61
62
  if len(numerical_keys) < len(metadata):
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ from dataeval.detectors.ood.output import OODOutput, OODScoreOutput
4
+
5
+ __all__ = []
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Callable, Generic, Literal, TypeVar
9
+
10
+ import numpy as np
11
+ from numpy.typing import ArrayLike, NDArray
12
+
13
+ from dataeval.interop import to_numpy
14
+ from dataeval.output import set_metadata
15
+
16
+ TGMMParams = TypeVar("TGMMParams")
17
+
18
+
19
+ class OODGMMMixin(Generic[TGMMParams]):
20
+ _gmm_params: TGMMParams
21
+
22
+
23
+ TModel = TypeVar("TModel", bound=Callable)
24
+ TLossFn = TypeVar("TLossFn", bound=Callable)
25
+ TOptimizer = TypeVar("TOptimizer")
26
+
27
+
28
+ class OODFitMixin(Generic[TLossFn, TOptimizer], ABC):
29
+ @abstractmethod
30
+ def fit(
31
+ self,
32
+ x_ref: ArrayLike,
33
+ threshold_perc: float,
34
+ loss_fn: TLossFn | None,
35
+ optimizer: TOptimizer | None,
36
+ epochs: int,
37
+ batch_size: int,
38
+ verbose: bool,
39
+ ) -> None:
40
+ """
41
+ Train the model and infer the threshold value.
42
+
43
+ Parameters
44
+ ----------
45
+ x_ref : ArrayLike
46
+ Training data.
47
+ threshold_perc : float, default 100.0
48
+ Percentage of reference data that is normal.
49
+ loss_fn : TLossFn
50
+ Loss function used for training.
51
+ optimizer : TOptimizer
52
+ Optimizer used for training.
53
+ epochs : int, default 20
54
+ Number of training epochs.
55
+ batch_size : int, default 64
56
+ Batch size used for training.
57
+ verbose : bool, default True
58
+ Whether to print training progress.
59
+ """
60
+
61
+
62
+ class OODBaseMixin(Generic[TModel], ABC):
63
+ _ref_score: OODScoreOutput
64
+ _threshold_perc: float
65
+ _data_info: tuple[tuple, type] | None = None
66
+
67
+ def __init__(
68
+ self,
69
+ model: TModel,
70
+ ) -> None:
71
+ self.model = model
72
+
73
+ def _get_data_info(self, X: NDArray) -> tuple[tuple, type]:
74
+ if not isinstance(X, np.ndarray):
75
+ raise TypeError("Dataset should of type: `NDArray`.")
76
+ return X.shape[1:], X.dtype.type
77
+
78
+ def _validate(self, X: NDArray) -> None:
79
+ check_data_info = self._get_data_info(X)
80
+ if self._data_info is not None and check_data_info != self._data_info:
81
+ raise RuntimeError(
82
+ f"Expect data of type: {self._data_info[1]} and shape: {self._data_info[0]}. \
83
+ Provided data is type: {check_data_info[1]} and shape: {check_data_info[0]}."
84
+ )
85
+
86
+ def _validate_state(self, X: NDArray) -> None:
87
+ attrs = [k for c in self.__class__.mro()[:-1][::-1] if hasattr(c, "__annotations__") for k in c.__annotations__]
88
+ if not all(hasattr(self, attr) for attr in attrs) or any(getattr(self, attr) for attr in attrs) is None:
89
+ raise RuntimeError("Metric needs to be `fit` before method call.")
90
+ self._validate(X)
91
+
92
+ @abstractmethod
93
+ def _score(self, X: ArrayLike, batch_size: int = int(1e10)) -> OODScoreOutput: ...
94
+
95
+ @set_metadata
96
+ def score(self, X: ArrayLike, batch_size: int = int(1e10)) -> OODScoreOutput:
97
+ """
98
+ Compute the :term:`out of distribution<Out-of-distribution (OOD)>` scores for a given dataset.
99
+
100
+ Parameters
101
+ ----------
102
+ X : ArrayLike
103
+ Input data to score.
104
+ batch_size : int, default 1e10
105
+ Number of instances to process in each batch.
106
+ Use a smaller batch size if your dataset is large or if you encounter memory issues.
107
+
108
+ Returns
109
+ -------
110
+ OODScoreOutput
111
+ An object containing the instance-level and feature-level OOD scores.
112
+ """
113
+ return self._score(X, batch_size)
114
+
115
+ def _threshold_score(self, ood_type: Literal["feature", "instance"] = "instance") -> np.floating:
116
+ return np.percentile(self._ref_score.get(ood_type), self._threshold_perc)
117
+
118
+ @set_metadata
119
+ def predict(
120
+ self,
121
+ X: ArrayLike,
122
+ batch_size: int = int(1e10),
123
+ ood_type: Literal["feature", "instance"] = "instance",
124
+ ) -> OODOutput:
125
+ """
126
+ Predict whether instances are :term:`out of distribution<Out-of-distribution (OOD)>` or not.
127
+
128
+ Parameters
129
+ ----------
130
+ X : ArrayLike
131
+ Input data for out-of-distribution prediction.
132
+ batch_size : int, default 1e10
133
+ Number of instances to process in each batch.
134
+ ood_type : "feature" | "instance", default "instance"
135
+ Predict out-of-distribution at the 'feature' or 'instance' level.
136
+
137
+ Returns
138
+ -------
139
+ Dictionary containing the outlier predictions for the selected level,
140
+ and the OOD scores for the data including both 'instance' and 'feature' (if present) level scores.
141
+ """
142
+ self._validate_state(X := to_numpy(X))
143
+ # compute outlier scores
144
+ score = self.score(X, batch_size=batch_size)
145
+ ood_pred = score.get(ood_type) > self._threshold_score(ood_type)
146
+ return OODOutput(is_ood=ood_pred, **score.dict())
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Literal
7
+
8
+ import numpy as np
9
+ from numpy.typing import NDArray
10
+
11
+ from dataeval.output import Output
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class OODOutput(Output):
16
+ """
17
+ Output class for predictions from out-of-distribution detectors.
18
+
19
+ Attributes
20
+ ----------
21
+ is_ood : NDArray
22
+ Array of images that are detected as :term:Out-of-Distribution (OOD)`
23
+ instance_score : NDArray
24
+ Instance score of the evaluated dataset
25
+ feature_score : NDArray | None
26
+ Feature score, if available, of the evaluated dataset
27
+ """
28
+
29
+ is_ood: NDArray[np.bool_]
30
+ instance_score: NDArray[np.float32]
31
+ feature_score: NDArray[np.float32] | None
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class OODScoreOutput(Output):
36
+ """
37
+ Output class for instance and feature scores from out-of-distribution detectors.
38
+
39
+ Parameters
40
+ ----------
41
+ instance_score : NDArray
42
+ Instance score of the evaluated dataset.
43
+ feature_score : NDArray | None, default None
44
+ Feature score, if available, of the evaluated dataset.
45
+ """
46
+
47
+ instance_score: NDArray[np.float32]
48
+ feature_score: NDArray[np.float32] | None = None
49
+
50
+ def get(self, ood_type: Literal["instance", "feature"]) -> NDArray[np.float32]:
51
+ """
52
+ Returns either the instance or feature score
53
+
54
+ Parameters
55
+ ----------
56
+ ood_type : "instance" | "feature"
57
+
58
+ Returns
59
+ -------
60
+ NDArray
61
+ Either the instance or feature score based on input selection
62
+ """
63
+ return self.instance_score if ood_type == "instance" or self.feature_score is None else self.feature_score
dataeval/interop.py CHANGED
@@ -1,18 +1,19 @@
1
- from __future__ import annotations
2
-
3
- from types import ModuleType
1
+ """Utility functions for interoperability with different array types."""
4
2
 
5
- from dataeval.logging import LogMessage
3
+ from __future__ import annotations
6
4
 
7
- __all__ = ["as_numpy", "to_numpy", "to_numpy_iter"]
5
+ __all__ = []
8
6
 
9
7
  import logging
10
8
  from importlib import import_module
9
+ from types import ModuleType
11
10
  from typing import Any, Iterable, Iterator
12
11
 
13
12
  import numpy as np
14
13
  from numpy.typing import ArrayLike, NDArray
15
14
 
15
+ from dataeval.log import LogMessage
16
+
16
17
  _logger = logging.getLogger(__name__)
17
18
 
18
19
  _MODULE_CACHE = {}
@@ -1,3 +1,5 @@
1
+ __all__ = []
2
+
1
3
  from typing import Callable
2
4
 
3
5
 
@@ -3,6 +3,6 @@ Metrics are a way to measure the performance of your models or datasets that
3
3
  can then be analyzed in the context of a given problem.
4
4
  """
5
5
 
6
- from dataeval.metrics import bias, estimators, stats
7
-
8
6
  __all__ = ["bias", "estimators", "stats"]
7
+
8
+ from dataeval.metrics import bias, estimators, stats