Views
No views yet
Qwen/Qwen2.5-1.5B-Instruct that converts noisy OCR text from invoices and receipts into structured JSON.rvl-cdip-invoice-extracted, filtered to high/medium-confidence examples| Metric | Base | Fine-tuned |
|---|---|---|
| Factual correctness | 3.88 | 7.11 |
| Schema following | 3.34 | 8.66 |
| Field completeness | 3.71 | 6.91 |
| Hallucination control | 2.31 | 7.52 |
| Overall | 2.98 | 7.40 |
null instead of guessing. See the repo's examples.md for annotated before/after cases.1import torch
2from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
3from peft import PeftModel
4
5BASE_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
6ADAPTER = "your-username/billstructai-qwen-lora"
7
8bnb_config = BitsAndBytesConfig(
9 load_in_4bit=True,
10 bnb_4bit_quant_type="nf4",
11 bnb_4bit_compute_dtype=torch.float16,
12 bnb_4bit_use_double_quant=True,
13)
14
15tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
16base_model = AutoModelForCausalLM.from_pretrained(
17 BASE_MODEL, quantization_config=bnb_config, device_map="auto"
18)
19model = PeftModel.from_pretrained(base_model, ADAPTER)
20model.eval()
21
22SYSTEM_PROMPT = """You are a precise invoice information extraction assistant.
23Your task is to extract structured invoice data from noisy OCR text.
24Return only valid JSON.
25Do not explain.
26Do not add markdown.
27Use null for missing fields.
28Do not hallucinate values that are not present in the OCR text."""
29
30ocr_text = "PASTE OCR TEXT HERE"
31
32messages = [
33 {"role": "system", "content": SYSTEM_PROMPT},
34 {"role": "user", "content": f"Extract invoice information from the OCR text below and return only valid JSON.\n\nOCR_TEXT:\n{ocr_text}"},
35]
36prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
37inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
38
39with torch.no_grad():
40 output = model.generate(**inputs, max_new_tokens=768, do_sample=False, pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id)
41
42print(tokenizer.decode(output[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))1{
2 "vendor": {"name": null, "address": null, "tax_id": null, "phone": null, "email": null, "website": null},
3 "customer": {"name": null, "address": null, "customer_id": null},
4 "invoice_number": null,
5 "invoice_date": null,
6 "due_date": null,
7 "purchase_order": null,
8 "line_items": [{"description": null, "quantity": null, "unit": null, "unit_price": null, "amount": null, "tax_rate": null}],
9 "subtotal": null,
10 "tax_amount": null,
11 "discount": null,
12 "total_amount": null,
13 "amount_paid": null,
14 "balance_due": null,
15 "currency": null,
16 "payment_terms": null,
17 "payment_method": null,
18 "notes": null
19}| LoRA rank | 16 |
| LoRA alpha | 32 |
| LoRA dropout | 0.05 |
| Target modules | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
| Quantization | 4-bit NF4, double quant |
| Epochs | 2 |
| Effective batch size | 8 (1 × 8 grad. accumulation) |
| Learning rate | 2e-4, cosine schedule |
| Selected checkpoint | step 900 / 940 (lowest eval_loss) |