vivekkaushal commited on
Commit
186406f
Β·
verified Β·
1 Parent(s): 3bdf110

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ invoice_classifier_fp32.onnx.data filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,240 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - image-classification
5
+ - onnx
6
+ - onnxruntime
7
+ - mobilenet
8
+ - mobile
9
+ - on-device
10
+ - document-classification
11
+ - quantized
12
+ - int8
13
+ - qdq
14
+ library_name: onnx
15
+ pipeline_tag: image-classification
16
+ metrics:
17
+ - accuracy
18
+ base_model: timm/mobilenetv3_small_100.lamb_in1k
19
+ datasets: []
20
+ ---
21
+
22
+ # tally-ocr-document-classifier
23
+
24
+ A small on-device document classifier that sorts a single image into one of:
25
+
26
+ - `bank_statement`
27
+ - `invoice`
28
+ - `other`
29
+
30
+ It is the first-stage triage model in the [Tally OCR](https://github.com/) Flutter
31
+ app β€” every uploaded or scanned page hits this model before any OCR or
32
+ downstream extraction is attempted, so it has to be **fast, small, and run
33
+ fully offline**.
34
+
35
+ The repo ships two artifacts:
36
+
37
+ | File | Format | Size | Use |
38
+ |------|--------|-----:|------|
39
+ | `invoice_classifier_int8_qdq.onnx` | ONNX, **QDQ static int8** | ~1.7 MB | **Ship this on-device.** Runs on ONNX Runtime Mobile. |
40
+ | `invoice_classifier_fp32.onnx` | ONNX, fp32 | ~5.8 MB | Reference / desktop / accuracy comparisons. |
41
+
42
+ ## Model details
43
+
44
+ - **Architecture**: MobileNetV3-Small (torchvision `mobilenet_v3_small`),
45
+ ImageNet-1k pretrained backbone, final 1000-class head replaced with a
46
+ 3-class linear layer.
47
+ - **Parameters**: ~1.5 M.
48
+ - **Input**: 1 Γ— 3 Γ— 224 Γ— 224, float32, NCHW, ImageNet-normalized.
49
+ - **Output**: `logits` β€” 1 Γ— 3 unnormalized scores. Apply softmax to get
50
+ per-class confidence.
51
+ - **Class index order** (alphabetical, must match `labels.json`):
52
+
53
+ ```
54
+ 0 bank_statement
55
+ 1 invoice
56
+ 2 other
57
+ ```
58
+
59
+ - **Opset**: 18.
60
+ - **Quantization**: static, **QDQ format**, per-channel,
61
+ `QuantType.QUInt8` activations / `QuantType.QInt8` weights, calibrated
62
+ on ~200 in-domain images.
63
+
64
+ ### Why QDQ?
65
+
66
+ ONNX Runtime Mobile (the kernel set used by the
67
+ [`onnxruntime` Flutter package](https://pub.dev/packages/onnxruntime))
68
+ does **not** include `ConvInteger` / `MatMulInteger` operators. A model
69
+ quantized with `QuantFormat.QOperator` or `quantize_dynamic` will load
70
+ fine on desktop ORT and then fail at runtime on mobile with
71
+ `code=9 (NOT_IMPLEMENTED)`. QDQ keeps the original `Conv` / `MatMul`
72
+ nodes and surrounds them with `QuantizeLinear` / `DequantizeLinear`,
73
+ which is the path ORT Mobile actually executes. Use the QDQ build for
74
+ any phone deployment.
75
+
76
+ ## Intended use
77
+
78
+ - Triage page on whether an uploaded document is worth running heavyweight
79
+ invoice / statement extraction on.
80
+ - Lightweight client-side filtering before backend OCR to save round-trips.
81
+
82
+ ### Out of scope
83
+
84
+ - **Not an OCR model** β€” it doesn't extract text, totals, dates, or
85
+ account numbers. Pair it with a downstream OCR stage.
86
+ - **Not a fraud / authenticity detector.**
87
+ - **Not a layout analyzer.** It looks at the page as a whole, not at
88
+ regions.
89
+ - Any class outside `{bank_statement, invoice}` collapses into `other`.
90
+ Don't expect meaningful gradients between `other` sub-types
91
+ (receipts vs IDs vs photos).
92
+
93
+ ## How to use
94
+
95
+ ### Python (ONNX Runtime)
96
+
97
+ ```python
98
+ import json
99
+ import numpy as np
100
+ import onnxruntime as ort
101
+ from PIL import Image
102
+
103
+ session = ort.InferenceSession("invoice_classifier_int8_qdq.onnx")
104
+ labels = json.load(open("labels.json"))
105
+ mean = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape(3, 1, 1)
106
+ std = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape(3, 1, 1)
107
+
108
+ img = Image.open("page.jpg").convert("RGB").resize((256, 256))
109
+ left = (256 - 224) // 2
110
+ img = img.crop((left, left, left + 224, left + 224))
111
+ x = np.asarray(img, dtype=np.float32).transpose(2, 0, 1) / 255.0
112
+ x = ((x - mean) / std)[None].astype(np.float32)
113
+
114
+ logits = session.run(["logits"], {"input": x})[0][0]
115
+ probs = np.exp(logits - logits.max())
116
+ probs /= probs.sum()
117
+ print(labels[int(probs.argmax())], float(probs.max()))
118
+ ```
119
+
120
+ ### Flutter (ONNX Runtime Mobile)
121
+
122
+ The companion Flutter app loads the model at startup, verifies its SHA-256,
123
+ and runs inference per uploaded image / first PDF page. See `pinned_model.dart`
124
+ in the app repo. The preprocessing pipeline (resize 256 β†’ center-crop 224 β†’
125
+ ImageNet normalize β†’ NCHW) matches the Python snippet above byte-for-byte.
126
+
127
+ ## Preprocessing
128
+
129
+ | Step | Value |
130
+ |------|-------|
131
+ | Resize | shorter edge β†’ 256 |
132
+ | Crop | center crop to 224 Γ— 224 |
133
+ | Color | RGB |
134
+ | Scale | divide by 255 |
135
+ | Normalize mean | `[0.485, 0.456, 0.406]` |
136
+ | Normalize std | `[0.229, 0.224, 0.225]` |
137
+ | Layout | NCHW |
138
+ | Dtype | float32 |
139
+
140
+ These are the standard ImageNet stats β€” also captured in
141
+ `preprocess.json` for programmatic loading.
142
+
143
+ ## Training
144
+
145
+ - **Backbone weights**: torchvision `MobileNet_V3_Small_Weights.IMAGENET1K_V1`.
146
+ - **Head**: replaced with `nn.Linear(in, 3)`.
147
+ - **Optimizer**: AdamW, weight decay 1 Γ— 10⁻⁴.
148
+ - **Schedule**: cosine annealing across all epochs.
149
+ - **Stage 1**: backbone frozen for 2 epochs, only the new head trains
150
+ (lr = 3 Γ— 10⁻⁴).
151
+ - **Stage 2**: backbone unfrozen at lr / 10, head stays at base lr
152
+ (discriminative learning rates).
153
+ - **Loss**: `CrossEntropyLoss` with inverse-frequency class weights and
154
+ label smoothing 0.05.
155
+ - **Augmentation**: Resize(256) β†’ RandomResizedCrop(224, scale 0.7–1.0)
156
+ β†’ ColorJitter (brightness/contrast/saturation/hue) β†’ small RandomRotation
157
+ β†’ occasional grayscale β†’ ImageNet normalize.
158
+ - **Best checkpoint**: selected by validation accuracy.
159
+
160
+ The training, export, and quantization scripts are open-sourced in the
161
+ [Tally OCR Flutter repo](https://github.com/) under `training/`.
162
+
163
+ ## Evaluation
164
+
165
+ > **TODO**: replace with measured numbers from your held-out test set.
166
+
167
+ Recommended metrics to fill in before publishing a v1.0 model card:
168
+
169
+ | Metric | fp32 | int8 (QDQ) |
170
+ |--------|-----:|-----------:|
171
+ | Top-1 accuracy (val) | _–_ | _–_ |
172
+ | Macro F1 (val) | _–_ | _–_ |
173
+ | Per-class F1 | _–_ | _–_ |
174
+ | Top-1 disagreement vs fp32 | n/a | _–_ |
175
+
176
+ ## Quantization quality check
177
+
178
+ Always validate the int8 build before shipping:
179
+
180
+ ```bash
181
+ python -m src.infer --model outputs/invoice_classifier_fp32.onnx --image test/...
182
+ python -m src.infer --model outputs/invoice_classifier_int8_qdq.onnx --image test/...
183
+ ```
184
+
185
+ If int8 disagrees with fp32 on more than ~1–2% of held-out test images,
186
+ retry with more calibration data, switch to per-tensor weights, or fall
187
+ back to fp32 (still only ~6 MB).
188
+
189
+ ## Limitations and bias
190
+
191
+ - **Domain bias toward English-language, Western-format documents.**
192
+ Performance on non-Latin scripts, right-to-left layouts, and regional
193
+ statement / invoice formats has not been systematically measured.
194
+ - **Photo conditions matter.** Heavy glare, motion blur, extreme skew
195
+ (>~15Β°), or occlusion shifts predictions toward `other`.
196
+ - **`other` is an open set.** Its decision boundary is determined entirely
197
+ by what is present in the training data's `other/` folder. Receipts,
198
+ IDs, screenshots, and shipping labels were included; any class not seen
199
+ in training may be classified inconsistently.
200
+ - **No PII handling.** Documents are processed as opaque pixels; the model
201
+ does not redact or filter sensitive fields. Add your own redaction layer
202
+ if uploading user data anywhere downstream.
203
+
204
+ ## Files
205
+
206
+ | File | Purpose |
207
+ |------|---------|
208
+ | `invoice_classifier_int8_qdq.onnx` | Mobile-ready int8 model (ship this). |
209
+ | `invoice_classifier_fp32.onnx` | fp32 reference model. |
210
+ | `labels.json` | Class name list, in model index order. |
211
+ | `preprocess.json` | Input shape + ImageNet mean/std. |
212
+ | `sha256.txt` | SHA-256 hashes + file sizes for pinned downloads. |
213
+
214
+ ### Pinning hashes
215
+
216
+ ```
217
+ 8f006366fcd633caae958ce511cdba87eb4a6d9d5de302e3d0cb8dd070d774dc invoice_classifier_fp32.onnx 6084524
218
+ c39c3352d38379ee707642a056e55926719d7940f3e886be40e7afcc05526687 invoice_classifier_int8_qdq.onnx 1779282
219
+ ```
220
+
221
+ These are referenced verbatim in the Flutter app's `pinned_model.dart`
222
+ to refuse any downloaded model whose hash doesn't match.
223
+
224
+ ## License
225
+
226
+ Apache-2.0. The pretrained ImageNet backbone is also Apache-2.0
227
+ (torchvision MobileNetV3 weights).
228
+
229
+ ## Citation
230
+
231
+ If you use this model, please cite:
232
+
233
+ ```bibtex
234
+ @software{tally_ocr_document_classifier,
235
+ title = {Tally OCR Document Classifier (MobileNetV3-Small, QDQ int8)},
236
+ author = {Tally OCR contributors},
237
+ year = {2026},
238
+ url = {https://huggingface.co/<your-username>/tally-ocr-document-classifier}
239
+ }
240
+ ```
best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:91711e8e0c38af4c39801bcbf46cb509b90c4a3db5d5ea9221c3a3579dc9ae81
3
+ size 6203497
invoice_classifier_fp32.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f006366fcd633caae958ce511cdba87eb4a6d9d5de302e3d0cb8dd070d774dc
3
+ size 6084524
invoice_classifier_fp32.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f90d60ce9b53653d375580ad02dc3d695ab69912002ec1aa3a46e94b72bdd68
3
+ size 6094848
invoice_classifier_int8_qdq.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c39c3352d38379ee707642a056e55926719d7940f3e886be40e7afcc05526687
3
+ size 1779282
labels.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [
2
+ "bank_statement",
3
+ "invoice",
4
+ "other"
5
+ ]
preprocess.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "input_name": "input",
3
+ "output_name": "logits",
4
+ "input_size": 224,
5
+ "mean": [0.485, 0.456, 0.406],
6
+ "std": [0.229, 0.224, 0.225]
7
+ }
sha256.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ 8f006366fcd633caae958ce511cdba87eb4a6d9d5de302e3d0cb8dd070d774dc invoice_classifier_fp32.onnx 6084524
2
+ c39c3352d38379ee707642a056e55926719d7940f3e886be40e7afcc05526687 invoice_classifier_int8_qdq.onnx 1779282