hung-k-nguyen commited on
Commit
6c930d5
·
verified ·
1 Parent(s): 63f2a8f

v1 re-scored heuristic + fused usage; v2 col

Browse files
Files changed (1) hide show
  1. README.md +37 -21
README.md CHANGED
@@ -34,42 +34,58 @@ two self-contained **ONNX** graphs — an image tower and a text tower — that
34
 
35
  | Benchmark | **v1 (open)** | v2 (commercial) | best cloud VLM |
36
  | --- | ---: | ---: | ---: |
37
- | DocLayNet | **0.89** | 0.88 | 0.83 |
38
- | Forms | 0.80 | 1.00 | 1.00 |
39
- | Tobacco | 0.62 | 0.69 | 0.85 |
40
- | OOD (unseen types) | 0.87 | 0.97 | — |
41
- | OOV (synonym wording) | 0.74 | 0.80 | — |
42
 
43
  Every entry is scored by the same open scorer — full ranking, plus a **generalist zero-shot baseline** and
44
  each cloud model, on the
45
- [leaderboard](https://huggingface.co/spaces/nutrientdocs/document-classification-leaderboard). v1 leads on the
46
- visual document-type track (DocLayNet) as a free download; like all embedding models it trails large VLMs on
47
- Tobacco (a read-the-header task). ~**3–7 docs/s on an A40** (image branch).
 
48
 
49
  ## Usage (ONNX)
50
 
51
  ```python
52
  import numpy as np, onnxruntime as ort, json
53
- from transformers import AutoProcessor, AutoTokenizer
54
  from huggingface_hub import hf_hub_download
 
55
 
56
  R = "nutrientdocs/document-classification-v1"
57
- img_sess = ort.InferenceSession(hf_hub_download(R, "modules/omni-image/image_model.onnx"))
58
- txt_sess = ort.InferenceSession(hf_hub_download(R, "modules/omni-image/text_model.onnx"))
59
  cal = json.load(open(hf_hub_download(R, "modules/omni-image/config.json")))["calibration"]
60
- proc = AutoProcessor.from_pretrained(R, subfolder="modules/omni-image") # bundled preprocessor
61
- tok = AutoTokenizer.from_pretrained(R, subfolder="modules/omni-image") # bundled tokenizer (right-pad + attention_mask)
62
 
63
- from PIL import Image
64
  labels = ["invoice", "letter", "memo", "form", "scientific article", "resume"]
 
 
 
 
 
 
 
 
 
 
65
  pix = proc(images=[Image.open("doc.png").convert("RGB")], return_tensors="np")["pixel_values"].astype(np.float16)
66
- ie = img_sess.run(["image_emb"], {"pixel_values": pix})[0] # [1, 1024] L2
67
- enc = tok(labels, padding=True, truncation=True, max_length=64, return_tensors="np")
68
- te = txt_sess.run(["text_emb"], {"input_ids": enc["input_ids"].astype(np.int64),
69
- "attention_mask": enc["attention_mask"].astype(np.int64)})[0] # [N,1024] L2
70
- cos = (ie @ te.T)[0]
71
- probs = 1 / (1 + np.exp(-(cal["scale"] * cos + cal["bias"])))
72
- print(dict(zip(labels, probs.round(3).tolist())))
 
 
 
 
 
73
  ```
74
 
75
  ## What's in this repo
 
34
 
35
  | Benchmark | **v1 (open)** | v2 (commercial) | best cloud VLM |
36
  | --- | ---: | ---: | ---: |
37
+ | DocLayNet | 0.75 | **0.97** | 0.83 |
38
+ | Forms | 0.80 | **1.00** | 1.00 |
39
+ | Tobacco | 0.61 | 0.74 | **0.85** |
40
+ | OOD (unseen types) | 0.86 | **0.95** | — |
41
+ | OOV (synonym wording) | 0.73 | **0.83** | — |
42
 
43
  Every entry is scored by the same open scorer — full ranking, plus a **generalist zero-shot baseline** and
44
  each cloud model, on the
45
+ [leaderboard](https://huggingface.co/spaces/nutrientdocs/document-classification-leaderboard). v1 is the
46
+ free, open-weight sibling: it trails the commercial [`v2`](https://huggingface.co/nutrientdocs/document-classification-v2)
47
+ and the large cloud VLMs on accuracy, but it's Apache-2.0 and downloadable. Like all embedding models it
48
+ trails VLMs most on Tobacco (a read-the-header task). ~**5.7 pages/s on an A40** (fused image+text).
49
 
50
  ## Usage (ONNX)
51
 
52
  ```python
53
  import numpy as np, onnxruntime as ort, json
54
+ from transformers import AutoImageProcessor, AutoTokenizer
55
  from huggingface_hub import hf_hub_download
56
+ from PIL import Image
57
 
58
  R = "nutrientdocs/document-classification-v1"
59
+ img_sess = ort.InferenceSession(hf_hub_download(R, "modules/omni-image/image_model.onnx")) # SigLIP image tower
60
+ txt_sess = ort.InferenceSession(hf_hub_download(R, "modules/omni-image/text_model.onnx")) # Qwen text tower
61
  cal = json.load(open(hf_hub_download(R, "modules/omni-image/config.json")))["calibration"]
62
+ proc = AutoImageProcessor.from_pretrained(R, subfolder="modules/omni-image") # SigLIP image processor
63
+ tok = AutoTokenizer.from_pretrained(R, subfolder="modules/omni-image") # Qwen tokenizer
64
 
 
65
  labels = ["invoice", "letter", "memo", "form", "scientific article", "resume"]
66
+ calib = lambda cos: 1 / (1 + np.exp(-(cal["scale"] * cos + cal["bias"])))
67
+
68
+ def embed_text(texts, maxlen):
69
+ e = tok(texts, padding=True, truncation=True, max_length=maxlen, return_tensors="np")
70
+ return txt_sess.run(["text_emb"], {"input_ids": e["input_ids"].astype(np.int64),
71
+ "attention_mask": e["attention_mask"].astype(np.int64)})[0] # [.,1024] L2
72
+
73
+ lab = embed_text(labels, 64) # label embeds, once
74
+
75
+ # --- image branch: page image vs labels (image ONNX has batch=1; loop+pool for multi-page) ---
76
  pix = proc(images=[Image.open("doc.png").convert("RGB")], return_tensors="np")["pixel_values"].astype(np.float16)
77
+ ie = img_sess.run(["image_emb"], {"pixel_values": pix})[0] # [1,1024] L2
78
+ image_probs = calib((ie @ lab.T)[0]) # [N]
79
+
80
+ # --- text branch: the page's OCR text vs labels (up to ~2048 tokens) ---
81
+ doc_text = open("doc.txt").read()
82
+ text_probs = calib((embed_text([doc_text], 2048) @ lab.T)[0]) # [N]
83
+
84
+ # --- reliability fusion: weight each branch by how DECISIVE it is (top1-top2 margin) ---
85
+ margin = lambda p: float(np.partition(p, -2)[-1] - np.partition(p, -2)[-2])
86
+ wi, wt = margin(image_probs), margin(text_probs); s = wi + wt + 1e-9
87
+ fused = (wi / s) * image_probs + (wt / s) * text_probs
88
+ print(dict(zip(labels, fused.round(3).tolist())))
89
  ```
90
 
91
  ## What's in this repo