soroban-abacus-flashcards/apps/web/.claude/reference/tensorflow-browser-debuggin...

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