|
| 1 | +# Harmonic Anomaly Detection: When Attractor-Bucketing Beats IsolationForest (and When It Doesn't) |
| 2 | + |
| 3 | +> A documented comparison of OMNIcode's `harmonic_anomaly` library against scikit-learn's `IsolationForest` on three datasets — synthesized credential stuffing, a real network-intrusion benchmark (NSL-KDD), and a three-attack signature zoo. Honest about wins and losses. |
| 4 | +
|
| 5 | +## TL;DR |
| 6 | + |
| 7 | +| Dataset | Top-K | Harmonic | IsolationForest | Winner | |
| 8 | +|---|---|:---:|:---:|---| |
| 9 | +| Credential stuffing (synthesized, multi-dim) | K=10 | **10/10** | 7/10 | **Harmonic** | |
| 10 | +| Credential stuffing | K=25 | **25/25** | 17/25 | Harmonic | |
| 11 | +| Credential stuffing | K=50 | **50/50** | 40/50 | Harmonic | |
| 12 | +| Attack zoo: exfiltration + scraping + DDoS | K=10×3 | **30/30** | unmeasured | Harmonic (all 100%) | |
| 13 | +| Power-law latency outliers (synthesized, 1-D) | K=5 | **4/5** | 0/5 | **Harmonic** | |
| 14 | +| Power-law latency outliers | K=30 | 5/30 | **15/30** | IF | |
| 15 | +| NAB realKnownCause (1-D time series) | K=10 windows | 7/19 | 7/19 | **Tie** | |
| 16 | +| **NSL-KDD network intrusion (real)** | K=10 | 7/10 | **9/10** | **IF** | |
| 17 | +| NSL-KDD | K=50 | 42/50 | **45/50** | IF | |
| 18 | +| NSL-KDD | K=500 | 348/500 | 351/500 | Tie | |
| 19 | + |
| 20 | +**The pattern:** harmonic wins on *structural* anomalies (rare combinations of normal-looking values), loses on *magnitude* anomalies (values that are simply unusual in scale). NAB and NSL-KDD are mostly magnitude anomalies; credential stuffing is structural. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## What the harmonic detector does |
| 25 | + |
| 26 | +For each row in a tabular dataset: |
| 27 | + |
| 28 | +1. Bucket each feature dimension to a Fibonacci attractor via `fold(value)` or `fold(log10(value) * scale)`. |
| 29 | +2. Build a frequency histogram per dimension over those buckets. |
| 30 | +3. Score each row = sum over dimensions of `-log(p_dim_bucket)`. High score = the row sits in the tail of MULTIPLE dimensions simultaneously. |
| 31 | + |
| 32 | +The full algorithm fits in 40 lines of OMC (see [`examples/lib/harmonic_anomaly.omc`](../examples/lib/harmonic_anomaly.omc)). No training, no hyperparameters, deterministic, single-pass over data. |
| 33 | + |
| 34 | +```omc |
| 35 | +import "harmonic_anomaly" as ha; |
| 36 | +
|
| 37 | +h det = ha.new(["latency", "status", "endpoint", "hour"]); |
| 38 | +ha.set_strategy(det, 1, "discrete"); # status_code is categorical |
| 39 | +ha.set_strategy(det, 2, "discrete"); # endpoint_id is categorical |
| 40 | +ha.set_strategy(det, 3, "modulo"); # hour-of-day is small periodic |
| 41 | +
|
| 42 | +ha.fit(det, training_rows); |
| 43 | +h alerts = ha.top_k(det, all_rows, 10); |
| 44 | +``` |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## Result 1: Credential stuffing (the strongest win) |
| 49 | + |
| 50 | +**Setup:** 5000 normal HTTP requests + 50 injected credential-stuffing rows. Each row has 4 features: `[latency_ms, status_code, endpoint_id, hour_of_day]`. The attack pattern is `(15ms latency, status=401, endpoint=8 /api/login, hour=3am)`. |
| 51 | + |
| 52 | +Every individual value in an attack row is normal-looking: |
| 53 | +- 15ms latency happens (cached responses) |
| 54 | +- status=401 happens (~1.5% of bulk traffic) |
| 55 | +- /api/login (endpoint 8) sees occasional legitimate traffic |
| 56 | +- 3am has off-peak users |
| 57 | + |
| 58 | +The TUPLE is the anomaly. |
| 59 | + |
| 60 | +**Result:** |
| 61 | +``` |
| 62 | + K=10 K=25 K=50 K=100 |
| 63 | + IsolationForest 7/10 17/25 40/50 50/100 |
| 64 | + OMC harmonic 10/10 25/25 50/50 50/100 |
| 65 | +``` |
| 66 | + |
| 67 | +Harmonic catches every credential-stuffing row in the top 10, then top 25, then top 50. IsolationForest catches some but mixes in unrelated magnitude outliers (large 500-error responses, slow batch jobs). |
| 68 | + |
| 69 | +**Why harmonic wins here:** the credential-stuffing pattern is *exactly* the kind of structural anomaly sum-of-marginal-log-rarities targets. Each dimension's bucket is uncommon but not impossible; the rarity multiplies across dimensions. |
| 70 | + |
| 71 | +**Reproduction:** |
| 72 | +```bash |
| 73 | +./target/release/omnimcode-standalone examples/datascience/multidim_anomaly.omc |
| 74 | +``` |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## Result 2: Three-attack zoo (clean sweep) |
| 79 | + |
| 80 | +**Setup:** Three separate experiments, each with 1000 normal rows + 15 injected attacks of a specific type. |
| 81 | + |
| 82 | +1. **Insider exfiltration**: huge response sizes (80-120KB), to a rare endpoint, during business hours, low request count |
| 83 | +2. **API abuse / scraping**: status=200 (all successful), every endpoint, any hour, extreme request rate |
| 84 | +3. **DDoS pattern**: tiny latency (3-10ms), mixed 200/503 status, single entry endpoint, off-peak hours |
| 85 | + |
| 86 | +**Result (top-10 per scenario):** |
| 87 | +``` |
| 88 | + Insider exfiltration : harmonic 10/10 (100% precision) |
| 89 | + API abuse / scraping : harmonic 10/10 (100% precision) |
| 90 | + DDoS pattern : harmonic 10/10 (100% precision) |
| 91 | + Aggregate : 30/30 across all three scenarios |
| 92 | +``` |
| 93 | + |
| 94 | +All three attack signatures share the "normal per dim, anomalous in tuple" structure. Harmonic catches all of them. |
| 95 | + |
| 96 | +**Reproduction:** |
| 97 | +```bash |
| 98 | +./target/release/omnimcode-standalone examples/datascience/anomaly_attack_zoo.omc |
| 99 | +``` |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Result 3: Power-law latency outliers (mixed) |
| 104 | + |
| 105 | +**Setup:** 1000 Pareto-distributed API latencies + 30 injected anomalies of two kinds: |
| 106 | +- **On-attractor outliers** (15): large but log-aligned values (100ms, 1000ms — slow batch jobs, expected outliers) |
| 107 | +- **Between-attractor anomalies** (15): large AND off-grid (317ms, 731ms — system thrashing, GC pauses, lock contention) |
| 108 | + |
| 109 | +Detection target: catch the between-attractor anomalies (real incidents), ignore the on-attractor ones (slow but routine). |
| 110 | + |
| 111 | +**Result:** |
| 112 | +``` |
| 113 | + K=5 K=10 K=20 K=30 |
| 114 | + IsolationForest 0/5 5/10 8/20 15/30 |
| 115 | + OMC harmonic 4/5 5/10 5/20 5/30 |
| 116 | +``` |
| 117 | + |
| 118 | +At K=5 (the alert-budget regime — what oncall actually pages on), harmonic gets 4/5 between-attractor anomalies; IF gets 0/5 because it picks the largest magnitudes first (which are the on-attractor "expected slow" values). |
| 119 | + |
| 120 | +At K=30, IF eventually catches all 15 between-attractor anomalies plus all 15 on-attractor ones; harmonic plateaus at 5. |
| 121 | + |
| 122 | +**Honest take:** harmonic wins on the metric that matters in production (low-K precision) but loses on broad recall. Different optimization targets. |
| 123 | + |
| 124 | +**Reproduction:** |
| 125 | +```bash |
| 126 | +./target/release/omnimcode-standalone examples/datascience/anomaly_detection.omc |
| 127 | +``` |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +## Result 4: NAB realKnownCause (honest tie) |
| 132 | + |
| 133 | +**Setup:** Numenta Anomaly Benchmark — canonical labeled 1-D time-series dataset for anomaly detection. Seven real production traces (AWS CloudWatch CPU, ad exchange, NYC taxi, EC2 latency, etc.) with hand-labeled anomaly windows. |
| 134 | + |
| 135 | +Metric: how many distinct labeled windows the top-K picks cover (NMS-spread to prevent stacking on one spike). |
| 136 | + |
| 137 | +**Result:** |
| 138 | +``` |
| 139 | + windows IF@K=10 H@K=10 IF@K=20 H@K=20 |
| 140 | + ambient_temp 2 1/2 1/2 1/2 1/2 |
| 141 | + cpu_misconfig 1 1/1 1/1 1/1 1/1 |
| 142 | + ec2_latency 3 1/3 1/3 1/3 1/3 |
| 143 | + machine_temp 4 1/4 1/4 1/4 1/4 |
| 144 | + nyc_taxi 5 1/5 1/5 1/5 1/5 |
| 145 | + rogue_agent_hold 2 1/2 1/2 1/2 1/2 |
| 146 | + rogue_agent_updown 2 1/2 1/2 1/2 1/2 |
| 147 | +
|
| 148 | + TOTALS: 19 7/19 7/19 7/19 7/19 |
| 149 | +``` |
| 150 | + |
| 151 | +Both detectors tie at 7/19. The discriminator works as expected (catches the largest anomaly per series) but neither captures multiple distinct windows. |
| 152 | + |
| 153 | +**Honest take:** beating IF on NAB requires real time-series machinery — CUSUM (cumulative change-point detection), seasonality decomposition via FFT, or HMM/LSTM autoencoders. Numenta's own HTM detector gets ~70%; Twitter's ADVec gets ~60%; naive top-K detectors (us and IF) sit at the 30-40% baseline tier. |
| 154 | + |
| 155 | +The NAB result documents what doesn't work — and where the next architectural move would have to land. |
| 156 | + |
| 157 | +**Reproduction:** |
| 158 | +```bash |
| 159 | +./target/release/omnimcode-standalone examples/datascience/nab_validation.omc |
| 160 | +./target/release/omnimcode-standalone examples/datascience/nab_time_aware.omc # 3 iterations of harmonic, all still 7/19 |
| 161 | +``` |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Result 5: NSL-KDD network intrusion (honest loss) |
| 166 | + |
| 167 | +**Setup:** Real labeled network intrusion dataset from University of New Brunswick. 22,544 captured connections; we use a 5000-row sample with 2147 normal + 2853 attacks across many classes (neptune DoS, mscan, satan, smurf, warezmaster, etc.). Each row has 41 features; we use 6 numeric ones (duration, src/dst bytes, count, srv_count, dst_host_count). |
| 168 | + |
| 169 | +**Result:** |
| 170 | +``` |
| 171 | + K=10 K=50 K=100 K=500 |
| 172 | + IsolationForest 9/10 45/50 92/100 351/500 |
| 173 | + OMC harmonic 7/10 42/50 76/100 348/500 |
| 174 | +``` |
| 175 | + |
| 176 | +IsolationForest wins at low K (9/10 vs 7/10) and the gap widens through K=100, then closes again by K=500. |
| 177 | + |
| 178 | +Looking at IF's top picks: 9 of 10 are labeled `smurf` (a volumetric ICMP flood attack — huge byte counts). |
| 179 | +Looking at harmonic's top picks: a mix of `mscan` (port scanning), `warezmaster` (privilege escalation), `back` (buffer overflow), `smurf`. |
| 180 | + |
| 181 | +**Why IF wins here:** NSL-KDD's labeled attacks are dominated by *volumetric* events — DoS floods with massive byte counts. IF picks magnitude outliers first; the labeled attacks ARE magnitude outliers. Harmonic spreads picks across diverse attack types but lower per-pick precision. |
| 182 | + |
| 183 | +**Why harmonic still has value here:** look at the *diversity* of what each detector flags. IF stacks on smurf because every smurf row looks the same in magnitude space. Harmonic finds mscan + warezmaster + back + smurf — multiple distinct attack patterns instead of N redundant flags of one. |
| 184 | + |
| 185 | +For an SRE on a tight alert budget hunting unknown threats, "diversity in the top 10" can matter more than "raw precision per pick." For a known DoS-dominated threat model, IF is the right tool. |
| 186 | + |
| 187 | +**Reproduction:** |
| 188 | +```bash |
| 189 | +# Data is committed at examples/datascience/nsl_kdd_data/sample_5k.csv |
| 190 | +./target/release/omnimcode-standalone examples/datascience/nsl_kdd_validation.omc |
| 191 | +``` |
| 192 | + |
| 193 | +--- |
| 194 | + |
| 195 | +## The pattern across all five datasets |
| 196 | + |
| 197 | +| Anomaly type | Harmonic | IsolationForest | |
| 198 | +|---|:---:|:---:| |
| 199 | +| **Structural** (rare combination of normal-looking values) | ✅ Wins decisively | ❌ Mixes in magnitude outliers | |
| 200 | +| **Multi-dim attack signatures** (different per dim, anomalous as tuple) | ✅ 30/30 across three patterns | not measured | |
| 201 | +| **Top-of-queue alert precision** (low-K regime on power-law data) | ✅ 4/5 vs 0/5 | ❌ Picks magnitude outliers | |
| 202 | +| **Broad recall** (K spans most of dataset) | ❌ Plateaus | ✅ Reaches saturation | |
| 203 | +| **1-D time series with extreme spikes** (NAB) | Tie at naive baseline | Tie at naive baseline | |
| 204 | +| **Volumetric attacks** (DoS, brute force, huge magnitudes) | ❌ Spreads picks across types | ✅ Wins on precision | |
| 205 | + |
| 206 | +**The honest framing for production use:** |
| 207 | + |
| 208 | +- **Use `harmonic_anomaly` when:** your threat model includes credential stuffing, account takeover, exfiltration via normal-looking traffic, low-and-slow attacks, multi-vector campaigns, or any "looks normal per dim, suspicious in aggregate" pattern. |
| 209 | +- **Use `IsolationForest` when:** your threat model is dominated by volumetric attacks (DoS, brute force), high-magnitude resource misuse, or anything where "biggest spike = real incident." |
| 210 | +- **Use both** if your alert budget allows — they catch different things and the overlap is small. |
| 211 | + |
| 212 | +--- |
| 213 | + |
| 214 | +## Why this matters |
| 215 | + |
| 216 | +Multi-dim structural anomaly detection has been an active research area for 20 years. The current production tooling — IsolationForest, Local Outlier Factor, one-class SVM — was designed for magnitude detection on roughly-Gaussian data. None of them have attractor-bucketing as a first-class primitive. |
| 217 | + |
| 218 | +OMC's `harmonic_anomaly` is 40 lines of OMC on top of `fold()` and `harmonic_partition`. It catches a class of real attack signatures that scikit-learn's tools genuinely miss at low K. |
| 219 | + |
| 220 | +That's not magic. That's not "we replaced IsolationForest." That's: a specific algorithmic primitive (Fibonacci-attractor bucketing) is the right fit for a specific class of anomalies (structural / multi-vector). Knowing which tool to use when is the engineering work; having the tool available is the contribution. |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## Installing + using |
| 225 | + |
| 226 | +```bash |
| 227 | +# Install the library |
| 228 | +omnimcode-standalone --install harmonic_anomaly |
| 229 | + |
| 230 | +# Or from URL |
| 231 | +omnimcode-standalone --install https://raw.githubusercontent.com/RandomCoder-lab/OMC/main/examples/lib/harmonic_anomaly.omc |
| 232 | + |
| 233 | +# Use it |
| 234 | +cat > detect.omc <<'EOF' |
| 235 | +import "harmonic_anomaly" as ha; |
| 236 | +h det = ha.new(["latency", "status", "endpoint", "hour"]); |
| 237 | +ha.set_strategy(det, 1, "discrete"); |
| 238 | +ha.set_strategy(det, 2, "discrete"); |
| 239 | +ha.set_strategy(det, 3, "modulo"); |
| 240 | +ha.fit(det, training_rows); |
| 241 | +h alerts = ha.top_k(det, all_rows, 10); |
| 242 | +println(alerts); |
| 243 | +EOF |
| 244 | +omnimcode-standalone detect.omc |
| 245 | +``` |
| 246 | + |
| 247 | +Source: [`examples/lib/harmonic_anomaly.omc`](../examples/lib/harmonic_anomaly.omc) (~150 lines). |
| 248 | + |
| 249 | +Tutorial: [`examples/datascience/anomaly_tutorial.omc`](../examples/datascience/anomaly_tutorial.omc). |
| 250 | + |
| 251 | +Tests: [`examples/tests/test_harmonic_libs.omc`](../examples/tests/test_harmonic_libs.omc) (18 tests, all passing). |
| 252 | + |
| 253 | +--- |
| 254 | + |
| 255 | +## What's not done |
| 256 | + |
| 257 | +- Time-aware anomaly detection (CUSUM, FFT seasonality, HMM) — would be needed to beat IF on NAB. |
| 258 | +- Real production deployment — synthetic + benchmark wins are encouraging but not enterprise proof. |
| 259 | +- Streaming / incremental fit — currently `fit()` is one-shot; `update()` for online learning is on the roadmap. |
| 260 | +- Multi-modal data (text + numeric + categorical) — current bucketing only handles scalar dims. |
| 261 | + |
| 262 | +These are honest gaps. The wins documented above hold within the regime they're measured in. The pattern is the contribution — knowing structural anomalies need structural detection isn't novel; having a one-line OMC library that demonstrates the difference quantitatively is. |
0 commit comments