A PoC (a-proof-of-concept) con LoRA
- texservice13
- 27 nov
- Tempo di lettura: 6 min
Ho già trattato LoRA in questo mio post 🔍 Cos’è LoRA e perché è così importante nella programmazione dell'AI , e oggi voglio presentare un PoC sull'utilizzo di LoRA:
# =========================================== # PoC: Thought chain completa con LoRA # Con frecce e commenti visivi per capire il flusso # =========================================== from transformers import AutoModelForImageClassification, AutoFeatureExtractor from peft import LoraConfig, get_peft_model from PIL import Image, ImageDraw import torch import pandas as pd # =========================================== # 1️⃣ Creazione immagine "fake" # =========================================== # Input → immagine tabella (può essere reale o fake) width, height = 400, 200 image = Image.new("RGB", (width, height), "white") draw = ImageDraw.Draw(image) # Disegno 2x2 griglia per simulare celle rows, cols = 2, 2 cell_w, cell_h = width // cols, height // rows for r in range(rows): for c in range(cols): x0, y0 = c*cell_w, r*cell_h x1, y1 = x0+cell_w, y0+cell_h draw.rectangle([x0, y0, x1, y1], outline="black") draw.text((x0+10, y0+10), f"R{r}C{c}", fill="black") # visualizzazione opzionale # image.show() # =========================================== # 2️⃣ Modello ViT + LoRA # =========================================== # Modello base → conosce pattern generali (non task specifico) model_id = "google/vit-base-patch16-224" model = AutoModelForImageClassification.from_pretrained(model_id) extractor = AutoFeatureExtractor.from_pretrained(model_id) # LoRA → aggiunge pesi mirati per task specifico (estrazione tabelle) config = LoraConfig( r=4, lora_alpha=16, target_modules=["query", "key", "value"], lora_dropout=0.1, bias="none", ) lora_model = get_peft_model(model, config) lora_model.eval() # modalità inferenza # =========================================== # 3️⃣ Preprocess immagine # =========================================== # Image → tensore compatibile con ViT # pixel_values → input del modello inputs = extractor(images=image, return_tensors="pt") pixel_values = inputs["pixel_values"] # Visualizzazione commentata # Input immagine --> preprocess --> pixel_values --> modello # 📌 Freccia: Input → Preprocess → Modello # =========================================== # 4️⃣ Inferenza con modello + LoRA # =========================================== with torch.no_grad(): logits = lora_model(pixel_values) # output grezzo del modello # Visualizzazione commentata # pixel_values --> modello + LoRA --> logits # 📌 Freccia: Modello + LoRA → logits/features # =========================================== # 5️⃣ Conversione logits → valori leggibili # =========================================== # Questa funzione rappresenta il decoding dei logits in celle/valori def convert_logits_to_cells(logits_tensor): """ Placeholder: in un caso reale decodificherebbe logits → valori delle celle """ return [ {"parameter": "R0C0", "date": "2025-11-27", "value": 42, "measurement": "unit"}, {"parameter": "R0C1", "date": "2025-11-27", "value": 17, "measurement": "unit"}, {"parameter": "R1C0", "date": "2025-11-27", "value": 33, "measurement": "unit"}, {"parameter": "R1C1", "date": "2025-11-27", "value": 58, "measurement": "unit"}, ] predictions = convert_logits_to_cells(logits) # Visualizzazione commentata # logits → convert_logits_to_cells() → predictions # 📌 Freccia: Logits → Decoding → Output leggibile # =========================================== # 6️⃣ Conversione in DataFrame # =========================================== df = pd.DataFrame(predictions) # Visualizzazione commentata # predictions → DataFrame → output finale # 📌 Freccia: Output leggibile → DataFrame → stampa # =========================================== # 7️⃣ Output finale # =========================================== print("=== Thought chain completa ===") print(df) |
Ecco cosa fa il codice, passo per passo, con il “film mentale” del flusso dati:
Immagine (input)
│
▼
Preprocess (pixel_values)
│
▼
Modello ViT + LoRA
│
▼
Logits/features grezzi
│
▼
Decoding (convert_logits_to_cells)
│
▼
Predictions (JSON/celle)
│
▼
DataFrame / output finale
N.B: La funzione convert_logits_to_cells() è un placeholder logico e rappresenta dove avviene il vero mapping dei logits in valori leggibili.
Spiegazione:
1. Generazione dell’immagine finta
Viene creata un’immagine RGB 400×200 bianca con PIL (Image.new).
Si disegna una griglia 2×2: si calcolano larghezza/altezza di ogni cella e si tracciano i rettangoli con draw.rectangle.
In ogni cella viene scritto un testo tipo R0C0, R0C1, ecc., così l’immagine simula una tabella con 4 celle.
In pratica: si sta costruendo un input di prova che assomiglia a una tabella, senza caricare un file reale.
2. Caricamento ViT e applicazione LoRA
model_id = "google/vit-base-patch16-224" carica un Vision Transformer pre-addestrato per classificazione immagini (ImageNet) tramite AutoModelForImageClassification.from_pretrained.
extractor = AutoFeatureExtractor.from_pretrained(model_id) crea il preprocessore che sa come ridimensionare/normalizzare l’immagine per questo ViT.
Poi entra in gioco LoRA:
Si definisce un LoraConfig con rank r=4, lora_alpha=16, lora_dropout=0.1 e target_modules=["query", "key", "value"].
get_peft_model(model, config) avvolge il modello con strati LoRA sulle sotto-componenti di attenzione (query/key/value), aggiungendo pesi addestrabili “a basso rango” solo lì.
lora_model.eval() mette il modello in modalità inferenza (no dropout addestrativo, batchnorm fissa, ecc.).
Concettualmente: il ViT pre-addestrato fornisce la “base” di conoscenza visiva, LoRA è lo strato leggero che, se addestrato, specializzerebbe il modello sul task “estrai valori da tabelle”.
3. Preprocess dell’immagine
inputs = extractor(images=image, return_tensors="pt") converte l’immagine PIL in tensore PyTorch con le trasformazioni che il ViT si aspetta (resize a 224×224, normalizzazione canali, ecc.).
pixel_values = inputs["pixel_values"] è il tensore che entra nel modello, tipicamente di forma (batch_size, 3, 224, 224).
Qui avviene il passaggio:Immagine PIL → extractor → pixel_values (tensore pronto per ViT).
4. Inferenza con ViT + LoRA
Con torch.no_grad() si disattiva il calcolo dei gradienti (serve solo per inferenza).
logits = lora_model(pixel_values) calcola l’output del modello: per un modello di classificazione, logits sono i punteggi grezzi per ogni classe (prima di softmax).
Qui la freccia logica è:pixel_values → ViT + LoRA → logits (feature/logits grezzi non ancora leggibili “a livello umano”).
5. Decoding: da logits a celle leggibili
La funzione convert_logits_to_cells(logits_tensor) è volutamente un placeholder: nel mondo reale prenderebbe i logits e li trasformerebbe nei valori di tabella (coordinate cella, valore, unità, data, ecc.).
Nel PoC, invece, ritorna una lista fissa di 4 dizionari, uno per ogni cella, con campi parameter, date, value, measurement.
Quindi, ricapitolando :logits → convert_logits_to_cells() → predictions (lista di dict, tipo JSON, che rappresentano le celle della tabella).
L’importante è che il punto concettuale in cui “leggi” davvero la tabella è proprio questa funzione: qui si fa il mapping logits → semantica (celle e valori).
6. Conversione in DataFrame
df = pd.DataFrame(predictions) prende la lista di dizionari e la trasforma in un DataFrame pandas, con colonne parameter, date, value, measurement.
Il DataFrame è la forma “standard” per lavorare poi analiticamente sulla tabella (filtri, groupby, esport in CSV, ecc.).
Freccia concettuale: predictions (JSON-like) → DataFrame → tabella strutturata per analisi downstream.
7. Output finale e “thought chain”
print("=== Thought chain completa ===") e print(df) mostrano in console il risultato finale leggibile.
Il diagramma testuale sotto riassume il chain of thought del dato:
Immagine→ Preprocess (pixel_values)→ Modello ViT + LoRA→ Logits grezzi→ Decoding (convert_logits_to_cells)→ Predictions (celle in JSON)→ DataFrame / output finale
Il PoC quindi è una pipeline completa, anche se:
la parte “intelligente” di estrazione tabellare (decoding dei logits) è simulata;
tutti gli altri passaggi (creazione immagine, preprocess, inferenza, strutturazione in DataFrame) sono reali e funzionanti, solo che i dati di uscita sono finti.
E come si potrebbe integrere la parte intelligente che è ora solo simulata ?
Per integrare la parte “intelligente” bisogna sostituire il placeholder convert_logits_to_cells() con un vero modello addestrato a fare table extraction (o con una testa addestrata sopra ViT) e una logica di decoding dei suoi output.
Opzione 1: usare direttamente Table Transformer
La strada più “realistica” oggi è usare un modello già specializzato come Microsoft Table Transformer (DETR per tabelle), che esiste sia per detection che per structure recognition.In pratica:
Si sostituisce il ViT di classificazione con un modello Table Transformer per object detection/structure recognition:
from transformers import TableTransformerForObjectDetection, AutoImageProcessor processor = AutoImageProcessor.from_pretrained( "microsoft/table-transformer-structure-recognition" ) model = TableTransformerForObjectDetection.from_pretrained( "microsoft/table-transformer-structure-recognition" )
Si Preprocessa l’immagine con il processor:
inputs = processor(images=image, return_tensors="pt") outputs = model(**inputs)
Gli output contengono bounding box e classi per righe, colonne, celle, ecc.
Si implementa un convert_logits_to_cells() che:
filtra le predizioni per oggetti di tipo “table cell / row / column”;
converte le bbox in coordinate immagine;
per ogni cella croppa il pezzetto di immagine corrispondente e lo passa a un OCR (es. Tesseract) per ottenere il testo;
costruisce la lista di dict con parameter (posizione cella), value (testo OCR), altri metadati (data, unità, ecc.).
In questo schema, la “parte intelligente” è quindi:Table Transformer → strutture (celle) + OCR → valori di cella → JSON/DataFrame.
Opzione 2: usare il tuo ViT + LoRA come extractor
Se si vuole tenere il ViT + LoRA, bisogna:
Cambiare modello in AutoModel (non solo classification head) e prendi le feature intermedie:
from transformers import AutoModel, AutoFeatureExtractor base_model = AutoModel.from_pretrained("google/vit-base-patch16-224") extractor = AutoFeatureExtractor.from_pretrained("google/vit-base-patch16-224") lora_model = get_peft_model(base_model, config)
Così si può usare outputs.last_hidden_state o outputs.pooler_output come embedding.
Quindi decidere che cosa vuoi predire:
caso semplice: “classe” della tabella (es. tipo di parametro in tutto il foglio);
caso complesso: coordinate di celle o valori numerici.
Aggiungere una piccola testa sopra le feature ViT:
per classificazione: Linear(768, num_labels) e addestri con cross-entropy;
per regressione di bounding box: Linear(768, 4) + loss L1/IoU;
per valori di cella: o fai OCR + piccola rete che classifica il tipo di cella, oppure tratti la rete come regressore sui valori se l’immagine è sintetica.
Sostituire convert_logits_to_cells() con:
una funzione che prende outputs dal modello (logits o hidden states),
applica softmax/sigmoid o inverse scaling,
mappa output → struttura tabella (righe/colonne/valori) secondo il formato che ti sei scelto.
Opzione 3: pipeline ibrida molto pratica
Tuttavia, spesso il flusso più robusto è:
Modello di table detection/structure (es. Table Transformer) per avere bbox di celle.
OCR per leggere il contenuto di ogni cella.
(Opzionale) Il tuo ViT+LoRA sopra le immagini di singole celle o righe per:
classificare il tipo di parametro,
normalizzare l’unità di misura,
stimare la colonna “giusta” se l’OCR è rumoroso.
In questo caso, convert_logits_to_cells() diventerebbe un orchestratore di:
chiamata al modello di struttura,
loop sulle celle per OCR,
eventuale passo ViT+LoRA per arricchire/normalizzare,
costruzione finale della lista di dict che finisce nel DataFrame.









Commenti