Pytorch, quais são os argumentos gradientes

112

Estou lendo a documentação do PyTorch e encontrei um exemplo onde eles escrevem

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

onde x era uma variável inicial, a partir da qual y foi construído (um 3-vetor). A questão é: quais são os argumentos de 0,1, 1,0 e 0,0001 do tensor de gradientes? A documentação não é muito clara sobre isso.

Qubix
fonte

Respostas:

15

O código original não encontrei mais no site do PyTorch.

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

O problema com o código acima não existe função baseada no que calcular os gradientes. Isso significa que não sabemos quantos parâmetros (argumentos que a função usa) e a dimensão dos parâmetros.

Para entender isso, criei um exemplo próximo ao original:

Exemplo 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])

Presumi que nossa função é y=3*a + 2*b*b + torch.log(c)e os parâmetros são tensores com três elementos dentro.

Você pode pensar gradients = torch.FloatTensor([0.1, 1.0, 0.0001])que este é o acumulador.

Como você pode ouvir, o cálculo do sistema autogradado de PyTorch é equivalente ao produto Jacobiano.

Jacobiano

Caso você tenha uma função, como nós:

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

Jacobian seria [3, 4*b, 1/c]. No entanto, este Jacobiano não é como PyTorch está fazendo as coisas para calcular os gradientes em determinado ponto.

O PyTorch usa a diferenciação automática (AD) de modo de avanço e retrocesso em conjunto.

Não há matemática simbólica envolvida e nenhuma diferenciação numérica.

A diferenciação numérica seria calcular δy/δb, para b=1e b=1+εonde ε é pequeno.

Se você não usa gradientes em y.backward():

Exemplo 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.)

Você vai simples obter o resultado em um ponto, com base em como você definir suas a, b, ctensores inicialmente.

Tenha cuidado como você inicializa o seu a, b, c:

Exemplo 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])

Se você usar torch.empty()e não usar, pin_memory=Truepoderá ter resultados diferentes a cada vez.

Além disso, gradientes de nota são como acumuladores, então zere-os quando necessário.

Exemplo 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.)

Por último, algumas dicas sobre os termos que o PyTorch usa:

O PyTorch cria um gráfico computacional dinâmico ao calcular os gradientes na passagem para frente. Isso se parece muito com uma árvore.

Portanto, você ouvirá frequentemente que as folhas desta árvore são tensores de entrada e a raiz é o tensor de saída .

Os gradientes são calculados traçando o gráfico da raiz até a folha e multiplicando todos os gradientes no caminho usando a regra da cadeia . Essa multiplicação ocorre na passagem para trás.

Prosti
fonte
Ótima resposta! No entanto, não acho que Pytorch faça diferenciação numérica ("Para a função anterior, PyTorch faria, por exemplo, δy / δb, para b = 1 eb = 1 + ε onde ε é pequeno. Portanto, não há nada como matemática simbólica envolvida. ") - Acredito que haja diferenciação automática.
max_max_mir
Sim, usa AD, ou diferenciação automática, depois investiguei mais o AD como neste PDF , porém, ao definir esta resposta não fui bem informado.
prosti
Por exemplo, o exemplo 2 fornece RuntimeError: Mismatch in shape: grad_output [0] tem um formato de torch.Size ([3]) e a saída [0] tem um formato de torch.Size ([]).
Andreas K.
@AndreasK., Você estava certo, o PyTorch introduziu recentemente tensores de tamanho zero e isso teve impacto nos meus exemplos anteriores. Removido porque esses exemplos não eram cruciais.
prosti
100

Explicação

Para redes neurais, geralmente usamos losspara avaliar o quão bem a rede aprendeu a classificar a imagem de entrada (ou outras tarefas). O losstermo geralmente é um valor escalar. A fim de atualizar os parâmetros da rede, precisamos calcular o gradiente de losswrt para os parâmetros, que está realmente leaf nodeno gráfico de computação (a propósito, esses parâmetros são principalmente o peso e o viés de várias camadas, como Convolução, Linear e em breve).

De acordo com a regra da cadeia, para calcular o gradiente de losswrt para um nó folha, podemos calcular a derivada de losswrt alguma variável intermediária, e o gradiente da variável intermediária wrt para a variável folha, fazer um produto escalar e somar tudo isso.

Os gradientargumentos de um Variable's backward()método é usado para calcular uma soma ponderada de cada elemento de uma variável wrt a variável folha . Este peso é apenas a derivada final de losscada elemento da variável intermediária.

Um exemplo concreto

Vamos dar um exemplo concreto e simples para entender isso.

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)    

No exemplo acima, o resultado do primeiro printé

2 0 0 0
[tocha.Tensor de flutuação de tamanho 1x4]

que é exatamente a derivada de z_1 em relação a x.

O resultado da segunda printé:

0 2 0 0
[tocha.Tensor de flutuação de tamanho 1x4]

que é a derivada de z_2 em relação a x.

Agora, se usar um peso de [1, 1, 1, 1] para calcular a derivada de z para x, o resultado é 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx. Portanto, não é de surpreender que o resultado do terceiro printseja:

2 2 2 2
[tocha.Tensor de flutuação de tamanho 1x4]

Deve-se notar que o vetor de peso [1, 1, 1, 1] é exatamente derivado de losswrt para z_1, z_2, z_3 e z_4. A derivada de losswrt to xé calculada como:

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

Portanto, a saída da 4ª printé a mesma da 3ª print:

2 2 2 2
[tocha.Tensor de flutuação de tamanho 1x4]

jdhao
fonte
1
apenas uma dúvida, por que estamos calculando x.grad.data para gradientes de perda ou z.
Priyank Pathak
7
Talvez eu tenha perdido algo, mas acho que a documentação oficial poderia realmente ter explicado gradientmelhor o argumento. Obrigado pela sua resposta.
protagonista de
3
@jdhao "Deve-se notar que vetor de peso [1, 1, 1, 1]é exatamente derivado do losswrt para z_1, z_2, z_3e z_4." Acho que essa afirmação é realmente a chave para a resposta. Ao examinar o código do OP, um grande ponto de interrogação é de onde vêm esses números arbitrários (mágicos) para o gradiente. Em seu exemplo concreto, acho que seria muito útil apontar a relação entre o [1, 0, 0 0]tensor eg e a lossfunção imediatamente para que possamos ver que os valores não são arbitrários neste exemplo.
a_convidado
1
@smwikipedia, isso não é verdade. Se expandirmos loss = z.sum(dim=1), ele se tornará loss = z_1 + z_2 + z_3 + z_4. Se você conhece cálculo simples, saberá que a derivada de losswrt to z_1, z_2, z_3, z_4é [1, 1, 1, 1].
jdhao
1
Eu te amo. Resolveu minha dúvida!
Black Jack 21 de
45

Normalmente, seu gráfico computacional tem uma saída escalar, diz loss. Então você pode calcular o gradiente de losswrt os pesos ( w) por loss.backward(). Onde o argumento padrão de backward()é 1.0.

Se sua saída tiver vários valores (por exemplo loss=[loss1, loss2, loss3]), você pode calcular os gradientes de perda em relação aos pesos por loss.backward(torch.FloatTensor([1.0, 1.0, 1.0])).

Além disso, se você quiser adicionar pesos ou importâncias a diferentes perdas, você pode usar loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001])).

Isso significa calcular -0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dwsimultaneamente.

Gu Wang
fonte
1
"se você quiser adicionar pesos ou importâncias a diferentes perdas, você pode usar loss.backward (torch.FloatTensor ([- 0.1, 1.0, 0.0001]))." -> Isso é verdade, mas um tanto enganoso, porque a principal razão pela qual passamos grad_tensorsnão é para pesá-los de forma diferente, mas eles são gradientes em relação a cada elemento dos tensores correspondentes.
Aerin
27

Aqui, a saída de forward (), ou seja, y é um 3-vetor.

Os três valores são os gradientes na saída da rede. Eles geralmente são definidos como 1.0 se y for a saída final, mas podem ter outros valores também, especialmente se y fizer parte de uma rede maior.

Por exemplo. se x é a entrada, y = [y1, y2, y3] é uma saída intermediária que é usada para calcular a saída final z,

Então,

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

Então, aqui, os três valores para trás são

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

e então backward () calcula dz / dx

greenberet123
fonte
5
Obrigado pela resposta, mas como isso é útil na prática? Quero dizer, onde precisamos de [dz / dy1, dz / dy2, dz / dy3] além de backprop de hardcoding?
hi15
É correto dizer que o argumento gradiente fornecido é o gradiente calculado na última parte da rede?
Khanetor de