markuz89/nano-scraper-qwen2.5-0.5b-lora
nano-scraper-qwen2.5-0.5b-lora (v2)
Adapter LoRA per Qwen2.5-0.5B-Instruct, fine-tuned per l'estrazione di dati strutturati (JSON) da pagine web, nell'ambito del progetto mini-jev-scraper — un estrattore URL + JSON Schema -> JSON strutturato ispirato al contratto operativo di "Jev", pensato per girare con modelli open-weight, constrained decoding e validazione applicativa a valle.
Novità nella v2
Test manuali sulla v1 avevano rivelato un bias sistematico: il modello tendeva a citare sempre gli stessi evidenceBlockId (in particolare jsonld-1 e block-2) indipendentemente dal contenuto reale del blocco, e a "normalizzare" valori fuori distribuzione invece di copiarli letteralmente. Analizzando il generatore del dataset sintetico sono state trovate e corrette 4 cause concrete:
- Scelta del blocco di evidenza: quando un valore compariva sia in JSON-LD sia nel DOM, veniva sempre etichettato come "trovato" nel primo blocco che lo conteneva (quasi sempre
jsonld-1, perché i blocchi strutturati precedono sempre quelli DOM nel chunker di produzione). Ora la scelta è casuale tra tutti i blocchi che contengono davvero il valore. - Difficoltà "simple" forzata al 100% JSON-LD: un quarto del dataset usava sempre la modalità JSON-LD, amplificando il bias del punto 1. Ora anche gli esempi "simple" variano tra json-ld/microdata/nessuno, come le altre difficoltà.
- Valori sintetici fuori distribuzione: aggiunti generatori di nomi/codici "alieni" (es.
ZXQ-NEBULA-482 HyperCube), mescolati nei pool esistenti con probabilità ~20-25%, per insegnare la copia letterale invece del pattern-matching su un vocabolario chiuso. - Peso del layout `dl`: le liste di definizione HTML (
<dl>) non vengono mai spezzate dal chunker di produzione (comportamento reale e voluto), quindi in quel layout tutti i campi collassano in un unico blocco — che essendo il titolo sempreblock-1, diventa sempreblock-2. Il peso di questo layout è stato ridotto dal 33% al 25% del dataset (mantenuto, non eliminato, perché siti reali lo usano davvero).
Test diagnostici post-training confermano il miglioramento: evidenceBlockId ora riflette la posizione reale del valore anche quando è presente sia JSON-LD sia più campi DOM distinti, e valori sintetici "alieni" vengono copiati esattamente.
Limitazioni residue emerse dai test (non affrontate in questo giro): un bug ricorrente nel parsing di prezzi in formato italiano (virgola decimale, es. "499,00" letto come 49900 invece di 499.00), e un caso isolato di "arricchimento" di un valore invece di copiarlo letteralmente. Vedi sezione Limitazioni.
Descrizione del modello
Dato un elenco di "blocchi" di evidenza estratti da una pagina web (JSON-LD, meta tag, testo del DOM, ecc.) e uno schema JSON con i campi richiesti, il modello restituisce solo un oggetto JSON — nessun testo libero — in cui ogni campo richiesto è popolato con:
value: il valore estratto (onull)decision: uno trafound,not_found,ambiguous,conflicting,invalid_sourceevidenceBlockId: l'id del blocco da cui è stato estratto il valore (onull)confidence: un numero tra 0 e 1
Il modello è addestrato a non inventare mai valori non presenti nei blocchi forniti (grounding rigoroso sull'evidenza) e a ignorare istruzioni eventualmente presenti nel testo della pagina stessa (mitigazione di prompt injection via contenuto scrapato).
- Modello base: Qwen/Qwen2.5-0.5B-Instruct
- Metodo: LoRA (PEFT) su tutti i proiettori attention + MLP
- Lingua: italiano (prompt, dati e output)
- Task: structured JSON extraction / grounded information extraction
- Licenza: Apache 2.0 (eredita quella del modello base)
Come usarlo
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base_model_id = "Qwen/Qwen2.5-0.5B-Instruct"
adapter_id = "markuz89/nano-scraper-qwen2.5-0.5b-lora"
tokenizer = AutoTokenizer.from_pretrained(adapter_id)
model = AutoModelForCausalLM.from_pretrained(base_model_id, dtype="bfloat16", device_map="auto")
model = PeftModel.from_pretrained(model, adapter_id)
prompt = "..." # vedi il formato di prompt descritto sopra: regole + campi + blocchi di evidenza
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
output = model.generate(**inputs, max_new_tokens=512)
print(tokenizer.decode(output[0], skip_special_tokens=True))Il modello si aspetta un prompt nel formato specifico usato dal progetto mini-jev-scraper (regole di estrazione + elenco campi/schema + blocchi di evidenza con id). Senza quel formato di prompt le prestazioni calano sensibilmente.
Dati di training
- Training set: 74.234 esempi (v2, corretto — vedi sezione "Novità nella v2")
- Validation set: 9.456 esempi
- Coppie
prompt/completiongenerate sinteticamente a partire da pagine web (reali e sintetiche) con schema JSON associato, nel formato "estrattore" descritto sopra (istruzioni di sistema, regole anti-hallucination/anti-injection, blocchi di evidenza con id/source). - Loss calcolata solo sulla porzione di
completion(completion-only loss masking): il modello non viene penalizzato sulla generazione del prompt.
Procedura di training
Risultati
La validation loss è in linea con la training loss finale, senza segnali evidenti di overfitting.
Limitazioni
- Modello piccolo (0.5B parametri): affidabile sul task specifico di estrazione strutturata guidata da schema, ma non va usato come assistente conversazionale generico.
- Le prestazioni dipendono fortemente dal rispettare il formato di prompt esatto usato in training (regole + campi + blocchi di evidenza con id).
- Addestrato e valutato solo su dati in lingua italiana.
- Parsing numerico: bug ricorrente nella lettura di numeri in formato italiano con virgola decimale (es. "499,00" può essere letto come 49900 invece di 499.00). Non affrontato in questo giro di fix.
- Copia letterale non sempre rispettata: osservato almeno un caso in cui un valore testuale è stato "arricchito" con conoscenza generale invece di essere copiato esattamente dal blocco. I fix v2 hanno migliorato la copia di valori sintetici/codici, ma non garantiscono la fedeltà letterale su ogni tipo di campo testuale libero.
- Come ogni modello linguistico, può comunque commettere errori di estrazione su casi ambigui o pagine con struttura molto diversa da quelle viste in training.
Framework versions
- PEFT 0.21.0
- Transformers 5.17.0
- PyTorch 2.6.0+cu124
