Path: blob/master/site/zh-cn/guide/advanced_autodiff.ipynb
25115 views
Copyright 2020 The TensorFlow Authors.
高级自动微分
梯度和自动微分简介指南包括在 TensorFlow 中计算梯度所需的全部内容。本指南重点介绍 tf.GradientTape
API 更深入、更不常见的功能。
设置
停止记录
如果您希望停止记录梯度,可以使用 tf.GradientTape.stop_recording
暂时挂起记录。
如果您不希望在模型中间对复杂运算微分,这可能有助于减少开销。其中可能包括计算指标或中间结果:
重置/从头开始纪录
如果您希望完全重新开始,请使用 tf.GradientTape.reset
。通常,直接退出梯度带块并重新开始比较易于读取,但在退出梯度带块有困难或不可行时,可以使用 reset
方法。
精确停止梯度流
与上面的全局条带控制相比,tf.stop_gradient
函数更加精确。它可以用来阻止梯度沿着特定路径流动,而不需要访问条带本身:
自定义梯度
在某些情况下,您可能需要精确控制梯度的计算方式,而不是使用默认值。这些情况包括:
正在编写的新运算没有定义的梯度。
默认计算在数值上不稳定。
您希望从前向传递缓存开销大的计算。
您想修改一个值(例如,使用
tf.clip_by_value
或tf.math.round
)而不修改梯度。
对于第一种情况,要编写新运算,您可以使用 tf.RegisterGradient
自行设置。(请参阅 API 文档了解详细信息)。(注意,梯度注册为全局,需谨慎更改。)
对于后三种情况,可以使用 tf.custom_gradient
。
以下示例将 tf.clip_by_norm
应用于中间梯度:
请参阅 tf.custom_gradient
装饰器 API 文档,了解更多详细信息。
SavedModel 中的自定义梯度
注:此功能将从 TensorFlow 2.6 开始提供。
可以使用选项 tf.saved_model.SaveOptions(experimental_custom_gradients=True)
将自定义梯度保存到 SavedModel。
要保存到 SavedModel,梯度函数必须可以跟踪(要了解详情,请参阅使用 tf.function 提高性能指南)。
关于上述示例的注意事项:如果您尝试用 tf.saved_model.SaveOptions(experimental_custom_gradients=False)
替换上面的代码,梯度仍会在加载时产生相同的结果。原因在于,梯度注册表仍然包含函数 call_custom_op
中使用的自定义梯度。但是,如果在没有自定义梯度的情况下保存后重新启动运行时,则在 tf.GradientTape
下运行加载的模型会抛出错误:LookupError: No gradient defined for operation 'IdentityN' (op type: IdentityN)
。
多个条带
多个条带无缝交互。
例如,下面每个条带监视不同的张量集:
高阶梯度
tf.GradientTape
上下文管理器内的运算会被记录下来,以供自动微分。如果在该上下文中计算梯度,梯度计算也会被记录。因此,完全相同的 API 也适用于高阶梯度。
例如:
示例:输入梯度正则化
许多模型都容易受到“对抗样本”影响。这种技术的集合会修改模型的输入,进而混淆模型输出。最简单的实现(例如,使用 Fast Gradient Signed Method 攻击的对抗样本)沿着输出相对于输入的梯度(即“输入梯度”) 迈出一步。
一种增强相对于对抗样本的稳健性的方法是输入梯度正则化(Finlay 和 Oberman,2019 年),这种方法会尝试将输入梯度的幅度最小化。如果输入梯度较小,那么输出的变化也应该较小。
以下是输入梯度正则化的简单实现:
使用内条带计算输出相对于输入的梯度。
计算该输入梯度的幅度。
计算该幅度相对于模型的梯度。
雅可比矩阵
注意:
类似于
gradient
:sources
参数可以是张量或张量的容器。不同于
gradient
:target
张量必须是单个张量。
标量源
作为第一个示例,以下是矢量目标相对于标量源的雅可比矩阵。
当您相对于标量取雅可比矩阵时,结果为目标的形状,并给出每个元素相对于源的梯度:
张量源
无论输入是标量还是张量,tf.GradientTape.jacobian
都能有效计算源的每个元素相对于目标的每个元素的梯度。
例如,此层的输出的形状为 (10,7)
。
层内核的形状是 (5,10)
。
将这两个形状连在一起就是输出相对于内核的雅可比矩阵的形状:
如果您在目标的维度上求和,会得到由 tf.GradientTape.gradient
计算的总和的梯度:
虽然 tf.GradientTape
并没有给出构造黑塞矩阵的显式方法,但可以使用 tf.GradientTape.jacobian
方法进行构建。
注:黑塞矩阵包含 N**2
个参数。由于这个原因和其他原因,它对于大多数模型都不实际。此示例主要是为了演示如何使用 tf.GradientTape.jacobian
方法,并不是对直接黑塞矩阵优化的认可。黑塞矩阵向量积可以通过嵌套条带有效计算,这也是一种更有效的二阶优化方法。
要将此黑塞矩阵用于牛顿方法步骤,首先需要将其轴展平为矩阵,然后将梯度展平为向量:
黑塞矩阵应当对称:
牛顿方法更新步骤如下所示。
注:实际上不反转矩阵。
虽然这对于单个 tf.Variable
来说相对简单,但将其应用于非平凡模型则需要仔细的级联和切片,以产生跨多个变量的完整黑塞矩阵。
批量雅可比矩阵
在某些情况下,您需要取各个目标堆栈相对于源堆栈的雅可比矩阵,其中每个目标-源对的雅可比矩阵都是独立的。
例如,此处的输入 x
形状为 (batch, ins)
,输出 y
形状为 (batch, outs)
:
y
相对 x
的完整雅可比矩阵的形状为 (batch, ins, batch, outs)
,即使您只想要 (batch, ins, outs)
。
如果堆栈中各项的梯度相互独立,那么此张量的每一个 (batch, batch)
切片都是对角矩阵:
要获取所需结果,您可以对重复的 batch
维度求和,或者使用 tf.einsum
选择对角线:
没有额外维度时,计算会更加高效。tf.GradientTape.batch_jacobian
方法就是如此运作的:
小心:tf.GradientTape.batch_jacobian
只验证源和目标的第一维是否匹配,并不会检查梯度是否独立。用户需要确保仅在合理条件下使用 batch_jacobian
。例如,添加 tf.keras.layers.BatchNormalization
将破坏独立性,因为它在 batch
维度进行了归一化:
在此示例中,batch_jacobian
仍然可以运行并返回某些信息与预期形状,但其内容具有不明确的含义: