PyTorch 入门指南
学习 PyTorch
图像和视频
音频
后端
强化学习
在生产环境中部署 PyTorch 模型
Profiling PyTorch
代码变换与FX
前端API
扩展 PyTorch
模型优化
并行和分布式训练
边缘端的 ExecuTorch
推荐系统
多模态

介绍 || 张量 || 自动求导 || 构建模型 || TensorBoard 支持 || 训练模型 || 模型理解

Autograd 基础

请跟随下方视频或前往 youtube 进行学习。

PyTorch 的 Autograd 功能是 PyTorch 在构建机器学习项目时灵活且高效的原因之一。它能够快速且简便地计算复杂运算中的多个偏导数(也称为 梯度)。这一操作是基于反向传播的神经网络学习的核心。

Autograd 的强大之处在于它能够在 运行时 动态地追踪您的计算过程,这意味着即使您的模型包含决策分支或循环,且这些循环的长度在运行时才能确定,计算过程仍会被正确追踪,您将获得正确的梯度来驱动学习过程。这一特性,加上您的模型是用 Python 构建的,使得 PyTorch 在灵活性上远超那些依赖对更严格结构的模型进行静态分析来计算梯度的框架。

为什么我们需要 Autograd?

机器学习模型是一个包含输入和输出的函数。在本次讨论中,我们将输入视为一个i维向量 \(\vec{x}\),其元素为 \(x_{i}\)。然后,我们可以将模型 M 表示为输入的向量值函数:\(\vec{y} = \vec{M}(\vec{x})\)。(我们将 M 的输出值视为一个向量,因为通常情况下,模型可能具有任意数量的输出。)

由于我们主要将在训练的背景下讨论自动求导,我们关心的输出将是模型的损失。损失函数 L(\(\vec{y}\)) = L(\(\vec{M}\)(\(\vec{x}\))) 是模型输出的单值标量函数。该函数表示模型的预测与特定输入的理想输出之间的偏差。注意:从这一点开始,我们将在上下文明确的情况下省略向量符号——例如, \(y\) 而不是 \(\vec y\)。

在训练模型时,我们希望最小化损失。在理想情况下,完美模型意味着调整其学习权重——即函数的可调参数——使得所有输入的损失为零。在现实世界中,这意味着一个迭代过程,即不断微调学习权重,直到我们看到对于各种输入都能获得可容忍的损失。

我们如何决定调整权重的幅度和方向?我们希望最小化损失,这意味着使其关于输入的一阶导数等于 0:\(\frac{\partial L}{\partial x} = 0\)。

然而,需要注意的是,损失并不是直接从输入推导出来的,而是模型输出(直接是输入的函数)的函数,即 \(\frac{\partial L}{\partial x}\) = \(\frac{\partial {L({\vec y})}}{\partial x}\)。根据微分链式法则,我们有 \(\frac{\partial {L({\vec y})}}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial y}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial M(x)}{\partial x}\)。

\(\frac{\partial M(x)}{\partial x}\) 是事情变得复杂的地方。模型输出关于其输入的偏导数,如果我们再次使用链式法则展开表达式,将涉及模型中每个相乘的学习权重、每个激活函数以及每个其他数学变换的局部偏导数。每个这样的偏导数的完整表达式是所有通过计算图的可能路径的局部梯度的乘积之和,这些路径以我们试图测量梯度的变量结尾。

特别是,我们对学习权重的梯度非常感兴趣——它们告诉我们每个权重应该朝哪个方向调整,以使损失函数更接近于零。

由于这种局部导数的数量(每一个都对应于模型计算图中的一条独立路径)会随着神经网络深度的增加呈指数级增长,计算它们的复杂性也随之增加。这就是自动求导(autograd)发挥作用的地方:它跟踪每一次计算的历史。在 PyTorch 模型中,每个计算得到的张量都携带了其输入张量和用于创建它的函数的历史记录。结合 PyTorch 中用于操作张量的函数都内置了计算其自身导数的实现这一事实,这大大加快了学习所需的局部导数的计算速度。

一个简单的示例

刚才讲了很多理论内容——但在实践中使用自动求导(autograd)是什么样子呢?

让我们从一个简单的例子开始。首先,我们会导入一些必要的库,以便绘制结果图表:

# %matplotlib inline

importtorch

importmatplotlib.pyplotasplt
importmatplotlib.tickerasticker
importmath

接下来,我们将创建一个在区间 \([0, 2{\pi}]\) 上均匀分布值的输入张量,并指定 requires_grad=True。(与大多数创建张量的函数一样,torch.linspace() 也接受一个可选的 requires_grad 参数。)设置此标志意味着在后续的每次计算中,autograd 都会在该计算的输出张量中累积计算历史。

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
print(a)
tensor([0.0000, 0.2618, 0.5236, 0.7854, 1.0472, 1.3090, 1.5708, 1.8326, 2.0944,
        2.3562, 2.6180, 2.8798, 3.1416, 3.4034, 3.6652, 3.9270, 4.1888, 4.4506,
        4.7124, 4.9742, 5.2360, 5.4978, 5.7596, 6.0214, 6.2832],
       requires_grad=True)

接下来,我们将执行一个计算,并根据其输入绘制输出结果:

b = torch.sin(a)
plt.plot(a.detach(), b.detach())

autogradyt tutorial

[<matplotlib.lines.Line2D object at 0x7f774025c070>]

让我们仔细看一下张量 b。当我们打印它时,可以看到一个指示符,表明它正在追踪其计算历史:

print(b)
tensor([ 0.0000e+00,  2.5882e-01,  5.0000e-01,  7.0711e-01,  8.6603e-01,
         9.6593e-01,  1.0000e+00,  9.6593e-01,  8.6603e-01,  7.0711e-01,
         5.0000e-01,  2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,
        *7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,
        *8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01,  1.7485e-07],
       grad_fn=<SinBackward0>)

这个 grad_fn 提示我们,当执行反向传播步骤并计算梯度时,我们需要为这个张量的所有输入计算 \(\sin(x)\) 的导数。

让我们进行更多计算:

c = 2 * b
print(c)

d = c + 1
print(d)
tensor([ 0.0000e+00,  5.1764e-01,  1.0000e+00,  1.4142e+00,  1.7321e+00,
         1.9319e+00,  2.0000e+00,  1.9319e+00,  1.7321e+00,  1.4142e+00,
         1.0000e+00,  5.1764e-01, -1.7485e-07, -5.1764e-01, -1.0000e+00,
        *1.4142e+00, -1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00,
        *1.7321e+00, -1.4142e+00, -1.0000e+00, -5.1764e-01,  3.4969e-07],
       grad_fn=<MulBackward0>)
tensor([ 1.0000e+00,  1.5176e+00,  2.0000e+00,  2.4142e+00,  2.7321e+00,
         2.9319e+00,  3.0000e+00,  2.9319e+00,  2.7321e+00,  2.4142e+00,
         2.0000e+00,  1.5176e+00,  1.0000e+00,  4.8236e-01, -3.5763e-07,
        *4.1421e-01, -7.3205e-01, -9.3185e-01, -1.0000e+00, -9.3185e-01,
        *7.3205e-01, -4.1421e-01,  4.7684e-07,  4.8236e-01,  1.0000e+00],
       grad_fn=<AddBackward0>)

最后,让我们计算一个单一元素的输出。当您在没有参数的情况下对一个张量调用 .backward() 时,它期望调用张量只包含一个元素,就像在计算损失函数时的情况一样。

out = d.sum()
print(out)
tensor(25., grad_fn=<SumBackward0>)

每个与张量存储在一起的 grad_fn 都允许您通过其 next_functions 属性追溯计算过程,直至其输入。我们可以在下面看到,深入挖掘 d 上的这个属性会展示出所有先前张量的梯度函数。请注意,a.grad_fn 被报告为 None,这表明它是一个没有自身历史记录的输入。

print('d:')
print(d.grad_fn)
print(d.grad_fn.next_functions)
print(d.grad_fn.next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions[0][0].next_functions)
print('\nc:')
print(c.grad_fn)
print('\nb:')
print(b.grad_fn)
print('\na:')
print(a.grad_fn)
d:
<AddBackward0 object at 0x7f7740744370>
((<MulBackward0 object at 0x7f7740745b70>, 0), (None, 0))
((<SinBackward0 object at 0x7f7740745b70>, 0), (None, 0))
((<AccumulateGrad object at 0x7f7740744370>, 0),)
()

c:
<MulBackward0 object at 0x7f7740745b70>

b:
<SinBackward0 object at 0x7f7740745b70>

a:
None

在所有这些机制准备就绪后,我们如何获取导数呢?您可以在输出上调用 backward() 方法,并检查输入的 grad 属性以查看梯度:

out.backward()
print(a.grad)
plt.plot(a.detach(), a.grad.detach())

autogradyt tutorial

tensor([ 2.0000e+00,  1.9319e+00,  1.7321e+00,  1.4142e+00,  1.0000e+00,
         5.1764e-01, -8.7423e-08, -5.1764e-01, -1.0000e+00, -1.4142e+00,
        *1.7321e+00, -1.9319e+00, -2.0000e+00, -1.9319e+00, -1.7321e+00,
        *1.4142e+00, -1.0000e+00, -5.1764e-01,  2.3850e-08,  5.1764e-01,
         1.0000e+00,  1.4142e+00,  1.7321e+00,  1.9319e+00,  2.0000e+00])

[<matplotlib.lines.Line2D object at 0x7f774064a170>]

回顾我们到达此处的计算步骤:

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
b = torch.sin(a)
c = 2 * b
d = c + 1
out = d.sum()

添加一个常量,就像我们计算 d 时所做的,不会改变导数。于是剩下 \(c = 2 * b = 2 * \sin(a)\),其导数应该是 \(2 * \cos(a)\)。观察上面的图表,这正是我们所看到的。

需要注意的是,只有计算中的叶节点才会计算其梯度。例如,如果你尝试 print(c.grad),你会得到 None。在这个简单的例子中,只有输入是叶节点,因此只有它计算了梯度。

训练中的自动求导

我们已经简要了解了 autograd 的工作原理,但当它用于其预期目的时,情况又是怎样的呢?让我们定义一个简单的模型,并观察它在一次训练批次后的变化。首先,定义一些常量、我们的模型,以及输入和输出的占位符:

BATCH_SIZE = 16
DIM_IN = 1000
HIDDEN_SIZE = 100
DIM_OUT = 10

classTinyModel(torch.nn.Module):

    def__init__(self):
        super(TinyModel, self).__init__()

        self.layer1 = torch.nn.Linear(DIM_IN, HIDDEN_SIZE)
        self.relu = torch.nn.ReLU()
        self.layer2 = torch.nn.Linear(HIDDEN_SIZE, DIM_OUT)

    defforward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

some_input = torch.randn(BATCH_SIZE, DIM_IN, requires_grad=False)
ideal_output = torch.randn(BATCH_SIZE, DIM_OUT, requires_grad=False)

model = TinyModel()

你可能会注意到,我们从未为模型的层指定 requires_grad=True。在 torch.nn.Module 的子类中,默认假定我们希望跟踪层的权重以计算梯度用于学习。

如果我们查看模型的层,可以检查权重的值,并确认尚未计算任何梯度:

print(model.layer2.weight[0][0:10]) # just a small slice
print(model.layer2.weight.grad)
tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
        *0.0086,  0.0157], grad_fn=<SliceBackward0>)
None

让我们看看在运行一个训练批次时这会发生什么变化。对于损失函数,我们将使用 predictionideal_output 之间的欧几里得距离的平方,并且我们将使用一个基本的随机梯度下降优化器。

optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

prediction = model(some_input)

loss = (ideal_output - prediction).pow(2).sum()
print(loss)
tensor(211.2634, grad_fn=<SumBackward0>)

现在,让我们调用 loss.backward() 看看会发生什么:

loss.backward()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])
tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
        *0.0086,  0.0157], grad_fn=<SliceBackward0>)
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])

我们可以看到,已经为每个学习权重计算了梯度,但权重保持不变,因为我们尚未运行优化器。优化器负责根据计算出的梯度更新模型权重。

optimizer.step()
print(model.layer2.weight[0][0:10])
print(model.layer2.weight.grad[0][0:10])
tensor([ 0.0791,  0.0886,  0.0098,  0.0064, -0.0106,  0.0293,  0.0186, -0.0300,
        *0.0088,  0.0211], grad_fn=<SliceBackward0>)
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])

您应该会看到 layer2 的权重已经发生了变化。

关于这个过程有一个重要的注意事项:在调用 optimizer.step() 之后,您需要调用 optimizer.zero_grad(),否则每次运行 loss.backward() 时,学习权重的梯度将会累积:

print(model.layer2.weight.grad[0][0:10])

for i in range(0, 5):
    prediction = model(some_input)
    loss = (ideal_output - prediction).pow(2).sum()
    loss.backward()

print(model.layer2.weight.grad[0][0:10])

optimizer.zero_grad(set_to_none=False)

print(model.layer2.weight.grad[0][0:10])
tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
         0.1732, -5.3835])
tensor([ 19.2095, -15.9459,   8.3306,  11.5096,   9.5471,   0.5391,  -0.3370,
          8.6386,  -2.5141, -30.1419])
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

在运行上述单元后,您会发现多次运行 loss.backward() 后,大多数梯度的大小会变得更大。如果在运行下一个训练批次之前没有将梯度清零,将会导致梯度以这种方式爆炸,造成不正确且不可预测的学习结果。

启用和禁用 Autograd

在某些情况下,您需要对是否启用自动求导进行精细控制。根据具体情况,有几种方法可以实现这一点。

最简单的方法是直接在张量上更改 requires_grad 标志:

a = torch.ones(2, 3, requires_grad=True)
print(a)

b1 = 2 * a
print(b1)

a.requires_grad = False
b2 = 2 * a
print(b2)
tensor([[1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
tensor([[2., 2., 2.],
        [2., 2., 2.]], grad_fn=<MulBackward0>)
tensor([[2., 2., 2.],
        [2., 2., 2.]])

在上面的代码单元中,我们看到 b1 有一个 grad_fn(即一个追踪的计算历史),这是我们预期的,因为它是从启用了自动求导的张量 a 派生而来的。当我们通过 a.requires_grad = False 显式关闭自动求导时,计算历史不再被追踪,正如我们在计算 b2 时看到的那样。

如果你只需要暂时关闭自动求导,更好的方式是使用 torch.no_grad()

a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = a + b
print(c1)

with torch.no_grad():
    c2 = a + b

print(c2)

c3 = a * b
print(c3)
tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])
tensor([[6., 6., 6.],
        [6., 6., 6.]], grad_fn=<MulBackward0>)

torch.no_grad() 也可以用作函数或方法装饰器:

defadd_tensors1(x, y):
    return x + y

@torch.no_grad()
defadd_tensors2(x, y):
    return x + y


a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = add_tensors1(a, b)
print(c1)

c2 = add_tensors2(a, b)
print(c2)
tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])

有一个对应的上下文管理器 torch.enable_grad(),用于在未启用自动求导时启用它。它也可以用作装饰器。

最后,您可能有一个需要梯度跟踪的张量,但您想要一个不需要梯度跟踪的副本。为此,我们提供了 Tensor 对象的 detach() 方法——它会创建一个与计算历史分离的张量副本:

x = torch.rand(5, requires_grad=True)
y = x.detach()

print(x)
print(y)
tensor([0.0670, 0.3890, 0.7264, 0.3559, 0.6584], requires_grad=True)
tensor([0.0670, 0.3890, 0.7264, 0.3559, 0.6584])

我们在上面想要绘制一些张量时就这样做了。这是因为 matplotlib 期望输入的是一个 NumPy 数组,而对于 requires_grad=True 的张量,从 PyTorch 张量到 NumPy 数组的隐式转换是禁用的。创建一个分离的副本可以让我们继续操作。

自动求导与原地操作

在本笔记的所有示例中,我们一直使用变量来捕获计算的中间值。Autograd需要这些中间值来执行梯度计算。*因此,在使用autograd时,您必须小心使用原地操作。*这样做可能会破坏您在backward()调用中计算导数所需的信息。如果您尝试对需要autograd的叶子变量执行原地操作,PyTorch甚至会阻止您,如下所示。

以下代码单元会抛出一个运行时错误。这是预期的结果。

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
torch.sin_(a)

Autograd 性能分析器

Autograd 会详细跟踪您计算的每一步。这种计算历史与时间信息相结合,可以成为一个方便的性能分析工具——而 autograd 已经内置了这一功能。以下是一个简单的使用示例:

device = torch.device('cpu')
run_on_gpu = False
if torch.cuda.is_available():
    device = torch.device('cuda')
    run_on_gpu = True

x = torch.randn(2, 3, requires_grad=True)
y = torch.rand(2, 3, requires_grad=True)
z = torch.ones(2, 3, requires_grad=True)

with torch.autograd.profiler.profile(use_cuda=run_on_gpu) as prf:
    for _ in range(1000):
        z = (z / x) * y

print(prf.key_averages().table(sort_by='self_cpu_time_total'))
/var/lib/workspace/beginner_source/introyt/autogradyt_tutorial.py:485: FutureWarning:

The attribute `use_cuda` will be deprecated soon, please use ``use_device = 'cuda'`` instead.

*------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
                     Name    Self CPU %      Self CPU   CPU total %     CPU total  CPU time avg     Self CUDA   Self CUDA %    CUDA total  CUDA time avg    # of Calls
*------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
          cudaEventRecord        41.02%       8.818ms        41.02%       8.818ms       2.204us       0.000us         0.00%       0.000us       0.000us          4000
                aten::div        29.65%       6.375ms        29.65%       6.375ms       6.375us      10.926ms        50.35%      10.926ms      10.926us          1000
                aten::mul        29.27%       6.293ms        29.27%       6.293ms       6.293us      10.774ms        49.65%      10.774ms      10.774us          1000
    cudaDeviceSynchronize         0.06%      11.863us         0.06%      11.863us      11.863us       0.000us         0.00%       0.000us       0.000us             1
*------------------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------  ------------
Self CPU time total: 21.497ms
Self CUDA time total: 21.700ms

性能分析器还可以标记代码的各个子块,按输入张量形状分解数据,并将数据导出为 Chrome 追踪工具文件。有关 API 的完整详细信息,请参阅文档

高级主题:更多 Autograd 细节与高级 API

如果您有一个具有n维输入和m维输出的函数,即 (\vec{y}=f(\vec{x})),那么完整的梯度是一个矩阵,表示每个输出相对于每个输入的导数,称为雅可比矩阵(Jacobian)

\[J = \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\]

如果您有第二个函数 \(l=g\left(\vec{y}\right)\),它接收 m 维输入(即与上述输出的维度相同),并返回一个标量输出,您可以将其关于 \(\vec{y}\) 的梯度表示为一个列向量,\(v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\) ——这实际上就是一个单列的雅可比矩阵。

更具体地说,可以将第一个函数视为您的 PyTorch 模型(可能有多个输入和多个输出),而第二个函数视为损失函数(以模型的输出作为输入,损失值作为标量输出)。

如果我们用第一个函数的雅可比矩阵乘以第二个函数的梯度,并应用链式法则,我们得到:

\[J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)\]

注意:您也可以使用等价操作 \(v^{T}\cdot J\),并得到一个行向量。

生成的列向量是第二个函数相对于第一个函数输入的梯度——或者在我们的模型和损失函数的情况下,损失相对于模型输入的梯度。

``torch.autograd`` 是一个用于计算这些乘积的引擎。 这就是我们在反向传播过程中累积学习权重梯度的方式。

因此,backward() 调用也可以接受一个可选的向量输入。该向量表示张量上的一组梯度,这些梯度会与自动微分跟踪的张量的雅可比矩阵相乘。让我们用一个小的向量来尝试一个具体的例子:

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)
tensor([  299.4868,   425.4009, -1082.9885], grad_fn=<MulBackward0>)

如果我们现在尝试调用 y.backward(),将会出现一个运行时错误,并且会提示我们梯度只能隐式地计算标量输出。对于多维输出,autograd 期望我们提供这些输出的梯度,以便它可以将这些梯度与雅可比矩阵相乘:

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) # stand-in for gradients
y.backward(v)

print(x.grad)
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

(请注意,输出梯度都与2的幂相关——这在重复的加倍操作中是预期的。)

高级 API

autograd 提供了一个 API,使您能够直接访问重要的微分矩阵和向量操作。特别是,它允许您计算特定函数在特定输入下的雅可比矩阵和海森矩阵。(海森矩阵类似于雅可比矩阵,但它表示所有二阶偏导数。)它还提供了与这些矩阵进行向量乘积的方法。

让我们以一个简单函数的雅可比矩阵为例,针对两个单元素输入进行评估:

defexp_adder(x, y):
    return 2 * x.exp() + 3 * y

inputs = (torch.rand(1), torch.rand(1)) # arguments for the function
print(inputs)
torch.autograd.functional.jacobian(exp_adder, inputs)
(tensor([0.7212]), tensor([0.2079]))

(tensor([[4.1137]]), tensor([[3.]]))

如果你仔细观察,第一个输出应该等于 \(2e^x\)(因为 \(e^x\) 的导数是 \(e^x\)),第二个值应该是 3。

当然,你也可以使用更高阶的张量来实现这一点:

inputs = (torch.rand(3), torch.rand(3)) # arguments for the function
print(inputs)
torch.autograd.functional.jacobian(exp_adder, inputs)
(tensor([0.2080, 0.2604, 0.4415]), tensor([0.5220, 0.9867, 0.4288]))

(tensor([[2.4623, 0.0000, 0.0000],
        [0.0000, 2.5950, 0.0000],
        [0.0000, 0.0000, 3.1102]]), tensor([[3., 0., 0.],
        [0., 3., 0.],
        [0., 0., 3.]]))

torch.autograd.functional.hessian() 方法的工作原理与之相同(假设您的函数是二次可微的),但它返回的是所有二阶导数的矩阵。

如果您提供了向量,还有一个函数可以直接计算向量-雅可比积:

defdo_some_doubling(x):
    y = x * 2
    while y.data.norm() < 1000:
        y = y * 2
    return y

inputs = torch.randn(3)
my_gradients = torch.tensor([0.1, 1.0, 0.0001])
torch.autograd.functional.vjp(do_some_doubling, inputs, v=my_gradients)
(tensor([-665.7186, -866.7054,  -58.4194]), tensor([1.0240e+02, 1.0240e+03, 1.0240e-01]))

torch.autograd.functional.jvp() 方法与 vjp() 执行相同的矩阵乘法,但操作数的顺序相反。vhp()hvp() 方法则对向量-Hessian 乘积执行相同的操作。

更多信息,包括性能说明,请参阅 功能 API 文档

本页目录