Autograd 机制¶
此说明将概述 autograd 的工作原理,并记录 操作。了解所有这些并非绝对必要,但我们建议 熟悉它,因为它会帮助你更高效、更清晰地写作 程序,并可以帮助您进行调试。
autograd 如何对历史记录进行编码¶
Autograd 是一种反向自动微分系统。概念 autograd 会录制一个图表,记录 执行操作时的数据,为您提供有向无环图 其叶子是输入张量,根是输出张量。 通过从根到叶跟踪此图表,您可以自动 使用链式法则计算梯度。
在内部,autograd 将此图表示为对象图(实际上是表达式),可以将其用于计算
评估图形。在计算前向传递时,autograd
同时执行请求的计算并构建图形
表示计算梯度的函数(每个的属性是此图的入口点)。
前向传递完成后,我们在
backwards 传递来计算梯度。
Function
apply()
.grad_fn
需要注意的重要一点是,该图是在每个 迭代,而这正是允许使用任意 Python 控制的原因 flow 语句,这些语句可以在 每次迭代。您不必在编码之前对所有可能的路径进行编码 启动培训 - 您运行的内容就是您的差异化。
保存的张量¶
某些操作需要在正向传递期间保存中间结果 以执行向后传递。例如,函数保存输入来计算梯度。
定义自定义 Python 时,您可以使用保存
张量并检索它们
在向后传球期间。有关更多信息,请参阅扩展 PyTorch。
save_for_backward()
saved_tensors
对于 PyTorch 定义的操作(例如 ),张量为
根据需要自动保存。您可以探索 (用于教育或调试
目的)通过查找其
以前缀 .
grad_fn
_saved
x = torch.randn(5, requires_grad=True)
y = x.pow(2)
print(x.equal(y.grad_fn._saved_self)) # True
print(x is y.grad_fn._saved_self) # True
在前面的代码中,引用与 x 相同的 Tensor 对象。
但情况可能并非总是如此。例如:y.grad_fn._saved_self
x = torch.randn(5, requires_grad=True)
y = x.exp()
print(y.equal(y.grad_fn._saved_result)) # True
print(y is y.grad_fn._saved_result) # False
在后台,为了防止引用循环,PyTorch 打包了张量
保存并将其解压缩到不同的 Tensor 中以供读取。在这里,
从 access 获得的 Tensor 是不同的 Tensor
对象(但它们仍共享相同的存储空间)。y.grad_fn._saved_result
y
一个 Tensor 是否会被打包到不同的 Tensor 对象中取决于 是否是自身grad_fn的输出,即实现细节 可能会发生变化,用户不应依赖。
您可以控制 PyTorch 如何使用 Hook 对保存的张量进行打包/解包。
不可微函数的梯度¶
使用自动微分的梯度计算仅在使用的每个初等函数都是可微分的时才有效。
不幸的是,我们在实践中使用的许多函数都没有这个属性(例如 , 或 at )。
为了尝试减少不可微函数的影响,我们通过按顺序应用以下规则来定义初等运算的梯度:relu
sqrt
0
如果该函数是可微分的,因此在当前点存在梯度,则使用它。
如果函数是凸的(至少是局部的),则使用 minimum 范数的子梯度(这是最陡的下降方向)。
如果函数是凹的(至少是局部的),请使用 minimum 范数的超级梯度(考虑 -f(x) 并应用前一点)。
如果定义了函数,则通过连续性定义当前点的梯度(请注意,这里是可能的,例如 )。如果可能有多个值,请任意选择一个值。
inf
sqrt(0)
如果未定义函数(例如,当 input 为 时,或大多数函数),则用作梯度的值是任意的(我们也可能引发错误,但不能保证)。大多数函数将用作渐变,但出于性能原因,某些函数将使用其他值(例如)。
sqrt(-1)
log(-1)
NaN
NaN
log(-1)
如果函数不是确定性映射(即它不是数学函数),它将被标记为不可微分。如果用于需要在环境之外 grad 的张量,这将使其向后出错。
no_grad
在本地禁用梯度计算¶
Python 提供了多种机制来在本地禁用渐变 计算:
要禁用整个代码块的渐变,可以使用上下文管理器
就像 no-grad 模式和推理模式一样。
要从梯度计算中更细粒度地排除子图,
有设置张量的字段。requires_grad
下面,除了讨论上述机制外,我们还描述了
evaluation mode () (),一种未使用的方法
禁用梯度计算,但由于其名称,经常与这三者混淆。nn.Module.eval()
设置requires_grad
¶
requires_grad
是一个标志,默认为 false,除非包装
在 中,允许精细排除
梯度计算的子图。它在
向前和向后传递:nn.Parameter
在前向传递期间,如果满足以下条件,则操作仅记录在后向图中
至少有一个 Importing Tensor 需要 grad。
在向后传递 () 期间,只有 的叶张量才会将梯度累积到其字段中。.backward()
requires_grad=True
.grad
需要注意的是,即使每个张量都有此标志,设置它也只对叶张量(没有 的张量 ,例如 a 的参数)有意义。
非叶张量(具有 的张量 )是具有
与它们关联的后向图。因此,将需要它们的梯度
作为中间结果来计算叶张量的梯度,其中
需要 grad。从这个定义可以清楚地看出,所有非叶张量
将自动具有 。grad_fn
nn.Module
grad_fn
require_grad=True
设置应该是您控制哪些部分的主要方式
是梯度计算的一部分,例如,如果需要
在模型微调期间冻结预训练模型的某些部分。requires_grad
要冻结模型的某些部分,只需应用于
您不希望更新的参数。如上所述,
因为使用这些参数作为输入的计算不会记录在
forward 传递时,它们不会在 backward 中更新其字段
传递,因为它们本来就不会成为 backward 图的一部分,因为
期望。.requires_grad_(False)
.grad
因为这是这样常见的模式,所以也可以设置为
模块级别。
当应用于模块时,对所有
模块的参数(默认情况下具有)。requires_grad
nn.Module.requires_grad_()
.requires_grad_()
requires_grad=True
Grad 模式¶
除了设置,还有三种 grad 模式可以
从 Python 中选择,这可能会影响 PyTorch 中的计算方式
由 autograd 内部处理:默认模式(grad 模式)、no-grad 模式、
和 inference 模式,所有这些都可以通过上下文管理器和
装饰。requires_grad
模式 |
将操作排除在后向图中记录 |
跳过额外的 autograd 跟踪开销 |
启用该模式时创建的张量稍后可以在 grad-mode 中使用 |
例子 |
---|---|---|---|---|
违约 |
✓ |
前向传球 |
||
无级 |
✓ |
✓ |
优化器更新 |
|
推理 |
✓ |
✓ |
数据处理、模型评估 |
默认模式(Grad 模式)¶
“默认模式” 是当没有其他模式(如 no-grad 和 inference 模式。与 “no-grad mode” 默认模式有时也称为 “grad mode”。
关于默认模式,要了解的最重要的一点是它是唯一的
模式,该模式生效。 始终被覆盖
以处于其他两种模式。requires_grad
requires_grad
False
无毕业生模式¶
no-grad 模式下的计算表现得好像没有任何 inputs 需要 grad 一样。
换句话说,no-grad 模式下的计算永远不会记录在反向图中
即使有具有 .require_grad=True
当您需要执行不应该执行的操作时,启用 no-grad 模式
由 autograd 录制,但您仍希望使用这些输出
稍后在 grad 模式下进行计算。此上下文管理器可以方便地
禁用代码块或函数的渐变,而不使用
必须临时将张量设置为 have ,然后
返回 。requires_grad=False
True
例如,在编写优化器时,no-grad 模式可能很有用:当 执行训练更新您想要更新参数 就地操作,而无需 Autograd 记录更新。 您还打算使用更新的参数进行 grad 模式。
torch.nn.init 中的实现也 在初始化参数时依赖 no-grad 模式,以避免 Autograd 跟踪。
推理模式¶
推理模式是 no-grad 模式的极端版本。就像在 no-grad 中一样 模式下,推理模式下的计算不会记录在反向图中,但 启用推理模式将使 PyTorch 能够进一步加快您的模型速度。 这种更好的运行时间有一个缺点:在推理模式下创建的张量 将无法用于 autograd 之后记录的计算 退出推理模式。
当您执行没有 与 autograd 交互,并且您不打算使用创建的张量 在推理模式下,在 autograd 稍后将记录的任何计算中。
建议您在代码的各个部分试用推理模式 不需要 Autograd 跟踪(例如,数据处理和模型评估)。 如果开箱即用 对于您的使用案例,这是一个免费的性能胜利。如果您在 启用推理模式,检查您是否没有使用在 退出推理后由 Autograd 记录的计算中的推理模式 模式。如果您无法避免此类使用,您可以随时切换回来 设置为 no-grad 模式。
有关推理模式的详细信息,请参阅 推理模式。
有关推理模式的实现详细信息,请参阅 RFC-0011-InferenceMode。
评估模式 (nn.Module.eval()
)¶
求值模式不是在本地禁用梯度计算的机制。 无论如何,它都包含在这里,因为它有时会被混淆为这样的机制。
从功能上讲,(或等效地)完全
与 no-grad 模式和推理模式正交。如何影响
您的模型完全依赖于模型中使用的特定模块,并且
它们是否定义了任何特定于训练模式的行为。module.eval()
module.train(False)
model.eval()
您负责致电,如果您的
model 依赖于 和 等可能行为的模块
根据训练模式的不同,例如,为了避免更新您的
BatchNorm 对验证数据运行统计信息。
model.eval()
model.train()
建议您始终在以下情况下使用
训练以及评估模型(验证/测试)时
如果您不确定您的模型是否具有特定于训练模式的行为,因为
您正在使用的模块可能会更新为在训练中表现不同,并且
eval 模式。model.train()
model.eval()
使用 autograd 进行就地操作¶
在 autograd 中支持就地操作是一件困难的事情,我们不鼓励这样做 它们在大多数情况下的使用。Autograd 激进的缓冲区释放和重用使 它非常有效,并且很少有就地操作的情况 将内存使用量降低任何显著量。除非您正在操作 在内存压力很大的情况下,您可能永远不需要使用它们。
限制就地操作适用性的主要原因有两个:
就地操作可能会覆盖计算所需的值 梯度。
每个就地操作都需要实现重写 计算图。异地版本只需分配新对象和 保留对旧图形的引用,而就地操作需要 将所有输入的创建者更改为表示 此操作。这可能很棘手,尤其是在有许多 Tensor 的情况下 引用相同的存储(例如,通过索引或转置创建), 如果 修改后的输入被任何其他 引用。
Function
Tensor
就地正确性检查¶
每个张量都保留一个版本计数器,每次
在任何操作中标记为脏。当 Function 保存任何张量进行 backward 时,
它们包含的 Tensor 的版本计数器也会被保存。访问后,将检查它,以及它是否大于保存的值
引发错误。这可确保在就地使用
函数,并且没有看到任何错误,则可以确保计算出的
梯度是正确的。self.saved_tensors
多线程 Autograd¶
autograd 引擎负责运行所有向后操作 计算向后传递所必需的。本节将介绍所有详细信息 这可以帮助您在多线程环境中充分利用它。(这是 仅与 PyTorch 1.6+ 相关,因为以前版本中的行为不同。
用户可以使用多线程代码(例如 Hogwild 训练)来训练他们的模型,并且 不会阻塞并发向后计算,示例代码可以是:
# Define a train function to be used in different threads
def train_fn():
x = torch.ones(5, 5, requires_grad=True)
# forward
y = (x + 3) * (x + 4) * 0.5
# backward
y.sum().backward()
# potential optimizer update
# User write their own threading code to drive the train_fn
threads = []
for _ in range(10):
p = threading.Thread(target=train_fn, args=())
p.start()
threads.append(p)
for p in threads:
p.join()
请注意,用户应注意的一些行为:
CPU 并发¶
C++当您在多个
threads 的 CPU 上,您期望看到额外的并发,而不是
在执行期间按特定顺序序列化所有向后调用
(PyTorch 1.6 之前的行为)。backward()
grad()
非确定性¶
如果您同时从多个线程调用,并且具有
共享输入(即 Hogwild CPU 训练),那么应该预期是非确定性的。
这可能是因为参数在线程之间自动共享。
因此,多个线程可能会在梯度累积期间访问并尝试累积相同的属性。这在技术上是不安全的,并且
这可能会导致争用条件,并且结果可能无效。backward()
.grad
开发具有共享参数的多线程模型的用户应具有 threading 模型,并且应该了解上述问题。
图形保留¶
如果 autograd 图的一部分在线程之间共享,即先运行
forward single thread 的一部分,然后在多个线程中运行 second part,
然后共享 Graph 的第一部分。在本例中,不同的线程
execute 或在同一图表上可能存在
在一个线程的动态中销毁图形,另一个线程将
崩溃。Autograd 会向用户发送错误,类似于没有 out 的两次调用,并让用户知道
他们应该使用 .grad()
backward()
backward()
retain_graph=True
retain_graph=True
Autograd 节点上的线程安全¶
由于 Autograd 允许调用方线程驱动其向后执行
潜在的并行性,因此我们必须确保 CPU 上的线程安全
共享部分/全部 GraphTask 的并行调用。backward()
由于 GIL,自定义 Python 会自动成为线程安全的。
对于内置的 C++ Autograd 节点(例如 AccumulateGrad、CopySlices)和自定义节点,Autograd 引擎使用线程互斥锁来确保
可能具有 write/read 状态的 autograd 节点上的线程安全。autograd.Function
autograd::Function
C++ 钩子上没有线程安全¶
Autograd 依赖于用户编写线程安全的 C++ 钩子。如果你想要钩子 要在多线程环境中正确应用,您需要编写 正确的线程锁定代码,以确保 hook 是线程安全的。
复数的 Autograd¶
简短版本:
当您使用 PyTorch 区分任何函数时具有复杂域和/或共域, 梯度的计算假设该函数是更大的实值的一部分 loss 函数.计算出的梯度为(注意 z 的共轭),其负数恰好是 STEEPEST DESCENT 的方向 用于 Gradient Descent 算法。因此,制作现有优化器有一条可行的途径 使用复杂的参数开箱即用。
此约定与 TensorFlow 的 complex 约定 微分,但与 JAX 不同(JAX 计算).
如果你有一个 real-to-real 函数,它在内部使用 complex 操作,这里的约定并不重要:您总是会得到 如果实施它,您将获得相同的结果 只有真正的操作。
如果您对数学细节感到好奇,或者想知道怎么做 要在 PyTorch 中定义复杂衍生品,请继续阅读。
什么是复杂衍生品?¶
复微分性的数学定义采用 limit 定义,并将其推广为对 复数。考虑一个函数,
哪里和是两个变量实值函数 和是虚数单位。
使用导数定义,我们可以写成:
为了存在这个限制,不仅必须和必须是 real 微分,但还必须满足 Cauchy-Riemann 方程。在 换句话说:为实部和虚部计算的极限 () 必须相等。这是一个限制性更强的条件。
复数可微函数通常称为全态 功能。他们乖巧,拥有所有美好的特性 您已经从真正的可微函数中看到了,但实际上没有 在优化领域使用。对于优化问题,只有真正的值目标 函数用于研究社区,因为复数不是任何 ordered 字段,因此具有 Complex Value Loss 没有多大意义。
还证明,没有有趣的实值目标满足 Cauchy-Riemann 方程。所以具有全态函数的理论不能是 用于优化,因此大多数人使用 Wirtinger 微积分。
Wirtinger Calculus 出现了......¶
所以,我们有了这个伟大的复微分理论,并且 holomorphic 函数,我们根本无法使用其中的任何一个,因为许多 的常用函数不是全态的。什么是穷人 数学家要做吗?好吧,Wirtinger 观察到,即使不是全态的,可以将其重写为双变量函数它总是全态的。这是因为真实和 的 imaginary 的组成部分可以用和如:
Wirtinger 微积分建议学习相反,它是 保证为 holomorphic,如果是实数可微分的(另一个 可以把它看作是坐标系的变化,从自.)此函数具有偏导数和. 我们可以使用链式规则来建立一个 这些偏导数与偏导数之间的关系 导数 w.r.t.,实部和虚部.
从上面的方程式中,我们得到:
这是维辛格微积分的经典定义,你可以在维基百科上找到。
这种变化有很多美好的后果。
首先,Cauchy-Riemann 方程可以简单地说(也就是说,函数可以写入 完全就,而不引用).
正如我们稍后将看到的,另一个重要的(但有点违反直觉的)结果是,当我们对实际价值的损失进行优化时,我们应该 take 的 take 的 API 表达式为(不是).
如需更多阅读,请查看:https://arxiv.org/pdf/0906.4835.pdf
Wirtinger Calculus 在优化中有何用处?¶
音频和其他领域的研究人员更常用地使用 Gradient descent 来优化具有复变量的实值损失函数。 通常,这些人将实值和虚值视为分开的 可以更新的频道。对于步长和损失,我们可以在:
这些方程如何转化为复空间?
发生了一件非常有趣的事情:维廷格微积分告诉我们 我们可以将上面的复数变量更新公式简化为仅 参考共轭 Wirtinger 导数,这正是我们在优化中采取的步骤。
因为共轭 Wirtinger 导数为我们提供了实值损失函数的正确步骤,所以 PyTorch 为您提供了这个导数 当您区分具有实际价值损失的函数时。
PyTorch 如何计算共轭 Wirtinger 导数?¶
通常,我们的导数公式将 grad_output 作为输入, 表示我们已经输入的 Vector-Jacobian 积 computed,又名哪里是整个计算的损失(产生真正的损失),而是我们函数的输出。这里的目标是计算哪里是 函数。事实证明,在实际亏损的情况下,我们可以 只计算, 尽管链式法则意味着我们还需要 有权访问.如果需要帮助, 要跳过此推导,请查看本节中的最后一个方程 ,然后跳到下一部分。
让我们继续合作定义为.如上所述, Autograd 的 gradient 约定以 Real 优化为中心 有值损失函数,所以我们假设是 larger 的一部分 实值损失函数.使用链式法则,我们可以写成:
(1)¶
现在使用 Wirtinger 导数定义,我们可以写成:
这里需要注意的是,由于和是真实的 函数和是真实的,根据我们的假设是一个 作为实值函数的一部分,我们有:
(2)¶
即等于.
求解上述方程和,我们得到:
(3)¶
使用 (2),我们得到:
(4)¶
最后一个方程式是编写你自己的梯度的重要方程式, 因为它将我们的导数公式分解成一个更简单的公式 手动计算。
如何为复杂函数编写自己的导数公式?¶
上面的方框方程为我们提供了所有 复杂函数的导数。但是,我们仍然需要 计算和. 有两种方法可以执行此操作:
第一种方法是直接使用 Wirtinger 导数的定义并计算和由 用和(您可以按正常方式计算)。
第二种方法是使用变量更改 trick 并重写作为双变量函数和 compute 共轭 Wirtinger 导数,通过处理和作为自变量。这通常更容易;例如,如果所讨论的函数是 holomorphic,则只有将使用(并且将为零)。
让我们考虑函数例如,其中.
使用第一种方法来计算 Wirtinger 导数,我们有。
使用 (4) 和 grad_output = 1.0(这是在 PyTorch 中对标量输出调用时使用的默认 grad 输出值),我们得到:backward()
使用第二种方法计算 Wirtinger 导数,我们直接得到:
再次使用 (4),我们得到.如您所见,第二种方式涉及较少的计算,并且 更方便,计算更快。
已保存张量的钩子¶
您可以通过定义一对 / 钩子来控制保存的张量的打包/解包方式。该函数应将张量作为其单个参数
但可以返回任何 Python 对象(例如另一个张量、一个元组,甚至是一个
string 包含文件名)。该函数将
参数的输出 and 应返回要在
向后传递。返回的 tensor 只需要有
与作为输入传递给 的 Tensor 的内容相同。特别
任何与 Autograd 相关的元数据都可以忽略,因为它们将在
打开。pack_hook
unpack_hook
pack_hook
unpack_hook
pack_hook
unpack_hook
pack_hook
此类对的一个例子是:
class SelfDeletingTempFile():
def __init__(self):
self.name = os.path.join(tmp_dir, str(uuid.uuid4()))
def __del__(self):
os.remove(self.name)
def pack_hook(tensor):
temp_file = SelfDeletingTempFile()
torch.save(tensor, temp_file.name)
return temp_file
def unpack_hook(temp_file):
return torch.load(temp_file.name)
请注意,不应删除临时文件,因为它
可能会被多次调用:临时文件应处于活动状态的时间
,因为返回的 SelfDeletingTempFile 对象处于活动状态。在上面的示例中,
我们通过在不再需要临时文件时关闭临时文件来防止它泄漏
(在删除 SelfDeletingTempFile 对象时)。unpack_hook
注意
我们保证这只会被调用一次,但可以
被调用的次数与向后传递所需的次数相同,我们希望它能够
每次返回相同的数据。pack_hook
unpack_hook
警告
禁止对任何函数的输入执行就地操作 因为它们可能会导致意想不到的副作用。如果 PyTorch 的 input 的 input 被就地修改,但不会捕获 unpack 钩子的 input 被就地修改。
为保存的张量注册钩子¶
您可以通过对对象调用 method 在保存的张量上注册一对钩子。这些对象作为 a 的属性公开,并以前缀开头。register_hooks()
SavedTensor
grad_fn
_raw_saved_
x = torch.randn(5, requires_grad=True)
y = x.pow(2)
y.grad_fn._raw_saved_self.register_hooks(pack_hook, unpack_hook)
一旦注册了对,就会调用该方法。
每次保存的张量需要时,都会调用该方法
访问,通过 或 在 backward 期间访问
通过。pack_hook
unpack_hook
y.grad_fn._saved_self
警告
如果您在保存的
张量已被释放(即在调用 backward 之后),调用
它是被禁止的。
PyTorch 大多数情况下会抛出错误,但可能会失败
在某些情况下,可能会出现未定义的行为。SavedTensor
register_hooks()
为保存的张量注册默认钩子¶
或者,你可以使用 context-manager 来注册一对
钩子,该钩子将应用于在
那个背景。
例:
# Only save on disk tensors that have size >= 1000
SAVE_ON_DISK_THRESHOLD = 1000
def pack_hook(x):
if x.numel() < SAVE_ON_DISK_THRESHOLD:
return x
temp_file = SelfDeletingTempFile()
torch.save(tensor, temp_file.name)
return temp_file
def unpack_hook(tensor_or_sctf):
if isinstance(tensor_or_sctf, torch.Tensor):
return tensor_or_sctf
return torch.load(tensor_or_sctf.name)
class Model(nn.Module):
def forward(self, x):
with torch.autograd.graph.saved_tensors_hooks(pack_hook, unpack_hook):
# ... compute output
output = x
return output
model = Model()
net = nn.DataParallel(model)
使用此上下文管理器定义的钩子是线程本地的。 因此,下面的代码不会产生所需的效果,因为钩子不会去 通过 DataParallel。
# Example what NOT to do
net = nn.DataParallel(model)
with torch.autograd.graph.saved_tensors_hooks(pack_hook, unpack_hook):
output = net(input)
请注意,使用这些钩子会禁用所有优化以减少 Tensor 对象创建。例如:
with torch.autograd.graph.saved_tensors_hooks(lambda x: x, lambda x: x):
x = torch.randn(5, requires_grad=True)
y = x * x
如果没有钩子, , 和 都引用同一个 tensor 对象。
使用钩子,PyTorch 会将 x 打包和解压缩为两个新的 Tensor 对象
与原始 X 共享同一存储空间(不执行复制)。x
y.grad_fn._saved_self
y.grad_fn._saved_other
向后钩子执行¶
本节将讨论不同的钩子何时触发或不触发。
然后它将讨论他们被触发的顺序。
将涵盖的钩子是:通过 , post-accumulate-grad 注册到 Tensor 的向后钩子
通过 , post-hooks 的
Tensor
通过
注册到 Node,以及
通过
.
是否会触发特定的钩子¶
注册到 Tensor via 的钩子在计算该 Tensor 的梯度时执行。(请注意,这不需要
要执行的 Tensor grad_fn。例如,如果 Tensor 传递
作为 的参数的一部分
,
Tensor 的 grad_fn可能不会被执行,但该 Tensor 的钩子寄存器将始终被执行。
inputs
注册到 Tensor via 的钩子是在该 Tensor 的梯度累积后执行的,这意味着
Tensor 的 grad 字段已设置。注册 via
的钩子是在计算梯度时运行的,而注册通过 的
钩子只有在 autograd 在结束时更新 Tensor 的 grad 字段时才会触发
向后传递。因此,post-accumulate-grad 钩子只能为 leaf 注册
张。在非叶 Tensor 上注册 hook via
会出错,即使你调用 backward(retain_graph=True) 也是如此。
注册为 using 或
仅在
执行它注册到的 Node。
torch.autograd.graph.Node
是否执行特定 Node 可能取决于是否使用 或
调用了向后传递。
具体来说,当您在
Node 对应于您要传递给
的 Tensor 或
作为参数的一部分。
inputs
如果你正在使用 ,上面提到的所有钩子都将被执行,
是否指定了参数。这是因为 .backward() 执行所有
节点,即使它们对应于指定为输入的 Tensor。
(请注意,执行与传递的 Tensors as 对应的这个附加 Node 通常是不必要的,但无论如何都会完成。此行为可能会发生变化;
你不应该依赖它。
inputs
inputs
另一方面,如果你使用的是 ,则向后钩子已注册
to 节点时,由于
除非有另一个依赖于梯度的输入,否则不会执行这些 Node
此节点的结果。
input
不同钩子的触发顺序¶
事情发生的顺序是:
执行注册到 Tensor 的钩子
执行注册到 Node 的 pre-hooks(如果执行 Node)。
该字段将针对retain_grad
.grad
节点被执行(受上述规则约束)
对于已累积的叶子 Tensor,执行 post-accumulate-grad 钩子
.grad
执行注册到 Node 的后钩子(如果执行 Node)
如果在同一 Tensor 或 Node 上注册了多个相同类型的 hook 它们将按照注册的顺序执行。 稍后执行的 Hook 可以观察到 早期的钩子。
特殊钩¶
使用已注册的 hook 实现
到 Tensors。每个单独的 Tensor 钩子都按照 Tensor 钩子的顺序触发
定义,并且注册的 multi-grad 钩子在最后一个 Tensor 梯度
被计算。
使用 hook 实现
注册到 Node。在计算 forward 时,hook 被注册到相应的 grad_fn
添加到模块的输入和输出中。因为一个模块可能接受多个输入并返回
多个 outputs,首先将一个虚拟的自定义 autograd Function 应用于模块的 inputs
before forward 和 forward 输出之前的模块输出返回,以确保
这些 Tensor 共享一个 grad_fn,然后我们可以将钩子附加到该 Tensor 上。
就地修改 Tensor 时 Tensor 钩子的行为¶
通常,注册到 Tensor 的 hook 接收输出相对于该 Tensor 的 Tensor 值,其中 Tensor 的值取为其在计算 backward 时的值。
但是,如果你将钩子注册到 Tensor,然后就地修改该 Tensor,钩子 registered before in-place modification 同样接收输出的梯度 尊重 Tensor,但 Tensor 的值被认为是它在 就地修改。
如果您更喜欢前一种情况下的行为, 您应该在对 Tensor 进行所有就地修改后将它们注册到 Tensor 中。 例如:
t = torch.tensor(1., requires_grad=True).sin()
t.cos_()
t.register_hook(fn)
t.backward()
此外,了解在后台, 当 hook 注册到 Tensor 时,它们实际上会永久绑定到 grad_fn 中,如果该 Tensor 随后被就地修改, 即使 Tensor 现在有一个新的 grad_fn,钩子在它之前注册 “就地修改”将继续与旧grad_fn关联,例如,它们将 当 autograd 引擎在图中达到该 Tensor 的旧 grad_fn 时触发。