Differenziazione automatica
La differenziazione automatica (in lingua inglese automatic differentiation, AD), nota anche come differenziazione algoritmica o differenziazione computazionale,[1] è un insieme di tecniche per il calcolo automatico delle derivate di una funzione matematica implementata da un programma informatico. La differenziazione automatica sfrutta il fatto che l'implementazione di una funzione, indipendentemente da quanto sia complessa, si riduce all'esecuzione di una serie di operazioni aritmetiche (somma, sottrazione, moltiplicazione, divisione, etc.) e funzioni elementari (esponenziale, funzioni trigonometriche, etc.). Applicando la regola della catena ripetutamente a tali operazioni, la derivata di una funzione arbitrariamente complessa può essere calcolata automaticamente, alla precisione di calcolo in uso, e velocemente, usando un numero di operazioni equivalente al più ad un fattore piccolo e costante rispetto al numero di operazioni usate nella funzione originale.
La differenziazione automatica non deve essere confusa con la differenziazione simbolica, che viene eseguita manipolando le espressioni rappresentate in forma simbolica, né con la differenziazione numerica, che approssima la derivata con una differenza finita. Il primo metodo è solitamente molto più lento e limitato dalla difficoltà di convertire funzioni arbitrarie in forma simbolica, mentre il secondo metodo introduce errori numerici di discretizzazione che tendono a propagarsi e limitano la stabilità numerica. Inoltre, entrambi i metodi sono lenti nel calcolare derivate parziali di funzioni con un numero elevato di variabili, problema comunemente incontrato in metodi di ottimizzazione basati su discesa del gradiente. Tali metodi sono alla base dell'implementazione di molte tecniche di apprendimento automatico, in particolare apprendimento profondo, e la maggior parte dei framework di apprendimento profondo implementano la differenziazione automatica per il calcolo automatico del gradiente.[2][3][4]
Regola della catena e accumulazione in avanti e inversa
[modifica | modifica wikitesto]La regola della catena permette di scomporre una derivata in una serie di derivate più semplici. Per una composizione la regola della catena fornisce come risultato
La differenziazione automatica può essere operata in due modi distinti, accumulazione in avanti (forward accumulation o forward mode) e accumulazione inversa (reverse accumulation o reverse mode). L'accumulazione in avanti attraversa la catena di operazioni dall'interno all'esterno (nell'esempio, calcolando prima , poi e infine ), mentre l'accumulazione inversa prevede di attraversare la catena dall'esterno all'interno (calcolando prima , poi e infine ). In sintesi, si ha che l'accumulazione in avanti calcola la relazione ricorsiva
con , mentre l'accumulazione inversa calcola
con . In generale, entrambi i metodi sono casi particolari dell'applicazione dell'operatore di composizione dei programmi, fissando opportunamente uno dei due argomenti.
Accumulazione in avanti
[modifica | modifica wikitesto]Nell'accumulazione in avanti, vengono fissate le variabili indipendenti nelle quali la differenziazione è calcolata, quindi il calcolo delle derivate delle sotto-espressioni è eseguito ricorsivamente. Questo può essere eseguito sostituendo ripetutamente le derivate delle funzioni interne nella regola della catena:
Il procedimento viene generalizzato al caso in più variabili, come prodotto di matrici jacobiane.
A differenza dell'accumulazione inversa, l'accumulazione in avanti è più naturale e semplice da implementare, in quanto il flusso di informazioni nel calcolo della derivata coincide con l'ordine di valutazione, aumentando ogni variabile con il valore numerico della sua derivata,
Le derivate sono quindi calcolate in sincronia con i passaggi di valutazione delle espressioni, e combinate con altre derivate tramite la regola della catena.
Per esempio, sia data la funzione
dove, per chiarezza, ogni singola sotto-espressione è stata etichettata con una variabile . La scelta delle variabili indipendenti determina i valori iniziali e (chiamati semi). Ad esempio, se si vuole calcolare la derivata della funzione rispetto a , i valori iniziali vengono impostati a
Con questi semi, le derivate vengono propagate tramite la regola della catena, come mostrato nella tabella seguente e nel grafo computazionale nella figura a lato.
Per calcolare il gradiente della funzione, sono necessari attraversamenti addizionali del grafo computazionale, usando semi adeguati, ad esempio per calcolare la derivata rispetto a .
La complessità computazionale di ogni attraversamento del grafo è proporzionale alla complessità della funzione iniziale. L'accumulazione in avanti è più efficiente rispetto a quella inversa per funzioni nella forma con , in quanto sono necessari solamente attraversamenti, rispetto agli richiesti dall'accumulazione inversa.
Accumulazione inversa
[modifica | modifica wikitesto]Nell'accumulazione inversa, vengono fissate le variabili dipendenti rispetto alle quali la differenziazione è calcolata, quindi la derivata viene calcolata ricorsivamente rispetto ad ogni sotto-espressione. Equivalentemente, la derivata della funzione più esterna nella regola della catena viene sostituita
La quantità usata nel calcolo tramite accumulazione inversa è chiamata aggiunta (adjoint), denotata con una barra (), ed è la derivata di una variabile dipendente rispetto alla sotto-espressione :
L'accumulazione inversa attraversa la catena dall'esterno verso l'interno, ovvero attraversa il grafo computazionale (figura a lato) dall'alto verso il basso. È necessario un attraversamento del grafo per ogni componente della funzione, quindi solo uno nel caso di funzioni scalari. Questo genera un compromesso tra costo in spazio e in tempo rispetto all'accumulazione in avanti: mentre l'accumulazione inversa può richiedere un numero inferiore di attraversamenti del grafo, è però necessario memorizzare le variabili intermedie (tipicamente usando una lista di Wengert),[5][6] che può rappresentare un costo insostenibile in termini di memoria nel caso il grafo computazionale sia molto grande. Tale problema è parzialmente mitigato memorizzando solo una parte dei valori intermedi, e ricostruendo i restanti tramite calcoli addizionali (metodo noto come checkpointing).
Le operazioni richieste per calcolare la derivata tramite accumulazione inversa in questo esempio sono:
Il grafo computazionale può essere manipolato per calcolare il gradiente dell'operazione originaria, aumentando ogni nodo con un nodo aggiunto, e con i vertici tra nodi aggiunti orientati in direzione opposta rispetto ai vertici tra nodi originali. Tali nodi aggiunti rappresentano la moltiplicazione per la derivata dell'espressione calcolata nel nodo originale. Per esempio, un nodo contenente la funzione avrà l'espressione nel corrispondente nodo aggiunto.
L'accumulazione inversa è più efficiente rispetto a quella in avanti per funzioni nella forma con , in quanto richiede attraversamenti del grafo computazionale, rispetto ai attraversamenti richiesti dall'accumulazione in avanti.
L'accumulazione inversa è stata pubblicata per la prima volta nel 1970 da Seppo Linnainmaa nella sua tesi di M.Sc.,[7][8][9] ed è comunemente usata per implementare la retropropagazione dell'errore nelle reti neurali artificiali.
Altri metodi
[modifica | modifica wikitesto]Le accumulazioni in avanti e inversa sono solo due metodi estremi per attraversare la catena di derivate. Il problema del calcolo della matrice jacobiana con un numero minimo di operazioni è noto come problema dell'accumulazione ottimale (optimal Jacobian accumulation, OJA), ed è un problema NP-completo.[10]
Differenziazione automatica tramite numeri duali
[modifica | modifica wikitesto]L'accumulazione in avanti può essere implementata estendendo l'algebra dei numeri reali, dove ad ogni numero è associata una componente addizionale nilpotente che rappresenta la derivata della funzione valutata in quel numero. Le operazioni aritmetiche possono essere estese a questa nuova algebra, nota come algebra dei numeri duali. Tale formulazione può essere ulteriormente generalizzata, in quella che è nota come teoria del calcolo operazionale.
L'aritmetica estesa può essere costruita sostituendo ogni numero reale con la quantità , dove è un numero reale e è un infinitesimo, un numero astratto nilpotente tale che . Da tale definizione, si ottiene che
e le altre operazioni possono essere definite analogamente.
È possibile definire polinomi in questa aritmetica aumentata. Se , allora
dove è la derivata di rispetto al primo argomento, e , chiamato seme (seed), può essere assegnato arbitrariamente.
Tale nuova aritmetica consiste di coppie ordinate , con aritmetica ordinaria nella prima componente e aritmetica della derivata prima nella seconda componente. Estendendo i risultati precedenti dai polinomi alle funzioni analitiche, si ottiene una nuova aritmetica elementare:
e in generale, per una funzione si ha
dove e sono le derivate di rispetto al primo e al secondo argomento rispettivamente.
Un'operazione binaria elementare può essere estesa a costanti, e l'applicazione a un numero duale e una costante (numero reale) è eseguita promuovendo la costante al numero duale . È possibile calcolare la derivata di una funzione in , calcolando in questa aritmetica, il cui risultato è .
Funzioni vettoriali e a più variabili
[modifica | modifica wikitesto]Funzioni vettoriali a più variabili possono essere trattate con analoga efficienza usando lo stesso meccanismo per le funzioni a una variabile, definendo un operatore di derivata direzionale che calcola , la derivata direzionale di at in direzione , che può essere calcolata come usando la stessa aritmetica definita in precedenza. Per calcolare il gradiente sono necessarie valutazioni.
Derivate di ordine superiore
[modifica | modifica wikitesto]L'aritmetica definita per la derivata prima può essere estesa per calcolare derivate di ordine superiore. Tuttavia, all'aumentare dell'ordine le espressioni diventano molto più complesse, e il costo è quadratico nell'ordine della derivata più alta. È possibile definire una simile algebra su polinomi di Taylor troncati, consentendo di eseguire i calcoli più velocemente. Una formulazione generale e rigorosa è definita nel contesto del calcolo operazionale.
Calcolo operazionale
[modifica | modifica wikitesto]Il calcolo operazionale nello spazio dei programmi[11] generalizza il concetto di differenziazione automatica, generalizzando l'algebra dei numeri duali con un'algebra tensoriale, e fornisce una fondazione rigorosa alla matematica usata nell'apprendimento profondo.
Implementazione
[modifica | modifica wikitesto]L'accumulazione in avanti può essere implementata tramite un'interpretazione non-standard del programma, nella quale ogni numero reale è sostituito con un numero duale, le costanti sono sostituite da numeri duali con parte infinitesima nulla, e le operazioni sui reali sono sostituite con operazioni sui duali. L'interpretazione non-standard può essere generalmente implementata con due approcci: trasformazione del codice sorgente, e overloading degli operatori.
Trasformazione del codice sorgente
[modifica | modifica wikitesto]Il codice sorgente di una funzione è sostituito con il codice, generato automaticamente, di una funzione che interlaccia le istruzioni originarie con le istruzioni aggiuntive per il calcolo delle derivate. Tale approccio può essere implementato in ogni linguaggio, e rende possibile un maggior numero di ottimizzazioni automatiche da parte del compilatore, tuttavia l'implementazione dello strumento di generazione automatica è abbastanza complesso.
Overloading degli operatori
[modifica | modifica wikitesto]Tale metodo può essere usato in linguaggi che supportano l'overloading degli operatori. Non richiede generalmente di alterare la sequenza di operazioni nel programma, ma richiede la definizione di tipi di dati numerici aggiuntivi. È più semplice da implementare, e può essere usato anche per l'accumulazione inversa, tuttavia rende più complessa l'ottimizzazione automatica del codice. Esempi di librerie di differenziazione automatica tramite overloading degli operatori sono Adept e Stan.
Note
[modifica | modifica wikitesto]- ^ Richard D. Neidinger, Introduction to Automatic Differentiation and MATLAB Object-Oriented Programming (PDF), in SIAM Review, vol. 52, n. 3, 2010, pp. 545–563, DOI:10.1137/080743627.
- ^ Eager Execution, su tensorflow.org.
- ^ Automatic differentiation package - torch.autograd, su pytorch.org.
- ^ Derivatives in Theano — Theano 1.0.0 documentation, su deeplearning.net.
- ^ R.E. Wengert, A simple automatic derivative evaluation program, in Comm. ACM, vol. 7, 1964, pp. 463–464, DOI:10.1145/355586.364791.
- ^ Michael Bartholomew-Biggs, Steven Brown, Bruce Christianson e Laurence Dixon, Automatic differentiation of algorithms [collegamento interrotto], in Journal of Computational and Applied Mathematics, vol. 124, n. 1-2, 2000, pp. 171–190, DOI:10.1016/S0377-0427(00)00422-2.
- ^ Linnainmaa, Seppo (1970). The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors. Master's Thesis (in Finnish), Univ. Helsinki, 6-7.
- ^ Linnainmaa, Seppo (1976). Taylor expansion of the accumulated rounding error. BIT Numerical Mathematics, 16(2), 146-160.
- ^ Griewank, Andreas (2012). Who Invented the Reverse Mode of Differentiation?. Optimization Stories, Documenta Matematica, Extra Volume ISMP (2012), 389-400.
- ^ Uwe Naumann, Optimal Jacobian accumulation is NP-complete, in Mathematical Programming, vol. 112, n. 2, aprile 2008, pp. 427–441, DOI:10.1007/s10107-006-0042-z.
- ^ Žiga Sajovic e Martin Vuk, Operational calculus on programming spaces, 24 ottobre 2016.
Bibliografia
[modifica | modifica wikitesto]- Louis B. Rall, Automatic Differentiation: Techniques and Applications, Lecture Notes in Computer Science, vol. 120, Springer, 1981, ISBN 3-540-10861-0.
- Andreas Griewank e Andrea Walther, Evaluating Derivatives: Principles and Techniques of Algorithmic Differentiation, Other Titles in Applied Mathematics, vol. 105, 2nd, SIAM, 2008, ISBN 978-0-89871-659-7 (archiviato dall'url originale il 23 marzo 2010).
- Richard Neidinger, Introduction to Automatic Differentiation and MATLAB Object-Oriented Programming (PDF), in SIAM Review, vol. 52, n. 3, 2010, pp. 545–563, DOI:10.1137/080743627. URL consultato il 15 marzo 2013.
Collegamenti esterni
[modifica | modifica wikitesto]- (EN) www.autodiff.org
- (EN) Automatic Differentiation of Parallel OpenMP Programs
- (EN) Automatic Differentiation, C++ Templates and Photogrammetry
- (EN) Compute analytic derivatives of any Fortran77, Fortran95, or C program through a web-based interface
- (EN) JuliaDiff, strumenti di differenziazione automatica in Julia
Controllo di autorità | LCCN (EN) sh2011004690 · J9U (EN, HE) 987012430911405171 |
---|