Przykład automatycznego różnicowania w trybie odwrotnym


27

Nie jestem pewien, czy to pytanie należy tutaj, ale jest ściśle związane z metodami gradientu w optymalizacji, co wydaje się być tutaj na temat. W każdym razie możesz swobodnie przeprowadzić migrację, jeśli uważasz, że inna społeczność ma lepsze doświadczenie w tym temacie.

Krótko mówiąc, szukam krok po kroku przykładu automatycznego różnicowania w trybie wstecznym . Nie ma zbyt wiele literatury na ten temat, a istniejąca implementacja (taka jak w TensorFlow ) jest trudna do zrozumienia bez znajomości stojącej za nią teorii. Tak więc byłbym bardzo wdzięczny jeśli ktoś mógłby pokazać szczegółowo, co przekazać w , jak my go przetworzyć , a czego wyjąć obliczeniowej wykresie.

Kilka pytań, z którymi mam najwięcej trudności:

  • nasiona - dlaczego w ogóle ich potrzebujemy?
  • zasady odwrotnego różnicowania - wiem, jak wprowadzić różnicowanie do przodu, ale jak cofamy się? Np. W przykładzie z tej sekcji , skąd wiemy, że w2¯=w3¯w1 ?
  • czy pracujemy tylko z symbolami czy przekazujemy rzeczywiste wartości ? Np. W tym samym przykładzie , czy wi i wi¯ symbole lub wartości?

„Praktyczne uczenie maszynowe za pomocą Scikit-Learn i TensorFlow” Dodatek D zawiera moim zdaniem bardzo dobre wyjaśnienie. Polecam to.
Agustin Barrachina

Odpowiedzi:


37

Powiedzmy, że mamy wyrażenie z=x1x2+sin(x1) i chcemy znaleźć pochodne dzdx1 idzdx2 . AD w trybie do tyłu dzieli to zadanie na 2 części, mianowicie do przodu i do tyłu.

Przekaż do przodu

Po pierwsze, rozkładamy nasze złożone wyrażenie na zbiór pierwotnych, tj. Wyrażeń składających się z co najwyżej jednego wywołania funkcji. Zauważ też, że zmieniam nazwę zmiennych wejściowych i wyjściowych dla zachowania spójności, chociaż nie jest to konieczne:

w1=x1
w2=x2
w3=w1w2
w4=sin(w1)
w5=w3+w4
z=w5

Zaletą tej reprezentacji jest to, że reguły różnicowania dla każdego oddzielnego wyrażenia są już znane. Na przykład wiemy, że pochodną sin jest cos , a więc dw4dw1=cos(w1). Wykorzystamy ten fakt w przekazaniu wstecznym poniżej.

Zasadniczo przekazanie dalej polega na ocenie każdego z tych wyrażeń i zapisaniu wyników. Powiedzmy, że nasze dane wejściowe to: x1=2 i x2=3 . Następnie mamy:

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

Przełęcz wsteczny

To właśnie tam zaczyna się magia i zaczyna się ona od reguły łańcucha . W swojej podstawowej formie, reguła łańcuch stwierdza, że jeśli masz zmienną t(u(v)) , który zależy od u , który z kolei zależy od v , a następnie:

dtdv=dtdududv

lub, jeżeli t zależy od v przez kilka ścieżek / zmiennych ui , np .:

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

następnie (patrz dowód tutaj ):

dtdv=idtduiduidv

Jeśli chodzi o graf wyrażeń, jeśli mamy końcowy węzeł z i węzły wejściowe wi , a ścieżka od z do wi przechodzi przez węzły pośrednie wp (tj. z=g(wp) gdzie wp=f(wi) ), możemy znaleźć pochodną dzdwi as

dzdwi=pparents(i)dzdwpdwpdwi

Innymi słowy, aby obliczyć pochodną zmiennej wyjściowej z wrt dowolnej zmiennej pośredniej lub wejściowej wi , musimy jedynie znać pochodne jej rodziców i wzór do obliczenia pochodnej pierwotnego wyrażenia wp=f(wi) .

Przebieg wsteczny rozpoczyna się na końcu (tj. dzdz ) i propaguje wstecz do wszystkich zależności. Oto mamy (wyrażenie na „seed”):

dzdz=1

Można to odczytać jako „zmiana w z powoduje dokładnie taką samą zmianę w z ”, co jest dość oczywiste.

Następnie wiemy, że z=w5 i tak:

dzdw5=1

w5 liniowo zależy odw3 i w4 , więc dw5dw3=1 idw5dw4=1. Stosując regułę łańcucha, znajdujemy:

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

Z definicji w3=w1w2 i reguł pochodnych cząstkowych wynika, żedw3dw2=w1. A zatem:

dzdw2=dzdw3dw3dw2=1×w1=w1

Co, jak już wiemy z przekazania, to:

dzdw2=w1=2

Wreszcie, w1 przyczynia się do z poprzez w3 i w4 . Po raz kolejny z zasad pochodnych cząstkowych wiemy, że dw3dw1=w2idw4dw1=cos(w1). A zatem:

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

I znowu, biorąc pod uwagę znane dane wejściowe, możemy to obliczyć:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.