【问题标题】:Using PyTorch's autograd efficiently with tensors by calculating the Jacobian通过计算雅可比行列有效地使用 PyTorch 的 autograd 和张量
【发布时间】:2021-05-17 09:30:17
【问题描述】:

在我之前的question 中,我发现了如何将 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) #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

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

问题在于grad 只知道如何从标量张量(我的网络输出不是)传播梯度,这就是我必须计算雅可比行列式的原因。

但是,这不是很有效而且有点慢,因为我的矩阵很大并且计算整个雅可比矩阵需要一段时间(而且我也没有使用整个雅可比矩阵)。

有没有办法只计算雅可比的对角线(在这个例子中得到 4 个向量)?

似乎有一个open feature request,但似乎没有引起太多关注。

更新 1:
我尝试了@iacob 所说的设置torch.autograd.functional.jacobian(vectorize=True)
但是,这似乎更慢。为了测试这一点,我将网络输出从 4 更改为 400,并将我的输入 t 更改为:

val = 100
t = torch.rand(val, requires_grad = True) #input vector
t = torch.reshape(t, (val,1)) #reshape for batch

没有vectorized = True

Wall time: 10.4 s

与:

Wall time: 14.6 s

【问题讨论】:

    标签: python pytorch


    【解决方案1】:

    好的,结果优先:

    性能(我的笔记本电脑有 RTX-2070,PyTorch 正在使用它):

    # Method 1: Use the jacobian function
    CPU times: user 34.6 s, sys: 165 ms, total: 34.7 s
    Wall time: 5.8 s
    
    # Method 2: Sample with appropriate vectors
    CPU times: user 1.11 ms, sys: 0 ns, total: 1.11 ms
    Wall time: 191 µs
    

    大约快 30000 倍。


    为什么你应该使用backward 而不是jacobian(在你的情况下)

    我不是 PyTorch 的专家。但是,根据我的经验,如果您不需要其中的所有元素,那么计算 jacobi-matrix 是非常低效的。

    如果你只需要对角元素,你可以使用backward函数来计算vector-jacobian乘法与一些特定的向量。如果您正确设置向量,您可以从 Jacobi 矩阵中采样/提取特定元素。

    一点线性代数:

    j = np.array([[1,2],[3,4]]) # 2x2 jacobi you want 
    sv = np.array([[1],[0]])     # 2x1 sampling vector
    
    first_diagonal_element = sv.T.dot(j).dot(sv)  # it's j[0, 0]
    

    对于这个简单的案例来说,它并没有那么强大。但是如果 PyTorch 需要计算所有的雅可比矩阵(j 可能是一长串矩阵-矩阵乘法的结果),那就太慢了。相反,如果我们计算一个向量-雅可比乘法序列,计算速度会非常快。


    解决方案

    来自 jacobian 的示例元素:

    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, 400) #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
    
    val = 100
    a = torch.rand(val, requires_grad = True) #input vector
    t = torch.reshape(a, (val,1)) #reshape for batch
    
    
    #method 
    %time 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
    print(dx)
    
    
    out = nx(t)
    m = torch.zeros((val,400))
    m[:, 0] = 1
    %time out.backward(m)
    print(a.grad)
    

    a.grad 应该等于第一个张量dx。而且,m 是与代码中所谓的“第一个向量”相对应的采样向量。


    1. 但如果我再次运行它,答案将会改变。

    是的,你已经明白了。每次调用backward 时,梯度都会累积。因此,如果您必须多次运行该单元格,则必须先将 a.grad 设置为零。

    1. 您能解释一下m 方法背后的想法吗?两者都使用torch.zeros 并将列设置为1。另外,为什么毕业生在a而不是t
    • m方法背后的思想是:函数backward计算的其实是vector-jacobian乘法,其中vector代表所谓的“上游梯度”,Jacobi-matrix就是“局部梯度” "(这个 jacobian 也是您使用 jacobian 函数获得的那个,因为您的 lambda 可以被视为单个“本地”操作)。如果您需要来自 jacobian 的一些元素,您可以伪造(或更准确地说,构造)一些“上游梯度”,您可以使用它从 jacobian 中提取特定条目。但是,如果涉及复杂的张量运算,有时可能很难找到这些上游梯度(至少对我而言)。
    • PyTorch 在计算图的叶节点上累积梯度。而且,您的原始代码行t = torch.reshape(t, (3,1)) 失去了叶节点的句柄,t 现在指的是中间节点而不是叶节点。为了访问叶节点,我创建了张量 a

    【讨论】:

    • 哇!我尝试使用backward 很长时间了,但没有成功。 m = torch.zeros((val,400)) 的“技巧”正是缺少的。如果你能解释一下,有两件事我很乐意。 1. 如果我多次运行jacobian 方法,我似乎总是得到相同的答案。然而,我第一次运行 backward 方法时,我得到了与 jacobian 相同的答案,但如果我再次运行它,答案将会改变。 2.你能解释一下m方法背后的想法吗?两者都使用torch.zeros 并将列设置为1。另外,为什么grad 是在a 而不是t
    • 好的,我每次调用它时都修复了添加的渐变(这就是答案发生变化的原因)。我添加了这个:a.grad.detach_() a.grad.zero_()
    • 完美!!这很有意义,而且效果很好。太感谢了。一个月来一直在努力解决这个问题
    • @Penguin 我只记得我应该解释一下:out = nx(t) 可能会给人一种错觉,如果你运行out.backward(),梯度将被计算并存储在t 中。但事实并非如此。计算图类似于:a -(reshape)-> t -(nx)-> out,其中a 是叶节点,t 是中间节点。所以,如果你运行out.backward(),只有a 有渐变。
    • 有趣。我实际上遇到了一个奇怪的问题,我想知道您刚才所说的是否可以解释它。我在计算中使用上述相同的方法,问题是如果我的输入有 100 个点 (len(a)=100),y_actualy_predict 之间的差异为 50:y-y_predict = tensor([50.1337, 49.6812, 50.0728...。如果我的输入大小为 50 点,则差异为 25。如果我的输入大小为 25,则差异为 12.5。我想知道是不是因为渐变或其他原因。感谢帮助和解释!
    【解决方案2】:

    你试过设置torch.autograd.functional.jacobian(vectorize=True)吗?

    vectorizebool,可选) - 此功能是实验性的,请自行承担使用风险。在计算雅可比时,通常我们在雅可比的每一行调用一次autograd.grad。如果这个标志是True,我们使用vmap原型特性作为后端来向量化对autograd.grad的调用,所以我们只调用它一次而不是每行一次。这应该会在许多用例中带来性能改进,但是,由于此功能不完整,可能会出现性能悬崖。请使用 torch._C._debug_only_display_vmap_fallback_warnings(True) 显示任何性能警告,如果您的用例存在警告,请向我们提交问题。默认为False

    【讨论】:

      猜你喜欢
      • 2021-07-23
      • 1970-01-01
      • 2019-05-10
      • 1970-01-01
      • 2021-01-23
      • 2020-12-28
      • 2020-05-01
      • 2020-03-28
      • 2021-09-20
      相关资源
      最近更新 更多