Derivate matriciali per ML
Il gradiente della loss di una rete non è un numero e nemmeno un vettore: è una matrice, della stessa forma della matrice di pesi. Questo capitolo costruisce il calcolo differenziale matriciale — come si deriva uno scalare rispetto a un vettore o a una matrice — e mostra il metodo del differenziale, l’unica procedura che produce il gradiente senza farlo indovinare. Il bersaglio finale: leggere la backpropagation di un layer lineare come una formula di tre righe, e riconoscere a colpo d’occhio l’errore di trasposta che fa saltare le shape.
Perché questo capitolo
Sezione intitolata “Perché questo capitolo”Una matrice di pesi W di un layer lineare può avere centinaia di migliaia di entrate. La funzione di errore di un modello — la loss — è uno scalare, un numero solo, che dipende contemporaneamente da tutte quelle entrate. Addestrare il modello vuol dire spostare quelle entrate per far scendere quel numero, e lo strumento per sapere in che direzione spostarle è il gradiente. La domanda “qual è il gradiente della loss rispetto a W” chiede una cosa che il calcolo a una variabile non sa fare: derivare uno scalare rispetto a una matrice. Il risultato è esso stesso una matrice, con la stessa forma di W.
Il calcolo differenziale matriciale, in inglese matrix calculus, è il linguaggio che rende sistematica questa operazione. Senza di esso ci si riduce a derivare componente per componente — l’entrata W[i][j], una alla volta — e poi a indovinare come riassemblare i risultati in una matrice, con tutte le trasposte al posto giusto. È un metodo fragile: basta una trasposta sbagliata e le forme delle matrici non combaciano più. Con poche identità fondamentali e con il metodo del differenziale, il gradiente di uno scalare rispetto a una matrice diventa una formula da leggere, non da congetturare.
C’è un’obiezione ragionevole: oggi nessuno deriva i gradienti a mano. I framework di automatic differentiation — PyTorch, JAX, TensorFlow — li calcolano da soli. Vero, e questo capitolo non chiede di tornare al calcolo manuale. Ma capire le identità serve a tre cose molto concrete. La prima è leggere i paper: la letteratura di deep learning scrive la backpropagation in forma matriciale compatta e dà per scontate queste identità, e una riga come “il gradiente è ” resta opaca se non le si conosce. La seconda è il debugging: l’errore più frequente in un training loop scritto a mano è una trasposta mancante, e lo si riconosce in pochi secondi solo se si sa quale forma deve avere il gradiente. La terza è scrivere layer o loss su misura, dove l’autograd non arriva da solo e il passo all’indietro va derivato a mano.
Contesto
Sezione intitolata “Contesto”Questo capitolo è l’ultimo gradino di una scala salita nei capitoli precedenti di questa Parte. Il capitolo Analisi matematica: limiti, continuità, derivate ha costruito la derivata per funzioni con un ingresso e un’uscita, una funzione da numeri a numeri. Il capitolo Derivate parziali, gradienti, Jacobiani ha esteso la derivata a funzioni multivariabili: le derivate parziali per più ingressi, il gradiente che le raccoglie, la matrice Jacobiana per quando anche l’uscita è multipla. Il calcolo differenziale matriciale è il passo successivo, ed è un cambio di prospettiva più che di sostanza: invece di trattare ogni componente di un vettore o di una matrice come una variabile scalare separata, si lavora direttamente con il vettore e con la matrice come oggetti unitari, e si usano identità che operano su quegli oggetti interi. Il risultato è una notazione molto più compatta — e una procedura molto più affidabile.
Il riferimento canonico è il libro di Jan R. Magnus, econometrico olandese, e Heinz Neudecker (1933-2017), econometrico olandese: Matrix Differential Calculus with Applications in Statistics and Econometrics (Wiley, prima edizione 1988, edizione riveduta 2019). L’idea che dà al libro la sua spina dorsale risale a Neudecker: lavorare attraverso i differenziali — piccole variazioni — e passare dai differenziali alle derivate solo all’ultimo passo. È un’idea apparentemente modesta che cambia tutto, perché elimina il momento fragile, quello in cui si indovina come disporre i risultati. Magnus, in un saggio dal titolo Matrix Derivatives: Why and Where Did It Go Wrong?, argomenta che la derivazione componente per componente, fatta senza metodo, ha prodotto in letteratura definizioni incoerenti della derivata matriciale, e che il metodo del differenziale è superiore perché conduce a una regola della catena che funziona davvero anche per le funzioni matriciali.
Le radici del calcolo differenziale matriciale, però, non sono né recenti né nate per il machine learning. La derivata di una funzione rispetto a un vettore o a una matrice è uno strumento dell’algebra lineare e della statistica multivariata da molto prima che esistessero le reti neurali profonde. Gli econometrici la usavano per derivare gli stimatori dei minimi quadrati e di massima verosimiglianza nei modelli a molte variabili; gli statistici per studiare la matrice di covarianza e le sue trasformazioni. Quando il deep learning ha reso il calcolo dei gradienti l’operazione più frequente di un’intera industria, ha ereditato un apparato matematico già maturo: non ha dovuto inventarlo, ha dovuto solo automatizzarlo. Questo spiega perché le notazioni in giro siano così disomogenee — provengono da comunità diverse, ciascuna con le sue convenzioni — e perché valga la pena fermarsi, in questo capitolo, a fissare una convenzione e a tenerla.
Sul versante machine learning le stesse identità circolano in cheat sheet largamente usati. Il più citato è il Matrix Cookbook di Kaare Brandt Petersen e Michael Syskind Pedersen (technical report, ultima edizione 2012): una raccolta di formule pronte, comoda da tenere accanto ma muta sul perché di ogni formula. Le note di matrix calculus dei corsi introduttivi — MIT 6.390, Princeton COS 302, Stanford CS231n — presentano lo stesso materiale in chiave esplicitamente ML. Questo capitolo sta in mezzo: non un ricettario, ma nemmeno un trattato; le poche identità che servono, ciascuna con la sua derivazione, e il metodo per ricavarne altre quando serviranno.
C’è infine una storia più recente, quella della differenziazione automatica, e conviene nominarla perché è il motivo per cui oggi quasi nessuno applica a mano le identità di questo capitolo. Per decenni, calcolare i gradienti di un programma è stato un lavoro manuale — derivare riga per riga, con tutti gli errori del caso — oppure numerico, per differenze finite, con i problemi di precisione che il primo capitolo di questa Parte ha descritto. La differenziazione automatica in modalità reverse, cioè la backpropagation generalizzata a un grafo di calcolo qualsiasi, era nota in forma matematica già dagli anni Settanta, ma è diventata infrastruttura quotidiana solo con i framework moderni: prima Theano, poi TensorFlow, poi PyTorch e JAX. Il salto non è stato matematico — la regola della catena è quella di Leibniz — ma ingegneristico: rendere automatica, affidabile e veloce la costruzione della catena di derivate per qualunque rete. I framework, sotto il cofano, applicano esattamente le identità di questo capitolo; impararle non serve a sostituirli, serve a non essere ciechi di fronte a ciò che fanno.
Un’ultima fondamenta, dichiarata per onestà verso il lettore. Questo capitolo dà per acquisiti i vettori e le matrici della Parte IV: cosa sia un vettore colonna, cosa significhi moltiplicare due matrici e perché l’ordine conti, cosa sia la trasposta e cosa l’inversa. Chi quei concetti non li ha freschi farebbe bene a ripassare i capitoli Matrici come trasformazioni e Prodotto scalare come proiezione e somiglianza prima di procedere: qui le matrici sono lo strumento, non l’oggetto di studio, e si suppone che lo strumento sia già in mano.
L’ordine del capitolo: prima l’intuizione di cosa significhi derivare rispetto a una matrice; poi la convenzione di layout, perché senza fissarla si annega negli errori di trasposta; poi le identità fondamentali; poi il metodo del differenziale che le genera tutte; infine i layer lineari, la backpropagation in forma matriciale, e gli esempi.
L’intuizione
Sezione intitolata “L’intuizione”Derivare uno scalare rispetto a una matrice suona astratto. Due angoli lo rendono concreto: uno che riusa un’immagine già vista, uno che anticipa il metodo affidabile.
Angolo 1 — la matrice come tabella di manopole
Sezione intitolata “Angolo 1 — la matrice come tabella di manopole”Nel capitolo sulle derivate parziali una funzione di n variabili era descritta come una scatola con n manopole sul pannello frontale e un display che mostra un numero. Ogni manopola era una variabile di ingresso, il display era il valore della funzione. La derivata parziale rispetto a una manopola rispondeva a una domanda di sensibilità: se ruoto di un filo solo quella manopola, tenendo ferme tutte le altre, di quanto si muove il numero sul display?
Una funzione di una matrice è esattamente la stessa scatola, con una sola differenza estetica: le manopole non sono allineate in fila, sono disposte in griglia. La manopola alla riga i, colonna j controlla l’entrata W[i][j] della matrice. Il display mostra ancora un numero solo, la loss. Niente di nuovo concettualmente: tante variabili, una uscita scalare.
Conviene insistere su quanto sia diverso, in pratica, da ciò a cui si è abituati. Nel calcolo a una variabile la derivata di uno scalare è uno scalare; nel calcolo multivariabile la derivata di uno scalare rispetto a un vettore è un vettore. Qui si sale di un altro gradino: la derivata di uno scalare rispetto a una matrice è una matrice. La progressione è regolare — l’oggetto-derivata ha sempre la forma dell’oggetto rispetto a cui si deriva — ma il passo dalla colonna alla griglia è quello che disorienta, perché la griglia ha due indici e ogni operazione (somma, prodotto, trasposta) può scambiarli. È esattamente lì che nascono gli errori, ed è esattamente per disciplinare quei due indici che servono le convenzioni e il metodo del resto del capitolo.
Il gradiente rispetto alla matrice è la risposta alla domanda di sensibilità per ogni manopola della griglia. Sono m \times n numeri, uno per manopola, e la cosa naturale è disporli nella stessa griglia m \times n da cui provengono. Da qui la regola di forma, che è la bussola di tutto il capitolo:
Il gradiente di uno scalare rispetto a una matrice
m \times nè una matricem \times n. Il gradiente ha sempre la stessa forma dell’oggetto rispetto a cui si deriva.
Questa regola sembra una banalità contabile. È invece lo strumento di controllo più potente che si ha: se a fine derivazione il gradiente esce di forma diversa da W, la derivazione è sbagliata, e non serve nemmeno guardare i numeri per saperlo.
Un modo utile di pensarci è per analogia con i tipi di un linguaggio di programmazione. Un compilatore con un buon sistema di tipi rifiuta un programma in cui si somma un intero a una stringa, prima ancora di eseguirlo: l’errore è nel tipo, non nel valore. La regola di forma è un sistema di tipi per i gradienti. Una derivazione che produce un gradiente di forma sbagliata è un programma mal tipato: non importa quali numeri ci siano dentro, è già rotto. E come i tipi catturano una grossa fetta dei bug senza eseguire niente, la regola di forma cattura la maggior parte degli errori di trasposta a colpo d’occhio. Questa è un’analogia didattica, non un’equivalenza formale — ma è un’analogia che, una volta interiorizzata, cambia il modo in cui si guarda una formula di gradiente.
Angolo 2 — il differenziale come lettura della pendenza
Sezione intitolata “Angolo 2 — il differenziale come lettura della pendenza”Il secondo angolo è meno visivo ma è il cuore del metodo affidabile, e conviene introdurlo subito perché tutto il resto del capitolo vi si appoggia.
Per una funzione scalare ordinaria , il differenziale è la scrittura . In parole povere, questo dice che una piccola variazione dell’ingresso produce una variazione dell’uscita pari alla pendenza moltiplicata per quella variazione. Il differenziale è la parte lineare della variazione: l’approssimazione che ignora i termini di ordine superiore, esatta nel limite di variazioni infinitesime. È lo stesso “zoom” del capitolo sull’analisi: vista abbastanza da vicino, una funzione liscia è indistinguibile da una retta, e è l’equazione di quella retta.
L’idea di Neudecker è estendere questa scrittura a vettori e matrici. Se la funzione è scalare e l’ingresso è un vettore , il differenziale ha sempre — sempre — la forma
per un certo vettore . Non è un’ipotesi: è un fatto, e quel è, per definizione di gradiente, il gradiente di . Il motivo per cui la forma è sempre questa è che , la parte lineare della variazione, deve dipendere linearmente da — e ogni funzione lineare di un vettore che restituisce uno scalare si scrive come un prodotto scalare con un certo vettore. Quel vettore non può che essere il gradiente: è l’unico oggetto compatibile con la forma. Il metodo allora diventa una ricetta in tre tempi: calcola con le sole regole algebriche, riscrivilo nella forma canonica , e leggi . Non si indovina nessuna trasposta, perché la forma canonica esibisce già il gradiente bell’e disposto. Per una funzione scalare di una matrice la forma canonica è leggermente diversa — coinvolge la traccia, e la si vedrà fra poco — ma la logica è identica: porta il differenziale nella forma standard, e il gradiente è lì da leggere.
Angolo 3 — perché non basta derivare componente per componente
Sezione intitolata “Angolo 3 — perché non basta derivare componente per componente”Si potrebbe obiettare: se il gradiente rispetto a una matrice è solo una griglia di derivate parziali, perché non calcolarle una alla volta e basta? Tecnicamente funziona, ed è anzi la definizione del gradiente matriciale. Il problema è di scala e di affidabilità.
Di scala, perché una matrice di pesi di un layer reale ha decine o centinaia di migliaia di entrate, e scrivere a mano altrettante derivate parziali non è un lavoro umano. Ma il problema vero è di affidabilità, e si vede anche su matrici piccole. Quando si derivano le componenti separatamente, alla fine bisogna rimetterle insieme in una matrice — decidere quale derivata va in quale posizione, e se la matrice risultante va trasposta. Quel momento di riassemblaggio è cieco: non c’è una regola che lo guidi, si procede per analogia con casi già visti. È esattamente lì che entrano le trasposte sbagliate.
Il calcolo differenziale matriciale toglie quel momento. Le identità operano sulla matrice intera, e il metodo del differenziale produce il gradiente già disposto nella forma giusta. Non si rimette insieme niente: si legge. Questo è il senso profondo del lavorare con vettori e matrici come oggetti unitari invece che come collezioni di scalari — non è una comodità notazionale, è l’eliminazione del passo fragile.
Tenere insieme i tre angoli: il primo dice che forma ha il gradiente (la stessa dell’argomento); il secondo dice come ottenerlo senza congetture (porta il differenziale in forma canonica e leggilo); il terzo dice perché serve un metodo e non basta la forza bruta componente per componente.
Convenzioni di layout: la fonte di metà degli errori
Sezione intitolata “Convenzioni di layout: la fonte di metà degli errori”Prima delle identità c’è un nodo da sciogliere, perché chiunque confronti due libri di matrix calculus ci sbatte contro. Quando si deriva un vettore di componenti rispetto a un vettore di componenti, il risultato contiene numeri — la sensibilità di ogni rispetto a ogni . Quei numeri si possono disporre in una matrice in due modi opposti, e i due modi sono uno la trasposta dell’altro.
Il layout numeratore, detto anche layout Jacobiano, dispone il risultato come una matrice : la forma del numeratore . In questo layout la derivata di rispetto a esce come un vettore riga, . È la disposizione in cui la matrice Jacobiana, incontrata nel capitolo precedente, compone in modo naturale.
Il layout denominatore, detto anche layout Hessiano, dispone il risultato come una matrice : la forma del denominatore . Qui la derivata di rispetto a esce come un vettore colonna, . È la disposizione in cui il gradiente ha la stessa forma del parametro.
Nessuno dei due è “giusto”: sono convenzioni, scelte di impaginazione dei numeri. Il problema pratico — e Magnus lo segnala con una certa irritazione nel suo saggio — è che moltissimi autori mescolano le due convenzioni dentro lo stesso testo, layout numeratore per certe derivate, denominatore per altre, senza dichiararlo. Combinare una formula scritta in un layout con una scritta nell’altro produce una trasposta di troppo, e quella trasposta di troppo diventa, qualche riga dopo, un errore di dimensione che fa crashare il codice.
C’è anche una ragione storica per questo disordine, ed è la genealogia menzionata nel Contesto. Il layout numeratore viene naturale a chi pensa in termini di Jacobiana e di cambio di variabili — la tradizione dell’analisi multivariata e della fisica. Il layout denominatore viene naturale a chi pensa in termini di gradiente e di ottimizzazione — la tradizione della statistica e, oggi, del machine learning. Le due comunità hanno sviluppato le loro notazioni in parallelo, ciascuna coerente al suo interno, e l’incontro è avvenuto tardi, quando il deep learning ha pescato strumenti da entrambe. Il risultato è che il singolo lettore di oggi si trova davanti a fonti scritte in convenzioni diverse senza un avviso, e deve fare da sé il lavoro di traduzione. Sapere che il lavoro di traduzione esiste — che due formule “contraddittorie” possono essere la stessa cosa trasposta — è già metà della difesa.
Un esempio concreto di come la confusione morde. Si supponga di leggere su un libro di econometria che la derivata di rispetto a è , un vettore riga — quel libro usa il layout numeratore. Si supponga poi di trovare su una dispensa di machine learning che lo stesso oggetto è , un vettore colonna — quella dispensa usa il layout denominatore. Le due affermazioni sembrano contraddirsi, e un lettore che non conosca la storia dei layout passa minuti a chiedersi quale sia “giusta”. Non lo è nessuna delle due: sono la stessa quantità, scritta in due convenzioni diverse, una la trasposta dell’altra. Il problema diventa serio quando si copia una formula da una fonte e la si incolla in un calcolo che procede nell’altra convenzione: l’algebra non avverte, semplicemente il risultato finale esce trasposto, e quel risultato trasposto è un bug silenzioso che la discesa del gradiente non segnala — fa solo convergere il modello verso il posto sbagliato.
La comunità del machine learning ha una preferenza netta, e per un motivo operativo, non estetico. L’update della discesa del gradiente è
dove è il learning rate. Questa sottrazione ha senso solo se ha esattamente la stessa forma di : non si può sottrarre una matrice da una matrice . Per questo in ML il gradiente di uno scalare rispetto a un parametro si dispone sempre con la forma del parametro. Questo capitolo fa la stessa scelta e la dichiara una volta sola, qui:
Convenzione del capitolo. Il gradiente di uno scalare rispetto a un vettore è un vettore della stessa forma di . Il gradiente di uno scalare rispetto a una matrice è una matrice della stessa forma di .
Un dettaglio non secondario: il metodo del differenziale, quello dell’Angolo 2, produce automaticamente il gradiente in questa forma. La forma canonica dà un con la forma di ; la forma canonica per le matrici darà un con la forma di . È una ragione in più per preferire quel metodo: rispetta la convenzione senza che si debba pensarci.
La meccanica: le identità fondamentali
Sezione intitolata “La meccanica: le identità fondamentali”Qui sotto le identità che servono nel 95% dei casi. Notazione: è un vettore colonna , una matrice, un vettore costante; i gradienti hanno la forma dell’argomento, come dichiarato. Ogni identità ha la sua intuizione; la derivazione formale via differenziale arriva nella sezione successiva.
Forma lineare. Per , lo scalare che è la somma pesata delle componenti di con pesi :
L’intuizione è diretta. Lo scalare vale . La sensibilità rispetto a — di quanto cambia se muovo — è il coefficiente , perché compare in in un solo termine, , e moltiplicato proprio per . Raccogliendo tutte le sensibilità si ottiene il vettore . È la generalizzazione esatta del caso scalare , la cui derivata è la costante : una funzione lineare ha pendenza costante, e in più dimensioni quella pendenza costante è un vettore.
Prodotto matrice-vettore. Per — attenzione, qui l’uscita è un vettore, non uno scalare — la derivata è la matrice Jacobiana, e vale
Caso composto utile: , perché è una forma lineare con vettore di pesi — si è semplicemente raggruppato come un unico vettore riga, e applicata l’identità della forma lineare. Vale la pena fermarsi sulla differenza tra le prime due identità: è uno scalare, e la sua derivata rispetto a è un gradiente, un vettore della forma di ; è un vettore, e la sua derivata è una Jacobiana, una matrice. È la distinzione che la sezione “Dove si rompe” segnala come trappola: il primo oggetto si sottrae nell’update, il secondo si moltiplica nella catena. Tenerli separati nella testa, fin dalle identità più semplici, evita di confonderli su espressioni complicate.
Forma quadratica. Per , lo scalare che generalizza a più dimensioni:
Se è simmetrica, cioè , la formula si semplifica in . Il caso più speciale è , la matrice identità: allora , la norma al quadrato, e il gradiente è . Controprova in una dimensione: con la forma quadratica è , la cui derivata ordinaria è , in accordo con .
Un conto a due dimensioni rende la formula tangibile. Sia , che non è simmetrica, e . La forma quadratica vale . Il gradiente, per la formula, è con , quindi . Chi avesse usato per distrazione avrebbe sbagliato: non è simmetrica, e la scorciatoia non si applica. La differenza tra e è piccola in apparenza, ma in un training loop produrrebbe un modello che converge verso il posto sbagliato.
Norma quadratica del residuo. Per , cioè :
Questa è la pietra angolare dei minimi quadrati, e la sua derivazione passo per passo è l’esempio guida della prossima sezione.
Traccia. La traccia di una matrice quadrata è la somma dei suoi elementi diagonali. Ha due proprietà che la rendono il perno di tutto il metodo. È lineare: . Ed è ciclica: si possono ruotare i fattori di un prodotto senza cambiare la traccia, . Le identità centrali con la traccia, dove è la matrice rispetto a cui si deriva:
La traccia è importante non per sé stessa ma perché è il ponte tra differenziale e gradiente: ogni differenziale di uno scalare funzione di matrice si riscrive nella forma , e quel è il gradiente. Senza la traccia, la regola di lettura per le matrici non esisterebbe.
Perché proprio la traccia, e non un’altra operazione? Per una ragione geometrica precisa. Per i vettori, la forma canonica del differenziale è il prodotto scalare — l’operazione che “accoppia” due vettori in un numero. Per le matrici serve l’analogo: un’operazione che accoppia due matrici della stessa forma in un numero, sommando i prodotti delle entrate corrispondenti. Quell’operazione esiste, si chiama prodotto interno di Frobenius, e si scrive proprio : la traccia di è esattamente la somma . La forma canonica per le matrici non è un trucco notazionale: è il prodotto scalare, esteso dalle colonne di numeri alle griglie di numeri. E come per i vettori il gradiente è il vettore che sta nel prodotto scalare, per le matrici il gradiente è la matrice che sta nel prodotto di Frobenius.
Determinante e log-determinante. Per , con invertibile:
Una nota di intuizione sul determinante, perché la formula sembra uscire dal nulla. Il determinante misura quanto la trasformazione dilata o comprime i volumi — questo è il senso geometrico visto nel capitolo sulle matrici come trasformazioni. La sua derivata dice quanto cambia quel fattore di dilatazione se si perturba un’entrata di , e risulta proporzionale a stesso moltiplicato per l’inversa trasposta: il fattore di dilatazione cambia in modo proporzionale a sé stesso, modulato dalla “sensibilità inversa” della trasformazione. Il logaritmo rende la cosa più semplice perché derivare un logaritmo divide per l’argomento, e quel al numeratore si elide.
In machine learning si incontra più spesso il logaritmo del determinante, perché compare nella log-verosimiglianza della gaussiana multivariata, nei normalizing flow (modelli generativi in cui si somma il log del determinante dello Jacobiano della trasformazione), nella regressione con processi gaussiani. La sua derivata è notevolmente più pulita di quella del determinante nudo:
Il differenziale corrispondente è , e da quello il gradiente si legge in un colpo solo.
Vale la pena fermarsi su un dettaglio di queste sei identità, perché è il filo che le tiene insieme. In ognuna il gradiente esce con la stessa forma dell’oggetto rispetto a cui si deriva: il gradiente di uno scalare rispetto a un vettore è un vettore, quello rispetto a una matrice è una matrice. Non è un caso fortunato: è la conseguenza della convenzione fissata prima, e il metodo del differenziale, che vedremo ora, la rispetta automaticamente. Chi prende una di queste identità da un cheat sheet scritto in layout numeratore la troverà trasposta rispetto a come è scritta qui — un’altra ragione, oltre a quelle già dette, per derivare da sé con un metodo invece che copiare formule da fonti con convenzioni ignote.
Il metodo del differenziale, passo per passo
Sezione intitolata “Il metodo del differenziale, passo per passo”Le identità sopra sono comode da avere a portata di mano, ma impararle a memoria non è il punto. Il punto è il metodo che le genera tutte, e che si applica anche a espressioni che nessun cheat sheet copre. È il metodo dell’Angolo 2, ora in forma operativa: tre passi.
Una premessa sul perché funziona, perché senza capirla i tre passi sembrano una procedura magica. La forza del metodo sta in una divisione netta del lavoro. Il calcolo del differenziale, Passo 1, usa solo regole algebriche — somma, prodotto, trasposta, inversa — che si comportano in modo prevedibile e non richiedono alcuna decisione su come disporre i risultati. Il passaggio dal differenziale al gradiente, Passo 3, è una semplice lettura di una forma standard. L’unico punto in cui si esercita un po’ di abilità è il Passo 2, la manipolazione algebrica che porta in forma canonica — ma anche lì gli strumenti sono pochi e meccanici (ciclicità della traccia, ). Non c’è, in nessun punto, il momento “a sentimento” della derivazione componente per componente. Ogni passo o è puramente algebrico o è puramente meccanico. È questa l’origine dell’affidabilità.
Passo 1 — calcola il differenziale . Si usano solo regole algebriche, nessuna derivata parziale scritta esplicitamente. Le regole sono poche e si comportano come ci si aspetta:
- linearità: e per costante;
- regola del prodotto: , esattamente come per gli scalari ma rispettando l’ordine dei fattori, perché le matrici non commutano;
- trasposta: ;
- inversa: ;
- una costante ha differenziale nullo: se è costante, .
Passo 2 — porta nella forma canonica. Per uno scalare funzione di un vettore, la forma canonica è . Per uno scalare funzione di una matrice è . Per arrivarci si usano due trucchi: uno scalare è uguale alla propria traccia (, perché la traccia di una matrice è il suo unico elemento), e la ciclicità della traccia permette di ruotare i fattori finché il termine resta isolato all’estrema destra.
Passo 3 — leggi il gradiente. È la regola di identificazione di Magnus e Neudecker: se sei arrivato a , allora ; se sei arrivato a , allora . Non c’è niente da indovinare: la forma canonica esibisce il gradiente. Questo è il motivo per cui il metodo è affidabile dove la derivazione componente per componente è fragile.
La regola di identificazione merita una parola in più, perché è il punto delicato. Perché si può “leggere” il gradiente da ? Perché quel è unico: se due vettori e dessero per ogni possibile , allora e sarebbero lo stesso vettore. Il differenziale, in altre parole, determina il gradiente senza ambiguità. È un teorema, non un’analogia: la forma canonica è una rappresentazione e quella rappresentazione è unica. Lo stesso vale per la forma e la matrice . È questa unicità che trasforma il Passo 3 da una congettura a una lettura sicura.
Vale la pena vederlo all’opera sull’identità più usata, il gradiente di . Si pone , il vettore residuo, così che .
Passo 1, il differenziale. Per la regola del prodotto, . I due termini sono entrambi scalari, e sono uno la trasposta dell’altro, quindi sono uguali: . Ora serve . Poiché con e costanti, si ha (il termine e il termine sono nulli). Sostituendo: .
Passo 2, la forma canonica. Serve la forma . Il fattore davanti a è , che è un vettore riga; lo si scrive come con . Dunque .
Passo 3, la lettura. La forma è con . Quindi
Il gradiente è stato letto, non congetturato. Si noti che a nessun punto si è dovuto decidere “qui ci va una trasposta”: la forma canonica l’ha imposta da sola. Il momento in cui la trasposta è entrata — il passaggio da a — non è stato una scelta ma una riscrittura obbligata: un vettore riga è, per definizione, la trasposta del vettore colonna , e per riconoscere la forma canonica si è solo dovuto nominare quel . Confrontare questo con la derivazione componente per componente: lì, alla fine, si avrebbero derivate parziali sciolte, e si dovrebbe decidere se impilarle come stanno o trasporle. Il metodo del differenziale non lascia spazio a quella decisione, ed è per questo che non la si può sbagliare.
Conviene vedere il metodo una seconda volta sul caso della matrice, perché lì entra in gioco la traccia e il meccanismo è meno ovvio. Si deriva rispetto a , dove è costante.
Passo 1, il differenziale. La traccia è lineare, quindi commuta con il differenziale: . Poiché è costante, , e dunque .
Passo 2, la forma canonica. Per uno scalare funzione di matrice la forma canonica è . Il differenziale è già , con isolato a destra: basta confrontare. Si ha , da cui .
Passo 3, la lettura. Da segue , quindi . Ed ecco da dove viene la trasposta che tanti dimenticano: non è una scelta arbitraria, è la conseguenza inevitabile del confronto con la forma canonica , che ha quel già trasposto al suo interno. Chi salta il passaggio per la forma canonica perde la trasposta perché perde il punto in cui nasce.
Lo stesso metodo, applicato a , parte dal differenziale — è una delle regole standard, e la si può prendere per data come si prende la regola del prodotto. Confrontando con si legge , quindi . Tre righe, e l’identità del log-determinante è dimostrata, non memorizzata.
Vale la pena vedere anche una derivazione che usa la regola del prodotto e la ciclicità insieme, perché è il pattern più comune. Sia . Passo 1: per la regola del prodotto applicata dentro la traccia, . Il primo termine ha trasposto e a sinistra; va sistemato. Si usa il fatto che la traccia di una matrice è uguale alla traccia della sua trasposta, : il primo termine diventa . Passo 2: ora entrambi i termini hanno isolato a destra, e si sommano: . Passo 3: confronto con , da cui , e quindi . Si ritrova la terza identità della traccia, ma stavolta dimostrata: e si vede che la simmetria esce dalla somma dei due termini della regola del prodotto, uno per ciascuna occorrenza di .
Un’ultima applicazione del metodo, leggermente più articolata, mostra la regola dell’inversa al lavoro. Sia con costante. Passo 1: per la linearità della traccia, , e per la regola dell’inversa , quindi . Passo 2: la ciclicità della traccia permette di ruotare il fattore finale fino a portarlo davanti, isolando a destra: . Passo 3: confronto con , da cui , e quindi . L’esempio è istruttivo perché nessun cheat sheet, probabilmente, riporta proprio questa formula: ma il metodo la produce senza esitazioni. È la differenza tra avere un ricettario finito e avere la tecnica per cucinare qualsiasi piatto.
Vale la pena estrarre la morale da questi esempi, perché è la ragione per cui questa sezione viene prima di tutte le applicazioni. Le sei identità della sezione precedente sono utili da conoscere — coprono i casi più frequenti, e averle pronte risparmia tempo. Ma sono casi particolari di un’unica procedura, e la procedura è ciò che davvero si porta a casa. Chi memorizza solo le identità è disarmato di fronte alla prima espressione che non vi compare; chi ha interiorizzato il metodo del differenziale deriva l’identità che gli serve, nuova o nota, sempre nello stesso modo, e con la stessa garanzia di non sbagliare le trasposte. Per il resto del capitolo le identità verranno usate liberamente, ma è bene ricordare che dietro ciascuna c’è la stessa procedura di tre passi.
La regola della catena in forma matriciale
Sezione intitolata “La regola della catena in forma matriciale”Una sola identità manca all’appello, ed è quella che tiene insieme le altre quando si compongono più funzioni: la regola della catena. Nel calcolo a una variabile la regola dice che le pendenze si moltiplicano lungo la catena, . In forma matriciale il principio è lo stesso, ma i fattori sono matrici, e l’ordine in cui si moltiplicano conta — le matrici non commutano.
Si consideri una catena , dove è una funzione vettoriale e è uno scalare. Per la regola della catena, il gradiente di rispetto a è
dove è la matrice Jacobiana di rispetto a , l’oggetto visto nel capitolo precedente. In parole povere: il gradiente che arriva dal lato uscita, , viene “tirato indietro” attraverso lo stadio moltiplicandolo per la Jacobiana trasposta di quello stadio. Quel “tirare indietro” è la backpropagation; quella trasposta è la stessa che il metodo del differenziale produce da solo.
Il differenziale rende la regola della catena quasi banale, ed è uno dei suoi pregi maggiori. Se e con la Jacobiana, basta sostituire: . La forma canonica è raggiunta, e si legge . La composizione di funzioni diventa una composizione di differenziali, e i differenziali si sostituiscono l’uno nell’altro senza che si debba ragionare su quale fattore vada trasposto: la forma canonica decide. È esattamente per questo che Magnus e Neudecker insistono sul fatto che il metodo del differenziale “dà una regola della catena soddisfacente” mentre la derivazione componente per componente, da sola, no.
Si noti la trasposta della Jacobiana, , nella formula del gradiente. È la stessa trasposta che compare in e in : non sono trasposte diverse messe lì per far tornare le forme, è sempre la stessa cosa, la trasposta che la regola della catena impone quando si tira il gradiente all’indietro attraverso uno stadio. Riconoscerla come un unico fenomeno — e non come una collezione di trasposte da memorizzare caso per caso — è metà del lavoro per non sbagliarle mai.
Verificare un gradiente: il gradient checking
Sezione intitolata “Verificare un gradiente: il gradient checking”Prima dei layer, una nota pratica che chiude il cerchio sul metodo. Una volta derivato un gradiente a mano — con le identità o con il differenziale — come si controlla che sia giusto, senza fidarsi ciecamente dell’algebra? La risposta è il gradient checking, e si appoggia alla definizione stessa di derivata.
Per definizione, la derivata di rispetto alla componente è il limite del rapporto incrementale: si perturba di una quantità piccola , si guarda di quanto cambia , e si divide. Numericamente, con piccolo ma non nullo, si ottiene un’approssimazione del gradiente:
dove è il vettore con un 1 in posizione e zeri altrove. Questa è la differenza finita centrata: lenta, perché richiede due valutazioni di per ogni componente, ma indipendente dal gradiente analitico, e quindi un controllo onesto. Si calcola il gradiente con la formula derivata a mano, lo si calcola di nuovo per differenze finite, e si confrontano: se i due quasi coincidono — entro una tolleranza dovuta al fatto che non è davvero zero — la derivazione è corretta; se divergono in modo macroscopico, da qualche parte c’è una trasposta sbagliata o un fattore mancante.
In codice il controllo è poche righe, e vale la pena vederlo perché è il test che si scrive ogni volta che si deriva un gradiente a mano:
def gradient_check(f, x, grad_analitico, eps=1e-5): # f: funzione scalare; x: punto; grad_analitico: gradiente da verificare grad_numerico = zeros_like(x) for i in range(x.size): x_piu, x_meno = x.copy(), x.copy() x_piu[i] += eps x_meno[i] -= eps grad_numerico[i] = (f(x_piu) - f(x_meno)) / (2 * eps) # errore relativo: piccolo se la derivazione e' corretta return norm(grad_analitico - grad_numerico) / norm(grad_analitico)Un errore relativo dell’ordine di o inferiore segnala che il gradiente analitico è corretto; un errore dell’ordine di o peggiore segnala un bug — quasi sempre una trasposta o un fattore. Il prezzo è il ciclo for su tutte le componenti, che rende il gradient checking troppo lento per l’addestramento vero: lo si usa una volta, in fase di sviluppo, su un esempio piccolo, e poi si spegne.
Il gradient checking è il modo standard per validare il backward di un layer custom prima di metterlo in produzione, ed è anche il motivo per cui le derivate matriciali e le differenze finite del primo capitolo di questa Parte non sono in competizione: le prime danno la formula veloce ed esatta, le seconde danno il controllo lento e indipendente. Si usano insieme — la formula per addestrare, la differenza finita per fidarsi della formula.
Una cautela sul valore di , perché è il punto in cui il gradient checking può ingannare. Se è troppo grande, la differenza finita approssima male la derivata e il confronto fallisce anche se il gradiente analitico è giusto. Se è troppo piccolo, gli errori di arrotondamento dell’aritmetica in virgola mobile dominano il rapporto incrementale, e di nuovo il confronto fallisce. È lo stesso compromesso descritto nel capitolo sull’analisi matematica: esiste un intermedio, tipicamente intorno a , in cui l’approssimazione è buona e il rumore numerico è tollerabile. Sapere che questo compromesso esiste evita di concludere “il mio gradiente è sbagliato” quando il problema è solo un scelto male.
Derivate rispetto a matrici nei layer lineari
Sezione intitolata “Derivate rispetto a matrici nei layer lineari”Tutto questo serve a un bersaglio preciso: il layer lineare, il mattone più comune di una rete neurale. Un layer lineare — in PyTorch è il modulo nn.Linear — calcola , dove è la matrice degli input del batch (una riga per ogni esempio del batch, una colonna per ogni feature) e è la matrice dei pesi. A questo si aggiunge un vettore di bias . La loss è uno scalare prodotto da tutto ciò che sta a valle del layer.
Prima di scrivere le formule, una parola sulle dimensioni in gioco, perché è ciò che le formule devono rispettare. In un layer tipico (le feature in ingresso) e (le uscite) sono dell’ordine delle centinaia o migliaia, e (la dimensione del batch) di qualche decina o centinaio. La matrice di pesi è , e in una rete grande è proprio dove vive la stragrande maggioranza dei parametri. Ogni formula di gradiente che segue deve restituire un oggetto della forma giusta — esattamente , esattamente — altrimenti l’update non si può nemmeno scrivere. La regola di forma non è un controllo opzionale: è un vincolo che le formule, per essere corrette, non possono che soddisfare.
Per addestrare il layer servono tre gradienti, e il metodo del differenziale li produce tutti. Il primo è il gradiente della loss rispetto ai pesi, quello che alimenta l’update . Ha la forma cardine:
dove è il gradiente arrivato dal layer a valle. La regola di forma verifica subito la correttezza: se è (batch size , feature), allora è ; se è , il prodotto è , identica a . Per un singolo esempio del batch questa formula è il prodotto esterno : l’entrata riceve un gradiente proporzionale al prodotto tra l’input e il segnale d’uscita .
Il prodotto esterno merita un momento, perché dà l’intuizione di cosa la rete impara. Quando un esempio passa nel layer, il suo input è un vettore e il segnale di errore che torna dal lato uscita è un vettore . Il gradiente sui pesi, , è la griglia di tutti i prodotti tra una componente di e una componente del segnale: l’entrata è grande quando l’input è stato forte e l’uscita ha sbagliato di molto. L’update spingerà quella entrata in modo da correggere proprio quella combinazione. È una versione precisa, in algebra matriciale, del principio per cui “si rinforza il collegamento tra ciò che era attivo in ingresso e ciò che andava cambiato in uscita” — e mostra che la regola di aggiornamento di un layer non è arbitraria, è esattamente ciò che il gradiente impone.
Il secondo gradiente è quello rispetto all’input, che non serve per aggiornare questo layer ma per consegnare il segnale al layer precedente nella catena:
di forma , identica a . Il terzo è il gradiente rispetto al bias, che si ottiene sommando il gradiente d’uscita su tutto il batch:
di forma . La somma sul batch nasce dal fatto che lo stesso bias è condiviso da tutti gli esempi: ogni esempio contribuisce un pezzetto al suo gradiente.
La somma sul batch è un esempio di un principio generale che vale ogni volta che un parametro è condiviso. Quando lo stesso peso interviene in più punti del calcolo — il bias usato da tutti gli esempi del batch, ma anche i pesi di un layer convoluzionale applicati a ogni posizione dell’immagine, o i pesi di un modello ricorrente riusati a ogni passo temporale — il suo gradiente è la somma dei contributi da tutti quei punti. Non una media, non uno solo: la somma. La ragione è la linearità della derivata: se un parametro influenza la loss attraverso più strade indipendenti, il suo effetto totale è la somma degli effetti lungo ciascuna strada. Tenere a mente questo principio spiega molte sommatorie che altrimenti sembrano comparire dal nulla nelle formule di backprop.
Vale la pena vedere da dove esce la formula cardine, perché è il metodo del differenziale applicato a un caso che conta davvero. Si parta da uno scalare funzione di , e si supponga di conoscere già — è ciò che la backpropagation consegna dal lato uscita. Il differenziale di rispetto al solo (tenendo fissa) è, nella forma canonica per le matrici, . Ora , perché è costante in questa derivazione. Sostituendo: . La ciclicità della traccia non serve nemmeno: è già isolato a destra. Confronto con : , quindi . La formula è stata derivata, non postulata, e la trasposta su è uscita da sola dal confronto con la forma canonica.
Una nota di convenzione che salva da un’ora di confusione. Alcuni testi scrivono il layer come , con gli esempi disposti in colonna invece che in riga. In quella convenzione le formule diventano e . La logica è identica, cambia solo la disposizione di righe e colonne. La regola pratica: dentro una singola derivazione si sceglie una convenzione e non la si cambia mai. Mescolare e è esattamente lo stesso errore del mescolare i due layout.
Il legame con la backpropagation in forma matriciale
Sezione intitolata “Il legame con la backpropagation in forma matriciale”La backpropagation, vista nel capitolo Derivate parziali, gradienti, Jacobiani, è la regola della catena applicata al grafo di calcolo della rete e percorsa a ritroso, dall’uscita verso l’ingresso. In forma matriciale ogni layer si comporta come una stazione con due porte. Dal lato uscita riceve un gradiente, . Da quel gradiente produce due cose: il gradiente verso i propri parametri ( e , che servono per l’update) e il gradiente verso il proprio input (, che viene consegnato al layer precedente).
Le tre formule della sezione precedente sono il passo all’indietro — il backward — di un layer lineare. Non sono un’illustrazione della backpropagation: sono la backpropagation di quel layer, scritta per esteso. Una rete intera è una catena di questi backward: il calcolato dal layer è precisamente il del layer , già pronto, da passare indietro. Il capitolo sulle derivate parziali mostra che la backpropagation, in astratto, è una sequenza di prodotti vettore-Jacobiana letti a ritroso. Qui quell’astrazione prende corpo: le Jacobiane sono le matrici di pesi, i prodotti sono moltiplicazioni di matrici, e le identità di questo capitolo dicono quali matrici si moltiplicano e con quali trasposte. La trasposta in non è una decorazione: è ciò che fa tornare le forme, ed è esattamente ciò che il metodo del differenziale produce senza farlo indovinare.
C’è un dettaglio di economia computazionale che vale la pena notare, perché è il motivo per cui la backpropagation è praticabile e il calcolo “in avanti” dei gradienti non lo sarebbe. In una rete profonda, il segnale che attraversa la catena di backward è sempre un oggetto della forma di un output — per un layer di classificazione, un vettore di poche componenti per esempio del batch. I gradienti rispetto ai pesi si ottengono moltiplicando questo segnale per gli input memorizzati durante il passo in avanti. Se invece si propagassero le Jacobiane intere dall’ingresso verso l’uscita, si moltiplicherebbero matrici enormi tra loro, e il costo esploderebbe. La backpropagation è veloce perché percorre la catena nel verso giusto — dall’uscita scalare verso l’ingresso — e in quel verso ogni passo è un prodotto matrice-per-segnale, non matrice-per-matrice. Le identità di questo capitolo sono la forma esplicita di quei prodotti.
Quattro esempi deliberatamente diversi tra loro: uno numerico con i conti fatti a mano, uno in codice, uno di debugging puro per ragionamento sulle forme, uno scenario reale di una scelta di design ricorrente.
Esempio 1 — numerico: il gradiente di OLS in forma chiusa
Sezione intitolata “Esempio 1 — numerico: il gradiente di OLS in forma chiusa”OLS, ordinary least squares, i minimi quadrati ordinari: si cerca il vettore di parametri che minimizza , la somma dei quadrati degli scarti tra le predizioni e i bersagli . Per l’identità del residuo, .
Imporre che il gradiente sia il vettore nullo — condizione necessaria per un minimo — dà , le equazioni normali, e da lì la soluzione in forma chiusa . È raro in machine learning avere una soluzione esplicita: quasi sempre si è costretti a iterare con la discesa del gradiente. Qui si può perché la loss è quadratica e convessa, e per una funzione convessa l’annullamento del gradiente individua il minimo globale, non un minimo locale qualsiasi.
Un conto minimale per fissare le idee. Sia (due esempi, una sola feature) e . Allora e , da cui . Verifica che il gradiente si annulli davvero in : il residuo è , e il gradiente è . Il gradiente è esattamente nullo nel minimo, come deve essere.
Due osservazioni che generalizzano il conto. La prima: la stessa formula vale identica con molte feature e molti esempi — diventa una matrice ( esempi, feature), un vettore di componenti, e una matrice da invertire. Il gradiente resta , e la soluzione resta : l’algebra matriciale ha permesso di scrivere una volta una formula che vale in ogni dimensione. La seconda: se la regressione ha una regolarizzazione , cioè si minimizza , il gradiente diventa — basta sommare il gradiente del termine di penalità, — e la soluzione in forma chiusa diventa . Il termine aggiunto alla matrice da invertire è precisamente ciò che rende il problema risolvibile anche quando da sola non è invertibile: la regolarizzazione, vista dal lato del gradiente matriciale, è un’aggiunta di un termine lineare.
Esempio 2 — in codice: il backward di un layer fully-connected
Sezione intitolata “Esempio 2 — in codice: il backward di un layer fully-connected”Le tre formule del layer lineare, scritte come si scriverebbero in un framework che non usa l’autograd — per esempio implementando un layer custom o per capire cosa fa nn.Linear sotto il cofano:
# forward: Y = X @ W + b# X: (B, n) W: (n, m) b: (1, m) Y: (B, m)def fc_forward(X, W, b): return X @ W + b
# backward: riceve dY = grad della loss rispetto a Y, forma (B, m)def fc_backward(dY, X, W): dW = X.T @ dY # (n, B) @ (B, m) -> (n, m): forma di W db = dY.sum(axis=0) # somma sul batch -> (m,): forma di b dX = dY @ W.T # (B, m) @ (m, n) -> (B, n): forma di X return dW, db, dXIl test di correttezza non è guardare i numeri: è guardare le forme. dW deve uscire , db deve avere componenti, dX deve uscire . Se una trasposta è al posto sbagliato, o la moltiplicazione fallisce per dimensioni incompatibili, o esce una forma errata che farà crashare il layer successivo. Questo è esattamente ciò che loss.backward() esegue per ogni nn.Linear della rete: la differenza è solo che l’autograd costruisce e percorre la catena al posto nostro, ma le formule che applica sono queste tre.
Il modo onesto di fidarsi di fc_backward è passarlo al gradient_check della sezione precedente: si costruisce una loss scalare fittizia a valle del layer — per esempio , il cui gradiente è una matrice di soli 1 — si calcolano dW, db, dX con le formule, e li si confronta con le differenze finite. Se l’errore relativo è dell’ordine di , le tre formule sono corrette; se una salta, si è isolato esattamente quale gradiente è sbagliato. È la procedura che ogni framework usa nella propria suite di test per i layer.
Esempio 3 — la regola di forma come strumento di debug
Sezione intitolata “Esempio 3 — la regola di forma come strumento di debug”Questo esempio non ha numeri: mostra come la regola di forma trovi un bug in un’espressione, senza eseguire niente. Si supponga di aver scritto, in un layer custom, che il gradiente rispetto ai pesi sia dW = dY @ X.T. È giusto? Le forme: dY è , X.T è . Il prodotto dY @ X.T richiede che le dimensioni interne combacino — la seconda di dY con la prima di X.T — cioè . In un layer reale (numero di uscite) e (numero di feature) sono diversi, quindi la moltiplicazione fallisce subito con un errore di dimensione. Se per caso , peggio ancora: la moltiplicazione passa, ma il risultato esce invece di , e il bug si manifesta più tardi e più lontano. La forma corretta, come visto, è X.T @ dY: per dà , identica a W. La regola di forma — il gradiente di W ha la forma di W — ha individuato l’errore prima di qualunque esecuzione, e ha anche detto come correggerlo: l’unica disposizione di X.T, dY e dei prodotti che restituisce è quella giusta. È questo il senso pratico di tutto il capitolo: le forme sono un sistema di tipi per i gradienti, e si controllano a occhio.
Esempio 4 — scenario reale: il gradiente della cross-entropy con softmax
Sezione intitolata “Esempio 4 — scenario reale: il gradiente della cross-entropy con softmax”Classificazione multi-classe, lo scenario di ogni testa di classificazione di una rete — la classificazione di un’immagine tra mille categorie, la predizione del prossimo token tra decine di migliaia di possibilità in un modello di linguaggio. I logit , un vettore di componenti (una per classe), passano per la funzione softmax, che li trasforma in un vettore di probabilità . La loss è la cross-entropy , dove è il vettore one-hot della classe vera — tutto zeri tranne un 1 nella posizione della classe corretta. È lo schema con cui si addestra, alla lettera, la testa di un transformer: i logit sono l’uscita dell’ultimo layer, la softmax li trasforma in una distribuzione sul vocabolario, la cross-entropy confronta quella distribuzione con il token effettivamente osservato.
Derivare rispetto a tenendo i due passaggi separati è laborioso. Servirebbe la matrice Jacobiana della softmax — una matrice con sulla diagonale e fuori diagonale — e poi il gradiente della cross-entropy rispetto a , e infine il prodotto dei due secondo la regola della catena. Ma quando si compongono, quasi tutti i termini si elidono.
Conviene vedere la cancellazione almeno in abbozzo, perché spiega perché la formula è così pulita e non è una coincidenza. Poiché è one-hot, la somma ha un solo termine non nullo: se la classe vera è , allora . Derivando rispetto al logit tramite la regola della catena, e usando la Jacobiana della softmax, il termine diagonale e i termini fuori diagonale si combinano in modo che il fattore della cross-entropy si elide con i dentro la Jacobiana. Quello che resta, componente per componente, è : la probabilità predetta per la classe meno l’indicatore della classe vera.
La cancellazione non è fortuna: è una scelta di design ripagata. La softmax e la cross-entropy sono state accoppiate proprio perché la composizione delle due ha questa derivata semplice. Se si usasse la softmax con una loss diversa — per esempio l’errore quadratico — la cancellazione non avverrebbe, il gradiente conterrebbe i termini scomodi della Jacobiana della softmax, e l’addestramento sarebbe più lento e meno stabile. È un esempio di come la conoscenza delle derivate guidi le scelte di architettura: si sceglie la coppia loss-attivazione anche per come si comporta il suo gradiente, non solo per cosa misura. In forma vettoriale, la cancellazione dà:
Il gradiente sui logit è semplicemente la probabilità predetta meno il bersaglio. Un conto rapido: tre classi, la vera è la prima, quindi ; il modello predice . Il gradiente è . La prima componente è negativa: l’update spingerà a salire la probabilità della classe corretta (la discesa del gradiente sottrae il gradiente, e sottrarre un numero negativo aumenta). Le altre due sono positive: l’update abbasserà le probabilità delle classi sbagliate. La somma delle componenti è zero, e non è un caso — sia sia sommano a 1, quindi la loro differenza somma a 0.
Se il modello è già perfetto, e il gradiente è nullo; più è lontano da , più il gradiente è grande, e punta esattamente nella direzione che corregge l’errore. Questa cancellazione è la ragione per cui softmax e cross-entropy si usano quasi sempre in coppia: il gradiente della coppia è stabile, economico, privo di termini che possono esplodere o annullarsi spuriamente. I framework lo sanno e implementano la coppia come un’unica operazione fusa — CrossEntropyLoss in PyTorch include la softmax al suo interno, proprio per sfruttare questa semplificazione e per evitare problemi di stabilità numerica che nascerebbero calcolando i due pezzi separatamente.
Applicazioni pratiche
Sezione intitolata “Applicazioni pratiche”Le identità di questo capitolo non si applicano a mano tutti i giorni — per quello c’è l’autograd. Ma sapere che esistono e come funzionano cambia tre attività quotidiane di chi costruisce sistemi AI.
La prima è il debugging delle shape in un training loop. Quando si scrive a mano un pezzo di pipeline — un layer custom, un loop di addestramento didattico, un kernel ottimizzato — l’errore più frequente in assoluto è una trasposta mancante o di troppo nel passo all’indietro. Il sintomo è un errore di dimensione, spesso lontano dalla causa, e senza una bussola si finisce a provare a caso .T qua e là finché non smette di crashare — un approccio che a volte “funziona” producendo un gradiente sbagliato che non crasha. La regola di forma è la bussola: il gradiente di W deve avere la forma di W, quello di X la forma di X. Con quella regola l’errore si individua per ragionamento, come nell’Esempio 3, senza nemmeno rieseguire il codice.
La seconda è leggere i paper. La letteratura di deep learning scrive la backpropagation, gli update e le derivazioni delle loss in forma matriciale compatta, e dà per scontate le identità di questo capitolo. Una riga come “the gradient of the loss with respect to the weights is ” è densissima: contiene la composizione di softmax e cross-entropy, la convenzione di layout, e la struttura del layer lineare, tutto compresso in cinque simboli. Chi conosce le identità la decomprime al volo; chi non le conosce la salta, e con essa salta il cuore tecnico del paper.
La terza è scrivere loss e layer su misura. Ci sono casi in cui l’autograd non basta: un’operazione che il framework non copre nativamente, un kernel CUDA da ottimizzare a mano, un layer la cui derivata va calcolata in forma chiusa per ragioni di velocità o stabilità numerica. In quei casi il passo all’indietro va scritto, e scriverlo vuol dire derivare uno scalare rispetto a una matrice — esattamente l’operazione di questo capitolo, con il metodo del differenziale come strumento. Lo stesso vale fuori dalle reti neurali: la stima dei parametri di una gaussiana multivariata, l’ottimizzazione di una matrice di covarianza, la derivazione di uno stimatore di massima verosimiglianza in un modello statistico sono tutti problemi di derivata matriciale, e il metodo è lo stesso.
Dove si rompe
Sezione intitolata “Dove si rompe”Le derivate matriciali sono solide come matematica, ma il modo in cui le si usa nella pratica ha diversi punti in cui ci si fa male. Vale la pena elencarli, perché conoscere il difetto è metà del debugging.
Mescolare i due layout. È l’errore numero uno, e il suo sintomo è quasi sempre lo stesso: una trasposta di troppo o una di meno, che si manifesta come un errore di dimensione qualche riga dopo, lontano dalla causa. La trappola è insidiosa perché ciascuna delle due convenzioni, presa da sola, è coerente: l’errore nasce solo quando si copia una formula da una fonte in layout numeratore in un calcolo che procede in layout denominatore. L’unico rimedio davvero affidabile è dichiarare la convenzione all’inizio — qui: il gradiente ha la forma del parametro — e non cambiarla mai dentro una derivazione.
Dimenticare la trasposta nella derivata della traccia. Molti si aspettano che sia , e invece è . La trasposta non è un capriccio: nasce dalla regola di identificazione. Il differenziale è ; per leggere il gradiente lo si deve portare nella forma canonica , e confrontando i due si ha , quindi . Chi salta il passaggio per la forma canonica perde la trasposta e non capisce perché il codice non funziona.
Confondere il gradiente con la Jacobiana. Sono due oggetti diversi con due ruoli diversi. Il gradiente ha la forma del parametro ed è l’oggetto che si sottrae nell’update di addestramento. La Jacobiana è l’oggetto che compone nella regola della catena. Per una funzione scalare i due coincidono a meno di una trasposta, e questa coincidenza è proprio ciò che genera la confusione: si prende l’abitudine di trattarli come la stessa cosa, e poi, su una funzione a uscita vettoriale dove sono genuinamente diversi, si sbaglia il verso della composizione.
Derivare componente per componente e riassemblare a occhio. Funziona benissimo su esempi piccoli — due variabili, una matrice — e fallisce non appena la formula si complica, perché il momento del riassemblaggio è dove si decide “a sentimento” dove vanno le trasposte. Il metodo del differenziale non ha questo momento: la forma canonica esibisce il gradiente già disposto. Se ci si trova a riassemblare a occhio, si sta usando lo strumento sbagliato.
Il fattore 2 e il caso non simmetrico. Due sviste classiche sulle forme quadratiche. La prima: dimenticare il fattore 2, scrivendo invece di . La seconda, più sottile: scrivere quando non è simmetrica. La formula generale è , e si riduce a soltanto quando . In molti contesti ML la matrice in gioco è effettivamente simmetrica — una matrice di covarianza, una Hessiana — e l’abitudine a scrivere diventa automatica; il giorno in cui la matrice non è simmetrica, l’automatismo sbaglia.
L’ordine dei fattori in una catena. Le matrici non commutano: e sono in generale diverse, e spesso non hanno nemmeno la stessa forma. In una composizione di più funzioni la regola della catena impone un ordine preciso ai fattori, e invertirlo non dà un risultato “quasi giusto”, dà un risultato sbagliato o un errore di dimensione. È la versione matriciale di un errore che nel calcolo scalare non esiste — lì è uguale a , perché i numeri commutano. Chi porta nel caso matriciale l’abitudine scalare di “tanto l’ordine non conta” sbaglia sistematicamente. Il metodo del differenziale protegge anche da questo: sostituendo i differenziali l’uno nell’altro, l’ordine dei fattori viene fuori da solo, corretto.
Confondere la convenzione con . Anche fissato il layout, resta una seconda scelta: gli esempi del batch in riga o in colonna. È una scelta indipendente dal layout, e mescolarla dentro una derivazione produce gli stessi errori di trasposta. Diversi tutorial e diverse librerie usano convenzioni opposte, e copiare una formula da un tutorial in riga dentro un codice scritto in colonna è una sorgente di bug subdola, perché entrambe le convenzioni, prese da sole, sono corrette. Vale la stessa regola di igiene del layout: una convenzione sola per derivazione, dichiarata.
Il limite di fondo: l’autograd nasconde, non insegna. C’è una rottura più concettuale che tecnica. I framework di automatic differentiation calcolano i gradienti perfettamente, e questo toglie la necessità di derivare a mano — ma toglie anche l’occasione di imparare le identità. Si finisce per saper addestrare un modello senza saper leggere il gradiente che lo addestra. Finché tutto funziona non è un problema; nel momento in cui un layer custom dà risultati sbagliati, o un paper va letto in dettaglio, o un kernel a basso livello va scritto, l’autograd non aiuta più, e la conoscenza che non si è costruita serve tutta in una volta. Questo capitolo è, in un certo senso, l’assicurazione contro quel momento.
Un filo lega tutti questi punti di rottura: nessuno di essi è un difetto della matematica delle derivate matriciali, che è solida e priva di ambiguità. Sono tutti difetti del modo in cui la matematica viene trascritta — convenzioni mescolate, trasposte saltate, ordini invertiti, scorciatoie applicate fuori dalle loro ipotesi. La conseguenza pratica è incoraggiante: non serve diffidare delle identità, serve essere disciplinati nell’usarle. Fissare una convenzione e tenerla, derivare con il metodo del differenziale invece di copiare formule da fonti con notazione ignota, e validare con il gradient checking prima di fidarsi. Tre abitudini, e la maggior parte dei bug di gradiente non nasce nemmeno.
Collegamenti
Sezione intitolata “Collegamenti”- Analisi matematica: limiti, continuità, derivate — la derivata scalare e la regola della catena di base, di cui il calcolo differenziale matriciale è la generalizzazione diretta.
- Derivate parziali, gradienti, Jacobiani — derivate parziali, gradiente, Jacobiana e la backpropagation come prodotti vettore-Jacobiana: il prerequisito immediato di questo capitolo.
- Hessiana, curvatura, condizionamento — le derivate seconde rispetto a vettori e matrici, e i metodi di ottimizzazione del secondo ordine.
- Convessità, Jensen, minimi globali — perché, per la loss OLS che è convessa, annullare il gradiente individua il minimo globale e non un minimo locale.
- Matrici come trasformazioni — le matrici viste come trasformazioni lineari, l’oggetto rispetto a cui qui si deriva.
- Prodotto scalare come proiezione e somiglianza — la forma canonica è un prodotto scalare tra gradiente e variazione.
- Discesa del gradiente: SGD, momentum, Adam — l’update che consuma il gradiente matriciale prodotto qui.
- Dalla somma alla probabilità: softmax e sigmoid — la softmax di cui l’ultimo esempio deriva il gradiente.
- Entropia, cross-entropy, KL divergence — la cross-entropy come loss di classificazione.
- Vettori, spazi vettoriali, intuizione geometrica — i vettori colonna e riga su cui poggia tutta la notazione del capitolo.
- regressione-lineare (in preparazione) — OLS, equazioni normali e regressione come modello, di cui qui si dà il gradiente.
- mlp-backprop (in preparazione) — la backpropagation di un MLP completo, di cui questo capitolo fornisce l’algebra layer per layer.
- maximum-likelihood (in preparazione) — la stima di massima verosimiglianza, dove le derivate rispetto a matrici di covarianza usano le identità di traccia e log-determinante.
Per andare oltre
Sezione intitolata “Per andare oltre”- Jan R. Magnus, Heinz Neudecker, Matrix Differential Calculus with Applications in Statistics and Econometrics (Wiley, edizione riveduta 2019). Il testo canonico del metodo del differenziale e della regola di identificazione. Il capitolo 18 è disponibile online sul sito di Magnus.
- Jan R. Magnus, Matrix Derivatives: Why and Where Did It Go Wrong? (saggio, scaricabile da janmagnus.nl). Una lettura breve e polemica sulle convenzioni di layout e sugli errori storici nel definire la derivata matriciale.
- Kaare Brandt Petersen, Michael Syskind Pedersen, The Matrix Cookbook (technical report, ultima edizione 2012). Il cheat sheet più citato: tutte le identità pronte all’uso, da tenere accanto come tabella di consultazione.
- Appendix A: Matrix Calculus, note del corso MIT 6.390 Intro to Machine Learning (introml.mit.edu). Le stesse identità presentate in chiave esplicitamente ML, con la convenzione del layout denominatore.
- Eli Bendersky, Backpropagation through a fully-connected layer (2018) e The Softmax function and its derivative (2016), sul blog dell’autore. Due derivazioni dettagliate e verificabili dei gradienti del layer lineare e della coppia softmax + cross-entropy.