Esempio dettagliato di differenziazione automatica in modalità inversa


27

Non sono sicuro che questa domanda appartenga qui, ma è strettamente correlata ai metodi di gradiente nell'ottimizzazione, che qui sembra essere in argomento. Ad ogni modo, sentiti libero di migrare se ritieni che un'altra comunità abbia una migliore esperienza in materia.

In breve, sto cercando un esempio dettagliato della differenziazione automatica in modalità inversa . Non c'è molta letteratura sull'argomento e l'implementazione esistente (come quella in TensorFlow ) è difficile da capire senza conoscere la teoria alla base. Quindi sarei molto grato se qualcuno potesse mostrare in dettaglio cosa passiamo , come lo elaboriamo e cosa prendiamo dal grafico computazionale.

Un paio di domande con cui ho maggiori difficoltà:

  • semi : perché ne abbiamo bisogno?
  • regole di differenziazione inversa : so come differenziare in avanti, ma come possiamo tornare indietro? Ad esempio nell'esempio di questa sezione , come facciamo a sapere che w2¯=w3¯w1 ?
  • lavoriamo solo con simboli o passiamo attraverso valori reali ? Ad esempio , nello stesso esempio , sono simboli e valori wio e wio¯ ?

"Apprendimento automatico pratico con Scikit-Learn & TensorFlow" L'Appendice D fornisce una spiegazione molto valida a mio avviso. Lo consiglio.
Agustin Barrachina,

Risposte:


37

Diciamo che abbiamo espressione z=x1x2+sin(x1) e vogliamo trovare derivate dzdX1 edzdX2 . La modalità inversa AD suddivide questa attività in 2 parti, vale a dire, i passaggi avanti e indietro.

Forward pass

Innanzitutto, scomporremo la nostra espressione complessa in un insieme di espressioni primitive, cioè espressioni costituite al massimo da una singola chiamata di funzione. Nota che ho anche rinominato le variabili di input e output per coerenza, anche se non è necessario:

w1=X1
w2=X2
w3=w1w2
w4=peccato(w1)
w5=w3+w4
z=w5

Il vantaggio di questa rappresentazione è che sono già note regole di differenziazione per ciascuna espressione separata. Ad esempio, sappiamo che derivato del peccato è cos , e quindi dw4dw1=cos(w1). Useremo questo fatto nel passaggio inverso di seguito.

In sostanza, il forward pass consiste nel valutare ciascuna di queste espressioni e nel salvare i risultati. Supponiamo che i nostri input siano: x1=2 e x2=3 . Poi abbiamo:

w1=x1=2
w2=x2=3
w3=w1w2=6
w4=sin(w1) =0.9
w5=w3+w4=6.9
z=w5=6.9

Passaggio inverso

Questo è dove la magia inizia e inizia con la regola della catena . Nella sua forma base, regola della catena afferma che se si dispone di variabili t(u(v)) , che dipende da u , che, a sua volta, dipende dalla v , allora:

dtdv=dtdududv

o, se t dipende v tramite più percorsi / variabili uio , ad esempio:

u1=f(v)
u2=g(v)
t=h(u1,u2)

quindi (vedi la prova qui ):

dtdv=idtduiduidv

In termini di grafico espressione, se abbiamo un finale nodo z e nodi di ingresso wi , e percorso da z a wi passano attraverso nodi intermedi wp (cioè z=g(wp) dove wp=f(wi) ), possiamo trovare la derivata dzdwi come

dzdwi=pparents(i)dzdwpdwpdwi

In altre parole, per calcolare la derivata della variabile di output z qualsiasi variabile intermedia o di input wi , abbiamo solo bisogno di conoscere le derivate dei suoi genitori e la formula per calcolare la derivata dell'espressione primitiva wp=f(wi) .

Il passaggio inverso inizia alla fine (cioè dzdz ) e si propaga all'indietro verso tutte le dipendenze. Qui abbiamo (espressione per "seme"):

dzdz=1

Ciò può essere letto come "il cambiamento in z provoca esattamente lo stesso cambiamento in z ", il che è abbastanza ovvio.

Quindi sappiamo che z=w5 e così:

dzdw5=1

w5 dipende linearmente daw3 ew4 , quindidw5dw3=1edw5dw4=1

dzdw3=dzdw5dw5dw3=1×1=1
dzdw4=dzdw5dw5dw4=1×1=1

w3=w1w2dw3dw2=w1. Thus:

dzdw2=dzdw3dw3dw2=1×w1=w1

Which, as we already know from forward pass, is:

dzdw2=w1=2

Finally, w1 contributes to z via w3 and w4. Once again, from the rules of partial derivatives we know that dw3dw1=w2 and dw4dw1=cos(w1). Thus:

dzdw1=dzdw3dw3dw1+dzdw4dw4dw1=w2+cos(w1)

And again, given known inputs, we can calculate it:

dzdw1=w2+cos(w1)=3+cos(2) =2.58

Since w1 and w2 are just aliases for x1 and x2, we get our answer:

dzdx1=2.58
dzdx2=2

And that's it!


This description concerns only scalar inputs, i.e. numbers, but in fact it can also be applied to multidimensional arrays such as vectors and matrices. Two things that one should keep in mind when differentiating expressions with such objects:

  1. Derivatives may have much higher dimensionality than inputs or output, e.g. derivative of vector w.r.t. vector is a matrix and derivative of matrix w.r.t. matrix is a 4-dimensional array (sometimes referred to as a tensor). In many cases such derivatives are very sparse.
  2. Each component in output array is an independent function of 1 or more components of input array(s). E.g. if y=f(x) and both x and y are vectors, yi never depends on yj, but only on subset of xk. In particular, this means that finding derivative dyidxj boils down to tracking how yi depends on xj.

The power of automatic differentiation is that it can deal with complicated structures from programming languages like conditions and loops. However, if all you need is algebraic expressions and you have good enough framework to work with symbolic representations, it's possible to construct fully symbolic expressions. In fact, in this example we could produce expression dzdw1=w2+cos(w1)=x2+cos(x1) and calculate this derivative for whatever inputs we want.


1
Very useful question/answer. Thanks. Just a litte criticism: you seem to move on a tree structure without explaining (that's when you start talking about parents, etc..)
MadHatter

1
Also it won't hurt clarifying why we need seeds.
MadHatter

@MadHatter thanks for the comment. I tried to rephrase a couple of paragraphs (these that refer to parents) to emphasize a graph structure. I also added "seed" to the text, although this name itself may be misleading in my opinion: in AD seed is always a fixed expression - dzdz=1, not something you can choose or generate.
ffriend

Thanks! I noticed when you have to set more than one "seed", generally one chooses 1 and 0. I'd like to know why. I mean, one takes the "quotient" of a differential w.r.t. itself, so "1" is at least intuitively justified.. But what about 0? And what if one has to pick more than 2 seeds?
MadHatter

1
As far as I understand, more than one seed is used only in forward-mode AD. In this case you set the seed to 1 for an input variable you want to differentiate with respect to and set the seed to 0 for all the other input variables so that they don't contribute to the output value. In reverse-mode you set the seed to an output variable, and you normally have only one output variable. I guess, you can construct reverse-mode AD pipeline with several output variables and set all of them but one to 0 to get the same effect as in forward mode, but I have never investigated this option.
ffriend
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.