Vai al contenuto
Machine Learning e Reti Neurali

Regressione e discesa del gradiente: equazioni normali, convergenza, regressione logistica e sovradattamento

Il problema dell'apprendimento supervisionato, le equazioni normali dimostrate, la convergenza della discesa del gradiente con il limite 2/L, la regressione logistica e l'entropia incrociata, sovradattamento e regolarizzazione ridge con esperimenti.

Il machine learning (apprendimento automatico) costruisce modelli che imparano dai dati invece di essere programmati regola per regola: si sceglie una famiglia di funzioni con dei parametri, si definisce un errore che misura quanto le previsioni si allontanano dai dati, e si cercano i parametri che lo rendono minimo. Tutte le reti neurali, anche le più grandi, funzionano così. Questa prima lezione presenta gli ingredienti con i modelli più semplici: la regressione lineare (con la soluzione esatta e quella iterativa della discesa del gradiente, di cui si dimostra la convergenza), la regressione logistica per la classificazione, e il problema centrale di tutto il corso, il sovradattamento.

Il problema dell’apprendimento supervisionato

Si hanno n esempi (\mathbf x_i, y_i): un ingresso (le misure di un sensore, i pixel di un’immagine) e la risposta giusta (una temperatura, una cifra). Si vuole una funzione f_{\mathbf w}(\mathbf x) che preveda y anche per ingressi nuovi. Si sceglie una perdita \ell(f_{\mathbf w}(\mathbf x), y) e si minimizza la sua media sui dati:

    \[ J(\mathbf w) = \frac{1}{n}\sum_{i=1}^n\ell\big(f_{\mathbf w}(\mathbf x_i),\ y_i\big) \]

Il vero obiettivo però non è far bene sui dati che si hanno, ma su quelli che arriveranno: per questo si tengono da parte alcuni esempi (insieme di validazione e insieme di prova) che non si usano per l’addestramento e servono solo a misurare.

La regressione lineare e le equazioni normali

Con f_{\mathbf w}(\mathbf x) = \mathbf w^T\mathbf x (aggiungendo a \mathbf x una componente costante 1, il primo peso fa da termine noto) e l’errore quadratico, mettendo gli esempi nelle righe di una matrice X:

    \[ J(\mathbf w) = \frac{1}{n}\|X\mathbf w - \mathbf y\|^2 \]

Teorema. Se X^TX è invertibile, il minimo è \mathbf w^* = (X^TX)^{-1}X^T\mathbf y (equazioni normali).

Dimostrazione. J è una funzione quadratica convessa; il gradiente è \nabla J = \frac{2}{n}X^T(X\mathbf w - \mathbf y) e si annulla per X^TX\mathbf w = X^T\mathbf y. La matrice hessiana \frac{2}{n}X^TX è definita positiva (se le colonne di X sono indipendenti), quindi il punto stazionario è l’unico minimo. Geometricamente, X\mathbf w^* è la proiezione ortogonale di \mathbf y sullo spazio generato dalle colonne di X: il residuo X\mathbf w^* - \mathbf y è perpendicolare a tutte le colonne. ∎

La discesa del gradiente

Per modelli più complicati non esiste una formula chiusa. Si parte da un \mathbf w_0 qualunque e si fanno piccoli passi nella direzione in cui l’errore diminuisce più in fretta, l’opposto del gradiente:

    \[ \mathbf w_{k+1} = \mathbf w_k - \eta\,\nabla J(\mathbf w_k) \]

con \eta il passo di apprendimento (learning rate). È l’algoritmo che addestra tutte le reti neurali.

Teorema (convergenza sul quadratico). Per J quadratico con hessiana H definita positiva, di autovalori 0 \lt  \mu \le \dots \le L, la discesa del gradiente converge a \mathbf w^* da qualunque punto di partenza se e solo se 0 \lt  \eta \lt  \frac{2}{L}.

Dimostrazione. Per un quadratico \nabla J(\mathbf w) = H(\mathbf w - \mathbf w^*), quindi l’errore \mathbf e_k = \mathbf w_k - \mathbf w^* evolve come \mathbf e_{k+1} = (I - \eta H)\mathbf e_k. Negli autovettori di H ogni componente si moltiplica a ogni passo per 1 - \eta\lambda_i, e tende a zero se e solo se |1 - \eta\lambda_i| \lt  1, cioè 0 \lt  \eta \lt  2/\lambda_i. La condizione più stringente è quella dell’autovalore più grande, L. ∎

La velocità dipende dal numero di condizionamento L/\mu: con il passo migliore il fattore di riduzione per passo è \frac{L - \mu}{L + \mu}, vicino a 1 quando gli autovalori sono molto diversi. È il motivo per cui si normalizzano gli ingressi (stessa scala per tutte le variabili) e per cui esistono gli ottimizzatori della lezione 4.

Esempio numerico. Con 50 punti y = 2 + 3x + rumore, le equazioni normali danno \mathbf w^* = (1{,}927;\ 3{,}157). Per questi dati 2/L = 0{,}771: la discesa del gradiente con \eta = 0{,}1, 0{,}5 e 0{,}694 converge allo stesso risultato (con \eta = 0{,}5 servono 213 passi per un errore di 10^{-6}), mentre con \eta = 0{,}848, appena oltre il limite, diverge fino a valori di 10^{158}.

La classificazione: regressione logistica

Per prevedere una classe (sì o no, guasto o non guasto) si usa la sigmoide \sigma(z) = \frac{1}{1 + e^{-z}}, che trasforma un numero qualunque in una probabilità: P(y = 1 \mid \mathbf x) = \sigma(\mathbf w^T\mathbf x).

Teorema. Massimizzare la verosimiglianza dei dati equivale a minimizzare l’entropia incrociata J(\mathbf w) = -\frac{1}{n}\sum_i\big[y_i\log p_i + (1 - y_i)\log(1 - p_i)\big], con p_i = \sigma(\mathbf w^T\mathbf x_i), e il gradiente è \nabla J = \frac{1}{n}\sum_i(p_i - y_i)\,\mathbf x_i.

Dimostrazione. La probabilità di osservare y_i è p_i^{y_i}(1 - p_i)^{1 - y_i}; per esempi indipendenti la verosimiglianza è il prodotto, e il suo logaritmo cambiato di segno e diviso per n è J. Per il gradiente si usa \sigma'(z) = \sigma(z)(1 - \sigma(z)): \frac{\partial}{\partial z}\big[-y\log\sigma - (1 - y)\log(1 - \sigma)\big] = -y(1 - \sigma) + (1 - y)\sigma = \sigma - y. ∎

Il gradiente ha la stessa forma della regressione lineare: (previsione − verità) × ingresso. Con più classi la sigmoide diventa la softmax p_k = e^{z_k}/\sum_je^{z_j} e il gradiente rispetto a z_k è ancora p_k - y_k: è l’ultimo strato di quasi tutte le reti di classificazione.

Il sovradattamento

Un modello troppo flessibile impara anche il rumore dei dati. Con 15 punti di \sin 2\pi x più rumore, si adattano polinomi di grado crescente e si misura l’errore quadratico medio sui dati di addestramento e su 200 punti nuovi:

gradoerrore di addestramentoerrore su dati nuovi
10,2520,259
30,0130,095
90,0070,808
140,00250,2
Polinomi di grado 1, 3 e 14 adattati a 15 punti
La retta, grigia, è troppo rigida; il polinomio di grado 3, blu, segue bene l’andamento; quello di grado 14, rosso, passa quasi per tutti i punti ma oscilla violentemente tra uno e l’altro. In verde il polinomio di grado 14 con la regolarizzazione.

L’errore di addestramento scende sempre, quello sui dati nuovi prima scende poi esplode. Il grado 1 sottoadatta (troppo semplice: distorsione alta), il grado 14 sovradatta (troppo flessibile: varianza alta). È lo scambio tra distorsione e varianza: l’errore atteso su un punto nuovo si scompone in distorsione al quadrato, più varianza del modello al variare dei dati, più rumore irriducibile.

La regolarizzazione penalizza i pesi grandi: J(\mathbf w) + \lambda\|\mathbf w\|^2 (ridge, o weight decay nelle reti), con soluzione (X^TX + \lambda I)^{-1}X^T\mathbf y (stessa dimostrazione delle equazioni normali). Sul polinomio di grado 14 con \lambda = 10^{-3} l’errore sui dati nuovi scende da 50,2 a 0,069, meglio anche del grado 3. Il valore di \lambda si sceglie guardando l’insieme di validazione, mai quello di prova.

Il codice

import numpy as np

rng = np.random.default_rng(0)
x = rng.uniform(0, 1, 50)
y = 2 + 3 * x + rng.normal(0, 0.3, 50)
X = np.c_[np.ones(50), x]                                   # colonna di 1 per il termine noto

w_esatto = np.linalg.solve(X.T @ X, X.T @ y)                # equazioni normali
L = 2 * np.linalg.eigvalsh(X.T @ X / 50).max()              # autovalore massimo dell'hessiana
print("equazioni normali:", np.round(w_esatto, 4), " limite del passo 2/L =", round(2 / L, 3))
for eta in (0.5, 0.9 * 2 / L, 1.1 * 2 / L):
    w = np.zeros(2)
    for k in range(2000):
        w -= eta * 2 / 50 * X.T @ (X @ w - y)              # discesa del gradiente
    print(f"eta = {eta:.3f}:", np.round(w, 4) if np.all(np.isfinite(w)) else "diverge")

# sovradattamento: polinomi su 15 punti, errore su 200 punti nuovi
f = lambda t: np.sin(2 * np.pi * t)
xa = np.sort(rng.uniform(0, 1, 15)); ya = f(xa) + rng.normal(0, 0.2, 15)
xn = rng.uniform(0, 1, 200); yn = f(xn) + rng.normal(0, 0.2, 200)
def adatta(grado, lam=0.0):
    A = np.vander(xa, grado + 1, increasing=True)
    return np.linalg.solve(A.T @ A + lam * np.eye(grado + 1), A.T @ ya)     # ridge se lam > 0
def errore(c, xs, ys):
    return np.mean((np.vander(xs, len(c), increasing=True) @ c - ys) ** 2)
for grado, lam in ((1, 0), (3, 0), (9, 0), (14, 0), (14, 1e-3)):
    c = adatta(grado, lam)
    print(f"grado {grado:2d}, lambda {lam:g}: addestramento {errore(c, xa, ya):.4f}, dati nuovi {errore(c, xn, yn):.4f}")

L’esecuzione riproduce i numeri del testo. Le lezioni successive sostituiscono il modello lineare con le reti neurali, che restano addestrate con la discesa del gradiente e soffrono degli stessi problemi di sovradattamento.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
0
Would love your thoughts, please comment.x
()
x