CoolFace
Modelpublic

markuz89/nano-scraper-qwen2.5-0.5b-lora

sourceHugging Faceapache-2.0updated 4d agoView on Hugging Face
0likes35downloads
Model Card

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:

  1. 1.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.
  2. 2.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à.
  3. 3.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.
  4. 4.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 sempre block-1, diventa sempre block-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 (o null)
  • —decision: uno tra found, not_found, ambiguous, conflicting, invalid_source
  • —evidenceBlockId: l'id del blocco da cui è stato estratto il valore (o null)
  • —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

python
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/completion generate 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

IperparametroValore
LoRA rank (r)8
LoRA alpha16
LoRA dropout0.05
Target modulesqproj, kproj, vproj, oproj, gateproj, upproj, down_proj
Parametri allenabili4.399.104 / 498.431.872 (0.88%)
Batch size8
Gradient accumulation2 (batch effettivo: 16)
Epoche2
Step totali9.278
Precisionebf16
Gradient checkpointingSì
Hardware1x NVIDIA RTX 4090 (24GB), noleggiata su Vast.ai
Tempo di training~5h (18.038s)
Memoria di picco13.8 GB

Risultati

MetricaValore
Loss iniziale (step 1)0.6192
Loss finale (step 9278)0.0448
Validation loss finale (full set, 9.456 esempi)0.0422

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