Why Arabic OCR needs this
Many Arabic letters share an identical skeleton (rasm) and differ only by
dots (i'jam): ب ت ث ن are the same stroke. A scanner that loses or invents
one dot changes the letter. The script is also cursive, so word boundaries are
narrow and words get split or fused.
The consequence is that an 8% character error rate carries a 41% word error
rate — and search, indexing and extraction all operate on words.
Results
200 held-out segments, greedy decoding.
Table with columns: CER, WER | CER | WER |
|---|
| Raw OCR (do nothing) | 0.0808 | 0.4112 |
| Untuned Qwen2.5-0.5B | 1.8123 | 2.2680 |
| This adapter | 0.0724 | 0.1765 |
| + drift guardrail | 0.0671 | 0.2042 |
Word error rate falls 41.1% → 20.4% (a 50.3% reduction) while character
accuracy simultaneously improves 16.9%.
The untuned base model scores CER 1.81 — above 1.0 means its output is
unrelated text rather than a damaged version of the truth. It answers the
instruction conversationally instead of restoring, so every gain here is
attributable to the finetune.
Improvement holds across all severity bands:
Table with columns: severity, baseline CER, model CER| severity | baseline CER | model CER |
|---|
| light (<8%) | 0.0438 | 0.0404 |
| medium (8–14%) | 0.0813 | 0.0713 |
| heavy (>14%) | 0.1211 | 0.1092 |
Usage
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
BASE = "Qwen/Qwen2.5-0.5B-Instruct"
tok = AutoTokenizer.from_pretrained(BASE)
model = PeftModel.from_pretrained(
AutoModelForCausalLM.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto"),
"Sheeda/arabic-ocr-post-correction-0.5b",
).eval()
INSTRUCTION = "صحّح أخطاء المسح الضوئي في النص التالي وأعد كتابته بشكل صحيح:"
noisy = 'وأضا ف أن "بريطايا يمكن أن تؤثر على الاتحاد الأوروبى عبر الـعمل هع شركائها".'
prompt = tok.apply_chat_template(
[{"role": "user", "content": f"{INSTRUCTION}\n\n{noisy}"}],
tokenize=False, add_generation_prompt=True,
)
inputs = tok(prompt, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=256, do_sample=False)
print(tok.decode(out[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
Use greedy decoding. This is restoration with one correct answer; sampling
only invents text that was never on the page.
Guardrail
A generative model can rewrite rather than repair. Reject any correction that
diverges too far from its input — measured against the input, never a
reference, so it works in production:
from Levenshtein import distance
def guard(source, prediction, max_drift=0.20):
if distance(source, prediction) / max(len(source), 1) <= max_drift:
return prediction
return source
max_drift=0.20 is near-optimal on both metrics. Tighten it where nothing may
be made worse; loosen it where findability outweighs character fidelity.
Training
- Base: Qwen/Qwen2.5-0.5B-Instruct
- Method: LoRA (r=32, alpha=64, dropout=0.05) on all attention and MLP projections
- Data: 56,931 synthetic (noisy → clean) pairs from AraSum
- Schedule: 2 epochs, effective batch 32, lr 2e-4 cosine, bf16
- Hardware: single RTX 5070 Ti (12GB), ~80 minutes
No public Arabic OCR-correction corpus exists, so training pairs are
synthesized: clean Arabic text corrupted by a confusion model weighted toward
how the script actually fails — 42% dot/skeleton confusion, 22% word
splitting and merging, the rest dropped characters, kashida insertion and
spurious diacritics. Severity is sampled per example between 4% and 18%.
Limitations
- The corruption is synthetic. It models Arabic OCR failure from the
structure of the script; it is not a recording of a specific engine's
output. Calibrating against real Tesseract or PaddleOCR output on scanned
pages is the honest next step, and until that is done these numbers describe
performance on synthetic corruption.
- Trained on Modern Standard Arabic news text. Dialectal and heavily classical
text are out of distribution.
- The guardrail catches wholesale rewrites, not small confident wrong edits.
Those need token-level confidence or a lexicon check.
Links
Code, data pipeline and evaluation harness:
https://github.com/Crypto47/arabic-ocr-post-correction
License
MIT. The base model's license governs the merged weights independently.