vigil-drift 0.1.0__tar.gz
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.
- vigil_drift-0.1.0/PKG-INFO +369 -0
- vigil_drift-0.1.0/README.md +323 -0
- vigil_drift-0.1.0/pyproject.toml +78 -0
- vigil_drift-0.1.0/setup.cfg +4 -0
- vigil_drift-0.1.0/tests/test_autoencoder.py +60 -0
- vigil_drift-0.1.0/tests/test_drift_detector.py +70 -0
- vigil_drift-0.1.0/tests/test_sentinel.py +69 -0
- vigil_drift-0.1.0/vigil/__init__.py +47 -0
- vigil_drift-0.1.0/vigil/attribution.py +185 -0
- vigil_drift-0.1.0/vigil/core/__init__.py +1 -0
- vigil_drift-0.1.0/vigil/core/autoencoder.py +181 -0
- vigil_drift-0.1.0/vigil/core/drift_detector.py +235 -0
- vigil_drift-0.1.0/vigil/core/novelty_detector.py +171 -0
- vigil_drift-0.1.0/vigil/logging/__init__.py +1 -0
- vigil_drift-0.1.0/vigil/logging/mlflow_logger.py +168 -0
- vigil_drift-0.1.0/vigil/sentinel.py +337 -0
- vigil_drift-0.1.0/vigil_drift.egg-info/PKG-INFO +369 -0
- vigil_drift-0.1.0/vigil_drift.egg-info/SOURCES.txt +19 -0
- vigil_drift-0.1.0/vigil_drift.egg-info/dependency_links.txt +1 -0
- vigil_drift-0.1.0/vigil_drift.egg-info/requires.txt +30 -0
- vigil_drift-0.1.0/vigil_drift.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vigil-drift
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Vigil β unsupervised concept drift detection for network streams with feature-level attribution. Based on arXiv:2605.29834.
|
|
5
|
+
Author: Venkateswara Sahu
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://venkateswara-sahu.github.io/OWADD/
|
|
8
|
+
Project-URL: Repository, https://github.com/Venkateswara-Sahu/OWADD
|
|
9
|
+
Project-URL: Documentation, https://venkateswara-sahu.github.io/OWADD/
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/Venkateswara-Sahu/OWADD/issues
|
|
11
|
+
Keywords: concept-drift,data-streams,autoencoder,novelty-detection,mlops,unsupervised
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: torch>=2.0.0
|
|
23
|
+
Requires-Dist: numpy>=1.24.0
|
|
24
|
+
Requires-Dist: scipy>=1.10.0
|
|
25
|
+
Requires-Dist: scikit-learn>=1.2.0
|
|
26
|
+
Requires-Dist: pandas>=2.0.0
|
|
27
|
+
Provides-Extra: api
|
|
28
|
+
Requires-Dist: fastapi>=0.100.0; extra == "api"
|
|
29
|
+
Requires-Dist: uvicorn>=0.22.0; extra == "api"
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0; extra == "api"
|
|
31
|
+
Provides-Extra: dashboard
|
|
32
|
+
Requires-Dist: streamlit>=1.25.0; extra == "dashboard"
|
|
33
|
+
Requires-Dist: plotly>=5.15.0; extra == "dashboard"
|
|
34
|
+
Provides-Extra: mlflow
|
|
35
|
+
Requires-Dist: mlflow>=2.5.0; extra == "mlflow"
|
|
36
|
+
Provides-Extra: kafka
|
|
37
|
+
Requires-Dist: confluent-kafka>=2.2.0; extra == "kafka"
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
41
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.0.285; extra == "dev"
|
|
43
|
+
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: vigil-drift[api,dashboard,dev,kafka,mlflow]; extra == "all"
|
|
46
|
+
|
|
47
|
+
<div align="center">
|
|
48
|
+
|
|
49
|
+
<img src="https://img.shields.io/badge/Python-3.11-blue?style=flat-square&logo=python&logoColor=white"/>
|
|
50
|
+
<img src="https://img.shields.io/badge/PyTorch-2.x-EE4C2C?style=flat-square&logo=pytorch&logoColor=white"/>
|
|
51
|
+
<img src="https://img.shields.io/github/actions/workflow/status/Venkateswara-Sahu/OWADD/ci.yml?branch=main&style=flat-square&label=CI&logo=github"/>
|
|
52
|
+
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square"/>
|
|
53
|
+
<img src="https://img.shields.io/badge/arXiv-2605.29834-b31b1b?style=flat-square&logo=arxiv"/>
|
|
54
|
+
|
|
55
|
+
<br/><br/>
|
|
56
|
+
|
|
57
|
+
# π‘οΈ Vigil
|
|
58
|
+
|
|
59
|
+
### Production-ready unsupervised concept drift detection for tabular data streams
|
|
60
|
+
|
|
61
|
+
**No labels. No manual thresholds. Knows when your data changes β and tells you exactly which features changed.**
|
|
62
|
+
|
|
63
|
+
[Quick Start](#-quick-start) Β· [Dashboard](#-live-dashboard) Β· [API Docs](#-rest-api) Β· [Architecture](#-architecture) Β· [Paper](#-research-basis)
|
|
64
|
+
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## The Problem
|
|
70
|
+
|
|
71
|
+
In production ML systems, the data your model was trained on eventually stops looking like the data it receives β a phenomenon called **concept drift**. For network security, this means attacks evolve. For fraud detection, patterns shift. For predictive maintenance, machinery ages.
|
|
72
|
+
|
|
73
|
+
Most drift detectors either require labels (which you never have in real-time) or only tell you *that* drift happened, not *what* drifted.
|
|
74
|
+
|
|
75
|
+
**OWADD Sentinel solves both.**
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## What It Does
|
|
80
|
+
|
|
81
|
+
| Capability | How |
|
|
82
|
+
|---|---|
|
|
83
|
+
| π **Detect concept drift** | Replicated T-Test on autoencoder reconstruction errors β no labels needed |
|
|
84
|
+
| π **Identify novel classes** | KDE density estimation on a frozen mirror autoencoder (A_KC) |
|
|
85
|
+
| π **Explain the drift** | Feature-level attribution: ranks which input features caused the drift |
|
|
86
|
+
| β‘ **Serve at scale** | FastAPI REST service with `/detect` endpoint |
|
|
87
|
+
| π **Track experiments** | MLflow logs every chunk's drift severity, novelty rate, and attribution report |
|
|
88
|
+
| π₯οΈ **Visualize live** | SOC-style Streamlit dashboard with real-time charts |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Architecture
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
96
|
+
β OWADD Sentinel System β
|
|
97
|
+
β β
|
|
98
|
+
Data Stream β ββββββββββββ βββββββββββββββββββββββββββ β
|
|
99
|
+
ββββββββββββββΊ β β Chunk βββββΊβ Autoencoder A β β
|
|
100
|
+
(CSV / API / β β (200 rowsβ β (adapts on drift) β β ββββββββββββ
|
|
101
|
+
Kafka topic) β ββββββββββββ ββββββββββββ¬βββββββββββββββ ββββββΊβ MLflow β
|
|
102
|
+
β β β errors_A β β Tracking β
|
|
103
|
+
β β ββββββββββββΌβββββββββββββββ β ββββββββββββ
|
|
104
|
+
β β β Replicated T-Test β β
|
|
105
|
+
β β β (15 tests, Ξ±=0.05) β β ββββββββββββ
|
|
106
|
+
β β β Buffer size: 1000 β ββββββΊβFastAPI β
|
|
107
|
+
β β ββββββββββββ¬βββββββββββββββ β β /detect β
|
|
108
|
+
β β β DRIFT? β ββββββββββββ
|
|
109
|
+
β β ββββββββββββΌβββββββββββββββ β
|
|
110
|
+
β β β DriftAttributor β β ββββββββββββ
|
|
111
|
+
β β β (novel contribution) β ββββββΊβStreamlit β
|
|
112
|
+
β β β Which features drifted? β β βDashboard β
|
|
113
|
+
β β βββββββββββββββββββββββββββ β ββββββββββββ
|
|
114
|
+
β β β
|
|
115
|
+
β β βββββββββββββββββββββββββββ β
|
|
116
|
+
β ββββββββββΊβ Autoencoder A_KC β β
|
|
117
|
+
β β (frozen mirror) β β
|
|
118
|
+
β ββββββββββββ¬βββββββββββββββ β
|
|
119
|
+
β β errors_AKC β
|
|
120
|
+
β ββββββββββββΌβββββββββββββββ β
|
|
121
|
+
β β KDE Novelty Detector β β
|
|
122
|
+
β β Novel class proportion β β
|
|
123
|
+
β βββββββββββββββββββββββββββ β
|
|
124
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Key design decisions (from arXiv:2605.29834):**
|
|
128
|
+
- Uses 1-D reconstruction error proxy instead of full feature vectors β memory complexity O(buffer_size) not O(n Γ d)
|
|
129
|
+
- Dual autoencoders: **A** adapts on drift; **A_KC** stays frozen to distinguish drift from novelty
|
|
130
|
+
- Replicated T-tests (r=15) reduce variance vs. a single test
|
|
131
|
+
|
|
132
|
+
**Novel contribution (OWADD Sentinel, not in the paper):**
|
|
133
|
+
- `DriftAttributor` computes per-feature reconstruction error delta β ranks features by drift contribution β makes alerts *actionable*
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## π¦ Quick Start
|
|
138
|
+
|
|
139
|
+
### Install
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
git clone https://github.com/Venkateswara-Sahu/OWADD.git
|
|
143
|
+
cd OWADD
|
|
144
|
+
python -m venv .venv
|
|
145
|
+
.venv\Scripts\activate # Windows
|
|
146
|
+
# source .venv/bin/activate # Linux/Mac
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Run as a Python library
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import pandas as pd
|
|
154
|
+
from owadd_sentinel import OWADDSentinel
|
|
155
|
+
|
|
156
|
+
# Load your data
|
|
157
|
+
df = pd.read_csv("your_data.csv")
|
|
158
|
+
feature_cols = [c for c in df.columns if c != "label"]
|
|
159
|
+
X = df[feature_cols].values.astype("float32")
|
|
160
|
+
|
|
161
|
+
# Initialise
|
|
162
|
+
sentinel = OWADDSentinel(
|
|
163
|
+
feature_names=feature_cols,
|
|
164
|
+
top_k_features=5,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Phase 1: train on first batch (offline)
|
|
168
|
+
sentinel.fit(X[:200])
|
|
169
|
+
|
|
170
|
+
# Phase 2: process stream chunk by chunk
|
|
171
|
+
chunk_size = 200
|
|
172
|
+
for i in range(200, len(X), chunk_size):
|
|
173
|
+
result = sentinel.detect(X[i:i+chunk_size])
|
|
174
|
+
|
|
175
|
+
print(result)
|
|
176
|
+
# SentinelResult(chunk=3, status=β οΈ DRIFT, severity=0.93, novelty=0.0%)
|
|
177
|
+
|
|
178
|
+
if result.drift_detected:
|
|
179
|
+
for feat in result.attribution.top_features:
|
|
180
|
+
print(f" {feat['feature_name']}: {feat['contribution']:.1%} contribution")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## π₯οΈ Live Dashboard
|
|
186
|
+
|
|
187
|
+
SOC-style real-time monitoring of the NSL-KDD network traffic stream:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install -e ".[dashboard]"
|
|
191
|
+
streamlit run dashboard/app.py
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Open **http://localhost:8501**, press **βΆ Start** and watch:
|
|
195
|
+
- Reconstruction error timeline update chunk by chunk
|
|
196
|
+
- Red β drift markers appear when the traffic distribution shifts
|
|
197
|
+
- Feature attribution chart reveals which network features drifted
|
|
198
|
+
- Novelty gauge tracks unknown attack class emergence
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## π REST API
|
|
203
|
+
|
|
204
|
+
FastAPI microservice for production deployment:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pip install -e ".[api]"
|
|
208
|
+
uvicorn api.app:app --host 0.0.0.0 --port 8000 --reload
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Open **http://localhost:8000/docs** for interactive Swagger UI.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Train on initial data
|
|
215
|
+
curl -X POST http://localhost:8000/fit \
|
|
216
|
+
-H "Content-Type: application/json" \
|
|
217
|
+
-d '{"data": [[0.1, 0.5, ...], ...]}'
|
|
218
|
+
|
|
219
|
+
# Detect drift in incoming batch
|
|
220
|
+
curl -X POST http://localhost:8000/detect \
|
|
221
|
+
-H "Content-Type: application/json" \
|
|
222
|
+
-d '{"data": [[0.3, 0.9, ...], ...]}'
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Response:**
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"drift_detected": true,
|
|
229
|
+
"drift_severity": 0.93,
|
|
230
|
+
"novelty_proportion": 0.0,
|
|
231
|
+
"top_drifted_features": [
|
|
232
|
+
{"feature_name": "diff_srv_rate", "contribution": 0.23, "error_delta": 0.041},
|
|
233
|
+
{"feature_name": "service_other", "contribution": 0.07, "error_delta": 0.012}
|
|
234
|
+
],
|
|
235
|
+
"message": "β οΈ Concept drift detected (severity=0.93)"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## π MLflow Experiment Tracking
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pip install -e ".[mlflow]"
|
|
245
|
+
|
|
246
|
+
# Run stream with experiment logging
|
|
247
|
+
python -c "
|
|
248
|
+
from data.nsl_kdd_loader import load_nsl_kdd
|
|
249
|
+
from data.stream_simulator import StreamSimulator
|
|
250
|
+
from owadd_sentinel import OWADDSentinel
|
|
251
|
+
from owadd_sentinel.logging.mlflow_logger import MLflowLogger
|
|
252
|
+
|
|
253
|
+
X, y, feat = load_nsl_kdd()
|
|
254
|
+
sim = StreamSimulator(X, y)
|
|
255
|
+
sentinel = OWADDSentinel(feature_names=feat)
|
|
256
|
+
logger = MLflowLogger(experiment_name='nsl-kdd-stream')
|
|
257
|
+
|
|
258
|
+
first = next(sim.stream(n_chunks=1))
|
|
259
|
+
sentinel.fit(first.X, verbose=False)
|
|
260
|
+
|
|
261
|
+
logger.start_run(params={'buffer_size': 1000, 'drift_threshold': 0.3})
|
|
262
|
+
for chunk in sim.stream(n_chunks=25):
|
|
263
|
+
result = sentinel.detect(chunk.X)
|
|
264
|
+
logger.log_chunk(result, ground_truth_label=chunk.dominant_class)
|
|
265
|
+
logger.end_run()
|
|
266
|
+
"
|
|
267
|
+
|
|
268
|
+
# View results
|
|
269
|
+
mlflow ui # β http://localhost:5000
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## π§ͺ Tests
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pytest tests/ -v --cov=owadd_sentinel
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
tests/test_autoencoder.py::test_autoencoder_forward_shape PASSED
|
|
282
|
+
tests/test_autoencoder.py::test_reconstruction_errors_non_negative PASSED
|
|
283
|
+
tests/test_autoencoder.py::test_mirrored_pair_identical_weights PASSED
|
|
284
|
+
tests/test_autoencoder.py::test_training_reduces_loss PASSED
|
|
285
|
+
tests/test_drift_detector.py::test_no_drift_during_warmup PASSED
|
|
286
|
+
tests/test_drift_detector.py::test_no_false_drift_on_stable_stream PASSED
|
|
287
|
+
tests/test_drift_detector.py::test_drift_detected_on_shifted_dist PASSED
|
|
288
|
+
tests/test_sentinel.py::test_sentinel_fit_and_detect PASSED
|
|
289
|
+
tests/test_sentinel.py::test_sentinel_attribution_on_drift PASSED
|
|
290
|
+
... 14 passed in 15s, 81% coverage
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## ποΈ Project Structure
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
OWADD/
|
|
299
|
+
βββ owadd_sentinel/ # Core pip package
|
|
300
|
+
β βββ core/
|
|
301
|
+
β β βββ autoencoder.py # Dual mirrored autoencoders (A and A_KC)
|
|
302
|
+
β β βββ drift_detector.py # Replicated T-Test drift detection
|
|
303
|
+
β β βββ novelty_detector.py # KDE-based novel class recognition
|
|
304
|
+
β βββ logging/
|
|
305
|
+
β β βββ mlflow_logger.py # MLflow experiment tracking
|
|
306
|
+
β βββ attribution.py # Feature-level drift attribution (novel)
|
|
307
|
+
β βββ sentinel.py # Main OWADDSentinel public API
|
|
308
|
+
β
|
|
309
|
+
βββ api/
|
|
310
|
+
β βββ app.py # FastAPI REST service
|
|
311
|
+
β βββ schemas.py # Pydantic request/response models
|
|
312
|
+
β
|
|
313
|
+
βββ data/
|
|
314
|
+
β βββ nsl_kdd_loader.py # NSL-KDD download + preprocessing
|
|
315
|
+
β βββ stream_simulator.py # 3-phase non-stationary stream
|
|
316
|
+
β
|
|
317
|
+
βββ dashboard/
|
|
318
|
+
β βββ app.py # SOC-style Streamlit dashboard
|
|
319
|
+
β
|
|
320
|
+
βββ tests/ # 14 unit + integration tests
|
|
321
|
+
βββ .github/workflows/ci.yml # GitHub Actions CI
|
|
322
|
+
βββ pyproject.toml # pip install owadd-sentinel
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## π¬ Research Basis
|
|
328
|
+
|
|
329
|
+
This project implements and extends:
|
|
330
|
+
|
|
331
|
+
> **"Open World Autoencoding Drift Detection with Novel Class Recognition in Tabular Non-stationary Data Streams"**
|
|
332
|
+
> arXiv:2605.29834
|
|
333
|
+
|
|
334
|
+
**Extensions in Vigil (not in the paper):**
|
|
335
|
+
- `DriftAttributor`: per-feature reconstruction error delta analysis β makes drift detection *actionable* by identifying which features drifted and by how much
|
|
336
|
+
- FastAPI REST service for production deployment
|
|
337
|
+
- MLflow integration for experiment tracking and model versioning
|
|
338
|
+
- SOC-style real-time monitoring dashboard
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## π οΈ Tech Stack
|
|
343
|
+
|
|
344
|
+
| Component | Technology |
|
|
345
|
+
|---|---|
|
|
346
|
+
| Core algorithm | Python, PyTorch, SciPy, scikit-learn |
|
|
347
|
+
| REST API | FastAPI, Pydantic, Uvicorn |
|
|
348
|
+
| Experiment tracking | MLflow |
|
|
349
|
+
| Dashboard | Streamlit, Plotly |
|
|
350
|
+
| Dataset | NSL-KDD (Canadian Institute for Cybersecurity) |
|
|
351
|
+
| Testing | pytest, pytest-cov |
|
|
352
|
+
| Linting | Ruff |
|
|
353
|
+
| CI/CD | GitHub Actions |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## π License
|
|
358
|
+
|
|
359
|
+
MIT Β© Venkateswara Sahu
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
<div align="center">
|
|
364
|
+
|
|
365
|
+
**Built with the belief that ML systems should know when they're wrong.**
|
|
366
|
+
|
|
367
|
+
[β Star this repo](https://github.com/Venkateswara-Sahu/OWADD) if you find it useful
|
|
368
|
+
|
|
369
|
+
</div>
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://img.shields.io/badge/Python-3.11-blue?style=flat-square&logo=python&logoColor=white"/>
|
|
4
|
+
<img src="https://img.shields.io/badge/PyTorch-2.x-EE4C2C?style=flat-square&logo=pytorch&logoColor=white"/>
|
|
5
|
+
<img src="https://img.shields.io/github/actions/workflow/status/Venkateswara-Sahu/OWADD/ci.yml?branch=main&style=flat-square&label=CI&logo=github"/>
|
|
6
|
+
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square"/>
|
|
7
|
+
<img src="https://img.shields.io/badge/arXiv-2605.29834-b31b1b?style=flat-square&logo=arxiv"/>
|
|
8
|
+
|
|
9
|
+
<br/><br/>
|
|
10
|
+
|
|
11
|
+
# π‘οΈ Vigil
|
|
12
|
+
|
|
13
|
+
### Production-ready unsupervised concept drift detection for tabular data streams
|
|
14
|
+
|
|
15
|
+
**No labels. No manual thresholds. Knows when your data changes β and tells you exactly which features changed.**
|
|
16
|
+
|
|
17
|
+
[Quick Start](#-quick-start) Β· [Dashboard](#-live-dashboard) Β· [API Docs](#-rest-api) Β· [Architecture](#-architecture) Β· [Paper](#-research-basis)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## The Problem
|
|
24
|
+
|
|
25
|
+
In production ML systems, the data your model was trained on eventually stops looking like the data it receives β a phenomenon called **concept drift**. For network security, this means attacks evolve. For fraud detection, patterns shift. For predictive maintenance, machinery ages.
|
|
26
|
+
|
|
27
|
+
Most drift detectors either require labels (which you never have in real-time) or only tell you *that* drift happened, not *what* drifted.
|
|
28
|
+
|
|
29
|
+
**OWADD Sentinel solves both.**
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What It Does
|
|
34
|
+
|
|
35
|
+
| Capability | How |
|
|
36
|
+
|---|---|
|
|
37
|
+
| π **Detect concept drift** | Replicated T-Test on autoencoder reconstruction errors β no labels needed |
|
|
38
|
+
| π **Identify novel classes** | KDE density estimation on a frozen mirror autoencoder (A_KC) |
|
|
39
|
+
| π **Explain the drift** | Feature-level attribution: ranks which input features caused the drift |
|
|
40
|
+
| β‘ **Serve at scale** | FastAPI REST service with `/detect` endpoint |
|
|
41
|
+
| π **Track experiments** | MLflow logs every chunk's drift severity, novelty rate, and attribution report |
|
|
42
|
+
| π₯οΈ **Visualize live** | SOC-style Streamlit dashboard with real-time charts |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Architecture
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
50
|
+
β OWADD Sentinel System β
|
|
51
|
+
β β
|
|
52
|
+
Data Stream β ββββββββββββ βββββββββββββββββββββββββββ β
|
|
53
|
+
ββββββββββββββΊ β β Chunk βββββΊβ Autoencoder A β β
|
|
54
|
+
(CSV / API / β β (200 rowsβ β (adapts on drift) β β ββββββββββββ
|
|
55
|
+
Kafka topic) β ββββββββββββ ββββββββββββ¬βββββββββββββββ ββββββΊβ MLflow β
|
|
56
|
+
β β β errors_A β β Tracking β
|
|
57
|
+
β β ββββββββββββΌβββββββββββββββ β ββββββββββββ
|
|
58
|
+
β β β Replicated T-Test β β
|
|
59
|
+
β β β (15 tests, Ξ±=0.05) β β ββββββββββββ
|
|
60
|
+
β β β Buffer size: 1000 β ββββββΊβFastAPI β
|
|
61
|
+
β β ββββββββββββ¬βββββββββββββββ β β /detect β
|
|
62
|
+
β β β DRIFT? β ββββββββββββ
|
|
63
|
+
β β ββββββββββββΌβββββββββββββββ β
|
|
64
|
+
β β β DriftAttributor β β ββββββββββββ
|
|
65
|
+
β β β (novel contribution) β ββββββΊβStreamlit β
|
|
66
|
+
β β β Which features drifted? β β βDashboard β
|
|
67
|
+
β β βββββββββββββββββββββββββββ β ββββββββββββ
|
|
68
|
+
β β β
|
|
69
|
+
β β βββββββββββββββββββββββββββ β
|
|
70
|
+
β ββββββββββΊβ Autoencoder A_KC β β
|
|
71
|
+
β β (frozen mirror) β β
|
|
72
|
+
β ββββββββββββ¬βββββββββββββββ β
|
|
73
|
+
β β errors_AKC β
|
|
74
|
+
β ββββββββββββΌβββββββββββββββ β
|
|
75
|
+
β β KDE Novelty Detector β β
|
|
76
|
+
β β Novel class proportion β β
|
|
77
|
+
β βββββββββββββββββββββββββββ β
|
|
78
|
+
βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Key design decisions (from arXiv:2605.29834):**
|
|
82
|
+
- Uses 1-D reconstruction error proxy instead of full feature vectors β memory complexity O(buffer_size) not O(n Γ d)
|
|
83
|
+
- Dual autoencoders: **A** adapts on drift; **A_KC** stays frozen to distinguish drift from novelty
|
|
84
|
+
- Replicated T-tests (r=15) reduce variance vs. a single test
|
|
85
|
+
|
|
86
|
+
**Novel contribution (OWADD Sentinel, not in the paper):**
|
|
87
|
+
- `DriftAttributor` computes per-feature reconstruction error delta β ranks features by drift contribution β makes alerts *actionable*
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## π¦ Quick Start
|
|
92
|
+
|
|
93
|
+
### Install
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone https://github.com/Venkateswara-Sahu/OWADD.git
|
|
97
|
+
cd OWADD
|
|
98
|
+
python -m venv .venv
|
|
99
|
+
.venv\Scripts\activate # Windows
|
|
100
|
+
# source .venv/bin/activate # Linux/Mac
|
|
101
|
+
pip install -e ".[dev]"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Run as a Python library
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import pandas as pd
|
|
108
|
+
from owadd_sentinel import OWADDSentinel
|
|
109
|
+
|
|
110
|
+
# Load your data
|
|
111
|
+
df = pd.read_csv("your_data.csv")
|
|
112
|
+
feature_cols = [c for c in df.columns if c != "label"]
|
|
113
|
+
X = df[feature_cols].values.astype("float32")
|
|
114
|
+
|
|
115
|
+
# Initialise
|
|
116
|
+
sentinel = OWADDSentinel(
|
|
117
|
+
feature_names=feature_cols,
|
|
118
|
+
top_k_features=5,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Phase 1: train on first batch (offline)
|
|
122
|
+
sentinel.fit(X[:200])
|
|
123
|
+
|
|
124
|
+
# Phase 2: process stream chunk by chunk
|
|
125
|
+
chunk_size = 200
|
|
126
|
+
for i in range(200, len(X), chunk_size):
|
|
127
|
+
result = sentinel.detect(X[i:i+chunk_size])
|
|
128
|
+
|
|
129
|
+
print(result)
|
|
130
|
+
# SentinelResult(chunk=3, status=β οΈ DRIFT, severity=0.93, novelty=0.0%)
|
|
131
|
+
|
|
132
|
+
if result.drift_detected:
|
|
133
|
+
for feat in result.attribution.top_features:
|
|
134
|
+
print(f" {feat['feature_name']}: {feat['contribution']:.1%} contribution")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## π₯οΈ Live Dashboard
|
|
140
|
+
|
|
141
|
+
SOC-style real-time monitoring of the NSL-KDD network traffic stream:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
pip install -e ".[dashboard]"
|
|
145
|
+
streamlit run dashboard/app.py
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Open **http://localhost:8501**, press **βΆ Start** and watch:
|
|
149
|
+
- Reconstruction error timeline update chunk by chunk
|
|
150
|
+
- Red β drift markers appear when the traffic distribution shifts
|
|
151
|
+
- Feature attribution chart reveals which network features drifted
|
|
152
|
+
- Novelty gauge tracks unknown attack class emergence
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## π REST API
|
|
157
|
+
|
|
158
|
+
FastAPI microservice for production deployment:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install -e ".[api]"
|
|
162
|
+
uvicorn api.app:app --host 0.0.0.0 --port 8000 --reload
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Open **http://localhost:8000/docs** for interactive Swagger UI.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Train on initial data
|
|
169
|
+
curl -X POST http://localhost:8000/fit \
|
|
170
|
+
-H "Content-Type: application/json" \
|
|
171
|
+
-d '{"data": [[0.1, 0.5, ...], ...]}'
|
|
172
|
+
|
|
173
|
+
# Detect drift in incoming batch
|
|
174
|
+
curl -X POST http://localhost:8000/detect \
|
|
175
|
+
-H "Content-Type: application/json" \
|
|
176
|
+
-d '{"data": [[0.3, 0.9, ...], ...]}'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Response:**
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"drift_detected": true,
|
|
183
|
+
"drift_severity": 0.93,
|
|
184
|
+
"novelty_proportion": 0.0,
|
|
185
|
+
"top_drifted_features": [
|
|
186
|
+
{"feature_name": "diff_srv_rate", "contribution": 0.23, "error_delta": 0.041},
|
|
187
|
+
{"feature_name": "service_other", "contribution": 0.07, "error_delta": 0.012}
|
|
188
|
+
],
|
|
189
|
+
"message": "β οΈ Concept drift detected (severity=0.93)"
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## π MLflow Experiment Tracking
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pip install -e ".[mlflow]"
|
|
199
|
+
|
|
200
|
+
# Run stream with experiment logging
|
|
201
|
+
python -c "
|
|
202
|
+
from data.nsl_kdd_loader import load_nsl_kdd
|
|
203
|
+
from data.stream_simulator import StreamSimulator
|
|
204
|
+
from owadd_sentinel import OWADDSentinel
|
|
205
|
+
from owadd_sentinel.logging.mlflow_logger import MLflowLogger
|
|
206
|
+
|
|
207
|
+
X, y, feat = load_nsl_kdd()
|
|
208
|
+
sim = StreamSimulator(X, y)
|
|
209
|
+
sentinel = OWADDSentinel(feature_names=feat)
|
|
210
|
+
logger = MLflowLogger(experiment_name='nsl-kdd-stream')
|
|
211
|
+
|
|
212
|
+
first = next(sim.stream(n_chunks=1))
|
|
213
|
+
sentinel.fit(first.X, verbose=False)
|
|
214
|
+
|
|
215
|
+
logger.start_run(params={'buffer_size': 1000, 'drift_threshold': 0.3})
|
|
216
|
+
for chunk in sim.stream(n_chunks=25):
|
|
217
|
+
result = sentinel.detect(chunk.X)
|
|
218
|
+
logger.log_chunk(result, ground_truth_label=chunk.dominant_class)
|
|
219
|
+
logger.end_run()
|
|
220
|
+
"
|
|
221
|
+
|
|
222
|
+
# View results
|
|
223
|
+
mlflow ui # β http://localhost:5000
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## π§ͺ Tests
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pytest tests/ -v --cov=owadd_sentinel
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
tests/test_autoencoder.py::test_autoencoder_forward_shape PASSED
|
|
236
|
+
tests/test_autoencoder.py::test_reconstruction_errors_non_negative PASSED
|
|
237
|
+
tests/test_autoencoder.py::test_mirrored_pair_identical_weights PASSED
|
|
238
|
+
tests/test_autoencoder.py::test_training_reduces_loss PASSED
|
|
239
|
+
tests/test_drift_detector.py::test_no_drift_during_warmup PASSED
|
|
240
|
+
tests/test_drift_detector.py::test_no_false_drift_on_stable_stream PASSED
|
|
241
|
+
tests/test_drift_detector.py::test_drift_detected_on_shifted_dist PASSED
|
|
242
|
+
tests/test_sentinel.py::test_sentinel_fit_and_detect PASSED
|
|
243
|
+
tests/test_sentinel.py::test_sentinel_attribution_on_drift PASSED
|
|
244
|
+
... 14 passed in 15s, 81% coverage
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## ποΈ Project Structure
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
OWADD/
|
|
253
|
+
βββ owadd_sentinel/ # Core pip package
|
|
254
|
+
β βββ core/
|
|
255
|
+
β β βββ autoencoder.py # Dual mirrored autoencoders (A and A_KC)
|
|
256
|
+
β β βββ drift_detector.py # Replicated T-Test drift detection
|
|
257
|
+
β β βββ novelty_detector.py # KDE-based novel class recognition
|
|
258
|
+
β βββ logging/
|
|
259
|
+
β β βββ mlflow_logger.py # MLflow experiment tracking
|
|
260
|
+
β βββ attribution.py # Feature-level drift attribution (novel)
|
|
261
|
+
β βββ sentinel.py # Main OWADDSentinel public API
|
|
262
|
+
β
|
|
263
|
+
βββ api/
|
|
264
|
+
β βββ app.py # FastAPI REST service
|
|
265
|
+
β βββ schemas.py # Pydantic request/response models
|
|
266
|
+
β
|
|
267
|
+
βββ data/
|
|
268
|
+
β βββ nsl_kdd_loader.py # NSL-KDD download + preprocessing
|
|
269
|
+
β βββ stream_simulator.py # 3-phase non-stationary stream
|
|
270
|
+
β
|
|
271
|
+
βββ dashboard/
|
|
272
|
+
β βββ app.py # SOC-style Streamlit dashboard
|
|
273
|
+
β
|
|
274
|
+
βββ tests/ # 14 unit + integration tests
|
|
275
|
+
βββ .github/workflows/ci.yml # GitHub Actions CI
|
|
276
|
+
βββ pyproject.toml # pip install owadd-sentinel
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## π¬ Research Basis
|
|
282
|
+
|
|
283
|
+
This project implements and extends:
|
|
284
|
+
|
|
285
|
+
> **"Open World Autoencoding Drift Detection with Novel Class Recognition in Tabular Non-stationary Data Streams"**
|
|
286
|
+
> arXiv:2605.29834
|
|
287
|
+
|
|
288
|
+
**Extensions in Vigil (not in the paper):**
|
|
289
|
+
- `DriftAttributor`: per-feature reconstruction error delta analysis β makes drift detection *actionable* by identifying which features drifted and by how much
|
|
290
|
+
- FastAPI REST service for production deployment
|
|
291
|
+
- MLflow integration for experiment tracking and model versioning
|
|
292
|
+
- SOC-style real-time monitoring dashboard
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## π οΈ Tech Stack
|
|
297
|
+
|
|
298
|
+
| Component | Technology |
|
|
299
|
+
|---|---|
|
|
300
|
+
| Core algorithm | Python, PyTorch, SciPy, scikit-learn |
|
|
301
|
+
| REST API | FastAPI, Pydantic, Uvicorn |
|
|
302
|
+
| Experiment tracking | MLflow |
|
|
303
|
+
| Dashboard | Streamlit, Plotly |
|
|
304
|
+
| Dataset | NSL-KDD (Canadian Institute for Cybersecurity) |
|
|
305
|
+
| Testing | pytest, pytest-cov |
|
|
306
|
+
| Linting | Ruff |
|
|
307
|
+
| CI/CD | GitHub Actions |
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## π License
|
|
312
|
+
|
|
313
|
+
MIT Β© Venkateswara Sahu
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
<div align="center">
|
|
318
|
+
|
|
319
|
+
**Built with the belief that ML systems should know when they're wrong.**
|
|
320
|
+
|
|
321
|
+
[β Star this repo](https://github.com/Venkateswara-Sahu/OWADD) if you find it useful
|
|
322
|
+
|
|
323
|
+
</div>
|