Pytorch, каковы аргументы градиента

Я читаю документацию PyTorch и нашел пример, где они пишут

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

где x - начальная переменная, из которой построено y (3-вектор). Вопрос в том, каковы аргументы градиентов 0.1, 1.0 и 0.0001? Документация не очень понятна.

Ответы

Ответ 1

  Оригинальный код, которого я больше не нашел на сайте PyTorch.

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

Проблема с кодом выше - нет функции для расчета градиентов. Это означает, что мы не знаем, сколько параметров (аргументов принимает функция) и размерность параметров.

Чтобы полностью понять это, я создал несколько примеров, близких к оригиналу:

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

Как вы можете видеть, я предположил, что в первом примере наша функция - y=3*a + 2*b*b + torch.log(c), а параметры - тензоры с тремя элементами внутри.

Но есть и другой вариант:

Example 2:

import torch

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)    
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)

print(a.grad) # tensor(3.3003)
print(b.grad) # tensor(4.4004)
print(c.grad) # tensor(1.1001)

gradients = torch.FloatTensor([0.1, 1.0, 0.0001]) является аккумулятором.

Следующий пример даст идентичные результаты.

Example 3:

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)

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

print(a.grad) # tensor(3.3003)
print(b.grad) # tensor(4.4004)
print(c.grad) # tensor(1.1001)

Как вы можете слышать, расчет системы Autograd PyTorch эквивалентен продукту Якобиана.

Jacobian

Если у вас есть функция, как у нас:

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

Якобиан будет [3, 4*b, 1/c]. Однако этот якобианский совсем не так, как PyTorch делает вещи для вычисления градиентов в определенной точке.

Для предыдущей функции PyTorch будет делать, например, δy/δb, для b=1 и b=1+ε, где ε мало. Так что тут нет ничего похожего на символическую математику.

Если вы не используете градиенты в y.backward():

Example 4

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

Вы просто получите результат в определенный момент, основываясь на том, как изначально были установлены ваши тензоры a, b, c.

Будьте внимательны при инициализации своих a, b, c:

Example 5:

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

Если вы используете torch.empty() и не используете pin_memory=True, вы можете каждый раз получать разные результаты.

Кроме того, градиенты нот похожи на аккумуляторы, поэтому обнуляйте их при необходимости.

Example 6:

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

Наконец, я просто хотел сформулировать некоторые термины, которые использует PyTorch:

PyTorch создает динамический вычислительный граф при расчете градиентов. Это очень похоже на дерево.

Таким образом, вы часто будете слышать, что листья этого дерева являются входными тензорами, а корень - выходным тензором.

Градиенты рассчитываются путем трассировки графа от корня до листа и умножения каждого градиента на пути с использованием правила цепочки.

Ответ 2

Объяснение

Для нейронных сетей мы обычно используем loss, чтобы оценить, насколько хорошо сеть научилась классифицировать входное изображение (или другие задачи). Термин loss обычно является скалярным значением. Чтобы обновить параметры сети, нам нужно рассчитать градиент loss относительно параметров, который фактически равен leaf node в графе вычислений (кстати, эти параметры в основном представляют собой вес и смещение различных такие слои, как Convolution, Linear и т.д.).

Согласно правилу цепочки, чтобы вычислить градиент loss относительно листового узла, мы можем вычислить производную от loss по некоторой промежуточной переменной и градиент промежуточной переменной по отношению к листовой переменной, сделать скалярное произведение и сумму все это.

Аргументы gradient метода Variable backward() используются для вычисления взвешенной суммы каждого элемента переменной с листовой переменной. Этот вес является производной от окончательного loss по каждому элементу промежуточной переменной.

Конкретный пример

Давайте возьмем конкретный и простой пример, чтобы понять это.

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)    

В приведенном выше примере результат первого print равен

2 0 0 0
[torch.FloatTensor размером 1x4]

которая в точности является производной от z_1 w.r.t до x.

Результат второго print:

0 2 0 0
[torch.FloatTensor размером 1x4]

которая является производной от z_2 w.r.t до x.

Теперь, если использовать вес [1, 1, 1, 1], чтобы вычислить производную от z w.r.t до x, результат будет 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx. Поэтому неудивительно, что выход 3-го print:

2 2 2 2
[torch.FloatTensor of size 1x4]

Следует отметить, что вектор весов [1, 1, 1, 1] в точности является производным от loss w.r.t для z_1, z_2, z_3 и z_4. Производная от loss w.r.t до x рассчитывается как:

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

Таким образом, вывод 4-го print такой же, как и 3-го print:

2 2 2 2
[torch.FloatTensor of size 1x4]

Ответ 3

Как правило, ваш вычислительный граф имеет один скалярный вывод: loss. Затем вы можете вычислить градиент loss w.r.t. веса (w) на loss.backward(). Если аргументом по умолчанию backward() является 1.0.

Если ваш вывод имеет несколько значений (например, loss=[loss1, loss2, loss3]), вы можете вычислить градиенты потери w.r.t. весами loss.backward(torch.FloatTensor([1.0, 1.0, 1.0])).

Кроме того, если вы хотите добавить вес или значения к разным потерям, вы можете использовать loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001])).

Это означает, что вы одновременно вычисляете -0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dw.

Ответ 4

Здесь выходной сигнал forward(), т.е. y является 3-вектором.

Три значения - это градиенты на выходе сети. Обычно они устанавливаются в 1.0, если y является конечным результатом, но может иметь и другие значения, особенно если y является частью более крупной сети.

Например, если x - вход, y = [y1, y2, y3] - промежуточный вывод, который используется для вычисления конечного выхода z,

Затем

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

Итак, три значения в обратном направлении:

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

а затем назад() вычисляет dz/dx