(Come giocare con uno slinky e capire meglio la backpropagation)
Qualche giorno fa, mentre mio figlio giocava con uno slinky, ho ripensato agli articoli sulla rete neurale e nello specifico alla parte sulla backpropagation.
In quei due articoli l’obbiettivo era quello di dare un quadro generale della matematica dietro una rete neurale ed effettivamente, mentre guardavo quel giocattolo a molla scendere giù dalle scale, diventava sempre più chiaro che sull’argomento c’era ancora qualcosa in più da dire.
Negli articoli sull’origami abbiamo guardato la backpropagation da un’angolazione precisa, quella della minimizzazione dell’errore. Avevamo un giudice che misurava lo scarto, una superficie di loss da percorrere verso il basso, un gradiente che indicava la direzione e un passo, il learning rate, con cui scendere. La backpropagation era lo strumento che, risalendo i livelli a ritroso, calcolava di quanto correggere ogni parametro.
Quel punto di vista è completo e resta valido. Qui però voglio tornare sulla backpropagation da un altro punto di vista, perché c’è un aspetto solo accennato in quegli articoli e che merita di essere guardato da vicino.
Nello specifico voglio ripartire dal modo in cui anche la più piccola variazione si trasmette in avanti, attraversa la rete neurale e arriva fino all’output. È un fenomeno di propagazione, ed è il vero motivo per cui addestrare una rete somiglia a portare un sistema fisico verso il suo equilibrio.
Abbiamo già visto il concetto di equilibrio, ma in senso statistico, passando dal caffè che si raffredda andando verso l’entropia massima, alla softmax che ridistribuisce la probabilità tra le classi.
It’s all about the Entropy of Coffee
Qui parliamo di un equilibrio diverso, di natura meccanica, quello verso cui si muovono i pesi durante l’addestramento finché il sistema non si assesta.
Cosa è uno Slinky?
Giusto, per chi non lo sapesse, uno slinky è un giocattolo a molla elicoidale molto flessibile. Generazioni di bambini “di tutte le età” (tra cui il sottoscritto in questi giorni) hanno giocato più o meno inconsapevolmente con la fisica, facendolo camminare giù per le scale o trattenendo gli estremi per osservarne i vari comportamenti.
Tra i vari possibili, uno degli esperimenti che mi ha fatto sorridere più del solito, si svolge in questo modo: si trattiene lo slinky da entrambi gli estremi e si osserva come una perturbazione si propaga da un capo all’altro.
Se il gioco viene bene, sembra quasi che quella massa di spire compresse sia sempre la stessa e si muove lungo tutta la lunghezza della molla. In realtà nessuna spira si sposta davvero, ognuna si limita a oscillare attorno alla propria posizione spingendo la spira adiacente. Certo, stiamo parlando di un’onda longitudinale, però l’aspetto che trovo interessante osservare sta nel fatto che ogni spira “trasmette l’informazione” solo a quelle più prossime. Ciò nonostante l’effetto attraversa l’intera molla.
E se provassimo a guardare la rete neurale dallo stesso punto di vista?
Una rete neurale, quando viene usata, fa una cosa molto simile. Un input entra da un capo, attraversa gli strati uno dopo l’altro, e produce un’uscita all’altro capo. Ogni neurone riceve i valori dal precedente, li combina con i propri pesi e passa il risultato al successivo. L’informazione scorre sempre nello stesso verso, dall’ingresso all’uscita, come la compressione lungo lo Slinky.
Lo abbiamo già visto nel capitolo delle reti neurali, riprendiamo una rete generica con: tre livelli nascosti, un certo insieme di dati in input $\boldsymbol{x}$, il ground truth $\boldsymbol{y}$ e una funzione di attivazione $a$.
Quando l’informazione passa attraverso la rete avviene questo:
$$ \begin{array}{c} \boxed{x} \\[4pt] \downarrow \\[4pt] f_0 = A_0 \cdot \underline{x} + \beta_0 \\[4pt] \downarrow \\[4pt] h_1 = a(\underline{f_0}) \\[4pt] \downarrow \\[4pt] f_1 = A_1 \cdot \underline{h_1} + \beta_1 \\[4pt] \downarrow \\[4pt] h_2 = a(\underline{f_1}) \\[4pt] \downarrow \\[4pt] f_2 = A_2 \cdot \underline{h_2} + \beta_2 \\[4pt] \downarrow \\[4pt] h_3 = a(\underline{f_2}) \\[4pt] \downarrow \\[4pt] f_3 = A_3 \cdot \underline{h_3} + \beta_3 \\[4pt] \downarrow \\[4pt] \boxed{\hat{y}} \end{array} $$dove $A_l$ raccoglie in forma matriciale i pesi $\alpha^l_{kj}$ e l’ultimo passaggio, $f_3$ fa le veci della combinazione finale con $\phi$, vista negli articoli sulla rete neurale.
Abbiamo anche visto che se la nostra rete deve restituire una distribuzione di probabilità l’ultimo passaggio sarebbe:
$\hat{\boldsymbol{y}}=softmax(f3)$
Ma per semplicità da qui in avanti ci si riferirà alla rete senza quest’ultimo passaggio
E cosa accade quando uno Slinky si deforma in più punti? Non è raro che il nostro Slinky si ritrovi con ammaccature dovute a prolungate sessioni di “sperimentazione” (provate a lasciarlo penzolare da una certa altezza per poi mollare l’estremità rimasta in mano, una volta che la molla ha raggiunto la sua estensione massima).
Abbiamo detto che ogni spira trasmette l’informazione relativa al movimento da compiere, alla spira successiva. Ed è chiaro che se una spira non funziona, la spinta viene modificata e così quella della spira successiva fino in fondo. Detto altrimenti: una perturbazione locale si è trasformata in un cambiamento che percorre l’intera molla, attraversando tutte le spire che stanno dopo il punto danneggiato.
E la cosa interessante è che lungo il cammino, questa “increspatura” può crescere o smorzarsi, a seconda di come ogni tratto della molla la trasmette.
Questa è esattamente l’immagine che vorrei rendesse chiaro il punto che avevo solo sfiorato nei precedenti articoli.
Ogni peso moltiplica l’attivazione che arriva da un’unità e passa il risultato all’unità successiva. L’effetto di una piccola variazione di quel peso viene perciò amplificato o attenuato dall’attivazione che incontra, e si propaga come un’increspatura attraverso tutta la rete.
Guardare indietro seguendo le spire
Ogni peso ha quindi una parte di responsabilità nello scarto finale, e quella responsabilità è precisamente l’effetto che la sua piccola variazione produce alla fine, dopo aver attraversato tutta la catena.
Misurare questa responsabilità significa rispondere a una domanda: se cambiassi di pochissimo questo parametro, di quanto cambierebbe l’errore? È la sensibilità dell’errore rispetto al parametro, che scriviamo come la derivata della loss $\mathcal{L}$ rispetto ad esso.
$$ \frac{\partial \mathcal{L}}{\partial \theta} $$Più questo valore è grande, più quel parametro è responsabile dell’errore, e più conviene intervenire. Correggere la rete vuol dire spostare ogni parametro in proporzione a questa responsabilità, nel verso che riduce l’errore.
Tornando allo Slinky, è il lavoro di chi deve rimettere a posto le molle che si sono deformate. Non ha senso guardare le spire una per una sperando di indovinare a vista quale non va. Conviene piuttosto partire da ciò che si osserva partendo dal fondo e da lì risalire per capire quanto ciascuna spira ha effettivamente contribuito.
Qui si capisce anche il senso del nome backpropagation. Il fenomeno, l’increspatura, va in avanti, dal peso verso l’output. Ma il calcolo della responsabilità conviene farlo nel verso opposto. Anche qui conviene partire dallo scarto finale e risalire fino alla prima operazione, perché così le quantità già calcolate per i livelli vicini all’output si riutilizzano per quelli più profondi, invece di rifare ogni volta tutto il percorso.
Per capire come l’errore dipende da una pre-attivazione profonda si risale la catena, e ogni passo riusa il lavoro del passo più vicino alla loss.
$$ \begin{aligned} \frac{\partial \mathcal{L}}{\partial f_2} &= \frac{\partial h_3}{\partial f_2}\, \underbrace{\frac{\partial f_3}{\partial h_3}\,\frac{\partial \mathcal{L}}{\partial f_3}}_{\text{dalla loss}} \\[10pt] \frac{\partial \mathcal{L}}{\partial f_1} &= \frac{\partial h_2}{\partial f_1}\,\frac{\partial f_2}{\partial h_2}\, \underbrace{\left(\frac{\partial h_3}{\partial f_2}\,\frac{\partial f_3}{\partial h_3}\,\frac{\partial \mathcal{L}}{\partial f_3}\right)}_{\text{da } f_2} \\[10pt] \frac{\partial \mathcal{L}}{\partial f_0} &= \frac{\partial h_1}{\partial f_0}\,\frac{\partial f_1}{\partial h_1}\, \underbrace{\frac{\partial h_2}{\partial f_1}\,\frac{\partial f_2}{\partial h_2}}_{\text{da } f_1}\, \underbrace{\frac{\partial h_3}{\partial f_2}\,\frac{\partial f_3}{\partial h_3}\,\frac{\partial \mathcal{L}}{\partial f_3}}_{\text{da } f_2} \end{aligned} $$Se diamo un nome alla quantità che si accumula, chiamando $\delta_k = \dfrac{\partial \mathcal{L}}{\partial f_k}$ la sensibilità della loss rispetto alla pre-attivazione di livello $k$, il riuso diventa esplicito, perché ogni $\delta$ si scrive a partire dal precedente.
$$ \begin{aligned} \delta_2 &= \frac{\partial h_3}{\partial f_2}\,\frac{\partial f_3}{\partial h_3}\,\delta_3 \\[8pt] \delta_1 &= \frac{\partial h_2}{\partial f_1}\,\frac{\partial f_2}{\partial h_2}\,\delta_2 \\[8pt] \delta_0 &= \frac{\partial h_1}{\partial f_0}\,\frac{\partial f_1}{\partial h_1}\,\delta_1 \end{aligned} $$Qui si vede a colpo d’occhio che $\delta_2$ entra dentro $\delta_1$, che a sua volta entra dentro $\delta_0$, e che ogni passo riusa per intero la quantità già calcolata un livello più vicino alla loss.
Si capisce anche perché l’increspatura di cui parlavamo può smorzarsi o crescere lungo il cammino. Ogni passo della catena moltiplica per un fattore, ed è un prodotto di tante quantità messe in fila. Un prodotto del genere collassa verso zero quando i fattori restano mediamente sotto l’unità, e diverge quando la superano. Nel primo caso la correzione non arriva fino ai livelli più profondi, che restano quasi fermi. Nel secondo l’addestramento diventa instabile. È esattamente il tratto di molla che assorbe l’onda fino a spegnerla, oppure la rilancia finché non si gonfia. Questo fenomeno ha un nome, vanishing ed exploding gradient.
Più la pre-attivazione è profonda, più lunga è la catena, ma il percorso verso la loss è sempre lo stesso e si calcola una volta sola. È questo il senso del “guardare all’indietro”.
Vista così, la backpropagation non è solo una tecnica per scendere lungo una curva. È il procedimento con cui un sistema cerca la propria configurazione di equilibrio.
E sia chiaro, un sistema che ha raggiunto un equilibrio non è di per sé un sistema che produce risultati corretti o funziona correttamente. Non confondiamo le cose.
Lo vedremo quando parleremo di Large Language Model e “allucinazioni”. Se per noi ha senso parlare di “corretto” o “sbagliato”, non lo è da un punto di vista di un modello che fa emergere ciò che i suoi stati interni propagano.
Storicamente la backpropagation è nata molto prima di arrivare alle reti neurali. La sua genealogia passa per la teoria del controllo ottimo degli anni Cinquanta e Sessanta, il metodo aggiunto di Pontryagin, ecc.
Solo molto dopo è arrivata la riscoperta in chiave connessionista di Rumelhart, Hinton e Williams nel 1986 con l’articolo scientifico “Learning representations by back-propagating errors”.
La matematica dell’equilibrio (paragrafo bonus)
Quando la riparazione è finita, lo Slinky si trova di nuovo in quella che possiamo ritenere, dopo varie prove, come una “configurazione stabile”. Possiamo considerare questa configurazione come lo stato in cui le tensioni interne si bilanciano e il sistema raggiunge l’equilibrio.
In fisica un equilibrio di questo tipo si descrive come lo stato che rende minima l’energia del sistema. La condizione si scrive dicendo che la variazione dell’energia, per una piccola perturbazione, è nulla.
$$ \delta E(u) = 0 $$Dove $u$ è lo spostamento o la deformazione, ed $E$ è il funzionale di energia. È lo stesso linguaggio con cui si descrivono moltissimi problemi di equilibrio, dall’elasticità alle membrane ai fluidi stazionari, e da questa condizione si ricavano le equazioni che caratterizzano lo stato di equilibrio, le cosiddette equazioni ellittiche.
In matematica esiste una disuguaglianza, chiamata disuguaglianza di Caccioppoli che dice una cosa molto intuitiva su un sistema in equilibrio e che si lega proprio all’increspatura di cui abbiamo parlato. In uno Slinky a riposo, la deformazione interna non può crescere a piacere senza che lo stato complessivo del sistema lo riveli. Quanto le cose sono deformate localmente è tenuto sotto controllo da quanto lo è la configurazione nel suo insieme. Un sistema in equilibrio, in altre parole, non può nascondere tensioni interne.
la disuguaglianza di Caccioppoli, in breve. Per le soluzioni delle equazioni di equilibrio (le PDE ellittiche), la disuguaglianza controlla l’energia della deformazione su una regione interna a partire dai valori della funzione su una regione un po’ più ampia. In forma semplificata, su una sfera di raggio $R$, si scrive $\int |\nabla u|^2 \leq \frac{C}{R^2} \int |u|^2$, con l’integrale a sinistra sulla regione interna e quello a destra su una più ampia. È nota anche come disuguaglianza di Poincaré inversa, perché va nel verso opposto a quello consueto. Di norma si controlla una funzione a partire dalle sue derivate, qui invece si controllano le derivate, cioè la deformazione, a partire dalla sola funzione, cioè lo stato. Questa disuguaglianza caratterizza lo stato di equilibrio, cioè la destinazione verso cui il sistema si assesta, non il procedimento con cui ci si arriva. Parla di come è fatto l’equilibrio, non di come lo si raggiunge
Naturalmente la disuguaglianza di Caccioppoli vive nel campo delle equazioni ellittiche e descrive le soluzioni di equilibrio, cioè lo stato finale di un sistema, non il procedimento con cui una rete viene addestrata.
A margine, esiste un filone recente che usa le reti neurali per risolvere proprio le disuguaglianze variazionali, i problemi di equilibrio vincolato che generalizzano la condizione $\delta E(u)=0 $. È il punto in cui le due metà di questo articolo si toccano, anche se per altra via. Lì la rete è lo strumento che approssima la soluzione di equilibrio, mentre l’equilibrio resta la destinazione, non il procedimento.
Per chi voglia farsi un’idea, un esempio è: Zhao e Zheng, Neural Network Convergence for Variational Inequalities (preprint, 2025).
Ho voluto scrivere questa aggiunta perché il concetto di equilibrio mi ha ricordato la figura appunto di Renato Caccioppoli. Oltre ad essere stato uno dei più importanti matematici italiani del Novecento è stato una di quelle figure intellettuali che mi ha insegnato a tracciare un intorno sempre più largo attorno ai miei pensieri.
Magari è un’aggiunta superfula, magari è uno spunto, magari è solo strano e allora tanto basta.