Upload README.md with huggingface_hub
Browse files
README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: onnx
|
| 4 |
+
tags:
|
| 5 |
+
- ad-blocking
|
| 6 |
+
- cybersecurity
|
| 7 |
+
- domain-classification
|
| 8 |
+
- dns-filtering
|
| 9 |
+
- edge-inference
|
| 10 |
+
- on-device
|
| 11 |
+
- esp8266
|
| 12 |
+
- nodemcu
|
| 13 |
+
- logistic-regression
|
| 14 |
+
language:
|
| 15 |
+
- en
|
| 16 |
+
metrics:
|
| 17 |
+
- accuracy
|
| 18 |
+
- precision
|
| 19 |
+
- recall
|
| 20 |
+
- f1
|
| 21 |
+
- roc_auc
|
| 22 |
+
model-index:
|
| 23 |
+
- name: AdVig
|
| 24 |
+
results:
|
| 25 |
+
- task:
|
| 26 |
+
type: text-classification
|
| 27 |
+
name: Domain Classification
|
| 28 |
+
dataset:
|
| 29 |
+
name: AdTrap v1 (test split)
|
| 30 |
+
type: saidutta69/AdTrap
|
| 31 |
+
metrics:
|
| 32 |
+
- type: accuracy
|
| 33 |
+
value: 0.9485
|
| 34 |
+
name: Accuracy
|
| 35 |
+
- type: precision
|
| 36 |
+
value: 0.8491
|
| 37 |
+
name: Precision
|
| 38 |
+
- type: recall
|
| 39 |
+
value: 0.7307
|
| 40 |
+
name: Recall
|
| 41 |
+
- type: f1
|
| 42 |
+
value: 0.7854
|
| 43 |
+
name: F1
|
| 44 |
+
- type: roc_auc
|
| 45 |
+
value: 0.9478
|
| 46 |
+
name: ROC AUC
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
# AdVig
|
| 50 |
+
|
| 51 |
+

|
| 52 |
+
|
| 53 |
+
> Block ads and trackers at the DNS layer before they load - **16 KB of int8 weights running on a NodeMCU**. No cloud, no runtime lists, no API calls. The offline, no-dependency Pi-hole, distilled into a single dot product.
|
| 54 |
+
|
| 55 |
+
Priorities: **Quality > Size > Speed**
|
| 56 |
+
|
| 57 |
+
**Trained on:** AdTrap v1 - 713,539 domains (93,541 ad/tracker + 619,998 legitimate), built from StevenBlack/hosts, AdAway, Yoyo, and Majestic Million.
|
| 58 |
+
|
| 59 |
+
Sibling of [saidutta69/PhishScout](https://huggingface.co/saidutta69/PhishScout).
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## Model Overview
|
| 64 |
+
|
| 65 |
+
AdVig is a tiny hybrid logistic regression that classifies any bare domain as `BLOCK` (ad/tracker) or `ALLOW` (legitimate) using the **domain string alone** - the exact input a DNS query carries. No page content, no network calls at inference time. Score = one int32 dot product over hashed character n-grams plus 34 structural features.
|
| 66 |
+
|
| 67 |
+
| Property | Value |
|
| 68 |
+
|---|---|
|
| 69 |
+
| Architecture | Logistic regression: hashed char n-grams + structural features |
|
| 70 |
+
| Features | 16,384 hashed n-gram buckets (FNV-1a, sign trick) + 34 structural |
|
| 71 |
+
| Input | Bare domain string (DNS query hostname) |
|
| 72 |
+
| Model size | **64.4 KB** float32 ONNX / **16.0 KB** int8 quantized |
|
| 73 |
+
| Training time | ~18 s CPU |
|
| 74 |
+
| Inference | **933 us/domain measured on NodeMCU @160 MHz**; ~113 us reference Python |
|
| 75 |
+
| License | MIT |
|
| 76 |
+
|
| 77 |
+
## Performance
|
| 78 |
+
|
| 79 |
+
Final model (test split, held out, group-disjoint by registrable domain, seed 42):
|
| 80 |
+
|
| 81 |
+
| Metric | float32 | int8 quantized |
|
| 82 |
+
|---|---|---|
|
| 83 |
+
| Accuracy | 0.9485 | 0.9486 |
|
| 84 |
+
| Precision | 0.8491 | 0.8597* |
|
| 85 |
+
| Recall | 0.7307 | 0.7069* |
|
| 86 |
+
| F1 | **0.7854** | **0.7862** |
|
| 87 |
+
| ROC AUC | **0.9478** | 0.9477 |
|
| 88 |
+
| False positive rate | 1.87% | 1.87% |
|
| 89 |
+
|
| 90 |
+
*int8 row evaluated at its own val-tuned threshold; quantization changed F1 by +0.0007 (hash-collision regularization).
|
| 91 |
+
|
| 92 |
+
### Why not bigger? (bucket sweep)
|
| 93 |
+
|
| 94 |
+
Smaller hashing spaces beat larger ones - collisions regularize:
|
| 95 |
+
|
| 96 |
+
| buckets | C | F1 | AUC | int8 size |
|
| 97 |
+
|---|---|---|---|---|
|
| 98 |
+
| 2^16 | 10 | 0.7271 | 0.9288 | 64 KB |
|
| 99 |
+
| 2^15 | 10 | 0.7415 | 0.9337 | 32 KB |
|
| 100 |
+
| **2^14** | **1** | **0.7854** | **0.9478** | **16 KB** |
|
| 101 |
+
| 2^13 | 10 | 0.7756 | 0.9444 | 8 KB |
|
| 102 |
+
| 2^12 | 10 | 0.7585 | 0.9372 | 4 KB |
|
| 103 |
+
|
| 104 |
+
### Baseline benchmark
|
| 105 |
+
|
| 106 |
+
Structural features alone top out at F1 0.634 (XGBoost): most blocklisted spam/parked domains have no structural tells. Lexical memory is what unlocks quality:
|
| 107 |
+
|
| 108 |
+
| model | F1 | AUC |
|
| 109 |
+
|---|---|---|
|
| 110 |
+
| gaussian_nb | 0.5674 | 0.8197 |
|
| 111 |
+
| logistic_regression (structural) | 0.6006 | 0.8298 |
|
| 112 |
+
| decision_tree d8 (structural) | 0.6157 | 0.8112 |
|
| 113 |
+
| lightgbm 30x6L15 (structural) | 0.6222 | 0.8515 |
|
| 114 |
+
| xgboost 30x4 (structural) | 0.6341 | 0.8598 |
|
| 115 |
+
| gram-only LR 2^15 | 0.6953 | 0.9180 |
|
| 116 |
+
| **AdVig hybrid LR 2^14** | **0.7854** | **0.9478** |
|
| 117 |
+
|
| 118 |
+
## On-Device Benchmarks (NodeMCU ESP8266)
|
| 119 |
+
|
| 120 |
+
Measured on hardware (Arduino core 3.1.2, 240 stratified test domains x 30 passes = 7,200 inferences per run):
|
| 121 |
+
|
| 122 |
+
| Metric | 80 MHz | 160 MHz |
|
| 123 |
+
|---|---|---|
|
| 124 |
+
| Avg latency | **1840 us/domain** | **933 us/domain** |
|
| 125 |
+
| Min / Max | 1280 / 3043 us | 650 / 1879 us |
|
| 126 |
+
| Throughput | ~543 domains/s | ~1072 domains/s |
|
| 127 |
+
| Gram-hash phase | 144 us | 77 us |
|
| 128 |
+
| Structural phase | 1685 us | 850 us |
|
| 129 |
+
|
| 130 |
+
- **Memory**: int8 weights live in flash (`PROGMEM`, 16.4 KB, zero RAM); working set is a 2048-entry tally table + bookkeeping (~7.7 KB static). Free heap: **42,192 B**.
|
| 131 |
+
- **Accuracy on-device** (balanced sample): acc 0.8792, precision **0.9789**, recall 0.7750, F1 0.8651 - bit-exact with host emulation, **100% parity (240/240)**, identical prediction bitmap at both clock speeds.
|
| 132 |
+
- **Scaling is linear with clock** (compute-bound); other MCUs scale predictably.
|
| 133 |
+
- Known headroom: the structural phase dominates (~92% of latency) due to linear PROGMEM lexicon scans; sorted arrays + binary search should roughly halve total latency.
|
| 134 |
+
|
| 135 |
+
At ~1,000 domains/s, one NodeMCU comfortably keeps up with household-scale DNS traffic.
|
| 136 |
+
|
| 137 |
+
## Use Cases
|
| 138 |
+
|
| 139 |
+
- **DNS-level Pi-hole replacement** - answer DNS queries with an on-device verdict; fully offline, zero external dependencies
|
| 140 |
+
- **Router/firewall firmware integration** - classify unknown domains at query time, complementing exact-match blocklists
|
| 141 |
+
- **Parental controls & IoT gateways** - block ad/tracker endpoints on devices that cannot run browser extensions
|
| 142 |
+
- **Privacy tooling research** - a compact baseline model for tracker-domain generalization studies
|
| 143 |
+
- **Browser/proxy pre-fetch screening** - cheap first-pass filter ahead of heavier analysis
|
| 144 |
+
|
| 145 |
+
## Features
|
| 146 |
+
|
| 147 |
+
### Structural (34)
|
| 148 |
+
|
| 149 |
+
`length`, `label_count`, `max_label_len`, `digit_count`, `max_digit_run`, `hyphen_count`, `entropy`, `vowel_ratio`, `starts_with_www`, `has_punycode`, `subdomain_depth`, `tld_trusted`, `tld_adheavy`, `tld_is_cctld`, `tld_length`, `tok_ad`, `tok_advert`, `tok_banner`, `tok_promo`, `tok_sponsor`, `tok_track`, `tok_analytics`, `tok_metrics`, `tok_telemetry`, `tok_beacon`, `tok_pixel`, `tok_tag`, `tok_click`, `tok_impression`, `tok_affiliate`, `tok_syndication`, `tok_vendor`, `tok_bigtech`, `bigtech_and_adtoken`
|
| 150 |
+
|
| 151 |
+
### Hashed lexical memory
|
| 152 |
+
|
| 153 |
+
Char 3/4/5-grams over `.domain.` hashed with FNV-1a (random sign projection) into 2^14 buckets. Inference collapses to `logit = SCALE * gram_sum + BIAS + dot(STRUCT_W, feats)` - trivially portable to any MCU in C.
|
| 154 |
+
|
| 155 |
+
## Usage
|
| 156 |
+
|
| 157 |
+
### Python (ONNX Runtime)
|
| 158 |
+
|
| 159 |
+
```python
|
| 160 |
+
import numpy as np
|
| 161 |
+
import onnxruntime as ort
|
| 162 |
+
from gramlib import hash_grams # reference hasher (in repo files)
|
| 163 |
+
from features import extract_features # reference structural extractor
|
| 164 |
+
|
| 165 |
+
sess = ort.InferenceSession("advig.onnx", providers=["CPUExecutionProvider"])
|
| 166 |
+
domain = "ads.tracker-cdn.example.com"
|
| 167 |
+
|
| 168 |
+
grams = np.zeros((1 << 14), dtype=np.float32)
|
| 169 |
+
for idx, val in hash_grams(domain, buckets=1 << 14).items():
|
| 170 |
+
grams[idx] += np.sign(val)
|
| 171 |
+
x = np.concatenate([grams, extract_features(domain)]).astype(np.float32)[None]
|
| 172 |
+
|
| 173 |
+
p_block = sess.run(["prob"], {"features": x})[0].item()
|
| 174 |
+
blocked = p_block >= 0.477
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
### ESP8266 / NodeMCU (C)
|
| 178 |
+
|
| 179 |
+
Flash `advig_weights.h` alongside the ~40-line scorer (reference firmware in the linked training repo). Decision rule on device:
|
| 180 |
+
|
| 181 |
+
```c
|
| 182 |
+
logit = GRAM_SCALE_F * gsum_int32 + BIAS_F + dot(STRUCT_W, struct_feats);
|
| 183 |
+
block = (logit >= ADVIG_T_LNIT_F);
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
## Verification & Reproducibility
|
| 187 |
+
|
| 188 |
+
- **ONNX parity** - 100% match between ONNX Runtime and closed-form sigmoid(Gemm).
|
| 189 |
+
- **On-device parity** - 100% (240/240) agreement between NodeMCU firmware and host emulation; confusion matrices identical.
|
| 190 |
+
- **Deterministic** - identical test predictions across seeds 42/1/7/123.
|
| 191 |
+
- **No leakage** - train/val/test disjoint at the registrable-domain level; synthetic subdomains inherit their parent's split.
|
| 192 |
+
- **int8 verified end-to-end** - quantized weights re-evaluated after quantization, not assumed lossless.
|
| 193 |
+
|
| 194 |
+
## Limitations
|
| 195 |
+
|
| 196 |
+
- **Enumeration ceiling** - random parked/spam domains (`05tz2e9.com`) carry zero signal in their names; no string-based model can catch them. Pair AdVig with an exact-match blocklist: the list memorizes the tail, AdVig generalizes over unseen trackers.
|
| 197 |
+
- **"ad-" prefix traps** - `adyen.com`-style collateral exists (~1.9% FPR). Raise the threshold for allow-biased operation.
|
| 198 |
+
- **English-centric lexicons** - token lists are Western-market oriented.
|
| 199 |
+
- **Feed dependence** - inherits StevenBlack/AdAway/Yoyo coverage as of build date; retrain for fresh feeds.
|
| 200 |
+
- **Dataset noise** - Majestic top-1M contains some parked/ad-heavy registrable domains treated as ALLOW.
|
| 201 |
+
|
| 202 |
+
## Training Data
|
| 203 |
+
|
| 204 |
+
AdTrap v1: 713,539 rows, 70/15/15 split by registrable domain, seed 42.
|
| 205 |
+
|
| 206 |
+
| Source | Class | Domains |
|
| 207 |
+
|---|---|---|
|
| 208 |
+
| StevenBlack hosts (ads+trackers base) | BLOCK | 93,512 |
|
| 209 |
+
| AdAway | BLOCK | 6,540 |
|
| 210 |
+
| Yoyo | BLOCK | 3,515 |
|
| 211 |
+
| Majestic Million (overlap removed) | ALLOW | 400,000 |
|
| 212 |
+
| Synthetic legit subdomains (augmentation) | ALLOW | 220,000 |
|
| 213 |
+
|
| 214 |
+
## Citation
|
| 215 |
+
|
| 216 |
+
```bibtex
|
| 217 |
+
@misc{saidutta69_2026_advig,
|
| 218 |
+
author = {Sai Dutta Abhishek Dash},
|
| 219 |
+
title = {AdVig: Tiny On-Device Ad and Tracker Domain Classifier},
|
| 220 |
+
year = {2026},
|
| 221 |
+
publisher = {Hugging Face},
|
| 222 |
+
howpublished = {\url{https://huggingface.co/saidutta69/AdVig}},
|
| 223 |
+
note = {Trained on the AdTrap dataset}
|
| 224 |
+
}
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
Built on AdTrap + StevenBlack/hosts, AdAway, Yoyo, and Majestic Million. MIT licensed.
|