W3docs

Cross-Validation in Python: K-Fold, Stratificata, LOOCV

Impara la cross-validation in Python: K-Fold, Stratificata, Leave-One-Out, CV annidata e pipeline con scikit-learn — con esempi eseguibili.

La cross-validation è il metodo standard per stimare quanto bene un modello di machine learning si comporterà su dati mai visti. Invece di affidarsi a una singola suddivisione train/test — che può produrre un punteggio eccessivamente ottimistico o pessimistico a seconda di quali campioni finiscono in quale parte — la cross-validation addestra e valuta il modello più volte su diverse partizioni dei dati e ne calcola la media.

Questa pagina tratta:

  • Perché una singola suddivisione train/test non è sufficiente
  • La cross-validation K-Fold e come scegliere k
  • La K-Fold Stratificata per distribuzioni di classi sbilanciate
  • La cross-validation Leave-One-Out (LOO) per dataset di piccole dimensioni
  • La valutazione di metriche multiple con cross_validate
  • L'uso di una Pipeline all'interno della cross-validation per prevenire la perdita di dati
  • La cross-validation annidata per la regolazione imparziale degli iperparametri

Tutti gli esempi usano scikit-learn e il dataset Iris integrato, quindi puoi eseguirli immediatamente senza scaricare nulla.

Perché la Cross-Validation è Importante

Un flusso di valutazione naive divide i dati una volta sola, addestra su una parte e testa sull'altra. Il punteggio ottenuto dipende fortemente da quali campioni sono finiti in ciascuna parte — una suddivisione fortunata può far sembrare buono un modello debole; una sfortunata può far sembrare scarso un modello forte.

La cross-validation risolve questo problema ripetendo il processo train/test k volte, ogni volta usando una porzione diversa dei dati come insieme di test. Il punteggio finale è la media su tutti i fold, che è molto più stabile di una singola misurazione.

La cross-validation sfrutta al massimo i dati limitati: ogni campione viene usato sia per l'addestramento che per la valutazione nell'intero esperimento.

Consulta la pagina train/test split per la tecnica di base più semplice che la cross-validation migliora.

Cross-Validation K-Fold

K-Fold è la strategia di cross-validation più diffusa. I dati vengono divisi in k fold di uguale dimensione. In ognuna delle k iterazioni:

  1. Un fold viene tenuto da parte come insieme di test.
  2. I restanti k - 1 fold formano l'insieme di addestramento.
  3. Il modello viene addestrato da zero e valutato sul fold di test.

Dopo k iterazioni si hanno k punteggi. La loro media è la stima delle prestazioni con cross-validation; la loro deviazione standard indica quanto siano consistenti le prestazioni su diverse sezioni di dati.

Per k = 5 e un dataset di 150 campioni, ogni fold contiene 30 campioni (20%) per il test e 120 campioni (80%) per l'addestramento.

Esempio Base di K-Fold

from sklearn.model_selection import KFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

# Load the built-in Iris dataset (150 samples, 4 features, 3 classes)
iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
kfold = KFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(model, X, y, cv=kfold, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     1.     0.9333 0.9667 0.9667]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9733  Std: 0.0249

La funzione cross_val_score gestisce internamente tutte le iterazioni. Parametri chiave:

ParametroScopo
estimatorQualsiasi modello scikit-learn (o Pipeline)
X, yMatrice delle feature e vettore target
cvOggetto cross-validator o intero (es. cv=5)
scoringStringa della metrica — 'accuracy', 'f1_macro', 'roc_auc', ecc.

Scegliere k

  • k = 5 o k = 10 è consigliato per la maggior parte dei dataset. Questi valori offrono un buon compromesso bias-varianza nella stima.
  • k più grande (es. 10) produce un bias inferiore ma una varianza maggiore nella stima, ed è più costoso da calcolare.
  • k più piccolo (es. 3) è più veloce ma la stima è più sensibile al modo in cui i dati sono stati suddivisi.
  • Per dataset molto piccoli (meno di ~100 campioni) considera di usare Leave-One-Out.

Ispezione Manuale dei Fold

Puoi iterare sui fold manualmente quando hai bisogno di ispezionare cosa entra in ogni suddivisione o quando vuoi eseguire una logica personalizzata per fold:

from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
import numpy as np

iris = load_iris()
X, y = iris.data, iris.target

kfold = KFold(n_splits=5, shuffle=True, random_state=42)

for fold, (train_idx, test_idx) in enumerate(kfold.split(X), start=1):
    X_train, X_test = X[train_idx], X[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]
    print(f"Fold {fold}: train={len(train_idx)} samples, test={len(test_idx)} samples")

Output:

Fold 1: train=120 samples, test=30 samples
Fold 2: train=120 samples, test=30 samples
Fold 3: train=120 samples, test=30 samples
Fold 4: train=120 samples, test=30 samples
Fold 5: train=120 samples, test=30 samples

Cross-Validation K-Fold Stratificata

La K-Fold semplice divide i dati in base all'ordine degli indici. Con distribuzioni di classi sbilanciate, alcuni fold possono contenere pochissimi esempi di una classe minoritaria, rendendo il punteggio inaffidabile.

La K-Fold Stratificata garantisce che ogni fold contenga approssimativamente la stessa proporzione di ciascuna classe presente nell'intero dataset. Usa StratifiedKFold ogni volta che il tuo target è categorico:

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
skfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

scores = cross_val_score(model, X, y, cv=skfold, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     0.9667 0.9333 1.     0.9333]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9667  Std: 0.0298

StratifiedKFold è il cross-validator predefinito usato internamente da GridSearchCV e RandomizedSearchCV per i problemi di classificazione — la stratificazione viene applicata automaticamente in questi contesti.

Cross-Validation Leave-One-Out

La cross-validation Leave-One-Out (LOO) è il caso estremo: k è uguale al numero di campioni. In ogni iterazione, un campione è l'insieme di test e tutti i restanti campioni formano l'insieme di addestramento. Per un dataset di 150 campioni ciò significa 150 cicli di addestramento/valutazione.

from sklearn.model_selection import LeaveOneOut, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
loocv = LeaveOneOut()

scores = cross_val_score(model, X, y, cv=loocv, scoring='accuracy')

print(f"Number of folds: {len(scores)}")        # 150
print(f"Mean accuracy: {scores.mean():.4f}")     # 0.9667
print(f"Std deviation: {scores.std():.4f}")      # 0.1795

Quando usare LOO:

  • Il tuo dataset ha meno di ~100 campioni e non puoi permetterti di riservarne alcuno per il test.
  • Vuoi la stima a bias minimo delle prestazioni del modello.

Svantaggi di LOO:

  • Costo computazionale molto elevato — il modello viene riaddesstrato n volte.
  • Alta varianza nella stima: il punteggio di test di ogni fold è 0 o 1 (classificazione binaria) o un singolo punto, quindi la deviazione standard non è significativa per i singoli fold.

Per la maggior parte dei dataset, la K-Fold con k=5 o k=10 offre un compromesso migliore.

Valutare Più Metriche Contemporaneamente

cross_val_score può calcolare solo una metrica per chiamata. Usa cross_validate per calcolare più metriche simultaneamente e recuperare anche i punteggi di addestramento per verificare l'overfitting:

from sklearn.model_selection import KFold, cross_validate
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
import numpy as np

iris = load_iris()
X, y = iris.data, iris.target

model = LogisticRegression(max_iter=200)
kfold = KFold(n_splits=5, shuffle=True, random_state=42)

cv_results = cross_validate(
    model, X, y,
    cv=kfold,
    scoring=['accuracy', 'f1_macro'],
    return_train_score=True,
)

print("Test accuracy: ", cv_results['test_accuracy'].round(4))
# Test accuracy:  [1.     1.     0.9333 0.9667 0.9667]

print("Train accuracy:", cv_results['train_accuracy'].round(4))
# Train accuracy: [0.975  0.9583 0.9833 0.975  0.9833]

print("Test F1-macro: ", cv_results['test_f1_macro'].round(4))
# Test F1-macro:  [1.     1.     0.9259 0.9691 0.971 ]

Confrontare i punteggi di addestramento e di test tra i fold è un modo rapido per individuare l'overfitting: se l'accuratezza di addestramento è costantemente molto più alta di quella di test, il modello sta memorizzando i dati di addestramento. Consulta la discussione su bias e varianza per ulteriori informazioni.

Usare una Pipeline all'Interno della Cross-Validation

Un errore comune è adattare i passi di preprocessing (come la scalatura delle feature o l'imputazione) sull'intero dataset prima della cross-validation. Questo fa trapelare informazioni dal fold di test nel processo di addestramento, portando a un punteggio eccessivamente ottimistico.

Il pattern corretto è racchiudere il preprocessing e il modello insieme in una Pipeline e passare la pipeline a cross_val_score. scikit-learn riadatta l'intera pipeline — scaler incluso — in modo indipendente all'interno di ogni fold:

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

# Correct: preprocessing is fitted only on training folds
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('clf', LogisticRegression(max_iter=200)),
])

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X, y, cv=skf, scoring='accuracy')

print("Fold scores:", scores.round(4))
# Fold scores: [1.     0.9667 0.9    1.     0.9   ]

print("Mean accuracy: %.4f  Std: %.4f" % (scores.mean(), scores.std()))
# Mean accuracy: 0.9533  Std: 0.0452

Usa sempre una Pipeline quando il tuo flusso di lavoro include qualsiasi passo che apprende dai dati (scalatura, PCA, codifica, imputazione).

Cross-Validation Annidata

Quando usi la cross-validation sia per regolare gli iperparametri che per valutare le prestazioni del modello sugli stessi dati, rischi di fare overfitting sui fold di validazione — gli iperparametri scelti sono quelli che hanno ottenuto il punteggio migliore su quelle particolari partizioni, quindi il punteggio riportato è ottimistico.

La cross-validation annidata separa le due preoccupazioni:

  • Loop interno: seleziona gli iperparametri tramite grid search sui fold di addestramento.
  • Loop esterno: valuta il modello migliore trovato dal loop interno su un fold di test tenuto da parte.
from sklearn.model_selection import GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

# Inner CV: hyperparameter selection
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=2)
param_grid = {'C': [0.01, 0.1, 1, 10]}
gs = GridSearchCV(
    LogisticRegression(max_iter=300),
    param_grid,
    cv=inner_cv,
    scoring='accuracy',
)

# Outer CV: unbiased performance estimation
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
nested_scores = cross_val_score(gs, X, y, cv=outer_cv, scoring='accuracy')

print("Nested CV fold scores:", nested_scores.round(4))
# Nested CV fold scores: [0.9667 1.     0.9333 1.     0.9   ]

print("Mean accuracy: %.4f" % nested_scores.mean())
# Mean accuracy: 0.9600

L'approccio annidato fornisce una stima imparziale delle prestazioni del modello finale distribuito. Usalo ogni volta che stai riportando risultati in un contesto di ricerca o confrontando algoritmi. Per un flusso di lavoro di deployment semplice, in cui riaddestrerai comunque su tutti i dati disponibili, un singolo loop esterno con GridSearchCV è di solito sufficiente. Consulta la pagina grid search per una guida dettagliata sulla regolazione degli iperparametri.

Errori Comuni

Preprocessing fuori dal fold

Adattare uno scaler sull'intero dataset prima di chiamare cross_val_score — invece di farlo all'interno di una Pipeline — fa trapelare le statistiche del fold di test nell'addestramento. La soluzione è usare sempre una Pipeline.

Uso scorretto di random_state

Se imposti shuffle=True senza un random_state, ogni esecuzione produce una suddivisione diversa e i tuoi risultati non sono riproducibili. Imposta sempre random_state su un intero fisso quando riporti i risultati.

Interpretare la deviazione standard

Una deviazione standard elevata tra i fold non è sempre negativa — può riflettere una genuina variabilità nel dataset (es. alcuni fold sono più facili di altri). Esamina i punteggi dei singoli fold prima di trarre conclusioni.

Cross-validation su dati di serie temporali

K-Fold mescola i dati in modo casuale, il che miscelerebbe informazioni future nelle finestre di addestramento passate per problemi di serie temporali. Usa invece TimeSeriesSplit di scikit-learn, che rispetta l'ordine temporale.

Riferimento Rapido

TecnicaQuando usarlaClasse scikit-learn
K-FoldScelta predefinita per la maggior parte dei task di regressione/classificazioneKFold
K-Fold StratificataClassificazione con classi sbilanciateStratifiedKFold
Leave-One-OutDataset molto piccoli (< ~100 campioni)LeaveOneOut
CV AnnidataRiportare punteggi imparziali con la regolazione degli iperparametriGridSearchCV dentro cross_val_score
CV Serie TemporaliDati con ordinamento temporaleTimeSeriesSplit

Argomenti Correlati

  • Train/Test Split — la tecnica di base più semplice che la cross-validation migliora
  • Grid Search — regolazione degli iperparametri, comunemente abbinata alla cross-validation
  • Logistic Regression — uno dei classificatori usati negli esempi precedenti
  • Linear Regression — controparte di regressione, anch'essa valutata con la cross-validation
  • Confusion Matrix — analisi delle prestazioni per classe a complemento dei punteggi di accuratezza
  • AUC-ROC Curve — un'altra metrica di valutazione che puoi passare a scoring in cross_val_score
Was this page helpful?