dqal 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.
- dqal-0.1.0/CHANGELOG.md +21 -0
- dqal-0.1.0/LICENSE +21 -0
- dqal-0.1.0/MANIFEST.in +5 -0
- dqal-0.1.0/PKG-INFO +269 -0
- dqal-0.1.0/README.md +225 -0
- dqal-0.1.0/benchmarks/__init__.py +7 -0
- dqal-0.1.0/benchmarks/run_benchmark.py +218 -0
- dqal-0.1.0/benchmarks/simulator.py +85 -0
- dqal-0.1.0/configs/default_config.yaml +32 -0
- dqal-0.1.0/dqal/__init__.py +33 -0
- dqal-0.1.0/dqal/base.py +54 -0
- dqal-0.1.0/dqal/config.py +134 -0
- dqal-0.1.0/dqal/learned_scorer.py +106 -0
- dqal-0.1.0/dqal/logger.py +190 -0
- dqal-0.1.0/dqal/orchestrator.py +221 -0
- dqal-0.1.0/dqal/scorer.py +497 -0
- dqal-0.1.0/dqal/trigger.py +131 -0
- dqal-0.1.0/dqal/wrapper.py +271 -0
- dqal-0.1.0/dqal.egg-info/PKG-INFO +269 -0
- dqal-0.1.0/dqal.egg-info/SOURCES.txt +30 -0
- dqal-0.1.0/dqal.egg-info/dependency_links.txt +1 -0
- dqal-0.1.0/dqal.egg-info/requires.txt +22 -0
- dqal-0.1.0/dqal.egg-info/top_level.txt +2 -0
- dqal-0.1.0/pyproject.toml +58 -0
- dqal-0.1.0/setup.cfg +4 -0
- dqal-0.1.0/tests/test_benchmark.py +14 -0
- dqal-0.1.0/tests/test_learned_scorer.py +48 -0
- dqal-0.1.0/tests/test_logger.py +46 -0
- dqal-0.1.0/tests/test_orchestrator.py +69 -0
- dqal-0.1.0/tests/test_scorer.py +126 -0
- dqal-0.1.0/tests/test_trigger.py +76 -0
- dqal-0.1.0/tests/test_wrapper.py +103 -0
dqal-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to DQAL will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-09-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Quality Scorer** with three sub-signals: Missingness, Drift (PSI/JS divergence), and Outlier (Isolation Forest / Mahalanobis).
|
|
13
|
+
- **3-tier gating state machine** (`SERVE` / `FLAG` / `ABSTAIN`) with hysteresis dead-band and consecutive confirmation to prevent flapping.
|
|
14
|
+
- **Model-agnostic adapter** wrapping scikit-learn estimators, PyTorch `nn.Module`, and custom callables via `DQAL.predict()`.
|
|
15
|
+
- **Privacy-preserving SQLite telemetry logger** — logs statistical metrics only; raw feature logging disabled by default.
|
|
16
|
+
- **Retraining orchestrator** with minimum data volume, label verification, and validation-gated model promotion with rollback.
|
|
17
|
+
- **Learned Quality Scorer** for training a meta-model on historical quality signals.
|
|
18
|
+
- **Terminal telemetry reporting & SQLite logger** for real-time batch metrics, gating decisions, and selective retraining history.
|
|
19
|
+
- **60-batch degradation benchmark** (`benchmarks/run_benchmark.py`) validating correlation, latency, early warning, and label safety.
|
|
20
|
+
- **CI pipeline** via GitHub Actions across Python 3.10 / 3.11 / 3.12.
|
|
21
|
+
- **YAML-based configuration** (`configs/default_config.yaml`) for weights, thresholds, hysteresis, logging, and retrain policy.
|
dqal-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DQAL Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dqal-0.1.0/MANIFEST.in
ADDED
dqal-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dqal
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DQAL — Data-Quality-Aware Learning: A real-time, model-agnostic quality assurance and selective retraining framework for ML pipelines.
|
|
5
|
+
Author-email: Pratham Khatri <prathamdkhatri@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Pratham1227-beep/dqal
|
|
8
|
+
Project-URL: Repository, https://github.com/Pratham1227-beep/dqal
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Pratham1227-beep/dqal/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Pratham1227-beep/dqal/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: machine-learning,data-quality,mlops,drift-detection,model-monitoring
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.23.0
|
|
26
|
+
Requires-Dist: pandas>=1.5.0
|
|
27
|
+
Requires-Dist: scipy>=1.9.0
|
|
28
|
+
Requires-Dist: scikit-learn>=1.1.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Provides-Extra: torch
|
|
31
|
+
Requires-Dist: torch>=2.0.0; extra == "torch"
|
|
32
|
+
Provides-Extra: viz
|
|
33
|
+
Requires-Dist: matplotlib>=3.6.0; extra == "viz"
|
|
34
|
+
Requires-Dist: seaborn>=0.12.0; extra == "viz"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: torch>=2.0.0; extra == "all"
|
|
40
|
+
Requires-Dist: matplotlib>=3.6.0; extra == "all"
|
|
41
|
+
Requires-Dist: seaborn>=0.12.0; extra == "all"
|
|
42
|
+
Requires-Dist: pytest>=7.0.0; extra == "all"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# 🛡️ DQAL — Data-Quality-Aware Learning
|
|
46
|
+
|
|
47
|
+
[](https://github.com/Pratham1227-beep/dqal)
|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://opensource.org/licenses/MIT)
|
|
50
|
+
|
|
51
|
+
> **Real-time, model-agnostic inference quality scoring, 3-tier gating (`SERVE` / `FLAG` / `ABSTAIN`), and selective retraining orchestration.**
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. Problem Statement
|
|
56
|
+
|
|
57
|
+
Deployed machine learning models fail silently. A model trained on clean, stationary data continues generating overconfident predictions when:
|
|
58
|
+
- **Upstream features drop out** (missing fields, API breaking changes, sensor timeouts)
|
|
59
|
+
- **Input distributions drift** (seasonal shifts, demographic changes, covariate shifts)
|
|
60
|
+
- **Extreme noise/outliers slip through** (hardware faults, corrupted data joins)
|
|
61
|
+
|
|
62
|
+
Traditional monitoring detects these weeks after downstream accuracy degrades. **DQAL** provides a lightweight, inline guardrail layer that inspects each prediction batch in real time, computes an interpretable Quality Score $Q \in [0, 1]$, gates execution, and safely triggers retraining.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 2. System Architecture
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
┌─────────────────────────────────────────┐
|
|
70
|
+
Incoming batch → │ 1. QUALITY SCORER │
|
|
71
|
+
of data │ • Missingness (Feature null rates) │
|
|
72
|
+
│ • Drift (Continuous PSI / Cat JS) │
|
|
73
|
+
│ • Outlier (Isolation Forest / Mahala) │
|
|
74
|
+
└────────────────────┬────────────────────┘
|
|
75
|
+
│ Q ∈ [0, 1], Sub-signals
|
|
76
|
+
▼
|
|
77
|
+
┌─────────────────────────────────────────┐
|
|
78
|
+
│ 2. TRIGGER LOGIC │
|
|
79
|
+
│ • Q > 0.80 → SERVE │
|
|
80
|
+
│ • 0.50 < Q ≤ 0.80 → FLAG │
|
|
81
|
+
│ • Q ≤ 0.50 → ABSTAIN │
|
|
82
|
+
│ • Hysteresis dead-band & confirmation │
|
|
83
|
+
└────────────────────┬────────────────────┘
|
|
84
|
+
│ Decision
|
|
85
|
+
┌────────────────────┴────────────────────┐
|
|
86
|
+
▼ ▼
|
|
87
|
+
┌───────────────────┐ ┌───────────────────┐
|
|
88
|
+
│ 3. WRAPPED MODEL │ │ 4. SQLITE LOGGER │
|
|
89
|
+
│ (Sklearn/PyTorch) │ │ (Privacy-safe: no │
|
|
90
|
+
│ Serve/Abstain │ │ raw PII by def) │
|
|
91
|
+
└───────────────────┘ └─────────┬─────────┘
|
|
92
|
+
│
|
|
93
|
+
▼
|
|
94
|
+
┌───────────────────┐
|
|
95
|
+
│ 5. ORCHESTRATOR │
|
|
96
|
+
│ • Volume checks │
|
|
97
|
+
│ • Label gating │
|
|
98
|
+
│ • Val. promotion │
|
|
99
|
+
└─────────┬─────────┘
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
┌───────────────────┐
|
|
103
|
+
│ 6. TERMINAL LOGS │
|
|
104
|
+
│ (Real-time stdout │
|
|
105
|
+
│ telemetry & Q) │
|
|
106
|
+
└───────────────────┘
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 3. Key Highlights & Features
|
|
112
|
+
|
|
113
|
+
- **⚡ Model-Agnostic Adapter**: Wraps any `scikit-learn` estimator, `PyTorch nn.Module`, or custom callable without modifying underlying model code.
|
|
114
|
+
- **📊 Interpretable Quality Score $Q$**: Combines Missingness, Population Stability Index (PSI), and Isolation Forest anomaly scores into a single metric $Q \in [0, 1]$.
|
|
115
|
+
- **🚦 3-Tier State Machine with Hysteresis**: Prevents state flapping near threshold boundaries with dead-bands and consecutive confirmation counts.
|
|
116
|
+
- **🔒 Privacy-Preserving Telemetry**: Logs statistical metrics to SQLite; disables raw feature logging by default to prevent PII exposure.
|
|
117
|
+
- **🔄 Validated Retraining & Rollback**: Enforces minimum data volume and verified label availability before retraining. Only promotes candidate models if validation accuracy beats active models.
|
|
118
|
+
- **💻 Terminal-First Telemetry & Inspection**: Real-time command-line logging of $Q$, gating decisions (`SERVE`/`FLAG`/`ABSTAIN`), sub-signal diagnostics, and model transition events directly in terminal output.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 4. Installation & Setup
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Clone repository
|
|
126
|
+
git clone https://github.com/Pratham1227-beep/dqal.git
|
|
127
|
+
cd dqal
|
|
128
|
+
|
|
129
|
+
# Install in editable mode with all optional dependencies (visualization, PyTorch, dev)
|
|
130
|
+
pip install -e ".[all]"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 5. Quickstart Example
|
|
136
|
+
|
|
137
|
+
### Wrapping a Scikit-Learn Model
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
141
|
+
from sklearn.datasets import load_breast_cancer
|
|
142
|
+
from sklearn.model_selection import train_test_split
|
|
143
|
+
from dqal import DQAL, DQALConfig
|
|
144
|
+
|
|
145
|
+
# 1. Train your baseline model
|
|
146
|
+
data = load_breast_cancer(as_frame=True)
|
|
147
|
+
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.3, random_state=42)
|
|
148
|
+
|
|
149
|
+
model = RandomForestClassifier(n_estimators=100, random_state=42).fit(X_train, y_train)
|
|
150
|
+
|
|
151
|
+
# 2. Wrap with DQAL and fit baseline distributions
|
|
152
|
+
# CRITICAL: Baseline distributions must be captured from clean training data!
|
|
153
|
+
dqal = DQAL(model=model, model_version="v1.0.0")
|
|
154
|
+
dqal.fit_baseline(X_train)
|
|
155
|
+
|
|
156
|
+
# 3. Predict on incoming batches
|
|
157
|
+
result = dqal.predict(X_test.iloc[:50])
|
|
158
|
+
|
|
159
|
+
print(f"Quality Score Q: {result.Q:.3f}")
|
|
160
|
+
print(f"Gating Decision: {result.decision}") # SERVE, FLAG, or ABSTAIN
|
|
161
|
+
print(f"Sub-signals: {result.signals}")
|
|
162
|
+
print(f"Predictions: {result.predictions[:5]}")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Wrapping a PyTorch Model
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import torch
|
|
169
|
+
import torch.nn as nn
|
|
170
|
+
from dqal import DQAL
|
|
171
|
+
|
|
172
|
+
torch_model = nn.Sequential(
|
|
173
|
+
nn.Linear(30, 16),
|
|
174
|
+
nn.ReLU(),
|
|
175
|
+
nn.Linear(16, 2)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
dqal = DQAL(model=torch_model, model_version="v1.0.0-torch")
|
|
179
|
+
dqal.fit_baseline(X_train)
|
|
180
|
+
|
|
181
|
+
result = dqal.predict(X_test.iloc[:20])
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 6. Configuration (`configs/default_config.yaml`)
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
weights:
|
|
190
|
+
missingness: 0.30
|
|
191
|
+
drift: 0.40
|
|
192
|
+
outlier: 0.30
|
|
193
|
+
|
|
194
|
+
thresholds:
|
|
195
|
+
abstain_below: 0.50
|
|
196
|
+
flag_below: 0.80
|
|
197
|
+
|
|
198
|
+
hysteresis:
|
|
199
|
+
deadband: 0.03
|
|
200
|
+
consecutive_batches: 2
|
|
201
|
+
|
|
202
|
+
logging:
|
|
203
|
+
db_path: "dqal_telemetry.db"
|
|
204
|
+
log_raw_features: false # Opt-in only
|
|
205
|
+
|
|
206
|
+
retrain:
|
|
207
|
+
min_flagged_batches: 5
|
|
208
|
+
min_samples: 100
|
|
209
|
+
require_labels: true
|
|
210
|
+
min_improvement_delta: 0.01
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 7. Empirical Validation & Benchmarks
|
|
216
|
+
|
|
217
|
+
We evaluated DQAL across a progressive 60-batch degradation simulation (`benchmarks/run_benchmark.py`):
|
|
218
|
+
1. **Pristine Baseline** (Batches 0–11)
|
|
219
|
+
2. **Mild Covariate Shift** (Batches 12–23)
|
|
220
|
+
3. **Missing Fields Spike** (Batches 24–35)
|
|
221
|
+
4. **Severe Noise & Outliers** (Batches 36–47)
|
|
222
|
+
5. **Catastrophic Drift** (Batches 48–59)
|
|
223
|
+
|
|
224
|
+
### Benchmark Results
|
|
225
|
+
|
|
226
|
+
| Metric | Target | DQAL Result | Status |
|
|
227
|
+
| :--- | :--- | :--- | :--- |
|
|
228
|
+
| **Pearson Correlation ($r$)** | $r < -0.70$ or $> 0.70$ | **$r = 0.8502$** ($p = 8.45 \times 10^{-18}$) | Passed |
|
|
229
|
+
| **Inference Overhead** | $< 50$ ms / batch | **$49.79$ ms / batch** | Passed |
|
|
230
|
+
| **Early Warning Lead Time** | $> 0$ batches | **$9$ batches lead time** | Passed |
|
|
231
|
+
| **Label Safety Enforced** | $100\%$ | **Blocked unlabelled retraining** | Passed |
|
|
232
|
+
|
|
233
|
+

|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 8. Inspecting Telemetry in Terminal
|
|
238
|
+
|
|
239
|
+
DQAL logs structured, privacy-safe metrics per batch to SQLite (`dqal_telemetry.db`) and displays real-time gating decisions and sub-signals in your terminal:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from dqal.logger import TelemetryLogger
|
|
243
|
+
|
|
244
|
+
# Inspect logged telemetry in terminal
|
|
245
|
+
logger = TelemetryLogger("dqal_telemetry.db")
|
|
246
|
+
df = logger.get_telemetry_df()
|
|
247
|
+
print(df[["batch_id", "Q", "decision", "missing_signal", "drift_signal", "outlier_signal"]].tail(10))
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Or query directly using the SQLite CLI:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
sqlite3 dqal_telemetry.db "SELECT batch_id, Q, decision, model_version FROM predictions_telemetry ORDER BY id DESC LIMIT 10;"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 9. Running Tests
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
pytest -v
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 10. Known Limitations
|
|
267
|
+
|
|
268
|
+
- **Row vs. Batch Granularity**: Scoring single isolated rows has higher variance due to sample statistics. Batch mode ($\ge 30$ rows) provides robust distribution estimation.
|
|
269
|
+
- **Concept Drift vs. Anomaly**: DQAL detects statistical covariate and quality shift. Distinguishing permanent regime shift from temporary noise is surfaced to operators via the `FLAG` state rather than automated blind retraining.
|
dqal-0.1.0/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# 🛡️ DQAL — Data-Quality-Aware Learning
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Pratham1227-beep/dqal)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
> **Real-time, model-agnostic inference quality scoring, 3-tier gating (`SERVE` / `FLAG` / `ABSTAIN`), and selective retraining orchestration.**
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Problem Statement
|
|
12
|
+
|
|
13
|
+
Deployed machine learning models fail silently. A model trained on clean, stationary data continues generating overconfident predictions when:
|
|
14
|
+
- **Upstream features drop out** (missing fields, API breaking changes, sensor timeouts)
|
|
15
|
+
- **Input distributions drift** (seasonal shifts, demographic changes, covariate shifts)
|
|
16
|
+
- **Extreme noise/outliers slip through** (hardware faults, corrupted data joins)
|
|
17
|
+
|
|
18
|
+
Traditional monitoring detects these weeks after downstream accuracy degrades. **DQAL** provides a lightweight, inline guardrail layer that inspects each prediction batch in real time, computes an interpretable Quality Score $Q \in [0, 1]$, gates execution, and safely triggers retraining.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. System Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────────────────────────────┐
|
|
26
|
+
Incoming batch → │ 1. QUALITY SCORER │
|
|
27
|
+
of data │ • Missingness (Feature null rates) │
|
|
28
|
+
│ • Drift (Continuous PSI / Cat JS) │
|
|
29
|
+
│ • Outlier (Isolation Forest / Mahala) │
|
|
30
|
+
└────────────────────┬────────────────────┘
|
|
31
|
+
│ Q ∈ [0, 1], Sub-signals
|
|
32
|
+
▼
|
|
33
|
+
┌─────────────────────────────────────────┐
|
|
34
|
+
│ 2. TRIGGER LOGIC │
|
|
35
|
+
│ • Q > 0.80 → SERVE │
|
|
36
|
+
│ • 0.50 < Q ≤ 0.80 → FLAG │
|
|
37
|
+
│ • Q ≤ 0.50 → ABSTAIN │
|
|
38
|
+
│ • Hysteresis dead-band & confirmation │
|
|
39
|
+
└────────────────────┬────────────────────┘
|
|
40
|
+
│ Decision
|
|
41
|
+
┌────────────────────┴────────────────────┐
|
|
42
|
+
▼ ▼
|
|
43
|
+
┌───────────────────┐ ┌───────────────────┐
|
|
44
|
+
│ 3. WRAPPED MODEL │ │ 4. SQLITE LOGGER │
|
|
45
|
+
│ (Sklearn/PyTorch) │ │ (Privacy-safe: no │
|
|
46
|
+
│ Serve/Abstain │ │ raw PII by def) │
|
|
47
|
+
└───────────────────┘ └─────────┬─────────┘
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
┌───────────────────┐
|
|
51
|
+
│ 5. ORCHESTRATOR │
|
|
52
|
+
│ • Volume checks │
|
|
53
|
+
│ • Label gating │
|
|
54
|
+
│ • Val. promotion │
|
|
55
|
+
└─────────┬─────────┘
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
┌───────────────────┐
|
|
59
|
+
│ 6. TERMINAL LOGS │
|
|
60
|
+
│ (Real-time stdout │
|
|
61
|
+
│ telemetry & Q) │
|
|
62
|
+
└───────────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Key Highlights & Features
|
|
68
|
+
|
|
69
|
+
- **⚡ Model-Agnostic Adapter**: Wraps any `scikit-learn` estimator, `PyTorch nn.Module`, or custom callable without modifying underlying model code.
|
|
70
|
+
- **📊 Interpretable Quality Score $Q$**: Combines Missingness, Population Stability Index (PSI), and Isolation Forest anomaly scores into a single metric $Q \in [0, 1]$.
|
|
71
|
+
- **🚦 3-Tier State Machine with Hysteresis**: Prevents state flapping near threshold boundaries with dead-bands and consecutive confirmation counts.
|
|
72
|
+
- **🔒 Privacy-Preserving Telemetry**: Logs statistical metrics to SQLite; disables raw feature logging by default to prevent PII exposure.
|
|
73
|
+
- **🔄 Validated Retraining & Rollback**: Enforces minimum data volume and verified label availability before retraining. Only promotes candidate models if validation accuracy beats active models.
|
|
74
|
+
- **💻 Terminal-First Telemetry & Inspection**: Real-time command-line logging of $Q$, gating decisions (`SERVE`/`FLAG`/`ABSTAIN`), sub-signal diagnostics, and model transition events directly in terminal output.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Installation & Setup
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Clone repository
|
|
82
|
+
git clone https://github.com/Pratham1227-beep/dqal.git
|
|
83
|
+
cd dqal
|
|
84
|
+
|
|
85
|
+
# Install in editable mode with all optional dependencies (visualization, PyTorch, dev)
|
|
86
|
+
pip install -e ".[all]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. Quickstart Example
|
|
92
|
+
|
|
93
|
+
### Wrapping a Scikit-Learn Model
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
97
|
+
from sklearn.datasets import load_breast_cancer
|
|
98
|
+
from sklearn.model_selection import train_test_split
|
|
99
|
+
from dqal import DQAL, DQALConfig
|
|
100
|
+
|
|
101
|
+
# 1. Train your baseline model
|
|
102
|
+
data = load_breast_cancer(as_frame=True)
|
|
103
|
+
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.3, random_state=42)
|
|
104
|
+
|
|
105
|
+
model = RandomForestClassifier(n_estimators=100, random_state=42).fit(X_train, y_train)
|
|
106
|
+
|
|
107
|
+
# 2. Wrap with DQAL and fit baseline distributions
|
|
108
|
+
# CRITICAL: Baseline distributions must be captured from clean training data!
|
|
109
|
+
dqal = DQAL(model=model, model_version="v1.0.0")
|
|
110
|
+
dqal.fit_baseline(X_train)
|
|
111
|
+
|
|
112
|
+
# 3. Predict on incoming batches
|
|
113
|
+
result = dqal.predict(X_test.iloc[:50])
|
|
114
|
+
|
|
115
|
+
print(f"Quality Score Q: {result.Q:.3f}")
|
|
116
|
+
print(f"Gating Decision: {result.decision}") # SERVE, FLAG, or ABSTAIN
|
|
117
|
+
print(f"Sub-signals: {result.signals}")
|
|
118
|
+
print(f"Predictions: {result.predictions[:5]}")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Wrapping a PyTorch Model
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import torch
|
|
125
|
+
import torch.nn as nn
|
|
126
|
+
from dqal import DQAL
|
|
127
|
+
|
|
128
|
+
torch_model = nn.Sequential(
|
|
129
|
+
nn.Linear(30, 16),
|
|
130
|
+
nn.ReLU(),
|
|
131
|
+
nn.Linear(16, 2)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
dqal = DQAL(model=torch_model, model_version="v1.0.0-torch")
|
|
135
|
+
dqal.fit_baseline(X_train)
|
|
136
|
+
|
|
137
|
+
result = dqal.predict(X_test.iloc[:20])
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 6. Configuration (`configs/default_config.yaml`)
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
weights:
|
|
146
|
+
missingness: 0.30
|
|
147
|
+
drift: 0.40
|
|
148
|
+
outlier: 0.30
|
|
149
|
+
|
|
150
|
+
thresholds:
|
|
151
|
+
abstain_below: 0.50
|
|
152
|
+
flag_below: 0.80
|
|
153
|
+
|
|
154
|
+
hysteresis:
|
|
155
|
+
deadband: 0.03
|
|
156
|
+
consecutive_batches: 2
|
|
157
|
+
|
|
158
|
+
logging:
|
|
159
|
+
db_path: "dqal_telemetry.db"
|
|
160
|
+
log_raw_features: false # Opt-in only
|
|
161
|
+
|
|
162
|
+
retrain:
|
|
163
|
+
min_flagged_batches: 5
|
|
164
|
+
min_samples: 100
|
|
165
|
+
require_labels: true
|
|
166
|
+
min_improvement_delta: 0.01
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 7. Empirical Validation & Benchmarks
|
|
172
|
+
|
|
173
|
+
We evaluated DQAL across a progressive 60-batch degradation simulation (`benchmarks/run_benchmark.py`):
|
|
174
|
+
1. **Pristine Baseline** (Batches 0–11)
|
|
175
|
+
2. **Mild Covariate Shift** (Batches 12–23)
|
|
176
|
+
3. **Missing Fields Spike** (Batches 24–35)
|
|
177
|
+
4. **Severe Noise & Outliers** (Batches 36–47)
|
|
178
|
+
5. **Catastrophic Drift** (Batches 48–59)
|
|
179
|
+
|
|
180
|
+
### Benchmark Results
|
|
181
|
+
|
|
182
|
+
| Metric | Target | DQAL Result | Status |
|
|
183
|
+
| :--- | :--- | :--- | :--- |
|
|
184
|
+
| **Pearson Correlation ($r$)** | $r < -0.70$ or $> 0.70$ | **$r = 0.8502$** ($p = 8.45 \times 10^{-18}$) | Passed |
|
|
185
|
+
| **Inference Overhead** | $< 50$ ms / batch | **$49.79$ ms / batch** | Passed |
|
|
186
|
+
| **Early Warning Lead Time** | $> 0$ batches | **$9$ batches lead time** | Passed |
|
|
187
|
+
| **Label Safety Enforced** | $100\%$ | **Blocked unlabelled retraining** | Passed |
|
|
188
|
+
|
|
189
|
+

|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 8. Inspecting Telemetry in Terminal
|
|
194
|
+
|
|
195
|
+
DQAL logs structured, privacy-safe metrics per batch to SQLite (`dqal_telemetry.db`) and displays real-time gating decisions and sub-signals in your terminal:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from dqal.logger import TelemetryLogger
|
|
199
|
+
|
|
200
|
+
# Inspect logged telemetry in terminal
|
|
201
|
+
logger = TelemetryLogger("dqal_telemetry.db")
|
|
202
|
+
df = logger.get_telemetry_df()
|
|
203
|
+
print(df[["batch_id", "Q", "decision", "missing_signal", "drift_signal", "outlier_signal"]].tail(10))
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Or query directly using the SQLite CLI:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
sqlite3 dqal_telemetry.db "SELECT batch_id, Q, decision, model_version FROM predictions_telemetry ORDER BY id DESC LIMIT 10;"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 9. Running Tests
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
pytest -v
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 10. Known Limitations
|
|
223
|
+
|
|
224
|
+
- **Row vs. Batch Granularity**: Scoring single isolated rows has higher variance due to sample statistics. Batch mode ($\ge 30$ rows) provides robust distribution estimation.
|
|
225
|
+
- **Concept Drift vs. Anomaly**: DQAL detects statistical covariate and quality shift. Distinguishing permanent regime shift from temporary noise is surfaced to operators via the `FLAG` state rather than automated blind retraining.
|