Skip to content

Commit

Permalink
Merge pull request #224 from IFCA/fix-include-cusum-change-detection
Browse files Browse the repository at this point in the history
Include CUSUM methods into change detection
  • Loading branch information
jaime-cespedes-sisniega committed Jun 21, 2023
2 parents 7b016a8 + 3e8336d commit cea5af4
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 318 deletions.
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
17 changes: 0 additions & 17 deletions frouros/detectors/concept_drift/streaming/cusum_based/__init__.py

This file was deleted.

Loading

0 comments on commit cea5af4

Please sign in to comment.