Skip to content

Include CUSUM methods into change detection #224

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

Merged
merged 3 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,13 @@ The currently implemented detectors are listed in the following table.
<tr>
<td rowspan="13" style="text-align: center; border: 1px solid grey; padding: 8px;">Concept drift</td>
<td rowspan="13" style="text-align: center; border: 1px solid grey; padding: 8px;">Streaming</td>
<td rowspan="1" style="text-align: center; border: 1px solid grey; padding: 8px;">Change detection</td>
<td rowspan="4" style="text-align: center; border: 1px solid grey; padding: 8px;">Change detection</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">U</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">N</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">BOCD</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;"><a href="https://doi.org/10.48550/arXiv.0710.3742">Adams and MacKay (2007)</a></td>
</tr>
<tr>
<td rowspan="3" style="text-align: center; border: 1px solid grey; padding: 8px;">CUSUM</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">U</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">N</td>
<td style="text-align: center; border: 1px solid grey; padding: 8px;">CUSUM</td>
Expand Down
15 changes: 0 additions & 15 deletions docs/source/api_reference/detectors/concept_drift/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,6 @@ The {mod}`frouros.detectors.concept_drift.streaming` module contains streaming c

BOCD
BOCDConfig
```

## CUSUM Test

```{eval-rst}
.. automodule:: frouros.detectors.concept_drift.streaming.cusum_based
:no-members:
:no-inherited-members:
```

```{eval-rst}
.. autosummary::
:toctree: auto_generated/
:template: class.md

CUSUM
CUSUMConfig
GeometricMovingAverage
Expand Down
3 changes: 0 additions & 3 deletions frouros/detectors/concept_drift/streaming/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
from .change_detection import (
BOCD,
BOCDConfig,
)

from .cusum_based import (
CUSUM,
CUSUMConfig,
GeometricMovingAverage,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
"""Concept drift change detection methods' init."""

from .bocd import BOCD, BOCDConfig
from .bocd import (
BOCD,
BOCDConfig,
)
from .cusum import (
CUSUM,
CUSUMConfig,
)
from .geometric_moving_average import (
GeometricMovingAverage,
GeometricMovingAverageConfig,
)
from .page_hinkley import (
PageHinkley,
PageHinkleyConfig,
)

__all__ = [
"BOCD",
"BOCDConfig",
"CUSUM",
"CUSUMConfig",
"GeometricMovingAverage",
"GeometricMovingAverageConfig",
"PageHinkley",
"PageHinkleyConfig",
]
208 changes: 207 additions & 1 deletion frouros/detectors/concept_drift/streaming/change_detection/base.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Base concept drift ChangeDetection based module."""

import abc
from typing import Union
from typing import List, Optional, Union

from frouros.callbacks.streaming.base import BaseCallbackStreaming
from frouros.detectors.concept_drift.streaming.base import (
BaseConceptDriftStreaming,
BaseConceptDriftStreamingConfig,
)
from frouros.utils.stats import Mean


class BaseChangeDetectionConfig(BaseConceptDriftStreamingConfig):
Expand All @@ -21,3 +23,207 @@ class BaseChangeDetection(BaseConceptDriftStreaming):
@abc.abstractmethod
def _update(self, value: Union[int, float], **kwargs) -> None:
pass


class BaseCUSUMConfig(BaseChangeDetectionConfig):
"""Class representing a CUSUM based configuration class."""

def __init__(
self,
lambda_: float = 50.0,
min_num_instances: int = 30,
) -> None:
"""Init method.

:param lambda_: lambda value
:type lambda_: float
:param min_num_instances: minimum numbers of instances
to start looking for changes
:type min_num_instances: int
"""
super().__init__(min_num_instances=min_num_instances)
self.lambda_ = lambda_

@property
def lambda_(self) -> float:
"""Threshold property.

:return: lambda to use
:rtype: float
"""
return self._lambda

@lambda_.setter
def lambda_(self, value: float) -> None:
"""Threshold setter.

:param value: value to be set
:type value: float
:raises ValueError: Value error exception
"""
if value < 0:
raise ValueError("lambda_ must be great or equal than 0.")
self._lambda = value


class DeltaConfig:
"""Class representing a delta configuration class."""

def __init__(
self,
delta: float = 0.005,
) -> None:
"""Init method.

:param delta: delta value
:type delta: float
"""
self.delta = delta

@property
def delta(self) -> float:
"""Delta property.

:return: delta to use
:rtype: float
"""
return self._delta

@delta.setter
def delta(self, value: float) -> None:
"""Delta setter.

:param value: value to be set
:type value: float
:raises ValueError: Value error exception
"""
if not 0.0 <= value <= 1.0:
raise ValueError("delta must be in the range [0, 1].")
self._delta = value


class AlphaConfig:
"""Class representing an alpha configuration class."""

def __init__(
self,
alpha: float = 0.9999,
) -> None:
"""Init method.

:param alpha: forgetting factor value
:type alpha: float
"""
self.alpha = alpha

@property
def alpha(self) -> float:
"""Forgetting factor property.

:return: forgetting factor value
:rtype: float
"""
return self._alpha

@alpha.setter
def alpha(self, value: float) -> None:
"""Forgetting factor setter.

:param value: forgetting factor value
:type value: float
:raises ValueError: Value error exception
"""
if not 0.0 <= value <= 1.0:
raise ValueError("alpha must be in the range [0, 1].")
self._alpha = value


class BaseCUSUM(BaseChangeDetection):
"""CUSUM based algorithm class."""

config_type = BaseCUSUMConfig

def __init__(
self,
config: Optional[BaseCUSUMConfig] = None,
callbacks: Optional[
Union[BaseCallbackStreaming, List[BaseCallbackStreaming]]
] = None,
) -> None:
"""Init method.

:param config: configuration parameters
:type config: Optional[BaseCUSUMConfig]
:param callbacks: callbacks
:type callbacks: Optional[Union[BaseCallbackStreaming,
List[BaseCallbackStreaming]]]
"""
super().__init__(
config=config,
callbacks=callbacks,
)
self.additional_vars = {
"mean_error_rate": Mean(),
"sum_": 0.0,
}
self._set_additional_vars_callback()

@property
def mean_error_rate(self) -> Mean:
"""Mean error rate property.

:return: mean error rate to use
:rtype: Mean
"""
return self._additional_vars["mean_error_rate"]

@mean_error_rate.setter
def mean_error_rate(self, value: Mean) -> None:
"""Mean error rate setter.

:param value: value to be set
:type value: Mean
"""
self._additional_vars["mean_error_rate"] = value

@property
def sum_(self) -> float:
"""Sum count property.

:return: sum count value
:rtype: float
"""
return self._additional_vars["sum_"]

@sum_.setter
def sum_(self, value: float) -> None:
"""Sum count setter.

:param value: value to be set
:type value: float
"""
self._additional_vars["sum_"] = value

@abc.abstractmethod
def _update_sum(self, error_rate: float) -> None:
pass

def reset(self) -> None:
"""Reset method."""
super().reset()
self.mean_error_rate = Mean()
self.sum_ = 0.0

def _update(self, value: Union[int, float], **kwargs) -> None:
self.num_instances += 1

self.mean_error_rate.update(value=value)
self._update_sum(error_rate=value)

if (
self.num_instances >= self.config.min_num_instances # type: ignore
and self.sum_ > self.config.lambda_ # type: ignore
):
self.drift = True
else:
self.drift = False
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class GaussianUnknownMean(BaseBOCDModel):
def __init__(
self,
prior_mean: float = 0,
prior_var: float = 0,
prior_var: float = 1,
data_var: float = 1,
) -> None:
"""Init method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np # type: ignore

from frouros.detectors.concept_drift.streaming.cusum_based.base import (
from frouros.detectors.concept_drift.streaming.change_detection.base import (
BaseCUSUM,
BaseCUSUMConfig,
DeltaConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Geometric Moving Average module."""

from frouros.detectors.concept_drift.streaming.cusum_based.base import (
from frouros.detectors.concept_drift.streaming.change_detection.base import (
BaseCUSUM,
BaseCUSUMConfig,
AlphaConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Page Hinkley module."""

from frouros.detectors.concept_drift.streaming.cusum_based.base import (
from frouros.detectors.concept_drift.streaming.change_detection.base import (
BaseCUSUM,
BaseCUSUMConfig,
DeltaConfig,
Expand Down

This file was deleted.

Loading