Hai presente quella sensazione di déjà-vu quando usi scikit-learn? Crei uno StandardScaler e chiami fit. Crei una LogisticRegression e chiami fit. Un encoder, un imputer, una PCA: sempre fit. Non è pigrizia dei progettisti, è la scelta di design più importante della libreria: tutto in scikit-learn è uno stimatore, e tutti gli stimatori si usano allo stesso modo. È il motivo per cui le prese elettriche di casa tua hanno tutte la stessa forma: una volta imparata la spina, colleghi qualsiasi elettrodomestico senza leggere il manuale. In questo tutorial smontiamo il ciclo di vita di uno stimatore, così non confonderai mai più fit, transform, fit_transform e predict.
Nascita: l’istanza e i suoi iperparametri
Uno stimatore nasce quando lo istanzi — se non ricordi cos’è un’istanza, ti basta sapere che è un oggetto concreto costruito da una classe, come una torta specifica sfornata da una ricetta. Al momento della nascita gli passi gli iperparametri: le scelte che fai tu, prima di vedere i dati.
# ambiente consigliato nel 2026: Python 3.12 + uv, editor VS Code o PyCharm
# uv venv .venv && source .venv/bin/activate && uv pip install scikit-learn
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(C=1.0, max_iter=5000) # iperparametri: scelti da te
print(model.get_params()["C"]) # 1.0 — ispezionabili in ogni momento
Pensa agli iperparametri come alle impostazioni di una lavatrice: temperatura e velocità di centrifuga le scegli prima di avviare il ciclo, e non cambiano da sole durante il lavaggio. A questo punto lo stimatore è configurato ma completamente ignorante: non ha ancora visto un solo dato. Se provi a fargli fare una previsione adesso, scikit-learn ti ferma con un errore parlante:
from sklearn.exceptions import NotFittedError
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
X, y = load_breast_cancer(return_X_y=True) # incluso in sklearn, nessun download
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
try:
model.predict(X_test)
except NotFittedError as e:
print("Non ancora addestrato:", e)
Apprendistato: fit, il momento in cui si impara
fit(X, y) è il cuore di tutto: lo stimatore guarda i dati e impara i suoi parametri. Attenzione alla distinzione, che genera confusione infinita: gli iperparametri li scegli tu alla creazione, i parametri li impara il modello dai dati durante fit. È la differenza tra le misure che il sarto decide di prendere (torace, vita, spalle: il metodo di lavoro) e i centimetri effettivi che legge sul tuo corpo: il metodo è fissato prima, i numeri arrivano solo quando il cliente è davanti a lui.
model.fit(X_train, y_train)
# convenzione di scikit-learn: tutto ciò che è stato IMPARATO dai dati
# finisce in attributi con underscore finale
print(model.coef_.shape) # (1, 30) — un peso per ciascuna delle 30 feature
print(model.classes_) # [0 1] — le classi viste durante il fit
print(model.n_iter_) # iterazioni servite per convergere
Quell’underscore finale (coef_, mean_, classes_) è una convenzione preziosa: se un attributo termina con _, esiste solo dopo il fit. Un colpo d’occhio ti dice cosa è impostazione e cosa è conoscenza acquisita.
Iperparametri: le impostazioni della lavatrice. Parametri: lo sporco specifico che il ciclo ha effettivamente tolto. I primi li scegli tu, i secondi arrivano dai dati.
Maturità: predict per i modelli, transform per i trasformatori
Dopo il fit, lo stimatore entra nella fase produttiva. Qui la famiglia si divide in due mestieri. I predittori (classificatori e regressori) rispondono con predict: dai loro dati nuovi, restituiscono una previsione. I trasformatori (scaler, encoder, PCA, imputer) rispondono con transform: dai loro dati, li restituiscono modificati. Stesso ciclo di vita, output diverso — come un traduttore e un correttore di bozze: entrambi prima studiano il testo, ma uno te lo restituisce in un’altra lingua, l’altro te lo restituisce ripulito.
from sklearn.preprocessing import StandardScaler
# --- un TRASFORMATORE ---
scaler = StandardScaler()
scaler.fit(X_train) # impara media e deviazione (mean_, scale_)
X_train_s = scaler.transform(X_train) # applica la trasformazione
X_test_s = scaler.transform(X_test) # stesse statistiche, MAI ri-fittare sul test
# fit_transform = fit + transform in un colpo solo (solo sul train!)
# X_train_s = scaler.fit_transform(X_train)
# --- un PREDITTORE ---
model = LogisticRegression(C=1.0, max_iter=5000)
model.fit(X_train_s, y_train)
y_pred = model.predict(X_test_s) # classi previste
y_proba = model.predict_proba(X_test_s) # probabilità per classe
print(f"Accuratezza sul test: {model.score(X_test_s, y_test):.3f}") # ~0.98
fit_transform è solo una scorciatoia (spesso più efficiente) per fit seguito da transform: legittima sul train, un errore concettuale sul test, perché ri-imparerebbe le statistiche dai dati d’esame. E score completa il quadro: ogni predittore sa autovalutarsi con una metrica di default (accuratezza per i classificatori, R² per i regressori).
Ogni stimatore nasce con gli iperparametri, impara i parametri con fit, poi lavora: i trasformatori restituiscono dati modificati con transform, i predittori restituiscono previsioni con predict e si valutano con score.
Un’interfaccia, mille pezzi intercambiabili
Ora il colpo di scena: siccome tutti gli stimatori parlano la stessa lingua, puoi sostituirli tra loro senza toccare il resto del codice. Vuoi provare una Random Forest al posto della regressione logistica? Cambi una riga.
from sklearn.ensemble import RandomForestClassifier
from sklearn.base import clone
for estimatore in [
LogisticRegression(max_iter=5000),
RandomForestClassifier(n_estimators=200, random_state=42),
]:
est = clone(estimatore) # copia fresca, non addestrata
est.fit(X_train_s, y_train)
nome = type(est).__name__
print(f"{nome}: {est.score(X_test_s, y_test):.3f}")
clone merita una menzione: crea una copia dello stimatore con gli stessi iperparametri ma senza i parametri imparati — una lavatrice nuova con le stesse impostazioni, mai usata. È quello che scikit-learn fa internamente nella cross-validation, per garantire che ogni fold parta da zero. Questa intercambiabilità è anche ciò che rende possibili le Pipeline, che incateneranno trasformatori e predittore in un unico stimatore: le vedremo nel prossimo articolo della serie.
Quando questo pattern non basta
Onestà intellettuale: l’API di scikit-learn è pensata per dati tabellari che stanno in memoria e per modelli che si addestrano in una passata. Se lavori con reti neurali profonde, addestramento incrementale su GPU o dataset che non entrano in RAM, il ciclo fit/predict ti andrà stretto: lì servono PyTorch o JAX, con i loro loop di training espliciti. E per un’analisi statistica con test di ipotesi e p-value, statsmodels è più adatto. Scikit-learn non è la risposta a tutto — è la risposta migliore al 90% dei problemi tabellari, che è già moltissimo.
Prova tu
Prendi il dataset load_wine e scrivi un ciclo che confronti tre stimatori a tua scelta (per esempio KNeighborsClassifier, DecisionTreeClassifier, LogisticRegression) sugli stessi dati scalati, usando clone e score. Poi ispeziona gli attributi con underscore finale di ciascuno dopo il fit: quali parametri ha imparato ognuno? Scoprirai che ogni famiglia di modelli «ricorda» i dati in modo completamente diverso.
Per approfondire: il paper che ha definito questo design, API design for machine learning software: experiences from the scikit-learn project (Buitinck et al., 2013, arXiv:1309.0238), e la pagina Developing scikit-learn estimators della documentazione ufficiale, dove le regole del ciclo di vita sono scritte nero su bianco.

I commenti sono riservati agli iscritti.
Accedi per commentare