【问题标题】:How to differentiate a gradient in Pytorch如何区分 Pytorch 中的渐变
【发布时间】:2021-04-08 06:17:38
【问题描述】:

我正在尝试区分 PyTorch 中的渐变。我找到了this 链接,但无法使用。

我的代码如下:

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(2, 20) 
            self.fc2=nn.Linear(20, 20)
            self.out=nn.Linear(20, 4) 

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

nx = net_x()
r = torch.tensor([1.0,2.0])
nx(r)
>>>tensor([-0.2356, -0.7315, -0.2100, -0.6741], grad_fn=<AddBackward0>)

但是当我尝试根据第一个参数来区分函数时

grad(nx, r[0])

我得到了错误

TypeError: 'net_x' object is not iterable

更新 试图将其扩展到张量:
由于某种原因,所有输入的梯度都是相同的。

a = torch.rand((8,2), requires_grad=True)
s = []
s_t = []
for input_tensor in a:
    output_tensor = nx(input_tensor)
    s.append(output_tensor[0])
    s_t_value = grad(output_tensor[0], input_tensor)[0][0]
    s_t.append(s_t_value)
print(s_t)

但是输出是:

[tensor(-0.1326), tensor(-0.1326), tensor(-0.1326), tensor(-0.1326), tensor(-0.1326), tensor(-0.1326), tensor(-0.1326), tensor(-0.1326)]

【问题讨论】:

  • 我觉得问题在于您将模型输入到 grad 函数中。在您链接的示例中,我认为他使用中间人函数来获取梯度,因为作为第一个参数的输出需要是张量序列,而不是全尺寸模型,对吗?
  • @Canbach 你可能是对的。我不知道如何改变它。有什么建议吗?

标签: python pytorch


【解决方案1】:

如果您想获得关于r 的渐变,首先要更改的是将此张量的requires_grad 标志设置为True:

nx = net_x()
r = torch.tensor([1.0,2.0], requires_grad=True)

然后,如autograd documentation 中所述,grad 计算输出相对于输入的梯度,因此您需要保存模型的输出:

y = nx(r)

现在您可以计算关于r 的梯度。但还有最后一个问题:grad 只知道如何从标量张量传播梯度,y 不知道。所以你需要计算每个坐标的梯度:

for x in y:
    print(grad(x, r, retain_graph=True))

或等效:

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

你需要retain_graph,因为没有这个标志,计算图在第一次梯度传播后被清除。这就是nx(r) 的每个坐标相对于r 的导数!

在 cmets 中回答您的问题:

不是错误,是正常的。所以你有一个大小为 (B, 2) 的批量输入,其中 B = 8。你得到一个形状为 (B, 4) 的批量输出。现在,对于批处理输出的每个向量,对于该向量的每个坐标,您可以计算关于批处理输入的导数,这将产生大小为 (B,2) 的梯度,如下所示:

for b in y: # There a B vectors b of shape (4)
    for x in b: # There are 4 coordinates
        # This prints a tensor of shape (B, 2)
        print(grad(x, r, retain_graph=True))

现在请记住批处理的工作方式:所有批处理一起计算以获取 GPU 的功能,但它们实际上是完全独立的。所以所有b 向量实际上是来自不同输入的网络结果。这意味着,如果 i!=j,第 i 个向量 b 相对于输入的第 j 个向量的梯度必须为 0。那有意义吗 ?这就像计算f(x,y) = (x^2, y^2)y^2x 的导数显然是 0 !好吧,将xy 视为一批中的两个样本,你有你解释为什么你的结果中有很多0。

最后一个代码示例使其更加清晰:

inputs = [torch.randn(1, 2, requires_grad=True) for i in range(8)]
r = torch.cat(inputs) # shape : (8, 2)
y = nx(r) # shape : (8, 4)
for i in range(len(y)):
    print(f"Gradients of y[{i}] wrt r[{i}]") 
    for x in y[i]:
        # prints a tensor of size (2)
        print(grad(x, inputs[i], retain_graph=True))

关于为什么所有渐变都相同。这是因为你的神经网络是完全线性的。您有 3 个nn.Linear 层,并且没有非线性激活函数(因此,这实际上等同于只有一层的网络)。线性层的一个特性是它们的梯度是恒定的:d(alpha*x)/dx = alpha(独立于x)。因此梯度在所有维度上都是相同的。只需添加非线性激活层,如 sigmoid,这种行为就不会再次发生。

【讨论】:

  • 太棒了!谢谢你解释得这么好。所以为了澄清,这个y操作的第一个输出x是我正在寻找的第一个参数(即1.0)的梯度?看起来是(tensor([-0.1564, 0.1480]),)-0.1564 是我正在寻找的价值吗?我也应该更具体一点,我只对第一个坐标(即y[0])相对于r(即r[0])的第一个参数的梯度感兴趣
  • 是的(我添加了另一个代码截图来澄清),你得到的第一个printy[0] 相对于r 的渐变。它是一个向量,每个坐标都是相对于r 的对应坐标的导数(所以r[0]r[1] ....)。因此,如果您需要y[0] 相对于r[0] 的梯度,您正在寻找第一个打印向量的第一个坐标
  • 另外,有没有办法将其扩展到张量?如果我有a = torch.ones((8,2), requires_grad=True)net_output = nx(a)。我尝试调用grad(net_output[:,0], a[:,0], retain_graph=True),但收到错误RuntimeError: grad can be implicitly created only for scalar outputs。我相信你谈到了这部分,但到目前为止我无法修复它。有什么建议吗?
  • 是的,这条消息意味着你试图在一个非标量张量上调用grad,这里是net_output[:,0]。这就是我做for 循环的原因:您需要遍历张量的每个坐标,您无法计算向量相对于向量的梯度。我不知道net_output[:,0] 的形状,但我知道它不是标量
  • 啊,有道理!因此,由于a 是 (8,2),net_output 的大小为 (8,4),因为 net_x 每个输入输出 4 个值。通过执行net_output[:,0],我试图获取第一个参数(就像你之前向我展示的那样)。无论如何,你建议的工作:for x in net_output: print(grad(x[0], a, retain_graph=True))。这不会带来错误,但似乎我再次得到相同的张量:它充满了零,除了每次迭代都会下降的 1 行。真奇怪
猜你喜欢
  • 1970-01-01
  • 2019-08-27
  • 2018-08-15
  • 2019-07-10
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 2018-09-14
相关资源
最近更新 更多