Skip to content

Commit

Permalink
Start implementing OC-SVM (#46)
Browse files Browse the repository at this point in the history
* Start implementing OC-SVM

* More implementation on OC-SVM (only testing still needed)

* Added tests + update changelog.rst
  • Loading branch information
LouisCarpentier42 authored Nov 29, 2024
1 parent aa42991 commit fe4a447
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/additional_information/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Added
or labels
- Added the property ``__version__`` to ``dtaianomaly``, which can be accessed from code.
- Included the used version of ``dtaianomaly`` when logging errors.
- Implemented ``PrincipalComponentAnalysis``, ``KernelPrincipalComponentAnalysis`` and
``RobustPrincipalComponentAnalysis`` anomaly detectors.
- Implemented ``HistogramBasedOutlierScore`` anomaly detector.
- Implemented ``OneClassSupportVectorMachine`` anomaly detector.

Changed
^^^^^^^
Expand Down
6 changes: 6 additions & 0 deletions docs/api/anomaly_detection_algorithms/ocsvm.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
One Class Support Vector Machine
================================

.. autoclass:: dtaianomaly.anomaly_detection.OneClassSupportVectorMachine
:inherited-members:
:members:
59 changes: 59 additions & 0 deletions dtaianomaly/anomaly_detection/OneClassSupportVectorMachine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

from pyod.models.ocsvm import OCSVM
from dtaianomaly.anomaly_detection.BaseDetector import Supervision
from dtaianomaly.anomaly_detection.PyODAnomalyDetector import PyODAnomalyDetector


class OneClassSupportVectorMachine(PyODAnomalyDetector):
"""
Anomaly detector based on One-Class Support Vector Machines (OC-SVM).
The OC-SVM [Scholkopf1999support]_ uses a Support Vector Machine to learn
a boundary around the normal behavior with minimal margin. New data can
then be identified as anomaly or not, depending on if the data falls within
this boundary (and thus is normal) or outside the boundary (and thus is
anomalous).
Notes
-----
The OC-SVM inherets from :py:class:`~dtaianomaly.anomaly_detection.PyodAnomalyDetector`.
Parameters
----------
window_size: int or str
The window size to use for extracting sliding windows from the time series. This
value will be passed to :py:meth:`~dtaianomaly.anomaly_detection.compute_window_size`.
stride: int, default=1
The stride, i.e., the step size for extracting sliding windows from the time series.
**kwargs:
Arguments to be passed to the PyOD OC-SVM
Attributes
----------
window_size_: int
The effectively used window size for this anomaly detector
pyod_detector_ : OCSVM
A OCSVM-detector of PyOD
Examples
--------
>>> from dtaianomaly.anomaly_detection import OneClassSupportVectorMachine
>>> from dtaianomaly.data import demonstration_time_series
>>> x, y = demonstration_time_series()
>>> ocsvm = OneClassSupportVectorMachine(10).fit(x)
>>> ocsvm.decision_function(x)
array([-0.7442125 , -1.57019847, -1.86868112, ..., 13.33883568,
12.6492399 , 11.8761641 ])
References
----------
.. [Scholkopf1999support] Schölkopf, Bernhard, et al. "Support vector method
for novelty detection." Advances in neural information processing systems 12
(1999).
"""

def _initialize_detector(self, **kwargs) -> OCSVM:
return OCSVM(**kwargs)

def _supervision(self):
return Supervision.SEMI_SUPERVISED
2 changes: 2 additions & 0 deletions dtaianomaly/anomaly_detection/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .LocalOutlierFactor import LocalOutlierFactor
from .MatrixProfileDetector import MatrixProfileDetector
from .MedianMethod import MedianMethod
from .OneClassSupportVectorMachine import OneClassSupportVectorMachine
from .PrincipalComponentAnalysis import PrincipalComponentAnalysis
from .RobustPrincipalComponentAnalysis import RobustPrincipalComponentAnalysis

Expand Down Expand Up @@ -48,6 +49,7 @@
'LocalOutlierFactor',
'MatrixProfileDetector',
'MedianMethod',
'OneClassSupportVectorMachine',
'PrincipalComponentAnalysis',
'PyODAnomalyDetector',
'RobustPrincipalComponentAnalysis'
Expand Down
3 changes: 3 additions & 0 deletions dtaianomaly/workflow/workflow_from_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ def detector_entry(entry):
elif detector_type == 'RobustPrincipalComponentAnalysis':
return anomaly_detection.RobustPrincipalComponentAnalysis(**entry_without_type)

elif detector_type == 'OneClassSupportVectorMachine':
return anomaly_detection.OneClassSupportVectorMachine(**entry_without_type)

else:
raise ValueError(f'Invalid detector entry: {entry}')

Expand Down
15 changes: 15 additions & 0 deletions tests/anomaly_detection/test_OneClassSupportVectorMachine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

from dtaianomaly.anomaly_detection import OneClassSupportVectorMachine, Supervision


class TestOneClassSupportVectorMachine:

def test_supervision(self):
detector = OneClassSupportVectorMachine(1)
assert detector.supervision == Supervision.SEMI_SUPERVISED

def test_str(self):
assert str(OneClassSupportVectorMachine(5)) == "OneClassSupportVectorMachine(window_size=5)"
assert str(OneClassSupportVectorMachine('fft')) == "OneClassSupportVectorMachine(window_size='fft')"
assert str(OneClassSupportVectorMachine(15, 3)) == "OneClassSupportVectorMachine(window_size=15,stride=3)"
assert str(OneClassSupportVectorMachine(25, kernel='poly')) == "OneClassSupportVectorMachine(window_size=25,kernel='poly')"
6 changes: 6 additions & 0 deletions tests/anomaly_detection/test_PyODAnomalyDetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
@pytest.mark.parametrize('detector_class,kwargs', [
(anomaly_detection.HistogramBasedOutlierScore, {'n_bins': 'auto', 'alpha': 0.5}),
(anomaly_detection.IsolationForest, {'n_estimators': 42, 'max_samples': 'auto'}),
(anomaly_detection.KernelPrincipalComponentAnalysis, {'kernel': 'poly', 'n_components': 0.5}),
(anomaly_detection.KNearestNeighbors, {'n_neighbors': 42, 'metric': 'euclidean'}),
(anomaly_detection.LocalOutlierFactor, {'n_neighbors': 3}),
(anomaly_detection.OneClassSupportVectorMachine, {'kernel': 'poly'}),
(anomaly_detection.PrincipalComponentAnalysis, {'n_components': 0.5}),
])
class TestPyodAnomalyDetectorAdditionalArgs:

Expand All @@ -20,8 +23,11 @@ def test(self, detector_class, kwargs):
@pytest.mark.parametrize('detector_class', [
anomaly_detection.HistogramBasedOutlierScore,
anomaly_detection.IsolationForest,
anomaly_detection.KernelPrincipalComponentAnalysis,
anomaly_detection.KNearestNeighbors,
anomaly_detection.LocalOutlierFactor,
anomaly_detection.OneClassSupportVectorMachine,
anomaly_detection.PrincipalComponentAnalysis,
])
class TestPyodAnomalyDetector:

Expand Down
2 changes: 2 additions & 0 deletions tests/anomaly_detection/test_detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
anomaly_detection.MatrixProfileDetector(15, novelty=True),
anomaly_detection.MedianMethod(15),
anomaly_detection.MedianMethod(15, 10),
anomaly_detection.OneClassSupportVectorMachine(15),
anomaly_detection.PrincipalComponentAnalysis(15),
anomaly_detection.RobustPrincipalComponentAnalysis(15),
anomaly_detection.RobustPrincipalComponentAnalysis(15, svd_solver='randomized'),
Expand Down Expand Up @@ -117,6 +118,7 @@ def test_fit_predict_on_different_time_series(self, detector, univariate_time_se
(anomaly_detection.KNearestNeighbors, {}),
(anomaly_detection.LocalOutlierFactor, {}),
(anomaly_detection.MatrixProfileDetector, {}),
(anomaly_detection.OneClassSupportVectorMachine, {}),
(anomaly_detection.PrincipalComponentAnalysis, {}),
(anomaly_detection.RobustPrincipalComponentAnalysis, {}),
])
Expand Down
3 changes: 3 additions & 0 deletions tests/workflow/test_workflow_from_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ def test_chained_preprocessor(self):
(detector_entry, anomaly_detection.MatrixProfileDetector, {'window_size': 25, 'normalize': True, 'p': 1.5, 'k': 5}),
(detector_entry, anomaly_detection.MedianMethod, {'neighborhood_size_before': 15}),
(detector_entry, anomaly_detection.MedianMethod, {'neighborhood_size_before': 25, 'neighborhood_size_after': 5}),
(detector_entry, anomaly_detection.OneClassSupportVectorMachine, {'window_size': 15}),
(detector_entry, anomaly_detection.OneClassSupportVectorMachine, {'window_size': 15, 'kernel': 'poly'}),
(detector_entry, anomaly_detection.PrincipalComponentAnalysis, {'window_size': 15}),
(detector_entry, anomaly_detection.PrincipalComponentAnalysis, {'window_size': 15, 'n_components': 0.5}),
(detector_entry, anomaly_detection.KernelPrincipalComponentAnalysis, {'window_size': 15}),
Expand Down Expand Up @@ -429,6 +431,7 @@ def test_no_type(self, entry_function, object_type, entry):
(detector_entry, anomaly_detection.LocalOutlierFactor),
(detector_entry, anomaly_detection.MatrixProfileDetector),
(detector_entry, anomaly_detection.MedianMethod),
(detector_entry, anomaly_detection.OneClassSupportVectorMachine),
(detector_entry, anomaly_detection.PrincipalComponentAnalysis),
(detector_entry, anomaly_detection.RobustPrincipalComponentAnalysis),
# Preprocessors
Expand Down

0 comments on commit fe4a447

Please sign in to comment.