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.
- dataeval/__init__.py +27 -23
- dataeval/detectors/__init__.py +2 -2
- dataeval/detectors/drift/__init__.py +14 -12
- dataeval/detectors/drift/base.py +1 -1
- dataeval/detectors/drift/cvm.py +1 -1
- dataeval/detectors/drift/ks.py +1 -1
- dataeval/detectors/drift/mmd.py +6 -5
- dataeval/detectors/drift/torch.py +12 -12
- dataeval/detectors/drift/uncertainty.py +3 -2
- dataeval/detectors/linters/__init__.py +4 -4
- dataeval/detectors/linters/clusterer.py +2 -7
- dataeval/detectors/linters/duplicates.py +6 -10
- dataeval/detectors/linters/outliers.py +4 -2
- dataeval/detectors/ood/__init__.py +3 -10
- dataeval/detectors/ood/{ae_torch.py → ae.py} +6 -4
- dataeval/detectors/ood/base.py +64 -161
- dataeval/detectors/ood/metadata_ks_compare.py +34 -42
- dataeval/detectors/ood/metadata_least_likely.py +3 -3
- dataeval/detectors/ood/metadata_ood_mi.py +6 -5
- dataeval/detectors/ood/mixin.py +146 -0
- dataeval/detectors/ood/output.py +63 -0
- dataeval/interop.py +6 -5
- dataeval/{logging.py → log.py} +2 -0
- dataeval/metrics/__init__.py +2 -2
- dataeval/metrics/bias/__init__.py +9 -12
- dataeval/metrics/bias/balance.py +10 -8
- dataeval/metrics/bias/coverage.py +52 -4
- dataeval/metrics/bias/diversity.py +42 -14
- dataeval/metrics/bias/parity.py +15 -12
- dataeval/metrics/estimators/__init__.py +2 -2
- dataeval/metrics/estimators/ber.py +3 -1
- dataeval/metrics/estimators/divergence.py +1 -1
- dataeval/metrics/estimators/uap.py +1 -1
- dataeval/metrics/stats/__init__.py +18 -18
- dataeval/metrics/stats/base.py +4 -4
- dataeval/metrics/stats/boxratiostats.py +8 -9
- dataeval/metrics/stats/datasetstats.py +10 -14
- dataeval/metrics/stats/dimensionstats.py +4 -4
- dataeval/metrics/stats/hashstats.py +12 -8
- dataeval/metrics/stats/labelstats.py +5 -5
- dataeval/metrics/stats/pixelstats.py +4 -9
- dataeval/metrics/stats/visualstats.py +4 -9
- dataeval/utils/__init__.py +4 -13
- dataeval/utils/dataset/__init__.py +7 -0
- dataeval/utils/{torch → dataset}/datasets.py +2 -0
- dataeval/utils/dataset/read.py +63 -0
- dataeval/utils/{split_dataset.py → dataset/split.py} +38 -30
- dataeval/utils/image.py +2 -2
- dataeval/utils/metadata.py +310 -5
- dataeval/{metrics/bias/metadata_utils.py → utils/plot.py} +1 -104
- dataeval/utils/torch/__init__.py +2 -17
- dataeval/utils/torch/gmm.py +29 -6
- dataeval/utils/torch/{utils.py → internal.py} +82 -58
- dataeval/utils/torch/models.py +10 -8
- dataeval/utils/torch/trainer.py +6 -85
- dataeval/workflows/__init__.py +2 -5
- dataeval/workflows/sufficiency.py +16 -6
- dataeval-0.75.0.dist-info/METADATA +136 -0
- dataeval-0.75.0.dist-info/RECORD +67 -0
- dataeval/detectors/ood/base_torch.py +0 -109
- dataeval/metrics/bias/metadata_preprocessing.py +0 -285
- dataeval/utils/gmm.py +0 -26
- dataeval-0.74.2.dist-info/METADATA +0 -120
- dataeval-0.74.2.dist-info/RECORD +0 -66
- {dataeval-0.74.2.dist-info → dataeval-0.75.0.dist-info}/LICENSE.txt +0 -0
- {dataeval-0.74.2.dist-info → dataeval-0.75.0.dist-info}/WHEEL +0 -0
dataeval/detectors/ood/base.py
CHANGED
@@ -8,94 +8,30 @@ Licensed under Apache Software License (Apache 2.0)
|
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
|
-
__all__ = [
|
11
|
+
__all__ = []
|
12
12
|
|
13
|
-
from
|
14
|
-
from dataclasses import dataclass
|
15
|
-
from typing import Callable, Generic, Literal, TypeVar
|
13
|
+
from typing import Callable, cast
|
16
14
|
|
17
|
-
import
|
18
|
-
from numpy.typing import ArrayLike
|
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.
|
22
|
-
from dataeval.utils.
|
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
|
83
|
-
|
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:
|
98
|
-
optimizer:
|
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 :
|
48
|
+
loss_fn : Callable | None, default None
|
113
49
|
Loss function used for training.
|
114
|
-
optimizer :
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
"""
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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)
|
75
|
+
>>> md_out = meta_distribution_compare(md0, md1)
|
74
76
|
>>> for k, v in md_out.items():
|
75
|
-
|
76
|
-
|
77
|
-
|
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 =
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
2
|
-
|
3
|
-
from types import ModuleType
|
1
|
+
"""Utility functions for interoperability with different array types."""
|
4
2
|
|
5
|
-
from
|
3
|
+
from __future__ import annotations
|
6
4
|
|
7
|
-
__all__ = [
|
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 = {}
|
dataeval/{logging.py → log.py}
RENAMED
dataeval/metrics/__init__.py
CHANGED
@@ -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
|