aberrant 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. aberrant/__init__.py +15 -0
  2. aberrant/base/__init__.py +46 -0
  3. aberrant/base/architecture.py +76 -0
  4. aberrant/base/exceptions.py +51 -0
  5. aberrant/base/model.py +48 -0
  6. aberrant/base/pipeline.py +127 -0
  7. aberrant/base/similarity.py +48 -0
  8. aberrant/base/transformer.py +56 -0
  9. aberrant/drift/__init__.py +13 -0
  10. aberrant/drift/adwin.py +297 -0
  11. aberrant/drift/base.py +54 -0
  12. aberrant/drift/kswin.py +151 -0
  13. aberrant/drift/page_hinkley.py +172 -0
  14. aberrant/model/__init__.py +40 -0
  15. aberrant/model/deep/__init__.py +16 -0
  16. aberrant/model/deep/autoencoder.py +116 -0
  17. aberrant/model/distance/__init__.py +20 -0
  18. aberrant/model/distance/knn.py +62 -0
  19. aberrant/model/distance/lof.py +256 -0
  20. aberrant/model/iforest/__init__.py +40 -0
  21. aberrant/model/iforest/asd.py +226 -0
  22. aberrant/model/iforest/halfspace.py +321 -0
  23. aberrant/model/iforest/mondrian.py +427 -0
  24. aberrant/model/iforest/online.py +760 -0
  25. aberrant/model/iforest/rand_hist.py +114 -0
  26. aberrant/model/iforest/random_cut.py +461 -0
  27. aberrant/model/iforest/xstream.py +424 -0
  28. aberrant/model/null.py +50 -0
  29. aberrant/model/quantile_threshold.py +149 -0
  30. aberrant/model/random.py +57 -0
  31. aberrant/model/stat/__init__.py +35 -0
  32. aberrant/model/stat/multi.py +317 -0
  33. aberrant/model/stat/uni.py +780 -0
  34. aberrant/model/svm/__init__.py +20 -0
  35. aberrant/model/svm/adaptive.py +293 -0
  36. aberrant/model/svm/gadget.py +144 -0
  37. aberrant/model/svm/todo.txt +3 -0
  38. aberrant/model/threshold.py +99 -0
  39. aberrant/stream/__init__.py +46 -0
  40. aberrant/stream/dataset/__init__.py +205 -0
  41. aberrant/stream/dataset/loader.py +362 -0
  42. aberrant/stream/dataset/registry.py +402 -0
  43. aberrant/stream/dataset/streamers.py +260 -0
  44. aberrant/stream/streamer.py +135 -0
  45. aberrant/transform/__init__.py +11 -0
  46. aberrant/transform/preprocessing/__init__.py +8 -0
  47. aberrant/transform/preprocessing/scaler.py +182 -0
  48. aberrant/transform/projection/__init__.py +9 -0
  49. aberrant/transform/projection/incremental_pca.py +269 -0
  50. aberrant/transform/projection/random_projection.py +110 -0
  51. aberrant/utils/__init__.py +0 -0
  52. aberrant/utils/deep/__init__.py +0 -0
  53. aberrant/utils/deep/architecture.py +91 -0
  54. aberrant/utils/deep/loss_func.py +28 -0
  55. aberrant/utils/similar/__init__.py +0 -0
  56. aberrant/utils/similar/faiss_engine.py +172 -0
  57. aberrant-0.5.0.dist-info/METADATA +168 -0
  58. aberrant-0.5.0.dist-info/RECORD +60 -0
  59. aberrant-0.5.0.dist-info/WHEEL +4 -0
  60. aberrant-0.5.0.dist-info/licenses/LICENSE +21 -0
aberrant/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """ABERRANT: Online Anomaly Detection library for streaming data.
2
+
3
+ A Python library implementing the online learning paradigm for anomaly
4
+ detection. All models process data one point at a time, updating their
5
+ state incrementally without storing historical data.
6
+
7
+ Modules:
8
+ base: Core abstract classes (BaseModel, BaseTransformer, Pipeline)
9
+ drift: Concept drift detection algorithms (ADWIN, KSWIN, PageHinkley)
10
+ model: Anomaly detection models
11
+ stream: Streaming data utilities
12
+ transform: Data transformers (scalers, projections)
13
+ """
14
+
15
+ __version__ = "0.5.0"
@@ -0,0 +1,46 @@
1
+ """Base classes for online anomaly detection models and components.
2
+
3
+ This module provides the fundamental abstract base classes that define the
4
+ interfaces for models, transformers, pipelines, and other core components
5
+ in the aberrant library.
6
+ """
7
+
8
+ import importlib
9
+ from typing import Any
10
+
11
+ from aberrant.base.exceptions import (
12
+ AberrantError,
13
+ IncompatibleComponentError,
14
+ ModelNotFittedError,
15
+ PipelineError,
16
+ TransformationError,
17
+ UnsupportedFeatureError,
18
+ ValidationError,
19
+ )
20
+ from aberrant.base.model import BaseModel
21
+ from aberrant.base.pipeline import Pipeline
22
+ from aberrant.base.similarity import BaseSimilaritySearchEngine
23
+ from aberrant.base.transformer import BaseTransformer
24
+
25
+ __all__ = [
26
+ "Architecture",
27
+ "BaseModel",
28
+ "BaseSimilaritySearchEngine",
29
+ "BaseTransformer",
30
+ "IncompatibleComponentError",
31
+ "ModelNotFittedError",
32
+ "AberrantError",
33
+ "Pipeline",
34
+ "PipelineError",
35
+ "TransformationError",
36
+ "UnsupportedFeatureError",
37
+ "ValidationError",
38
+ ]
39
+
40
+
41
+ def __getattr__(name: str) -> Any:
42
+ """Lazy import of Architecture to avoid torch dependency."""
43
+ if name == "Architecture":
44
+ module = importlib.import_module("aberrant.base.architecture")
45
+ return module.Architecture
46
+ raise AttributeError(f"module 'aberrant.base' has no attribute '{name}'")
@@ -0,0 +1,76 @@
1
+ """Neural network architecture base class for deep learning models."""
2
+
3
+ import abc
4
+ import random
5
+
6
+ import numpy as np
7
+ import torch
8
+ from torch import nn
9
+
10
+
11
+ class Architecture(abc.ABC, nn.Module):
12
+ """
13
+ Abstract base class for defining neural network architectures.
14
+
15
+ This class ensures that any neural network architecture can be plugged into
16
+ online anomaly detection models. It provides a consistent interface and
17
+ device handling capabilities.
18
+
19
+ Subclasses must implement the `forward` and `input_size` methods.
20
+ """
21
+
22
+ def __init__(self, device: torch.device | None = None) -> None:
23
+ """
24
+ Initialize the architecture.
25
+
26
+ Args:
27
+ device: The device to run the model on. If None, uses CPU.
28
+ """
29
+ super().__init__()
30
+ self.device = device or torch.device("cpu")
31
+ self.to(self.device)
32
+
33
+ @abc.abstractmethod
34
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
35
+ """
36
+ Forward pass through the network.
37
+
38
+ Args:
39
+ x: Input tensor.
40
+
41
+ Returns:
42
+ Output tensor.
43
+ """
44
+ raise NotImplementedError
45
+
46
+ @property
47
+ @abc.abstractmethod
48
+ def input_size(self) -> int:
49
+ """
50
+ The expected input size for the network.
51
+
52
+ Returns:
53
+ Number of input features.
54
+ """
55
+ raise NotImplementedError
56
+
57
+ @staticmethod
58
+ def set_seed(seed: int) -> None:
59
+ """
60
+ Set random seeds for reproducibility.
61
+
62
+ Args:
63
+ seed: Random seed value.
64
+ """
65
+ random.seed(seed)
66
+ np.random.seed(seed)
67
+ torch.manual_seed(seed)
68
+ if torch.cuda.is_available():
69
+ torch.cuda.manual_seed(seed)
70
+ torch.cuda.manual_seed_all(seed)
71
+ torch.backends.cudnn.deterministic = True
72
+ torch.backends.cudnn.benchmark = False
73
+
74
+ def __repr__(self) -> str:
75
+ """Return a string representation of the architecture."""
76
+ return f"{self.__class__.__name__}(input_size={self.input_size}, device={self.device})"
@@ -0,0 +1,51 @@
1
+ """Custom exceptions for the aberrant library."""
2
+
3
+
4
+ class AberrantError(Exception):
5
+ """Base exception class for all aberrant-specific errors."""
6
+
7
+ pass
8
+
9
+
10
+ class ModelNotFittedError(AberrantError):
11
+ """Raised when a model method is called before the model has been fitted."""
12
+
13
+ def __init__(self, message: str = "Model has not been fitted yet.") -> None:
14
+ super().__init__(message)
15
+
16
+
17
+ class TransformationError(AberrantError):
18
+ """Raised when a transformation operation fails."""
19
+
20
+ pass
21
+
22
+
23
+ class PipelineError(AberrantError):
24
+ """Raised when a pipeline operation fails."""
25
+
26
+ pass
27
+
28
+
29
+ class ValidationError(AberrantError):
30
+ """Raised when input validation fails."""
31
+
32
+ pass
33
+
34
+
35
+ class UnsupportedFeatureError(AberrantError):
36
+ """Raised when an unsupported feature is encountered."""
37
+
38
+ def __init__(self, feature_name: str) -> None:
39
+ super().__init__(f"Feature '{feature_name}' has not been seen during training.")
40
+ self.feature_name = feature_name
41
+
42
+
43
+ class IncompatibleComponentError(PipelineError):
44
+ """Raised when pipeline components are incompatible."""
45
+
46
+ def __init__(self, component_name: str, expected_type: str) -> None:
47
+ super().__init__(
48
+ f"Component '{component_name}' is not compatible. Expected: {expected_type}"
49
+ )
50
+ self.component_name = component_name
51
+ self.expected_type = expected_type
aberrant/base/model.py ADDED
@@ -0,0 +1,48 @@
1
+ """Base model interface for online anomaly detection."""
2
+
3
+ import abc
4
+
5
+
6
+ class BaseModel(abc.ABC):
7
+ """
8
+ Abstract base class for online anomaly detection models.
9
+
10
+ This class defines the interface that all online anomaly detection models should implement.
11
+ Online models process one data point at a time, updating their internal state and providing
12
+ anomaly scores for each data point.
13
+
14
+ Subclasses must implement the `learn_one` and `score_one` methods.
15
+ """
16
+
17
+ @abc.abstractmethod
18
+ def learn_one(self, x: dict[str, float]) -> None:
19
+ """
20
+ Update the model with a single data point.
21
+
22
+ Args:
23
+ x: A dictionary representing a single data point. The keys are feature names,
24
+ and the values are the corresponding feature values.
25
+ """
26
+ raise NotImplementedError
27
+
28
+ @abc.abstractmethod
29
+ def score_one(self, x: dict[str, float]) -> float:
30
+ """
31
+ Compute the anomaly score for a single data point.
32
+
33
+ Args:
34
+ x: A dictionary representing a single data point. The keys are feature names,
35
+ and the values are the corresponding feature values.
36
+
37
+ Returns:
38
+ The anomaly score for the data point. Higher scores indicate greater anomaly.
39
+ """
40
+ raise NotImplementedError
41
+
42
+ def __repr__(self) -> str:
43
+ """Return a string representation of the model."""
44
+ return f"{self.__class__.__name__}()"
45
+
46
+ def __str__(self) -> str:
47
+ """Return a human-readable string representation of the model."""
48
+ return self.__repr__()
@@ -0,0 +1,127 @@
1
+ """Pipeline for chaining transformers and models together."""
2
+
3
+ from typing import Any
4
+
5
+ from .exceptions import IncompatibleComponentError, PipelineError
6
+
7
+
8
+ class Pipeline:
9
+ """
10
+ Pipeline for chaining transformers and models.
11
+
12
+ A pipeline allows you to chain multiple components together, where the output
13
+ of one component becomes the input of the next. The pipeline can contain
14
+ transformers (which modify data) and models (which score data).
15
+
16
+ Args:
17
+ first: The first component in the pipeline (transformer or model).
18
+ second: The second component in the pipeline (transformer or model).
19
+
20
+ Example:
21
+ >>> scaler = MinMaxScaler()
22
+ >>> model = RandomModel()
23
+ >>> pipeline = Pipeline(scaler, model)
24
+ >>> pipeline.learn_one({"feature": 1.0})
25
+ >>> score = pipeline.score_one({"feature": 2.0})
26
+ """
27
+
28
+ def __init__(self, first: Any, second: Any) -> None:
29
+ self._validate_components(first, second)
30
+ self.first = first
31
+ self.second = second
32
+
33
+ def learn_one(self, x: dict[str, float]) -> None:
34
+ """
35
+ Learn from a single data point through the pipeline.
36
+
37
+ Args:
38
+ x: A dictionary representing a single data point.
39
+ """
40
+ self.first.learn_one(x)
41
+ transformed_x = self.first.transform_one(x)
42
+ self.second.learn_one(transformed_x)
43
+
44
+ def transform_one(self, x: dict[str, float]) -> dict[str, float]:
45
+ """
46
+ Transform a single data point through the pipeline.
47
+
48
+ Args:
49
+ x: A dictionary representing a single data point.
50
+
51
+ Returns:
52
+ The transformed data point.
53
+ """
54
+ transformed_x = self.first.transform_one(x)
55
+ transformed = self.second.transform_one(transformed_x)
56
+ if not isinstance(transformed, dict):
57
+ raise PipelineError(
58
+ "The final transformer must return a dict[str, float] from transform_one."
59
+ )
60
+ return transformed
61
+
62
+ def score_one(self, x: dict[str, float]) -> float:
63
+ """
64
+ Score a single data point using the pipeline.
65
+
66
+ Args:
67
+ x: A dictionary representing a single data point.
68
+
69
+ Returns:
70
+ The anomaly score for the data point.
71
+
72
+ Raises:
73
+ PipelineError: If the final component doesn't support scoring.
74
+ """
75
+ transformed_x = self.first.transform_one(x)
76
+
77
+ if hasattr(self.second, "score_one"):
78
+ score = self.second.score_one(transformed_x)
79
+ if not isinstance(score, int | float):
80
+ raise PipelineError(
81
+ "The final component must return a numeric score from score_one."
82
+ )
83
+ return float(score)
84
+ else:
85
+ raise PipelineError(
86
+ f"The final component ({self.second.__class__.__name__}) does not have a 'score_one' method."
87
+ )
88
+
89
+ def __or__(self, other: Any) -> "Pipeline":
90
+ """Overload the | operator to allow further chaining of pipelines."""
91
+ return Pipeline(self, other)
92
+
93
+ def __repr__(self) -> str:
94
+ """Return a string representation of the pipeline."""
95
+ return f"Pipeline({self.first!r} | {self.second!r})"
96
+
97
+ def __str__(self) -> str:
98
+ """Return a human-readable string representation of the pipeline."""
99
+ return f"{self.first.__class__.__name__} | {self.second.__class__.__name__}"
100
+
101
+ def _validate_components(self, first: Any, second: Any) -> None:
102
+ """
103
+ Validate that the pipeline components are compatible.
104
+
105
+ Args:
106
+ first: The first component.
107
+ second: The second component.
108
+
109
+ Raises:
110
+ IncompatibleComponentError: If components are not compatible.
111
+ """
112
+ # Check first component has required methods
113
+ if not hasattr(first, "learn_one"):
114
+ raise IncompatibleComponentError(
115
+ first.__class__.__name__, "component with 'learn_one' method"
116
+ )
117
+
118
+ if not hasattr(first, "transform_one"):
119
+ raise IncompatibleComponentError(
120
+ first.__class__.__name__, "component with 'transform_one' method"
121
+ )
122
+
123
+ # Check second component has required methods
124
+ if not hasattr(second, "learn_one"):
125
+ raise IncompatibleComponentError(
126
+ second.__class__.__name__, "component with 'learn_one' method"
127
+ )
@@ -0,0 +1,48 @@
1
+ """Base similarity search engine interface."""
2
+
3
+ import abc
4
+
5
+
6
+ class BaseSimilaritySearchEngine(abc.ABC):
7
+ """
8
+ Abstract base class for similarity search engines.
9
+
10
+ This class defines the interface for similarity search engines that can store
11
+ data points and find similar neighbors for query points.
12
+
13
+ Subclasses must implement the `append` and `search` methods.
14
+ """
15
+
16
+ @abc.abstractmethod
17
+ def append(self, x: dict[str, float]) -> None:
18
+ """
19
+ Add a data point to the search engine.
20
+
21
+ Args:
22
+ x: A dictionary representing a single data point. The keys are feature names,
23
+ and the values are the corresponding feature values.
24
+ """
25
+ pass
26
+
27
+ @abc.abstractmethod
28
+ def search(self, x: dict[str, float], n_neighbors: int) -> float:
29
+ """
30
+ Search for the n nearest neighbors of a data point.
31
+
32
+ Args:
33
+ x: A dictionary representing the query data point.
34
+ n_neighbors: The number of nearest neighbors to find.
35
+
36
+ Returns:
37
+ A similarity or distance score based on the nearest neighbors.
38
+ Should return a consistent numeric value (e.g., 0.0 if insufficient data).
39
+ """
40
+ pass
41
+
42
+ def __repr__(self) -> str:
43
+ """Return a string representation of the search engine."""
44
+ return f"{self.__class__.__name__}()"
45
+
46
+ def __str__(self) -> str:
47
+ """Return a human-readable string representation of the search engine."""
48
+ return self.__repr__()
@@ -0,0 +1,56 @@
1
+ """Base transformer interface for online data transformation."""
2
+
3
+ import abc
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from aberrant.base.pipeline import Pipeline
7
+
8
+ if TYPE_CHECKING:
9
+ from aberrant.base.pipeline import Pipeline
10
+
11
+
12
+ class BaseTransformer(abc.ABC):
13
+ """
14
+ Abstract base class for online transformers.
15
+
16
+ This class defines the interface for transformers that can learn from and transform
17
+ data points incrementally. Transformers modify the input data while maintaining
18
+ the streaming nature of the processing.
19
+
20
+ Subclasses must implement the `learn_one` and `transform_one` methods.
21
+ """
22
+
23
+ @abc.abstractmethod
24
+ def learn_one(self, x: dict[str, float]) -> None:
25
+ """
26
+ Update the transformer with a single data point.
27
+
28
+ Args:
29
+ x: A dictionary representing a single data point. The keys are feature names,
30
+ and the values are the corresponding feature values.
31
+ """
32
+ raise NotImplementedError
33
+
34
+ @abc.abstractmethod
35
+ def transform_one(self, x: dict[str, float]) -> dict[str, float]:
36
+ """
37
+ Transform a single data point.
38
+
39
+ Args:
40
+ x: A dictionary representing a single data point to transform.
41
+
42
+ Returns:
43
+ A dictionary with transformed feature values.
44
+ """
45
+ raise NotImplementedError
46
+
47
+ def __or__(self, other: Any) -> "Pipeline":
48
+ return Pipeline(self, other)
49
+
50
+ def __repr__(self) -> str:
51
+ """Return a string representation of the transformer."""
52
+ return f"{self.__class__.__name__}()"
53
+
54
+ def __str__(self) -> str:
55
+ """Return a human-readable string representation of the transformer."""
56
+ return self.__repr__()
@@ -0,0 +1,13 @@
1
+ """Concept drift detection algorithms for streaming data."""
2
+
3
+ from aberrant.drift.adwin import ADWIN
4
+ from aberrant.drift.base import BaseDriftDetector
5
+ from aberrant.drift.kswin import KSWIN
6
+ from aberrant.drift.page_hinkley import PageHinkley
7
+
8
+ __all__ = [
9
+ "ADWIN",
10
+ "BaseDriftDetector",
11
+ "KSWIN",
12
+ "PageHinkley",
13
+ ]