<marco />
← Tutti gli articoli
10 min di lettura

Bias, varianza e overfitting

aiprogrammazione

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 EinE_\text{in}, sui NN dati usati per stimare il modello: è la funzione di costo che abbiamo sempre minimizzato;
  • l'errore out-of-sample EoutE_\text{out}, su tutti i possibili ingressi, anche quelli mai osservati.

Ci interessa EoutE_\text{out}, ma possiamo calcolare solo EinE_\text{in}. 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 f(x)=sin⁡(πx)f(x) = \sin(\pi x) tra −1 e 1 e facciamo finta di non conoscerla: ne vediamo solo 12 misure, prese in punti xx 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:

  • EinE_\text{in}: quanto il polinomio sbaglia sui 12 punti usati per stimarlo;
  • EoutE_\text{out}: 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:

out-of-sample: 3,2 ↑ out-of-sample in-sample 0 0,1 0,2 0,3 0,4 1 3 5 7 9 11 grado del polinomio

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 Ein=0E_\text{in} = 0. L'errore out-of-sample invece scende fino al grado 3 e poi risale.

Quando diminuire EinE_\text{in} fa aumentare EoutE_\text{out}, 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 gˉ\bar g. Allora l'errore out-of-sample atteso si divide in tre parti:

E[Eout]=bias2+varianza+σ2\mathbb{E}[E_\text{out}] = \text{bias}^2 + \text{varianza} + \sigma^2
  • 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;
  • σ2\sigma^2 è la varianza del rumore, l'errore irriducibile: nessun modello può prevedere il rumore. Qui vale 0,32=0,090{,}3^2 = 0{,}09.

È la stessa scomposizione dell'errore quadratico medio degli stimatori, solo che ora l'oggetto stimato è un'intera funzione.

Grado EinE_\text{in} bias² varianza EoutE_\text{out}
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 gˉ\bar g è la media dei 20.000 polinomi. Il bias² è la distanza quadratica media tra gˉ\bar g e ff, la varianza è quanto i polinomi si allontanano in media da gˉ\bar g. Entrambi sono calcolati su una griglia fitta di punti tra −1 e 1, e EoutE_\text{out} è la loro somma più σ2=0,09\sigma^2 = 0{,}09.

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.

python
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 EinE_\text{in} e l'errore stimato su dati non usati per l'addestramento, al variare del numero NN 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.
Retta (grado 1): problema di bias rumore σ² = 0,09 errore medio 0 0,1 0,2 0,3 bias² grande (0,20), resta anche con 100 dati Eout Ein 25 50 75 100 Grado 5: problema di varianza errore medio 0 0,1 0,2 0,3 varianza grande (0,12) con più dati la varianza sparisce Eout Ein 25 50 75 100 N, numero di dati di training
  • 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: EinE_\text{in} scende ma EoutE_\text{out} sale.
  • La complessità va proporzionata ai dati disponibili; le learning curves mostrano se il problema è di bias o di varianza.