verifydoc 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {verifydoc-0.1.0 → verifydoc-0.2.0}/CHANGELOG.md +21 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/Makefile +3 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/PKG-INFO +5 -1
- {verifydoc-0.1.0 → verifydoc-0.2.0}/README.md +4 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/benchmark/card.md +3 -2
- verifydoc-0.2.0/benchmark/datasets/cord.py +150 -0
- verifydoc-0.2.0/benchmark/datasets/funsd.py +164 -0
- verifydoc-0.2.0/configs/cord-paddleocr.yaml +14 -0
- verifydoc-0.2.0/configs/cord.yaml +14 -0
- verifydoc-0.2.0/configs/funsd.yaml +12 -0
- verifydoc-0.2.0/docs/REAL_MODELS.md +61 -0
- verifydoc-0.2.0/docs/demo.gif +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/calibration.md +10 -5
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/conformal.md +4 -2
- verifydoc-0.2.0/paper/generated/cord/calibration.md +34 -0
- verifydoc-0.2.0/paper/generated/cord/conformal.md +16 -0
- verifydoc-0.2.0/paper/generated/cord/extraction.md +5 -0
- verifydoc-0.2.0/paper/generated/cord/grounding.md +5 -0
- verifydoc-0.2.0/paper/generated/cord/rc_curves.png +0 -0
- verifydoc-0.2.0/paper/generated/cord/reliability.png +0 -0
- verifydoc-0.2.0/paper/generated/cord/selective.md +10 -0
- verifydoc-0.2.0/paper/generated/cord/summary.json +46 -0
- verifydoc-0.2.0/paper/generated/funsd/calibration.md +34 -0
- verifydoc-0.2.0/paper/generated/funsd/conformal.md +16 -0
- verifydoc-0.2.0/paper/generated/funsd/extraction.md +5 -0
- verifydoc-0.2.0/paper/generated/funsd/grounding.md +5 -0
- verifydoc-0.2.0/paper/generated/funsd/rc_curves.png +0 -0
- verifydoc-0.2.0/paper/generated/funsd/reliability.png +0 -0
- verifydoc-0.2.0/paper/generated/funsd/selective.md +10 -0
- verifydoc-0.2.0/paper/generated/funsd/summary.json +46 -0
- verifydoc-0.2.0/paper/generated/rc_curves.png +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/selective.md +2 -1
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/summary.json +8 -6
- verifydoc-0.2.0/paper/main.tex +90 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/pyproject.toml +1 -1
- verifydoc-0.2.0/scripts/make_demo_gif.py +167 -0
- verifydoc-0.2.0/tests/test_cord_loader.py +131 -0
- verifydoc-0.2.0/tests/test_funsd_loader.py +89 -0
- verifydoc-0.2.0/tests/test_harness_extractors.py +32 -0
- verifydoc-0.2.0/tests/test_learned_combiner.py +73 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_pipeline.py +26 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/__init__.py +1 -1
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/text_search.py +7 -3
- verifydoc-0.2.0/verifydoc/confidence/learned.py +63 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/harness.py +71 -24
- verifydoc-0.2.0/verifydoc/grounding/attach.py +173 -0
- verifydoc-0.1.0/benchmark/datasets/cord.py +0 -77
- verifydoc-0.1.0/paper/generated/rc_curves.png +0 -0
- verifydoc-0.1.0/verifydoc/grounding/attach.py +0 -87
- {verifydoc-0.1.0 → verifydoc-0.2.0}/.github/workflows/ci.yml +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/.github/workflows/release.yml +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/.gitignore +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/.pre-commit-config.yaml +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/CLAUDE.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/LICENSE +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/PROJECT.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/benchmark/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/benchmark/datasets/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/benchmark/datasets/synthetic.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/benchmark/labeling/guide.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/configs/demo.yaml +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/docs/USP.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/extraction.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/grounding.md +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/paper/generated/reliability.png +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/scripts/run_benchmark.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_calibration.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_calibrators.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_confidence.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_extraction.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_grounding.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_harness.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_selective.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_stats.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/tests/test_types.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/ui/streamlit_app.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/_ocr_common.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/api_vlm.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/base.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/docling.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/dots_ocr.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/mock.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/adapters/paddleocr_vl.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/base.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/conformal.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/histogram.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/isotonic.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/platt.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/splits.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/calibration/temperature.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/cli.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/combined.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/consensus.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/grounding_based.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/token_prob.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/confidence/verbalized.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/calibration.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/extraction.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/grounding.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/selective.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/eval/stats.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/grounding/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/ingest/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/ingest/loader.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/pipeline.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/policy/__init__.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/policy/abstention.py +0 -0
- {verifydoc-0.1.0 → verifydoc-0.2.0}/verifydoc/types.py +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.0 — 2026-07-21
|
|
4
|
+
|
|
5
|
+
Real data, faster grounding, learned fusion.
|
|
6
|
+
|
|
7
|
+
- **CORD v2 slice**: real receipts with real text layers (word quads) and
|
|
8
|
+
located gold boxes; Indonesian thousands-comma prices score numerically.
|
|
9
|
+
- **FUNSD slice**: 199 scanned forms; question→answer links become gold
|
|
10
|
+
fields with exact annotated answer boxes.
|
|
11
|
+
- **Harness**: `dataset:` (synthetic | cord | funsd) and `extractor:` (any
|
|
12
|
+
adapter registry name) dispatch; `make results` regenerates all slices.
|
|
13
|
+
- **Learned combiner**: logistic fusion over signal values + missing
|
|
14
|
+
indicators, fit on the calibration split; reported as a `learned` row in
|
|
15
|
+
every table (ablation vs the transparent weighted mean).
|
|
16
|
+
- **Grounder performance (~1000×)**: exact-match fast path, token-anchored
|
|
17
|
+
fuzzy scan with length pruning, token-overlap scoring for paragraph-length
|
|
18
|
+
values. FUNSD run: hours → 8 seconds.
|
|
19
|
+
- **Fix**: text-search label matching uses word boundaries ("total" no longer
|
|
20
|
+
fires inside "Subtotal") — caught by the harness itself.
|
|
21
|
+
- Demo GIF rendered from a real pipeline run; GPU runbook
|
|
22
|
+
(docs/REAL_MODELS.md) + pinned real-model config; paper skeleton.
|
|
23
|
+
|
|
3
24
|
## v0.1.0 — 2026-07-21
|
|
4
25
|
|
|
5
26
|
First public release.
|
|
@@ -23,8 +23,11 @@ cov:
|
|
|
23
23
|
pytest --cov=verifydoc --cov-report=term-missing
|
|
24
24
|
|
|
25
25
|
# Regenerate every table/figure in paper/ from pinned configs + seeds.
|
|
26
|
+
# (cord/funsd download their source data once into data/, then cache)
|
|
26
27
|
results:
|
|
27
28
|
$(PY) scripts/run_benchmark.py --config configs/demo.yaml --out paper/generated
|
|
29
|
+
$(PY) scripts/run_benchmark.py --config configs/cord.yaml --out paper/generated/cord
|
|
30
|
+
$(PY) scripts/run_benchmark.py --config configs/funsd.yaml --out paper/generated/funsd
|
|
28
31
|
|
|
29
32
|
demo:
|
|
30
33
|
streamlit run ui/streamlit_app.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: verifydoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A trust layer for document -> structured-JSON extraction: calibrated per-field confidence, source grounding, and abstention on top of any extractor.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bhaskargurram-ai/verifydoc
|
|
6
6
|
Project-URL: Repository, https://github.com/bhaskargurram-ai/verifydoc
|
|
@@ -253,6 +253,10 @@ Description-Content-Type: text/markdown
|
|
|
253
253
|
[](pyproject.toml)
|
|
254
254
|
[](https://github.com/psf/black)
|
|
255
255
|
|
|
256
|
+

|
|
257
|
+
|
|
258
|
+
*Above: a real pipeline run (`scripts/make_demo_gif.py`). The extractor returned `$1,432.50`; the page says `$1,234.50`. Grounding support drops to 0.78, the field misses the accept threshold, and the reviewer is pointed at the exact source region. The other three fields are auto-accepted.*
|
|
259
|
+
|
|
256
260
|
## The problem
|
|
257
261
|
|
|
258
262
|
Modern document parsers read pages at 96%+ benchmark accuracy — and still emit **fluent, plausible, silently-wrong values** (`$42.50` → `$45.20`) with **no reliable per-field signal telling you which values to trust**. Commercial APIs (Box, Azure, Textract) sell field-level confidence as a closed feature. No popular open-source parser leads with it. ([full USP audit](docs/USP.md))
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
[](pyproject.toml)
|
|
8
8
|
[](https://github.com/psf/black)
|
|
9
9
|
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
*Above: a real pipeline run (`scripts/make_demo_gif.py`). The extractor returned `$1,432.50`; the page says `$1,234.50`. Grounding support drops to 0.78, the field misses the accept threshold, and the reviewer is pointed at the exact source region. The other three fields are auto-accepted.*
|
|
13
|
+
|
|
10
14
|
## The problem
|
|
11
15
|
|
|
12
16
|
Modern document parsers read pages at 96%+ benchmark accuracy — and still emit **fluent, plausible, silently-wrong values** (`$42.50` → `$45.20`) with **no reliable per-field signal telling you which values to trust**. Commercial APIs (Box, Azure, Textract) sell field-level confidence as a closed feature. No popular open-source parser leads with it. ([full USP audit](docs/USP.md))
|
|
@@ -10,8 +10,9 @@ extraction quality, calibration, selective risk, and grounding
|
|
|
10
10
|
| Slice | Source | License | Status | Gold boxes |
|
|
11
11
|
|---|---|---|---|---|
|
|
12
12
|
| `synthetic` | generated in-repo (`benchmark/datasets/synthetic.py`) | Apache-2.0 | shipped, CI-run | yes (from layout) |
|
|
13
|
-
| `cord` | naver-clova-ix/cord-v2 (HF hub) | CC-BY-4.0 (verify at release) |
|
|
14
|
-
| `funsd`
|
|
13
|
+
| `cord` | naver-clova-ix/cord-v2 (HF hub) | CC-BY-4.0 (verify at release) | shipped — real text layers, harness-run | yes (located word quads) |
|
|
14
|
+
| `funsd` | guillaumejaume.github.io/FUNSD | research use | shipped — real forms, harness-run | yes (annotated answer boxes) |
|
|
15
|
+
| `sroie` / `docile` / `xfund` | official downloads | varies — some research-only | planned | planned |
|
|
15
16
|
|
|
16
17
|
## Redistribution policy (PROJECT.md §5.H)
|
|
17
18
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""CORD v2 loader (receipts, CC-BY-4.0) -> VerifyDocBench items with REAL
|
|
2
|
+
text layers and gold boxes.
|
|
3
|
+
|
|
4
|
+
Pages are built from CORD's ``valid_line`` word quads (normalized to image
|
|
5
|
+
size), gold values from ``gt_parse`` (flattened to leaf paths with our path
|
|
6
|
+
convention, so arrays like ``menu[0].price`` score natively), and gold boxes
|
|
7
|
+
are located on the real page. Downloads stream from the HF hub
|
|
8
|
+
(pip install 'verifydoc[data]'); results are cached as JSON under ``data/``
|
|
9
|
+
so a run downloads once. Never imported by unit tests (golden rule #5) —
|
|
10
|
+
pure helpers below are tested with fixture rows instead.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from benchmark.datasets.synthetic import BenchDocument
|
|
20
|
+
from verifydoc.grounding.attach import _locate
|
|
21
|
+
from verifydoc.types import Document, FieldGold, Page, Schema, Word, flatten_json
|
|
22
|
+
|
|
23
|
+
CORD_SCHEMA_RAW: dict = {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"menu": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"nm": {"type": "string", "x-scoring": "semantic"},
|
|
32
|
+
"cnt": {"type": "string"},
|
|
33
|
+
"price": {"type": "number", "x-numeric-tol": 0.01},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
"sub_total": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"subtotal_price": {"type": "number", "x-numeric-tol": 0.01},
|
|
41
|
+
"tax_price": {"type": "number", "x-numeric-tol": 0.01},
|
|
42
|
+
"discount_price": {"type": "number", "x-numeric-tol": 0.01},
|
|
43
|
+
"service_price": {"type": "number", "x-numeric-tol": 0.01},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
"total": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"total_price": {"type": "number", "x-numeric-tol": 0.01},
|
|
50
|
+
"cashprice": {"type": "number", "x-numeric-tol": 0.01},
|
|
51
|
+
"changeprice": {"type": "number", "x-numeric-tol": 0.01},
|
|
52
|
+
"creditcardprice": {"type": "number", "x-numeric-tol": 0.01},
|
|
53
|
+
"menuqty_cnt": {"type": "string"},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def document_from_valid_lines(
|
|
61
|
+
doc_id: str, valid_lines: list[dict[str, Any]], width: int, height: int
|
|
62
|
+
) -> Document:
|
|
63
|
+
"""Build a real text-layer Document from CORD ``valid_line`` word quads."""
|
|
64
|
+
words: list[Word] = []
|
|
65
|
+
lines: list[str] = []
|
|
66
|
+
for line in valid_lines:
|
|
67
|
+
line_words = line.get("words") or []
|
|
68
|
+
texts = []
|
|
69
|
+
for w in line_words:
|
|
70
|
+
quad = w["quad"]
|
|
71
|
+
xs = [quad["x1"], quad["x2"], quad["x3"], quad["x4"]]
|
|
72
|
+
ys = [quad["y1"], quad["y2"], quad["y3"], quad["y4"]]
|
|
73
|
+
bbox = (
|
|
74
|
+
max(0.0, min(xs) / width),
|
|
75
|
+
max(0.0, min(ys) / height),
|
|
76
|
+
min(1.0, max(xs) / width),
|
|
77
|
+
min(1.0, max(ys) / height),
|
|
78
|
+
)
|
|
79
|
+
if bbox[2] > bbox[0] and bbox[3] > bbox[1]:
|
|
80
|
+
words.append(Word(text=w["text"], bbox=bbox))
|
|
81
|
+
texts.append(w["text"])
|
|
82
|
+
if texts:
|
|
83
|
+
lines.append(" ".join(texts))
|
|
84
|
+
page = Page(
|
|
85
|
+
page=0, width=float(width), height=float(height), text="\n".join(lines), words=words
|
|
86
|
+
)
|
|
87
|
+
return Document(doc_id=doc_id, pages=[page])
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def golds_from_gt_parse(gt_parse: dict[str, Any], schema: Schema, doc: Document) -> list[FieldGold]:
|
|
91
|
+
"""Flatten gt_parse into scored gold fields; locate gold boxes on the page."""
|
|
92
|
+
golds = []
|
|
93
|
+
for path, value in flatten_json(gt_parse).items():
|
|
94
|
+
if value is None or str(value).strip() == "":
|
|
95
|
+
continue
|
|
96
|
+
leaf = schema.leaf(path)
|
|
97
|
+
golds.append(
|
|
98
|
+
FieldGold(
|
|
99
|
+
path=path,
|
|
100
|
+
value=value,
|
|
101
|
+
scoring=leaf.scoring if leaf else "exact",
|
|
102
|
+
numeric_tol=leaf.numeric_tol if leaf else 1e-6,
|
|
103
|
+
gold_box=_locate(str(value), doc, min_support=0.7),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
return golds
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def load(
|
|
110
|
+
split: str = "validation",
|
|
111
|
+
limit: int | None = 100,
|
|
112
|
+
cache_dir: str | Path = "data",
|
|
113
|
+
) -> list[BenchDocument]: # pragma: no cover - network on first call
|
|
114
|
+
"""Load CORD v2 (streamed; JSON-cached locally after the first download)."""
|
|
115
|
+
schema = Schema.from_json_schema(CORD_SCHEMA_RAW, name="cord-receipt")
|
|
116
|
+
cache = Path(cache_dir) / f"cord_{split}_{limit or 'all'}.json"
|
|
117
|
+
if cache.exists():
|
|
118
|
+
rows = json.loads(cache.read_text(encoding="utf-8"))
|
|
119
|
+
else:
|
|
120
|
+
try:
|
|
121
|
+
from datasets import load_dataset
|
|
122
|
+
except ImportError as exc:
|
|
123
|
+
raise ImportError("CORD loader requires: pip install 'verifydoc[data]'") from exc
|
|
124
|
+
stream = load_dataset("naver-clova-ix/cord-v2", split=split, streaming=True)
|
|
125
|
+
rows = []
|
|
126
|
+
for i, row in enumerate(stream):
|
|
127
|
+
if limit is not None and i >= limit:
|
|
128
|
+
break
|
|
129
|
+
gt = json.loads(row["ground_truth"])
|
|
130
|
+
width, height = row["image"].size
|
|
131
|
+
rows.append(
|
|
132
|
+
{
|
|
133
|
+
"gt_parse": gt["gt_parse"],
|
|
134
|
+
"valid_line": gt["valid_line"],
|
|
135
|
+
"width": width,
|
|
136
|
+
"height": height,
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
cache.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
cache.write_text(json.dumps(rows), encoding="utf-8")
|
|
141
|
+
|
|
142
|
+
out = []
|
|
143
|
+
for i, row in enumerate(rows):
|
|
144
|
+
doc = document_from_valid_lines(
|
|
145
|
+
f"cord-{split}-{i:05d}", row["valid_line"], row["width"], row["height"]
|
|
146
|
+
)
|
|
147
|
+
golds = golds_from_gt_parse(row["gt_parse"], schema, doc)
|
|
148
|
+
if golds and doc.pages[0].words:
|
|
149
|
+
out.append(BenchDocument(doc=doc, schema=schema, golds=golds))
|
|
150
|
+
return out
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""FUNSD loader (199 scanned forms, noisy real-world KIE) -> VerifyDocBench.
|
|
2
|
+
|
|
3
|
+
Question->answer links become gold fields: the path is a slug of the question
|
|
4
|
+
text, the value is the linked answer text, and the gold box is the exact
|
|
5
|
+
union of the answer's word boxes (no locating needed — FUNSD annotates them).
|
|
6
|
+
Download (~16 MB zip) happens once into ``data/``; unit tests use fixture
|
|
7
|
+
annotations only (golden rule #5).
|
|
8
|
+
|
|
9
|
+
License note (benchmark/card.md): FUNSD permits research use; we ship this
|
|
10
|
+
loader + our added splits, never the images.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from benchmark.datasets.synthetic import BenchDocument
|
|
21
|
+
from verifydoc.eval.extraction import parse_number
|
|
22
|
+
from verifydoc.types import Document, FieldGold, Grounding, Page, Schema, SchemaLeaf, Word
|
|
23
|
+
|
|
24
|
+
FUNSD_URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip"
|
|
25
|
+
|
|
26
|
+
_SLUG_RE = re.compile(r"[^a-z0-9]+")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def slugify(question: str) -> str:
|
|
30
|
+
"""Question text -> a stable field path ('DATE:' -> 'date')."""
|
|
31
|
+
slug = _SLUG_RE.sub("_", question.casefold()).strip("_")
|
|
32
|
+
return slug or "field"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _norm_box(box: list[int], width: int, height: int) -> tuple[float, float, float, float] | None:
|
|
36
|
+
x0, y0, x1, y1 = box
|
|
37
|
+
bbox = (
|
|
38
|
+
max(0.0, x0 / width),
|
|
39
|
+
max(0.0, y0 / height),
|
|
40
|
+
min(1.0, x1 / width),
|
|
41
|
+
min(1.0, y1 / height),
|
|
42
|
+
)
|
|
43
|
+
if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
|
|
44
|
+
return None
|
|
45
|
+
return bbox
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def bench_from_annotation(
|
|
49
|
+
doc_id: str, annotation: dict[str, Any], width: int, height: int
|
|
50
|
+
) -> BenchDocument | None:
|
|
51
|
+
"""Build one benchmark item from a FUNSD annotation dict."""
|
|
52
|
+
entries = {e["id"]: e for e in annotation.get("form", [])}
|
|
53
|
+
|
|
54
|
+
words: list[Word] = []
|
|
55
|
+
lines: list[str] = []
|
|
56
|
+
for entry in annotation.get("form", []):
|
|
57
|
+
texts = []
|
|
58
|
+
for w in entry.get("words", []):
|
|
59
|
+
bbox = _norm_box(w["box"], width, height)
|
|
60
|
+
if bbox is not None and w["text"].strip():
|
|
61
|
+
words.append(Word(text=w["text"], bbox=bbox))
|
|
62
|
+
texts.append(w["text"])
|
|
63
|
+
if texts:
|
|
64
|
+
lines.append(" ".join(texts))
|
|
65
|
+
doc = Document(
|
|
66
|
+
doc_id=doc_id,
|
|
67
|
+
pages=[
|
|
68
|
+
Page(
|
|
69
|
+
page=0, width=float(width), height=float(height), text="\n".join(lines), words=words
|
|
70
|
+
)
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# question -> linked answers, in annotation order
|
|
75
|
+
answers_by_question: dict[int, list[int]] = {}
|
|
76
|
+
for entry in annotation.get("form", []):
|
|
77
|
+
for a, b in entry.get("linking", []):
|
|
78
|
+
qa = (a, b)
|
|
79
|
+
if (
|
|
80
|
+
entries.get(qa[0], {}).get("label") == "question"
|
|
81
|
+
and entries.get(qa[1], {}).get("label") == "answer"
|
|
82
|
+
):
|
|
83
|
+
answers_by_question.setdefault(qa[0], [])
|
|
84
|
+
if qa[1] not in answers_by_question[qa[0]]:
|
|
85
|
+
answers_by_question[qa[0]].append(qa[1])
|
|
86
|
+
|
|
87
|
+
golds: list[FieldGold] = []
|
|
88
|
+
leaves: list[SchemaLeaf] = []
|
|
89
|
+
seen_paths: set[str] = set()
|
|
90
|
+
for q_id, a_ids in answers_by_question.items():
|
|
91
|
+
question = entries[q_id]["text"].strip()
|
|
92
|
+
# DECISION: multiple linked answers concatenate in annotation order.
|
|
93
|
+
value = " ".join(entries[a]["text"].strip() for a in a_ids if entries[a]["text"].strip())
|
|
94
|
+
if not question or not value:
|
|
95
|
+
continue
|
|
96
|
+
path = slugify(question)
|
|
97
|
+
n = 2
|
|
98
|
+
while path in seen_paths:
|
|
99
|
+
path = f"{slugify(question)}_{n}"
|
|
100
|
+
n += 1
|
|
101
|
+
seen_paths.add(path)
|
|
102
|
+
|
|
103
|
+
answer_boxes = [
|
|
104
|
+
_norm_box(w["box"], width, height) for a in a_ids for w in entries[a].get("words", [])
|
|
105
|
+
]
|
|
106
|
+
answer_boxes = [b for b in answer_boxes if b is not None]
|
|
107
|
+
gold_box = (
|
|
108
|
+
Grounding(
|
|
109
|
+
page=0,
|
|
110
|
+
bbox=(
|
|
111
|
+
min(b[0] for b in answer_boxes),
|
|
112
|
+
min(b[1] for b in answer_boxes),
|
|
113
|
+
max(b[2] for b in answer_boxes),
|
|
114
|
+
max(b[3] for b in answer_boxes),
|
|
115
|
+
),
|
|
116
|
+
support=1.0,
|
|
117
|
+
)
|
|
118
|
+
if answer_boxes
|
|
119
|
+
else None
|
|
120
|
+
)
|
|
121
|
+
scoring = "numeric" if parse_number(value) is not None else "semantic"
|
|
122
|
+
golds.append(
|
|
123
|
+
FieldGold(path=path, value=value, scoring=scoring, numeric_tol=1e-6, gold_box=gold_box)
|
|
124
|
+
)
|
|
125
|
+
leaves.append(SchemaLeaf(path=path, type="string", scoring=scoring))
|
|
126
|
+
|
|
127
|
+
if not golds or not words:
|
|
128
|
+
return None
|
|
129
|
+
schema = Schema(name=f"funsd-{doc_id}", leaves=leaves)
|
|
130
|
+
return BenchDocument(doc=doc, schema=schema, golds=golds)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def load(
|
|
134
|
+
split: str = "testing", limit: int | None = None, cache_dir: str | Path = "data"
|
|
135
|
+
) -> list[BenchDocument]: # pragma: no cover - network on first call
|
|
136
|
+
"""Load FUNSD (downloads + unzips once into ``cache_dir``)."""
|
|
137
|
+
from PIL import Image
|
|
138
|
+
|
|
139
|
+
root = Path(cache_dir) / "funsd"
|
|
140
|
+
if not root.exists():
|
|
141
|
+
import io
|
|
142
|
+
import urllib.request
|
|
143
|
+
import zipfile
|
|
144
|
+
|
|
145
|
+
Path(cache_dir).mkdir(parents=True, exist_ok=True)
|
|
146
|
+
with urllib.request.urlopen(FUNSD_URL) as resp:
|
|
147
|
+
payload = resp.read()
|
|
148
|
+
with zipfile.ZipFile(io.BytesIO(payload)) as zf:
|
|
149
|
+
zf.extractall(root)
|
|
150
|
+
|
|
151
|
+
ann_dir = root / "dataset" / f"{split}_data" / "annotations"
|
|
152
|
+
img_dir = root / "dataset" / f"{split}_data" / "images"
|
|
153
|
+
out: list[BenchDocument] = []
|
|
154
|
+
for i, ann_path in enumerate(sorted(ann_dir.glob("*.json"))):
|
|
155
|
+
if limit is not None and i >= limit:
|
|
156
|
+
break
|
|
157
|
+
annotation = json.loads(ann_path.read_text(encoding="utf-8"))
|
|
158
|
+
img_path = img_dir / f"{ann_path.stem}.png"
|
|
159
|
+
with Image.open(img_path) as img:
|
|
160
|
+
width, height = img.size
|
|
161
|
+
item = bench_from_annotation(f"funsd-{split}-{ann_path.stem}", annotation, width, height)
|
|
162
|
+
if item is not None:
|
|
163
|
+
out.append(item)
|
|
164
|
+
return out
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Pinned real-model experiment (issue #3): PaddleOCR-VL on CORD receipts.
|
|
2
|
+
# Needs a 24 GB GPU + `pip install paddlepaddle-gpu paddleocr` and CORD
|
|
3
|
+
# images exported to disk — see docs/REAL_MODELS.md.
|
|
4
|
+
dataset: cord
|
|
5
|
+
split: validation
|
|
6
|
+
limit: 100
|
|
7
|
+
extractor: paddleocr-vl # any name from the adapter registry
|
|
8
|
+
seed: 7
|
|
9
|
+
k: 5
|
|
10
|
+
calibration_fraction: 0.5
|
|
11
|
+
alphas: [0.02, 0.05]
|
|
12
|
+
n_boot: 300
|
|
13
|
+
# record at run time for the paper table header:
|
|
14
|
+
# paddleocr==<version>, GPU model, driver version
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# CORD v2 slice: REAL receipts, REAL text layers + gold boxes; extractor
|
|
2
|
+
# errors simulated at seeded rates (real-model configs land with issue #3).
|
|
3
|
+
# First run downloads ~100 receipts from the HF hub, then caches to data/.
|
|
4
|
+
dataset: cord
|
|
5
|
+
split: validation
|
|
6
|
+
limit: 100
|
|
7
|
+
seed: 7
|
|
8
|
+
k: 5
|
|
9
|
+
error_rate: 0.18
|
|
10
|
+
omit_rate: 0.04
|
|
11
|
+
hallucinate_rate: 0.06
|
|
12
|
+
calibration_fraction: 0.5
|
|
13
|
+
alphas: [0.02, 0.05]
|
|
14
|
+
n_boot: 300
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# FUNSD slice: real scanned forms, question->answer gold fields with exact
|
|
2
|
+
# annotated answer boxes; extractor errors simulated at seeded rates.
|
|
3
|
+
dataset: funsd
|
|
4
|
+
split: testing # 50 forms; use `training` (149) for scale
|
|
5
|
+
seed: 7
|
|
6
|
+
k: 5
|
|
7
|
+
error_rate: 0.18
|
|
8
|
+
omit_rate: 0.04
|
|
9
|
+
hallucinate_rate: 0.06
|
|
10
|
+
calibration_fraction: 0.5
|
|
11
|
+
alphas: [0.02, 0.05]
|
|
12
|
+
n_boot: 300
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Running real extractors (GPU runbook)
|
|
2
|
+
|
|
3
|
+
The shipped adapters run PaddleOCR-VL and dots.ocr on a single 24 GB GPU.
|
|
4
|
+
This is the paper's headline experiment (issue #3): the same harness, with a
|
|
5
|
+
real extractor instead of the simulated one.
|
|
6
|
+
|
|
7
|
+
## One-time setup (Linux + NVIDIA, 24 GB)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/bhaskargurram-ai/verifydoc && cd verifydoc
|
|
11
|
+
python3.11 -m venv .venv && source .venv/bin/activate
|
|
12
|
+
pip install -e ".[dev,pdf,data]"
|
|
13
|
+
|
|
14
|
+
# PaddleOCR-VL
|
|
15
|
+
pip install paddlepaddle-gpu paddleocr
|
|
16
|
+
|
|
17
|
+
# dots.ocr
|
|
18
|
+
pip install torch transformers accelerate
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Smoke test one receipt
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python - <<'EOF'
|
|
25
|
+
from benchmark.datasets import cord
|
|
26
|
+
from verifydoc.adapters import get_adapter
|
|
27
|
+
|
|
28
|
+
item = cord.load(split="validation", limit=1)[0]
|
|
29
|
+
adapter = get_adapter("paddleocr-vl")
|
|
30
|
+
for p in adapter.extract(item.doc, item.schema):
|
|
31
|
+
print(p.path, p.value, p.meta.get("token_logprobs"))
|
|
32
|
+
EOF
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Note: OCR adapters read `page.image_path` — for CORD, export images first
|
|
36
|
+
(`row["image"].save(...)`) and set `image_path` on each page; a
|
|
37
|
+
`--with-images` flag on the CORD loader is the intended patch (part of
|
|
38
|
+
issue #3).
|
|
39
|
+
|
|
40
|
+
## Full run
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python scripts/run_benchmark.py --config configs/cord-paddleocr.yaml \
|
|
44
|
+
--out paper/generated/cord-paddleocr
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`configs/cord-paddleocr.yaml` is checked in as the pinned experiment config;
|
|
48
|
+
the harness dispatches `extractor:` through the adapter registry
|
|
49
|
+
(`mock` | `text-search` | `paddleocr-vl` | `dots-ocr` | `docling` | `api-vlm`).
|
|
50
|
+
|
|
51
|
+
## What to report
|
|
52
|
+
|
|
53
|
+
The identical tables the simulated run produces (`calibration.md`,
|
|
54
|
+
`selective.md`, `conformal.md`, `grounding.md`) — swap them into the paper
|
|
55
|
+
via `make results`. Pin model versions in the config (`paddleocr==x.y.z`)
|
|
56
|
+
and record GPU + driver in the table header for reproducibility.
|
|
57
|
+
|
|
58
|
+
## Cloud options
|
|
59
|
+
|
|
60
|
+
Any of: RunPod / Lambda / Vast single RTX 4090 (24 GB) instance ≈ $0.4–0.8/hr;
|
|
61
|
+
the full 100-receipt sweep with k=5 sampling is an hours-not-days job.
|
|
Binary file
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
| verbalized | platt | 0.0430 | 0.0750 | 0.1047 | 0.3673 |
|
|
13
13
|
| verbalized | isotonic | 0.0419 | 0.0720 | 0.1050 | 0.3673 |
|
|
14
14
|
| verbalized | histogram | 0.0440 | 0.0973 | 0.1044 | 0.3657 |
|
|
15
|
-
| grounding | raw | 0.
|
|
16
|
-
| grounding | temperature | 0.
|
|
17
|
-
| grounding | platt | 0.
|
|
18
|
-
| grounding | isotonic | 0.
|
|
19
|
-
| grounding | histogram | 0.
|
|
15
|
+
| grounding | raw | 0.0856 | 0.0711 | 0.0655 | 0.3134 |
|
|
16
|
+
| grounding | temperature | 0.0675 | 0.0605 | 0.0439 | 0.1371 |
|
|
17
|
+
| grounding | platt | 0.0513 | 0.0146 | 0.0230 | 0.1106 |
|
|
18
|
+
| grounding | isotonic | 0.0017 | 0.0064 | 0.0056 | 0.0237 |
|
|
19
|
+
| grounding | histogram | 0.0023 | 0.0023 | 0.0090 | 0.0277 |
|
|
20
20
|
| consensus | raw | 0.1705 | 0.1705 | 0.0605 | 0.2222 |
|
|
21
21
|
| consensus | temperature | 0.0608 | 0.0608 | 0.0329 | 0.1070 |
|
|
22
22
|
| consensus | platt | 0.0111 | 0.0111 | 0.0113 | 0.0607 |
|
|
@@ -27,3 +27,8 @@
|
|
|
27
27
|
| combined | platt | 0.0109 | 0.0109 | 0.0105 | 0.0503 |
|
|
28
28
|
| combined | isotonic | 0.0107 | 0.0107 | 0.0101 | 0.1693 |
|
|
29
29
|
| combined | histogram | 0.0113 | 0.0113 | 0.0112 | 0.1825 |
|
|
30
|
+
| learned | raw | 0.0006 | 0.0199 | 0.0110 | 0.0553 |
|
|
31
|
+
| learned | temperature | 0.0026 | 0.0183 | 0.0111 | 0.0553 |
|
|
32
|
+
| learned | platt | 0.0111 | 0.0111 | 0.0109 | 0.0574 |
|
|
33
|
+
| learned | isotonic | 0.0114 | 0.0114 | 0.0113 | 0.1927 |
|
|
34
|
+
| learned | histogram | 0.0002 | 0.0195 | 0.0112 | 0.0622 |
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
| token_prob | 0.0500 | 0.9230 | 0.8779 | 0.0199 | True |
|
|
7
7
|
| verbalized | 0.0200 | inf | 0.0000 | 0.0000 | True |
|
|
8
8
|
| verbalized | 0.0500 | inf | 0.0000 | 0.0000 | True |
|
|
9
|
-
| grounding | 0.0200 | 0.8667 | 0.
|
|
10
|
-
| grounding | 0.0500 | 0.8462 | 0.
|
|
9
|
+
| grounding | 0.0200 | 0.8667 | 0.8779 | 0.0000 | True |
|
|
10
|
+
| grounding | 0.0500 | 0.8462 | 0.9070 | 0.0321 | True |
|
|
11
11
|
| consensus | 0.0200 | 0.2000 | 1.0000 | 0.0114 | True |
|
|
12
12
|
| consensus | 0.0500 | 0.2000 | 1.0000 | 0.0114 | True |
|
|
13
13
|
| combined | 0.0200 | 0.4761 | 1.0000 | 0.0114 | True |
|
|
14
14
|
| combined | 0.0500 | 0.4761 | 1.0000 | 0.0114 | True |
|
|
15
|
+
| learned | 0.0200 | 0.9741 | 1.0000 | 0.0114 | True |
|
|
16
|
+
| learned | 0.0500 | 0.9741 | 1.0000 | 0.0114 | True |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Calibration: signal x calibrator (test split)
|
|
2
|
+
|
|
3
|
+
| signal | calibrator | ece | adaptive_ece | brier | nll |
|
|
4
|
+
|---|---|---|---|---|---|
|
|
5
|
+
| token_prob | raw | 0.0957 | 0.1080 | 0.1206 | 0.4056 |
|
|
6
|
+
| token_prob | temperature | 0.1088 | 0.1175 | 0.1085 | 0.3563 |
|
|
7
|
+
| token_prob | platt | 0.0537 | 0.0560 | 0.0842 | 0.3137 |
|
|
8
|
+
| token_prob | isotonic | 0.0471 | 0.0536 | 0.0847 | 0.3316 |
|
|
9
|
+
| token_prob | histogram | 0.0535 | 0.0674 | 0.0887 | 0.3061 |
|
|
10
|
+
| verbalized | raw | 0.0610 | 0.0687 | 0.1246 | 0.4348 |
|
|
11
|
+
| verbalized | temperature | 0.0201 | 0.0556 | 0.1222 | 0.4106 |
|
|
12
|
+
| verbalized | platt | 0.0089 | 0.0414 | 0.1210 | 0.4067 |
|
|
13
|
+
| verbalized | isotonic | 0.0128 | 0.0490 | 0.1211 | 0.4470 |
|
|
14
|
+
| verbalized | histogram | 0.0101 | 0.0542 | 0.1214 | 0.4083 |
|
|
15
|
+
| grounding | raw | 0.0882 | 0.0816 | 0.0761 | 1.0679 |
|
|
16
|
+
| grounding | temperature | 0.0663 | 0.0761 | 0.0668 | 0.2475 |
|
|
17
|
+
| grounding | platt | 0.0429 | 0.0470 | 0.0545 | 0.2359 |
|
|
18
|
+
| grounding | isotonic | 0.0281 | 0.0468 | 0.0485 | 0.2854 |
|
|
19
|
+
| grounding | histogram | 0.0292 | 0.0482 | 0.0494 | 0.3670 |
|
|
20
|
+
| consensus | raw | 0.1541 | 0.1573 | 0.0875 | 0.5650 |
|
|
21
|
+
| consensus | temperature | 0.0892 | 0.0947 | 0.0751 | 0.5385 |
|
|
22
|
+
| consensus | platt | 0.0093 | 0.0461 | 0.0467 | 0.1955 |
|
|
23
|
+
| consensus | isotonic | 0.0271 | 0.0475 | 0.0478 | 0.2442 |
|
|
24
|
+
| consensus | histogram | 0.0271 | 0.0453 | 0.0478 | 0.2444 |
|
|
25
|
+
| combined | raw | 0.0845 | 0.0724 | 0.0516 | 0.2203 |
|
|
26
|
+
| combined | temperature | 0.0517 | 0.0360 | 0.0446 | 0.2210 |
|
|
27
|
+
| combined | platt | 0.0323 | 0.0367 | 0.0451 | 0.2023 |
|
|
28
|
+
| combined | isotonic | 0.0232 | 0.0376 | 0.0390 | 0.2944 |
|
|
29
|
+
| combined | histogram | 0.0212 | 0.0387 | 0.0373 | 0.2452 |
|
|
30
|
+
| learned | raw | 0.0131 | 0.0256 | 0.0424 | 0.1744 |
|
|
31
|
+
| learned | temperature | 0.0250 | 0.0311 | 0.0431 | 0.1796 |
|
|
32
|
+
| learned | platt | 0.0269 | 0.0309 | 0.0368 | 0.1869 |
|
|
33
|
+
| learned | isotonic | 0.0200 | 0.0427 | 0.0362 | 0.2394 |
|
|
34
|
+
| learned | histogram | 0.0211 | 0.0446 | 0.0371 | 0.2058 |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Conformal abstention: guarantee vs forced review
|
|
2
|
+
|
|
3
|
+
| signal | alpha | threshold | test_coverage | achieved_risk | guarantee_held |
|
|
4
|
+
|---|---|---|---|---|---|
|
|
5
|
+
| token_prob | 0.0200 | 0.9308 | 0.8165 | 0.0486 | False |
|
|
6
|
+
| token_prob | 0.0500 | 0.9170 | 0.8678 | 0.0705 | False |
|
|
7
|
+
| verbalized | 0.0200 | inf | 0.0000 | 0.0000 | True |
|
|
8
|
+
| verbalized | 0.0500 | inf | 0.0000 | 0.0000 | True |
|
|
9
|
+
| grounding | 0.0200 | 0.9048 | 0.8612 | 0.0365 | False |
|
|
10
|
+
| grounding | 0.0500 | 0.7143 | 0.8992 | 0.0570 | False |
|
|
11
|
+
| consensus | 0.0200 | inf | 0.0000 | 0.0000 | True |
|
|
12
|
+
| consensus | 0.0500 | 0.2000 | 1.0000 | 0.0494 | True |
|
|
13
|
+
| combined | 0.0200 | 0.6793 | 0.9793 | 0.0358 | False |
|
|
14
|
+
| combined | 0.0500 | 0.4801 | 0.9984 | 0.0494 | True |
|
|
15
|
+
| learned | 0.0200 | 0.9319 | 0.9793 | 0.0341 | False |
|
|
16
|
+
| learned | 0.0500 | 0.6041 | 0.9968 | 0.0479 | True |
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Selective prediction by signal (raw confidence, test split)
|
|
2
|
+
|
|
3
|
+
| signal | e_aurc | auroc | coverage@2% | coverage@5% | e_aurc_ci |
|
|
4
|
+
|---|---|---|---|---|---|
|
|
5
|
+
| token_prob | 0.0414 | 0.8343 | 0.0182 | 0.8165 | [0.0237, 0.0621] |
|
|
6
|
+
| verbalized | 0.1183 | 0.5280 | 0.0000 | 0.0331 | [0.0864, 0.1527] |
|
|
7
|
+
| grounding | 0.0497 | 0.8752 | 0.0000 | 0.8893 | [0.0161, 0.0530] |
|
|
8
|
+
| consensus | 0.0426 | 0.6095 | 0.0000 | 1.0000 | [0.0177, 0.0529] |
|
|
9
|
+
| combined | 0.0340 | 0.6304 | 0.1624 | 1.0000 | [0.0154, 0.0572] |
|
|
10
|
+
| learned | 0.0352 | 0.6320 | 0.0892 | 1.0000 | [0.0174, 0.0571] |
|