Thomas Piacentini
← Atelier

Fiche

OCR and Document Understanding

Texte original · anglais

Train CTC OCR, pick a document stack, retrieve pages with MaxSim and approve fields against exact source spans.

Classical OCR pipeline

ImageclientText detectionDB, EAST, CRAFTmodelBounding boxesword or linestoreCrop each regionfixed heightprocessRecognitionCRNN + CTCmodelText stringsstoreLayout orderingreading orderprocessReading-order textclient
Nodes and connections
NodeRoleKind
Imageclient
Text detectionDB, EAST, CRAFTmodel
Bounding boxesword or linestore
Crop each regionfixed heightprocess
RecognitionCRNN + CTCmodel
Text stringsstore
Layout orderingreading orderprocess
Reading-order textclient
FromToConnection
ImageText detection
Text detectionBounding boxes
Bounding boxesCrop each region
Crop each regionRecognition
RecognitionText strings
Text stringsLayout ordering
Layout orderingReading-order text
Sources

Tasks in code

TaskCode
CTC lossctc_loss(log_probs, targets, ...)
Greedy CTC decodegreedy_ctc_decode(log_probs, blank=0)
Line recogniserTinyCRNN(vocab_size=40, hidden=128, feat=32)
Production OCRPaddleOCR(lang="en").ocr(image_path)
LayoutLMv3 inputlayoutlm_input(tokens)
Donut JSON schemadonut_schema("invoice")
Top-k pages by MaxSimretrieve(query, pages, k=3)
Sources

Train a CRNN with CTC

vocab = ["_"] + list("0123456789abcdefghijklmnopqrstuvwxyz")blank at index 0
model = TinyCRNN(vocab_size=len(vocab))
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
imgs, targets, target_lens = build_batch(strings, vocab)height 32
log_probs = model(imgs)(W', N, vocab)
input_lens = torch.full((8,), log_probs.size(0), dtype=torch.long)
loss = F.ctc_loss(log_probs, targets, input_lens, target_lens,
blank=0, reduction="mean", zero_infinity=True)
opt.zero_grad(); loss.backward(); opt.step()~3 to ~0.2 in 200 steps
Sources

MaxSim late interaction

def maxsim(query_tokens, patches):
s = 0.0
for q in query_tokens:one vector per query token
best = max(cosine(q, p) for p in patches)best page patch
s += bestsum over query tokens
return s
scored = [(p.doc_id, maxsim(query.tokens, p.patches)) for p in pages]
scored.sort(key=lambda x: -x[1])top-k pages
idx = build_index(prune=True)DocPruner on, 50% patches
hits = idx.retrieve(q, k=3)
Sources

Commands

CommandWhat it does
python3 cli.py sample.txt --schema schema.json --output output/review.htmlBuilds the HTML review desk and a JSON report
--proposals <file>Loads external OCR or model candidates
--approve <file>Applies decisions downloaded from the HTML desk
python3 scripts/project_test.py document-extraction-desk --all --solution --strictGrades the reference solution
python main.pyRuns the capstone MaxSim and DocPruner scaffold
npm startRuns the viewer self-check once and exits 0
npm run serveStarts the viewer HTTP server on 127.0.0.1
Sources

Vision-first document QA

  1. Ingest render each PDF page to a 1536x2048 PNG. Keep {doc_id, page_num, image_path}.
  2. Embed run ColQwen2.5-v0.2. Get ~2048 patch vectors of dim 128 per page.
  3. Prune DocPruner keeps the highest-signal half of the patches.
  4. Query embed query tokens. Run MaxSim. Return the top-k pages.
  5. Synthesize give the query and the top-5 page images to the VLM. Cite (doc_id, page).
  6. Evidence draw the cited bounding boxes as overlays in the viewer.
  7. OCR fallback run Nougat or dots.ocr on equation-dense pages.
  8. Eval report ViDoRe v3 nDCG@5 and M3DocVQA against OCR-then-text.
Sources

Formulas and numbers

CTC collapse
merge repeats, then remove blanks. "h h h _ _ e e l l _ l l o _ _" gives "hello".
CER
Levenshtein distance divided by the reference length. WER is the same per word.
MaxSim
\sum_i \max_j \cos(q_i, p_j) over query tokens q_i and page patches p_j.
ColPali storage
50 pages x 729 patches x 128 dims x 4 bytes = ~18 MB raw, ~2.3 MB after 8x PQ.
Text-RAG storage
50 chunks x 768 dims x 4 bytes = ~150 kB. ColPali is ~120x more raw, ~15x after PQ.
ViDoRe
ColPali-v1 scores ~80% nDCG@5. Text-RAG scores ~50-60%.
Sources

Defaults and limits

SettingDefaultNote
CER target< 2%On clean scans
CRNN input height32CNN pools height to 1. Width is time
Page render180 DPIPyMuPDF in the capstone
DocPruner50%Under 0.5% accuracy loss
Schema fields1 to 30Lowercase names, unique labels
Document size100000Characters at most
Candidates300Proposals at most per review
Sources

Pick a document stack

Printed invoices at scale
LayoutLMv3 + rules. Cost-efficient.
Mixed, handwritten, forms
VLM-native (PaliGemma 2 or Qwen2.5-VL).
arXiv papers
Nougat for math, a VLM for figures.
Regulated
OCR pipeline + VLM validator as a cross-check.
Receipts with fixed structure
fine-tune Donut.
Pure-text documents
text-RAG. It is simpler and uses less storage.
Visually rich PDFs
ColPali for quality, VisRAG for scale.
Sources

Troubleshooting

SymptomCauseFix
"Approval belongs to another document version"Text changed, sourceSha256 differsReview the new version again
"Proposal does not match its exact source span"Quote is not at start, endFix the offsets of the proposal
"Use an ISO date: YYYY-MM-DD"Date field text is not ISOCorrect the source text
Field state is ambiguousTwo Seats quotesChoose one span with --approve
Donut fails on a paperOCR-free models are specialistsUse Nougat for papers
Chart data missing in RAGOCR drops chart dataEmbed page images with ColPali
Sources

Abbreviations

CTC
Connectionist Temporal Classification. Trains without per-timestep labels.
CRNN
conv features + BiLSTM + CTC. The 2015 baseline.
VLM-OCR
a vision-language model trained or prompted for OCR.
DocVQA
the document VQA standard. The most-cited score.
ViDoRe
Visual Document Retrieval Evaluation. Metric is nDCG@5.
PQ
product quantization. Shrinks storage ~8x.
Sources

Questions

Why CTC?
It trains without character-level alignment.
When do OCR pipelines still win?
Massive scan scale, deterministic failures, auditable output.
Why MaxSim over a mean?
Each query token picks its best patch. Irrelevant patches do not wash it out.
Do offsets prove the value is right?
No. They prove provenance. A reviewer confirms the field.
Sources
Original source
---
id: ocr-documents
title: OCR and Document Understanding
code: OD
subtitle: Train CTC OCR, pick a document stack, retrieve pages with MaxSim and approve fields against exact source spans.
hue: 165
sources: phases/04-computer-vision/19-ocr-document-understanding, phases/12-multimodal-ai/22-document-diagram-understanding, phases/12-multimodal-ai/23-colpali-vision-native-rag, phases/19-capstone-projects/04-multimodal-document-qa, projects/document-extraction-desk
source_hash: 31f050e1ba845909
---

## Classical OCR pipeline
type: architecture
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md

```arch
node | img | 1,1 | Image | | client
node | det | 2,1 | Text detection | DB, EAST, CRAFT | model
node | box | 3,1 | Bounding boxes | word or line | store
node | crop | 4,1 | Crop each region | fixed height | process
node | rec | 4,2 | Recognition | CRNN + CTC | model
node | txt | 3,2 | Text strings | | store
node | lay | 2,2 | Layout ordering | reading order | process
node | out | 1,2 | Reading-order text | | client
edge | img | det |
edge | det | box |
edge | box | crop |
edge | crop | rec |
edge | rec | txt |
edge | txt | lay |
edge | lay | out |
```

## Tasks in code
type: task-table
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/04-computer-vision/19-ocr-document-understanding/code/main.py, phases/12-multimodal-ai/22-document-diagram-understanding/code/main.py, phases/12-multimodal-ai/23-colpali-vision-native-rag/code/main.py, phases/19-capstone-projects/04-multimodal-document-qa/code/main.py

| Task | Code |
|---|---|
| CTC loss | `ctc_loss(log_probs, targets, ...)` |
| Greedy CTC decode | `greedy_ctc_decode(log_probs, blank=0)` |
| Line recogniser | `TinyCRNN(vocab_size=40, hidden=128, feat=32)` |
| Production OCR | `PaddleOCR(lang="en").ocr(image_path)` |
| LayoutLMv3 input | `layoutlm_input(tokens)` |
| Donut JSON schema | `donut_schema("invoice")` |
| Top-k pages by MaxSim | `retrieve(query, pages, k=3)` |

## Train a CRNN with CTC
type: annotated-code
lang: python
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/04-computer-vision/19-ocr-document-understanding/code/main.py

```python
vocab = ["_"] + list("0123456789abcdefghijklmnopqrstuvwxyz")  # blank at index 0
model = TinyCRNN(vocab_size=len(vocab))
opt = torch.optim.Adam(model.parameters(), lr=1e-3)
imgs, targets, target_lens = build_batch(strings, vocab)    # height 32
log_probs = model(imgs)                                     # (W', N, vocab)
input_lens = torch.full((8,), log_probs.size(0), dtype=torch.long)
loss = F.ctc_loss(log_probs, targets, input_lens, target_lens,
                  blank=0, reduction="mean", zero_infinity=True)
opt.zero_grad(); loss.backward(); opt.step()                # ~3 to ~0.2 in 200 steps
```

## MaxSim late interaction
type: annotated-code
lang: python
from: phases/12-multimodal-ai/23-colpali-vision-native-rag/code/main.py, phases/19-capstone-projects/04-multimodal-document-qa/code/main.py

```python
def maxsim(query_tokens, patches):
    s = 0.0
    for q in query_tokens:                      # one vector per query token
        best = max(cosine(q, p) for p in patches)  # best page patch
        s += best                               # sum over query tokens
    return s
scored = [(p.doc_id, maxsim(query.tokens, p.patches)) for p in pages]
scored.sort(key=lambda x: -x[1])                # top-k pages
idx = build_index(prune=True)                   # DocPruner on, 50% patches
hits = idx.retrieve(q, k=3)
```

## Commands
type: command-table
span: 2
from: projects/document-extraction-desk/README.md, projects/document-extraction-desk/solution/cli.py, phases/19-capstone-projects/04-multimodal-document-qa/code/ts/README.md, phases/19-capstone-projects/04-multimodal-document-qa/code/main.py

| Command | What it does |
|---|---|
| `python3 cli.py sample.txt --schema schema.json --output output/review.html` | Builds the HTML review desk and a JSON report |
| `--proposals <file>` | Loads external OCR or model candidates |
| `--approve <file>` | Applies decisions downloaded from the HTML desk |
| `python3 scripts/project_test.py document-extraction-desk --all --solution --strict` | Grades the reference solution |
| `python main.py` | Runs the capstone MaxSim and DocPruner scaffold |
| `npm start` | Runs the viewer self-check once and exits 0 |
| `npm run serve` | Starts the viewer HTTP server on 127.0.0.1 |

## Vision-first document QA
type: flow
from: phases/19-capstone-projects/04-multimodal-document-qa/docs/en.md

1. **Ingest**: render each PDF page to a 1536x2048 PNG. Keep `{doc_id, page_num, image_path}`.
2. **Embed**: run ColQwen2.5-v0.2. Get ~2048 patch vectors of dim 128 per page.
3. **Prune**: DocPruner keeps the highest-signal half of the patches.
4. **Query**: embed query tokens. Run MaxSim. Return the top-k pages.
5. **Synthesize**: give the query and the top-5 page images to the VLM. Cite (doc_id, page).
6. **Evidence**: draw the cited bounding boxes as overlays in the viewer.
7. **OCR fallback**: run Nougat or dots.ocr on equation-dense pages.
8. **Eval**: report ViDoRe v3 nDCG@5 and M3DocVQA against OCR-then-text.

## Formulas and numbers
type: concepts
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/12-multimodal-ai/23-colpali-vision-native-rag/docs/en.md

- **CTC collapse**: merge repeats, then remove blanks. "h h h _ _ e e l l _ l l o _ _" gives "hello".
- **CER**: Levenshtein distance divided by the reference length. WER is the same per word.
- **MaxSim**: $\sum_i \max_j \cos(q_i, p_j)$ over query tokens $q_i$ and page patches $p_j$.
- **ColPali storage**: 50 pages x 729 patches x 128 dims x 4 bytes = ~18 MB raw, ~2.3 MB after 8x PQ.
- **Text-RAG storage**: 50 chunks x 768 dims x 4 bytes = ~150 kB. ColPali is ~120x more raw, ~15x after PQ.
- **ViDoRe**: ColPali-v1 scores ~80% nDCG@5. Text-RAG scores ~50-60%.

## Defaults and limits
type: defaults
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/19-capstone-projects/04-multimodal-document-qa/docs/en.md, projects/document-extraction-desk/solution/main.py

| Setting | Default | Note |
|---|---|---|
| CER target | < 2% | On clean scans |
| CRNN input height | 32 | CNN pools height to 1. Width is time |
| Page render | 180 DPI | PyMuPDF in the capstone |
| DocPruner | 50% | Under 0.5% accuracy loss |
| Schema fields | 1 to 30 | Lowercase names, unique labels |
| Document size | 100000 | Characters at most |
| Candidates | 300 | Proposals at most per review |

## Pick a document stack
type: concepts
from: phases/12-multimodal-ai/22-document-diagram-understanding/docs/en.md, phases/12-multimodal-ai/23-colpali-vision-native-rag/docs/en.md, phases/04-computer-vision/19-ocr-document-understanding/docs/en.md

- **Printed invoices at scale**: LayoutLMv3 + rules. Cost-efficient.
- **Mixed, handwritten, forms**: VLM-native (PaliGemma 2 or Qwen2.5-VL).
- **arXiv papers**: Nougat for math, a VLM for figures.
- **Regulated**: OCR pipeline + VLM validator as a cross-check.
- **Receipts with fixed structure**: fine-tune Donut.
- **Pure-text documents**: text-RAG. It is simpler and uses less storage.
- **Visually rich PDFs**: ColPali for quality, VisRAG for scale.

## Troubleshooting
type: troubleshooting
from: projects/document-extraction-desk/solution/main.py, projects/document-extraction-desk/solution/cli.py, projects/document-extraction-desk/stages/03-review-and-approve/docs/en.md, phases/12-multimodal-ai/22-document-diagram-understanding/docs/en.md, phases/12-multimodal-ai/23-colpali-vision-native-rag/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| "Approval belongs to another document version" | Text changed, `sourceSha256` differs | Review the new version again |
| "Proposal does not match its exact source span" | Quote is not at `start`, `end` | Fix the offsets of the proposal |
| "Use an ISO date: YYYY-MM-DD" | Date field text is not ISO | Correct the source text |
| Field state is ambiguous | Two Seats quotes | Choose one span with `--approve` |
| Donut fails on a paper | OCR-free models are specialists | Use Nougat for papers |
| Chart data missing in RAG | OCR drops chart data | Embed page images with ColPali |

## Abbreviations
type: glossary
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/12-multimodal-ai/22-document-diagram-understanding/docs/en.md, phases/12-multimodal-ai/23-colpali-vision-native-rag/docs/en.md

- **CTC**: Connectionist Temporal Classification. Trains without per-timestep labels.
- **CRNN**: conv features + BiLSTM + CTC. The 2015 baseline.
- **VLM-OCR**: a vision-language model trained or prompted for OCR.
- **DocVQA**: the document VQA standard. The most-cited score.
- **ViDoRe**: Visual Document Retrieval Evaluation. Metric is nDCG@5.
- **PQ**: product quantization. Shrinks storage ~8x.

## Questions
type: qa
from: phases/04-computer-vision/19-ocr-document-understanding/docs/en.md, phases/12-multimodal-ai/22-document-diagram-understanding/docs/en.md, phases/12-multimodal-ai/23-colpali-vision-native-rag/docs/en.md, projects/document-extraction-desk/README.md

- **Why CTC?** It trains without character-level alignment.
- **When do OCR pipelines still win?** Massive scan scale, deterministic failures, auditable output.
- **Why MaxSim over a mean?** Each query token picks its best patch. Irrelevant patches do not wash it out.
- **Do offsets prove the value is right?** No. They prove provenance. A reviewer confirms the field.
Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓