(Come piegare la carta mentre si scende da una montagna e nel frattempo capire come addestrare le reti neurali)
Ho già detto di non essere particolarmente bravo con gli origami e quelle volte che mi cimento, di sicuro la prima versione non è mai quella buona.
Abbiamo visto come gli origami si formano attraverso diversi passaggi in cui ogni livello dipende dal precedente, quindi arrivati all’ultima piega cosa si fa? Si sceglie un metodo in grado di quantificare l’errore che commettiamo ogni volta che terminiamo il nostro processo di sintesi.
Io in genere uso come metodo quello di presentare il risultato a mio figlio.
E allora ecco che:
-
la prima versione sembra un Gremlin.

-
la seconda sembra un cane

-
la terza forse sembra un orsacchiotto

-
al quarto tentativo finalmente ci siamo

Proviamo a scrivere una ricetta per l’origami perfetto:
-
studio cosa rappresentare passando dalla forma generica ai dettagli cercando di capire come sintetizzare il tutto
-
le pieghe della carta, il punto in cui inserirle, sono i parametri del mio processo. Variando posizione posso creare una serie di passaggi progressivi in cui ogni livello è influenzato dal precedente.
-
eseguo una serie di tentativi in cui:
- cerco di quantificare l’errore che determina la distanza con il risultato atteso
- cerco di capire come minimizzare l’errore durante tutti i passaggi analizzando come questo errore si propaga tra le pieghe
- ripeto fino a raggiungere un’approssimazione accettabile con errore trascurabile.
Natura non facit saltus (la natura non fa salti)
Con questa espressione il matematico Leibniz richiamava l’idea che i cambiamenti avvengano attraverso una successione di passaggi continui. Tra uno stato e un altro non esiste necessariamente una frattura netta, ma una serie di trasformazioni progressive.
Questa apparirà a molti come una citazione puramente filosofica, ma ha avuto un’influenza profonda sul modo in cui la matematica descrive il cambiamento e rappresenta ancora oggi uno dei presupposti concettuali della scienza moderna. Ma a parte questa precisazione, trovo sia anche un ottimo punto di vista da cui cominciare ad osservare il nostro problema di minimizzazione dell’errore.
Non è solo una questione numerica, nella prima parte di questo articolo abbiamo messo insieme una composizione di funzioni il cui fine è modellare una serie di “cambiamenti” finalizzati a raggiungere una forma ideale.
Ma come possiamo descrivere questo cambiamento e quantificarlo?
Quantificare lo scarto
Se nel confrontare il nostro origami la misura resta un fattore visivo, per la curva introdotta nella prima parte dovremo trovare uno strumento decisamente più affidabile.
Ragioniamoci passo passo.
-
Abbiamo un modello $\hat{y} = f(x, \theta)$ dove $\theta$ rappresenta l’insieme di tutti i parametri:
$$\theta = \left\{\alpha_{kj}^{l},\ \beta_k^{l},\ \phi_0,\ \phi_k\right\}$$ -
Partiamo confrontando punto per punto i risultati previsti dal modello $\hat{y}_i$ con i valori reali del dataset $y_i$.
Si tratta di un’operazione molto semplice, basta calcolare la differenza tra i due valori:
$$ e_i = (y_i - \hat{y}_i)^2 $$Qui l’elevazione al quadrato ha due compiti: pesare di più gli errori grandi e ignorare il segno rendendo il risultato sempre positivo.
-
Quindi adesso abbiamo un insieme di valori composto da $N$ scarti in base a quanto è grande il nostro dataset.
$$ \boldsymbol{e} = \{ (y_1 - \hat{y}_1)^2, (y_2 - \hat{y}_2)^2, \ldots, (y_i - \hat{y}_i)^2 \} $$ -
Ci serve un numero che riassuma quanto stiamo sbagliando e quale modo migliore per ottenerlo della media degli scarti. Quindi sommiamoli tutti e dividiamoli per la loro quantità.
$$ L(\boldsymbol{\theta}) = \frac{1}{N} \sum_{i=1}^{N} \left( y_i - \hat y_i \right)^2 $$Chiameremo loss function $L(\boldsymbol{\theta})$, quella famiglia di funzioni in grado di quantificare l’errore tra i valori previsti e i valori reali (detti ground truth).
Cambiare punto di vista
Abbiamo dato un’idea di distanza per l’origami, adesso rappresentiamo l’errore così come lo abbiamo appena formalizzato per la curva.
Ora, fermiamoci un attimo e poniamoci una domanda: se portassimo tutti i punti dell’origami sull’immagine dell’orsacchiotto, sarebbe utile al fine di quantificare l’errore?
La risposta è no.
Finora è stata una questione sempre accennata ma che la funzione di loss fa emergere in modo netto. Se voglio raggiungere il minimo errore possibile devo cambiare punto di vista, devo capire cosa avviene all’interno delle pieghe.
È un ribaltamento dei ruoli a cui non siamo abituati a scuola: ciò che era variabile (la $x$) diventa costante, e ciò che sembrava un coefficiente fisso (i parametri $\alpha, \beta, \dots$) diventa la variabile. Con esso cambia anche lo spazio in cui guardiamo: non più lo spazio dei dati, ma lo spazio dei parametri.
Tralasciamo per un momento la nostra curva, vale la pena fissare questo concetto guardando cosa succede con un solo parametro $\alpha$ e una retta generica $y = x \cdot \alpha$.
Guardando le immagini, a sinistra vediamo lo spazio generato dai dati mentre a destra lo spazio generato dal parametro $\alpha$ e dalla loss function $L(\theta)$. Dove $\boldsymbol{\theta} = \{\alpha\}$
Proviamo partendo da un valore di $\alpha$ preso a caso, ma in un range di valori coerente con i miei dati.
È chiaro che il valore scelto è piuttosto basso, la linea di predizione è molto al di sotto dei punti target.
Adesso il coefficiente angolare sembra più grande del necessario. Il livello di predizione è migliorato, l’errore è diminuito, ma decisamente non ci siamo.
Quest’ultimo tentativo possiamo dire sia andato a buon fine, abbiamo trovato il valore di $\alpha$ che produce un valore di loss sufficiente.
Mettiamo in ordine quanto visto:
- trovare i parametri giusti significa trovare l’insieme di valori $\theta$ che minimizza $L(\theta)$.
- più i risultati della loss function tendono a zero più il modello si avvicina a generalizzare la forma dei dati target.
- la loss function ha una geometria sua dove è possibile vedere come la ricerca del minimo porta sostanzialmente a percorrerla verso la parte più bassa (minimo assoluto).
Bene, adesso però riguardiamo un attimo cosa abbiamo fatto per l’origami, se pur estremamente empirico, un metodo lo abbiamo trovato. Avevamo un giudice ed è stato possibile quantificare i nostri errori perché tra le orecchie di un Gremlin e quelle di un Orso una qualche “distanza” riusciamo a comprenderla e ridurla. Con la curva il giudice è la funzione di loss, e fin qui l’ho interrogata a occhio: questo $\alpha$ è troppo basso, quest’altro troppo ripido, quest’ultimo ci siamo quasi.
Continuiamo a ragionare sul solo parametro $\alpha$, è più facile da realizzare e il salto verso un insieme $\boldsymbol{\theta}$ più ricco risulterà più agevole.
Se ricordate quanto detto nella prima parte, agire sul parametro $\alpha$ vuol dire sostanzialmente muovere la retta per un estremo in modo da ruotarla quel tanto che basta ad avere una buona approssimazione. Ribaltiamo anche qui il punto di vista:
- da che parte mi devo muovere lungo la traiettoria tracciata dalla loss function?
- di quanto?
Avvicinarsi finché il salto sparisce
Se non voglio fare salti, allora non salto verso il minimo, faccio un passo infinitamente piccolo.
Chiamiamo $\mathrm{d}\alpha$ questo passo talmente minuscolo da essere più piccolo di qualunque numero positivo immaginabile. Ma attenzione, non è zero. Ricordo che siamo nello spazio dei parametri, quindi al variare di $\alpha$ risponderà la loss con uno spostamento altrettanto minuscolo che chiameremo $\mathrm{d} L$.
Se mettessimo una lente abbastanza potente sulla curva proprio in quel punto, vedremmo la curva diventare una retta e potremmo osservarne la pendenza notando che non è altro che il rapporto tra le due variazioni: $\frac{\mathrm{d} L}{\mathrm{d} \alpha}$
Stiamo parlando di un’operazione che in matematica ha un nome ben preciso: la derivata.
H. Jerome Keisler, nel suo libro Elementary Calculus: An Infinitesimal Approach (1976), costruisce tutto il calcolo proprio sugli infinitesimi, attraverso un meraviglioso approccio non-standard. Immaginate di poggiare sulla curva $L(\alpha)$, una lente a ingrandimento infinito. Questo è l’espediente didattico con cui Keisler rende visibili numeri infinitamente vicini tra loro.
Più ingrandisci, più la curvatura sparisce. A ingrandimento infinito quell’arco di curva diventa indistinguibile da una retta.
La derivata è semplicemente la pendenza di quella retta locale. Nient’altro. La curva era spaventosa nel suo insieme (fatta di tutte le pieghe, di tutti i parametri) ma vista abbastanza da vicino, attorno a un punto, è solo una linea con una sua inclinazione.
A questo punto possiamo scriverla. Prendiamo la posizione della loss $L(\alpha)$ (per un certo $\alpha$) e quello dopo un passo infinitesimo, $L(\alpha + d\alpha)$. Il loro scarto, diviso per il passo, è proprio la pendenza della retta che la lente di ingrandimento ci ha mostrato.
$$ \frac{\mathrm{d} L}{\mathrm{d} \alpha} = \frac{L(\alpha + \mathrm{d} \alpha) - L(\alpha)}{\mathrm{d} \alpha} $$E il risultato è un numero solo, esatto, e ci dice tutto ciò che ci serviva:
- il suo segno indica da che parte sale la curva. Se la pendenza è positiva a destra si sale; per scendere si va a sinistra. E viceversa. Per minimizzare ci si muove sempre nel verso opposto al segno.
- il suo valore assoluto indica quanto è ripido il pendio. Tanto più grande, tanto più siamo lontani dal fondo e tanto più deciso può essere il passo. Vicino al minimo si appiattisce verso zero e i passi si fanno piccoli da soli.
È esattamente la logica con cui abbiamo affrontato le pieghe dell’origami. Ad ogni tentativo si guarda la direzione da cui ci si allontana dalla forma giusta e si “quantifica” la correzione. La derivata ci dà la stessa indicazione, ma scritta in un numero.
Un gradiente di possibilità
Con il solo parametro $\alpha$ lo spazio dei parametri è piccolo e insufficiente. Però prima di espandere l’insieme $\theta$ verso la rappresentazione della rete neurale vista nella prima parte, vorrei mettere in chiaro una cosa: abbiamo compreso quali sono gli strumenti adatti e come usarli.
Facciamo un passo alla volta, passiamo da un parametro a due parametri: $\boldsymbol{\theta}= \{\alpha, \beta\}$. Lo spazio si espande e comincia a diventare più complicato avere una strategia di visualizzazione efficace. Con un solo parametro la derivata è solo “di qua o di là”, ma con due la loss è una superficie.
Proviamo a visualizzare questa superficie come curve di livello, in questo caso rappresentate come una serie di anelli concentrici. Più si va verso l’esterno e più la loss è alta, andando verso il centro puntiamo a valori di minimizzazione utili.
Con un solo parametro abbiamo una derivata. Ma il nostro $\theta$ nella realtà ha un sacco di parametri:
$$ \theta = \{\alpha_1, \beta_1, \ldots, \alpha_n, \beta_n, \phi_0, \phi_1, \ldots, \phi_n \} $$di conseguenza avremo una derivata per ciascuno di essi.
Quell’insieme di pendenze, una per direzione, ha un nome: si chiama gradiente, ed è la bussola con cui orientarsi nello spazio dei parametri.
Aumentando il nostro spazio anche al parametro dell’intercetta $\beta$, possiamo quindi scrivere:
$$ \nabla L(\theta) = \left( \frac{\partial L}{\partial \alpha}, \frac{\partial L}{\partial \beta} \right) $$dove la $\mathrm{d}$ diventa $\partial$ e le singole derivate vengono chiamate derivate parziali.
Abbiamo detto che l’obiettivo è minimizzare la loss e quindi bisogna “Percorrere la geometria della loss verso il basso”. E se pensiamo che il gradiente punta verso la salita, la soluzione diventa allora un’operazione precisa: la discesa del gradiente $-\nabla$.
Prima di andare avanti però vorrei fosse ben compresa la differenza tra derivata e gradiente e dove siamo arrivati. Ho introdotto quest’ultimo termine quasi riducendolo ad una semplice variazione linguistica, perciò vale la pena di puntualizzare.
Se cammino su una corda tesa, posso muovermi soltanto in due direzioni: avanti o indietro. In ogni punto della corda, la derivata indica la sua pendenza esatta in quel punto. In altre parole, la derivata misura quanto rapidamente l’altezza cambia mentre avanzo lungo la corda: se è positiva sto salendo, se è negativa sto scendendo, e quanto più il suo valore è grande in valore assoluto, tanto più la salita o la discesa è ripida.
Se invece mi trovo sul fianco di una collina, posso muovermi in più direzioni. Per capire la forma del terreno, posso misurare la pendenza lungo due direzioni fondamentali, ad esempio est-ovest e nord-sud. Queste pendenze sono le derivate parziali. Mettendo insieme le derivate parziali ottengo il gradiente, un vettore che indica la direzione di massima salita. Se voglio raggiungere il punto più basso della valle, invece di seguire il gradiente mi muovo nella direzione opposta: questo è il principio della discesa del gradiente.
Adesso possiamo presentare le due formule relative ad $\alpha$ e $\beta$:
-
Rispetto al coefficiente angolare $\alpha$:
$$ \frac{\partial L}{\partial \alpha} = \frac{2}{N} \sum_{i=1}^{N} x_i (\hat y_i - y_i) $$ -
Rispetto all’intercetta $\beta$:
$$ \frac{\partial L}{\partial \beta} = \frac{2}{N} \sum_{i=1}^{N} (\hat y_i - y_i) $$
Come si può vedere sono formule estremamente semplici.
In salita i polmoni, in discesa gli occhi
Non ricordo l’origine del detto, me lo ripeteva spesso un amico durante le passeggiate in montagna quando acceleravo il passo in discesa. Certo, sarebbe stato meglio aggiungere al detto anche: “in cima non portare il vino”. Ma il punto è un altro.
Quel detto, nella sua semplicità, descrive una cosa molto concreta: in salita il corpo costringe a seguire il respiro, mentre in discesa è la vista a dettare il passo, perché il pericolo non è la fatica, ma l’errore di appoggio.
Da tutt’altra parte, negli anni ’50 e ’60, si è incappati in una questione simile per i problemi di ricerca del minimo. I primi approcci tendevano a muoversi in modo rigido o “a salto” verso una soluzione stimata. Di base fallivano tutti per due motivi principali:
- Oscillazioni e instabilità (overshooting) Senza un controllo sull’ampiezza degli aggiornamenti, le correzioni possono diventare troppo aggressive e far “rimbalzare” la discesa del gradiente oltre la soluzione ottimale.
- Superfici di errore complesse Nei problemi reali la funzione di errore non è semplice né regolare. Presenta molte variazioni e minimi locali.
Una soluzione è stata trovata aggiungendo un nuovo parametro: il learning rate. Per ridurre la loss ci muoviamo nel verso opposto al gradiente, di un passo proporzionale al learning rate $\eta$. Un passo solo non basta: si ripete. E i passi sono piccoli e continui.
Di nuovo Leibniz: non si salta al minimo, ci si scende per trasformazioni progressive. Il cammino zigzaga verso il fondo scavalcando ad ogni passo e correggendosi.
$$ \hat{\theta} = \theta - \eta \cdot \nabla L(\theta)$$dove $\hat{\theta}$ è il nuovo parametro aggiornato e $\eta$ (learning rate) è la lunghezza del passo.
Fai l’origami, disfai l’origami, rifai l’origami, disfai l’origami….
Se pensiamo alla rete neurale disegnata nella prima parte (contiamo 26 parametri) diventa difficile visualizzare una superficie da percorrere, anche per chi è abituato a “pensare quadridimensionalmente” .
Ma non è necessario farlo. Sappiamo che ad ogni parere negativo devo disfare l’origami per poi rifarlo, finché qualcuno non ci dice: “sì, sembra un orsacchiotto coccoloso”. Abbiamo già tutto: la foto dell’orsacchiotto come riferimento da raggiungere, le pieghe come processo di trasformazione, mio figlio come strumento per misurare la distanza da quel riferimento, e il disfare per capire quali pieghe hanno causato l’errore prima di ricominciare.
Nel caso della rete, la foto dell’orsacchiotto è il dataset: una collezione di esempi in cui ogni input è associato al suo valore reale atteso, la ground truth. Le pieghe corrispondono alla forward propagation, la sequenza di calcoli che attraversa i livelli della parte 1. Mio figlio è la loss function, che misura la distanza tra la previsione del modello e la ground truth. Il disfare per capire dove correggere è la backpropagation: l’algoritmo che risale i livelli a ritroso per identificare i parametri responsabili dell’errore e calcolare di quanto correggerli.
Cerchiamo di visualizzare come verrebbero gestite le derivate in una rete con tre livelli:
-
Forward Pass
$$ \begin{align*} f_0 &= \beta_0 + x \cdot \alpha_0 \\ h_1 &= a(f_0) \\ \\ f_1 &= \beta_1 + h_1 \cdot \alpha_1 \\ h_2 &= a(f_1) \\ \\ f_2 &= \beta_2 + h_2 \cdot \alpha_2 \\ h_3 &= a(f_2) \\ \\ \hat{y} &= \phi_0 + \phi_1 \cdot h_3 \ (\text{output}) \\ \mathcal{L}_i& = (\hat{y} - y_i)^2 \ (\text{loss function}) \end{align*} $$quindi alla fine avremo la composizione della loss come una serie di composizione di funzioni:
$$ \mathcal{L}_i = \left\{[ \phi_0 + \phi_1 \cdot a(\beta_2 + a(\beta_1 + a(\beta_0 + x \cdot \alpha_0) \cdot \alpha_1) \cdot \alpha_2) ] - y_i \right\}^2 $$ -
Backward Pass
Segue la derivata di $\mathcal{L}_i$ rispetto ad ogni variabile intermedia e in ordine inverso
$$ \begin{gathered} \boxed{\longleftarrow} \\[8pt] \frac{\partial \mathcal{L}_i}{\partial f_0} \underset{\frac{\partial h_1}{\partial f_0}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial h_1} \underset{\frac{\partial f_1}{\partial h_1}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial f_1} \underset{\frac{\partial h_2}{\partial f_1}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial h_2} \underset{\frac{\partial f_2}{\partial h_2}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial f_2} \underset{\frac{\partial h_3}{\partial f_2}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial h_3} \underset{\frac{\partial \hat{y}}{\partial h_3}}{\longleftarrow} \frac{\partial \mathcal{L}_i}{\partial \hat{y}} \longleftarrow \mathcal{L}_i \end{gathered} $$La connessione tra tutte queste derivate segue la regola della catena. Se $\mathcal{L}$ dipende da $h_1$ e $h_1$ dipende da $f_0$, la derivata rispetto a $f_0$ si ottiene moltiplicando le derivate lungo il percorso.
$$\frac{\partial \mathcal{L}}{\partial f_0} = \frac{\partial \mathcal{L}}{\partial h_1} \cdot \frac{\partial h_1}{\partial f_0}$$Ogni freccia nel diagramma è una di queste derivate locali. Percorrendo la catena da destra verso sinistra e moltiplicando, si risale fino alla derivata rispetto ai parametri.
E adesso mettiamo insieme tutte le formule generalizzando un po'.
-
Input
- $I$: vettore di input con $n$ features, nella rete non è altro che il livello 0 di input: $h^{0} = I$
- $O$: output della rete, ovvero $\hat{y}$
-
Forward Pass
-
passo 1
Pre-attivazione (livello $l$, unità $k$, per $l = 1, \ldots, L$)
$$ f_k^{l} = \beta_k^{l} + \sum_{j=1}^{n_{l-1}} \alpha_{kj}^{l} \cdot h_j^{l-1} $$con $h_j^{0} = x_j \in I$ al primo livello.
-
passo 2
Attivazione (livelli $l = 1, \ldots, L$)
$$ h_k^{l} = a\left(f_k^{l}\right) $$Il vettore $h^l = (h_1^l, \ldots, h_{n_l}^l)$ diventa l’input del livello successivo. Si ripetono i passi 1 e 2 fino al livello $L$.
-
passo 3
Output (solo al livello finale)
$$ \hat{y} = \phi_0 + \sum_{k=1}^{n_L} \phi_k \cdot h_k^{L} $$I pesi $\phi$ compaiono solo qui.
-
passo 4
Loss (per il campione $i$)
$$ \mathcal{L}_i = \left(\hat{y} - y_i\right)^2 $$
-
-
Backward Pass
-
passo 1
Segnale di errore all’output (livello $L$)
Definiamo $\delta_k^{l} = \frac{\partial \mathcal{L}}{\partial f_k^{l}}$: la derivata della loss rispetto alla pre-attivazione dell’unità $k$ al livello $l$, corrispondente ai termini $\frac{\partial \mathcal{L}_i}{\partial f_l}$ dell’esempio precedente.
$$ \delta_k^{L} = \frac{\partial \mathcal{L}}{\partial \hat{y}} \cdot \phi_k \cdot a'\left(f_k^{L}\right) $$ -
passo 2
Propagazione a ritroso (da $L-1$ verso 1)
$$ \delta_k^{l} = \left(\sum_{m=1}^{n_{l+1}} \alpha_{mk}^{l+1} \cdot \delta_m^{l+1}\right) \cdot a'\left(f_k^{l}\right) $$ -
passo 3
Gradienti dei parametri
$$ \begin{align*} \frac{\partial \mathcal{L}}{\partial \alpha_{kj}^{l}} &= \delta_k^{l} \cdot h_j^{l-1} \\[4pt] \frac{\partial \mathcal{L}}{\partial \beta_k^{l}} &= \delta_k^{l} \\[4pt] \frac{\partial \mathcal{L}}{\partial \phi_k} &= \frac{\partial \mathcal{L}}{\partial \hat{y}} \cdot h_k^{L} \\[4pt] \frac{\partial \mathcal{L}}{\partial \phi_0} &= \frac{\partial \mathcal{L}}{\partial \hat{y}} \end{align*} $$ -
passo 4
Aggiornamento dei pesi
$$ (\theta - \eta \cdot \nabla_\theta \mathcal{L}) \rightarrow \theta $$
-
C’è una puntualizzazione che, arrivati alla fine, non voglio farmi sfuggire. La parola AI viene spesso correlata al termine algoritmo e anche qui, secondo me, il vocabolario ha creato un problema.
Perciò la mia precisazione è questa:
- Il modello non è un algoritmo in sé, ma una struttura matematica parametrica, cioè una composizione di funzioni il cui comportamento dipende da un insieme di parametri appresi.
- L’algoritmo non coincide con il modello: è la procedura che permette di addestrarlo (ottimizzazione dei parametri) e di eseguirlo (computazione del forward pass).
- Il deep learning moderno non è riducibile a un singolo elemento, ma è un sistema composto da modello, algoritmo di ottimizzazione, funzione obiettivo e dati, che insieme determinano il comportamento finale.
In breve: il modello è ciò che risulta dall’addestramento sui dati, l’algoritmo è il processo che lo ottimizza e lo mette in esecuzione, e i dati sono il punto di partenza da cui tutto viene costruito.
Accettabile e Trascurabile
Nella prima parte abbiamo costruito la struttura matematica di una rete neurale: una composizione di funzioni semplici, organizzate in livelli, capaci di approssimare qualsiasi funzione continua. In questa seconda parte abbiamo visto come, all’interno di un processo iterativo, minimizzare l’errore che commette una rete neurale.
Avevamo promesso di dare forma a due concetti: accettabile e trascurabile. E spero si sia capito dov’è il punto della questione: per quanto tentiamo di minimizzare l’errore compiuto da un modello, per quanti metodi di ottimizzazione efficaci si siano trovati, non è possibile arrivare a vedere il fondo $\delta = 0$.
Lo ripeto: se sentite le parole “siamo in grado di arrivare a percentuali di accuratezza vicine al 100%”, scappate. Se non avete modo di farlo, potete rispondere: “Ma il 100% di quale percentuale?”.
In conclusione è necessario aggiungere anche una precisazione: in questo lungo articolo abbiamo trattato una “forma generica” di rete neurale, cosa è davvero e come funziona. Ma nella realtà non esiste un solo tipo di rete neurale e non esiste un solo metodo di addestramento.
Le reti neurali fanno bene una e una sola cosa, modellare una funzione in grado di rappresentare i dati così come lo farebbe un’ideale funzione matematica. Va da sé che i dati rappresentano qualcosa di unico e si portano dietro le innumerevoli condizioni a contorno che li determinano.
La rete neurale trovata per la nostra curva è una tipologia, vedremo in un prossimo articolo come per la classificazione dovremo aggiungere alla fine della nostra mega composizione di funzioni un’ultima che ne cambia l’output. Cambierà il modo di calcolare l’errore. Non cambieranno i passaggi che abbiamo visto, si tratterà sempre di scegliere, i livelli, la funzione di attivazione, scegliere la loss function più appropriata, ecc.
La tipologia di addestramento non è sempre la stessa. Abbiamo visto un approccio in cui nel dataset sono presenti gli esempi di output su cui misurare l’errore, ma ci sono altre tipologie di addestramento in cui questi dati non ci sono o il modello deve misurare l’errore in base alle “valutazioni” sul suo operato (i modelli sviluppati per le auto a guida automatica, per esempio).
E allora ecco che i concetti di accettabile e trascurabile assumono forme molto più complesse di quanto sembri.
Il problema dei tre corpi
Due corpi che si attraggono (una stella e un pianeta) hanno un’orbita che sappiamo scrivere e prevedere. Aggiungetene un terzo e tutto cambia: il sistema resta perfettamente deterministico, le leggi sono sempre quelle, nessun elemento casuale, eppure non esiste più una soluzione analitica generale che ci dica dove saranno i corpi a un tempo arbitrario. Basta una differenza minuscola nelle condizioni di partenza perché le traiettorie divergano.
Deterministico, ma imprevedibile.
È esattamente ciò che scriveva Poincaré, nel passo in cui di fatto nasce l’idea di caos deterministico:
«Può accadere che piccole differenze nelle condizioni iniziali ne generino di grandissime nei fenomeni finali. Un piccolo errore sulle prime produrrà un errore enorme sui secondi. La previsione diventa impossibile.» Scienza e metodo (1908)
Eppure sapere questo non ci ha impedito di mandare sonde, satelliti e missioni nello spazio. Anzi: è proprio perché conosciamo il limite che abbiamo imparato a conviverci. Integrando numericamente, correggendo la rotta in corsa, tenendo l’errore entro margini noti. Non eliminandolo, ma sapendo entro quali confini possiamo fidarci dei nostri calcoli: rendendolo accettabile e trascurabile.
Spesso sento chiudere il discorso sul deep learning con l’inaffidabilità dovuta ad una non so quale pretesa di determinismo. Ma una rete neurale è deterministica (l’abbiamo visto: la matematica è solida, stessi pesi e stesso input danno sempre lo stesso output), eppure resta difficile da prevedere. Come per il problema dei tre corpi, quello che salta non è il determinismo, è la prevedibilità. Sono due cose diverse.
Il problema non è l’ultimo decimale di accuratezza che ci manca, abbiamo lo schema dell’origami, vediamo l’orso che si delinea in tutta la sua coccolosità fino all’ultima istruzione. Ma resta il problema di come noi poi facciamo lavorare questo sistema formale. Perché nei modelli l’errore non vive solo nell’addestramento: vive anche nell’uso. La varianza con cui diamo gli input si trasferisce sull’output, spesso si dice: garbage in, garbage out.
Accettabile e trascurabile, allora, non sono solo proprietà del modello. Sono proprietà del modo in cui scegliamo di usarlo.
Breviario del Deep Learning
Tipologie di reti neurali
| Domanda | Tipo | Spiegazione |
|---|---|---|
| “Che valore avrà?” | Regressione | Prevede un numero continuo, ad esempio un prezzo o una temperatura. |
| “A quale categoria appartiene?” | Classificazione | Assegna un dato a una o più classi predefinite. |
| “Quali gruppi naturali esistono?” | Clustering | Raggruppa dati simili senza conoscere le categorie in anticipo. |
| “Quali dati sono anomali?” | Rilevamento anomalie | Individua comportamenti o valori fuori dalla norma. |
| “Cosa succederà in futuro?” | Forecasting | Prevede eventi o valori futuri a partire da dati storici. |
| “Cosa dovrei suggerire all’utente?” | Raccomandazione | Propone contenuti, prodotti o servizi pertinenti. |
| “Cosa contiene questa immagine?” | Computer Vision | Riconosce oggetti, persone o caratteristiche visive. |
| “Cosa significa questo testo?” | NLP | Comprende, analizza o genera linguaggio naturale. |
| “Come posso creare nuovi contenuti?” | Generazione | Produce testo, immagini, audio, video o codice. |
| “Qual è la migliore azione da compiere?” | Reinforcement Learning | Impara una strategia ottimale tramite premi e penalità. |
Tipologie di addestramento
| Domanda | Tipo | Spiegazione |
|---|---|---|
| “Ho esempi con la risposta corretta?” | Apprendimento supervisionato | Il modello apprende da dati già etichettati. |
| “Non ho risposte, posso trovare schemi?” | Apprendimento non supervisionato | Il modello scopre autonomamente strutture e relazioni. |
| “Ho poche risposte e molti dati grezzi?” | Apprendimento semi-supervisionato | Combina dati etichettati e non etichettati. |
| “Posso creare da solo gli esempi di addestramento?” | Apprendimento auto-supervisionato | Le etichette vengono ricavate automaticamente dai dati. |
| “Posso imparare per tentativi ed errori?” | Apprendimento per rinforzo | Il modello migliora ricevendo premi o penalità. |
| “Esiste già un modello da riutilizzare?” | Transfer Learning | Sfrutta conoscenze apprese da un modello precedente. |
| “Voglio specializzare un modello già addestrato?” | Fine-Tuning | Adatta un modello generico a un compito specifico. |
| “Devo imparare continuamente da nuovi dati?” | Online / Continual Learning | Aggiorna progressivamente le proprie conoscenze nel tempo. |
Parole chiave
- Funzione di perdita (loss function): misura la distanza tra le previsioni del modello e i valori reali. Minimizzarla è l’obiettivo dell’addestramento.
- Errore quadratico medio (MSE): la loss function più comune, $L = \frac{1}{N} \sum (y_i - \hat{y}_i)^2$.
- Gradient descent: algoritmo che aggiorna iterativamente i parametri nella direzione opposta al gradiente della loss.
- Learning rate $\eta$: il passo di aggiornamento nel gradient descent.
- Regola della catena: regola di derivazione per funzioni composte, fondamento matematico della backpropagation.
- Backpropagation: algoritmo che calcola i gradienti propagando il segnale di errore a ritroso attraverso i livelli.
- Forward pass: il calcolo che produce l’output della rete dato un input.
- Backward pass: il calcolo dei gradienti a partire dall’output verso i parametri.
- Iperparametri: parametri del processo di addestramento (learning rate, numero di epoche, dimensione del batch) che non vengono appresi dalla rete.
- Batch: un sottoinsieme del dataset usato per calcolare il gradiente in un singolo passo.
- Epoca: un passaggio completo attraverso l’intero dataset durante l’addestramento.