Files
giglez/docs/ML_DESIGN.md
T
leetcrypt 9f73595b20 feat: RTL_433 protocol database import - iteration 1/5
- Expanded protocol database from 18 → 299 signatures (16.6x increase)
- Imported 281 protocols from RTL_433 open-source database (286 total devices)
- Created automated import script: scripts/import_rtl433_protocols.py
- Generated rtl433_protocols_imported.py with timing/frequency/modulation data
- Updated protocol_database.py to include RTL433_PROTOCOLS
- All 26 tests passing

Breakdown by category:
  - Weather: 116 protocols
  - Sensors: 36 protocols
  - TPMS: 25 protocols
  - Security: 23 protocols
  - Home Automation: 18 protocols
  - Other: 50+ protocols

Frequency coverage:
  - 433.92 MHz: 248 protocols
  - 315.00 MHz: 32 protocols
  - 915.00 MHz: 1 protocol

This provides comprehensive coverage of Sub-GHz IoT devices for accurate
identification from raw RF captures.
2026-02-14 18:55:55 -08:00

809 lines
22 KiB
Markdown

# ML/Neural Network Design for RF Signal Classification
## Executive Summary
This document outlines the machine learning approach for automatic RF device identification in GigLez, focusing on Sub-GHz IoT signals (300-928 MHz). The design prioritizes practical deployment using JavaScript/TensorFlow.js for browser-based inference, with Python training pipeline.
---
## Research Foundation
### Key Papers & Approaches
1. **Deep Learning for RF Signal Classification** (Shi & Davaslioglu, 2019)
- CNN architectures for modulation recognition
- Achieves >95% accuracy above 2dB SNR
- Uses IQ sample input (128-256 samples)
2. **RF Fingerprinting for IoT** (Jian et al., 2020)
- Device-specific transmitter signatures
- Massive experimental study on real IoT devices
- Addresses resource-constrained edge deployment
3. **Practical RF Machine Learning** (Panoradio SDR)
- CNN + RNN hybrid architectures
- Categorical cross-entropy optimization
- Real-world performance validation
### State-of-the-Art Insights
**What Works:**
- CNNs excel at extracting spatial features from signal representations
- 1D CNNs on raw pulse data perform well for timing-based protocols
- 2D CNNs on spectrograms capture frequency-domain patterns
- Ensemble methods (CNN + heuristics) outperform pure ML
**Challenges:**
- Single-transmission captures (Flipper Zero) vs. continuous streams (RTL-SDR)
- Variable signal lengths require padding or dynamic architectures
- Class imbalance (1000+ garage openers, <10 weather sensors)
- Synthetic vs. real-world signal distribution shift
---
## GigLez-Specific Requirements
### Input Characteristics
**Signal Types:**
1. **Decoded (.sub KEY format)**
- Protocol, frequency, bit length, key data, timing element
- Structured metadata, high information density
- Best for heuristic matching (current system)
2. **RAW (.sub RAW format)**
- Pulse/gap timing arrays (e.g., `2980 -240 520 -980 ...`)
- Variable length (10-10,000+ samples)
- Target for ML approach
3. **BinRAW (.sub BinRAW format)**
- Binary pulse data
- Less common, lower priority
**Challenge:** 97% of Flipper Zero captures are single-transmission (1-3 repeats), not continuous streams.
### Output Requirements
**Device Classification:**
- Primary: Device type (e.g., "Garage Door Opener", "Weather Sensor - Temperature")
- Secondary: Manufacturer (e.g., "Chamberlain", "Oregon Scientific")
- Tertiary: Model (e.g., "LiftMaster 891LM", "THGN123N")
**Confidence Scoring:**
- Probabilistic output (softmax)
- Combine with heuristic matcher scores
- Threshold: >0.7 for auto-ID, 0.5-0.7 for suggestions, <0.5 unknown
---
## Proposed Architecture: Hybrid Ensemble System
### Overview
Combine **three parallel classifiers** with weighted ensemble:
```
RAW .sub file
[Feature Extraction]
├─→ 1D CNN (Temporal Features) [35% weight]
├─→ Statistical Feature Classifier [25% weight]
└─→ Heuristic Matcher (existing) [40% weight]
[Weighted Ensemble]
Device ID + Confidence
```
**Rationale:**
- Heuristic matcher handles decoded signals perfectly (1.0 confidence)
- CNN learns patterns in RAW signals the heuristics miss
- Statistical features provide interpretable fallback
- Weighted ensemble prevents over-reliance on any single method
---
## Model 1: 1D CNN for Pulse Timing Classification
### Architecture
**Input:**
- RAW pulse data, fixed length 512 samples (padded/truncated)
- Normalization: Divide by max(abs(value)) to range [-1, 1]
- Shape: `(batch_size, 512, 1)`
**Network Structure:**
```python
# TensorFlow.js equivalent structure
model = Sequential([
# Block 1: Initial feature extraction
Conv1D(64, kernel_size=16, strides=2, activation='relu', padding='same'),
BatchNormalization(),
MaxPooling1D(pool_size=4),
Dropout(0.2),
# Block 2: Mid-level features
Conv1D(128, kernel_size=8, strides=2, activation='relu', padding='same'),
BatchNormalization(),
MaxPooling1D(pool_size=4),
Dropout(0.3),
# Block 3: High-level features
Conv1D(256, kernel_size=4, strides=2, activation='relu', padding='same'),
BatchNormalization(),
GlobalAveragePooling1D(),
# Classification head
Dense(512, activation='relu'),
Dropout(0.4),
Dense(num_classes, activation='softmax')
])
```
**Hyperparameters:**
- Optimizer: Adam (lr=0.001, decay=1e-6)
- Loss: Categorical cross-entropy with label smoothing (0.1)
- Batch size: 32
- Epochs: 50 with early stopping (patience=5)
**Data Augmentation:**
- Random time stretching (0.9x - 1.1x)
- Gaussian noise injection (SNR 20-40 dB)
- Random clipping (simulate distance variations)
**Output:**
- Softmax probabilities for top 100 device classes
- Classes determined by dataset frequency (>50 examples)
---
## Model 2: Statistical Feature Classifier
### Feature Extraction (Handcrafted)
From RAW pulse data, extract 47 features:
**Timing Features (16):**
- Mean, median, std, min, max of positive pulses
- Mean, median, std, min, max of negative gaps
- Pulse/gap ratio, duty cycle
- Short pulse width, long pulse width (K-means clusters)
- Coefficient of variation for pulses and gaps
**Frequency Domain (12):**
- FFT magnitude peaks (top 5 frequencies)
- Dominant frequency, total energy, spectral centroid
- Bandwidth (95% energy containment)
**Pattern Features (10):**
- Autocorrelation peaks (indicates repeating patterns)
- Zero-crossing rate
- Peak count, valley count
- Longest run of similar-width pulses
- Entropy of pulse width distribution
**Metadata Features (9):**
- Frequency (normalized to 433/868/315/915 bands)
- Modulation type (one-hot: OOK, FSK, ASK)
- Total pulse count
- Total duration (ms)
- Average bit rate (estimated)
### Classifier
**Algorithm:** Gradient Boosted Trees (LightGBM or XGBoost)
**Rationale:**
- Handles tabular features excellently
- Fast inference (critical for browser deployment)
- Interpretable feature importance
- Robust to missing values
**Training:**
```python
import lightgbm as lgb
params = {
'objective': 'multiclass',
'num_class': num_classes,
'metric': 'multi_logloss',
'boosting_type': 'gbdt',
'num_leaves': 63,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'max_depth': 8
}
model = lgb.train(params, train_data, num_boost_round=500)
```
**Export:** Convert to ONNX for browser inference via onnxruntime-web
---
## Model 3: Heuristic Matcher (Existing System)
**Keep current implementation** from `src/matcher/`:
- ExactMatcher, PartialMatcher, PatternMatcher, etc.
- Confidence scores: 0.5-1.0
- Fast, deterministic, handles decoded signals
**Integration:** Run in parallel with ML models, ensemble at end
---
## Ensemble Strategy
### Weighted Voting
```javascript
function ensemblePredict(cnn_probs, stat_probs, heuristic_results) {
const weights = {
cnn: 0.35,
statistical: 0.25,
heuristic: 0.40
};
// Combine probabilities
let combined = {};
// CNN contribution
for (let [device_id, prob] of Object.entries(cnn_probs)) {
combined[device_id] = (combined[device_id] || 0) + prob * weights.cnn;
}
// Statistical contribution
for (let [device_id, prob] of Object.entries(stat_probs)) {
combined[device_id] = (combined[device_id] || 0) + prob * weights.statistical;
}
// Heuristic contribution (convert confidence to probability)
for (let match of heuristic_results) {
let device_id = match.device_id;
let prob = match.confidence; // Already 0-1 range
combined[device_id] = (combined[device_id] || 0) + prob * weights.heuristic;
}
// Sort by combined score
let sorted = Object.entries(combined)
.sort((a, b) => b[1] - a[1])
.slice(0, 10); // Top 10
return sorted.map(([device_id, score]) => ({
device_id: device_id,
confidence: score,
method: 'ensemble'
}));
}
```
### Dynamic Weighting
Adjust weights based on input signal type:
| Signal Type | CNN Weight | Statistical Weight | Heuristic Weight |
|-------------|------------|--------------------| -----------------|
| Decoded (KEY format) | 0.10 | 0.10 | 0.80 |
| RAW with >500 samples | 0.45 | 0.25 | 0.30 |
| RAW with <100 samples | 0.20 | 0.40 | 0.40 |
| Known protocol detected | 0.05 | 0.05 | 0.90 |
---
## Training Data Pipeline
### Dataset Structure
**Source:** Downloaded datasets (41,334 .sub files)
**Labeling Strategy:**
1. **Automatic Labels (High Confidence)**
- Use file path structure (e.g., `Weather_stations/Oregon_Scientific/...`)
- Decoded protocol name match
- RTL_433 successful decode
- **Estimated:** 15,000 high-quality labels
2. **Manual Labels (User-Submitted)**
- Upload form includes device selection
- Community verification voting
- Store in `manual_identifications` table
- **Expected:** 1,000+ labels over 6 months
3. **Semi-Supervised Learning**
- Use high-confidence heuristic matches (1.0 confidence) as pseudo-labels
- Iterative refinement: Train model → Generate predictions → Review low-confidence → Retrain
- **Potential:** 25,000+ samples
### Class Distribution Handling
**Problem:** Long-tail distribution
- Top 10 classes: 60% of data (garage door openers)
- Bottom 50 classes: 5% of data (weather sensors, TPMS)
**Solutions:**
1. **Class Weighting:**
```python
from sklearn.utils.class_weight import compute_class_weight
class_weights = compute_class_weight(
'balanced',
classes=np.unique(y_train),
y=y_train
)
```
2. **Focal Loss:** Penalize easy examples (common classes)
```python
def focal_loss(y_true, y_pred, alpha=0.25, gamma=2.0):
ce = -y_true * np.log(y_pred + 1e-7)
weight = alpha * y_true * np.power(1 - y_pred, gamma)
return np.sum(weight * ce)
```
3. **Stratified Sampling:** Ensure rare classes in each batch
### Train/Val/Test Split
```python
from sklearn.model_selection import StratifiedShuffleSplit
splitter = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(splitter.split(X, y))
# Further split training into train/val
splitter = StratifiedShuffleSplit(n_splits=1, test_size=0.15, random_state=42)
train_idx, val_idx = next(splitter.split(X[train_idx], y[train_idx]))
```
**Split:**
- Training: 68% (~28,000 samples)
- Validation: 12% (~5,000 samples)
- Test: 20% (~8,000 samples)
---
## JavaScript Deployment (TensorFlow.js)
### Browser-Based Inference
**Why Browser:**
- Instant feedback during upload
- No server cost for inference
- Privacy: .sub files never leave device for pre-screening
- Offline capability (PWA)
### Model Conversion
**1. Train in Python (TensorFlow/Keras)**
```python
model.save('models/cnn_classifier_v1.h5')
```
**2. Convert to TensorFlow.js**
```bash
tensorflowjs_converter \
--input_format=keras \
--output_format=tfjs_graph_model \
models/cnn_classifier_v1.h5 \
static/models/cnn_v1/
```
**3. Load in Browser**
```javascript
import * as tf from '@tensorflow/tfjs';
const model = await tf.loadGraphModel('/static/models/cnn_v1/model.json');
// Inference
function predict(rawPulseArray) {
// Preprocess
let normalized = rawPulseArray.map(x => x / Math.max(...rawPulseArray.map(Math.abs)));
let padded = padOrTruncate(normalized, 512);
// Convert to tensor
let tensor = tf.tensor3d([padded.map(x => [x])], [1, 512, 1]);
// Predict
let probs = model.predict(tensor);
let probsArray = await probs.data();
// Get top 5
let indexed = Array.from(probsArray).map((p, i) => [i, p]);
indexed.sort((a, b) => b[1] - a[1]);
return indexed.slice(0, 5).map(([idx, prob]) => ({
device_id: classMapping[idx],
confidence: prob
}));
}
```
### Performance Optimization
**Model Quantization:**
```bash
tensorflowjs_converter \
--input_format=keras \
--output_format=tfjs_graph_model \
--quantization_bytes=2 \
models/cnn_classifier_v1.h5 \
static/models/cnn_v1_quantized/
```
**Benefits:**
- 4x smaller model (16-bit weights vs 32-bit)
- 2x faster inference on mobile
- Minimal accuracy loss (<1%)
**Web Workers:**
```javascript
// Main thread
const worker = new Worker('/static/js/ml_worker.js');
worker.postMessage({
type: 'predict',
raw_data: pulseArray
});
worker.onmessage = (e) => {
console.log('Predictions:', e.data.predictions);
};
// Worker thread (ml_worker.js)
importScripts('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs');
let model = null;
self.onmessage = async (e) => {
if (!model) {
model = await tf.loadGraphModel('/static/models/cnn_v1/model.json');
}
const predictions = predict(e.data.raw_data);
self.postMessage({ predictions });
};
```
---
## Statistical Feature Classifier Deployment
### ONNX Runtime Web
**1. Train in Python (LightGBM)**
```python
import lightgbm as lgb
import onnxmltools
from onnxmltools.convert import convert_lightgbm
# Train model
model = lgb.train(params, train_data)
# Convert to ONNX
onnx_model = convert_lightgbm(
model,
initial_types=[('input', FloatTensorType([None, 47]))],
target_opset=12
)
onnxmltools.utils.save_model(onnx_model, 'models/stat_classifier_v1.onnx')
```
**2. Load in Browser**
```javascript
import * as ort from 'onnxruntime-web';
const session = await ort.InferenceSession.create('/static/models/stat_v1.onnx');
async function predictStatistical(features) {
// features: Float32Array of length 47
const tensor = new ort.Tensor('float32', features, [1, 47]);
const feeds = { input: tensor };
const results = await session.run(feeds);
// results.probabilities: [1, num_classes]
const probs = results.probabilities.data;
return Array.from(probs).map((prob, idx) => ({
device_id: classMapping[idx],
confidence: prob
})).sort((a, b) => b.confidence - a.confidence).slice(0, 5);
}
```
---
## Training Schedule & Versioning
### Initial Training (Week 1)
**Goal:** Establish baseline
1. Label 5,000 high-confidence samples (automatic + file paths)
2. Train CNN on 50 most common classes
3. Train statistical classifier on same 50 classes
4. Export to TensorFlow.js and ONNX
5. Deploy v1.0 models
**Metrics:**
- Top-1 accuracy: Target >70%
- Top-5 accuracy: Target >85%
- Inference time: <200ms on desktop, <500ms mobile
### Continuous Improvement (Ongoing)
**Weekly Retraining:**
1. Collect new manual labels from upload form
2. Review confidence scores on production data
3. Add low-confidence samples to manual review queue
4. Retrain models with expanded dataset
5. A/B test new model vs. current model
6. Deploy if metrics improve by >2%
**Version Tracking:**
```
models/
├── cnn/
│ ├── v1.0_2026-01-15/
│ │ ├── model.json
│ │ ├── group1-shard1of2.bin
│ │ ├── group1-shard2of2.bin
│ │ ├── metadata.json (accuracy, classes, date)
│ │ └── training_log.txt
│ ├── v1.1_2026-01-22/
│ └── latest → v1.1_2026-01-22
├── statistical/
│ ├── v1.0_2026-01-15/
│ │ ├── model.onnx
│ │ ├── feature_metadata.json
│ │ └── training_log.txt
│ └── latest → v1.0_2026-01-15
└── ensemble_config.json
```
---
## Evaluation Metrics
### Model Performance
**Classification Metrics:**
- Top-1 Accuracy
- Top-5 Accuracy
- Macro-averaged F1 (handle class imbalance)
- Per-class Precision/Recall (focus on rare classes)
**Confidence Calibration:**
- Expected Calibration Error (ECE)
- Reliability diagram (predicted prob vs. actual accuracy)
**Ensemble Performance:**
- Improvement over best single model
- Disagreement analysis (where models differ)
### Production Metrics
**User Behavior:**
- Auto-accept rate (user confirms ML prediction without editing)
- Manual correction rate
- "Unknown device" submission rate
**System Performance:**
- Inference latency (p50, p95, p99)
- Model download size
- Cache hit rate
**Data Quality:**
- Label confidence distribution
- Inter-annotator agreement (when multiple users label same capture)
---
## Fallback Strategy & Unknown Devices
### Confidence Thresholds
```javascript
function interpretConfidence(max_confidence, predictions) {
if (max_confidence >= 0.85) {
return {
decision: 'auto_identify',
message: 'High confidence match',
show_alternatives: false
};
} else if (max_confidence >= 0.65) {
return {
decision: 'suggest',
message: 'Likely match - please verify',
show_alternatives: true,
top_k: 3
};
} else if (max_confidence >= 0.40) {
return {
decision: 'multiple_options',
message: 'Multiple possibilities detected',
show_alternatives: true,
top_k: 5
};
} else {
return {
decision: 'unknown',
message: 'Unknown device - please help us identify',
show_manual_form: true
};
}
}
```
### Unknown Device Handling
**UI Flow:**
1. Show "Unknown Device" badge
2. Display extracted features (frequency, protocol, etc.)
3. Provide search box to find similar devices in database
4. Allow manual device creation with form:
- Manufacturer (text input with autocomplete)
- Model (text input)
- Device type (dropdown: garage door, weather sensor, etc.)
- Notes (textarea)
- Upload photo (optional)
**Backend:**
- Store in `manual_identifications` table
- Flag for community review
- After 3+ confirmations, add to training set
- Retrain model in next batch
---
## Privacy & Security Considerations
### Browser-Based ML Benefits
1. **Data Privacy:** Raw .sub files processed locally, not sent to server until user confirms
2. **Bandwidth:** Only send metadata + matched device ID, not full signal
3. **Offline Mode:** Model cached, works without internet
### Model Security
**Challenges:**
- Models are public (JavaScript), can be reverse-engineered
- Adversarial attacks possible (craft .sub files to fool classifier)
**Mitigations:**
1. **Ensemble Defense:** Hard to fool all 3 classifiers simultaneously
2. **Server-Side Validation:** Re-run classification on server, flag large discrepancies
3. **Rate Limiting:** Prevent automated adversarial search
4. **Community Verification:** Human oversight on all auto-IDs
---
## Implementation Roadmap
### Phase 1: Training Infrastructure (Week 1-2)
- [ ] Dataset labeling pipeline (automatic + file path extraction)
- [ ] Feature extraction code (statistical features)
- [ ] CNN training script (TensorFlow/Keras)
- [ ] Statistical classifier training (LightGBM)
- [ ] Model evaluation suite
- [ ] Export to TensorFlow.js and ONNX
### Phase 2: Browser Integration (Week 3)
- [ ] JavaScript .sub parser (port from Python)
- [ ] TensorFlow.js inference pipeline
- [ ] ONNX Runtime Web integration
- [ ] Ensemble voting logic
- [ ] Web Worker for async processing
- [ ] Model caching and versioning
### Phase 3: UI/UX (Week 4)
- [ ] Upload form with real-time prediction
- [ ] Confidence visualization (progress bars, badges)
- [ ] Manual labeling interface
- [ ] Device search and selection
- [ ] Photo upload for unknown devices
### Phase 4: Production Pipeline (Week 5-6)
- [ ] Automatic matching on server after upload
- [ ] Background job queue (Celery/RQ)
- [ ] Store ensemble results in `capture_matches`
- [ ] API endpoints for match queries
- [ ] Community verification system
### Phase 5: Continuous Learning (Ongoing)
- [ ] Weekly retraining pipeline
- [ ] A/B testing framework
- [ ] Model performance monitoring
- [ ] Active learning (select most informative samples for labeling)
---
## Success Criteria
### Short-Term (3 Months)
- [ ] Deploy v1.0 models (CNN + Statistical + Heuristic ensemble)
- [ ] Achieve 75% top-1 accuracy on test set
- [ ] Achieve 90% top-5 accuracy on test set
- [ ] <300ms browser inference latency
- [ ] 1,000+ manual labels collected
- [ ] 60% auto-accept rate (users confirm without editing)
### Long-Term (12 Months)
- [ ] 85% top-1 accuracy (adding 10,000+ manual labels)
- [ ] 95% top-5 accuracy
- [ ] Support 200+ device classes
- [ ] 75% auto-accept rate
- [ ] Active learning pipeline reduces manual labeling by 50%
- [ ] Mobile app integration (TensorFlow Lite)
---
## Alternative Approaches Considered
### Transformer-Based Models
**Pros:**
- State-of-the-art for sequence data
- Attention mechanism captures long-range dependencies
**Cons:**
- Large model size (>10MB, too big for browser)
- Slower inference (100ms+ on desktop)
- Requires more training data (100k+ samples)
**Decision:** Revisit when dataset grows to 50k+ samples
### Autoencoder + Clustering
**Pros:**
- Unsupervised, doesn't require labels
- Can discover new device types automatically
**Cons:**
- No direct classification, requires post-processing
- Cluster assignment unstable with new data
**Decision:** Use for exploratory analysis, not production
### On-Device Training (Federated Learning)
**Pros:**
- Users contribute to model without sharing data
- Personalized models (local RF environment)
**Cons:**
- Complex infrastructure (TensorFlow Federated)
- Slow convergence, communication overhead
**Decision:** Future enhancement (Phase 2, 12+ months)
---
## Conclusion
The proposed hybrid ensemble system balances:
- **Accuracy:** Multi-model approach covers diverse signal types
- **Speed:** Browser-based inference, <300ms latency
- **Deployability:** TensorFlow.js + ONNX, standard web stack
- **Scalability:** Continuous learning from user feedback
**Next Steps:**
1. Label initial 5,000 samples
2. Train baseline models
3. Implement browser inference pipeline
4. Deploy v1.0 and collect production data
**Key Innovation:**
Combining traditional signal processing (heuristics), statistical ML (interpretable features), and deep learning (CNN) in a weighted ensemble provides robustness and explainability while achieving high accuracy.