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