A small on-device document classifier that sorts a single image into one of:
bank_statement
invoice
other
Designed as a first-stage triage step before any heavyweight OCR or
extraction — small enough to ship inside a mobile app and run fully offline.
Recommended artifact
File
Format
Size
Top-1 acc
Use
invoice_classifier_fp32.onnx
ONNX, fp32
~5.8 MB
98.35%
Ship this.
invoice_classifier_int8_qdq.onnx
ONNX, QDQ static int8
~1.7 MB
58.85%
⚠️ Experimental — see Quantization notes.
TL;DR — use the fp32 model. It's only ~6 MB, runs in well under
100 ms per image on modern phone CPUs, and has no accuracy drop. The int8
build is included for reference but is not recommended for deployment
(details below).
Model details
Architecture: MobileNetV3-Small (torchvision mobilenet_v3_small),
ImageNet-1k pretrained backbone, final 1000-class head replaced with a
3-class linear layer.
Held-out test set: 243 images across the three classes.
fp32
Metric
Value
Top-1 accuracy
98.35%
Macro F1
0.9801
Class
F1
bank_statement
0.9783
invoice
0.9697
other
0.9924
Confusion matrix (rows = true, cols = predicted):
bank_statement
invoice
other
bank_statement
45
2
0
invoice
0
64
0
other
0
2
130
int8 (QDQ) — not recommended
Metric
Value
Top-1 accuracy
58.85%
Macro F1
0.4517
Top-1 disagreement vs fp32
40.74% (99/243)
Best result observed across MinMax / Entropy / Percentile calibration
× per-channel / per-tensor weights. All configurations produce a similar
collapse (45–58% accuracy).
Quantization notes
Post-training static quantization of MobileNetV3-Small is a known-difficult
problem. The architecture's Hardswish activations and
Squeeze-and-Excitation blocks produce activation distributions with
extreme outliers that don't fit cleanly into INT8 scales. PTQ — regardless
of QDQ vs QOperator format, calibration method, or per-channel vs
per-tensor — accumulates enough error across ~140 tensors to collapse one
or more classes.
If you need a smaller model, in increasing order of effort:
FP16 — usually within rounding error of fp32. Simplest path to ~3 MB.
Quantization-aware training (QAT) — torchvision provides
models.quantization.mobilenet_v3_small. Requires a retraining run but
typically lands within 1–2 points of fp32.
Switch architectures — MobileNetV2, EfficientNet-Lite0, or a small
ConvNeXt variant all post-train-quantize more reliably than MNV3.
The shipped int8 file is left in this repo only as evidence of the failure
mode, not as a deployable artifact.
Why QDQ format anyway? ONNX Runtime Mobile does not include
ConvInteger / MatMulInteger operators. A model quantized with
QuantFormat.QOperator or quantize_dynamic will load on desktop ORT
and then fail at runtime on mobile with code=9 (NOT_IMPLEMENTED). QDQ
keeps standard Conv / MatMul nodes surrounded by
QuantizeLinear / DequantizeLinear, which is the path ORT Mobile
executes. So if you do produce a working int8 build (e.g. via QAT),
export it as QDQ.
Limitations and bias
Domain bias toward English-language, Western-format documents.
Performance on non-Latin scripts, right-to-left layouts, and regional
statement / invoice formats has not been systematically measured.
Photo conditions matter. Heavy glare, motion blur, extreme skew
(>~15°), or occlusion shifts predictions toward other.
other is an open set. Its decision boundary is determined entirely
by the contents of the training data's other/ folder. Receipts, IDs,
screenshots, and shipping labels were included; any class not seen in
training may be classified inconsistently.
No PII handling. Documents are processed as opaque pixels; the model
does not redact or filter sensitive fields.