chore: import upstream snapshot with attribution
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# Colab: !pip install "ludwig>=0.14"
|
||||
"""HyperNetworkCombiner vs concat — sensor anomaly detection.
|
||||
|
||||
Generates a synthetic multi-modal sensor dataset where the correct interpretation
|
||||
of numerical sensor readings depends entirely on the sensor type (temperature,
|
||||
pressure, or humidity). Trains a baseline concat model and a HyperNetworkCombiner
|
||||
model, then prints an accuracy comparison.
|
||||
|
||||
NOTE: Requires ludwig >= 0.14 (PR #4092). The hypernetwork combiner is not
|
||||
available in earlier versions.
|
||||
|
||||
Usage:
|
||||
python train_hypernetwork.py
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from ludwig.api import LudwigModel
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Generate synthetic sensor data
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
RNG = np.random.default_rng(42)
|
||||
|
||||
N_PER_TYPE = 600
|
||||
SENSOR_TYPES = ["temperature", "pressure", "humidity"]
|
||||
|
||||
|
||||
def make_samples(sensor_type: str, n: int, rng: np.random.Generator) -> pd.DataFrame:
|
||||
"""Generate n samples for a single sensor type.
|
||||
|
||||
Each type has its own 'normal' operating range and anomaly rule so that the same raw reading can mean very different
|
||||
things depending on the type.
|
||||
"""
|
||||
if sensor_type == "temperature":
|
||||
# Normal: sensors cluster near (0, 0, 0); anomaly: sensor_a > 2.5
|
||||
sensor_a = rng.normal(0.0, 1.0, n)
|
||||
sensor_b = rng.normal(0.0, 1.0, n)
|
||||
sensor_c = rng.normal(0.0, 1.0, n)
|
||||
anomaly = (sensor_a > 2.5).astype(int)
|
||||
elif sensor_type == "pressure":
|
||||
# Normal: sensors cluster near (1, 1, 1); anomaly: sensor_b < -1.5
|
||||
sensor_a = rng.normal(1.0, 0.8, n)
|
||||
sensor_b = rng.normal(1.0, 0.8, n)
|
||||
sensor_c = rng.normal(1.0, 0.8, n)
|
||||
anomaly = (sensor_b < -0.5).astype(int)
|
||||
else: # humidity
|
||||
# Normal: sensors cluster near (-1, -1, -1); anomaly: sum > 0
|
||||
sensor_a = rng.normal(-1.0, 0.9, n)
|
||||
sensor_b = rng.normal(-1.0, 0.9, n)
|
||||
sensor_c = rng.normal(-1.0, 0.9, n)
|
||||
anomaly = ((sensor_a + sensor_b + sensor_c) > 0).astype(int)
|
||||
|
||||
return pd.DataFrame(
|
||||
{
|
||||
"sensor_a": sensor_a,
|
||||
"sensor_b": sensor_b,
|
||||
"sensor_c": sensor_c,
|
||||
"sensor_type": sensor_type,
|
||||
"anomaly": anomaly,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
frames = [make_samples(t, N_PER_TYPE, RNG) for t in SENSOR_TYPES]
|
||||
df = pd.concat(frames, ignore_index=True).sample(frac=1, random_state=42).reset_index(drop=True)
|
||||
|
||||
# Train / validation / test split (70 / 15 / 15)
|
||||
n = len(df)
|
||||
split = np.full(n, 2, dtype=int) # default: test
|
||||
idx = np.arange(n)
|
||||
RNG.shuffle(idx)
|
||||
split[idx[: int(0.70 * n)]] = 0
|
||||
split[idx[int(0.70 * n) : int(0.85 * n)]] = 1
|
||||
df["split"] = split
|
||||
|
||||
print(f"Dataset: {n} rows ({df['anomaly'].mean():.1%} anomalies)")
|
||||
print(df.groupby("sensor_type")["anomaly"].mean().rename("anomaly_rate").to_string())
|
||||
print()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Model configs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
INPUT_FEATURES = [
|
||||
{"name": "sensor_a", "type": "number", "preprocessing": {"normalization": "zscore"}},
|
||||
{"name": "sensor_b", "type": "number", "preprocessing": {"normalization": "zscore"}},
|
||||
{"name": "sensor_c", "type": "number", "preprocessing": {"normalization": "zscore"}},
|
||||
{"name": "sensor_type", "type": "category"},
|
||||
]
|
||||
|
||||
OUTPUT_FEATURES = [{"name": "anomaly", "type": "binary"}]
|
||||
|
||||
TRAINER = {"epochs": 30, "learning_rate": 0.001}
|
||||
|
||||
config_concat = {
|
||||
"model_type": "ecd",
|
||||
"input_features": INPUT_FEATURES,
|
||||
"output_features": OUTPUT_FEATURES,
|
||||
"combiner": {
|
||||
"type": "concat",
|
||||
"fc_layers": [{"output_size": 128}, {"output_size": 64}],
|
||||
},
|
||||
"trainer": TRAINER,
|
||||
}
|
||||
|
||||
config_hypernetwork = {
|
||||
"model_type": "ecd",
|
||||
"input_features": INPUT_FEATURES,
|
||||
"output_features": OUTPUT_FEATURES,
|
||||
"combiner": {
|
||||
"type": "hypernetwork",
|
||||
"hidden_size": 128,
|
||||
"hyper_hidden_size": 64,
|
||||
"output_size": 128,
|
||||
},
|
||||
"trainer": TRAINER,
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Train and evaluate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
results = []
|
||||
|
||||
for label, config in [("Concat (baseline)", config_concat), ("HyperNetwork", config_hypernetwork)]:
|
||||
print(f"{'=' * 60}")
|
||||
print(f"Training: {label}")
|
||||
print("=" * 60)
|
||||
|
||||
model = LudwigModel(config, logging_level=30)
|
||||
results_obj = model.train(dataset=df)
|
||||
print(f" Saved to: {results_obj.output_directory}")
|
||||
|
||||
test_df = df[df["split"] == 2].copy()
|
||||
predictions, _ = model.predict(dataset=test_df)
|
||||
|
||||
pred_col = "anomaly_predictions"
|
||||
correct = (predictions[pred_col].values == test_df["anomaly"].values).mean()
|
||||
|
||||
results.append({"Model": label, "Test accuracy": round(float(correct), 4)})
|
||||
print(f" Test accuracy: {correct:.4f}\n")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Print summary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
results_df = pd.DataFrame(results)
|
||||
|
||||
print("=" * 50)
|
||||
print("SENSOR ANOMALY DETECTION — SUMMARY")
|
||||
print("=" * 50)
|
||||
print(results_df.to_string(index=False))
|
||||
print("=" * 50)
|
||||
print()
|
||||
print("The HyperNetworkCombiner lets sensor_type rewrite the")
|
||||
print("transformation applied to sensor_a/b/c rather than")
|
||||
print("just concatenating all features together.")
|
||||
Reference in New Issue
Block a user