Files
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Waiting to run
pytest / Integration (integration_tests_a) (push) Waiting to run
pytest / Integration (integration_tests_b) (push) Waiting to run
pytest / Integration (integration_tests_c) (push) Waiting to run
pytest / Integration (integration_tests_d) (push) Waiting to run
pytest / Integration (integration_tests_e) (push) Waiting to run
pytest / Integration (integration_tests_f) (push) Waiting to run
pytest / Integration (integration_tests_g) (push) Waiting to run
pytest / Integration (integration_tests_h) (push) Waiting to run
pytest / Integration (integration_tests_i) (push) Waiting to run
pytest / Integration (integration_tests_j) (push) Waiting to run
pytest / Distributed (distributed_a) (push) Waiting to run
pytest / Distributed (distributed_b) (push) Waiting to run
pytest / Distributed (distributed_c) (push) Waiting to run
pytest / Distributed (distributed_d) (push) Waiting to run
pytest / Distributed (distributed_e) (push) Waiting to run
pytest / Distributed (distributed_f) (push) Waiting to run
pytest / Minimal Install (push) Waiting to run
pytest / Event File (push) Waiting to run
pytest (slow) / py-slow (push) Waiting to run
Publish JSON Schema / publish-schema (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

161 lines
5.3 KiB
Python

# 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.")