Derivate parziali, gradienti, Jacobiani
Una funzione di una sola variabile è l’eccezione, non la regola. La loss di una rete neurale dipende da milioni di pesi insieme; la posizione di un braccio robotico da più angoli di giuntura. Questo capitolo estende la derivata in due direzioni — più variabili in ingresso, più variabili in uscita — e arriva alla matrice Jacobiana. Da lì il bersaglio: mostrare che la backpropagation non moltiplica matrici Jacobiane, ne moltiplica una sola colonna alla volta, e che loss.backward() è una sequenza di prodotti vettore-Jacobiana letti a ritroso.
Perché questo capitolo
Sezione intitolata “Perché questo capitolo”Il capitolo precedente, Analisi matematica: limiti, continuità, derivate, ha costruito la derivata per funzioni con un ingresso e un’uscita: una funzione R -> R, un numero che entra, un numero che esce, e una pendenza f'(x) che dice quanto velocemente il secondo cambia al variare del primo. È un caso pulito, e in pratica è raro.
Quasi tutto ciò che si ottimizza nel machine learning ha molti ingressi. La funzione di errore di un modello — la loss — dipende contemporaneamente da tutti i pesi della rete, che oggi si contano in miliardi. Appena le variabili sono più di una, la domanda “qual è la pendenza” diventa ambigua: pendenza rispetto a quale variabile, lungo quale direzione? La derivata parziale, il gradiente e la derivata direzionale sono i tre strumenti che disambiguano quella domanda. Sono il primo argomento di questo capitolo.
C’è un secondo salto, ortogonale al primo. Anche l’uscita può avere più componenti. Uno strato lineare di una rete prende un vettore e ne produce un altro: è una funzione da R^n a R^m. La sua derivata non è più un numero, e nemmeno un vettore: è una matrice, la matrice Jacobiana. E la regola della catena — il motore della backpropagation, già incontrato nella sua forma scalare — applicata a una composizione di funzioni vettoriali diventa un prodotto di matrici Jacobiane. Capire questo prodotto, e soprattutto capire perché in pratica non lo si calcola mai per intero, è la differenza tra trattare l’addestramento come un incantesimo e capire perché un modello sta o non sta nella memoria di una GPU.
Chi salta questo capitolo può comunque seguire la wiki. Chi lo legge smette di vedere loss.backward() come una riga magica e comincia a vederla per quello che è: una catena di derivate multivariabili, percorsa nell’unico ordine che la rende economica.
Contesto
Sezione intitolata “Contesto”Questo capitolo poggia su due fondamenta già posate altrove nella wiki, e conviene dichiararle subito.
La prima è il capitolo precedente di questa Parte. Da lì arrivano la derivata come limite del rapporto incrementale, la regola della catena scalare — (f ∘ g)'(x) = f'(g(x)) · g'(x), dove i tassi di trasmissione si moltiplicano lungo la catena — e l’idea dello zoom: una funzione liscia, vista abbastanza da vicino, è indistinguibile da una retta. Tutto questo capitolo è la stessa idea, portata in più dimensioni.
La seconda fondamenta è la Parte IV. Il capitolo Gradienti e derivate direzionali senza analisi ha già introdotto il gradiente “a intuito”, come la freccia che indica la direzione di massima salita su un paesaggio. Qui quell’intuizione viene messa sotto le fondamenta formali: il gradiente smette di essere una freccia disegnata e diventa il vettore delle derivate parziali, con una definizione e delle proprietà dimostrabili. Serviranno anche i vettori (Vettori, spazi vettoriali, intuizione geometrica), il prodotto scalare (Prodotto scalare come proiezione e somiglianza) e le matrici viste come trasformazioni (Matrici come trasformazioni).
Sul versante storico, una sola figura va nominata. La matrice Jacobiana prende il nome da Carl Gustav Jacob Jacobi (1804-1851, matematico tedesco, uno dei fondatori della teoria delle funzioni ellittiche e dei determinanti). Jacobi studiò sistematicamente, negli anni 1830 e 1840, le matrici di derivate parziali che compaiono nel cambio di variabili degli integrali multipli; il loro determinante — quello che oggi chiamiamo determinante jacobiano — misura come una trasformazione dilata o comprime i volumi. La matrice intera, e non solo il suo determinante, è poi diventata lo strumento standard per parlare della derivata di una funzione vettoriale. Il calcolo differenziale di più variabili non nasce con un singolo nome o una singola data: è la generalizzazione naturale, distribuita lungo l’Ottocento, del calcolo a una variabile che la storia raccontata nel capitolo precedente aveva appena reso rigoroso.
C’è anche una storia più recente, ed è quella della differenziazione automatica. Per decenni, calcolare i gradienti di un programma è stato un lavoro o manuale — derivare a mano, riga per riga, con tutti gli errori del caso — o numerico, per differenze finite, con i problemi di precisione che il capitolo precedente ha descritto. La differenziazione automatica in modalità reverse, cioè la backpropagation generalizzata, era nota in forma matematica già dagli anni Settanta, ma è diventata infrastruttura quotidiana solo con i framework moderni: Theano e poi TensorFlow, PyTorch, JAX. Il salto non è stato matematico — la regola della catena è la stessa di Leibniz — ma ingegneristico: rendere automatica, affidabile e veloce la costruzione della catena di prodotti vettore-Jacobiana per un grafo di calcolo arbitrario. Questo capitolo spiega la matematica che quei framework implementano.
L’ordine del capitolo segue la dipendenza logica dei concetti: prima la derivata parziale (un ingresso alla volta), poi il gradiente (tutte le parziali insieme), poi la derivata direzionale (una direzione qualsiasi), poi la Jacobiana (anche l’uscita diventa multipla), infine la regola della catena multivariata e la sua applicazione, la backpropagation.
L’intuizione: la derivata parziale
Sezione intitolata “L’intuizione: la derivata parziale”Prendi una funzione di due variabili, per esempio
Questa funzione non ha “una” derivata. La domanda “quanto vale la pendenza” è incompleta: pendenza muovendosi lungo quale variabile? Se cammini variando solo x, vedi una pendenza; se cammini variando solo y, ne vedi un’altra. Ogni variabile ha la sua derivata, e si chiama derivata parziale.
Angolo 1 — tagliare la superficie a fette
Sezione intitolata “Angolo 1 — tagliare la superficie a fette”Immagina il grafico di f(x, y) come un paesaggio collinare: per ogni coppia (x, y) sul pavimento, l’altezza del terreno è z = f(x, y).
La derivata parziale rispetto a x si ottiene così. Fissi un valore di y — diciamo y = 2 — e lo tieni inchiodato. Quello che resta, f(x, 2) = x^2 + 6x, è una funzione di una sola variabile: è il profilo del terreno lungo una fetta verticale del paesaggio, la fetta parallela all’asse x all’altezza y = 2. La derivata parziale rispetto a x è semplicemente la pendenza ordinaria di quel profilo, esattamente la derivata del capitolo precedente.
Lo stesso per y: congeli x, tagli il paesaggio con una fetta parallela all’asse y, e misuri la pendenza di quel profilo. La derivata parziale è sempre la pendenza di una fetta, mai della superficie intera.
Il simbolo per la derivata parziale è un d arrotondato, , per distinguerla dalla d dritta della derivata ordinaria. Si scrive e si legge “derivata parziale di f rispetto a x”.
Angolo 2 — la console di manopole
Sezione intitolata “Angolo 2 — la console di manopole”Un secondo angolo, più operativo. Pensa a f come a una scatola con n manopole sul pannello frontale e un display che mostra un numero. Ogni manopola è una variabile di ingresso; il display è f.
La derivata parziale rispetto alla manopola i risponde a una domanda di sensibilità: se ruoto di un filo solo quella manopola, tenendo tutte le altre esattamente dove sono, di quanto e in che verso si muove il numero sul display?
È una misura locale, e tiene fermo tutto il resto. Questa clausola — “tenendo tutte le altre ferme” — è il cuore del concetto e anche la sua trappola, su cui torneremo.
La regola di calcolo
Sezione intitolata “La regola di calcolo”In pratica calcolare una derivata parziale non richiede niente di nuovo. La ricetta è: tratta tutte le altre variabili come se fossero costanti, e deriva normalmente rispetto a quella che ti interessa, usando le regole di derivazione già note.
Per f(x, y) = x^2 + 3xy:
- Per , la
yè una costante. La derivata di è ; la derivata di , dove è una costante che moltiplica , è . Quindi . - Per , la
xè una costante. Il termine non contieney, quindi è una costante e la sua derivata è ; il termine , dove è costante, ha derivata . Quindi .
La definizione formale, per chi vuole vederla, è il limite del rapporto incrementale del capitolo precedente con le altre coordinate congelate:
L’unica differenza rispetto alla derivata ordinaria è che la seconda coordinata b resta inchiodata mentre solo la prima riceve l’incremento h. Tutto il resto — il limite, il fatto che h tende a zero senza mai valere zero, l’interpretazione come pendenza — è identico.
Derivare due volte: le parziali seconde
Sezione intitolata “Derivare due volte: le parziali seconde”Una derivata parziale è essa stessa una funzione di più variabili, e niente vieta di derivarla di nuovo. Da si può derivare ancora rispetto a x, ottenendo la parziale seconda , oppure rispetto a y, ottenendo la parziale mista . Per f(x, y) = x^2 + 3xy, partendo da : la parziale seconda pura è , e la mista è .
C’è un fatto non ovvio e molto comodo, noto come teorema di Schwarz (o di Clairaut): per le funzioni lisce — quelle con derivate seconde continue, cioè praticamente tutte quelle che si incontrano nel machine learning — l’ordine di derivazione non conta. Derivare prima rispetto a x e poi a y dà lo stesso risultato che derivare prima rispetto a y e poi a x: . Nel nostro esempio entrambe valgono , e non è un caso. Questa simmetria è la ragione per cui la matrice che raccoglie tutte le parziali seconde — la matrice Hessiana — è simmetrica, una proprietà su cui si appoggiano i metodi di ottimizzazione del secondo ordine. Le parziali seconde non sono il fuoco di questo capitolo: sono il tema di hessiane-curvatura (in preparazione). Qui basta sapere che esistono e che si calcolano applicando due volte la stessa ricetta del congelamento.
L’intuizione: il gradiente
Sezione intitolata “L’intuizione: il gradiente”Se ogni variabile ha la sua derivata parziale, è naturale raccoglierle tutte in un solo oggetto. Quel raccoglitore è il gradiente.
Il gradiente di f, scritto (il simbolo si chiama nabla, un triangolo capovolto), è semplicemente il vettore che impila tutte le derivate parziali:
Per la nostra f(x, y) = x^2 + 3xy, che ha due variabili:
Il gradiente non è un numero fisso: è un oggetto per punto. Valutato nel punto (1, 2) vale ; in un altro punto vale un altro vettore. In ogni punto del dominio c’è una freccia gradiente diversa.
Una precisazione che evita un fraintendimento tenace. Il gradiente non vive sulla superficie del grafico. Vive nel dominio — nel piano (x, y), il pavimento su cui poggia il paesaggio. È una freccia disegnata sulla mappa topografica vista dall’alto, non una freccia appiccicata al fianco della collina. Tre derivate parziali, tre componenti del vettore, ma il vettore sta nel piano degli ingressi.
Il gradiente ha tre proprietà che lo rendono lo strumento centrale dell’ottimizzazione. Le enuncio qui e le dimostro a intuito nella sezione sulla derivata direzionale, perché è lì che si vede perché sono vere.
Proprietà 1 — direzione di massima crescita. Tra tutte le direzioni in cui puoi muoverti partendo da un punto, quella indicata dal vettore gradiente è quella lungo cui f cresce più rapidamente. La direzione opposta, , è quella di discesa più rapida. Questa proprietà è la ragione per cui la discesa del gradiente, l’algoritmo che addestra i modelli, si muove proprio verso : per minimizzare la loss si va nella direzione di massima discesa.
Proprietà 2 — la lunghezza misura la ripidità. La norma , cioè la lunghezza della freccia gradiente, dice quanto è ripida la collina nella sua direzione di massima crescita. Un gradiente lungo significa pendenza forte; un gradiente di lunghezza zero significa terreno piatto — un minimo, un massimo, una sella o un plateau.
Proprietà 3 — ortogonalità alle curve di livello. Una curva di livello è l’insieme dei punti dove f assume un valore costante: sono le isoipse di una mappa topografica, le linee che uniscono i punti alla stessa quota. Il gradiente in un punto è sempre perpendicolare alla curva di livello che passa per quel punto. L’intuizione è semplice: lungo una curva di livello la funzione, per definizione, non cambia; la direzione lungo cui non cambia è ortogonale alla direzione lungo cui cambia di più. La salita più ripida è sempre perpendicolare ai “gradini” della mappa.
Un’osservazione che collega le tre proprietà. La discesa del gradiente, l’algoritmo di addestramento, non fa altro che applicarle ripetutamente: a ogni passo calcola nel punto corrente, ne legge la direzione (proprietà 1) e la lunghezza (proprietà 2), fa un piccolo passo verso , e ripete. Il fatto che ci si muova perpendicolarmente alle curve di livello (proprietà 3) ha una conseguenza visibile: su un paesaggio a forma di valle stretta e allungata, il gradiente punta quasi sempre verso il fianco ripido invece che lungo il fondovalle, e la traiettoria dell’optimizer zigzaga invece di scendere dritta. È un limite noto della discesa del gradiente “pura”, e una delle ragioni per cui esistono varianti come il momentum — un argomento del capitolo Discesa del gradiente: SGD, momentum, Adam.
La meccanica: la derivata direzionale
Sezione intitolata “La meccanica: la derivata direzionale”La derivata parziale risponde solo per le direzioni degli assi: lungo x, lungo y. Ma da un punto ci si può muovere in qualunque direzione, anche in diagonale. La derivata direzionale è lo strumento che misura la pendenza lungo una direzione arbitraria.
Una direzione si rappresenta con un vettore unitario u, cioè un vettore di lunghezza esattamente (la lunghezza unitaria serve perché vogliamo misurare la pendenza, non confonderla con la velocità a cui ci muoviamo). La derivata direzionale di f in direzione u, scritta , è la pendenza di f se ci si incammina lungo u.
Il fatto centrale, che lega tutto, è questo: la derivata direzionale è il prodotto scalare tra il gradiente e la direzione.
In parole povere, questo dice che la pendenza lungo u è la proiezione del gradiente sulla direzione u. Il gradiente contiene già tutta l’informazione sulle pendenze in ogni direzione; per estrarre quella di una direzione specifica, si proietta. Il prodotto scalare è esattamente l’operazione di proiezione, come visto in Prodotto scalare come proiezione e somiglianza.
Conviene fermarsi un attimo su quanto sia economico questo fatto. Per conoscere la pendenza lungo qualunque delle infinite direzioni possibili, non serve fare infiniti calcoli: basta calcolare un solo oggetto, il gradiente, e poi per ogni direzione fare un prodotto scalare. Il gradiente è una specie di riassunto comprimente di tutte le pendenze: n numeri da cui si recupera la pendenza in ogni direzione con un’operazione banale. È la stessa economia che rende utile il gradiente nell’ottimizzazione — un solo oggetto da calcolare, e tutte le direzioni di discesa diventano interrogabili.
Da questa singola formula cadono fuori le proprietà 1 e 3 del gradiente, senza bisogno di altro. Il prodotto scalare si può riscrivere usando l’angolo tra il gradiente e la direzione:
L’ultimo passaggio usa , perché u è unitario per costruzione. Ora leggi la formula come funzione dell’angolo θ:
- è massima quando , cioè quando : la direzione
uè perfettamente allineata col gradiente. È la proprietà 1: la direzione di massima crescita è quella del gradiente, e il valore di quella crescita massima è proprio , la lunghezza del gradiente — il che è anche la proprietà 2. - è zero quando , cioè quando : la direzione
uè perpendicolare al gradiente. Pendenza zero significa chefnon cambia: ci si sta muovendo lungo una curva di livello. È la proprietà 3: la direzione ortogonale al gradiente è quella tangente alla curva di livello. - è minima (la più negativa) quando , cioè :
upunta in verso opposto al gradiente. È la direzione di discesa più ripida, .
Una sola formula, , e le tre proprietà del gradiente diventano conseguenze invece di affermazioni da credere sulla parola.
Un calcolo concreto
Sezione intitolata “Un calcolo concreto”Riprendiamo f(x, y) = x^2 + 3xy nel punto (1, 2), dove il gradiente vale .
Verifichiamo prima la coerenza con le derivate parziali. Lungo l’asse x la direzione è : , che è esattamente . Lungo l’asse y, : , esattamente . Le derivate parziali sono il caso particolare della derivata direzionale lungo gli assi.
Ora una direzione diagonale. La direzione “in alto a destra a 45 gradi” è , ma ha lunghezza , non ; la normalizziamo dividendo per la sua lunghezza, ottenendo . La derivata direzionale è
E la direzione di massima crescita? È quella del gradiente stesso, normalizzato: . Lungo di essa la pendenza vale . Nessuna direzione fa meglio: è il massimo, della diagonale gli si avvicina ma resta sotto.
La meccanica: la matrice Jacobiana
Sezione intitolata “La meccanica: la matrice Jacobiana”Fin qui l’uscita di f era sempre un singolo numero: funzioni da R^n a R. Il salto successivo è ammettere che anche l’uscita sia un vettore: funzioni da R^n a R^m, che prendono n numeri e ne producono m.
Questo non è un caso di nicchia. Uno strato lineare di una rete neurale è proprio una funzione di questo tipo: prende un vettore di attivazioni e ne produce un altro, . Una trasformazione di coordinate, da polari a cartesiane, è una funzione da R^2 a R^2. Un encoder che mappa un’immagine in un embedding è una funzione da R^n (i pixel) a R^m (le componenti dell’embedding).
Qual è la derivata di una funzione del genere? Ogni componente dell’uscita, presa singolarmente, è una funzione scalare R^n -> R: ha quindi il suo gradiente, un vettore di n derivate parziali. Una funzione F con m componenti di uscita ha quindi m gradienti. La matrice Jacobiana li impila tutti, ciascuno come una riga:
L’elemento in posizione (i, j) è : la derivata parziale della i-esima uscita rispetto alla j-esima variabile di ingresso.
La forma della matrice si legge in due modi, ed entrambi sono utili:
- Riga per riga. La riga
iè il gradiente dellai-esima componente di uscita. Dice come quella singola uscita reagisce al variare di tutti gli ingressi. - Colonna per colonna. La colonna
jraccoglie le derivate di tutte le uscite rispetto al solo ingressoj. Dice come l’intero vettore di uscita reagisce al muovere la sola variabilej.
Le dimensioni: m righe (una per componente di uscita), n colonne (una per variabile di ingresso). Una matrice m × n.
La Jacobiana unifica tutto
Sezione intitolata “La Jacobiana unifica tutto”La cosa più importante da capire della Jacobiana è che non è un oggetto nuovo accanto alla derivata e al gradiente: è l’oggetto che li contiene entrambi come casi particolari.
- Se
m = 1, l’uscita è uno scalare. La Jacobiana ha una sola riga, e quella riga è il gradiente. Il gradiente è la Jacobiana di una funzione a valori scalari, scritta come vettore riga. - Se
n = 1em = 1, ingresso e uscita sono entrambi scalari. La Jacobiana è una matrice1 × 1, cioè un singolo numero: la derivata ordinariaf'(x)del capitolo precedente.
Derivata di una variabile, gradiente, matrice Jacobiana sono la stessa idea a tre livelli crescenti di generalità. La Jacobiana è “la” derivata nel caso più generale; gli altri due sono ciò che resta quando si restringono le dimensioni.
C’è un’interpretazione che lega la Jacobiana all’idea dello zoom del capitolo precedente. Lì, una funzione liscia R -> R, vista abbastanza da vicino, diventava indistinguibile da una retta, e la derivata era la pendenza di quella retta. Lo stesso vale qui: una funzione vettoriale liscia, vista abbastanza da vicino attorno a un punto a, diventa indistinguibile da una trasformazione lineare — e una trasformazione lineare è una matrice, come visto in Matrici come trasformazioni. La Jacobiana è quella matrice. In formula, per uno spostamento piccolo dx attorno ad a:
In parole: vicino al punto a, applicare la funzione complicata F equivale, con buona approssimazione, ad applicare la matrice J(a) allo spostamento. La Jacobiana è la migliore approssimazione lineare locale della funzione vettoriale.
Un’ultima nota, di passaggio. Quando n = m, la Jacobiana è quadrata e ha un determinante. Il determinante jacobiano misura quanto la trasformazione dilata o comprime un volumetto attorno al punto: è il fattore di correzione che compare nei cambi di variabile degli integrali e nei normalizing flow (modelli generativi che trasformano una distribuzione semplice in una complicata). Non è centrale per questo capitolo, ma è la ragione storica per cui Jacobi studiò queste matrici.
La regola della catena multivariata
Sezione intitolata “La regola della catena multivariata”Nel capitolo precedente la regola della catena per funzioni di una variabile diceva: la derivata di una composizione è il prodotto delle derivate locali. Per le funzioni vettoriali vale una regola identica nella forma — basta sostituire “numeri” con “matrici” e “moltiplicazione di numeri” con “moltiplicazione di matrici”.
Siano g: R^n -> R^m e f: R^m -> R^k due funzioni vettoriali, e sia h = f ∘ g la loro composizione (prima si applica g, poi f). La regola della catena multivariata dice:
La Jacobiana della composizione è il prodotto delle due Jacobiane. Le dimensioni si incastrano esattamente: è k × m, è m × n, il prodotto è k × n, che è giusto la forma della Jacobiana di una funzione da R^n a R^k. Vale la pena notare dove si valuta ciascuna Jacobiana: nel punto di ingresso x, ma nel punto — cioè dove g ha già portato l’ingresso. È la stessa cosa che nel caso scalare obbligava a conoscere il valore intermedio: per moltiplicare le derivate locali bisogna prima sapere a quale punto applicarle. Questo dettaglio, apparentemente minore, è la ragione per cui la backpropagation ha bisogno di un passaggio in avanti che calcoli e memorizzi tutti i punti intermedi prima di poter iniziare il passaggio all’indietro.
Un fraintendimento da spegnere subito: il prodotto di matrici non è commutativo, e l’ordine in è obbligato. La Jacobiana esterna sta a sinistra, l’interna a destra — lo stesso ordine in cui le funzioni si applicano leggendo da destra. Invertire i due fattori, oltre a essere matematicamente sbagliato, di solito produce dimensioni che non combaciano e l’errore si scopre subito; quando per caso le dimensioni tornano (matrici quadrate), l’errore è silenzioso e velenoso.
La regola si estende a composizioni di qualunque lunghezza. Se F è una catena di L funzioni, , la sua Jacobiana è il prodotto di L matrici Jacobiane:
Questa formula merita di essere fissata, perché è la struttura matematica esatta di una rete neurale profonda. Una rete è una composizione di funzioni vettoriali — strato lineare, funzione di attivazione, strato lineare, attivazione, e così via per decine di livelli. La sua derivata totale, quella che serve per addestrarla, è il prodotto delle Jacobiane di tutti gli strati. Tutto il resto di questo capitolo è il commento operativo a questa singola riga.
Perché non si materializza mai la Jacobiana completa
Sezione intitolata “Perché non si materializza mai la Jacobiana completa”C’è un problema pratico, grosso, nascosto nella formula . Le matrici Jacobiane degli strati sono enormi.
Facciamo il conto. Uno strato che mappa un vettore di dimensioni in un altro vettore di dimensioni ha una Jacobiana di forma : cento milioni di numeri. In precisione singola (float32, quattro byte per numero) sono circa MB. Per un solo strato, e per un solo esempio del batch di addestramento. Una rete ha decine di strati e si addestra su batch di centinaia di esempi. Materializzare in memoria tutte queste matrici è semplicemente fuori discussione: non ci starebbero in nessuna GPU.
La via d’uscita è un’osservazione tanto semplice quanto liberatoria: non serve mai la Jacobiana come matrice. Serve solo il risultato del prodotto della Jacobiana per un vettore. E quel prodotto si può calcolare direttamente, con una formula chiusa specifica per ogni tipo di strato, senza mai costruire la matrice intermedia.
Ci sono due prodotti possibili, e la distinzione tra i due è il bivio concettuale di tutta la differenziazione automatica.
Jacobian-vector product (JVP). È il prodotto , la matrice Jacobiana per un vettore-colonna v. Risponde alla domanda: dato uno spostamento v dell’ingresso, come si muove l’uscita? Propaga una perturbazione in avanti, dall’ingresso verso l’uscita. È ciò che calcola il forward-mode autodiff.
Vector-Jacobian product (VJP). È il prodotto , un vettore-riga v per la Jacobiana — equivalentemente . Risponde alla domanda speculare: data una sensibilità v sull’uscita, come si distribuisce all’indietro sull’ingresso? Propaga una sensibilità all’indietro, dall’uscita verso l’ingresso. È ciò che calcola il reverse-mode autodiff.
La backpropagation è, alla lettera, una sequenza di VJP. L’addestramento parte dalla loss, che è un singolo numero — un’uscita in R^1. Il “vettore di sensibilità” iniziale è banalmente il numero : la derivata della loss rispetto a se stessa. Da lì la sensibilità viene propagata indietro, strato per strato. A ogni strato si esegue l’operazione , che trasforma il vettore di sensibilità dello strato corrente nel vettore di sensibilità dello strato precedente. Mai, in nessun passaggio, si costruisce esplicitamente una matrice Jacobiana: viaggia solo un vettore, che attraversa la rete a ritroso e a ogni strato viene trasformato dall’operatore Jacobiano implicito di quello strato — in pratica una formula chiusa, nota per ogni tipo di strato. La documentazione ufficiale di JAX (la libreria di differenziazione automatica di Google) lo dice in modo netto: “reverse mode is matrix-free: no intermediate Jacobian matrices are computed at any point” — il reverse-mode non costruisce alcuna matrice Jacobiana intermedia.
La frase da incidere è questa: la backpropagation non moltiplica matrici Jacobiane, moltiplica un vettore lungo una catena di operatori Jacobiani impliciti. Il prodotto di matrici della sezione precedente è la verità matematica; la sequenza di prodotti vettore-Jacobiana è il modo in cui quella verità viene calcolata senza esaurire la memoria.
Vale la pena vedere concretamente cosa significa “operatore implicito”. Prendi lo strato lineare : la sua Jacobiana è W (lo si è visto, e lo si rivedrà tra gli esempi), ma il VJP non ha bisogno di nominare la Jacobiana — propaga la sensibilità con , una moltiplicazione matrice-vettore. Prendi una funzione di attivazione che agisce componente per componente, come la ReLU: la sua Jacobiana è una matrice diagonale (la componente i dell’uscita dipende solo dalla componente i dell’ingresso), e moltiplicare per una matrice diagonale è solo moltiplicare elemento per elemento — il VJP della ReLU è , un mascheramento. In nessuno dei due casi si alloca una matrice n × n. Ogni tipo di strato porta con sé la formula chiusa del proprio VJP, e i framework di differenziazione automatica non fanno altro che incatenare queste formule seguendo il grafo delle operazioni del forward pass, percorso a ritroso.
Forward-mode contro reverse-mode
Sezione intitolata “Forward-mode contro reverse-mode”Forward-mode e reverse-mode calcolano lo stesso identico prodotto di Jacobiane, . La differenza è solo l’ordine in cui eseguono le moltiplicazioni. E poiché la moltiplicazione di matrici è associativa, il risultato finale è lo stesso — ma il costo per arrivarci no.
Un esempio numerico fuori dal contesto delle reti rende la cosa concreta. Supponi di dover moltiplicare tre matrici: una 1 × 1000, una 1000 × 1000, una 1000 × 1. Se associ da destra, , il primo prodotto è 1000 × 1000 per 1000 × 1, costoso ma gestibile; il risultato finale è uno scalare. Se associi da sinistra, , il primo prodotto è 1 × 1000 per 1000 × 1000: lavori su vettori, mai su matrici piene. L’ordine cambia tutto.
- Forward-mode associa il prodotto da destra, dall’ingresso. Propaga in avanti un vettore-tangente e costruisce la Jacobiana totale una colonna alla volta. Per ottenere tutte le
ncolonne servononpassaggi in avanti. Conviene quando il numero di ingressinè piccolo e quello di uscitemè grande: poche colonne da calcolare. - Reverse-mode associa il prodotto da sinistra, dall’uscita. Propaga all’indietro un vettore-cotangente e costruisce la Jacobiana una riga alla volta. Per ottenere tutte le
mrighe servonompassaggi all’indietro. Conviene quando il numero di uscitemè piccolo e quello di ingressinè grande.
Ora applica questo al training di una rete neurale. La loss è una funzione scalare — m = 1 — di milioni o miliardi di pesi — n enorme. La Jacobiana della loss rispetto ai pesi è una matrice larghissima e bassissima, di forma 1 × n: una sola riga. Reverse-mode la ottiene con un solo passaggio all’indietro. Forward-mode richiederebbe n passaggi, uno per ogni peso: impraticabile.
Ecco perché backpropagation è reverse-mode autodiff: è il metodo giusto per la forma “tantissimi ingressi, una sola uscita”, che è esattamente la forma di ogni problema di addestramento. La differenziazione automatica in modalità reverse e la backpropagation non sono due cose imparentate: la seconda è un caso particolare della prima, il caso in cui la funzione finisce in uno scalare.
Sul costo, un numero utile dalla documentazione di JAX: valutare un VJP (o un JVP) costa in operazioni circa tre volte il costo di valutare la funzione f stessa. Quindi calcolare il gradiente completo della loss rispetto a milioni di parametri costa circa quanto tre passaggi in avanti — un affare straordinario, ed è il motivo per cui l’addestramento di modelli enormi è computazionalmente possibile.
Il prezzo, però, reverse-mode lo paga in memoria. Per moltiplicare le Jacobiane a ritroso, ogni strato ha bisogno dei valori intermedi calcolati durante il passaggio in avanti — è la stessa ragione, vista nel capitolo precedente, per cui la regola della catena richiede di conoscere i valori intermedi della catena. Quindi il forward pass deve memorizzare le sue attivazioni e tenerle da parte fino al backward pass: la memoria richiesta cresce con la profondità della rete. Forward-mode, al contrario, non memorizza nulla (la sua memoria è indipendente dalla profondità) ma è inefficiente sulla forma delle loss. È un classico scambio: reverse-mode compra efficienza in operazioni pagando in memoria. La activation memory — la memoria occupata dalle attivazioni salvate per il backward — è una delle voci principali del budget di memoria nell’addestramento, e tecniche come il gradient checkpointing (ricalcolare alcune attivazioni invece di memorizzarle) esistono proprio per gestire questo scambio.
Un modo compatto per ricordare la regola di scelta: conta gli estremi della funzione. Se l’ingresso è “stretto” e l’uscita “larga” — pochi parametri, molte uscite — conviene il forward-mode, che lavora per colonne. Se l’ingresso è “largo” e l’uscita “stretta” — molti parametri, poche uscite — conviene il reverse-mode, che lavora per righe. Il numero di passaggi di differenziazione è governato dall’estremo stretto della funzione: n per il forward, m per il reverse. L’addestramento di una rete è il caso estremo della seconda situazione, e per questo non esiste alternativa seria al reverse-mode. È anche il motivo per cui calcolare la matrice Jacobiana intera di una funzione con ingresso e uscita entrambi larghi resta intrinsecamente costoso, qualunque modalità si scelga: nessun trucco elimina il fatto che una matrice grande ha tante righe e tante colonne. La fortuna del machine learning è che la forma “loss scalare” rende quel problema raro.
Sei esempi eterogenei: due numerici, uno geometrico, uno in codice, due legati direttamente al machine learning.
Esempio 1 — gradiente e derivata direzionale, calcolo numerico
Sezione intitolata “Esempio 1 — gradiente e derivata direzionale, calcolo numerico”Riprendiamo f(x, y) = x^2 + 3xy, già usata sopra, e completiamo il conto in un punto diverso, (2, -1).
Le derivate parziali sono e . Nel punto (2, -1):
La pendenza nella direzione di massima crescita è , lungo la direzione . La pendenza lungo la direzione opposta, , è : è la direzione che un optimizer sceglierebbe per scendere. E lungo qualunque direzione perpendicolare a (1, 6) — per esempio (6, -1)/√37 — la derivata direzionale è zero: ci si muove lungo la curva di livello, la quota non cambia.
Esempio 2 — la Jacobiana di uno strato lineare
Sezione intitolata “Esempio 2 — la Jacobiana di uno strato lineare”Uno degli esempi più istruttivi è anche il più semplice. Considera la funzione lineare , dove W è una matrice m × n fissata e x un vettore in R^n. Qual è la sua Jacobiana?
La i-esima componente di uscita è . La sua derivata parziale rispetto a è semplicemente , perché nella somma solo il termine j-esimo contiene e la sua derivata è il coefficiente . Quindi :
La Jacobiana di una trasformazione lineare è la matrice della trasformazione stessa. Due osservazioni. La prima: è costante, non dipende dal punto x — coerente con l’idea dello zoom, perché una funzione già lineare non ha bisogno di essere approssimata, è già la sua approssimazione lineare. La seconda: questo è il motivo per cui il VJP di uno strato lineare è banale. Propagare una sensibilità v all’indietro attraverso significa calcolare , cioè — una semplice moltiplicazione matrice-vettore con la matrice dei pesi trasposta, senza costruire nessuna Jacobiana separata, perché la Jacobiana è già W.
Esempio 3 — Jacobiana di un cambio di coordinate
Sezione intitolata “Esempio 3 — Jacobiana di un cambio di coordinate”Un esempio geometrico, fuori dal machine learning. Le coordinate polari (r, θ) si convertono in cartesiane (x, y) con la funzione R^2 -> R^2:
La Jacobiana di questa trasformazione, derivando ogni uscita rispetto a ogni ingresso, è:
Il suo determinante è . Quel fattore r è esattamente il “fattore di correzione” che chiunque abbia calcolato un integrale in coordinate polari ha incontrato: l’elemento d’area diventa . Il determinante jacobiano dice quanto la trasformazione dilata l’area, e qui dipende da r perché lontano dall’origine lo stesso incremento di angolo copre un arco più lungo.
Esempio 4 — la regola della catena in codice, con e senza matrice
Sezione intitolata “Esempio 4 — la regola della catena in codice, con e senza matrice”Questo esempio mostra in python la differenza tra moltiplicare le Jacobiane esplicitamente e fare la stessa cosa come sequenza di VJP, senza mai costruire le matrici.
import numpy as np
# Una catena: x -> g(x) -> f(g(x)). g e f sono funzioni vettoriali.def g(x): # R^3 -> R^2 return np.array([x[0] * x[1], x[2] ** 2])
def f(u): # R^2 -> R^2 return np.array([u[0] + u[1], u[0] * u[1]])
# Jacobiane esplicite, calcolate a mano per questo esempio.def Jg(x): return np.array([[x[1], x[0], 0.0], [0.0, 0.0, 2 * x[2]]])
def Jf(u): return np.array([[1.0, 1.0], [u[1], u[0]]])
x = np.array([2.0, 3.0, 4.0])u = g(x)
# Via 1: chain rule come prodotto di matrici. Materializza le Jacobiane.J_total = Jf(u) @ Jg(x) # (2x2) @ (2x3) = (2x3)
# Via 2: vector-Jacobian product. Mai una matrice piena della catena.# Si parte da un vettore di sensibilita sull'uscita e si propaga indietro.def vjp(v): v = v @ Jf(u) # attraversa f all'indietro v = v @ Jg(x) # attraversa g all'indietro return v
# Le due vie concordano, riga per riga della Jacobiana totale.for i in range(2): seed = np.eye(2)[i] # un versore dell'uscita alla volta assert np.allclose(vjp(seed), J_total[i])La Via 1 costruisce il prodotto di Jacobiane: corretta, e per matrici così piccole va benissimo. La Via 2 fa la stessa cosa ma propagando un vettore alla volta: per ottenere la riga i della Jacobiana totale lancia un VJP con il versore i-esimo come seme. In una rete vera la Via 1 è impossibile per ragioni di memoria, e Via 2 con un solo seme — il numero , perché la loss è scalare — è esattamente la backpropagation.
Si noti, nel codice della Via 2, che le Jacobiane Jf(u) e Jg(x) sono ancora costruite esplicitamente: l’esempio è didattico e tiene le matrici visibili per chiarezza. In un framework reale anche quel passaggio sparisce — al posto di v @ Jf(u) c’è una formula chiusa che calcola direttamente il prodotto vettore-Jacobiana per quel tipo di operazione, e la matrice Jf non viene mai allocata. La differenza tra l’esempio e il codice di produzione è solo questa: il principio — propagare un vettore a ritroso lungo la catena — è identico.
Esempio 5 — il gradiente della loss rispetto a un peso
Sezione intitolata “Esempio 5 — il gradiente della loss rispetto a un peso”Un esempio che porta tutto sul terreno del machine learning. Considera un singolo neurone lineare con un peso w: dato un ingresso x, produce una previsione . La loss quadratica rispetto a un valore atteso t è .
La catena è w -> ŷ -> L. La derivata della loss rispetto al peso, per la regola della catena, è il prodotto delle derivate locali:
Letto come VJP: si parte dalla sensibilità della loss rispetto a se stessa, il seme . Lo si moltiplica per la derivata locale , ottenendo la sensibilità su . La si moltiplica per la derivata locale , ottenendo la sensibilità su w — che è il gradiente cercato. Con molti pesi e molti strati questa è una catena lunga di VJP, ma il principio resta questo: un vettore di sensibilità che parte da e attraversa la rete a ritroso, raccogliendo a ogni strato il gradiente rispetto ai pesi locali. Quel gradiente è ciò che la discesa del gradiente usa per aggiornare i pesi, come dettagliato in Discesa del gradiente: SGD, momentum, Adam.
Esempio 6 — il vanishing gradient come prodotto di norme
Sezione intitolata “Esempio 6 — il vanishing gradient come prodotto di norme”L’ultimo esempio mostra, con i numeri, perché il prodotto di Jacobiane può tradire. Considera una rete profonda di strati. Per ragionare sulla grandezza del gradiente che arriva agli strati iniziali non serve la struttura esatta delle Jacobiane: basta la loro norma, cioè di che fattore ciascuna Jacobiana amplifica o riduce la lunghezza del vettore di sensibilità che la attraversa.
Supponi che ogni strato contribuisca un fattore — un valore plausibile per una rete con attivazioni che comprimono il segnale. Il gradiente che raggiunge il primo strato, dopo aver attraversato all’indietro tutti e gli strati, è proporzionale a : poco più di un millesimo del segnale di partenza. Per il primo strato è un sussurro quasi inudibile, e quei pesi imparano lentissimamente. Questo è il vanishing gradient.
Lo specchio opposto: se ogni strato contribuisce un fattore , allora . Il gradiente che arriva ai primi strati è amplificato di oltre duecento volte, gli aggiornamenti dei pesi diventano enormi e il training diverge. Questo è l’exploding gradient. Nessuno dei due è un errore di programmazione: entrambi sono la conseguenza aritmetica diretta del fatto che la regola della catena multivariata è un prodotto di tante matrici, e un prodotto di tanti fattori diversi da va a zero o all’infinito in fretta. È il caso multidimensionale dello stesso fenomeno che il capitolo precedente descriveva per la sigmoid e il suo fattore .
Applicazioni pratiche
Sezione intitolata “Applicazioni pratiche”Gli usi di derivate parziali, gradienti e Jacobiani non sono analogie didattiche: sono lo strumento, usato alla lettera.
L’addestramento delle reti neurali. È l’applicazione centrale, e ormai dovrebbe essere chiara. Una rete è una composizione di funzioni vettoriali; la sua derivata totale è il prodotto delle Jacobiane di tutti gli strati; la backpropagation calcola quel prodotto come sequenza di vector-Jacobian product, partendo dalla loss scalare e propagando una sensibilità all’indietro. Quando scrivi loss.backward() in PyTorch, o usi jax.grad, o un GradientTape in TensorFlow, stai invocando esattamente questa macchina. Il fatto che il reverse-mode debba memorizzare le attivazioni del forward pass spiega una voce concreta del budget hardware: la activation memory è spesso la ragione per cui un modello con un certo batch size sta in una GPU e con un batch size doppio va in out of memory. Il dettaglio strato per strato è il tema di mlp-backprop (in preparazione).
Robotica e cinematica. Il braccio di un robot ha più giunture, ciascuna con il suo angolo. La posizione della pinza all’estremità — l’end-effector — è una funzione vettoriale degli angoli di giuntura: una funzione da R^n (gli angoli) a R^3 (la posizione nello spazio). La sua Jacobiana lega la velocità delle giunture alla velocità dell’end-effector. Il problema inverso — “quali velocità di giuntura servono per muovere la pinza in una certa direzione” — si risolve invertendo la Jacobiana, ed è il cuore del controllo di un braccio robotico.
Modelli generativi e probabilità. I normalizing flow sono modelli generativi che costruiscono una distribuzione di probabilità complicata trasformandone una semplice attraverso una funzione invertibile. Per sapere come cambia la densità di probabilità sotto la trasformazione serve il determinante jacobiano: misura di quanto la trasformazione comprime o dilata lo spazio, e quindi di quanto si concentra o si diluisce la probabilità. Progettare flow efficienti è in buona parte l’arte di costruire trasformazioni il cui determinante jacobiano sia facile da calcolare.
Leggere ed estendere codice di differenziazione automatica. Capire la distinzione tra JVP e VJP cambia il modo in cui si legge l’API di un framework moderno. In JAX, jax.grad è il caso speciale del VJP per una funzione a uscita scalare; jax.jacrev costruisce la Jacobiana intera per righe impilando VJP, jax.jacfwd la costruisce per colonne impilando JVP, e la scelta tra i due dipende esattamente dalla forma della funzione discussa sopra. In PyTorch, loss.backward() esegue un VJP a partire dal seme implicito , mentre torch.autograd.functional.jvp e vjp espongono i due prodotti direttamente. Chi sa che cosa sta dietro questi nomi non deve indovinare quale chiamata usare: la deduce dalla forma del problema. È anche la chiave per diagnosticare errori comuni, come passare a backward() un tensore non scalare senza fornire il vettore di seme — un errore che ha senso solo se si sa che il backward è un VJP e che il seme non è opzionale quando l’uscita ha più di una componente.
Analisi di sensibilità. Ovunque ci sia un sistema con molti parametri di ingresso e si voglia sapere quali contano davvero per l’uscita, la risposta è una derivata parziale o una Jacobiana. In un modello di simulazione ingegneristica, le derivate parziali dell’output rispetto ai parametri dicono dove conviene investire per ridurre l’incertezza. In finanza, le “greche” di un portafoglio — le derivate del valore rispetto ai fattori di mercato — sono una Jacobiana che misura l’esposizione al rischio.
Dove si rompe
Sezione intitolata “Dove si rompe”Gli strumenti di questo capitolo sono potenti ma hanno limiti e trappole precise. Conoscerle conta quanto conoscere le formule.
La derivata parziale tiene ferme le altre variabili — e a volte è un errore. La derivata parziale rispetto a x si calcola congelando tutte le altre variabili. Questo è perfetto se le variabili sono davvero indipendenti, manovrabili una alla volta. Ma se sono accoppiate — se muovere x costringe anche y a cambiare — la derivata parziale non cattura l’effetto totale. Esempio: se y dipende a sua volta da x, la variazione totale di f quando muovi x non è , perché quella formula ignora il fatto che anche y si è mossa. L’effetto completo richiede la derivata totale, che somma il contributo diretto e quello indiretto attraverso y — ed è, di nuovo, un’applicazione della regola della catena. Confondere derivata parziale e derivata totale quando le variabili sono accoppiate è un errore concettuale ricorrente.
Il gradiente non vive sulla superficie. Vale la pena ribadirlo perché l’immagine sbagliata è seducente. Il gradiente di f(x, y) è un vettore con due componenti, e si è tentati di immaginarlo come una freccia appoggiata al fianco della collina, che punta su per il pendio. È sbagliato: il gradiente vive nel dominio, nel piano (x, y) visto dall’alto. È una freccia sulla mappa topografica, non sul terreno. La pendenza — l’informazione “verticale” — è codificata nella lunghezza della freccia, non in una sua terza componente. Tenere il gradiente nel posto giusto evita confusioni quando si passa a spazi a molte dimensioni, dove “la superficie” non è nemmeno visualizzabile.
Il gradiente è perpendicolare alle curve di livello, non tangente. Una mezza memoria della proprietà di ortogonalità porta a invertirla. Il gradiente non segue le curve di livello: le taglia ad angolo retto. Seguire una curva di livello significa muoversi a quota costante, cioè con derivata direzionale zero, cioè perpendicolarmente al gradiente. Il gradiente punta nella direzione di massima variazione, che è la più lontana possibile dal “non variare”.
JVP e VJP non sono intercambiabili. Sono due prodotti diversi e rispondono a due domande diverse. Il JVP, , va dall’ingresso all’uscita: dato uno spostamento dell’ingresso, calcola lo spostamento dell’uscita. Il VJP, , va dall’uscita all’ingresso: data una sensibilità sull’uscita, calcola la sensibilità sull’ingresso. La backpropagation è VJP — parte dalla loss e va verso i pesi. Scambiare i due, o pensare che reverse-mode calcoli un JVP, è un errore che si traduce subito in dimensioni di matrici che non combaciano.
Reverse-mode non è “sempre meglio”. È diffusa l’idea che il reverse-mode sia universalmente superiore. È superiore solo per la forma “poche uscite, tanti ingressi” — m piccolo, n grande. Per la forma opposta — m grande, n piccolo, per esempio calcolare come molte uscite reagiscono a pochi parametri — è il forward-mode a vincere, perché costruisce la Jacobiana per colonne e le colonne sono poche. Esistono persino casi misti dove la strategia ottima è una combinazione dei due. Il reverse-mode domina nel machine learning solo perché quasi tutti i problemi di addestramento hanno la forma “loss scalare, milioni di pesi”: è la forma, non una superiorità intrinseca, a decidere.
La derivata parziale lungo gli assi non basta a descrivere la pendenza. Un errore sottile è credere che, conoscendo e , si sappia “tutto” sul comportamento locale della funzione. Le derivate parziali coprono solo le direzioni degli assi; la pendenza lungo una diagonale richiede la derivata direzionale, e la derivata direzionale richiede l’intero gradiente. Le parziali sono i mattoni, ma è il gradiente — il vettore che le assembla — l’oggetto che racconta la storia completa. C’è anche un caso patologico: esistono funzioni in cui tutte le derivate parziali esistono in un punto eppure la funzione non è “differenziabile” lì, cioè non ammette una buona approssimazione lineare. Per le funzioni lisce del machine learning questo non capita, ma vale sapere che “le parziali esistono” è una condizione più debole di “la funzione è differenziabile”.
La differenziazione automatica non è la differenziazione numerica. Una confusione frequente: pensare che i framework calcolino i gradienti per differenze finite, valutando la funzione con un piccolo incremento h e facendo il rapporto. Non è così. La differenziazione automatica applica le regole esatte di derivazione alle operazioni elementari del grafo di calcolo, e propaga i risultati con la regola della catena. Non c’è nessun h da scegliere, nessun errore di troncamento, nessuna perdita di cifre significative — i problemi che il capitolo precedente attribuiva alla derivata numerica. La differenziazione numerica sopravvive solo come strumento di verifica (il gradient checking): si confronta il gradiente dell’autodiff con un’approssimazione a differenze finite, e una discrepanza segnala un bug. Ma il gradiente che addestra il modello è sempre quello esatto dell’autodiff.
La backpropagation non costruisce matrici Jacobiane — credere il contrario porta a stime di memoria sbagliate. Chi immagina la backpropagation come “moltiplica le matrici Jacobiane degli strati” si fa un’idea catastroficamente errata del suo costo di memoria, e conclude che addestrare reti profonde sia impossibile. La realtà è che il reverse-mode è matrix-free: non materializza nessuna Jacobiana. La memoria che serve davvero è quella delle attivazioni salvate per il backward pass, che è grande ma gestibile e cresce linearmente — non quadraticamente — con la dimensione degli strati. Distinguere “matrice Jacobiana” (mai costruita) da “attivazioni salvate” (sì, e sono il vero costo) è essenziale per ragionare sul budget di memoria di un training.
Il prodotto di Jacobiane esplode o svanisce. La regola della catena multivariata è un prodotto di L matrici. Quel prodotto ha la stessa patologia che il capitolo precedente ha descritto per la versione scalare, amplificata. Se le Jacobiane lungo la catena hanno norme sistematicamente minori di , il loro prodotto collassa esponenzialmente verso zero con la profondità: è il vanishing gradient, e gli strati iniziali smettono di ricevere segnale di addestramento. Se hanno norme maggiori di , il prodotto diverge: è l’exploding gradient, e gli aggiornamenti dei pesi diventano enormi e instabili. Non sono bug del codice: sono conseguenze strutturali del fatto che la derivata di una rete profonda è un prodotto di molte matrici. Le contromisure — funzioni di attivazione con derivata vicina a , connessioni residue, normalizzazione, gradient clipping — sono tutte modi per tenere sotto controllo le norme dei fattori di quel prodotto.
L’approssimazione lineare vale solo localmente. La Jacobiana è la migliore approssimazione lineare di una funzione vettoriale, e “lineare” significa valida in un intorno piccolo del punto. è una buona approssimazione quando dx è piccolo, e degrada man mano che ci si allontana. Un optimizer che fa un passo basato sul gradiente — cioè sulla Jacobiana della loss — sta scommettendo che il passo resti dentro la regione in cui l’approssimazione lineare è ancora valida. È la stessa storia del learning rate del capitolo precedente, ora in più dimensioni: un passo troppo lungo esce dalla zona di validità dell’approssimazione e può far salire la loss invece di farla scendere.
Collegamenti
Sezione intitolata “Collegamenti”- Analisi matematica: limiti, continuità, derivate — il prerequisito diretto: la derivata di una variabile e la regola della catena scalare. Questo capitolo ne è l’estensione multidimensionale, sia in ingresso sia in uscita.
hessiane-curvatura(in preparazione) — il passo successivo di questa Parte: la derivata seconda multivariata, che è la Jacobiana del gradiente, e che distingue minimi, massimi e selle là dove il gradiente da solo non basta.serie-taylor(in preparazione) — l’approssimazione locale: la Jacobiana è il termine lineare dello sviluppo di Taylor di una funzione vettoriale attorno a un punto.ottimizzazione-matriciale(in preparazione) — derivate rispetto a matrici e tensori, l’estensione naturale quando i parametri non sono un vettore ma una matrice di pesi.- Gradienti e derivate direzionali senza analisi — lo stesso gradiente visto a intuito, come freccia su un paesaggio; qui riceve la definizione formale come vettore delle derivate parziali.
- Discesa del gradiente: SGD, momentum, Adam — l’algoritmo che usa per ottimizzare. Il “perché ci si muove contro il gradiente” è la proprietà di massima discesa dimostrata in questo capitolo.
- Vettori, spazi vettoriali, intuizione geometrica — il gradiente è un vettore e la derivata direzionale un prodotto scalare: senza vettori, nessuno dei due si scrive.
- Matrici come trasformazioni — la Jacobiana è una matrice, e va letta come la trasformazione lineare che approssima localmente una funzione vettoriale.
- Prodotto scalare come proiezione e somiglianza — la formula è un prodotto scalare: la derivata direzionale è la proiezione del gradiente su una direzione.
mlp-backprop(in preparazione) — la backpropagation strato per strato: la catena di vector-Jacobian product di questo capitolo, sviluppata per intero su una rete multilayer.
Per andare oltre
Sezione intitolata “Per andare oltre”- The Autodiff Cookbook — documentazione ufficiale di JAX (Google, aggiornamento continuo,
docs.jax.dev). La spiegazione più chiara di JVP e VJP, della differenza tra forward e reverse-mode, e del perché il reverse-mode è matrix-free. Il riferimento primario per la seconda metà di questo capitolo. - A. Baydin, B. Pearlmutter, A. Radul, J. Siskind, Automatic Differentiation in Machine Learning: a Survey (Journal of Machine Learning Research 18, 2018; arXiv:1502.05767) — la survey accademica di riferimento sulla differenziazione automatica, che dimostra come la backpropagation sia un caso particolare del reverse-mode AD e ne discute i costi.
- Serie Multivariable calculus di 3Blue1Brown (Grant Sanderson, in collaborazione con Khan Academy) — animazioni che rendono visibili il gradiente come direzione di massima salita, l’ortogonalità alle curve di livello e la derivata direzionale. Il modo più rapido per vedere la prima metà di questo capitolo.
- Mathematics for Machine Learning di Deisenroth, Faisal e Ong (Cambridge University Press, 2020; PDF gratuito sul sito degli autori) — il capitolo sul calcolo vettoriale tratta derivate parziali, gradienti e Jacobiane con il taglio giusto per chi le userà nel machine learning, senza eccesso di formalismo.
- Jacobian matrix and determinant — voce di Wikipedia in inglese. Una sintesi compatta e affidabile della definizione formale, della regola della catena multivariata come prodotto di Jacobiane, e della collocazione storica del lavoro di Carl Jacobi.