Nel 2017 un progetto di deep learning voleva dire scaricare ImageNet, affittare quattro GPU per una settimana e allenare una ResNet da zero. Nel 2026 vuol dire scrivere from_pretrained("...") e aver finito in venti minuti. Lo spostamento non è una piccola ottimizzazione. È la forma intera del campo.
La ragione è il transfer learning. Qualcun altro ha già bruciato un milione di GPU-ore insegnando a un modello a capire le immagini, o l’inglese, o il codice. Tu prendi quel modello, sostituisci qualche layer vicino all’output e ne fai il fine-tuning sul tuo piccolo dataset. I layer bassi della rete, quelli che hanno imparato a rilevare bordi o token sintattici, già sanno cose che si trasferiscono gratis. Non stai insegnando al modello a vedere; gli stai insegnando quali categorie ti interessano.
La lezione 57 ha attraversato l’allenamento di una piccola rete end-to-end. Questa lezione è la versione che davvero corrisponde al tuo lavoro quotidiano.
L’intuizione: i layer imparano a livelli di astrazione diversi
I primi layer di una rete convoluzionale allenata rispondono a bordi e blob di colore. I layer intermedi rispondono a texture e parti di oggetti. I layer alti rispondono a concetti interi: “questo sembra un cane, questo sembra una macchina.” Se visualizzi i filtri del layer 1 di un qualsiasi classificatore di immagini allenato su un dataset reale, sembrano sostanzialmente uguali: bordi orientati, gradienti di colore. Feature visive generiche.
Lo stesso vale per i language model. I primi layer transformer gestiscono la sintassi locale, i bigrammi comuni, la morfologia. I layer intermedi seguono la struttura della frase. I layer in cima portano segnale specifico del task: per un modello allenato su Wikipedia, è “questa è un’entità, di che tipo, come si relaziona alle altre entità.”
Il transfer learning sfrutta l’asimmetria: i layer di feature generiche non hanno bisogno di essere ri-allenati quando cambi task, ma quelli specifici del task sì. Quindi tieni il corpo del modello e sostituisci la testa.
In codice, lo schema è grosso modo:
- Carica un modello pre-addestrato.
- Opzionalmente congela i layer bassi (
requires_grad = False). - Sostituisci il classificatore finale con uno dimensionato per il tuo numero di classi.
- Allena con un learning rate basso così non spazzi via quello che già c’è.
Hugging Face: il model hub
Nel 2026, quando qualcuno dice “il modello” quasi sempre intende “un checkpoint su Hugging Face.” L’Hub ospita centinaia di migliaia di modelli: ogni variante di BERT, ogni variante di Llama, ogni fine-tune di Stable Diffusion, ogni modello audio e di visione. Il client Python è la libreria transformers e i suoi compagni datasets, tokenizers, accelerate e peft.
Il boilerplate è identico tra famiglie di modelli:
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_id = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
model_id, num_labels=2,
)
Tutto qui. Le classi Auto* guardano la config del checkpoint e istanziano la classe concreta giusta. Puoi sostituire distilbert-base-uncased con roberta-base o bert-base-multilingual-cased senza cambiare una riga di codice di allenamento. Questo è il regalo della libreria.
Un fine-tune vero: DistilBERT su un dataset di sentiment
Facciamo il fine-tuning di un classificatore di sentiment sul dataset di recensioni IMDB. Due classi, circa 50.000 recensioni. Il giro completo, dall’inizio alla fine.
from datasets import load_dataset
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
DataCollatorWithPadding,
)
import numpy as np
import evaluate
ds = load_dataset("imdb")
print(ds)
# DatasetDict({ train: 25000, test: 25000, unsupervised: 50000 })
# Sotto-campiona cosi' l'esempio gira in minuti, non ore
ds["train"] = ds["train"].shuffle(seed=42).select(range(2000))
ds["test"] = ds["test"].shuffle(seed=42).select(range(500))
model_id = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_id)
def tokenize(batch):
return tokenizer(batch["text"], truncation=True, max_length=256)
ds_tok = ds.map(tokenize, batched=True)
collator = DataCollatorWithPadding(tokenizer=tokenizer)
model = AutoModelForSequenceClassification.from_pretrained(
model_id, num_labels=2,
)
accuracy = evaluate.load("accuracy")
def metrics(eval_pred):
logits, labels = eval_pred
preds = np.argmax(logits, axis=1)
return accuracy.compute(predictions=preds, references=labels)
args = TrainingArguments(
output_dir="./distilbert-imdb",
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=32,
num_train_epochs=2,
weight_decay=0.01,
logging_steps=50,
report_to="none",
)
trainer = Trainer(
model=model,
args=args,
train_dataset=ds_tok["train"],
eval_dataset=ds_tok["test"],
tokenizer=tokenizer,
data_collator=collator,
compute_metrics=metrics,
)
trainer.train()
trainer.evaluate()
Su una GPU T4 di Colab gratuita questo si allena in forse quattro minuti. Otterrai un’accuratezza tra l’85 e l’89% senza altri sforzi. Per vedere cosa sta succedendo, guarda i pezzi:
load_dataset("imdb")fa streaming del dataset dall’Hub. La prima chiamata lo mette in cache localmente; le chiamate successive sono istantanee.tokenizer(...)trasforma il testo ininput_idseattention_mask.max_length=256tronca le recensioni lunghe: puoi salire a 512 per maggiore accuratezza a costo più alto.DataCollatorWithPaddingfa padding di ogni batch alla sequenza più lunga di quel batch invece che al massimo globale. Speedup gratuito.AutoModelForSequenceClassificationsostituisce la testa di masked-language-modeling del DistilBERT pre-addestrato con una testa di classificazione a 2 classi inizializzata casualmente. Il corpo è pre-addestrato; solo la testa è fresca.Trainerimpacchetta il boilerplate della lezione 57: l’optimizer, lo scheduler del learning rate, l’accumulo di gradiente, la mixed precision, il checkpointing, il logging. Configuri le manopole tramiteTrainingArgumentse smetti di scrivere il training loop a mano.
Due epoche a lr=2e-5 è la ricetta standard per fare il fine-tuning di un modello BERT base. Salire tende a rompere i pesi pre-addestrati; scendere sotto-allena la testa.
Salvare e ricaricare
trainer.save_model("./distilbert-imdb-final")
tokenizer.save_pretrained("./distilbert-imdb-final")
# Usalo dopo:
from transformers import pipeline
clf = pipeline("sentiment-analysis", model="./distilbert-imdb-final")
clf("This movie was beautifully shot but the script dragged.")
# [{'label': 'LABEL_1', 'score': 0.78}]
Puoi anche pusharlo direttamente sull’Hub:
trainer.push_to_hub("your-username/distilbert-imdb")
Così il checkpoint diventa condivisibile. Sei mesi dopo, chiunque (incluso te) può ricaricare il modello esatto in tre righe.
PEFT e LoRA: fine-tuning a basso costo
Il fine-tuning completo di un modello da 7 miliardi di parametri richiede hardware serio: ogni peso ha un gradiente e uno stato dell’optimizer, e questo triplica facilmente la memoria. Il parameter-efficient fine-tuning (PEFT) schiva il problema: invece di aggiornare tutti i pesi, alleni piccoli adapter addestrabili e congeli il resto.
La tecnica dominante è LoRA, Low-Rank Adaptation. Per ogni matrice di pesi che vuoi adattare, aggiungi una coppia di matrici a basso rango A e B tali che W' = W + B @ A dove A e B sono minuscole (rango 8 o 16) rispetto a W. Alleni solo A e B. I pesi originali restano congelati.
I numeri sono drammatici. Fare il fine-tuning di un modello 7B con LoRA potrebbe allenare circa 10 milioni di parametri invece di 7 miliardi. La memoria scende di 5-10x. La qualità resta competitiva.
La libreria PEFT di Hugging Face rende questo un cambiamento di poche righe:
from peft import LoraConfig, get_peft_model, TaskType
lora_cfg = LoraConfig(
task_type=TaskType.SEQ_CLS,
r=8,
lora_alpha=16,
lora_dropout=0.05,
target_modules=["q_lin", "v_lin"], # quali matrici adattare
)
base = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=2)
model = get_peft_model(base, lora_cfg)
model.print_trainable_parameters()
# trainable params: 296,450 || all params: 67,251,202 || trainable%: 0.44
Inserisci quel model nello stesso Trainer di prima. Stai facendo fine-tuning di meno dello 0,5% dei parametri ottenendo gran parte dell’accuratezza. Per gli LLM ormai questo è il workflow di default: una GPU consumer da 24 GB può fare il fine-tuning di un modello 7B con LoRA + quantizzazione a 4 bit, qualcosa che due anni fa avrebbe richiesto un piccolo data center.
Visione e multimodale: stesso schema
Lo schema è identico per altre modalità, solo con classi Auto* diverse:
# Computer vision: fine-tune di un ViT per classificazione di immagini
from transformers import AutoImageProcessor, AutoModelForImageClassification
processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
model = AutoModelForImageClassification.from_pretrained(
"google/vit-base-patch16-224",
num_labels=10, ignore_mismatched_sizes=True,
)
Per progetti puramente di visione, la libreria timm (ora parte di Hugging Face) è lo standard di riferimento: ogni backbone di visione interessante, intercambiabile dietro un’unica API. Per task multimodali (immagine + testo), modelli come CLIP, SigLIP e la famiglia LLaVA sono tutti a un from_pretrained di distanza.
Dovresti fare fine-tuning?
Nel 2026 la domanda spesso non è “fine-tuning o allenamento da zero.” È “fine-tuning, o chiamare un LLM con un prompt buono?” Questo è il tema della prossima lezione. Anticipazione: per molti task NLP, fare un prompt a un modello hosted ti porta al 90% del risultato in zero tempo di allenamento, e solo quando prompt + retrieval falliscono in modo evidente conviene tirare fuori il fine-tuning.
Il modello mentale:
- Prompt a un LLM hosted. Costo più basso per sperimentare, nessun dato di training necessario, costoso a scala.
- Fine-tuning di un modello open-source con LoRA. Costo medio, sforzo medio, possiedi i pesi, calza stretto su un task noto.
- Allenamento da zero. Riservato a embedding, time series, sistemi di raccomandazione, modalità nuove, oppure “abbiamo un miliardo di esempi e un budget di ricerca.”
La riga di mezzo era il default per qualsiasi progetto non banale. Nel 2026 è la seconda scelta, presa quando il prompting non basta in modo evidente.
Cosa viene dopo
Ora hai coperto lo stack pratico di deep learning del 2026: tensori e autograd (lezione 56), un training loop scritto a mano (lezione 57) e il transfer learning con Hugging Face (questa). La prossima lezione fa zoom out e pone la domanda più grossa: quando lo strumento giusto è una chiamata LLM, quando è un modello fine-tuned, e quando è una pipeline ML classica? È la domanda di design più consequente del campo in questo momento, e il costo di sbagliarla è reale.
Riferimenti: documentazione di Hugging Face transformers (https://huggingface.co/docs/transformers), documentazione di datasets (https://huggingface.co/docs/datasets), documentazione di PEFT (https://huggingface.co/docs/peft), Hu et al. “LoRA: Low-Rank Adaptation of Large Language Models” (2021), libreria timm (https://huggingface.co/docs/timm). Recupero 2026-05-01.