Как эффективно использовать автоград PyTorch с тензорами?

В моем предыдущем вопросе я узнал, как использовать автоградиент PyTorch для дифференциации. И это сработало:

#autograd
import torch
from torch.autograd import grad
import torch.nn as nn
import torch.optim as optim

class net_x(nn.Module): 
        def __init__(self):
            super(net_x, self).__init__()
            self.fc1=nn.Linear(1, 20) 
            self.fc2=nn.Linear(20, 20)
            self.out=nn.Linear(20, 4) 

        def forward(self, x):
            x=torch.tanh(self.fc1(x))
            x=torch.tanh(self.fc2(x))
            x=self.out(x)
            return x

nx = net_x()
r = torch.tensor([1.0], requires_grad=True)
print('r', r)
y = nx(r)
print('y', y)
print('')
for i in range(y.shape[0]):
    # prints the vector (dy_i/dr_0, dy_i/dr_1, ... dy_i/dr_n)
    print(grad(y[i], r, retain_graph=True))

>>>
r tensor([1.], requires_grad=True)
y tensor([ 0.1698, -0.1871, -0.1313, -0.2747], grad_fn=<AddBackward0>)

(tensor([-0.0124]),)
(tensor([-0.0952]),)
(tensor([-0.0433]),)
(tensor([-0.0099]),)

Проблема, с которой я сейчас сталкиваюсь, заключается в том, что мне нужно различать очень большой тензор, и повторение его, как я делаю сейчас (for i in range(y.shape[0])), занимает вечность. Причина, по которой я повторяю, заключается в том, что, исходя из понимания, grad знает только, как распространять градиенты из скалярного тензора, а y - нет. Поэтому мне нужно вычислить градиенты по каждой координате y.
Я знаю, что TensorFlow способен различать тензоры от здесь:

tf.gradients(
    ys, xs, grad_ys=None, name='gradients', gate_gradients=False,
    aggregation_method=None, stop_gradients=None,
    unconnected_gradients=tf.UnconnectedGradients.NONE
)
"ys and xs are each a Tensor or a list of tensors. grad_ys is a list of Tensor, holding the gradients received by the ys. The list must be the same length as ys.

gradients() adds ops to the graph to output the derivatives of ys with respect to xs. It returns a list of Tensor of length len(xs) where each tensor is the sum(dy/dx) for y in ys and for x in xs."

И надеялся, что в PyTorch есть более эффективный способ различать тензоры.

Например:

a = range(100)
b = range(100)
c = range(100)
d = range(100)
my_tensor = torch.tensor([a,b,c,d])

t = range(100)

#derivative = grad(my_tensor, t) --> not working

#Instead what I'm currently doing:
for i in range(len(t)):
    a_grad = grad(a[i],t[i], retain_graph=True)
    b_grad = grad(b[i],t[i], retain_graph=True)
    #etc.

Мне сказали, что это могло бы сработать, если бы я мог запустить autograd на прямом проходе, а не на обратном, но из здесь похоже, что в настоящее время PyTorch не поддерживает эту функцию.

Обновление 1:
@jodag упомянул, что то, что я ищу, может быть просто диагональю якобиана. Я перехожу по ссылке, которую он прикрепил, и пробую более быстрый метод. Хотя это, похоже, не работает и выдает ошибку: RuntimeError: grad can be implicitly created only for scalar outputs. Код:

nx = net_x()
x = torch.rand(10, requires_grad=True)
x = torch.reshape(x, (10,1))
x = x.unsqueeze(1).repeat(1, 4, 1)
y = nx(x)
dx = torch.diagonal(torch.autograd.grad(torch.diagonal(y, 0, -2, -1), x), 0, -2, -1)

person Penguin    schedule 29.04.2021    source источник
comment
Если я правильно понимаю, возможно, torch.autograd.functional.jacobian может оказаться полезным?   -  person jodag    schedule 29.04.2021
comment
Может быть. Прошло много времени с тех пор, как я изучал якобианцев, поэтому я не уверен, что это то, что я ищу. Я могу сказать вам, каков ожидаемый результат, и, может быть, это поможет? Мне нужно получить частную производную каждого элемента в a по каждому соответствующему элементу в t (т.е. a[0] с t[0]). Поскольку t - вектор длины 100, а тензор my_tensor имеет форму (4,100), я ожидаю, что выходной тензор формы (4,100). Где строки соответствуют каждому вектору (a, b, c, d), а столбцы соответствуют производной этого вектора относительно элемента t   -  person Penguin    schedule 29.04.2021
comment
ах, это просто диагональ якобиана, так что вычислять все это может быть излишним.   -  person jodag    schedule 30.04.2021
comment
Похоже, есть запрос на открытие функции, но, похоже, он мало что получил внимание.   -  person jodag    schedule 30.04.2021
comment
@jodag Пожалуйста, посмотрите мое обновление   -  person Penguin    schedule 30.04.2021
comment
Я не совсем уверен, что ожидалось от этого дополнительного кода, но для первого опубликованного вами фрагмента вы просто вычисляете градиент сетевого вывода (тензор) относительно w.r.t. скалярный вход x, который в точности является якобианом. Вы можете получить те же результаты, используя dx = torch.autograd.functional.jacobian(lambda x_: nx(x_), x). Но это, похоже, не согласуется с тем, что вы добавили во вторую часть и прокомментировали вычисление партиала [i] w.r.t. t [i] (что было бы диагональю квадратного якобиана).   -  person jodag    schedule 30.04.2021
comment
О, я думал, что вы сказали, что я ищу диагональ якобиана из вашего предыдущего комментария и ссылки в приложении. Это то, что пытается сделать добавленный мной код.   -  person Penguin    schedule 30.04.2021
comment
То, что вы сказали в комментарии, и то, что ваш код фактически делает в вопросе, отличаются. В своем комментарии вы сказали, что хотите получить партиал a[i] w.r.t. t[i] для всех i. Это будет диагональ якобиана a w.r.t. t. Однако в коде вашего вопроса вы вычисляете градиент y[i] w.r.t. скаляр r для всех i. В последнем случае это в точности эквивалентно вычислению (полного) якобиана a w.r.t. r (т.е. матрица, содержащая частичную часть каждого элемента a относительно каждого элемента r).   -  person jodag    schedule 30.04.2021
comment
Что касается кода в этой ссылке, я не могу говорить о намерениях автора, только то, что они просят добавить такую ​​функциональность в pytorch. Возможно, что в опубликованном ими примере кода есть ошибки.   -  person jodag    schedule 30.04.2021
comment
@jodag Очень признателен за помощь! Я решил это, используя то, что вы сказали :) Просто написал ответ   -  person Penguin    schedule 30.04.2021


Ответы (1)


Думаю, я решил это с помощью совета @ jodag - просто вычислил якобиан и взял диагональ.
Рассмотрим следующую сеть:

import torch
from torch.autograd import grad
import torch.nn as nn
import torch.optim as optim

class net_x(nn.Module): 
        def __init__(self):
            super(net_x, self).__init__()
            self.fc1=nn.Linear(1, 20) 
            self.fc2=nn.Linear(20, 20)
            self.out=nn.Linear(20, 4) #a,b,c,d

        def forward(self, x):
            x=torch.tanh(self.fc1(x))
            x=torch.tanh(self.fc2(x))
            x=self.out(x)
            return x

nx = net_x()

#input
t = torch.tensor([1.0, 2.0, 3.2], requires_grad = True) #input vector
t = torch.reshape(t, (3,1)) #reshape for batch

До сих пор мой подход заключался в переборе входных данных, поскольку grad хочет скалярное значение, как упомянуто выше:

#method 1
for timestep in t:
    y = nx(timestep) 
    print(grad(y[0],timestep, retain_graph=True)) #0 for the first vector (i.e "a"), 1 for the 2nd vector (i.e "b")

>>>
(tensor([-0.0142]),)
(tensor([-0.0517]),)
(tensor([-0.0634]),)

Использование диагонали якобиана кажется более эффективным и дает те же результаты:

#method 2
dx = torch.autograd.functional.jacobian(lambda t_: nx(t_), t)
dx = torch.diagonal(torch.diagonal(dx, 0, -1), 0)[0] #first vector
#dx = torch.diagonal(torch.diagonal(dx, 1, -1), 0)[0] #2nd vector
#dx = torch.diagonal(torch.diagonal(dx, 2, -1), 0)[0] #3rd vector
#dx = torch.diagonal(torch.diagonal(dx, 3, -1), 0)[0] #4th vector
dx

>>>
tensor([-0.0142, -0.0517, -0.0634])
person Penguin    schedule 30.04.2021