dataeval 0.88.0__tar.gz → 0.89.0__tar.gz
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-0.88.0 → dataeval-0.89.0}/PKG-INFO +1 -1
- {dataeval-0.88.0 → dataeval-0.89.0}/pyproject.toml +1 -1
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/_version.py +2 -2
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/_embeddings.py +2 -2
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/_metadata.py +2 -1
- dataeval-0.89.0/src/dataeval/detectors/drift/_base.py +351 -0
- dataeval-0.89.0/src/dataeval/detectors/drift/_cvm.py +105 -0
- dataeval-0.89.0/src/dataeval/detectors/drift/_ks.py +119 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_mmd.py +44 -18
- dataeval-0.89.0/src/dataeval/detectors/drift/_uncertainty.py +242 -0
- dataeval-0.89.0/src/dataeval/outputs/_drift.py +181 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_workflows.py +81 -17
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/typing.py +23 -4
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/workflows/sufficiency.py +1 -2
- dataeval-0.88.0/src/dataeval/detectors/drift/_base.py +0 -226
- dataeval-0.88.0/src/dataeval/detectors/drift/_cvm.py +0 -86
- dataeval-0.88.0/src/dataeval/detectors/drift/_ks.py +0 -91
- dataeval-0.88.0/src/dataeval/detectors/drift/_uncertainty.py +0 -168
- dataeval-0.88.0/src/dataeval/outputs/_drift.py +0 -143
- {dataeval-0.88.0 → dataeval-0.89.0}/.gitignore +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/LICENSE +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/README.md +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/_log.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/config.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/_images.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/_selection.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/_split.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_classbalance.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_classfilter.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_indices.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_limit.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_prioritize.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_reverse.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/data/selections/_shuffle.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_mvdc.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_base.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_chunk.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_domainclassifier.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_result.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_thresholds.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/drift/updates.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/linters/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/linters/duplicates.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/linters/outliers.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/ood/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/ood/ae.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/ood/base.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/ood/knn.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/detectors/ood/mixin.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metadata/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metadata/_distance.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metadata/_ood.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metadata/_utils.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/_balance.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/_completeness.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/_coverage.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/_diversity.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/bias/_parity.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/estimators/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_ber.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_clusterer.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_divergence.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_uap.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_base.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_boxratiostats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_dimensionstats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_hashstats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_imagestats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_labelstats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_pixelstats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/metrics/stats/_visualstats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_base.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_bias.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_estimators.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_linters.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_metadata.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_ood.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_stats.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/outputs/_utils.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/py.typed +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_array.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_bin.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_clusterer.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_fast_mst.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_image.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_method.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_mst.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_multiprocessing.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/_plot.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/data/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/data/_merge.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/data/_validate.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/data/collate.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/__init__.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/_blocks.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/_gmm.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/_internal.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/models.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/utils/torch/trainer.py +0 -0
- {dataeval-0.88.0 → dataeval-0.89.0}/src/dataeval/workflows/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dataeval
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.89.0
|
4
4
|
Summary: DataEval provides a simple interface to characterize image data and its impact on model performance across classification and object-detection tasks
|
5
5
|
Project-URL: Homepage, https://dataeval.ai/
|
6
6
|
Project-URL: Repository, https://github.com/aria-ml/dataeval/
|
@@ -252,7 +252,7 @@ docstring-code-line-length = "dynamic"
|
|
252
252
|
|
253
253
|
[tool.codespell]
|
254
254
|
skip = './*env*,./output,./docs/build,./docs/source/.jupyter_cache,CHANGELOG.md,uv.lock,requirements.txt,*.html,./docs/source/*/data'
|
255
|
-
ignore-words-list = ["Hart"]
|
255
|
+
ignore-words-list = ["Hart","FPR"]
|
256
256
|
|
257
257
|
[build-system]
|
258
258
|
requires = ["hatchling", "hatch-vcs"]
|
@@ -5,7 +5,7 @@ __all__ = []
|
|
5
5
|
import logging
|
6
6
|
import math
|
7
7
|
import os
|
8
|
-
from collections.abc import Iterator, Sequence
|
8
|
+
from collections.abc import Iterable, Iterator, Sequence
|
9
9
|
from pathlib import Path
|
10
10
|
from typing import Any, cast
|
11
11
|
|
@@ -80,7 +80,7 @@ class Embeddings:
|
|
80
80
|
# Technically more permissive than ImageClassificationDataset or ObjectDetectionDataset
|
81
81
|
dataset: Dataset[tuple[ArrayLike, Any, Any]] | Dataset[ArrayLike],
|
82
82
|
batch_size: int,
|
83
|
-
transforms: Transform[torch.Tensor] |
|
83
|
+
transforms: Transform[torch.Tensor] | Iterable[Transform[torch.Tensor]] | None = None,
|
84
84
|
model: torch.nn.Module | None = None,
|
85
85
|
device: DeviceLike | None = None,
|
86
86
|
cache: Path | str | bool = False,
|
@@ -15,6 +15,7 @@ from tqdm.auto import tqdm
|
|
15
15
|
from dataeval.typing import (
|
16
16
|
AnnotatedDataset,
|
17
17
|
Array,
|
18
|
+
DatumMetadata,
|
18
19
|
ObjectDetectionTarget,
|
19
20
|
)
|
20
21
|
from dataeval.utils._array import as_numpy
|
@@ -76,7 +77,7 @@ class Metadata:
|
|
76
77
|
|
77
78
|
def __init__(
|
78
79
|
self,
|
79
|
-
dataset: AnnotatedDataset[tuple[Any, Any,
|
80
|
+
dataset: AnnotatedDataset[tuple[Any, Any, DatumMetadata]],
|
80
81
|
*,
|
81
82
|
continuous_factor_bins: Mapping[str, int | Sequence[float]] | None = None,
|
82
83
|
auto_bin_method: Literal["uniform_width", "uniform_count", "clusters"] = "uniform_width",
|
@@ -0,0 +1,351 @@
|
|
1
|
+
"""
|
2
|
+
Source code derived from Alibi-Detect 0.11.4
|
3
|
+
https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
|
4
|
+
|
5
|
+
Original code Copyright (c) 2023 Seldon Technologies Ltd
|
6
|
+
Licensed under Apache Software License (Apache 2.0)
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
__all__ = []
|
12
|
+
|
13
|
+
import math
|
14
|
+
from abc import abstractmethod
|
15
|
+
from collections.abc import Callable
|
16
|
+
from functools import wraps
|
17
|
+
from typing import Any, Literal, Protocol, TypeVar, runtime_checkable
|
18
|
+
|
19
|
+
import numpy as np
|
20
|
+
from numpy.typing import NDArray
|
21
|
+
|
22
|
+
from dataeval.data import Embeddings
|
23
|
+
from dataeval.outputs import DriftOutput
|
24
|
+
from dataeval.outputs._base import set_metadata
|
25
|
+
from dataeval.typing import Array
|
26
|
+
from dataeval.utils._array import as_numpy, flatten
|
27
|
+
|
28
|
+
R = TypeVar("R")
|
29
|
+
|
30
|
+
|
31
|
+
@runtime_checkable
|
32
|
+
class UpdateStrategy(Protocol):
|
33
|
+
"""
|
34
|
+
Protocol for reference dataset update strategy for drift detectors
|
35
|
+
"""
|
36
|
+
|
37
|
+
def __call__(self, x_ref: NDArray[np.float32], x_new: NDArray[np.float32], count: int) -> NDArray[np.float32]: ...
|
38
|
+
|
39
|
+
|
40
|
+
def update_strategy(fn: Callable[..., R]) -> Callable[..., R]:
|
41
|
+
"""Decorator to update x_ref with x using selected update methodology"""
|
42
|
+
|
43
|
+
@wraps(fn)
|
44
|
+
def _(self: BaseDrift, data: Embeddings | Array, *args: tuple[Any, ...], **kwargs: dict[str, Any]) -> R:
|
45
|
+
output = fn(self, data, *args, **kwargs)
|
46
|
+
|
47
|
+
# update reference dataset
|
48
|
+
if self.update_strategy is not None:
|
49
|
+
self._x_ref = self.update_strategy(self.x_ref, self._encode(data), self.n)
|
50
|
+
self.n += len(data)
|
51
|
+
|
52
|
+
return output
|
53
|
+
|
54
|
+
return _
|
55
|
+
|
56
|
+
|
57
|
+
class BaseDrift:
|
58
|
+
"""Base class for drift detection algorithms.
|
59
|
+
|
60
|
+
Provides common functionality for drift detectors including reference data
|
61
|
+
management, encoding of input data, and statistical correction methods.
|
62
|
+
Subclasses implement specific drift detection algorithms.
|
63
|
+
|
64
|
+
Parameters
|
65
|
+
----------
|
66
|
+
data : Embeddings or Array
|
67
|
+
Reference dataset used as baseline for drift detection.
|
68
|
+
Can be image embeddings or raw arrays.
|
69
|
+
p_val : float, default 0.05
|
70
|
+
Significance threshold for drift detection, between 0 and 1.
|
71
|
+
Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
|
72
|
+
update_strategy : UpdateStrategy or None, default None
|
73
|
+
Strategy for updating reference data when new data arrives.
|
74
|
+
When None, reference data remains fixed throughout detection.
|
75
|
+
Default None maintains stable baseline for consistent comparison.
|
76
|
+
correction : {"bonferroni", "fdr"}, default "bonferroni"
|
77
|
+
Multiple testing correction method for multivariate drift detection.
|
78
|
+
"bonferroni" provides conservative family-wise error control.
|
79
|
+
"fdr" (False Discovery Rate) offers less conservative control.
|
80
|
+
Default "bonferroni" minimizes false positive drift detections.
|
81
|
+
|
82
|
+
Attributes
|
83
|
+
----------
|
84
|
+
p_val : float
|
85
|
+
Significance threshold for statistical tests.
|
86
|
+
update_strategy : UpdateStrategy or None
|
87
|
+
Reference data update strategy.
|
88
|
+
correction : {"bonferroni", "fdr"}
|
89
|
+
Multiple testing correction method.
|
90
|
+
n : int
|
91
|
+
Number of samples in the reference dataset.
|
92
|
+
"""
|
93
|
+
|
94
|
+
p_val: float
|
95
|
+
update_strategy: UpdateStrategy | None
|
96
|
+
correction: Literal["bonferroni", "fdr"]
|
97
|
+
n: int
|
98
|
+
|
99
|
+
def __init__(
|
100
|
+
self,
|
101
|
+
data: Embeddings | Array,
|
102
|
+
p_val: float = 0.05,
|
103
|
+
update_strategy: UpdateStrategy | None = None,
|
104
|
+
correction: Literal["bonferroni", "fdr"] = "bonferroni",
|
105
|
+
) -> None:
|
106
|
+
# Type checking
|
107
|
+
if update_strategy is not None and not isinstance(update_strategy, UpdateStrategy):
|
108
|
+
raise ValueError("`update_strategy` is not a valid UpdateStrategy class.")
|
109
|
+
if correction not in ["bonferroni", "fdr"]:
|
110
|
+
raise ValueError("`correction` must be `bonferroni` or `fdr`.")
|
111
|
+
|
112
|
+
self._data = data
|
113
|
+
self.p_val = p_val
|
114
|
+
self.update_strategy = update_strategy
|
115
|
+
self.correction = correction
|
116
|
+
self.n = len(data)
|
117
|
+
|
118
|
+
self._x_ref: NDArray[np.float32] | None = None
|
119
|
+
|
120
|
+
@property
|
121
|
+
def x_ref(self) -> NDArray[np.float32]:
|
122
|
+
"""Reference data for drift detection.
|
123
|
+
|
124
|
+
Lazily encodes the reference dataset on first access.
|
125
|
+
Data is flattened and converted to 32-bit floating point for
|
126
|
+
consistent numerical processing across different input types.
|
127
|
+
|
128
|
+
Returns
|
129
|
+
-------
|
130
|
+
NDArray[np.float32]
|
131
|
+
Reference data as flattened 32-bit floating point array.
|
132
|
+
Shape is (n_samples, n_features_flattened).
|
133
|
+
|
134
|
+
Notes
|
135
|
+
-----
|
136
|
+
Data is cached after first access to avoid repeated encoding overhead.
|
137
|
+
"""
|
138
|
+
if self._x_ref is None:
|
139
|
+
self._x_ref = self._encode(self._data)
|
140
|
+
return self._x_ref
|
141
|
+
|
142
|
+
def _encode(self, data: Embeddings | Array) -> NDArray[np.float32]:
|
143
|
+
"""
|
144
|
+
Encode input data to consistent numpy format.
|
145
|
+
|
146
|
+
Handles different input types (Embeddings, Arrays) and converts
|
147
|
+
them to flattened 32-bit floating point arrays for drift detection.
|
148
|
+
|
149
|
+
Parameters
|
150
|
+
----------
|
151
|
+
data : Embeddings or Array
|
152
|
+
Input data to encode.
|
153
|
+
|
154
|
+
Returns
|
155
|
+
-------
|
156
|
+
NDArray[np.float32]
|
157
|
+
Encoded data as flattened 32-bit floating point array.
|
158
|
+
"""
|
159
|
+
array = (
|
160
|
+
data.to_numpy().astype(np.float32)
|
161
|
+
if isinstance(data, Embeddings)
|
162
|
+
else self._data.new(data).to_numpy().astype(np.float32)
|
163
|
+
if isinstance(self._data, Embeddings)
|
164
|
+
else as_numpy(data).astype(np.float32)
|
165
|
+
)
|
166
|
+
return flatten(array)
|
167
|
+
|
168
|
+
|
169
|
+
class BaseDriftUnivariate(BaseDrift):
|
170
|
+
"""
|
171
|
+
Base class for univariate drift detection algorithms.
|
172
|
+
|
173
|
+
Extends BaseDrift with feature-wise drift detection capabilities.
|
174
|
+
Applies statistical tests independently to each feature (pixel) and
|
175
|
+
uses multiple testing correction to control false discovery rates.
|
176
|
+
|
177
|
+
Parameters
|
178
|
+
----------
|
179
|
+
data : Embeddings or Array
|
180
|
+
Reference dataset used as baseline for drift detection.
|
181
|
+
p_val : float, default 0.05
|
182
|
+
Significance threshold for drift detection, between 0 and 1.
|
183
|
+
Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
|
184
|
+
update_strategy : UpdateStrategy or None, default None
|
185
|
+
Strategy for updating reference data when new data arrives.
|
186
|
+
When None, reference data remains fixed throughout detection.
|
187
|
+
Default None maintains stable baseline for consistent comparison.
|
188
|
+
correction : {"bonferroni", "fdr"}, default "bonferroni"
|
189
|
+
Multiple testing correction method for controlling false positives
|
190
|
+
across multiple features. "bonferroni" divides significance level
|
191
|
+
by number of features. "fdr" uses Benjamini-Hochberg procedure.
|
192
|
+
Default "bonferroni" provides conservative family-wise error control.
|
193
|
+
n_features : int or None, default None
|
194
|
+
Number of features to analyze. When None, automatically inferred
|
195
|
+
from the first sample's flattened shape. Default None enables
|
196
|
+
automatic feature detection for flexible input handling.
|
197
|
+
|
198
|
+
Attributes
|
199
|
+
----------
|
200
|
+
p_val : float
|
201
|
+
Significance threshold for statistical tests.
|
202
|
+
update_strategy : UpdateStrategy or None
|
203
|
+
Reference data update strategy.
|
204
|
+
correction : {"bonferroni", "fdr"}
|
205
|
+
Multiple testing correction method.
|
206
|
+
n : int
|
207
|
+
Number of samples in the reference dataset.
|
208
|
+
"""
|
209
|
+
|
210
|
+
def __init__(
|
211
|
+
self,
|
212
|
+
data: Embeddings | Array,
|
213
|
+
p_val: float = 0.05,
|
214
|
+
update_strategy: UpdateStrategy | None = None,
|
215
|
+
correction: Literal["bonferroni", "fdr"] = "bonferroni",
|
216
|
+
n_features: int | None = None,
|
217
|
+
) -> None:
|
218
|
+
super().__init__(data, p_val, update_strategy, correction)
|
219
|
+
|
220
|
+
self._n_features = n_features
|
221
|
+
|
222
|
+
@property
|
223
|
+
def n_features(self) -> int:
|
224
|
+
"""Number of features in the reference data.
|
225
|
+
|
226
|
+
Lazily computes the number of features from the first data sample
|
227
|
+
if not provided during initialization. Features correspond to the
|
228
|
+
flattened dimensionality of the input data (e.g., pixels for images).
|
229
|
+
|
230
|
+
Returns
|
231
|
+
-------
|
232
|
+
int
|
233
|
+
Number of features (flattened dimensions) in the reference data.
|
234
|
+
Always > 0 for valid datasets.
|
235
|
+
|
236
|
+
Notes
|
237
|
+
-----
|
238
|
+
For image data, this equals C x H x W.
|
239
|
+
Computed once and cached for efficiency.
|
240
|
+
"""
|
241
|
+
# lazy process n_features as needed
|
242
|
+
if self._n_features is None:
|
243
|
+
self._n_features = int(math.prod(self._data[0].shape))
|
244
|
+
|
245
|
+
return self._n_features
|
246
|
+
|
247
|
+
def score(self, data: Embeddings | Array) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
248
|
+
"""Calculate feature-wise p-values and test statistics.
|
249
|
+
|
250
|
+
Applies the detector's statistical test independently to each feature,
|
251
|
+
comparing the distribution of each feature between reference and test data.
|
252
|
+
|
253
|
+
Parameters
|
254
|
+
----------
|
255
|
+
data : Embeddings or Array
|
256
|
+
Test dataset to compare against reference data.
|
257
|
+
|
258
|
+
Returns
|
259
|
+
-------
|
260
|
+
tuple[NDArray[np.float32], NDArray[np.float32]]
|
261
|
+
First array contains p-values for each feature (all between 0 and 1).
|
262
|
+
Second array contains test statistics for each feature (all >= 0).
|
263
|
+
Both arrays have shape (n_features,).
|
264
|
+
|
265
|
+
Notes
|
266
|
+
-----
|
267
|
+
Lower p-values indicate stronger evidence of drift for that feature.
|
268
|
+
Higher test statistics indicate greater distributional differences.
|
269
|
+
"""
|
270
|
+
x_np = self._encode(data)
|
271
|
+
p_val = np.zeros(self.n_features, dtype=np.float32)
|
272
|
+
dist = np.zeros_like(p_val)
|
273
|
+
for f in range(self.n_features):
|
274
|
+
dist[f], p_val[f] = self._score_fn(self.x_ref[:, f], x_np[:, f])
|
275
|
+
return p_val, dist
|
276
|
+
|
277
|
+
@abstractmethod
|
278
|
+
def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]: ...
|
279
|
+
|
280
|
+
def _apply_correction(self, p_vals: NDArray[np.float32]) -> tuple[bool, float]:
|
281
|
+
"""
|
282
|
+
Apply multiple testing correction to feature-wise p-values.
|
283
|
+
|
284
|
+
Corrects for multiple comparisons across features to control
|
285
|
+
false positive rates. Bonferroni correction divides the significance
|
286
|
+
threshold by the number of features. FDR correction uses the
|
287
|
+
Benjamini-Hochberg procedure for less conservative control.
|
288
|
+
|
289
|
+
Parameters
|
290
|
+
----------
|
291
|
+
p_vals : NDArray[np.float32]
|
292
|
+
Array of p-values from univariate tests for each feature.
|
293
|
+
All values should be between 0 and 1.
|
294
|
+
|
295
|
+
Returns
|
296
|
+
-------
|
297
|
+
tuple[bool, float]
|
298
|
+
Boolean indicating whether drift was detected after correction.
|
299
|
+
Float is the effective threshold used for detection.
|
300
|
+
|
301
|
+
Notes
|
302
|
+
-----
|
303
|
+
Bonferroni correction: threshold = p_val / n_features
|
304
|
+
FDR correction: Uses Benjamini-Hochberg step-up procedure
|
305
|
+
"""
|
306
|
+
if self.correction == "bonferroni":
|
307
|
+
threshold = self.p_val / self.n_features
|
308
|
+
drift_pred = bool((p_vals < threshold).any())
|
309
|
+
return drift_pred, threshold
|
310
|
+
if self.correction == "fdr":
|
311
|
+
n = p_vals.shape[0]
|
312
|
+
i = np.arange(n) + np.int_(1)
|
313
|
+
p_sorted = np.sort(p_vals)
|
314
|
+
q_threshold = self.p_val * i / n
|
315
|
+
below_threshold = p_sorted < q_threshold
|
316
|
+
try:
|
317
|
+
idx_threshold = int(np.where(below_threshold)[0].max())
|
318
|
+
except ValueError: # sorted p-values not below thresholds
|
319
|
+
return bool(below_threshold.any()), q_threshold.min()
|
320
|
+
return bool(below_threshold.any()), q_threshold[idx_threshold]
|
321
|
+
raise ValueError("`correction` needs to be either `bonferroni` or `fdr`.")
|
322
|
+
|
323
|
+
@set_metadata
|
324
|
+
@update_strategy
|
325
|
+
def predict(self, data: Embeddings | Array) -> DriftOutput:
|
326
|
+
"""Predict drift and update reference data using specified strategy.
|
327
|
+
|
328
|
+
Performs feature-wise drift detection, applies multiple testing
|
329
|
+
correction, and optionally updates the reference dataset based
|
330
|
+
on the configured update strategy.
|
331
|
+
|
332
|
+
Parameters
|
333
|
+
----------
|
334
|
+
data : Embeddings or Array
|
335
|
+
Test dataset to analyze for drift against reference data.
|
336
|
+
|
337
|
+
Returns
|
338
|
+
-------
|
339
|
+
DriftOutput
|
340
|
+
Complete drift detection results including overall :term:`drift<Drift>` prediction,
|
341
|
+
corrected thresholds, feature-level analysis, and summary :term:`statistics<Statistics>`.
|
342
|
+
"""
|
343
|
+
|
344
|
+
# compute drift scores
|
345
|
+
p_vals, dist = self.score(data)
|
346
|
+
|
347
|
+
feature_drift = (p_vals < self.p_val).astype(np.bool_)
|
348
|
+
drift_pred, threshold = self._apply_correction(p_vals)
|
349
|
+
return DriftOutput(
|
350
|
+
drift_pred, threshold, float(np.mean(p_vals)), float(np.mean(dist)), feature_drift, self.p_val, p_vals, dist
|
351
|
+
)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
"""
|
2
|
+
Source code derived from Alibi-Detect 0.11.4
|
3
|
+
https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
|
4
|
+
|
5
|
+
Original code Copyright (c) 2023 Seldon Technologies Ltd
|
6
|
+
Licensed under Apache Software License (Apache 2.0)
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
__all__ = []
|
12
|
+
|
13
|
+
from typing import Literal
|
14
|
+
|
15
|
+
import numpy as np
|
16
|
+
from numpy.typing import NDArray
|
17
|
+
from scipy.stats import cramervonmises_2samp
|
18
|
+
|
19
|
+
from dataeval.data._embeddings import Embeddings
|
20
|
+
from dataeval.detectors.drift._base import BaseDriftUnivariate, UpdateStrategy
|
21
|
+
from dataeval.typing import Array
|
22
|
+
|
23
|
+
|
24
|
+
class DriftCVM(BaseDriftUnivariate):
|
25
|
+
""":term:`Drift` detector using the :term:`Cramér-von Mises (CVM) Test`.
|
26
|
+
|
27
|
+
Detects distributional changes in continuous data by comparing empirical
|
28
|
+
cumulative distribution functions between reference and test datasets.
|
29
|
+
For multivariate data, applies CVM test independently to each feature
|
30
|
+
and aggregates results using either the Bonferroni or
|
31
|
+
:term:`False Discovery Rate (FDR)` correction.
|
32
|
+
|
33
|
+
The CVM test is particularly effective at detecting subtle
|
34
|
+
distributional shifts throughout the entire domain, providing higher
|
35
|
+
power than Kolmogorov-Smirnov for many types of drift.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
data : Embeddings or Array
|
40
|
+
Reference dataset used as baseline distribution for drift detection.
|
41
|
+
Should represent the expected data distribution.
|
42
|
+
p_val : float, default 0.05
|
43
|
+
Significance threshold for drift detection, between 0 and 1.
|
44
|
+
Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
|
45
|
+
update_strategy : UpdateStrategy or None, default None
|
46
|
+
Strategy for updating reference data when new data arrives.
|
47
|
+
When None, reference data remains fixed throughout detection.
|
48
|
+
correction : "bonferroni" or "fdr", default "bonferroni"
|
49
|
+
Multiple testing correction method for multivariate drift detection.
|
50
|
+
"bonferroni" provides conservative family-wise error control by
|
51
|
+
dividing significance threshold by number of features.
|
52
|
+
"fdr" uses Benjamini-Hochberg procedure for less conservative control.
|
53
|
+
Default "bonferroni" minimizes false positive drift detections.
|
54
|
+
n_features : int or None, default None
|
55
|
+
Number of features to analyze in univariate tests.
|
56
|
+
When None, automatically inferred from the flattened shape of first data sample.
|
57
|
+
|
58
|
+
Example
|
59
|
+
-------
|
60
|
+
Basic drift detection with image embeddings
|
61
|
+
|
62
|
+
>>> from dataeval.data import Embeddings
|
63
|
+
>>> train_emb = Embeddings(train_images, model=encoder, batch_size=64)
|
64
|
+
>>> drift_detector = DriftCVM(train_emb)
|
65
|
+
|
66
|
+
Test incoming images for distributional drift
|
67
|
+
|
68
|
+
>>> result = drift_detector.predict(test_images)
|
69
|
+
>>> print(f"Drift detected: {result.drifted}")
|
70
|
+
Drift detected: True
|
71
|
+
|
72
|
+
>>> print(f"Mean CVM statistic: {result.distance:.4f}")
|
73
|
+
Mean CVM statistic: 24.1325
|
74
|
+
|
75
|
+
Using different correction methods
|
76
|
+
|
77
|
+
>>> drift_fdr = DriftCVM(train_emb, correction="fdr", p_val=0.1)
|
78
|
+
>>> result = drift_fdr.predict(test_images)
|
79
|
+
|
80
|
+
Access feature level results
|
81
|
+
|
82
|
+
>>> n_features = result.feature_drift
|
83
|
+
>>> print(f"Features showing drift: {n_features.sum()} / {len(n_features)}")
|
84
|
+
Features showing drift: 576 / 576
|
85
|
+
"""
|
86
|
+
|
87
|
+
def __init__(
|
88
|
+
self,
|
89
|
+
data: Embeddings | Array,
|
90
|
+
p_val: float = 0.05,
|
91
|
+
update_strategy: UpdateStrategy | None = None,
|
92
|
+
correction: Literal["bonferroni", "fdr"] = "bonferroni",
|
93
|
+
n_features: int | None = None,
|
94
|
+
) -> None:
|
95
|
+
super().__init__(
|
96
|
+
data=data,
|
97
|
+
p_val=p_val,
|
98
|
+
update_strategy=update_strategy,
|
99
|
+
correction=correction,
|
100
|
+
n_features=n_features,
|
101
|
+
)
|
102
|
+
|
103
|
+
def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]:
|
104
|
+
result = cramervonmises_2samp(x, y, method="auto")
|
105
|
+
return np.float32(result.statistic), np.float32(result.pvalue)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
"""
|
2
|
+
Source code derived from Alibi-Detect 0.11.4
|
3
|
+
https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
|
4
|
+
|
5
|
+
Original code Copyright (c) 2023 Seldon Technologies Ltd
|
6
|
+
Licensed under Apache Software License (Apache 2.0)
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
__all__ = []
|
12
|
+
|
13
|
+
from typing import Literal
|
14
|
+
|
15
|
+
import numpy as np
|
16
|
+
from numpy.typing import NDArray
|
17
|
+
from scipy.stats import ks_2samp
|
18
|
+
|
19
|
+
from dataeval.data._embeddings import Embeddings
|
20
|
+
from dataeval.detectors.drift._base import BaseDriftUnivariate, UpdateStrategy
|
21
|
+
from dataeval.typing import Array
|
22
|
+
|
23
|
+
|
24
|
+
class DriftKS(BaseDriftUnivariate):
|
25
|
+
""":term:`Drift` detector employing the :term:`Kolmogorov-Smirnov (KS) \
|
26
|
+
distribution<Kolmogorov-Smirnov (K-S) test>` test.
|
27
|
+
|
28
|
+
Detects distributional changes by measuring the maximum distance between
|
29
|
+
empirical cumulative distribution functions of reference and test datasets.
|
30
|
+
For multivariate data, applies KS test independently to each feature
|
31
|
+
and aggregates results using multiple testing correction.
|
32
|
+
|
33
|
+
The Kolmogorov-Smirnov test is particularly sensitive to differences in
|
34
|
+
the middle portions of distributions but has reduced power in the tails
|
35
|
+
where cumulative distribution functions are constrained near 0 and 1.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
data : Embeddings or Array
|
40
|
+
Reference dataset used as baseline distribution for drift detection.
|
41
|
+
Should represent the expected data distribution.
|
42
|
+
p_val : float, default 0.05
|
43
|
+
Significance threshold for drift detection, between 0 and 1.
|
44
|
+
Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
|
45
|
+
update_strategy : UpdateStrategy or None, default None
|
46
|
+
Strategy for updating reference data when new data arrives.
|
47
|
+
When None, reference data remains fixed throughout detection.
|
48
|
+
correction : "bonferroni" or "fdr", default "bonferroni"
|
49
|
+
Multiple testing correction method for multivariate drift detection.
|
50
|
+
"bonferroni" provides conservative family-wise error control by
|
51
|
+
dividing significance threshold by number of features.
|
52
|
+
"fdr" uses Benjamini-Hochberg procedure for less conservative control.
|
53
|
+
Default "bonferroni" minimizes false positive drift detections.
|
54
|
+
alternative : "two-sided", "less" or "greater", default "two-sided"
|
55
|
+
Alternative hypothesis for the statistical test. "two-sided" detects
|
56
|
+
any distributional difference. "less" tests if test distribution is
|
57
|
+
stochastically smaller. "greater" tests if test distribution is
|
58
|
+
stochastically larger. Default "two-sided" provides most general
|
59
|
+
drift detection without directional assumptions.
|
60
|
+
n_features : int | None, default None
|
61
|
+
Number of features to analyze in univariate tests.
|
62
|
+
When None, automatically inferred from the flattened shape of first data sample.
|
63
|
+
|
64
|
+
Example
|
65
|
+
-------
|
66
|
+
Basic drift detection with image embeddings:
|
67
|
+
|
68
|
+
>>> from dataeval.data import Embeddings
|
69
|
+
>>> train_emb = Embeddings(train_images, model=encoder, batch_size=64)
|
70
|
+
>>> drift_detector = DriftKS(train_emb)
|
71
|
+
|
72
|
+
Test incoming images for distributional drift
|
73
|
+
|
74
|
+
>>> result = drift_detector.predict(test_images)
|
75
|
+
>>> print(f"Drift detected: {result.drifted}")
|
76
|
+
Drift detected: True
|
77
|
+
|
78
|
+
>>> print(f"Mean KS statistic: {result.distance:.4f}")
|
79
|
+
Mean KS statistic: 0.8750
|
80
|
+
|
81
|
+
Detect if test data has systematically higher values
|
82
|
+
|
83
|
+
>>> drift_greater = DriftKS(train_emb, alternative="greater")
|
84
|
+
>>> result = drift_greater.predict(test_images)
|
85
|
+
|
86
|
+
Using different correction methods
|
87
|
+
|
88
|
+
>>> drift_fdr = DriftKS(train_emb, correction="fdr", p_val=0.1)
|
89
|
+
>>> result = drift_fdr.predict(test_images)
|
90
|
+
|
91
|
+
Access feature-level results
|
92
|
+
|
93
|
+
>>> n_features = result.feature_drift
|
94
|
+
>>> print(f"Features showing drift: {n_features.sum()} / {len(n_features)}")
|
95
|
+
Features showing drift: 576 / 576
|
96
|
+
"""
|
97
|
+
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
data: Embeddings | Array,
|
101
|
+
p_val: float = 0.05,
|
102
|
+
update_strategy: UpdateStrategy | None = None,
|
103
|
+
correction: Literal["bonferroni", "fdr"] = "bonferroni",
|
104
|
+
alternative: Literal["two-sided", "less", "greater"] = "two-sided",
|
105
|
+
n_features: int | None = None,
|
106
|
+
) -> None:
|
107
|
+
super().__init__(
|
108
|
+
data=data,
|
109
|
+
p_val=p_val,
|
110
|
+
update_strategy=update_strategy,
|
111
|
+
correction=correction,
|
112
|
+
n_features=n_features,
|
113
|
+
)
|
114
|
+
|
115
|
+
# Other attributes
|
116
|
+
self.alternative = alternative
|
117
|
+
|
118
|
+
def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]:
|
119
|
+
return ks_2samp(x, y, alternative=self.alternative, method="exact")
|