Bias, varianza e overfitting
Un modello di machine learning si stima su un insieme di dati, ma lo si usa su dati nuovi: la casa che sta per essere messa in vendita, la mail appena arrivata. Un modello che sui dati di addestramento non sbaglia mai può comportarsi male. Questo articolo spiega perché, e come accorgersene.
In-sample e out-of-sample
Fissata una misura dell'errore su un singolo punto (il quadrato della differenza per la regressione), ci sono due modi di fare la media:
- l'errore in-sample , sui dati usati per stimare il modello: è la funzione di costo che abbiamo sempre minimizzato;
- l'errore out-of-sample , su tutti i possibili ingressi, anche quelli mai osservati.
Ci interessa , ma possiamo calcolare solo . La differenza tra i due è l'errore di generalizzazione.
È come per uno studente: gli esercizi d'esame devono essere diversi da quelli svolti a lezione, altrimenti non si capisce se ha capito il metodo o ha solo imparato a memoria le soluzioni.
Overfitting
Prendiamo la funzione tra −1 e 1 e facciamo finta di non conoscerla: ne vediamo solo 12 misure, prese in punti equidistanti e sporcate da un rumore gaussiano con deviazione standard 0,3. Ogni misura, cioè, si discosta tipicamente di qualche decimo dal valore vero.
Su questi 12 punti stimiamo con i minimi quadrati un polinomio: prima di grado 1 (una retta), poi di grado 2, 3 e così via fino a 11. Per ogni grado misuriamo due errori:
- : quanto il polinomio sbaglia sui 12 punti usati per stimarlo;
- : quanto sbaglia su punti nuovi, presi a caso tra −1 e 1 con lo stesso rumore.
Con un solo dataset il risultato dipenderebbe dalla fortuna: un rumore particolare può favorire o penalizzare un grado. Per questo ripetiamo l'esperimento molte volte, ogni volta con un rumore diverso sugli stessi 12 punti, e facciamo la media degli errori:
L'errore in-sample scende sempre: un polinomio più flessibile passa più vicino ai punti, e con grado 11 (12 coefficienti per 12 punti) li attraversa tutti, con . L'errore out-of-sample invece scende fino al grado 3 e poi risale.
Quando diminuire fa aumentare , si parla di overfitting: il modello ha imparato anche il rumore, che sui dati nuovi è diverso. Il caso opposto, un modello troppo semplice per la funzione da imparare, è l'underfitting.
Bias e varianza
Lo stesso esperimento si capisce meglio scomponendo l'errore. Immaginiamo di stimare il modello su moltissimi dataset diversi e di farne la media: otteniamo un'ipotesi media . Allora l'errore out-of-sample atteso si divide in tre parti:
- il bias misura quanto l'ipotesi media è lontana dalla funzione vera: dipende da quanto è espressiva la famiglia di modelli. Una retta, con quanti dati si vuole, non approssimerà mai bene una sinusoide;
- la varianza misura quanto il modello stimato cambia da un dataset all'altro: dipende da quanto il modello si lascia trascinare dai dati particolari;
- è la varianza del rumore, l'errore irriducibile: nessun modello può prevedere il rumore. Qui vale .
È la stessa scomposizione dell'errore quadratico medio degli stimatori, solo che ora l'oggetto stimato è un'intera funzione.
| Grado | bias² | varianza | ||
|---|---|---|---|---|
| 1 | 0,330 | 0,214 | 0,014 | 0,318 |
| 3 | 0,067 | 0,006 | 0,025 | 0,121 |
| 5 | 0,045 | 0,000 | 0,038 | 0,128 |
| 9 | 0,015 | 0,000 | 0,119 | 0,209 |
| 11 | 0,000 | 0,000 | 3,092 | 3,182 |
Simulazione
I valori della tabella e del grafico hanno origine da una simulazione, così costruita: si generano 20.000 dataset, ognuno con un rumore diverso sugli stessi 12 punti, e su ciascuno si stima un polinomio di ogni grado. L'ipotesi media è la media dei 20.000 polinomi. Il bias² è la distanza quadratica media tra e , la varianza è quanto i polinomi si allontanano in media da . Entrambi sono calcolati su una griglia fitta di punti tra −1 e 1, e è la loro somma più .
Lo script stampa una riga per ogni grado, da 1 a 11: la tabella ne riporta alcune, il grafico quelle dispari. Con il seme fissato a 0 si ottengono esattamente questi valori.
import numpy as np
from numpy.polynomial import polynomial as P
rng = np.random.default_rng(0)
f = lambda x: np.sin(np.pi * x)
x = np.linspace(-1, 1, 12) # i 12 punti equidistanti
xs = np.linspace(-1, 1, 2001) # griglia fitta per bias e varianza
sigma, R = 0.3, 20_000 # rumore e numero di ripetizioni
gradi = range(1, 12)
e_in = {g: [] for g in gradi}
preds = {g: [] for g in gradi}
for _ in range(R):
y = f(x) + sigma * rng.standard_normal(12) # un nuovo dataset
for g in gradi: # stesso dataset per ogni grado
c = P.polyfit(x, y, g)
e_in[g].append(np.mean((P.polyval(x, c) - y) ** 2))
preds[g].append(P.polyval(xs, c))
for g in gradi:
p = np.array(preds[g])
g_media = p.mean(axis=0) # ipotesi media
bias2 = np.mean((g_media - f(xs)) ** 2)
var = np.mean(p.var(axis=0))
print(f"{g:2d} E_in {np.mean(e_in[g]):.3f} bias² {bias2:.3f} "
f"varianza {var:.3f} E_out {bias2 + var + sigma**2:.3f}")La retta ha poca varianza ma tanto bias. Dal grado 5 in su il bias è praticamente zero, ma la varianza cresce. Il minimo sta nel mezzo: è il compromesso bias-varianza.
La complessità del modello va scelta in base al numero di dati, non alla complessità della funzione da imparare. Una regola pratica chiede almeno 10 dati per ogni parametro del modello. Con 12 dati la regola ammetterebbe poco più di un parametro, mentre il polinomio di grado 3 ne ha 4: siamo già oltre, e infatti dal grado 4 in su l'errore risale.
Learning curves
Nella realtà la funzione vera non si conosce e non si possono generare tanti dataset a piacere. Uno strumento pratico sono le learning curves: si disegnano e l'errore stimato su dati non usati per l'addestramento, al variare del numero di dati di training.
- Problema di bias: le due curve si avvicinano presto, ma a un errore alto. Aggiungere dati non aiuta; servono un modello più ricco o regressori in più.
- Problema di varianza: tra le due curve resta un distacco ampio. Più dati aiutano, e così usare meno regressori o la regolarizzazione.
Riepilogo
- Ciò che conta è l'errore out-of-sample; quello in-sample è ottimista, e con modelli complessi può arrivare a zero.
- L'errore atteso si scompone in bias² + varianza + rumore: modelli semplici hanno più bias, modelli complessi più varianza.
- L'overfitting è quando il modello impara il rumore: scende ma sale.
- La complessità va proporzionata ai dati disponibili; le learning curves mostrano se il problema è di bias o di varianza.