El código original ya no lo he encontrado en el sitio web de PyTorch.
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)
El problema con el código anterior es que no hay una función basada en qué calcular los gradientes. Esto significa que no sabemos cuántos parámetros (argumentos toma la función) y la dimensión de los parámetros.
Para entender completamente esto, creé un ejemplo cercano al original:
Ejemplo 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])
Supuse que nuestra función es y=3*a + 2*b*b + torch.log(c)
y los parámetros son tensores con tres elementos en su interior.
Puedes pensar en algo así gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
como en el acumulador.
Como puede escuchar, el cálculo del sistema de autogrado PyTorch es equivalente al producto jacobiano.
En caso de que tenga una función, como hicimos nosotros:
y=3*a + 2*b*b + torch.log(c)
Jacobian sería [3, 4*b, 1/c]
. Sin embargo, este jacobiano no es la forma en que PyTorch está haciendo las cosas para calcular los gradientes en cierto punto.
PyTorch utiliza la diferenciación automática (AD) del modo de avance y retroceso en conjunto.
No hay matemáticas simbólicas involucradas ni diferenciación numérica.
La diferenciación numérica sería calcular δy/δb
, para b=1
y b=1+ε
donde ε es pequeño.
Si no usa degradados en y.backward()
:
Ejemplo 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.)
Se le sencillo obtener el resultado en un punto, en base a la configuración de las a
, b
, c
tensores inicialmente.
Tener cuidado de cómo usted hace funcionar su a
, b
, c
:
Ejemplo 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])
Si usa torch.empty()
y no usa, pin_memory=True
es posible que obtenga resultados diferentes cada vez.
Además, los gradientes de notas son como acumuladores, así que ajústelos a cero cuando sea necesario.
Ejemplo 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, algunos consejos sobre los términos que usa PyTorch:
PyTorch crea un gráfico computacional dinámico al calcular los gradientes en el pase hacia adelante. Esto se parece mucho a un árbol.
Por lo tanto, a menudo escuchará que las hojas de este árbol son tensores de entrada y la raíz es un tensor de salida .
Los gradientes se calculan trazando el gráfico desde la raíz hasta la hoja y multiplicando cada gradiente en la forma usando la regla de la cadena . Esta multiplicación ocurre en el pase hacia atrás.
Explicación
En el caso de las redes neuronales, lo usamos
loss
para evaluar qué tan bien la red ha aprendido a clasificar la imagen de entrada (u otras tareas). Elloss
término suele ser un valor escalar. Para actualizar los parámetros de la red, necesitamos calcular el gradiente deloss
wrt a los parámetros, queleaf node
en realidad está en el gráfico de cálculo (por cierto, estos parámetros son principalmente el peso y el sesgo de varias capas, como Convolución, Lineal y pronto).De acuerdo con la regla de la cadena, para calcular el gradiente de
loss
wrt a un nodo hoja, podemos calcular la derivada deloss
wrt alguna variable intermedia y el gradiente de la variable intermedia wrt a la variable hoja, hacer un producto escalar y sumar todos estos.Los
gradient
argumentos de unaVariable
'sbackward()
método se utiliza para calcular una suma ponderada de cada elemento de una variable wrt la Variable hoja . Este peso es simplemente la derivada de laloss
wrt final de cada elemento de la variable intermedia.Un ejemplo concreto
Tomemos un ejemplo concreto y sencillo para entender esto.
En el ejemplo anterior, el resultado de first
print
esque es exactamente la derivada de z_1 wrt ax.
El resultado del segundo
print
es:que es la derivada de z_2 wrt ax.
Ahora, si usa un peso de [1, 1, 1, 1] para calcular la derivada de z wrt ax, el resultado es
1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx
. Entonces, no es sorprendente que la salida de 3rdprint
sea:Cabe señalar que el vector de peso [1, 1, 1, 1] es exactamente una derivada de
loss
wrt a z_1, z_2, z_3 y z_4. La derivada deloss
wrt tox
se calcula como:Entonces la salida del 4to
print
es el mismo que el 3roprint
:fuente
gradient
mejor el argumento. Gracias por tu respuesta.[1, 1, 1, 1]
es exactamente derivado deloss
wrt az_1
,z_2
,z_3
yz_4
." Creo que esta afirmación es realmente clave para la respuesta. Al mirar el código del OP, un gran signo de interrogación es de dónde provienen estos números arbitrarios (mágicos) para el gradiente. En su ejemplo concreto, creo que sería muy útil señalar la relación entre el[1, 0, 0 0]
tensor, por ejemplo, y laloss
función de inmediato para que pueda ver que los valores no son arbitrarios en este ejemplo.loss = z.sum(dim=1)
, se convertirá enloss = z_1 + z_2 + z_3 + z_4
. Si conoce el cálculo simple, sabrá que la derivada deloss
wrt toz_1, z_2, z_3, z_4
es[1, 1, 1, 1]
.Normalmente, su gráfico computacional tiene una salida escalar dice
loss
. Luego puede calcular el gradiente deloss
wrt los pesos (w
) porloss.backward()
. Donde el argumento predeterminado debackward()
es1.0
.Si su salida tiene varios valores (p
loss=[loss1, loss2, loss3]
. Ej. ), Puede calcular los gradientes de pérdida con los pesosloss.backward(torch.FloatTensor([1.0, 1.0, 1.0]))
.Además, si desea agregar pesos o importancias a diferentes pérdidas, puede usar
loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001]))
.Esto significa calcular
-0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dw
simultáneamente.fuente
grad_tensors
es no para pesarlos de manera diferente, sino que son gradientes con cada elemento de los tensores correspondientes.Aquí, la salida de forward (), es decir, y es un vector 3.
Los tres valores son los gradientes a la salida de la red. Por lo general, se establecen en 1.0 si y es el resultado final, pero también pueden tener otros valores, especialmente si y es parte de una red más grande.
Por ej. si x es la entrada, y = [y1, y2, y3] es una salida intermedia que se utiliza para calcular la salida final z,
Luego,
Entonces, aquí, los tres valores para retroceder son
y luego hacia atrás () calcula dz / dx
fuente