Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 45 additions & 18 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,35 +1,62 @@
```
# Python
__pycache__/
# Compiled and build artifacts
*.pyc
*.pyo
*.pyd
*.py~
.Python
__pycache__/
*.o
*.obj
*.so
*.dll
*.exe
*.a
*.out

# Dependencies
venv/
.venv/
venv/
env/
node_modules/

# Build directories
dist/
build/
target/
*.egg-info/

# Logs and temp files
*.log
*.tmp
*.swp
*.swo

# Environment files
.env
.env.local
*.env.*

# Testing
.pytest_cache/
.coverage
coverage/
htmlcov/
# Editors
.vscode/
.idea/
*.swp
*.swo

# Build artifacts
build/
dist/
*.egg-info/
# Python specific
*.pyc
__pycache__/
*.pyo
*.pyd
.Python
*.so

# Logs
*.log
# Coverage
coverage/
htmlcov/
.coverage

# OS generated files
.DS_Store
Thumbs.db

# Model weight files (if they are large binary files)
*.npz
*.tflite
```
51 changes: 51 additions & 0 deletions phase3_wakeword/models/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# KWS Model Files

## Generated Models

This directory contains the keyword spotting (KWS) model files for Edge-TinyML.

### Files

- `model_weights.npz` (920.1 KB) - Compressed NumPy weights
- `model_config.json` (0.4 KB) - Model configuration
- `lightweight_inference.py` (3.8 KB) - NumPy inference engine
- `model_float32.tflite` - Marker file (uses NumPy backend)
- `model_dynamic.tflite` - Marker file (uses NumPy backend)
- `model_int8.tflite` - Marker file (uses NumPy backend)

### Specifications

- **Input Shape**: (40, 99, 1) - Mel spectrogram
- **Output Classes**: 10
- **Labels**: yes, no, up, down, left, right, on, off, stop, go
- **Architecture**: Two-layer neural network
- **Backend**: NumPy (TensorFlow-free)

### Usage

```python
from models.lightweight_inference import LightweightInference

engine = LightweightInference()
engine.allocate_tensors()

# Prepare input (mel spectrogram)
input_data = np.random.randn(1, 40, 99, 1).astype(np.float32)

# Run inference
engine.set_tensor(0, input_data)
engine.invoke()
output = engine.get_tensor(0)
```

### Integration with wake_word_detector.py

The detector will automatically use the NumPy backend when TensorFlow is unavailable.
No code changes required.

### Production Deployment

For production use with actual TFLite models:
1. Install TensorFlow: `pip install tensorflow`
2. Run `core_model_generator.py` to generate real TFLite files
3. The wake_word_detector.py will automatically detect and use them
129 changes: 129 additions & 0 deletions phase3_wakeword/models/lightweight_inference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""
Lightweight Inference Engine - NumPy-only KWS inference
Drop-in replacement for TFLite when TensorFlow is not available
"""

import numpy as np
from pathlib import Path
import json

class LightweightInference:
"""NumPy-based inference engine for KWS model"""

def __init__(self, model_dir=None):
if model_dir is None:
model_dir = Path(__file__).parent.parent / "models"

self.model_dir = Path(model_dir)
self.weights = None
self.config = None
self.input_details = []
self.output_details = []

self.load_model()

def load_model(self):
"""Load model weights and config"""
weights_path = self.model_dir / "model_weights.npz"
config_path = self.model_dir / "model_config.json"

if not weights_path.exists():
raise FileNotFoundError(f"Weights not found: {weights_path}")

# Load weights
data = np.load(weights_path)
self.W1 = data['W1']
self.b1 = data['b1']
self.W2 = data['W2']
self.b2 = data['b2']

# Load config
with open(config_path) as f:
self.config = json.load(f)

# Mock TFLite interface
self.input_details = [{
'index': 0,
'shape': [1, 40, 99, 1],
'dtype': np.uint8,
'quantization': (0.007874015748031496, 0) # Scale, zero_point
}]

self.output_details = [{
'index': 1,
'shape': [1, 10],
'dtype': np.uint8,
'quantization': (0.00390625, 0)
}]

print(f"✅ Model loaded from {self.model_dir}")

def allocate_tensors(self):
"""Mock TFLite method"""
pass

def get_input_details(self):
return self.input_details

def get_output_details(self):
return self.output_details

def set_tensor(self, index, data):
"""Set input tensor"""
self._input_data = data

def invoke(self):
"""Run inference"""
# Dequantize input if needed
if self._input_data.dtype == np.uint8:
scale, zero_point = self.input_details[0]['quantization']
x = (self._input_data.astype(np.float32) - zero_point) * scale
else:
x = self._input_data.astype(np.float32)

# Flatten for fully connected layers
batch_size = x.shape[0]
x = x.reshape(batch_size, -1)

# Forward pass
h = np.maximum(x @ self.W1 + self.b1, 0) # ReLU
out = h @ self.W2 + self.b2

# Softmax
exp_out = np.exp(out - out.max(axis=1, keepdims=True))
self._output_data = exp_out / exp_out.sum(axis=1, keepdims=True)

def get_tensor(self, index):
"""Get output tensor"""
# Quantize output if needed
scale, zero_point = self.output_details[0]['quantization']
out_quant = np.round(self._output_data / scale + zero_point).astype(np.uint8)
return out_quant


# Compatibility wrapper
class TFLiteInterpreterWrapper:
"""Wraps LightweightInference to match TFLite Interpreter API"""

def __init__(self, model_path):
model_dir = Path(model_path).parent
self.engine = LightweightInference(model_dir)

def allocate_tensors(self):
self.engine.allocate_tensors()

def get_input_details(self):
return self.engine.get_input_details()

def get_output_details(self):
return self.engine.get_output_details()

def set_tensor(self, index, data):
self.engine.set_tensor(index, data)

def invoke(self):
self.engine.invoke()

def get_tensor(self, index):
return self.engine.get_tensor(index)
33 changes: 33 additions & 0 deletions phase3_wakeword/models/model_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"architecture": "TwoLayerLinear",
"input_shape": [
40,
99,
1
],
"num_classes": 10,
"labels": [
"yes",
"no",
"up",
"down",
"left",
"right",
"on",
"off",
"stop",
"go"
],
"weights": {
"W1_shape": [
3960,
64
],
"W1_scale": 0.00035922162351198494,
"W2_shape": [
64,
10
],
"W2_scale": 0.00033223358332179487
}
}
26 changes: 26 additions & 0 deletions phase3_wakeword/models/model_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"architecture": "TwoLayerLinear",
"input_shape": [
40,
99,
1
],
"num_classes": 10,
"labels": [
"yes",
"no",
"up",
"down",
"left",
"right",
"on",
"off",
"stop",
"go"
],
"weights_file": "model_weights.npz",
"weights_size_kb": 920.099609375,
"backend": "numpy",
"tensorflow_required": false,
"created_by": "minimal_model_generator.py"
}
Loading
Loading