diff --git a/Examples - capabilities.ipynb b/Examples - capabilities.ipynb new file mode 100644 index 0000000..4d8df9f --- /dev/null +++ b/Examples - capabilities.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "bf37b854-6b40-4c37-932d-7d0ba55e3d00", + "metadata": {}, + "outputs": [], + "source": [ + "from ats import logger\n", + "logger.setup('INFO')" + ] + }, + { + "cell_type": "markdown", + "id": "b0de2247-6a80-4617-9c37-ae34b2042d25", + "metadata": {}, + "source": [ + "# Anomaly detector capabilities\n", + "\n", + "This example shows the capabilities of some models." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4c9ebc5e-fb9b-4859-ab5a-e611ee5e07c8", + "metadata": {}, + "outputs": [], + "source": [ + "from ats.anomaly_detectors.base import AnomalyDetector\n", + "from ats.anomaly_detectors.naive.minmax import MinMaxAnomalyDetector\n", + "from ats.anomaly_detectors.stat.periodic_average import PeriodicAverageAnomalyDetector\n", + "from ats.anomaly_detectors.stat.robust import NHARAnomalyDetector\n", + "from ats.anomaly_detectors.ml.linear_regression import LinearRegressionAnomalyDetector\n", + "from ats.anomaly_detectors.ml.ifsom import IFSOMAnomalyDetector\n", + "from ats.anomaly_detectors.dl.lstm import LSTMAnomalyDetector" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "92e6e5bb-e746-4acd-8156-1ef28a8208dd", + "metadata": {}, + "outputs": [], + "source": [ + "anomaly_detectors = [MinMaxAnomalyDetector,\n", + " PeriodicAverageAnomalyDetector,\n", + " NHARAnomalyDetector,\n", + " LinearRegressionAnomalyDetector,\n", + " IFSOMAnomalyDetector,\n", + " LSTMAnomalyDetector]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d59cba53-0687-47bd-aa16-0956e95cd34d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "MinMaxAnomalyDetector\n", + "{'mode': 'unsupervised', 'streaming': False, 'context': 'series', 'granularity': 'point', 'multivariate': True, 'scope': 'specific'}\n", + "\n", + "PeriodicAverageAnomalyDetector\n", + "{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': True, 'scope': 'agnostic'}\n", + "\n", + "NHARAnomalyDetector\n", + "{'mode': 'unsupervised', 'streaming': False, 'context': 'series', 'granularity': 'point', 'multivariate': 'only', 'scope': 'specific'}\n", + "\n", + "LinearRegressionAnomalyDetector\n", + "{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': False, 'scope': 'agnostic'}\n", + "\n", + "IFSOMAnomalyDetector\n", + "{'mode': 'unsupervised', 'streaming': False, 'context': 'dataset', 'granularity': 'series', 'multivariate': False, 'scope': 'specific'}\n", + "\n", + "LSTMAnomalyDetector\n", + "{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': True, 'scope': 'agnostic'}\n" + ] + } + ], + "source": [ + "for anomaly_detector in anomaly_detectors:\n", + " print()\n", + " print(anomaly_detector.__name__)\n", + " print(anomaly_detector.capabilities)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ats/anomaly_detectors/base.py b/ats/anomaly_detectors/base.py index 93b0b43..2213a9a 100644 --- a/ats/anomaly_detectors/base.py +++ b/ats/anomaly_detectors/base.py @@ -10,8 +10,46 @@ import logging logger = logging.getLogger(__name__) + +class classproperty: + def __init__(self, func): + self.func = func + + def __get__(self, obj, cls): + return self.func(cls) + + class AnomalyDetector(): + allowed_capabilities = { + 'mode': {'unsupervised', 'semi-supervised', 'weakly-supervised', 'supervised'}, + 'streaming': {True, False}, + 'context': {'point', 'window', 'series', 'dataset'}, + 'granularity': {'series', 'point', 'variable'}, + 'multivariate': {True, False, 'only'}, + 'scope': {'specific', 'agnostic'}, + } + + @classproperty + def capabilities(cls): + raise NotImplementedError(f'Capabilities are not set for {cls.__name__}') + + def __new__(cls, *args, **kwargs): + cls._validate_capabilities(cls.capabilities, cls.allowed_capabilities) + return super().__new__(cls, *args, **kwargs) + + @classmethod + def _validate_capabilities(cls, capabilities, allowed_capabilities): + missing = set(allowed_capabilities) - set(capabilities) + if missing: + raise ValueError(f'Missing required capabilities: {sorted(missing)} for {cls.__name__}') + for key, value in capabilities.items(): + if key not in allowed_capabilities: + raise ValueError(f'Unknown capability: "{key}" for {cls.__name__}') + if value not in allowed_capabilities[key]: + raise ValueError(f'Invalid value "{value}" for capability "{key}" for {cls.__name__}') + + #======================== # Helpers #======================== diff --git a/ats/anomaly_detectors/dl/lstm.py b/ats/anomaly_detectors/dl/lstm.py index f51dd70..82216d1 100644 --- a/ats/anomaly_detectors/dl/lstm.py +++ b/ats/anomaly_detectors/dl/lstm.py @@ -2,4 +2,14 @@ from timeseria.models.anomaly_detectors import LSTMAnomalyDetector as TimeseriaLSTMAnomalyDetector class LSTMAnomalyDetector(TimeseriaAnomalyDetector): + + capabilities = { + 'mode': 'semi-supervised', + 'streaming': True, + 'context': 'window', + 'granularity': 'point', + 'multivariate': True, + 'scope': 'agnostic' + } + model_class = TimeseriaLSTMAnomalyDetector diff --git a/ats/anomaly_detectors/ml/ifsom.py b/ats/anomaly_detectors/ml/ifsom.py index b5d9630..b6ef686 100644 --- a/ats/anomaly_detectors/ml/ifsom.py +++ b/ats/anomaly_detectors/ml/ifsom.py @@ -45,6 +45,15 @@ class IFSOMAnomalyDetector(AnomalyDetector): features computed using FATS in order to identify anomalous series within a data set. """ + capabilities = { + 'mode': 'unsupervised', + 'streaming': False, + 'context': 'dataset', + 'granularity': 'series', + 'multivariate': False, + 'scope': 'specific' + } + @staticmethod def _wide_df_to_timeseries_df_with_anomaly_labels(wide_df, anomaly_col="outliers"): """ diff --git a/ats/anomaly_detectors/ml/linear_regression.py b/ats/anomaly_detectors/ml/linear_regression.py index cdc110d..18c57f0 100644 --- a/ats/anomaly_detectors/ml/linear_regression.py +++ b/ats/anomaly_detectors/ml/linear_regression.py @@ -2,4 +2,14 @@ from timeseria.models.anomaly_detectors import LinearRegressionAnomalyDetector as TimeseriaLinearRegressionAnomalyDetector class LinearRegressionAnomalyDetector(TimeseriaAnomalyDetector): + + capabilities = { + 'mode': 'semi-supervised', + 'streaming': True, + 'context': 'window', + 'granularity': 'point', + 'multivariate': False, + 'scope': 'agnostic' + } + model_class = TimeseriaLinearRegressionAnomalyDetector \ No newline at end of file diff --git a/ats/anomaly_detectors/naive/minmax.py b/ats/anomaly_detectors/naive/minmax.py index 0b8cfbd..3d0cdd1 100644 --- a/ats/anomaly_detectors/naive/minmax.py +++ b/ats/anomaly_detectors/naive/minmax.py @@ -12,6 +12,15 @@ class MinMaxAnomalyDetector(AnomalyDetector): + capabilities = { + 'mode': 'unsupervised', + 'streaming': False, + 'context': 'series', + 'granularity': 'point', + 'multivariate': True, + 'scope': 'specific' + } + @AnomalyDetector.apply_method def apply(self, data, inplace=False): diff --git a/ats/anomaly_detectors/stat/periodic_average.py b/ats/anomaly_detectors/stat/periodic_average.py index 199d487..55b6584 100644 --- a/ats/anomaly_detectors/stat/periodic_average.py +++ b/ats/anomaly_detectors/stat/periodic_average.py @@ -2,4 +2,14 @@ from timeseria.models.anomaly_detectors import PeriodicAverageAnomalyDetector as TimeseriaPeriodicAverageAnomalyDetector class PeriodicAverageAnomalyDetector(TimeseriaAnomalyDetector): + + capabilities = { + 'mode': 'semi-supervised', + 'streaming': True, + 'context': 'window', + 'granularity': 'point', + 'multivariate': True, + 'scope': 'agnostic' + } + model_class = TimeseriaPeriodicAverageAnomalyDetector diff --git a/ats/anomaly_detectors/stat/robust.py b/ats/anomaly_detectors/stat/robust.py index 77a946c..6e87648 100644 --- a/ats/anomaly_detectors/stat/robust.py +++ b/ats/anomaly_detectors/stat/robust.py @@ -14,6 +14,15 @@ class _COMNHARAnomalyDetector(AnomalyDetector): Statistically robust anomaly detector based on COM, HAR, and NHAR methodologies. """ + capabilities = { + 'mode': 'unsupervised', + 'streaming': False, + 'context': 'series', + 'granularity': 'point', + 'multivariate': 'only', + 'scope': 'specific' + } + def __init__(self, fq=2 * np.pi / 30, fw=2 * np.pi / 7, trend=2, methods=('COM', 'HAR', 'NHAR')): self.fq = fq self.fw = fw