3.0 KiB
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:
# 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:
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:
# ❌ 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:
// 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:
- Create a Python test script that loads the same model and tests with known images
- Verify normalization - what range does Python expect?
[0,1]or[-1,1]? - Check for preprocessing layers in model architecture (
model.summary()) - Try without quantization - re-export the model without
--quantize_uint8 - Verify output ordering - log shapes and compare to expected
- Test with the exact same image in both Python and browser
Key Files
scripts/test_model.py- Python script for local model testingsrc/lib/vision/columnClassifier.ts- Browser inference codescripts/train-column-classifier/train_model.py- Model training and export