Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add more powerful drift windowing strategies, warmup and dynamic thresholds #564

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9d71dfb
Add more powerful windowing strategies, warumup and dynamic thresholds
robinholzi Jul 4, 2024
244da67
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Jul 4, 2024
85ed4f5
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Jul 22, 2024
4ca50ed
Add tests
robinholzi Jul 24, 2024
91e0dda
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Jul 24, 2024
28dbb9f
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Jul 26, 2024
6b22b6d
fix linting
robinholzi Jul 26, 2024
0d42176
fix linting
robinholzi Jul 26, 2024
0028e1b
Implement suggestions, v1
robinholzi Aug 7, 2024
9f3b70f
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Aug 7, 2024
bbacf1e
Integrate suggestions, rename things, more tests, documentation
robinholzi Aug 8, 2024
44f29d1
Fix
robinholzi Aug 8, 2024
836af4f
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Aug 12, 2024
f950c25
Fix
robinholzi Aug 12, 2024
8912476
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Aug 12, 2024
c9091aa
Tests and adjustments to warmup
robinholzi Aug 12, 2024
4792fd4
Integrate suggestions, rename things
robinholzi Aug 12, 2024
ed0baaa
fix
robinholzi Aug 12, 2024
ccb6ba3
Fix
robinholzi Aug 13, 2024
46f52d3
Move averaging logic into detector
robinholzi Aug 13, 2024
a2392ad
Merge branch 'main' into robinholzi/feat/more-powerful-drift-windows-…
robinholzi Aug 13, 2024
ea036c1
Final adjustments
robinholzi Aug 14, 2024
9594453
Small fix wrt interval tests
robinholzi Aug 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion benchmark/huffpost_kaggle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ In this directory, you can find the files necessary to run experiments using the
The goal is to predict the tag of news given headlines.
The dataset contains more than 60k samples collected from 2012 to 2018.
Titles belonging to the same year are grouped into the same CSV file and stored together.
Each year is mapped to a year starting from 1/1/1970.
There is a total of 42 categories/classes.

> Note: The wild-time variant of the huffpost dataset has only 11 classes. This is due to the fact that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ trigger:
metrics:
ev_mmd:
id: EvidentlyModelDriftMetric
threshold: 0.7
decision_criterion:
id: ThresholdDecisionCriterion
threshold: 0.7
aggregation_strategy:
id: MajorityVote
selection_strategy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ trigger:
ev_mmd:
id: AlibiDetectMmdDriftMetric
num_permutations: 1000
decision_criterion:
id: HypothesisTestDecisionCriterion
aggregation_strategy:
id: MajorityVote
selection_strategy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ trigger:
metrics:
ev_mmd:
id: EvidentlyModelDriftMetric
threshold: 0.7
decision_criterion:
id: ThresholdDecisionCriterion
threshold: 0.7
aggregation_strategy:
id: MajorityVote
selection_strategy:
Expand Down
1 change: 1 addition & 0 deletions modyn/config/schema/pipeline/trigger/drift/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .aggregation import * # noqa
from .alibi_detect import * # noqa
from .config import * # noqa
from .detection_window import * # noqa
from .evidently import * # noqa
from .result import * # noqa
3 changes: 2 additions & 1 deletion modyn/config/schema/pipeline/trigger/drift/alibi_detect.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Annotated, Literal, Union

from modyn.config.schema.base_model import ModynBaseModel
from modyn.config.schema.pipeline.trigger.drift.metric import BaseMetric
from pydantic import Field, model_validator


class _AlibiDetectBaseDriftMetric(ModynBaseModel):
class _AlibiDetectBaseDriftMetric(BaseMetric):
p_val: float = Field(0.05, description="The p-value threshold for the drift detection.")
x_ref_preprocessed: bool = Field(False)

Expand Down
44 changes: 9 additions & 35 deletions modyn/config/schema/pipeline/trigger/drift/config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from __future__ import annotations

from functools import cached_property
from typing import Annotated, ForwardRef, Literal, Optional, Union

from modyn.config.schema.base_model import ModynBaseModel
from modyn.const.regex import REGEX_TIME_UNIT
from modyn.utils.utils import SECONDS_PER_UNIT
from modyn.config.schema.pipeline.trigger.drift.detection_window import AmountWindowingStrategy, DriftWindowingStrategy
from pydantic import Field

from .aggregation import DriftAggregationStrategy, MajorityVoteDriftAggregationStrategy
Expand All @@ -23,50 +21,26 @@
]


class AmountWindowingStrategy(ModynBaseModel):
id: Literal["AmountWindowingStrategy"] = Field("AmountWindowingStrategy")
amount: int = Field(1000, description="How many data points should fit in the window")


class TimeWindowingStrategy(ModynBaseModel):
id: Literal["TimeWindowingStrategy"] = Field("TimeWindowingStrategy")
limit: str = Field(
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)

@cached_property
def limit_seconds(self) -> int:
unit = str(self.limit)[-1:]
num = int(str(self.limit)[:-1])
return num * SECONDS_PER_UNIT[unit]


DriftWindowingStrategy = Annotated[
Union[
AmountWindowingStrategy,
TimeWindowingStrategy,
],
Field(discriminator="id"),
]


class DataDriftTriggerConfig(ModynBaseModel):
id: Literal["DataDriftTrigger"] = Field("DataDriftTrigger")

detection_interval: Optional[__TriggerConfig] = Field( # type: ignore[valid-type]
None, description="The Trigger policy to determine the interval at which drift detection is performed."
) # currently not used

detection_interval_data_points: int = Field(
1000, description="The number of samples in the interval after which drift detection is performed.", ge=1
)

windowing_strategy: DriftWindowingStrategy = Field(
AmountWindowingStrategy(), description="Which windowing strategy to use for current and reference data"
)

reset_current_window_on_trigger: bool = Field(
False, description="Whether the current window should be reset on trigger or rather be extended."
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
warmup_intervals: int | None = Field(
None,
description=(
"The number of intervals before starting to use the drift detection. Some "
"`DecisionCriteria` use this to calibrate the threshold. During the warmup, every interval will cause "
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
"a trigger."
),
)

metrics: dict[str, DriftMetric] = Field(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Annotated, Union

from modyn.config.schema.pipeline.trigger.drift.detection_window.window import (
AmountWindowingStrategy,
TimeWindowingStrategy,
)
from pydantic import Field

from .window import * # noqa

DriftWindowingStrategy = Annotated[
Union[
AmountWindowingStrategy,
TimeWindowingStrategy,
],
Field(discriminator="id"),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from __future__ import annotations

Check warning on line 1 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L1

Added line #L1 was not covered by tests
robinholzi marked this conversation as resolved.
Show resolved Hide resolved

from functools import cached_property
from typing import Annotated, Literal, Union

Check warning on line 4 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L3-L4

Added lines #L3 - L4 were not covered by tests

from modyn.config.schema.base_model import ModynBaseModel
from modyn.const.regex import REGEX_TIME_UNIT
from modyn.utils.utils import SECONDS_PER_UNIT
from pydantic import Field

Check warning on line 9 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L6-L9

Added lines #L6 - L9 were not covered by tests


class _BaseWindowingStrategy(ModynBaseModel):
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
allow_overlap: bool = Field(

Check warning on line 13 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L12-L13

Added lines #L12 - L13 were not covered by tests
False,
description=(
"Whether the windows are allowed to overlap. This is useful for time-based windows."
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
"If set to False, the current window will be reset after each trigger."
),
)


class AmountWindowingStrategy(_BaseWindowingStrategy):
id: Literal["AmountWindowingStrategy"] = Field("AmountWindowingStrategy")
amount_ref: int = Field(1000, description="How many data points should fit in the reference window", ge=1)
amount_cur: int = Field(1000, description="How many data points should fit in the current window", ge=1)

Check warning on line 25 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L22-L25

Added lines #L22 - L25 were not covered by tests

@property
def current_buffer_size(self) -> int | None:
return self.amount_cur

Check warning on line 29 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L27-L29

Added lines #L27 - L29 were not covered by tests
robinholzi marked this conversation as resolved.
Show resolved Hide resolved

@property
def reference_buffer_size(self) -> int | None:
return self.amount_ref

Check warning on line 33 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L31-L33

Added lines #L31 - L33 were not covered by tests


class TimeWindowingStrategy(_BaseWindowingStrategy):
id: Literal["TimeWindowingStrategy"] = Field("TimeWindowingStrategy")
limit_ref: str = Field(

Check warning on line 38 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L36-L38

Added lines #L36 - L38 were not covered by tests
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)
limit_cur: str = Field(

Check warning on line 42 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L42

Added line #L42 was not covered by tests
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)

@cached_property
def limit_seconds_ref(self) -> int:
unit = str(self.limit_ref)[-1:]
num = int(str(self.limit_ref)[:-1])
return num * SECONDS_PER_UNIT[unit]

Check warning on line 51 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L47-L51

Added lines #L47 - L51 were not covered by tests

@cached_property
def limit_seconds_cur(self) -> int:
unit = str(self.limit_cur)[-1:]
num = int(str(self.limit_cur)[:-1])
return num * SECONDS_PER_UNIT[unit]

Check warning on line 57 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L53-L57

Added lines #L53 - L57 were not covered by tests

@property
def current_buffer_size(self) -> int | None:
return None

Check warning on line 61 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L59-L61

Added lines #L59 - L61 were not covered by tests

@property
def reference_buffer_size(self) -> int | None:
return None

Check warning on line 65 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L63-L65

Added lines #L63 - L65 were not covered by tests


DriftWindowingStrategy = Annotated[

Check warning on line 68 in modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/amount.py#L68

Added line #L68 was not covered by tests
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
Union[
AmountWindowingStrategy,
TimeWindowingStrategy,
],
Field(discriminator="id"),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

Check warning on line 1 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L1

Added line #L1 was not covered by tests
MaxiBoether marked this conversation as resolved.
Show resolved Hide resolved

from functools import cached_property
from typing import Literal

Check warning on line 4 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L3-L4

Added lines #L3 - L4 were not covered by tests

from modyn.const.regex import REGEX_TIME_UNIT
from modyn.utils.utils import SECONDS_PER_UNIT
from pydantic import Field

Check warning on line 8 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L6-L8

Added lines #L6 - L8 were not covered by tests

from .window import _BaseWindowingStrategy

Check warning on line 10 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L10

Added line #L10 was not covered by tests


class TimeWindowingStrategy(_BaseWindowingStrategy):
id: Literal["TimeWindowingStrategy"] = Field("TimeWindowingStrategy")
limit_ref: str = Field(

Check warning on line 15 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L13-L15

Added lines #L13 - L15 were not covered by tests
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)
limit_cur: str = Field(

Check warning on line 19 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L19

Added line #L19 was not covered by tests
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)

@cached_property
def limit_seconds_ref(self) -> int:
unit = str(self.limit_ref)[-1:]
num = int(str(self.limit_ref)[:-1])
return num * SECONDS_PER_UNIT[unit]

Check warning on line 28 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L24-L28

Added lines #L24 - L28 were not covered by tests

@cached_property
def limit_seconds_cur(self) -> int:
unit = str(self.limit_cur)[-1:]
num = int(str(self.limit_cur)[:-1])
return num * SECONDS_PER_UNIT[unit]

Check warning on line 34 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L30-L34

Added lines #L30 - L34 were not covered by tests

@property
def current_buffer_size(self) -> int | None:
return None

Check warning on line 38 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L36-L38

Added lines #L36 - L38 were not covered by tests

@property
def reference_buffer_size(self) -> int | None:
return None

Check warning on line 42 in modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/timestamp.py#L40-L42

Added lines #L40 - L42 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from __future__ import annotations

from functools import cached_property
from typing import Literal

from modyn.config.schema.base_model import ModynBaseModel
from modyn.const.regex import REGEX_TIME_UNIT
from modyn.utils.utils import SECONDS_PER_UNIT
from pydantic import Field


class _BaseWindowingStrategy(ModynBaseModel):
allow_overlap: bool = Field(
False,
description=(
"Whether the windows are allowed to overlap. This is useful for time-based windows."
"If set to False, the current window will be reset after each trigger."
),
)


class AmountWindowingStrategy(_BaseWindowingStrategy):
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
id: Literal["AmountWindowingStrategy"] = Field("AmountWindowingStrategy")
amount_ref: int = Field(1000, description="How many data points should fit in the reference window", ge=1)
amount_cur: int = Field(1000, description="How many data points should fit in the current window", ge=1)

@property
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
def current_buffer_size(self) -> int | None:
return self.amount_cur

@property
def reference_buffer_size(self) -> int | None:
return self.amount_ref


class TimeWindowingStrategy(_BaseWindowingStrategy):
id: Literal["TimeWindowingStrategy"] = Field("TimeWindowingStrategy")
limit_ref: str = Field(
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)
limit_cur: str = Field(
description="Window size as an integer followed by a time unit: s, m, h, d, w, y",
pattern=rf"^\d+{REGEX_TIME_UNIT}$",
)

@cached_property
def limit_seconds_ref(self) -> int:
unit = str(self.limit_ref)[-1:]
num = int(str(self.limit_ref)[:-1])
return num * SECONDS_PER_UNIT[unit]

@cached_property
def limit_seconds_cur(self) -> int:
unit = str(self.limit_cur)[-1:]
num = int(str(self.limit_cur)[:-1])
return num * SECONDS_PER_UNIT[unit]

@property
def current_buffer_size(self) -> int | None:
robinholzi marked this conversation as resolved.
Show resolved Hide resolved
return None

Check warning on line 61 in modyn/config/schema/pipeline/trigger/drift/detection_window/window.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/window.py#L61

Added line #L61 was not covered by tests

@property
def reference_buffer_size(self) -> int | None:
return None

Check warning on line 65 in modyn/config/schema/pipeline/trigger/drift/detection_window/window.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/detection_window/window.py#L65

Added line #L65 was not covered by tests
4 changes: 2 additions & 2 deletions modyn/config/schema/pipeline/trigger/drift/evidently.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from typing import Annotated, Literal, Union

from modyn.config.schema.base_model import ModynBaseModel
from modyn.config.schema.pipeline.trigger.drift.metric import BaseMetric
from pydantic import Field


class _EvidentlyBaseDriftMetric(ModynBaseModel):
class _EvidentlyBaseDriftMetric(BaseMetric):
num_pca_component: int | None = Field(None)


Expand Down
47 changes: 47 additions & 0 deletions modyn/config/schema/pipeline/trigger/drift/metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Annotated, Literal, Union

from modyn.config.schema.base_model import ModynBaseModel
from pydantic import Field


class HypothesisTestDecisionCriterion(ModynBaseModel):
id: Literal["HypothesisTestDecisionCriterion"] = "HypothesisTestDecisionCriterion"

@property
def needs_calibration(self) -> bool:
MaxiBoether marked this conversation as resolved.
Show resolved Hide resolved
return False

Check warning on line 12 in modyn/config/schema/pipeline/trigger/drift/metric.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/metric.py#L12

Added line #L12 was not covered by tests


class ThresholdDecisionCriterion(ModynBaseModel):
id: Literal["ThresholdDecisionCriterion"] = "ThresholdDecisionCriterion"
threshold: float

@property
def needs_calibration(self) -> bool:
return False

Check warning on line 21 in modyn/config/schema/pipeline/trigger/drift/metric.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/metric.py#L21

Added line #L21 was not covered by tests


class DynamicThresholdCriterion(ModynBaseModel):
id: Literal["DynamicThresholdCriterion"] = "DynamicThresholdCriterion"
window_size: int
percentile_threshold: float = Field(
0.05, description="The percentile that a threshold has to be in to trigger a drift event."
)

@property
def needs_calibration(self) -> bool:
return True

Check warning on line 33 in modyn/config/schema/pipeline/trigger/drift/metric.py

View check run for this annotation

Codecov / codecov/patch

modyn/config/schema/pipeline/trigger/drift/metric.py#L33

Added line #L33 was not covered by tests


DecisionCriterion = Annotated[
Union[
HypothesisTestDecisionCriterion,
ThresholdDecisionCriterion,
DynamicThresholdCriterion,
],
Field(discriminator="id"),
]


class BaseMetric(ModynBaseModel):
decision_criterion: DecisionCriterion
Loading
Loading