La cross-validazione è la prima tecnica da utilizzare per evitare overfitting e data leakage quando vogliamo addestrare un modello predittivo sui nostri dati.

La sua funzione è essenziale in quanto permette di testare funzioni e logiche sui nostri dati in modo sicuro - senza che questi processi vadano a "sporcare" i nostri dati di validazione.

Se vogliamo fare preprocessing, feature engineering o altre trasformazioni, bisogna sempre prima ripartire i nostri dati correttamente.

Questo assicura che i nostri dati di validazione siano effettivamente rappresentativi del nostro training set e (possibilmente) anche dei dati nel mondo reale che ancora non possediamo.

Se quindi riusciamo a creare un dataset e a ripartire correttamente i nostri dati, allora possiamo essere relativamente sicuri che i risultati che osserviamo dal training siano effettivamente usabili e bias-free.

Ripartire i dati prima di applicare la cross-validazione

Come ho scritto nell'articolo sulla cross-validazione e come metterla in pratica in Python, uno degli step essenziali è stratificare la variabile target rispetto alle fold (porzioni di dataset) per evitare che uno sbilanciamento nelle classi da predire possa influenzare negativamente sulle performance di un modello.

Nel caso di una classificazione binaria, ad esempio, è possibile che l'80% delle classi siano positive e solo il 20% siano negative. Addestrare un modello senza tener conto di questo sbilanciamento potrebbe portare a dei risultati poco attendibili.

Esistono tecniche di bilanciamento del dato, ma non le tratteremo in questo articolo. Invece, spiegherò come usare Python, Pandas e Sklearn per creare un dataset con una colonna in più che indicherà a che fold appartiene una riga del nostro dataset.

Per ogni sample del dataset, indicheremo a quale fold esso appartiene in modo che possiamo testare il nostro algoritmo su ogni fold, valutando i nostri algoritmi porzione per porzione.

Vediamo come applicare questo procedimento in Python.

Iniziamo importando le librerie essenziali

import pandas as pd
from sklearn import model_selection

Ponendo che i nostri dati di training si trovino in un file al percorso ./data/dataset_train.csv, importiamolo con Pandas e creiamo una colonna chiama fold che inizializziamo con il valore di -1. Questo perché le nostre fold avranno valore da 0 in avanti.

# importiamo il dataset di addestramento
df = pd.read_csv("./data/dataset_train.csv")

# definiamo una nuova colonna chiamata 'fold'
df["fold"] = -1

Tipicamente il dataset viene mescolato - applichiamo questo ragionamento usando .sample. Useremo il parametro frac=1 per dire a Pandas di prendere tutto il dataset e campionarlo in maniera casuale. Questo, di fatto, mescolerà ogni riga del dataset in maniera semplice ed efficace.

df = df.sample(frac=1).reset_index(drop=True)

Ora che abbiamo mescolato il dataset, possiamo definire la nostra variabile target per passarla a StratifiedKFold per stratificare il dataset. Useremo un numero arbitrario di 5 per le nostre fold.

Creato l'oggetto, iteriamo attraverso ogni riga del dataset e applichiamo la porzione generata da Sklearn nella colonna che prima aveva il valore costante di -1.

Fatto questo, salviamo il dataset.

y = df["target"].values

# inizializziamo l'oggetto per la stratificazione kfold con 5 porzioni
kf = model_selection.StratifiedKFold(n_splits=5)

# popoliamo la colonna fold
for fold, (train_idx, valid_idx) in enumerate(kf.split(X=df, y=y)):
    df.loc[valid_idx, "fold"] = fold

# salviamo il dataset
df.to_csv("./data/dataset_train_folds.csv")

Ora abbiamo un dataset che, oltre alle colonne che lo caratterizzavano, possiede una colonna aggiuntiva che indica la porzione per effettuare validazione.

Vediamo l'intero script.

import pandas as pd
from sklearn import model_selection

if __name__ == "__main__":

    df = pd.read_csv("./data/dataset_train.csv")

    # definiamo una nuova colonna chiamata 'fold'
    df["fold"] = -1
    
    # mescoliamo il dataset
    df = df.sample(frac=1).reset_index(drop=True)
    
    # dichiariamo la variabile target
    y = df["target"].values

    # inizializziamo l'oggetto per la stratificazione kfold con 5 porzioni
    kf = model_selection.StratifiedKFold(n_splits=5)

    # popoliamo la colonna fold
    for fold, (train_idx, valid_idx) in enumerate(kf.split(X=df, y=y)):
        df.loc[valid_idx, "fold"] = fold

    # salviamo il dataset
    df.to_csv("./data/dataset_train_folds.csv")

Ora possiamo lanciare questo script e semplicemente cambiando il percorso del dataset su disco sarà possibile creare una sua copia con porzioni di validazione.

Questo approccio è estendibile completamente, ed è consigliabile farlo per praticamente qualsiasi problema di machine learning.