Hai mai scorso le recensioni di un ristorante chiedendoti: «ma in media, la gente è contenta o no?». Con dieci recensioni le leggi tutte. Con diecimila, no. Eppure un’azienda che riceve diecimila recensioni al mese ha bisogno di quella risposta ogni giorno. La sentiment analysis serve esattamente a questo: insegnare a un programma a leggere un testo e stimarne la polarità — positivo, negativo, a volte neutro.
In questo tutorial costruiamo un classificatore di sentiment completo in Python, dall’inizio alla fine: testo grezzo in ingresso, etichetta di polarità in uscita. E alla fine apriremo il cofano per vedere quali parole hanno convinto il modello — perché un classificatore che non sai spiegare è un classificatore di cui non ti puoi fidare.
Il problema: i modelli mangiano numeri, non parole
Se hai già seguito il tutorial sulla regressione logistica sai che i modelli di machine learning lavorano su vettori di numeri. Se non lo ricordi, ti basta sapere questo: un classificatore è una funzione che prende in ingresso una lista di numeri e restituisce una classe. Il problema della sentiment analysis, quindi, non è tanto il classificatore — è trasformare una frase in numeri senza perdere il suo significato.
Lo strumento classico è TF-IDF (term frequency – inverse document frequency). L’idea è semplice se la pensi come un investigatore che pesa gli indizi: una parola che compare in tutte le recensioni («il», «che», «pizza» se parliamo di pizzerie) è un indizio che non vale niente, come un’impronta digitale trovata in un luogo pubblico. Una parola rara nel corpus ma frequente in quella recensione («immangiabile», «sublime») è un indizio pesantissimo. TF-IDF assegna a ogni parola un peso proporzionale a quanto è distintiva.
Un classificatore di sentiment non capisce le frasi: impara quali parole, pesate bene, spostano l’ago verso il positivo o il negativo.
La pipeline completa: il testo diventa un vettore di pesi TF-IDF, il classificatore somma i contributi e sceglie la polarità.
Prepara l’ambiente
Ti serve Python 3.12 e un ambiente virtuale pulito. Se usi uv (il gestore di pacchetti moderno, velocissimo), bastano due righe; con il classico venv funziona uguale:
# Con uv (consigliato)
uv venv && source .venv/bin/activate
uv pip install scikit-learn
# Oppure, alla vecchia maniera
python3.12 -m venv .venv && source .venv/bin/activate
pip install scikit-learn
Apri VS Code o PyCharm, crea un file sentiment.py e sei pronto. Niente GPU, niente API: gira tutto sul tuo portatile in meno di un secondo.
Il codice completo: dal corpus alla polarità
Per rendere il tutorial eseguibile senza scaricare nulla, usiamo un piccolo corpus di recensioni in italiano scritto a mano. Nella pratica reale lo sostituirai con migliaia di esempi (ne parliamo alla fine), ma la struttura del codice resta identica al 100%.
# sentiment.py — classificatore di sentiment con scikit-learn
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.metrics import classification_report
# Il corpus: recensioni etichettate (1 = positivo, 0 = negativo).
# Nella realta' ne servono migliaia: qui bastano per capire il meccanismo.
recensioni = [
# --- positive ---
"Cibo eccellente e servizio impeccabile, torneremo di sicuro",
"Esperienza fantastica, personale gentile e piatti curati",
"Ottimo rapporto qualita' prezzo, consigliatissimo",
"Pizza sublime, la migliore della citta'",
"Ambiente accogliente e dolci strepitosi, che scoperta",
"Tutto perfetto, dalla cantina al dessert",
"Porzioni generose e sapori autentici, bravissimi",
"Location splendida e antipasti deliziosi",
"Serata meravigliosa, ci siamo sentiti coccolati",
"Piatti eccellenti e presentazione impeccabile",
"Menu creativo e ingredienti freschissimi, promosso",
"Chef bravissimo, cena indimenticabile",
"Accoglienza calorosa e servizio attento",
"Pesce freschissimo e cottura perfetta",
"Personale cordiale e ambiente rilassante",
"Sapori genuini e porzioni abbondanti, ottimo",
"Antipasto di mare delizioso, davvero eccellente",
"Prezzo onesto e qualita' sorprendente",
"Dolce spettacolare, cucina meravigliosa",
"Consiglio vivamente, esperienza fantastica",
"Vini pregiati e abbinamenti azzeccati",
"Cucina raffinata, che meraviglia",
"Tavolo con vista e servizio impeccabile",
"Ristorante splendido, piatti curati e gustosi",
# --- negative ---
"Servizio lento e cibo immangiabile, mai piu'",
"Attesa infinita, piatti freddi e conto salato",
"Personale scortese, esperienza pessima e deludente",
"Carne bruciata e vino sa di tappo, evitate",
"Locale sporco e rumoroso, cena da dimenticare",
"Prezzi assurdi per porzioni ridicole, che delusione",
"Pasta scotta e camerieri sgarbati, terribile",
"Prenotazione persa e tavolo traballante, un disastro",
"Servizio pessimo e cameriere maleducato",
"Attesa interminabile e piatti tiepidi",
"Conto gonfiato e porzioni misere",
"Cibo insipido, mai piu' torneremo",
"Pesce non fresco, serata rovinata",
"Prenotazione ignorata, pessima organizzazione",
"Camerieri distratti e cucina lentissima",
"Prezzi esagerati per una qualita' scadente",
"Ordine sbagliato due volte, che disastro",
"Carne dura e contorno freddo, evitate",
"Ambiente caotico e servizio sciatto",
"Dolce industriale e caffe' bruciato, deludente",
"Delusione totale, esperienza da dimenticare",
"Sala fredda e attesa infinita, pessimo",
"Piatti scotti e sapori inesistenti, terribile",
"Cameriere sgarbato e conto salato, evitate",
]
# 24 recensioni positive seguite da 24 negative
etichette = [1] * 24 + [0] * 24
# Dividiamo in train e test: il modello va valutato su frasi MAI viste
X_train, X_test, y_train, y_test = train_test_split(
recensioni, etichette, test_size=0.25, random_state=42, stratify=etichette
)
# La pipeline: vettorizzazione TF-IDF + regressione logistica.
# ngram_range=(1, 2) cattura anche coppie di parole come "mai piu'".
modello = make_pipeline(
TfidfVectorizer(ngram_range=(1, 2), lowercase=True),
LogisticRegression(max_iter=1000),
)
modello.fit(X_train, y_train)
# Valutazione sul test set
print(classification_report(y_test, modello.predict(X_test),
target_names=["negativo", "positivo"]))
# Il momento della verita': frasi nuove di zecca
nuove = [
"Serata meravigliosa, piatti eccellenti",
"Servizio pessimo e attesa infinita",
]
for frase, prob in zip(nuove, modello.predict_proba(nuove)):
polarita = "positivo" if prob[1] > 0.5 else "negativo"
print(f"{frase!r} -> {polarita} (p={prob[1]:.2f})")
Esegui con python sentiment.py: il modello assegna la polarità corretta a entrambe le frasi nuove. Con appena qualche decina di esempi le probabilità non sono nettissime — restano vicine al mezzo — ma cadono dal lato giusto. Nota il parametro stratify: garantisce che train e test abbiano la stessa proporzione di positivi e negativi. Con un corpus così piccolo, senza stratificazione rischieresti un test set tutto di un solo colore.
Apri il cofano: quali parole pesano?
Una regressione logistica ha un coefficiente per ogni parola del vocabolario: positivo se spinge verso la classe «positivo», negativo altrimenti. Possiamo guardarli — ed è qui che il modello smette di essere una scatola nera:
import numpy as np
# Estraiamo vettorizzatore e classificatore dalla pipeline
vec = modello.named_steps["tfidfvectorizer"]
clf = modello.named_steps["logisticregression"]
parole = np.array(vec.get_feature_names_out())
pesi = clf.coef_[0]
ordine = np.argsort(pesi)
print("Parole piu' NEGATIVE:", parole[ordine[:5]])
print("Parole piu' POSITIVE:", parole[ordine[-5:]])
Vedrai comparire «pessima», «pessimo», «mai più» da un lato e «impeccabile», «fantastica», «meravigliosa» dall’altro. Il modello ha imparato da solo un piccolo dizionario dei sentimenti, pesato sui dati. È come assaggiare la minestra di un cuoco e riuscire a dedurre la sua lista della spesa: se il risultato ti convince, gli ingredienti erano quelli giusti.
Onestà prima di tutto: dove questo approccio si rompe
TF-IDF più regressione logistica è la baseline giusta per iniziare: veloce, interpretabile, gratis. Ma devi conoscerne i limiti:
- Sarcasmo e ironia: «Complimenti, due ore di attesa, davvero il massimo» contiene solo parole positive. Il modello lo classificherà positivo, e sbaglierà. Le parole da sole non bastano quando il significato è nell’intonazione.
- Negazioni lunghe: «non è affatto male» viene spesso letto come negativo per via del «male». I bigrammi (
ngram_range=(1, 2)) aiutano, ma non risolvono. - Dominio diverso: un modello addestrato su recensioni di ristoranti non funziona su tweet politici. «Bomba» è positivo per una pizzeria, molto meno in cronaca.
- Corpus minuscolo: le nostre poche decine di esempi servono a capire il meccanismo, non a produrre un modello serio. Sotto i mille esempi etichettati, diffida di qualsiasi metrica.
Quando questi limiti diventano un problema reale, il passo successivo è un modello transformer pre-addestrato e fine-tuned per il sentiment (su Hugging Face ne trovi anche per l’italiano, come i modelli della famiglia UmBERTo o feel-it). E se hai pochi testi al giorno, un LLM via API fa sentiment eccellente senza addestrare nulla — ma occhio: per milioni di recensioni al mese i costi esplodono, e la baseline scikit-learn torna improvvisamente attraente. Lo strumento giusto dipende dal volume, non dalla moda.
Prova tu
Due esercizi, in ordine di ambizione:
- Aggiungi al corpus 6 recensioni neutre («Nella media, niente di speciale») con etichetta 2 e trasforma il problema in classificazione a tre classi. La pipeline non cambia di una riga: osserva come cambiano precision e recall per la classe neutra, che è sempre la più difficile.
- Scarica un dataset vero di recensioni in italiano (su Hugging Face cerca «italian sentiment»), riaddestra la stessa pipeline su qualche migliaio di esempi e confronta le parole più pesate con quelle del nostro giocattolo. Quante sopravvivono?
Per approfondire: il capitolo sulla classificazione di testi di Speech and Language Processing di Jurafsky e Martin (disponibile gratuitamente online, terza edizione) è la spiegazione più chiara in circolazione di TF-IDF, naive Bayes e regressione logistica applicati al linguaggio. E la documentazione ufficiale di scikit-learn sulla feature extraction testuale merita mezz’ora del tuo tempo.
I commenti sono riservati agli iscritti.
Accedi per commentare