Edge-Deployable Anomaly Detection for Industrial IoT
My Bachelor’s thesis, built as an Undergraduate Research Fellow at the ICOM Research Group, University of Oviedo. It tests whether a machine-learning intrusion detector for Industrial IoT traffic can stay accurate under class imbalance, survive losing capture-specific features, catch attacks it was never trained on, and still run on edge hardware — four experiments on the Edge-IIoTset dataset, each isolating one constraint.
Supervised baseline: near-perfect, for the wrong reason
Tree-based ensembles (XGBoost, LightGBM, Extra Trees), tuned with SMOTE against the dataset’s heavy class imbalance, reach near-perfect scores on held-out traffic:
| Model | Accuracy | F1 (weighted) | ROC AUC |
|---|---|---|---|
| XGBoost | 0.9975 | 0.9975 | 0.9999 |
| LightGBM | 0.9974 | 0.9974 | 0.9999 |
| Extra Trees | 0.9977 | 0.9977 | 1.0000 |
SHAP analysis showed why: the top-ranked feature across all three models was frame.time.order — a property of the packet capture, not of the attack. Dropping it, along with the equally testbed-specific frame.time.delta and source/destination IP categories, cost every model about 2.6 accuracy points, quantifying how much of that near-perfect score was really about memorizing the network rather than detecting intrusions.
Reframing for zero-days: unsupervised anomaly detection
A supervised classifier can only recognize attack types it was trained on. The thesis’s central experiment reframes detection as unsupervised anomaly detection instead: each model sees only normal traffic during training and flags anything that deviates from it at test time.
| Detector | AUROC | AUPRC | F1 | Precision | Recall |
|---|---|---|---|---|---|
| Sliding-window statistics | 0.9754 | 0.9934 | 0.9683 | 0.9867 | 0.9505 |
| Autoencoder | 0.8353 | 0.8501 | 0.6954 | 0.9094 | 0.5630 |
| K-Means | 0.8154 | 0.7995 | 0.5983 | 0.8718 | 0.4554 |
| Isolation Forest | 0.5266 | 0.6435 | 0.1484 | 0.6676 | 0.0835 |
The best configuration doesn’t need a neural network at all: a sliding-window statistical baseline — z-scoring an aggregated window of live traffic against a normal-traffic profile — reaches 98.6% precision and 95.0% recall without a single attack label ever shown to the model.
Making it run on the edge
Both detector families were benchmarked on a Raspberry Pi 4 (8GB, no GPU):
| Detector | Total time (full test set) | Per-packet cost | Throughput |
|---|---|---|---|
| Sliding-window statistics | 664 ms | ≈0.00033 ms | ≈3.07M packets/s |
| Autoencoder-based detector | 148,113 ms | 0.073 ms | ≈13.7K packets/s |
The sliding-window detector is roughly 200× faster and needs nothing but NumPy — but the comparison isn’t apples-to-apples. It scores fixed-size blocks of 520 packets at a time, so it can only react once a whole window has filled, capping its reaction latency at that block size. The autoencoder-based detector scores every packet the instant it arrives, with no such lag, at the cost of a heavier per-sample computation and a neural-network runtime (ideally with hardware acceleration). Both are legitimate edge choices for different placements: the sliding-window detector suits a deeply constrained sensor node buried in high-traffic segments, while the autoencoder-based detector fits better at an edge gateway that needs per-packet reaction and can host a heavier runtime.
Full methodology, code, and reproducible figures are in the companion repository.