93 lines
3.0 KiB
Markdown
93 lines
3.0 KiB
Markdown
# TensorFlow.js Model Debugging (Keras → Browser)
|
|
|
|
**CRITICAL: When a trained Keras model works in Python but fails in the browser, check these issues.**
|
|
|
|
## 1. Normalization Mismatch (Most Common)
|
|
|
|
**The Problem:** Keras models often include preprocessing layers (like `Rescaling`) that are preserved in SavedModel export but easy to forget about.
|
|
|
|
**The Failure Pattern (January 2025):**
|
|
|
|
- Model trained with `Rescaling(scale=2.0, offset=-1.0)` layer (converts `[0,1]` → `[-1,1]`)
|
|
- Browser code assumed the layer was stripped, manually applied `[-1,1]` normalization
|
|
- Result: Double normalization, completely wrong predictions
|
|
- Python test confirmed model expects `[0,1]` input (Rescaling layer IS preserved)
|
|
|
|
**How to Debug:**
|
|
|
|
```bash
|
|
# Test the model locally with Python to find correct normalization
|
|
python scripts/test_model.py --digit 3 --count 5
|
|
|
|
# Compare different normalizations
|
|
python scripts/test_model.py --compare-norm --digit 3
|
|
```
|
|
|
|
**The Fix:** Check your model architecture for preprocessing layers:
|
|
|
|
```python
|
|
model.summary() # Look for Rescaling, Normalization layers
|
|
```
|
|
|
|
If the model has internal normalization, your browser code should NOT duplicate it.
|
|
|
|
## 2. Quantization Corruption
|
|
|
|
**The Problem:** TensorFlow.js `--quantize_uint8` flag can corrupt model weights.
|
|
|
|
**Symptoms:**
|
|
|
|
- Model works perfectly in Python
|
|
- Same model gives completely different (wrong) results in browser
|
|
- Not a normalization issue (verified with Python testing)
|
|
|
|
**The Fix:** Export without quantization:
|
|
|
|
```bash
|
|
# ❌ WRONG - quantization corrupts weights
|
|
tensorflowjs_converter --quantize_uint8 ...
|
|
|
|
# ✅ CORRECT - no quantization
|
|
tensorflowjs_converter \
|
|
--input_format=tf_saved_model \
|
|
--output_format=tfjs_graph_model \
|
|
saved_model_dir \
|
|
output_dir
|
|
```
|
|
|
|
Model size increases (556KB → 2.2MB) but predictions are correct.
|
|
|
|
## 3. Output Tensor Ordering
|
|
|
|
**The Problem:** Multi-head models may have outputs in different order between Keras and GraphModel.
|
|
|
|
**The Fix:** Detect output types by shape, not by index:
|
|
|
|
```typescript
|
|
// Heaven output: shape [batch, 1] (binary sigmoid)
|
|
// Earth output: shape [batch, 5] (5-class softmax)
|
|
const shape0Last = output[0].shape[output[0].shape.length - 1];
|
|
if (shape0Last === 1) {
|
|
// output[0] is heaven
|
|
} else if (shape0Last === 5) {
|
|
// output[0] is earth (swapped!)
|
|
}
|
|
```
|
|
|
|
## Debugging Checklist
|
|
|
|
When browser inference gives wrong results:
|
|
|
|
1. **Create a Python test script** that loads the same model and tests with known images
|
|
2. **Verify normalization** - what range does Python expect? `[0,1]` or `[-1,1]`?
|
|
3. **Check for preprocessing layers** in model architecture (`model.summary()`)
|
|
4. **Try without quantization** - re-export the model without `--quantize_uint8`
|
|
5. **Verify output ordering** - log shapes and compare to expected
|
|
6. **Test with the exact same image** in both Python and browser
|
|
|
|
## Key Files
|
|
|
|
- `scripts/test_model.py` - Python script for local model testing
|
|
- `src/lib/vision/columnClassifier.ts` - Browser inference code
|
|
- `scripts/train-column-classifier/train_model.py` - Model training and export
|