Pytorch, jakie są argumenty gradientowe

112

Czytam dokumentację PyTorch i znalazłem przykład, na którym piszą

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

gdzie x było początkową zmienną, z której skonstruowano y (3-wektor). Pytanie brzmi, jakie są argumenty 0,1, 1,0 i 0,0001 tensora gradientu? Dokumentacja nie jest w tym zbyt jasna.

Qubix
źródło

Odpowiedzi:

15

Oryginalny kod, którego nie znalazłem już na stronie PyTorch.

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

Problem z powyższym kodem nie ma funkcji opartej na tym, co obliczyć gradienty. Oznacza to, że nie wiemy, ile parametrów (argumentów przyjmuje funkcja) i jakie są wymiary parametrów.

Aby w pełni to zrozumieć, stworzyłem przykład zbliżony do oryginału:

Przykład 1:

a = torch.tensor([1.0, 2.0, 3.0], requires_grad = True)
b = torch.tensor([3.0, 4.0, 5.0], requires_grad = True)
c = torch.tensor([6.0, 7.0, 8.0], requires_grad = True)

y=3*a + 2*b*b + torch.log(c)    
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients,retain_graph=True)    

print(a.grad) # tensor([3.0000e-01, 3.0000e+00, 3.0000e-04])
print(b.grad) # tensor([1.2000e+00, 1.6000e+01, 2.0000e-03])
print(c.grad) # tensor([1.6667e-02, 1.4286e-01, 1.2500e-05])

Założyłem, że nasza funkcja jest, y=3*a + 2*b*b + torch.log(c)a parametry to tensory z trzema elementami w środku.

Możesz pomyśleć o tym, gradients = torch.FloatTensor([0.1, 1.0, 0.0001])że to akumulator.

Jak słychać, obliczenia systemu PyTorch autograd są równoważne iloczynowi jakobian.

Jakobian

Jeśli masz funkcję, tak jak my:

y=3*a + 2*b*b + torch.log(c)

Jakobian byłby [3, 4*b, 1/c]. Jednak ten jakobian nie jest tym, jak PyTorch robi rzeczy, aby obliczyć gradienty w pewnym momencie.

PyTorch wykorzystuje równolegle automatyczne różnicowanie w trybie do przodu i do tyłu (AD).

Nie ma tu żadnej symbolicznej matematyki ani numerycznego rozróżnienia.

Zróżnicowanie numeryczne należałoby obliczyć δy/δbdla b=1i b=1+εgdzie ε jest małe.

Jeśli nie używasz gradientów w y.backward():

Przykład 2

a = torch.tensor(0.1, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(0.1, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward()

print(a.grad) # tensor(3.)
print(b.grad) # tensor(4.)
print(c.grad) # tensor(10.)

Będziesz prosty uzyskać wynik w punkcie, w oparciu o jak ustawić a, b, ctensorów początkowo.

Bądź ostrożny, jak zainicjować a, b, c:

Przykład 3:

a = torch.empty(1, requires_grad = True, pin_memory=True)
b = torch.empty(1, requires_grad = True, pin_memory=True)
c = torch.empty(1, requires_grad = True, pin_memory=True)

y=3*a + 2*b*b + torch.log(c)

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)

print(a.grad) # tensor([3.3003])
print(b.grad) # tensor([0.])
print(c.grad) # tensor([inf])

Jeśli używasz torch.empty()i nie używasz pin_memory=True, możesz za każdym razem uzyskać inne wyniki.

Ponadto gradienty nut są jak akumulatory, więc zeruj je w razie potrzeby.

Przykład 4:

a = torch.tensor(1.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(1.0, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)

y.backward(retain_graph=True)
y.backward()

print(a.grad) # tensor(6.)
print(b.grad) # tensor(8.)
print(c.grad) # tensor(2.)

Na koniec kilka wskazówek dotyczących terminów używanych przez PyTorch:

PyTorch tworzy dynamiczny wykres obliczeniowy podczas obliczania gradientów w biegu do przodu. To wygląda jak drzewo.

Dlatego często słyszysz, że liście tego drzewa są tensorami wejściowymi, a korzeń jest tensorem wyjściowym .

Gradienty są obliczane poprzez śledzenie wykresu od korzenia do liścia i mnożenie każdego gradientu na drodze za pomocą reguły łańcucha . To mnożenie następuje w przejściu wstecz.

prosti
źródło
Świetna odpowiedź! Jednak nie sądzę, żeby Pytorch robił różniczkowanie numeryczne („Dla poprzedniej funkcji PyTorch zrobiłby na przykład δy / δb, dla b = 1 i b = 1 + ε, gdzie ε jest małe. Nie ma więc nic takiego jak matematyka symboliczna. ") - Myślę, że robi to automatyczne różnicowanie.
max_max_mir
Tak, używa AD lub automatycznego różnicowania, później badałem AD dalej, jak w tym pliku PDF , jednak kiedy ustawiłem tę odpowiedź, nie byłem do końca poinformowany.
prosti
Np. Przykład 2 podaje RuntimeError: Mismatch in shape: grad_output [0] ma kształt torch.Size ([3]), a wyjście [0] ma kształt torch.Size ([]).
Andreas K.
@ AndreasK., Miałeś rację, PyTorch wprowadził ostatnio tensory o zerowej wielkości i miało to wpływ na moje poprzednie przykłady. Usunięto, ponieważ te przykłady nie były kluczowe.
prosti
100

Wyjaśnienie

W przypadku sieci neuronowych zwykle używamy lossdo oceny, jak dobrze sieć nauczyła się klasyfikować obraz wejściowy (lub inne zadania). lossTermin jest zazwyczaj wartość skalarna. Aby zaktualizować parametry sieci, musimy obliczyć gradient losswrt do parametrów, który faktycznie znajduje się leaf nodena wykresie obliczeniowym (nawiasem mówiąc, te parametry to głównie waga i obciążenie różnych warstw, takich jak Convolution, Linear i wkrótce).

Zgodnie z regułą łańcuchową, aby obliczyć gradient losswrt do węzła liścia, możemy obliczyć pochodną losswrt zmiennej pośredniej i gradient zmiennej pośredniej wrt do zmiennej liścia, wykonać iloczyn skalarny i zsumować wszystkie.

Te gradientargumenty danej Variable„s backward()metoda służy do obliczania sumy ważonej każdego elementu zmiennej wrt z liści zmienna . Ta waga jest po prostu pochodną końcowego losswrt każdego elementu zmiennej pośredniej.

Konkretny przykład

Aby to zrozumieć, weźmy konkretny i prosty przykład.

from torch.autograd import Variable
import torch
x = Variable(torch.FloatTensor([[1, 2, 3, 4]]), requires_grad=True)
z = 2*x
loss = z.sum(dim=1)

# do backward for first element of z
z.backward(torch.FloatTensor([[1, 0, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_() #remove gradient in x.grad, or it will be accumulated

# do backward for second element of z
z.backward(torch.FloatTensor([[0, 1, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# do backward for all elements of z, with weight equal to the derivative of
# loss w.r.t z_1, z_2, z_3 and z_4
z.backward(torch.FloatTensor([[1, 1, 1, 1]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()

# or we can directly backprop using loss
loss.backward() # equivalent to loss.backward(torch.FloatTensor([1.0]))
print(x.grad.data)    

W powyższym przykładzie wynik pierwszego printto

2 0 0 0
[torch.FloatTensor o rozmiarze 1x4]

co jest dokładnie pochodną z_1 wrt do x.

Wynik drugiego printto:

0 2 0 0
[torch.FloatTensor o rozmiarze 1x4]

który jest pochodną z_2 wrt do x.

Teraz, jeśli użyjemy wagi [1, 1, 1, 1] do obliczenia pochodnej z wrt do x, otrzymamy wynik 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx. Nic więc dziwnego, że wynik 3 printto:

2 2 2 2
[torch.FloatTensor o rozmiarze 1x4]

Należy zauważyć, że wektor wag [1, 1, 1, 1] jest dokładnie pochodną losswrt do z_1, z_2, z_3 i z_4. Pochodna losswrt to xjest obliczana jako:

d(loss)/dx = d(loss)/dz_1 * dz_1/dx + d(loss)/dz_2 * dz_2/dx + d(loss)/dz_3 * dz_3/dx + d(loss)/dz_4 * dz_4/dx

Zatem wynik czwartego printjest taki sam jak trzeciego print:

2 2 2 2
[torch.FloatTensor o rozmiarze 1x4]

jdhao
źródło
1
tylko wątpliwość, dlaczego obliczamy x.grad.data dla gradientów dla strat lub z.
Priyank Pathak
7
Może coś przeoczyłem, ale wydaje mi się, że oficjalna dokumentacja naprawdę mogła gradientlepiej wyjaśnić argument. Dziękuję za odpowiedź.
bohater
3
@jdhao „Należy zauważyć, że waga wektor [1, 1, 1, 1]jest dokładnie pochodna losswrt do z_1, z_2, z_3i z_4.” Myślę, że to stwierdzenie jest naprawdę kluczem do odpowiedzi. Patrząc na kod OP, duży znak zapytania wskazuje, skąd pochodzą te arbitralne (magiczne) liczby dla gradientu. Myślę, że w twoim konkretnym przykładzie bardzo pomocne byłoby od razu wskazanie relacji między np. [1, 0, 0 0]Tensorem a lossfunkcją, aby można było zobaczyć, że wartości nie są w tym przykładzie dowolne.
a_guest,
1
@smwikipedia, to nieprawda. Jeśli się rozszerzymy loss = z.sum(dim=1), stanie się loss = z_1 + z_2 + z_3 + z_4. Jeśli znasz prosty rachunek różniczkowy, będziesz wiedział, że pochodną losswrt to z_1, z_2, z_3, z_4jest [1, 1, 1, 1].
jdhao
1
Kocham Cię. Rozwiązałem moje wątpliwości!
Black Jack 21
45

Zwykle twój wykres obliczeniowy ma jedno wyjście skalarne loss. Następnie możesz obliczyć gradient losswrt weights ( w) według loss.backward(). Gdzie domyślnym argumentem backward()jest 1.0.

Jeśli wynik ma wiele wartości (np. loss=[loss1, loss2, loss3]), Możesz obliczyć gradienty strat w stosunku do wag loss.backward(torch.FloatTensor([1.0, 1.0, 1.0])).

Ponadto, jeśli chcesz dodać wagi lub wagi do różnych strat, możesz użyć loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001])).

Oznacza to -0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dwjednoczesne obliczanie .

Gu Wang
źródło
1
"jeśli chcesz dodać wagi lub wartości importu do różnych strat, możesz użyć loss.backward (torch.FloatTensor ([- 0,1, 1,0, 0,0001]))." -> To prawda, ale nieco mylące, ponieważ głównym powodem, dla którego przechodzimy, grad_tensorsnie jest to, aby je ważyć inaczej, ale są to gradienty względem każdego elementu odpowiednich tensorów.
Aerin
27

Tutaj wynik forward (), tj. Y, jest 3-wektorowym.

Te trzy wartości to gradienty na wyjściu sieci. Zwykle są ustawione na 1,0, jeśli y jest końcowym wyjściem, ale mogą mieć również inne wartości, szczególnie jeśli y jest częścią większej sieci.

Np. jeśli x jest wejściem, y = [y1, y2, y3] jest wyjściem pośrednim, które jest używane do obliczenia końcowego wyniku z,

Następnie,

dz/dx = dz/dy1 * dy1/dx + dz/dy2 * dy2/dx + dz/dy3 * dy3/dx

Więc tutaj trzy wartości do tyłu to

[dz/dy1, dz/dy2, dz/dy3]

a następnie backward () oblicza dz / dx

greenberet123
źródło
5
Dzięki za odpowiedź, ale jak to się przydaje w praktyce? Mam na myśli, gdzie potrzebujemy [dz / dy1, dz / dy2, dz / dy3] innego niż hardcoding backprop?
hi15
Czy słuszne jest stwierdzenie, że podany argument gradientu jest gradientem obliczonym w drugiej części sieci?
Khanetor