Skip to content

Commit cea5af4

Browse files
author
Jaime Céspedes Sisniega
authored
Merge pull request #224 from IFCA/fix-include-cusum-change-detection
Include CUSUM methods into change detection
2 parents 7b016a8 + 3e8336d commit cea5af4

File tree

13 files changed

+332
-318
lines changed

13 files changed

+332
-318
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,13 @@ The currently implemented detectors are listed in the following table.
209209
<tr>
210210
<td rowspan="13" style="text-align: center; border: 1px solid grey; padding: 8px;">Concept drift</td>
211211
<td rowspan="13" style="text-align: center; border: 1px solid grey; padding: 8px;">Streaming</td>
212-
<td rowspan="1" style="text-align: center; border: 1px solid grey; padding: 8px;">Change detection</td>
212+
<td rowspan="4" style="text-align: center; border: 1px solid grey; padding: 8px;">Change detection</td>
213213
<td style="text-align: center; border: 1px solid grey; padding: 8px;">U</td>
214214
<td style="text-align: center; border: 1px solid grey; padding: 8px;">N</td>
215215
<td style="text-align: center; border: 1px solid grey; padding: 8px;">BOCD</td>
216216
<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>
217217
</tr>
218218
<tr>
219-
<td rowspan="3" style="text-align: center; border: 1px solid grey; padding: 8px;">CUSUM</td>
220219
<td style="text-align: center; border: 1px solid grey; padding: 8px;">U</td>
221220
<td style="text-align: center; border: 1px solid grey; padding: 8px;">N</td>
222221
<td style="text-align: center; border: 1px solid grey; padding: 8px;">CUSUM</td>

docs/source/api_reference/detectors/concept_drift/streaming.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,6 @@ The {mod}`frouros.detectors.concept_drift.streaming` module contains streaming c
2626
2727
BOCD
2828
BOCDConfig
29-
```
30-
31-
## CUSUM Test
32-
33-
```{eval-rst}
34-
.. automodule:: frouros.detectors.concept_drift.streaming.cusum_based
35-
:no-members:
36-
:no-inherited-members:
37-
```
38-
39-
```{eval-rst}
40-
.. autosummary::
41-
:toctree: auto_generated/
42-
:template: class.md
43-
4429
CUSUM
4530
CUSUMConfig
4631
GeometricMovingAverage

frouros/detectors/concept_drift/streaming/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
from .change_detection import (
55
BOCD,
66
BOCDConfig,
7-
)
8-
9-
from .cusum_based import (
107
CUSUM,
118
CUSUMConfig,
129
GeometricMovingAverage,
Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
"""Concept drift change detection methods' init."""
22

3-
from .bocd import BOCD, BOCDConfig
3+
from .bocd import (
4+
BOCD,
5+
BOCDConfig,
6+
)
7+
from .cusum import (
8+
CUSUM,
9+
CUSUMConfig,
10+
)
11+
from .geometric_moving_average import (
12+
GeometricMovingAverage,
13+
GeometricMovingAverageConfig,
14+
)
15+
from .page_hinkley import (
16+
PageHinkley,
17+
PageHinkleyConfig,
18+
)
419

520
__all__ = [
621
"BOCD",
722
"BOCDConfig",
23+
"CUSUM",
24+
"CUSUMConfig",
25+
"GeometricMovingAverage",
26+
"GeometricMovingAverageConfig",
27+
"PageHinkley",
28+
"PageHinkleyConfig",
829
]

frouros/detectors/concept_drift/streaming/change_detection/base.py

Lines changed: 207 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""Base concept drift ChangeDetection based module."""
22

33
import abc
4-
from typing import Union
4+
from typing import List, Optional, Union
55

6+
from frouros.callbacks.streaming.base import BaseCallbackStreaming
67
from frouros.detectors.concept_drift.streaming.base import (
78
BaseConceptDriftStreaming,
89
BaseConceptDriftStreamingConfig,
910
)
11+
from frouros.utils.stats import Mean
1012

1113

1214
class BaseChangeDetectionConfig(BaseConceptDriftStreamingConfig):
@@ -21,3 +23,207 @@ class BaseChangeDetection(BaseConceptDriftStreaming):
2123
@abc.abstractmethod
2224
def _update(self, value: Union[int, float], **kwargs) -> None:
2325
pass
26+
27+
28+
class BaseCUSUMConfig(BaseChangeDetectionConfig):
29+
"""Class representing a CUSUM based configuration class."""
30+
31+
def __init__(
32+
self,
33+
lambda_: float = 50.0,
34+
min_num_instances: int = 30,
35+
) -> None:
36+
"""Init method.
37+
38+
:param lambda_: lambda value
39+
:type lambda_: float
40+
:param min_num_instances: minimum numbers of instances
41+
to start looking for changes
42+
:type min_num_instances: int
43+
"""
44+
super().__init__(min_num_instances=min_num_instances)
45+
self.lambda_ = lambda_
46+
47+
@property
48+
def lambda_(self) -> float:
49+
"""Threshold property.
50+
51+
:return: lambda to use
52+
:rtype: float
53+
"""
54+
return self._lambda
55+
56+
@lambda_.setter
57+
def lambda_(self, value: float) -> None:
58+
"""Threshold setter.
59+
60+
:param value: value to be set
61+
:type value: float
62+
:raises ValueError: Value error exception
63+
"""
64+
if value < 0:
65+
raise ValueError("lambda_ must be great or equal than 0.")
66+
self._lambda = value
67+
68+
69+
class DeltaConfig:
70+
"""Class representing a delta configuration class."""
71+
72+
def __init__(
73+
self,
74+
delta: float = 0.005,
75+
) -> None:
76+
"""Init method.
77+
78+
:param delta: delta value
79+
:type delta: float
80+
"""
81+
self.delta = delta
82+
83+
@property
84+
def delta(self) -> float:
85+
"""Delta property.
86+
87+
:return: delta to use
88+
:rtype: float
89+
"""
90+
return self._delta
91+
92+
@delta.setter
93+
def delta(self, value: float) -> None:
94+
"""Delta setter.
95+
96+
:param value: value to be set
97+
:type value: float
98+
:raises ValueError: Value error exception
99+
"""
100+
if not 0.0 <= value <= 1.0:
101+
raise ValueError("delta must be in the range [0, 1].")
102+
self._delta = value
103+
104+
105+
class AlphaConfig:
106+
"""Class representing an alpha configuration class."""
107+
108+
def __init__(
109+
self,
110+
alpha: float = 0.9999,
111+
) -> None:
112+
"""Init method.
113+
114+
:param alpha: forgetting factor value
115+
:type alpha: float
116+
"""
117+
self.alpha = alpha
118+
119+
@property
120+
def alpha(self) -> float:
121+
"""Forgetting factor property.
122+
123+
:return: forgetting factor value
124+
:rtype: float
125+
"""
126+
return self._alpha
127+
128+
@alpha.setter
129+
def alpha(self, value: float) -> None:
130+
"""Forgetting factor setter.
131+
132+
:param value: forgetting factor value
133+
:type value: float
134+
:raises ValueError: Value error exception
135+
"""
136+
if not 0.0 <= value <= 1.0:
137+
raise ValueError("alpha must be in the range [0, 1].")
138+
self._alpha = value
139+
140+
141+
class BaseCUSUM(BaseChangeDetection):
142+
"""CUSUM based algorithm class."""
143+
144+
config_type = BaseCUSUMConfig
145+
146+
def __init__(
147+
self,
148+
config: Optional[BaseCUSUMConfig] = None,
149+
callbacks: Optional[
150+
Union[BaseCallbackStreaming, List[BaseCallbackStreaming]]
151+
] = None,
152+
) -> None:
153+
"""Init method.
154+
155+
:param config: configuration parameters
156+
:type config: Optional[BaseCUSUMConfig]
157+
:param callbacks: callbacks
158+
:type callbacks: Optional[Union[BaseCallbackStreaming,
159+
List[BaseCallbackStreaming]]]
160+
"""
161+
super().__init__(
162+
config=config,
163+
callbacks=callbacks,
164+
)
165+
self.additional_vars = {
166+
"mean_error_rate": Mean(),
167+
"sum_": 0.0,
168+
}
169+
self._set_additional_vars_callback()
170+
171+
@property
172+
def mean_error_rate(self) -> Mean:
173+
"""Mean error rate property.
174+
175+
:return: mean error rate to use
176+
:rtype: Mean
177+
"""
178+
return self._additional_vars["mean_error_rate"]
179+
180+
@mean_error_rate.setter
181+
def mean_error_rate(self, value: Mean) -> None:
182+
"""Mean error rate setter.
183+
184+
:param value: value to be set
185+
:type value: Mean
186+
"""
187+
self._additional_vars["mean_error_rate"] = value
188+
189+
@property
190+
def sum_(self) -> float:
191+
"""Sum count property.
192+
193+
:return: sum count value
194+
:rtype: float
195+
"""
196+
return self._additional_vars["sum_"]
197+
198+
@sum_.setter
199+
def sum_(self, value: float) -> None:
200+
"""Sum count setter.
201+
202+
:param value: value to be set
203+
:type value: float
204+
"""
205+
self._additional_vars["sum_"] = value
206+
207+
@abc.abstractmethod
208+
def _update_sum(self, error_rate: float) -> None:
209+
pass
210+
211+
def reset(self) -> None:
212+
"""Reset method."""
213+
super().reset()
214+
self.mean_error_rate = Mean()
215+
self.sum_ = 0.0
216+
217+
def _update(self, value: Union[int, float], **kwargs) -> None:
218+
self.num_instances += 1
219+
220+
self.mean_error_rate.update(value=value)
221+
self._update_sum(error_rate=value)
222+
223+
if (
224+
self.num_instances >= self.config.min_num_instances # type: ignore
225+
and self.sum_ > self.config.lambda_ # type: ignore
226+
):
227+
self.drift = True
228+
else:
229+
self.drift = False

frouros/detectors/concept_drift/streaming/change_detection/bocd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class GaussianUnknownMean(BaseBOCDModel):
4747
def __init__(
4848
self,
4949
prior_mean: float = 0,
50-
prior_var: float = 0,
50+
prior_var: float = 1,
5151
data_var: float = 1,
5252
) -> None:
5353
"""Init method.

frouros/detectors/concept_drift/streaming/cusum_based/cusum.py renamed to frouros/detectors/concept_drift/streaming/change_detection/cusum.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np # type: ignore
44

5-
from frouros.detectors.concept_drift.streaming.cusum_based.base import (
5+
from frouros.detectors.concept_drift.streaming.change_detection.base import (
66
BaseCUSUM,
77
BaseCUSUMConfig,
88
DeltaConfig,

frouros/detectors/concept_drift/streaming/cusum_based/geometric_moving_average.py renamed to frouros/detectors/concept_drift/streaming/change_detection/geometric_moving_average.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Geometric Moving Average module."""
22

3-
from frouros.detectors.concept_drift.streaming.cusum_based.base import (
3+
from frouros.detectors.concept_drift.streaming.change_detection.base import (
44
BaseCUSUM,
55
BaseCUSUMConfig,
66
AlphaConfig,

frouros/detectors/concept_drift/streaming/cusum_based/page_hinkley.py renamed to frouros/detectors/concept_drift/streaming/change_detection/page_hinkley.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Page Hinkley module."""
22

3-
from frouros.detectors.concept_drift.streaming.cusum_based.base import (
3+
from frouros.detectors.concept_drift.streaming.change_detection.base import (
44
BaseCUSUM,
55
BaseCUSUMConfig,
66
DeltaConfig,

frouros/detectors/concept_drift/streaming/cusum_based/__init__.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)