Ti è mai capitato? Hai un dataset con 30, 50, 80 colonne, e la tentazione è darle tutte in pasto al modello: «più informazione ha, meglio predice», no? Poi però l’addestramento rallenta, l’accuratezza non migliora — a volte peggiora — e quando il capo ti chiede «quali fattori contano davvero?» ti ritrovi a balbettare davanti a 80 coefficienti. La verità scomoda è che in quasi tutti i dataset reali una parte delle colonne non porta informazione: porta rumore. La feature selection è l’arte di buttare via quel rumore, e in questo tutorial la mettiamo in pratica con tre strategie di scikit-learn: K-best, mutua informazione e selezione sequenziale.
Ripasso lampo: perché «più colonne» non significa «meglio»
Se non ricordi cos’è una feature, ti basta questo: è una colonna del dataset, una misura che descrive ogni esempio (l’età di un paziente, il raggio di una cellula, il prezzo di una casa). E se non ricordi la maledizione della dimensionalità, ti basta sapere che più dimensioni aggiungi, più i dati diventano «radi» e più il modello fatica a distinguere i pattern veri dalle coincidenze: con abbastanza colonne casuali, qualcuna sembrerà per caso correlata al target.
Pensa a quando prepari la valigia per un weekend: potresti portare tutto l’armadio «perché non si sa mai», ma poi la valigia pesa, non trovi niente, e il maglione da sci in agosto non ti è servito. Selezionare le feature è fare la valigia con criterio: porti solo ciò che serve al viaggio. I benefici concreti sono quattro: modelli più veloci da addestrare, meno overfitting, più interpretabilità, e meno dati da raccogliere in produzione (se una colonna non serve, smetti di misurarla).
SelectKBest: un colloquio individuale per ogni feature
La strategia più semplice è il filtro univariato: valuti ogni feature da sola, le assegni un punteggio che misura quanto è legata al target, e tieni le K migliori. È come fare colloqui individuali ai candidati per una squadra: veloce, perché ognuno viene valutato una volta e in isolamento. Per la classificazione il punteggio classico è la statistica F dell’ANOVA (f_classif): in una riga, misura quanto la media della feature cambia tra le classi rispetto a quanto la feature oscilla al proprio interno.
# ambiente 2026: Python 3.12 + uv, editor VS Code o PyCharm
# uv venv .venv && source .venv/bin/activate && uv pip install scikit-learn
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectKBest, f_classif
# 569 esempi, 30 feature numeriche: perfetto per esercitarsi
X, y = load_breast_cancer(return_X_y=True)
nomi = load_breast_cancer().feature_names
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# tieni le 8 feature con il punteggio F piu' alto
selettore = SelectKBest(score_func=f_classif, k=8)
X_train_sel = selettore.fit_transform(X_train, y_train) # fit SOLO sul train!
X_test_sel = selettore.transform(X_test)
# quali sono sopravvissute?
maschera = selettore.get_support()
print("Feature selezionate:")
for nome, punteggio in zip(nomi[maschera], selettore.scores_[maschera]):
print(f" {nome}: F = {punteggio:.1f}")
print(f"Da {X_train.shape[1]} colonne a {X_train_sel.shape[1]}")
Nota il dettaglio che vale un tutorial intero: il fit del selettore si fa solo sul training set. Se scegli le feature guardando anche il test set, stai facendo trapelare informazione — il famigerato data leakage — e il punteggio finale sarà gonfiato in modo invisibile.
Mutua informazione: quando la relazione non è una linea retta
La statistica F ha un punto cieco: misura relazioni lineari. Se una feature è utilissima ma in modo curvo — ad esempio il target è alto quando la feature è molto bassa oppure molto alta — l’ANOVA può darle un punteggio miserabile. La mutua informazione non ha questo limite: misura quanto sapere il valore della feature riduce l’incertezza sul target, qualunque sia la forma della relazione.
L’analogia giusta è il gioco delle venti domande (o Akinator, se hai seguito la serie): una buona domanda è quella la cui risposta ti avvicina alla soluzione, non importa se il legame è «diretto» o contorto. La mutua informazione dà un voto a ogni domanda-feature: zero se la risposta non ti dice nulla, alto se ti dice molto.
from sklearn.feature_selection import mutual_info_classif
# stima della mutua informazione tra ogni feature e il target
mi = mutual_info_classif(X_train, y_train, random_state=42)
# classifica delle 8 migliori secondo la mutua informazione
classifica = sorted(zip(nomi, mi), key=lambda coppia: coppia[1], reverse=True)
for nome, valore in classifica[:8]:
print(f"{nome}: MI = {valore:.3f}")
# per usarla dentro SelectKBest basta cambiare score_func:
selettore_mi = SelectKBest(score_func=mutual_info_classif, k=8)
Quale usare? La F è quasi gratis in termini di calcolo e funziona bene se ti aspetti relazioni monotone; la mutua informazione costa un po’ di più ma cattura anche le relazioni non lineari. Su dataset piccoli, provale entrambe e confronta le classifiche: dove concordano, puoi fidarti.
La feature selection in due mosse: prima assegni un punteggio a ogni colonna, poi tieni solo quelle sopra la soglia. Le colonne scartate (in ocra) non entrano nel modello.
Selezione sequenziale: costruire la squadra un giocatore alla volta
I filtri univariati hanno un difetto strutturale: valutano ogni feature da sola. Ma due feature mediocri da sole possono essere fortissime insieme, e due feature ottime possono essere ridondanti (dicono la stessa cosa due volte). La selezione sequenziale risolve il problema alla radice: invece di dare voti individuali, prova le squadre. Parte da zero feature, prova ad aggiungerne una alla volta — scegliendo ogni volta quella che migliora di più il punteggio in cross-validation del tuo modello — e si ferma quando raggiunge il numero richiesto. Come costruire una squadra di calcetto: non prendi i cinque migliori giocatori in assoluto, prendi quelli che giocano meglio insieme.
Se non ricordi la cross-validation, ti basta sapere che divide il training set in K fette e valuta il modello K volte, usando a turno una fetta come banco di prova: il punteggio medio è una stima onesta della generalizzazione.
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
modello = LogisticRegression(max_iter=5000)
# forward selection: parte da 0 feature e ne aggiunge una alla volta
sfs = SequentialFeatureSelector(
modello,
n_features_to_select=8,
direction="forward", # "backward" parte da tutte e toglie
cv=5,
n_jobs=-1, # le valutazioni sono indipendenti: parallelizza
)
# tutto in Pipeline: scaling e selezione ri-fittati dentro ogni fold, zero leakage
pipe = Pipeline([
("scaler", StandardScaler()),
("selezione", sfs),
("classificatore", LogisticRegression(max_iter=5000)),
])
pipe.fit(X_train, y_train)
print("Feature scelte:", nomi[pipe.named_steps["selezione"].get_support()])
print(f"Accuratezza sul test: {pipe.score(X_test, y_test):.3f}")
Il prezzo di questa accuratezza è il tempo: per scegliere 8 feature su 30, la forward selection addestra il modello centinaia di volte (ogni candidata, a ogni giro, per ogni fold). Sui dataset grandi diventa proibitiva — ed è per questo che nella pratica si parte spesso con un filtro veloce (K-best) per sfoltire, e si rifinisce con la sequenziale sulle superstiti.
Un filtro univariato dà un voto a ogni feature come se giocasse da sola. La selezione sequenziale prova le squadre — e le squadre, nei dati come nel calcetto, non sono la somma dei singoli.
Onestà: quando la feature selection non ti serve
Non sempre serve, e dirlo è doveroso. Se usi modelli ad albero come random forest o gradient boosting, la selezione è in gran parte implicita: le feature inutili vengono semplicemente ignorate negli split, e potarle prima cambia poco. Se usi la regolarizzazione L1 (Lasso), è il modello stesso ad azzerare i coefficienti delle feature superflue. E se hai 10 colonne scelte con cura da un esperto di dominio, sfoltirle è burocrazia. La feature selection brilla quando le colonne sono tante e di provenienza dubbia, quando l’interpretabilità conta, o quando ogni feature in produzione ha un costo di raccolta. E ricorda il mantra: la selezione va fatta dentro la cross-validation (in Pipeline), mai prima — altrimenti il leakage ti regala punteggi che in produzione non rivedrai mai.
Prova tu
Prendi il dataset load_wine di scikit-learn (13 feature) e aggiungi tu stesso 10 colonne di puro rumore con np.random.default_rng(42).normal(size=(len(X), 10)), concatenandole con np.hstack. Poi esegui le tre strategie di questo tutorial e verifica: quante colonne di rumore riescono a intrufolarsi tra le prime 8 di ciascun metodo? Confronta infine l’accuratezza di una regressione logistica con tutte le 23 colonne e con le sole 8 selezionate. Se il rumore viene scartato e l’accuratezza non cala, hai toccato con mano il senso di tutto questo.
Per approfondire: la guida ufficiale di scikit-learn sulla feature selection (sezione «Feature selection» della documentazione) è il riferimento pratico; per la teoria, il classico è Guyon e Elisseeff, An Introduction to Variable and Feature Selection (JMLR, 2003) — vent’anni dopo, ancora attualissimo.

I commenti sono riservati agli iscritti.
Accedi per commentare