autograd.grad 和 autograd.backward 之间的区别?

uns*_*ium 13 gradient backpropagation gradient-descent pytorch autograd

假设我有自定义损失函数,并且我想在神经网络的帮助下拟合某些微分方程的解。因此,在每次前向传递中,我都会计算神经网络的输出,然后通过采用 MSE 和我想要拟合感知器的预期方程来计算损失。

现在我的疑问是:我应该使用grad(loss)反向loss.backward()传播来计算和更新我的梯度吗?

我知道,在使用 loss.backward() 时,我必须用 Variable 包装我的张量,并且必须为我想要获取损失梯度的变量设置 require_grad = True 。

所以我的问题是:

  • 是否grad(loss)还需要任何此类显式参数来识别梯度计算的变量?
  • 它实际上是如何计算梯度的?
  • 哪种方法更好?
  • 在实际场景中两者的主要区别是什么?

如果您能解释这两种方法的实际含义,那就更好了,因为每当我试图在网上找到它时,我都会被很多与我的项目不太相关的东西轰炸。

Iva*_*van 18

太长了;两者是执行梯度计算的两个不同接口:torch.autograd.gradis 是不可变的,而torch.autograd.backwardis 是。

\n
\n

描述

\n

torch.autograd模块是PyTorch的自动微分包。如文档中所述,只需对代码库进行最小的更改即可使用:

\n
\n

您只需要声明Tensor应使用关键字计算梯度的 s requires_grad=True

\n
\n

torch.autograd提供梯度计算的两个主要函数是torch.autograd.backwardtorch.autograd.grad

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n
torch.autograd.backward来源torch.autograd.grad来源
描述计算给定张量相对于图 leaves的梯度总和。计算并返回输出相对于输入的梯度总和。
标头torch.autograd.backward(
tensors,
grad_tensors=None,
retain_graph=None,
create_graph=False,
grad_variables=None,
inputs=None)
torch.autograd.grad(
outputs,
inputs,
grad_outputs=None,
retain_graph=None,
create_graph=False,
only_inputs=True,
allow_unused=False)
参数- tensors\xe2\x80\x93 将计算导数的张量。
- grad_tensors\xe2\x80\x93 雅可比向量乘积中的“向量”,通常是对应张量的每个元素的梯度。
- retain_graph\xe2\x80\x93 如果False,则用于计算梯度的图将被释放。[...]
- inputs\xe2\x80\x93 输入梯度将被累积到 中.grad。所有其他张量将被忽略。如果未提供,梯度将累积到所有使用的叶张量中[...]。
- outputs\xe2\x80\x93 微分函数的输出。
- inputs\xe2\x80\x93 输入梯度将被返回(并且不累积到.grad)。
- grad_tensors\xe2\x80\x93 雅可比向量乘积中的“向量”,通常是对应张量的每个元素的梯度。
- retain_graph\xe2\x80\x93 如果False,则用于计算梯度的图将被释放。[...]。
\n

\n

使用示例

\n

就高级用法而言,您可以将其视为torch.autograd.grad不可变函数。正如上面的文档表中提到的,它不会累积属性上的梯度grad,而是返回计算的偏导数。相反,torch.autograd.backward可以通过更新grad叶节点的属性来改变张量,该函数不会返回任何值。换句话说,当计算大量参数的梯度时,后者更适合。

\n

接下来,我们将采用两个输入(x1和 ,x2),用它们计算张量y,然后计算结果对两个输入( dL/dx1和 )的偏导数dL/dx2

\n
>>> x1 = torch.rand(1, requires_grad=True)\n>>> x2 = torch.rand(1, requires_grad=True)\n>>> x1, x2\n(tensor(0.3939, grad_fn=<UnbindBackward>),\n tensor(0.7965, grad_fn=<UnbindBackward>))\n
Run Code Online (Sandbox Code Playgroud)\n

推理:

\n
>>> y = x1**2 + 5*x2\n>>> y\ntensor(4.1377, grad_fn=<AddBackward0>)\n
Run Code Online (Sandbox Code Playgroud)\n

由于y是使用需要梯度的张量(with requires_grad=True)计算的 - *在torch.no_grad上下文之外。它将grad_fn附加一个功能。此回调用于反向传播到计算图上以计算前面张量节点的梯度。

\n
    \n
  • torch.autograd.grad

    \n

    这里我们提供torch.ones_like(y)grad_outputs.

    \n
    >>> torch.autograd.grad(y, (x1, x2), torch.ones_like(y))\n(tensor(0.7879), tensor(5.))\n
    Run Code Online (Sandbox Code Playgroud)\n

    上面的输出是一个元组,包含分别按出现顺序提供的输入的两个偏导数, dL/dx1dL/dx2

    \n

    这对应于以下计算:

    \n
    # dL/dx1 = dL/dy * dy/dx1 = grad_outputs @ 2*x1\n# dL/dx2 = dL/dy * dy/dx2 = grad_outputs @ 5\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • torch.autograd.backward:相反,它将通过更新grad用于计算输出张量且需要梯度的张量来改变提供的张量。它相当于torch.Tensor.backwardAPI。在这里,我们通过再次定义x1x2、来完成相同的示例y。我们称之为backward

    \n
    >>> # y.backward(torch.ones_like(y))\n>>> torch.autograd.backward(y, torch.ones_like(y))\nNone\n
    Run Code Online (Sandbox Code Playgroud)\n

    x1.grad然后您可以检索和上的梯度x2.grad

    \n
    >>> x1.grad, x2.grad\n(tensor(0.7879), tensor(5.))\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
\n
\n

结论:两者执行相同的操作。它们是与库交互autograd并执行梯度计算的两个不同接口。后者torch.autograd.backward(相当于torch.Tensor.backward)通常用于神经网络训练循环中,以计算模型每个参数的损失的偏导数。

\n

您可以torch.autograd.grad通过阅读我所做的其他答案来了解有关工作原理的更多信息:Meaning of grad_outputs in PyTorch\'s torch.autograd.grad

\n


don*_*loo 2

除了伊万的回答之外,torch.autograd.grad不累积梯度.grad可以避免多线程场景中的竞争情况。

引用 PyTorch 文档https://pytorch.org/docs/stable/notes/autograd.html#non-determinism

如果您在多个线程上同时调用backward(),但具有共享输入(即Hogwild CPU 训练)。由于参数在线程之间自动共享,因此跨线程的向后调用时梯度累积可能会变得不确定,因为两个向后调用可能会访问并尝试累积相同的 .grad 属性。这在技术上是不安全的,可能会导致赛车状况,结果可能无法使用。

但是,如果您使用多线程方法来驱动整个训练过程但使用共享参数,则这是预期的模式,使用多线程的用户应该牢记线程模型并应该期望这种情况发生。用户可以使用函数式 API torch.autograd.grad() 来计算梯度而不是 back() 以避免不确定性。

实现细节https://github.com/pytorch/pytorch/blob/7e3a694b23b383e38f5e39ef960ba8f374d22404/torch/csrc/autograd/functions/accumulate_grad.h