Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- monitoring.py +88 -19
monitoring.py
CHANGED
|
@@ -1,26 +1,95 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
-
import random
|
| 3 |
from datasets import load_dataset
|
| 4 |
-
from model_setup import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
|
| 8 |
-
# Take a sample of random tweets to monitor
|
| 9 |
-
test_dataset = dataset["test"]
|
| 10 |
-
sample_size = 100
|
| 11 |
-
indices = random.sample(range(len(test_dataset["text"])), sample_size)
|
| 12 |
-
texts = [test_dataset["text"][i] for i in indices]
|
| 13 |
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
|
| 23 |
-
print(
|
| 24 |
-
print(f"Negative: {distribution[0]:.2%}")
|
| 25 |
-
print(f"Neutral: {distribution[1]:.2%}")
|
| 26 |
-
print(f"Positive: {distribution[2]:.2%}")
|
|
|
|
|
|
|
|
|
|
| 1 |
from datasets import load_dataset
|
| 2 |
+
from model_setup import predict_batch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import mlflow
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
WINDOW_SIZE = 500 # tweets per monitoring window, one "day" of traffic
|
| 10 |
+
DRIFT_THRESHOLD = 0.10 # alert when the negative share grows by more than 10 points
|
| 11 |
+
BASELINE_PATH = Path("baseline.json")
|
| 12 |
+
BASELINE_WINDOWS = 4 # first windows of the stream define normal behaviour
|
| 13 |
+
MLFLOW_URI = "sqlite:///mlflow.db"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def get_window(index, size=WINDOW_SIZE):
|
| 18 |
+
"""Return one window of incoming texts. Swap this to read from a live source."""
|
| 19 |
+
test = load_dataset("cardiffnlp/tweet_eval", "sentiment")["test"]
|
| 20 |
+
start = index * size
|
| 21 |
+
return test["text"][start:start + size]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def sentiment_distribution(texts):
|
| 25 |
+
"""Share of negative, neutral and positive predictions over a list of texts."""
|
| 26 |
+
predictions = predict_batch(texts)
|
| 27 |
+
counts = np.bincount(predictions, minlength=3)
|
| 28 |
+
return (counts / len(predictions)).tolist()
|
| 29 |
+
|
| 30 |
+
def build_baseline():
|
| 31 |
+
"""Compute the reference distribution on the first windows of the stream and store it."""
|
| 32 |
+
texts = []
|
| 33 |
+
for index in range(BASELINE_WINDOWS):
|
| 34 |
+
texts += get_window(index)
|
| 35 |
+
|
| 36 |
+
distribution = sentiment_distribution(texts)
|
| 37 |
+
BASELINE_PATH.write_text(json.dumps(distribution))
|
| 38 |
+
return distribution
|
| 39 |
+
|
| 40 |
+
def load_baseline():
|
| 41 |
+
"""Read the stored baseline, building it the first time."""
|
| 42 |
+
if BASELINE_PATH.exists():
|
| 43 |
+
return json.loads(BASELINE_PATH.read_text())
|
| 44 |
+
return build_baseline()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def check_drift(current, baseline, threshold=DRIFT_THRESHOLD):
|
| 48 |
+
"""Compare the negative share against the baseline; return the shift and whether it alerts."""
|
| 49 |
+
shift = current[0] - baseline[0] # index 0 is the negative class
|
| 50 |
+
return shift, shift > threshold
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def monitor(n_windows):
|
| 54 |
+
"""Run the monitoring loop over consecutive windows; return the alerting ones."""
|
| 55 |
+
baseline = load_baseline()
|
| 56 |
+
print(f"{'baseline negative share:':<26}{baseline[0]:>8.2%}\n")
|
| 57 |
+
|
| 58 |
+
header = f"{'window':<10}{'negative':>10}{'neutral':>10}{'positive':>10}{'shift':>10}"
|
| 59 |
+
print(header)
|
| 60 |
+
print("-" * len(header))
|
| 61 |
+
|
| 62 |
+
alerts = []
|
| 63 |
+
for index in range(BASELINE_WINDOWS, BASELINE_WINDOWS + n_windows):
|
| 64 |
+
distribution = sentiment_distribution(get_window(index))
|
| 65 |
+
shift, alerting = check_drift(distribution, baseline)
|
| 66 |
+
|
| 67 |
+
mlflow.log_metrics({
|
| 68 |
+
"negative": distribution[0],
|
| 69 |
+
"neutral": distribution[1],
|
| 70 |
+
"positive": distribution[2],
|
| 71 |
+
"negative_shift": shift,
|
| 72 |
+
"alert": int(alerting),
|
| 73 |
+
}, step=index)
|
| 74 |
+
|
| 75 |
+
flag = " ALERT" if alerting else ""
|
| 76 |
+
print(f"{index:<10}{distribution[0]:>10.2%}{distribution[1]:>10.2%}"
|
| 77 |
+
f"{distribution[2]:>10.2%}{shift:>+10.2%}{flag}")
|
| 78 |
+
if alerting:
|
| 79 |
+
alerts.append(index)
|
| 80 |
|
| 81 |
+
return alerts
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
mlflow.set_tracking_uri(MLFLOW_URI)
|
| 86 |
|
| 87 |
+
with mlflow.start_run():
|
| 88 |
+
mlflow.log_params({"window_size": WINDOW_SIZE, "threshold": DRIFT_THRESHOLD})
|
| 89 |
+
alerts = monitor(n_windows=5)
|
| 90 |
|
| 91 |
+
if alerts:
|
| 92 |
+
print(f"\nDrift detected on windows: {alerts}")
|
| 93 |
+
sys.exit(1) # non-zero exit fails the job and notifies
|
| 94 |
|
| 95 |
+
print("\nNo drift detected.")
|
|
|
|
|
|
|
|
|