# mypy: 允许未类型化定义
导入
集合
导入 functools
导入
警告
来自 collections.abc
导入
迭代器
来自 itertools
导入
产品
来自
打字
导入
可调用,
可选,
联合
来自 typing_extensions
导入
已弃用
导入
火炬
导入 torch.testing
来自 torch._vmap_internals
导入 _vmap, vmap
来自 torch.overrides
导入
类似于 tensor
来自
torch 的类型
导入 _TensorOrTensors
# 注:即使我们本来没有打算公开,这里也添加了 `get_*_jacobian` 函数
# 由于它们在添加 `__all__` 之前就已经被暴露,并且我们已经维护了它们的向后兼容性
# 我们最终应该弃用它们,并从 `__all__` 中移除
全部 = [
梯度检查,
"gradgradcheck",
GradcheckError,
"获取数值雅可比",
"获取解析雅可比",
"获取关于特定输入的数值雅可比",
]
[文档]class GradcheckError(RuntimeError):
由 :func:`gradcheck` 和 :func:`gradgradcheck` 抛出的错误。
def _is_sparse_compressed_tensor(对象:
PyTorch.
张量):
返回
对象.
布局
在 {
PyTorch.
稀疏压缩存储格式,
PyTorch.
稀疏压缩存储格式,
PyTorch.
稀疏_bsr,
PyTorch.
稀疏矩阵,
}
def _is_sparse_any_tensor(对象:
PyTorch.
张量):
返回 _is_sparse_compressed_tensor(
对象)
或者
对象.
布局
是
PyTorch.
稀疏的 COO
def _是否为浮点或复数张量(
对象):
返回
是否与张量类似(
对象)
和 (
对象.is_floating_point()
或者
对象.
是复杂的())
def _使用输入分配雅可比矩阵(
输入张量:
元组,
numel 输出
) -> 元组[
PyTorch.
张量, ...
]
从输入生成全零张量。如果`numel_output`不为 None,则
每个`input_tensors`中的张量,返回一个新创建的、高度为零的张量
`t.numel`的数量和`numel_output`的宽度。否则,对于每个张量,返回
# a 1-d tensor with size `(t.numel,)`
# the same dtype and device as those of the corresponding input.
输出:
列表[
PyTorch.
张量] = [
t.新零((t.
元素数量(), numel_output),
布局=
PyTorch.strided)
for t 在 input_tensors
如果 _is_float_or_complex_tensor(t)
和 t.
需要梯度
]
返回
元组(
输出)
def _allocate_jacobians_with_outputs(
output_tensors: 元组, numel_input,
数据类型=
无,
设备=
无
) -> 元组[
PyTorch.
张量, ...
]
将输出转换为全零张量。如果`dim`不为 None,则对每个张量
# 在 `output_tensors` 中,返回一个高度为 `dim` 的新零填充张量
`t.numel` 的宽度。否则,对于每个张量,返回一个大小为 1-d 的张量。
# (t.numel,)
选项 = {"dtype":
数据类型,
"设备":
设备,
布局:
PyTorch.strided}
输出:
列表[
PyTorch.
张量] = [
t.新零((numel_input, t.
元素数量()), **
选项)
for t 在 output_tensors
如果 _is_float_or_complex_tensor(t)
]
返回
元组(
输出)
def 迭代张量(
x: 联合[
PyTorch.
张量,
迭代器[
PyTorch.
张量]],
仅需要梯度:
布尔值 =
假
) -> 迭代器[
PyTorch.
张量
]
如果
是否与张量类似(x):
mypy 没有将`x`的类型缩小到 torch.Tensor
如果 x.
需要梯度
或者
不
仅需要梯度:
# 类型:忽略[联合属性]
产生 x
# 类型:忽略[杂项]
如果...否则 isinstance(x,
集合.abc.
迭代器)
和
不 isinstance(x, str):
for 元素
在 x:
yield from 迭代张量(
元素,
仅需要梯度)
def 稠密化(x):
# 返回稀疏 x 的所有未指定元素的副本
# "replaced" with zero-valued elements
如果 isinstance(x, (
列表,
元组)):
返回
类型(x)(
地图(
密集化, x))
如果...否则
不
是否与张量类似(x)
或者 x.
布局
在 {
PyTorch.strided,
PyTorch.
MKLDNN}: # type: ignore[attr-defined] # no attr _mkldnn
返回 x
如果...否则 x.
布局
是
PyTorch.
稀疏 COO:
设备 = x.
设备
索引数据类型 = x.
索引().dtype
临时 =
PyTorch.
一(x.
形状
[ x.
稀疏维度
]
数据类型=
PyTorch.int8,
设备=
设备)
索引 = tmp.
非零().t().
到(
数据类型=
索引数据类型)
values = PyTorch.
零值(
(tmp.元素数量(), *x.
形状[x.
稀疏维度()
]
数据类型=x.
数据类型,
设备=
设备
)
x_coalesced = x.detach().合并()
如果 x_coalesced.
元素数量() > 0:
步长 = tmp.
步长()
平坦索引 = (
合并后的 x.
索引()
.多(
PyTorch.
张量(
步长,
数据类型=
索引数据类型,
设备=
设备).
展平(
1
)
)
.总和(0)
)
值[
平坦索引] =
合并后的 x.
值()
返回 (
PyTorch.
稀疏 Coo 张量(
索引,
值, x.
形状)
.合并(True)
.需要梯度_(x.
需要梯度)
)
如果...否则
是稀疏压缩张量(x):
块大小 = (
x.值().
形状[1:3]
如果 x.
布局
在 {
PyTorch.
稀疏_bsr,
PyTorch.
稀疏矩阵}
否则
无
)
压缩索引 = (
x.鸡索引()
如果 x.
布局
在 {
PyTorch.
稀疏压缩存储格式,
PyTorch.
稀疏_bsr}
否则 x.
c 列索引()
)
为了简单起见,我们将使用中间的稀疏 COO
r = 稠密化(x.detach().
转换为稀疏格式(
布局=
PyTorch.
稀疏 COO)).
转换为稀疏格式(
布局=x.
布局,
块大小=
块大小
)
检查在 `to_sparse` 操作后所有元素是否已指定
稠密数值元素数量 = r.
值().
元素数量() //
最大值(1, r.
值().
形状[0])
批量数值元素数量 =
压缩索引.
元素数量() //
压缩索引.
形状[-1]
稀疏数值元素数量 = r.
元素数量() //
最大值(1,
稠密数值 *
批量数值)
如果
稀疏数值 != r._nnz():
抛出
断言错误(
f"{x.布局}
稠密化失败:期望非零元素数量为={
稀疏 numel}
但是得到了{r._nnz()}"
)
返回 r.
需要梯度_(x.
需要梯度)
如果...否则 _is_sparse_any_tensor(x):
抛出
不支持的操作异常(x.
布局)
返回 x
def _iter_tensor(x_tensor):
# (仅用于慢速梯度检查) 返回一个生成器,该生成器产生以下内容
# elements at each iteration:
# 1) 张量:在所有迭代中返回相同的张量。该张量
# 与输入的原始 x_tensor 不同 - 它已经被
# 准备好以便就地修改。根据是否
输入张量是跨步的、稀疏的或密集的,返回的张量可能或不与 x_tensor 共享存储。
2) 可以用于高级索引的索引元组(按字典顺序生成)
3) 一个可以用于高级索引的索引元组(按字典顺序生成)
4) 一个可以用于高级索引的索引元组(按字典顺序生成)
# 3) 用于索引雅可比张量的扁平索引
#
# 对于大小为 (2, 2) 的张量 t,_iter_tensor 返回:
# `x, (0, 0), 0`, `x, (0, 1), 1`, `x, (1, 0), 2`, `x, (1, 1), 3`
#
# 其中 x 是原始张量的 t.data。扰动 x 的条目
在索引(1,1)处得到整体雅可比矩阵的第三列。
如果 _is_sparse_any_tensor(x_tensor):
def 获取步长(
尺寸):
维度 =
长度(
尺寸)
临时 = 1
步长 = [0] *
维度
for i 在
反转(
范围(
维度)):
步长[i] =
临时
临时 *=
尺寸[i]
返回
步长
x_nnz = x_tensor._nnz()
x_size = 列表(x_tensor.
尺寸())
如果 x_tensor.
布局
是
PyTorch.
稀疏 COO:
x_indices = x_tensor.索引().t()
x_values = x_tensor.值()
如果...否则 x_tensor.
布局
是
PyTorch.
稀疏压缩存储格式:
x_indices = PyTorch._convert_indices_from_csr_to_coo(
x_tensor.鸡索引(), x_tensor.
列索引()
).t()
x_values = x_tensor.值()
如果...否则 x_tensor.
布局
是
PyTorch.
稀疏压缩存储格式:
x_indices = PyTorch._convert_indices_from_csr_to_coo(
x_tensor.c 列索引(), x_tensor.
行索引(),
转置=
真实
).t()
x_values = x_tensor.值()
如果...否则 x_tensor.
布局
是
PyTorch.
稀疏_bsr:
x_block_values = x_tensor.值()
x_blocksize = x_block_values.尺寸()[1:3]
x_indices
x 索引 = (
PyTorch.
将 CSR 索引转换为 COO 索引(
x 张量.
鸡索引(),
x 张量.
列索引()
)
.重复交织(
x 块大小[0] * x_blocksize[1
] 1)
.mul_(PyTorch.
张量(x_blocksize,
设备=x_tensor.
设备).
重塑(2, 1))
.加_(
PyTorch.
栈(
PyTorch.
哪里(
PyTorch.
一(x_blocksize,
设备=x_tensor.
设备))
).重复(1, x_nnz)
)
.t()
)
x_values = x_block_values.展平(0, 2)
x_nnz = x_values.尺寸(0)
如果...否则 x_tensor.
布局
是
PyTorch.
稀疏矩阵:
x_block_values = x_tensor.值()
x_blocksize = x_block_values.尺寸()[1:3]
x 索引 = (
PyTorch.
将 CSR 索引转换为 COO 索引(
x 张量.
c 列索引(),
x 张量.
行索引(),
转置=
真实
)
.重复交织(x_blocksize[0] * x_blocksize[1
] 1)
.mul_(PyTorch.
张量(x_blocksize,
设备=x_tensor.
设备).
重塑(2, 1))
.加_(
PyTorch.
栈(
PyTorch.
哪里(
PyTorch.
一(x_blocksize,
设备=x_tensor.
设备))
).重复(1, x_nnz)
)
.t()
)
x_values = x_block_values.展平(0, 2)
x_nnz =
x_values
x 值.尺寸(0)
否则:
抛出
不支持的操作异常(f
_iter_tensor{x_tensor.
布局}
输入")
x 步长 =
获取步长(
x 大小)
.data(用于绕过版本检查)
x_values = x_values.数据
for i 在
范围(x_nnz):
x_value = x_values[i]
for x_idx 在 product(*[
范围(m) for m
在 x_values.
尺寸()[1
]]):
索引 = x_indices[i].
转列表() +
列表(x_idx)
d_idx = 总和(
索引[k] * x_stride[k] for k
在
范围(
长度(x_size)))
产生 x_value, x_idx, d_idx
如果...否则 x_tensor.
布局 ==
PyTorch._mkldnn:
# 类型:忽略[已定义]
for d_idx, x_idx 在
列举(
产品(*[
范围(m) for m
在 x_tensor.
尺寸()])):
这实际上非常低效,但没有实现索引,别无他法
并没有比转换来转换去更好的方法
x_tensor_dense = x_tensor.转换为稠密格式()
产生 x_tensor_dense, x_idx, d_idx
否则:
# 使用 .data 来绕过版本检查
x_tensor = x_tensor.数据
for d_idx, x_idx 在
列举(
产品(*[
范围(m) for m
在 x_tensor.
尺寸()])):
产生 x_tensor, x_idx, d_idx
def _get_numerical_jacobian(
函数,
输入,
输出=
无,
目标=
无, eps=
0.001,
是前向广告=
假
) -> 列表[
元组[
PyTorch.
张量, ...
]]
计算 `fn(inputs)` 关于 `target` 的数值雅可比矩阵。
如果没有指定,目标就是输入。返回 M * N 的雅可比矩阵,其中 N 是
目标中需要梯度计算的张量数量为 number of tensors in target that require grad,M 是分数非整数的数量
输出。
参数:
fn:计算雅可比矩阵的函数
输入:`fn` 的输入
输出:提供预计算的输出以避免调用 fn 的一次额外调用
目标:计算雅可比矩阵的张量(默认=`inputs`)
eps:有限差分法中扰动的大小
(默认=`1e-3`)
is_forward_ad:如果这个数值雅可比矩阵是计算为检查相对于
前向 AD 梯度(仅用于错误检查)
返回值:
张量 M N-元组的列表
注意,`target` 可能甚至不是 `fn` 的 `input` 的部分,所以请在此处非常小心,不要克隆 `target`。
**非常小心**,以免克隆 `target`。
"""
雅可比矩阵:
列表[
元组[
PyTorch.
张量, ...]] =
输入文本为空,请提供需要翻译的文本
如果
输出
是
无:
输出 = _as_tuple(
函数(*_as_tuple(
输入)))
如果
不
是前向广告
和
任何(o.
是复杂的() for o
在
输出):
抛出
值错误(
预期输出非复杂。get_numerical_jacobian 无
支持返回复杂输出的功能。
)
如果
目标
是
无:
目标 =
输入
输入索引 = [
i for i, a 在
列举(
目标)
如果
是否与张量类似(a)
和 a.
需要梯度
]
for i, (输入,
输入索引)
在
列举(zip(
迭代张量(
目标, True),
输入索引)):
杰卡比安 += [
获取关于特定输入的数值雅可比(
函数,
输入索引,
输入,
输出,
eps,
输入=
输入,
前向广告=
前向广告,
)
]
返回
雅可比矩阵
@deprecated(
"`get_numerical_jacobian`曾是 PyTorch 的私有 API,而不是"
旨在公开。我们正在弃用它,并将移除
在 PyTorch 的未来版本中。如果您有对“
这是一个或对该功能提出稳定 API 的请求,请提交
我们有一个问题在 https://github.com/pytorch/pytorch/issues/new,
分类=
未来警告,
)
def 获取数值雅可比(
函数,
输入,
目标=
无, eps=
0.001,
毕业输出=1.0):
计算给定函数及其输入的数值雅可比矩阵。
这是一个已弃用的 API。
参数:
fn:计算雅可比矩阵的函数(必须接受一个元组作为输入)
inputs:`fn`的输入
目标:计算雅可比矩阵的张量(默认=`input`)
eps:有限差分法中扰动的大小
(默认=`1e-3`)
grad_out:默认为 1.0。
返回值:
`fn` 的雅可比矩阵列表(仅限于其第一个输出)相对于每个输入或目标,如果提供的话。
注意,`target` 可能甚至不是 `fn` 的输入部分,所以在不克隆 `target` 时请务必**非常小心**。
注意,`target` 可能甚至不是 `fn` 的输入部分,所以在不克隆 `target` 时请务必**非常小心**。
注意,`target` 可能甚至不是 `fn` 的输入部分,所以在不克隆 `target` 时请务必**非常小心**。
"""
如果 (
grad_out != 1.0
): # grad_out 参数仅保留为向后兼容的原因
抛出
值错误(
"期望 grad_out 为 1.0。get_numerical_jacobian 不再 "
"支持 grad_out != 1.0 的值。"
)
def fn_pack_inps(*输入):
返回
函数(
输入)
雅可比矩阵 =
_获取数值雅可比(
fn 打包输入,
输入,
无,
目标, eps)
返回
元组(
每个输出雅可比[0] for
每个输出雅可比
在
雅可比矩阵)
def _计算数值梯度(
函数,
条目, v, norm_v, nbhd_checks_fn):
# 计算数值方向导数作为有限差分
# 在输入 `entry` 处计算函数 `fn` 的数值,扰动向量为 `v`。
如果 _is_sparse_compressed_tensor(
条目):
# 稀疏压缩张量不实现 sub/add/copy_
# 然而,在非掩码语义上下文中,条目和 v
# 具有相同的稀疏索引 ...
断言
条目.
布局 == v.
布局, (
条目.
布局, v.
布局)
断言
条目._nnz() == v._nnz(), (
条目._nnz(), v._nnz(),
条目.
形状)
仅对数值进行有限差分:
条目 =
条目.
值()
v = v.值()
我们将断开连接,以避免进行稀疏矩阵的向后计算。
张量对支持有限。
条目 =
条目.detach()
原始 =
条目.
克隆()
条目.
复制_(
原始 - v)
外部 =
函数()
条目.
复制_(
原始 + v)
外部 =
函数()
条目.
复制_(
原始)
def 计算(a, b):
邻域检查函数(a, b)
返回 = (b - a) / (2 * norm_v)
使用中心差分近似
返回
返回.detach().
重塑(-1)
返回
元组(
计算(a, b) for (a, b)
在 zip(outa, outb))
def _针对特定输入的数值计算_jvps(
jvp_fn, Δ,
输入复杂,
是正向的=
假
) -> 列表[
PyTorch.
张量
]
仅当 delta 为实数时,计算雅可比矩阵才有效
关于此处使用的算法的详细信息,请参阅:
参见第 3.5.3 节 https://arxiv.org/pdf/1701.00392.pdf
s = fn(z),其中 z = x 对于实值输入
以及 z = x + yj 对于复值输入
jvps: 列表[
PyTorch.
张量] =
输入文本为空,请提供需要翻译的文本
ds_dx_tup = jvp_fn(Δ[0]
如果 isinstance(
Δ,
元组)
否则
Δ)
如果
输入复杂: # C -> R
ds_dy_tup = (
jvp_fn(Δ[1] * 1j)
如果 isinstance(
Δ,
元组)
否则 jvp_fn(delta * 1j)
)
for ds_dx, ds_dy 在 zip(ds_dx_tup, ds_dy_tup):
断言
不 ds_dx.
是复杂的()
共轭魏尔斯特拉斯导数
联合_w_d = ds_dx + ds_dy * 1j
jvps.添加(
连接_w_d)
否则:
for ds_dx 在 ds_dx_tup:
# R -> R 或 (R -> C 对于前向 AD 情况)
断言
是前向广告
或者
不 ds_dx.
是复杂的()
jvps.添加(ds_dx)
返回 jvps
def _合并雅可比列(
雅可比列:
字典[int,
列表[
PyTorch.
张量]],
输出,
输入, numel
) -> 元组[
PyTorch.
张量, ...
]
# 雅可比列将列索引映射到输出索引,然后映射到单个雅可比张量列
# 我们返回一个映射输出索引到完整雅可比张量的列表
雅可比矩阵 =
_带有输出的分配雅可比矩阵(
输出,
元素数量,
数据类型=
输入.dtype
如果
输入.
数据类型.
是否为复数
否则
无
)
for i, ja_c_bian
在
列举(
雅可比矩阵):
for k, v 在
雅可比矩阵列.
项目():
詹可布[k] = v[i]
返回
雅可比矩阵
def 准备输入(
输入:
PyTorch.
张量,
可能被扰动的输入:
可选[
PyTorch.
张量
]
快速模式=
假
) -> PyTorch.
张量:
# 准备输入以传递给函数,同时包括新的
# 修改后的输入。
如果
输入.
布局 ==
PyTorch.
MKLDNN: # type: ignore[attr-defined] # no attr _mkldnn
# 转回 mkldnn
如果
可能被扰动的输入
是
不
无:
返回
可能被扰动的输入.
转换为 mkldnn()
否则:
返回
输入
如果...否则 _is_sparse_any_tensor(
输入):
如果
快速模式
和
可能受干扰的输入
是
不
无:
# 该条目已经是原始张量的“克隆”版本
# 因此对条目的更改不会反映在输入中
返回 maybe_perturbed_input
否则:
返回
输入
否则:
# 我们不能使用 entry (input.data) 如果我们想 gradgrad 工作,因为
# fn (在 gradgrad 的情况下) 需要计算相对于 input 的 grad
返回
输入
def _检查输出数据类型和形状是否相同(output1, output2, eps,
索引=
无) ->
无:
# 检查返回的输出在 dtype 或 shape 上是否不同
# 扰动输入
索引页 =
索引{idx} "
如果
索引
是
不
无
否则
请提供需要翻译的文本
断言
输出 1.
形状 == output2.
形状, (
f"期望 `func` 返回与输入相同的形状的输出"
f"当输入被扰动时"{on_index}
通过{eps}
,但获取了:"
f"形状"{
输出 1.
形状}
和{
输出 2.
形状}
。
)
断言
输出 1.dtype ==
输出 2.
数据类型, (
f预期 `func` 在输入被扰动时返回相同的数据类型输出
f的输出{on_index}
通过{eps}
但是得到了:
f"数据类型"{
输出 1.
数据类型}
和{
输出 2.
数据类型}
。
)
def 获取关于特定输入的数值雅可比(
函数,
输入索引,
输入,
输出, eps,
输入=
无,
是否是前向传播=
假
) -> 元组[
PyTorch.
张量, ...
]
# 计算相对于单个输入的数值雅可比矩阵。返回 N 个雅可比矩阵,其中 N 是输出数量。我们使用字典来
# 表示 jacobian_cols,因为稀疏输入的索引不一定连续
# 当我们每次只扰动输入张量中的一个元素时,jvp
# 当我们每次只扰动输入张量中的一个元素时,jvp
# 等价于 fn 函数雅可比矩阵的单列
雅可比列:
字典[int,
列表[
PyTorch.
张量]] = {}
输入 =
输入[
输入索引]
如果
输入
是
无
否则
输入
断言
输入.
需要梯度
for x, 索引,
d 索引
在 _iter_tensor(
输入):
包装函数 = _with_prepare_inputs(
函数,
输入,
输入索引, x)
输入扰动 = x[
索引]
邻域检查函数 = functools.
偏函数(
_检查输出数据类型和形状相同,
索引=
索引, eps=eps
)
jvp_fn = 获取数值 JVP 函数(
wrapped_fn, 输入文本扰动, eps,
社区检查函数
)
雅可比列[d_idx] =
关于特定输入计算数值 JVPs(
JVP 函数, eps, x.
是复杂的(),
是前向广告
)
返回
_组合雅可比列(
雅可比列,
输出,
输入,
输入.
元素数量())
def 获取前向分析雅可比(
函数,
输入,
输出, *,
检查梯度数据类型=False,
所有_u=
无
) -> 元组[
元组[
PyTorch.
张量, ...
] ...
]
计算`fn(inputs)`相对于`target`的前向模式 AD 的解析雅可比矩阵
返回 N * M 雅可比矩阵,其中 N 是目标中需要梯度的张量数量
M 是非整数输出的数量。
与其他功能不同,此功能需要“输入”实际上被函数使用。
如果函数通过副作用捕获输入而不是使用传递的输入,则计算值预期是错误的(许多 torch.nn 测试都这样做)。
使用传递的输入(许多 torch.nn 测试都这样做)。
参数:
fn:计算雅可比矩阵的函数
输入:`fn`的输入
输出:提供预计算的输出以避免调用 fn 的额外一次调用
check_grad_dtypes:如果为 True,将检查梯度数据类型是否有效
all_u(可选):如果提供,雅可比矩阵将右乘此向量
返回值:
一个由 M 个 N 元组张量组成的元组
"""
避免早期导入问题
前 AD =
PyTorch.
自动微分.
前进广告
张量输入 =
元组(i for i
在
输入
如果
是否与张量类似(i)
和 i.
需要梯度)
如果
任何(i.
是复杂的() for i
在
张量输入):
抛出
值错误(
预期输入对于_get_analytical_jacobian_forward_ad_ 应该是非复杂的。
)
如果
所有_u:
雅可比矩阵 =
元组(
_allocate_jacobians_with_outputs(输出, 1) for i
在
张量输入
)
否则:
雅可比矩阵 =
元组(
带输出的分配雅可比矩阵(
输出, i.
元素数量()) for i
在
张量输入
)
替换为 fwAD.
双级():
前向梯度 =
输入文本为空,请提供需要翻译的文本
双输入 =
输入文本为空,请提供需要翻译的文本
for i, 输入
在
列举(
输入):
如果
是否与张量类似(
输入)
和
输入.
需要梯度:
如果
输入.
布局 ==
PyTorch.
MKLDNN:
# 类型:忽略[已定义]
抛出
值错误(
"MKLDNN 输入不支持前向 AD 梯度检查。"
)
输入 = fwAD.
使双重(
输入.detach(),
PyTorch.
等于零的(
输入))
如果 inp 是可微的视图,那么对偶可能不是给定的切线
# 从双精度张量中显式读取
前向梯度.
添加(fwAD.
解包双倍(
输入
)]1])
双输入.
添加(
输入)
如果
所有_u:
# 一次性完成全部的降维
实际上,为了与数值评估保持一致,我们实际上对每个输入计算一次缩减
for i, (前向梯度, u)
在
列举(zip(
前向梯度们,
所有_u)):
fw_grad.复制_(u.
查看方式(fw_grad))
原始输出 = _as_tuple(
函数(*
双输入))
双输出 =
过滤器(_is_float_or_complex_tensor,
原始输出)
for 索引_o, d_o
在
列举(
双输出):
val, res = fwAD.解包双倍(d_o)
如果 (
检查梯度数据类型
和 res
是
不
无
和 val.
是复杂的() !=
资源.
是复杂的()
):
抛出 GradcheckError(
"前向 AD 梯度数据类型不匹配。")
# 移除对应于减少输入的尺寸为 1 的额外维度
雅可比矩阵[i
]
[指数_o].
挤压_(0)
如果 res
是
无:
雅可比矩阵[i
]
[索引_o].
零()
否则:
雅可比矩阵[i
]
[指数_o].
复制_(
资源.
重塑(-1))
前向梯度.
零_()
否则:
# 逐列重建完整的雅可比矩阵列
for i, 前向梯度
在
列举(
前向梯度们):
for 线索引,
梯度索引
在
列举(
product(*[范围(m) for m
在 fw_grad.
尺寸()])
):
fw_grad[grad_idx] = 1.0
原始输出 = _as_tuple(
函数(*
双输入))
双输出 =
过滤(_is_float_or_complex_tensor,
原始输出)
for 索引_o, d_o
在
列举(
双输出):
val, res = fwAD.解包双倍(d_o)
如果 (
检查梯度数据类型
和 res
是
不
无
和 val.
是复杂的() !=
资源.
是复杂的()
):
抛出
梯度检查错误(
"前向自动微分梯度数据类型不匹配。"
)
如果 res
是
无:
雅可比矩阵[i
]
[索引_o
]
[行索引].
零_()
否则:
雅可比矩阵[i
]
[索引_o
]
[行索引].
复制_(
资源.
重塑(-1))
fw_grad[grad_idx] = 0.0
返回
杰卡比安
def _get_input_to_perturb(输入):
# 准备输入以便就地修改并进行某些操作
需要张量具有步长的操作。如果 fast_mode=False,
_iter_tensor 将处理以下情况:
如果
输入.
布局 ==
PyTorch.
MKLDNN: # type: ignore[attr-defined] # no attr _mkldnn
转换为稠密格式,以便执行需要步长张量的操作
输入扰动 =
输入.
转换为稠密格式()
如果...否则 _is_sparse_any_tensor(
输入):
因为输入可能需要梯度,所以需要克隆,并且 copy_调用 resize_,
这对于.data 是不允许的
需要扰动的输入 =
输入.
克隆()
否则:
需要扰动的输入 =
输入.
数据
返回
输入扰动
def _with_prepare_inputs(函数,
输入,
输入索引,
输入文本扰动,
快速模式=False):
包装 `fn` 以确保其输入已提供
def wrapped_fn():
输入 =
元组(
准备输入(a,
输入扰动
如果 i ==
输入索引
否则
无,
快速模式)
如果
是否与张量类似(a)
否则 a
for i, a 在
列举(_as_tuple(
输入))
)
返回
元组(a.
克隆() for a
在 _as_tuple(
函数(*
输入)))
返回 wrapped_fn
def 获取数值 JVP 函数(wrapped_fn,
输入文本扰动, eps,
邻域检查函数):
# 包装 jvp_fn 以便某些参数已经提供
def jvp_fn(Δ):
返回 _compute_numerical_gradient(
wrapped_fn, 输入文本扰动,
Δ, eps,
社区检查函数
)
返回 jvp_fn
def _reshape_tensor_or_tuple(u, 形状):
我们不需要对对应于 u 的稀疏输入进行重塑
如果 isinstance(u,
元组):
如果
不 _is_sparse_any_tensor(u[0
)]
返回 (u[0].
重塑(
形状), u[1].
重塑(
形状))
否则:
如果
不 _is_sparse_any_tensor(u):
返回 u.
重塑(
形状)
返回 u
def 乘张量或元组(u, k):
如果 isinstance(u,
元组):
返回 (k * u[0
] k * u[1])
否则:
返回 k * u
def 关于特定输入的数值 JVP 获取(
函数,
输入索引,
输入, u, eps,
是正向的=
假
) -> 列表[
PyTorch.
张量
]
输入 =
输入[
输入索引]
输入扰动 =
获取输入扰动(
输入)
包装函数 = _with_prepare_inputs(
函数,
输入,
输入索引,
输入文本扰动, True)
社区检查函数 = functools.
偏函数(
检查输出数据类型和形状是否相同, eps=eps)
jvp_fn = 获取数值 JVP 函数(wrapped_fn,
输入文本扰动, eps,
邻域检查函数)
u = 重新塑形张量或元组(u,
输入文本扰动.
形状)
u = 张量或元组相乘(u, eps)
返回
关于特定输入计算数值 JVPs(
JVP 函数, u,
输入.
是复杂的(),
是前向广告
)
def 获取数值_vJu(
函数,
输入,
输入索引,
函数输出, all_u, all_v, eps,
是前向广告
):
# 注意,如果 all_v 为 None,则此函数仅计算 Ju。
减小的雅可比矩阵:
列表[
列表[
PyTorch.
张量]] =
输入文本为空,请提供需要翻译的文本
for 输入索引, u
在 zip(
输入索引,
全部_u):
全部_Ju =
获取针对特定输入的数值 JVP(
函数,
输入索引,
输入, u, eps,
是前向广告
)
# 过滤非浮点数输出的 Ju
过滤后的 Ju =
输入文本为空,请提供需要翻译的文本
函数输出 = _as_tuple(
函数输出)
断言
长度(
所有_Ju) ==
长度(func_out)
for 集合,
输出
在 zip(
所有集合, func_out):
如果 _is_float_or_complex_tensor(
输出):
过滤后的 Ju.
添加(Ju)
否则:
# TODO: 处理其他的 Ju
通过
如果
所有_v
是
不
无:
雅可比标量:
列表[
PyTorch.
张量] =
输入文本为空,请提供需要翻译的文本
for v, 优
在 zip(all_v,
筛选后的优):
雅可比标量.
添加(
带类型提升的点积(v, Ju))
简化雅可比矩阵.
添加(
雅可比标量)
否则:
减少雅可比矩阵.
添加(
过滤后的句)
返回
减小的雅可比矩阵
def 检查雅可比矩阵是否相等(j1, j2, atol):
检查两个雅可比张量之间的最大差异是否在某个
容差 `atol` 内。
for j1_x, j2_x 在 zip(j1, j2):
如果 j1_x.
元素数量() != 0
和 (j1_x - j2_x).
绝对值().
最大值() > atol:
返回
假
返回
真实
def _stack_and_check_tensors(
list_of_list_of_tensors, 输入, numel_outputs
) -> 元组[
元组[
PyTorch.
张量, ...
] bool, bool
]
对于内层列表的第 i 个张量,检查它是否与第 i 个可微输入具有相同的大小
以及相同的 dtype。
输出雅可比矩阵 =
_使用输入分配雅可比矩阵(
输入,
numel 输出)
差分输入列表 =
列表(
_迭代张量(
输入, True))
正确的梯度大小 =
真实
正确的梯度类型 =
真实
for i, 张量列表
在
列举(
张量列表的列表):
输入 =
差分输入列表[i]
输出雅可比 =
输出雅可比矩阵[i]
for j, 张量
在
列举(
张量列表):
如果
张量
是
不
无
和
张量.
尺寸() !=
输入.
尺寸():
正确的梯度大小 =
假
如果...否则
张量
是
不
无
和
张量.dtype !=
输入.
数据类型:
正确的梯度类型 =
假
如果
张量
是
无:
输出雅可比矩阵[:, j].
零_()
否则:
稠密 = (
张量.
转换为稠密格式()
如果
不
张量.
布局 ==
PyTorch.
稀疏的
否则
张量
)
断言
输出雅可比[:, j].
元素数量() ==
稠密.
元素数量()
输出雅可比[:, j] =
密集.
重塑(-1)
返回
外雅可比矩阵,
正确的梯度大小,
正确的梯度类型
失败非确定性消息 = """
输入文本翻译为简体中文为:\n
注意:如果你的操作依赖于非确定性操作,即它列在这里:
https://pytorch.org/docs/stable/generated/torch.use_deterministic_algorithms.html
这种失败可能是预期的。
如果你正在添加一个新的操作符,请提交一个问题,然后使用以下之一
解决方案。解决方案取决于您的测试如何调用 gradcheck/gradgradcheck。
如果测试
- 手动调用 gradcheck/gradgradcheck,则调用 gradcheck/gradgradcheck
时,使用`nondet_tol=`作为关键字参数。
- 基于 OpInfo 的(例如,在 test_ops_gradients.py 中),则修改测试的 OpInfo
以具有 `gradcheck_nondet_tol=`。
- 是模块测试(例如,在 common_nn.py 中),则修改相应的
模块测试条目以具有 `gradcheck_nondet_tol=`
""
def 检查分析雅可比属性(
输入,
输出,
非确定性容差,
检查梯度数据类型,
快速模式=False, v=
无
) -> 元组[
PyTorch.
张量, ...
]
这是由快速模式和慢速模式共同使用的:
# - 对于慢速模式,vjps[i][j]是关于第 i 个的雅可比矩阵的第 j 行
输入
# - 快速模式下,vjps[i][0] 是行向量的线性组合
关于第 i 个输入的雅可比矩阵的导数
输入列表的微分 =
列表(
_迭代张量(
输入, True))
def 逆向传播函数(
输出梯度):
返回
PyTorch.
自动微分.
研究生(
输出, diff_input_list, grad_output, retain_graph=True,
允许未使用=
真实
)
# 重复计算一切以检查非确定性(我们称之为重入性)
如果
快速模式:
vjps1 = 关于特定输出的分析 VJP(
VJP 函数名,
输出.
克隆(), v)
vjps2 = _根据特定输出获取分析_vjps_(vjp_fn,
输出.
克隆(), v)
否则:
vjps1 = 计算分析雅可比矩阵行(
计算 vjp 函数,
输出.
克隆())
vjps2 = 计算分析雅可比矩阵行(vjp_fn,
输出.
克隆())
输出 numel =
输出.
元素数量()
如果
不
快速模式
否则 1
雅可比矩阵 1,
类型正确,
大小正确 =
_堆叠并检查张量(
vjps1, 输入,
输出_numel
)
jacobians2, _, _ = 栈和检查张量(vjps2,
输入,
输出 numel)
可重入的 =
检查雅可比矩阵是否相等(
雅可比矩阵 1,
雅可比矩阵 2,
非确定性容差)
如果
不
类型正常
和
检查梯度数据类型:
抛出
毕业审核错误(
"梯度数据类型不匹配")
如果
不
大小正常:
抛出 GradcheckError(
"分析梯度大小不正确")
如果
不
可重入的:
抛出 GradcheckError(
向后不是可递归的,即使用 "
相同输入和 grad_output 多次给出不同的值,
尽管分析梯度与数值梯度匹配。
f非确定性容忍度是{
非确定性公差}
。 +
非确定性失败信息
)
返回
雅可比矩阵 1
def 获取分析_vJu 反向模式(
输入,
输出,
非确定性公差,
检查梯度数据类型,
所有 v,
所有 u
):
减少雅可比矩阵:
列表[
列表[
PyTorch.
张量]] =
输入文本为空,请提供需要翻译的文本
for 输出, v
在 zip(
输出,
全部_v):
全部_vJ =
_检查分析雅可比属性(
输入,
输出,
非确定性容差,
检查梯度数据类型,
快速模式=True, v=v
)
雅可比标量:
列表[
PyTorch.
张量] =
输入文本为空,请提供需要翻译的文本
for vJ, u 在 zip(all_vJ,
所有_u):
# 为什么这里需要挤压?vJ 是一个 2 维张量,这样我们可以重用
# 慢模式中的错误检查逻辑
vJ = vJ.T.挤压(0)
如果 vJ.
是复杂的(): # C -> R
tv = PyTorch.
以实数视图查看(vJ.
解决连词())
tr = 电视.
选择(-1, 0)
题 =
电视.
选择(-1, 1)
雅可比标量.
添加(
三.
点(u[0]) + 1j * ti.
点(u[1]))
否则:
R -> R
雅可比标量.
添加(vJ.
点(u))
减少雅可比矩阵.
添加(
雅可比标量)
返回
简化雅可比
@deprecated(
"`get_analytical_jacobian`曾是 PyTorch 的私有 API,并不打算公开。我们正在弃用它,并将移除它。"
"我们正在弃用它,并将移除它。"
"在 PyTorch 的未来版本中。如果您有特定的用途或对该功能提出稳定 API 的请求,请提交"
"我们可以在 https://github.com/pytorch/pytorch/issues/new 上创建一个问题"
"提交一个 issue",
分类=
未来警告,
)
def get_analytical_jacobian(输入,
输出,
非确定容差=0.0,
毕业输出=1.0):
复现重构前的旧 get_analytical_jacobian 的行为
此代码与_check_analytical_jacobian_attributes 共享很多代码
如果 (
grad_out != 1.0
): # grad_out 参数仅保留为向后兼容的原因
抛出
值错误(
"期望 grad_out 为 1.0。get_analytical_jacobian 不再"
"支持 grad_out != 1.0 的值。"
)
如果
输出.
是复杂的():
抛出
值错误(
"期望输出为非复数。get_analytical_jacobian 不再"
支持返回复杂输出的功能。
)
diff_input_list = 列表(
_迭代张量(
输入, True))
def vjp_fn(梯度输出):
返回
PyTorch.
自动微分.
研究生(
输出,
差分输入列表,
梯度输出, retain_graph=True,
允许未使用=
真实
)
# 计算两次以检查非确定性(我们称之为重入性)
vjps1 = _计算分析雅可比矩阵行(vjp_fn,
输出.
克隆())
vjps2 = 计算分析雅可比矩阵行(
计算梯度函数,
输出.
克隆())
输出元素数量 =
输出.
元素数量()
雅可比矩阵 1,
类型正常,
尺寸正常 =
栈和检查张量(
vjps1, 输入,
输出_numel
)
jacobians2, _, _ = _stack_and_check_tensors(vjps2, 输入,
输出_numel)
可重入的 =
_检查雅可比矩阵是否相等(
雅可比矩阵 1,
雅可比矩阵 2,
非确定性容差)
返回
雅可比矩阵 1,
可重入的,
尺寸合适,
类型合适
def 获取分析雅可比矩阵(
输入,
输出,
输入索引,
输出索引):
以慢速模式为单个输入输出对计算分析雅可比矩阵。
跳过对数据类型、形状和可重入性的检查。
雅可比矩阵 =
_检查分析雅可比矩阵属性(
输入,
输出[
输出索引
]
非确定性公差=float(
"无穷大"),
检查梯度数据类型=
假
)
返回
雅可比矩阵[
输入索引]
def 计算分析雅可比矩阵行(
vjp_fn, 示例输出
) -> 列表[
列表[
可选[
PyTorch.
张量]]]:
# 通过投影 `vjp_fn` = v^T J 在标准基上逐行计算雅可比矩阵
# 向量:vjp_fn(e) = e^T J 是雅可比矩阵的对应行
# 注意:此函数不假设 vjp_fn(v) 返回具有相同
元素数量用于不同版本。稍后我们在将行合并为一个张量时进行检查。
将行合并为一个张量。
grad_out_base = PyTorch.
等于零的(
样本输出,
内存格式=
PyTorch.
旧连续格式
)
flat_grad_out = grad_out_base.视图(-1)
# jacobians_rows[i][j] 是第 i 个输入的雅可比矩阵的第 j 行
jacobians_rows: 列表[
列表[
可选[
PyTorch.
张量]]] =
输入文本为空,请提供需要翻译的文本
for j 在
范围(
平滑梯度输出.
元素数量()):
平滑梯度输出.
零_()
平滑梯度输出[j] = 1.0
雅可比矩阵第 j 行的投影
梯度输入 = vjp_fn(grad_out_base)
for i, d_x 在
列举(
梯度输入):
如果 j == 0:
jacobians_rows.添加([])
雅可比行列[i] += [
对 x 的导数.
克隆()
如果 isinstance(
对 x 的导数,
PyTorch.
张量)
否则
无
]
返回
雅可比行列
def 根据特定输出获取分析 VJP(
vjp 函数,
样本输出, v
) -> 列表[
列表[
可选[
PyTorch.
张量]]]:
梯度输入 = vjp_fn(v.
重塑(sample_output.
形状))
vjps: 列表[
列表[
可选[
PyTorch.
张量]]] = [
[vjp.克隆()
如果 isinstance(vjp,
PyTorch.
张量)
否则
无] for vjp
在
梯度输入
]
返回 vjps
def _检查输入(
元组输入) -> bool:
# 确保至少保存一个输入的梯度
任何需要梯度输入 =
假
for 索引,
输入
在
列举(
元组输入):
如果
是否与张量类似(
输入)
和
输入.
需要梯度:
如果
不 (
输入.dtype ==
PyTorch.float64
或者
输入.dtype ==
PyTorch.complex128):
警告.
警告(
f输入编号{
索引}
需要梯度以及“
"不是双精度浮点数或复数。"
"如果所有输入都不是双精度浮点数或复数,这个检查很可能会失败。"
"不是双精度浮点数或复数。"
)
如果
输入.is_sparse:
内容 =
输入.
值()
如果...否则 _is_sparse_compressed_tensor(
输入):
内容 =
输入.
值()
否则:
内容 =
输入
# TODO: 为了覆盖更多问题情况,将 stride = 0 的检查替换为
一旦我们有了适当的函数来检查,就不会有任何内存重叠。
如果
内容.
布局
是
不
PyTorch._mkldnn:
# 类型:忽略[已定义]
如果
不
所有(
st > 0 或者 sz <= 1
for st, sz 在 zip(
内容.
步长(),
内容.
尺寸())
):
抛出
运行时错误(
f"The {索引}
输入有一个步长为 0 的维度。gradcheck 只对"
支持非重叠输入,以便能够"
正确计算数值梯度。你应该在传递给 gradcheck 之前调用"
".contiguous 对输入进行连续操作。"
)
需要梯度计算输入 =
真实
如果
不
需要梯度计算输入:
抛出
值错误(
gradcheck 期望至少有一个输入张量需要梯度计算,
但它们都没有设置 requires_grad=True。
)
返回
真实
def 检查输出(
输出) ->
无:
如果
任何(_is_sparse_any_tensor(t) for t
在
输出
如果 isinstance(t,
PyTorch.
张量)):
在稀疏输出上调用 to_dense()更简单
修改分析雅可比矩阵
抛出
值错误(
目前 gradcheck 尚不支持稀疏输出。
请在 fn 的输出上调用 to_dense(masked_grad=...)进行 gradcheck。
)
如果
任何(t.
布局 ==
PyTorch._mkldnn for t
在
输出
如果 isinstance(t,
PyTorch.
张量)):
# 类型:忽略[已定义]
抛出
值错误(
MKLDNN 输出在 gradcheck 中尚不支持。
请对 fn 的输出调用 call to_dense(masked_grad=...)进行 gradcheck。
)
def 检查无不可导输出(
函数,
输入,
函数输出, eps, *,
是前向广告
) -> bool:
当没有可微分的输出时,函数的数值梯度为
预期为零。
雅可比全输入输出 = _get_numerical_jacobian(
函数,
输入,
函数输出, eps=eps,
前向广告=
是前向广告
)
for 雅可比全输出和固定输入
在
雅可比全输入输出:
for ja_c_bian
在
雅可比全输出和固定输入:
如果
PyTorch.
不(
詹可布, 0).
总和() > 0:
抛出 GradcheckError(
数值梯度对于期望为零的函数
)
返回
真实
def 检查无导数输出快速(
函数,
函数输出,
所有输入,
输入索引,
全部_u, eps,
非确定性容差
):
for 输入索引, u
在 zip(
输入索引,
全部_u):
jvps = 获取针对特定输入的数值 JVP(
函数,
输入索引,
所有输入, u, eps)
for 隐式求导
在 jvps:
如果 jvp.
元素数量() == 0:
continue
如果 (
隐式求导 -
PyTorch.
等于零的(jvp)).
绝对值().
最大值() >
非确定性容差:
抛出 GradcheckError(
函数期望的数值梯度为零
)
返回
真实
批处理梯度失败信息 =
""
在测试批梯度计算时,gradcheck 或 gradgradcheck 失败。
这可能以多种方式被调用(通过直接调用 gradcheck/gradgradcheck 的测试,或通过自动生成的测试)。
如果您正在添加新的操作符,请提交一个问题,然后使用以下之一。
如果您正在添加新的操作符,请提交一个问题,然后使用以下之一。
解决方案。解决方案取决于您的测试如何调用 gradcheck/gradgradcheck。
如果测试
- 手动调用 gradcheck/gradgradcheck,则调用 gradcheck/gradgradcheck
时,请将`check_batched_grad=False`作为关键字参数。
如果是基于 OpInfo 的(例如,在 test_ops_gradients.py 中),则修改测试的 OpInfo
使其具有`check_batched_grad=False`和/或`check_batched_gradgrad=False`。
如果您正在修改支持批量梯度计算的现有算子,
或者希望使新的算子支持批量梯度计算,请阅读
以下。
计算批量梯度(例如,雅可比矩阵、海森矩阵),我们使用 vmap 进行反向
计算。最常见的问题情况是如果存在一个 'vmap 不兼容
在反向传播中操作。请参阅
注意:[如何编写与 vmap 兼容的逆向公式]
在代码库中查看解释以修复此问题。
""".strip()
FAILED_BATCHED_GRAD_MSG_FWD_AD = ""
在测试使用前向模式的 AD 进行批量梯度计算时,gradcheck 失败。
此测试在同时设置 `check_batched_grad=True` 时自动启用
以及 `check_forward_ad=True` 后生效,但可以通过以下方式禁用
取决于测试是如何调用的(通过直接调用 gradcheck 的测试或通过自动生成的测试)
进行。
如果您正在添加新的操作符,请提交一个问题,然后使用以下解决方案之一。
解决方案取决于您的测试如何调用 gradcheck/gradgradcheck。
如果测试
- 手动调用 gradcheck/gradgradcheck,则调用 gradcheck/gradgradcheck
以`check_batched_forward_grad=False`作为关键字参数。
- 如果是基于 OpInfo 的(例如,在 test_ops_gradients.py 中),则修改 OpInfo 以进行测试
使其具有`check_batched_forward_grad=False`
""
def _get_failed_batched_grad_test_msg(
输出索引,
输入索引,
资源, exp,
前向广告=
假
):
返回 f
""
对于输出{
输出索引}
并且输入{
输入索引}:
{批处理梯度消息失败前向
如果
是前向广告
否则
批处理梯度消息失败}
收到:
{资源}
预期:
{exp}
""".strip()
def _测试批量梯度前向(
函数,
输入) -> bool:
前 AD =
PyTorch.
自动微分.
前进广告
# 避免早期导入问题(我们需要这个吗?)
断言 isinstance(
输入,
元组)
for 输入索引,
当前输入
在
列举(
输入):
如果
不 (
是否与张量类似(
当前输入)
和
当前输入.
需要梯度):
continue
def jvp(切线:
PyTorch.
张量):
替换为 fwAD.
双级():
双向 = fwAD.
使双重(
当前输入.detach(),
切线)
具有双重输入 =
元组(
双重
如果
索引 ==
输入索引
否则 (
输入.detach()
如果
是否与张量类似(
输入)
否则
输入)
for 索引,
输入
在
列举(
输入)
)
双输出 = _as_tuple(
函数(*
具有双重输入))
返回 =
输入文本为空,请提供需要翻译的文本
for 双输出
在
双输出:
如果
双输出
是
无:
continue
原始输出,
切线输出 = fwAD.
解包双倍(
双输出)
如果
切线输出
是
不
无:
返回.
添加(
切线输出)
否则:
返回.
添加(
PyTorch.
零值(
[]
数据类型=
原始输出.
数据类型,
设备=
原始输出.
设备
).展开(
原始输出.
形状)
)
返回
元组(
返回)
如果
不 _is_float_or_complex_tensor(
当前输入):
continue
切线 = [
PyTorch.randn_like(
当前输入) for _
在
范围(2)]
预期 = [jvp(t) for t
在
切线]
预期 = [
PyTorch.
栈(
碎片) for
碎片
在 zip(*
预期的)]
尝试:
结果 = _vmap(jvp)(
PyTorch.
栈(
切线))
除了
运行时错误 as
ext: ex:
# 重新抛出以提供更好的错误信息
抛出 GradcheckError(
f"在计算批梯度时,得到:"{
ext: ex}
\n\n
\n\n{批处理梯度消息前向传输失败}"
) 来自
示例
for 输入索引, (
资源, exp)
在
列举(zip(
结果,
预期)):
如果
PyTorch.allclose(
资源, exp):
continue
抛出 GradcheckError(
_获取失败批量梯度测试信息(
输入索引,
输入索引,
资源, exp,
前向广告=
真实
)
)
返回
真实
def _测试批量梯度(
输入,
输出,
输出索引) -> bool:
# NB: _test_batched_grad 比较两次 autograd.grad 调用与单个
# vmap(autograd.grad) 调用。这并不完全是一个“gradcheck”,因为
# 我们并没有比较解析雅可比与数值雅可比,
# 但在道德上相似(我们本可以计算完整的解析雅可比
通过 vmap,但可能会很慢)
diff_input_list = 列表(
_迭代张量(
输入, True))
梯度 = functools.
偏函数(
PyTorch.
自动微分.
研究生,
输出,
差分输入列表,
retain_graph=True,
允许未使用=True,
)
def vjp(v):
结果 =
研究生(v)
结果 =
元组(
梯度
如果
梯度
是
不
无
否则
PyTorch.
零值([],
数据类型=
输入.
数据类型,
设备=
输入.
设备).
展开(
输入.
形状)
for 研究生,
输入
在 zip(
结果,
差分输入列表)
)
返回
结果
grad_outputs = [PyTorch.randn_like(
输出) for _
在
范围(2)]
预期 = [vjp(gO) for
哎呦
在 grad_outputs]
预期 = [
PyTorch.
栈(
片段) for
片段
在 zip(*
预期)]
# Squash warnings since these are expected to happen in most cases
# 注意:这不会在 CUDA 测试中工作:https://github.com/pytorch/pytorch/issues/50209
替换为
警告.
捕获警告():
警告.
过滤警告(
"忽略",
消息=
"性能有所下降")
警告.
过滤警告(
"忽略",
消息=
请使用 `torch.vmap`)
尝试:
结果 = vmap(vjp)(
PyTorch.
栈(grad_outputs))
除了
运行时错误 as
ext: ex:
# 在正确的调用位置不抛出错误是可以的。
# 那是因为调用点始终在 Python 内部
# 使用 autograd.grad 而不是 C++的跟踪回溯,查看哪一行
# 反向公式
抛出 GradcheckError(
f"在计算批梯度时,出现:"{
ext: ex}
\n\n
\n\n{批处理梯度消息失败}"
) 来自
ext: ex
for 输入索引, (
资源, exp)
在
列举(zip(
结果,
预期)):
如果
PyTorch.allclose(
资源, exp):
continue
抛出 GradcheckError(
获取失败的批量梯度测试消息(
输出索引,
输入索引,
资源, exp)
)
返回
真实
def _测试反向乘以 grad_output(
输出,
输入,
遮蔽) -> bool:
# 测试反向是否乘以 grad_output
差分输入列表:
列表[
PyTorch.
张量] =
列表(
_迭代张量(
输入, True))
如果
不
差分输入列表:
抛出 GradcheckError(
输入中没有找到需要梯度信息的张量)
梯度输入 =
PyTorch.
自动微分.
研究生(
输出,
差分输入列表,
[
PyTorch.
等于零的(o,
内存格式=
PyTorch.
旧连续格式)
for o 在
输出
]
允许未使用=True,
)
for gi, di 在 zip(
梯度输入,
差分输入列表):
如果
你好
是
无:
continue
如果 isinstance(
你好,
PyTorch.
张量)
和
你好.
布局 !=
PyTorch.strided:
如果 gi.
布局 != di.
布局:
抛出 GradcheckError(
"grad 布局不正确("
+ str(gi.布局)
+ "不是 "
+ str(di.布局)
+ )"
)
如果 _is_sparse_any_tensor(gi):
稀疏的 = str(gi.
布局).
替换(
火炬。,
输入文本翻译为简体中文为:"").
替换(
_酷,
输入文本翻译为简体中文为:"")
如果 gi.
稀疏维度() != di.
稀疏维度():
抛出 GradcheckError(
f"梯度是{
稀疏的}
张量,但 sparse_dim 不正确"
f" {gi.稀疏维度()}
,预期{
对不起,您提供的文本 "di" 过于简短,无法进行有效的翻译。请提供更完整的句子或段落以便进行翻译.
稀疏维度()}"
)
如果 gi.
稠密维度() != di.
稠密维度():
抛出 GradcheckError(
f"梯度是{
稀疏的}
张量,但具有错误的 dense_dim"
f" {gi.稠密维度()}
,期望{di.
稠密维度()}"
)
吉 =
吉.
转换为稠密格式()
地 =
地.
转换为稠密格式()
如果
遮蔽:
如果
不
PyTorch.allclose(gi,
PyTorch.
等于零的(gi)):
抛出 GradcheckError(
"反向传播不乘以 grad_output")
如果...否则
不 gi.eq(0).
所有():
抛出 GradcheckError(
"反向梯度未乘以 grad_output")
如果 gi.dtype != di.
数据类型:
抛出 GradcheckError(
"梯度类型错误")
如果 gi.
设备 != di.
设备:
抛出 GradcheckError(
算法是错误的设备)
如果 gi.
尺寸() != di.
尺寸():
抛出 GradcheckError(
累加器大小不正确)
返回
真实
def _测试_未定义的前向模式(
函数,
输出,
输入):
前 AD =
PyTorch.
自动微分.
前进广告
_输入张量索引,
输入张量 =
_获取输入张量(
输入)
_all_v, 所有_u, _all_u_dense =
制作向量(
输入张量,
输出,
使用前向传播=
真实
)
替换为 fwAD.
双级():
前向梯度 =
输入文本为空,请提供需要翻译的文本
双输入 =
输入文本为空,请提供需要翻译的文本
张量索引 =
设置()
for i, 输入
在
列举(
输入):
如果
是否与张量类似(
输入)
和
输入.
需要梯度:
如果
输入.
布局 ==
PyTorch._mkldnn:
# 类型:忽略[已定义]
抛出
值错误(
"MKLDNN 输入不支持前向 AD 梯度检查。"
)
输入 = fwAD.
使双重(
输入.detach(),
PyTorch.
等于零的(
输入))
如果 inp 是可微的视图,那么对偶可能不是给定的切线
# 从双精度张量中显式读取
前向梯度.
添加(fwAD.
解包双倍(
输入
)]1])
张量索引.
添加(i)
双输入.
添加(
输入)
for i, (fw_grad, u) 在
列举(zip(
前向梯度,
所有_u)):
fw_grad.复制_(u.
查看方式(fw_grad))
for 索引,
输入
在
列举(
输入):
如果
索引
不
在
张量索引:
continue
双输入对象 =
双输入[
索引]
# 情况 1(零张量切线)
双输入[
索引] = fwAD.
使双重(
输入.detach(),
PyTorch.
等于零的(
输入))
原始输出 = _as_tuple(
函数(*
双输入))
dual_outputs1 = 过滤(_is_float_or_complex_tensor,
原始输出)
# 情况 2(高效的零张量切线,因为我们不创建双对象,而是传递一个常规张量)
双输入[
索引] =
输入.detach()
原始输出 = _as_tuple(
函数(*
双输入))
dual_outputs2 = 过滤(_is_float_or_complex_tensor,
原始输出)
# 重置
双输入[
索引] =
双输入对象
for 指数_o, (d_o1, d_o2)
在
列举(zip(
双输出 1,
双输出 2)):
_val1, res1 = fwAD.解包双倍(d_o1)
_val2, res2 = fwAD.解包双倍(d_o2)
如果
不 (res1
是
无
或者 res2
是
无):
如果
不
PyTorch.allclose(res1, res2):
抛出 GradcheckError(
"输出索引处的切线值不匹配:",
指数_o,
"当输入:",
输入,
"有一个未定义的切线值。",
"得到:",
res1,
"但预期:",
res2,
)
返回
真实
def _测试_未定义反向模式_(
函数,
输出,
输入) -> bool:
差分输入列表:
列表[
PyTorch.
张量] =
列表(
_迭代张量(
输入, True))
如果
不
差分输入列表:
抛出 GradcheckError(
"输入中没有找到需要求导的张量")
def 警告:break 兼容性():
警告.
警告(
"向后兼容性:新未定义梯度支持检查默认启用,但可能会破坏现有调用者"
"功能默认启用,但可能会破坏现有调用者"
"这个函数的。如果您是这样,您可以调用这个"
'使用 "check_undefined_grad=False" 来禁用此功能'
)
def 检查未定义梯度支持(
检查输出的):
grads_output = [
PyTorch.
等于零的(o,
内存格式=
PyTorch.
遗产连续格式)
for o 在
输出以供检查
]
尝试:
梯度输入 =
PyTorch.
自动微分.
研究生(
输出检查,
差分输入列表,
梯度输出,
允许未使用=
真实
)
除了
运行时错误 as e:
警告边界条件破坏()
抛出 GradcheckError(
"预期反向函数处理未定义输出梯度。"
请参阅“关于未定义输出梯度的说明”
'"tools/autograd/derivatives.yaml"'
) 来自 e
for gi 在
输入梯度:
如果 (gi
是
不
无)
和 (
不 gi.eq(0).
所有()):
警告:边界条件中断()
抛出 GradcheckError(
"期望当所有输出梯度都未定义或为零时,所有输入梯度都应为未定义或零。请参阅“关于未定义输出梯度的说明”"
'或为零。请查看“tools/autograd/derivatives.yaml”中的说明。'
'"tools/autograd/derivatives.yaml"'
)
返回
真实
# 所有反向函数都必须在所有输出梯度都未定义的情况下正常工作
需要检查的输出 = [
[
PyTorch._C.
函数.
未定义 Grad()(o)
for o 在
可微分输出(
函数(*
输入))
# 此检查过滤出不是 Tensor 实例的 Tensor-like 对象。
如果 isinstance(o,
PyTorch.
张量)
]
]
# 如果有多个输出梯度,我们应该能够逐个取消定义而不出错。
如果
长度(outputs_to_check[0]) > 1:
for undef_grad_idx 在
范围(
长度(
输出)):
需要检查的输出 =
可微分的输出(
函数(*
输入))
需要检查的输出.
添加(
[
PyTorch._C.
函数.
未定义梯度()(o)
如果
索引 ==
未定义梯度索引
否则 o
for 索引, o
在
列举(
需要检查的输出)
]
)
返回
所有(
检查未定义梯度支持(
输出) for
输出
在
检查输出)
def _as_tuple(x):
如果 isinstance(x,
元组):
返回 x
如果...否则 isinstance(x,
列表):
返回
元组(x)
否则:
返回 (x,)
def 可微输出(x):
返回
元组(o for o
在 _as_tuple(x)
如果 o.
需要梯度)
def get_notallclose_msg(
分析,
数值,
输出索引,
输入索引,
复杂索引,
测试图像=False,
前向广告=False,
) -> str:
输出是复杂的 = (
(不
前向广告)
和
复杂索引
和
输出索引
在
复杂索引
)
输入是否复杂 =
是前向广告
和
复杂索引
和
输入索引
在
复杂索引
部分 =
"想象"
如果
测试图像
否则
真实
元素 =
输入
如果
是前向广告
否则
输出
前缀 = (
请提供需要翻译的文本
如果
不 (
输出很复杂
或者
输入复杂)
否则 f
当考虑复杂{
部分}
的部分时,{
元素}
“
)
模式 =
"使用正向模式计算"
如果
是前向广告
否则
请提供需要翻译的文本
返回 (
前缀
+ f"雅可比矩阵"{
模式}
输出与输入不匹配{
输出索引:d}
关于输入的偏导数{
输入索引:d},
输入文本翻译为简体中文为:\n"
f"数值:{
数值}
输入文本翻译为简体中文为:\n
分析:{
分析}
输入文本翻译为简体中文为:\n"
)
def _转置(
张量矩阵):
# 返回元组列表
返回
列表(zip(*
张量矩阵))
def _实部和虚部输出(
函数):
# 返回新的函数 real(fn) 和 imag(fn),其中 real(fn) 和 imag(fn) 的行为与
# 原始的 fn 相同,除了在复数输出上应用 torch.real 或 torch.imag
def 应用于 c_outs(
函数, fn_to_apply):
def wrapped_fn(*输入):
输出:outs = _as_tuple(
函数(*
输入))
返回
元组(
应用函数(o)
如果 o.
是复杂的()
否则 o for o
在
输出)
返回 wrapped_fn
返回
应用到 C 输出(
函数,
PyTorch.
真实),
应用到 C 输出(
函数,
PyTorch.
图像)
def 实部和虚部输入(
函数,
复杂输入索引,
元组输入):
返回接受实数输入而不是复数输入的新函数
(x, y) -> fn(x + y * 1j)。它计算:inp -> fn(inp + y * 1j) 和 inp -> fn(x + inp * 1j)。
在每种情况下,另一部分被视为常数。
在这里我们不使用 0 作为常数,以确保我们始终以有效的输入调用用户函数。
def 应用到输入 c(
函数,
应用函数):
def wrapped_fn(*输入):
新输入 =
列表(
输入)
for 应该是复杂的
在
复杂输入索引:
新输入[
应该是复杂的] =
要应用的功能(
新输入[
应该很复杂
]
元组输入[
应该很复杂]
)
返回 _as_tuple(
函数(*
新输入))
返回 wrapped_fn
real_fn = apply_to_c_inps(函数,
Lambda 函数
输入,
原始:
输入 +
原始.
图像 * 1j)
imag_fn = apply_to_c_inps(函数,
Lambda 函数
输入,
原始:
原始.
真实 +
输入 * 1j)
返回 real_fn, imag_fn
def _gradcheck_real_imag(
gradcheck_fn,
函数,
func_out,
元组输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
check_forward_ad,
check_backward_ad,
非确定性容差,
检查未定义的梯度,
):
复杂输出索引 = [i for i, o
在
列举(
输出)
如果 o.
是复杂的()]
是否有复杂输出 =
任何(o.
是复杂的() for o
在 _as_tuple(func_out))
如果
检查反向_ad:
如果
是否有复杂输出:
实函数,
虚函数 =
实部和虚部输出(
函数)
虚函数输出 =
虚函数(*
元组输入)
虚部输出 =
可微输出(
图像函数输出)
梯度检查函数(
图像函数,
图像函数输出,
元组输入,
图像输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定性容差,
复杂索引=
复杂输出索引,
测试图像=True,
)
实际功能输出 =
实际函数(*
元组输入)
实际输出 =
可微输出(
实际函数输出)
梯度检查函数(
实际函数,
真实函数输出,
元组输入,
真实输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定性容差,
复杂索引=
复杂输出索引,
)
否则:
梯度检查函数(
函数,
func_out,
元组输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定性容差,
)
如果
检查前向地址:
复杂输入索引 = [
i
for i, 输入
在
列举(
元组输入)
如果
是否与张量类似(
输入)
和
输入.
是复杂的()
]
如果
复杂输入索引:
实际函数, imag_fn =
_实部和虚部输入(
函数,
复杂输入索引,
元组输入
)
图像输入 = [
输入.
图像
如果
是否与张量类似(
输入)
和
输入.
是复杂的()
否则
输入
for 输入
在
元组输入
]
图像函数输出 =
图像函数(*
图像输入)
diff_imag_func_out = _可微输出(imag_func_out)
gradcheck_fn(
imag_fn,
imag_func_out,
imag_inputs,
diff_imag_func_out,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定容差,
复杂索引=
复杂输入索引,
测试图像=True,
使用前向传播=True,
)
真实输入 = [
输入.
真实
如果
是否与张量类似(
输入)
和
输入.
是复杂的()
否则
输入
for 输入
在
元组输入
]
实际函数输出 =
实际函数(*
实际输入)
diff_real_func_out = _可微输出(real_func_out)
gradcheck_fn(
真实函数,
真实函数输出,
真实输入,
差分真实函数输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定容差,
复杂索引=
复杂输入索引,
使用前向传播=True,
)
如果
检查未定义的梯度:
_test_未定义前向模式(imag_fn, imag_func_out, imag_inputs)
_test_undefined_forward_mode(真实函数,
真实函数输出,
真实输入)
否则:
梯度检查函数(
函数,
func_out,
元组输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定容差,
使用前向广告=True,
)
如果
检查未定义的梯度:
_测试未定义的前向模式_(
函数,
输出,
元组输入)
def _慢速梯度检查_(
函数,
func_out,
元组输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定容差,
*,
使用前向广告=False,
复杂索引=
无,
测试图像=False,
遮蔽=False,
):
函数输出 = _as_tuple(func_out)
如果
不
输出:
返回
_检查无不可导输出(
函数,
元组输入, func_out, eps=eps,
前向广告=
使用前向传播
)
元组输入数值 =
元组输入
如果
隐藏
否则
稠密化(
元组输入)
数值 =
转置(
_get_numerical_jacobian(
函数,
元组输入数值,
func_out,
eps=eps,
前向广告=
使用前向传播,
)
)
# 注意:[数值输出与解析输出长度比较]
# 数值路径返回所有输出的雅可比量,即使该量的 requires_grad 为 False
# 输出为 False。这种行为对于 _check_no_differentiable_outputs 函数是必要的。
数值 = [
江南 for o,
江南
在 zip(func_out,
数值)
如果 o.
需要梯度]
如果
使用前向算法:
分析前向 =
获取前向分析雅可比(
函数,
元组输入, func_out,
检查梯度数据类型=
检查梯度数据类型
)
for i, 每输出 n 个
在
列举(
数值):
for j, n 在
列举(
输出每):
a = 分析前向[j
]
[i]
如果
不 _allclose_with_type_promotion(a, n.
到(a.
设备),
相对误差, atol):
抛出
梯度检查错误(
_get_notallclose_msg(
a, n, i, j, complex_indices, test_imag, 前向广告=
真实
)
)
否则:
for i, o 在
列举(
输出):
分析 =
_检查分析雅可比属性(
元组输入, o,
非确定容差,
检查梯度数据类型
)
for j, (a, n) 在
列举(zip(
分析性的,
数值的[i])):
如果
不 _allclose_with_type_promotion(a, n.
到(a.
设备),
相对误差, atol):
抛出
梯度检查错误(
获取 notallclose 消息(a, n, i, j,
复杂索引,
测试图像)
)
返回
真实
def 点与类型提升(u, v):
断言 u.
维度() == 1
和 v.
维度() == 1
返回 (u * v).
总和()
def _allclose_with_type_promotion(a, b, 相对误差, atol):
类型提升 =
PyTorch.
推广类型(a.
数据类型, b.
数据类型)
a = a.到(
数据类型=
类型提升)
b = b.到(
数据类型=
类型提升)
返回
PyTorch.allclose(a, b,
相对误差, atol)
def _to_real_dtype(数据类型):
如果 dtype ==
PyTorch.complex128:
返回
PyTorch.float64
如果...否则 dtype ==
PyTorch.complex64:
返回
PyTorch.float32
否则:
返回 dtype
def _vec_from_tensor(x, 生成器, downcast_complex=False):
# 创建与 x 相同元素数量的随机向量
如果 x 是复数且 downcast_complex 为 False,则创建一个只包含实部的复数张量。
对于稀疏张量,创建一个具有随机值的随机稀疏向量,确保大小设置得足够大,以免被推断为更小的值。
如果 x.
布局 ==
PyTorch.
稀疏 COO:
对于稀疏张量,创建一个具有随机值的随机稀疏向量,确保大小设置得足够大,以免被推断为更小的值。
确保大小设置得足够大,以免被推断为更小的值。
x_values = x.值()
dtype = _to_real_dtype(x.数据类型)
如果 downcast_complex
否则 x.dtype
values = (
PyTorch.
随机(x_values.
元素数量(),
生成器=
生成器)
.到(
数据类型=
数据类型,
设备=x.
设备)
.视图(x_values.
形状)
)
values /= 值.
归一化()
vec = PyTorch.
稀疏 Coo 张量(x.
索引(),
值, x.
尺寸(),
设备=x.
设备)
如果...否则 _is_sparse_compressed_tensor(x):
如果 x.
布局
在 {
PyTorch.
稀疏压缩存储格式,
PyTorch.
稀疏_bsr}:
压缩索引,
平凡索引 = x.
鸡索引(), x.
列索引()
否则:
压缩索引,
平凡索引 = x.
c 列索引(), x.
行索引()
x_values = x.值()
dtype = _to_real_dtype(x.数据类型)
如果 downcast_complex
否则 x.dtype
values = (
PyTorch.
随机(x_values.
元素数量(),
生成器=
生成器)
.到(
数据类型=
数据类型,
设备=x.
设备)
.视图(x_values.
形状)
)
values /= 值.
归一化()
vec = PyTorch.
稀疏压缩张量(
压缩索引,
平凡索引,
值,
x.尺寸(),
布局=x.
布局,
设备=x.
设备,
)
否则:
dtype = _to_real_dtype(x.数据类型)
如果 downcast_complex
否则 x.dtype
vec = PyTorch.
随机(x.
元素数量(),
生成器=
生成器).
到(
数据类型=
数据类型,
设备=x.
设备
)
vec /= 向量.
归一化()
返回 vec
def _get_inp_tensors(元组输入):
inp_idx_tup = [
(i, t)
for i, t 在
列举(
元组输入)
如果
是否与张量类似(t)
和 t.
需要梯度
]
返回 [tup[0] for tup
在
输入索引元组
] [tup[1] for tup
在 inp_idx_tup]
def _adjusted_atol(atol, u, v):
在慢速梯度检查中,我们逐元素比较 A 和 B,即对于某些 a、b 我们
允许 |a - b| ≤ atol + rtol * b。但由于我们现在比较 q1 = v^T A u 和
q2 = v^T B u,我们必须允许 |q1 - q2| ≤ v^T E u + rtol * v^T B u,其中 E 是
正确尺寸的矩阵,其中每个元素是 atol。
#
我们看到 atol 需要通过 v^T M u 进行缩放(其中 M 是一个全一的 M x N 矩阵)
v^T M u = ∑_i ∑_j u_i * v_j = (∑_i u_i)(∑_i v_i)
TODO: 正确处理 u 为元组而不是仅取第一个元素的情况
u = u[0] 如果 isinstance(u,
元组)
否则 u
累加_u = u.
总和()
累加_v = 1.0
如果 v
是
无
否则 v.
总和()
返回
容差 * float(
累加_u) * float(sum_v)
快速失败慢速成功信息 =
""
快速梯度检查失败,但元素间的差异很小。这意味着测试可能在慢速模式下通过了!
测试可能已经在慢速模式下通过了!
如果您正在添加新的操作符,请提交一个问题,然后使用以下之一。
解决方案。解决方案取决于您的测试如何调用 gradcheck/gradgradcheck:
如果测试
- 手动调用 gradcheck/gradgradcheck,则调用 gradcheck/gradgradcheck
使用`fast_mode=False`作为关键字参数。
- 如果是基于 OpInfo 的(例如,在 test_ops_gradients.py 中),则修改测试的 OpInfo
以具有`gradcheck_fast_mode=False`
- 是一个模块测试(例如,在 common_nn.py 中),然后修改相应的
模块测试条目为 `gradcheck_fast_mode=False`
""".strip()
def _run_slow_mode_and_get_error(
函数,
元组输入,
输出,
输入索引,
输出索引,
相对误差, atol, eps,
是前向广告
):
# 以慢速模式计算雅可比矩阵,以获得更好的错误信息
慢数值 = _get_numerical_jacobian(
函数,
元组输入,
输出, eps=eps,
前向广告=
是前向广告
)]
输入索引
]
[输出索引]
如果
前向广告:
def 新函数(
输入):
新输入 =
列表(
元组输入)
新输入[
输入索引] =
输入
返回 _as_tuple(
函数(*
新输入))[
输出索引]
慢分析 =
获取前向分析雅可比(
新函数, (
元组输入[
输入索引
],), (
输出[
输出索引],)
)]0
]
[0]
否则:
慢速分析 =
获取分析雅可比矩阵(
元组输入,
输出,
输入索引,
输出索引
)
# 假设雅可比矩阵非空且形状相同
慢速最大差异 = (
慢速数值 -
慢速分析).
绝对值().
最大值()
慢速完全一致 =
PyTorch.allclose(
慢分析,
慢数值,
相对误差, atol)
msg = (
"输入文本翻译为简体中文为:\n
上述与数值和解析雅可比相关的量已计算
输入文本翻译为简体中文为:
\n"
在快速模式下。参见:https://github.com/pytorch/pytorch/issues/53876 了解更多背景
输入文本翻译为简体中文为:
\n"
关于快速模式。下面,我们以慢速模式重新计算数值和解析雅可比矩阵:
\n\n
\n\n"
f数值:
输入文本翻译为简体中文为:\n {
慢速数值}
输入文本翻译为简体中文为:\n"
f"分析:
输入文本翻译为简体中文为:\n{
慢速分析}
\n\n
\n\n"
f"最大元素差异(慢速模式)为:{
慢速最大差异}.
输入文本翻译为简体中文为:\n"
)
如果
慢速全部接近:
# 慢速梯度检查本应通过!
msg += 快速失败慢速通过信息
返回 msg
def _to_flat_dense_if_sparse(张量):
如果 _is_sparse_any_tensor(
张量):
返回
张量.
转换为稠密格式().
重塑(-1)
否则:
返回
张量
def 生成向量(
输入张量,
输出, *,
使用前向广告):
使用我们自己的生成器以避免干扰用户的随机数生成器状态
g_cpu = PyTorch.
生成器()
def _vec_from_tensor_cpu(*参数):
# 默认在 CPU 上分配所有张量,因此它们与生成器位于同一设备上
# 即使用户指定了默认设备
替换为
PyTorch.
设备("cpu"):
返回
张量从(*
参数)
所有_u =
输入文本为空,请提供需要翻译的文本
所有_u 密集 =
输入文本为空,请提供需要翻译的文本
for 输入
在
输入张量:
ur = _vec_from_tensor_cpu(输入, g_cpu, True)
ur_dense = _稀疏转扁平稠密(ur)
如果
输入.
是复杂的():
用户界面 = _vec_from_tensor_cpu(
输入, g_cpu, True)
所有_u.
添加((ur, ui))
ui 密集 = _to_flat_dense_if_sparse(
用户界面)
所有用户密集.
添加((
用户密集型,
用户界面密集))
否则:
所有_u.
添加(ur)
全部_u_密集.
添加(
ur_密集)
所有_v = (
无
如果
使用前向传播
否则 [_vec_from_tensor_cpu(
输出, g_cpu) for
外部
在
输出]
)
返回 all_v,
所有_u,
全部密集
def 检查分析数值是否相等(
全部分析,
全部数值,
复杂索引,
元组输入,
输出,
函数,
all_v,
所有_u,
相对误差,
atol,
eps,
测试图像,
*,
前向广告=False,
):
for i, 输入_i 的所有数值
在
列举(
所有数值):
for j, n 在
列举(
输入_i 的所有数值):
前向 AD 生成该函数期望的转置
如果
前向广告:
a = 全分析[i
]
[j]
否则:
a = 全分析[j
]
[i]
n = n.到(
设备=a.
设备)
更新_atol =
调整后的 atol(atol,
所有_u[i
] all_v[j]
如果
所有_v
否则
无)
如果
不 _allclose_with_type_promotion(a, n.
到(a.
设备),
相对误差,
更新后的 atol):
雅可比矩阵字符串 =
运行慢模式并获取错误(
函数,
元组输入,
输出, i, j,
相对误差, atol, eps,
是前向广告
)
抛出
梯度检查错误(
获取 notallclose 消息(
a, n, j, i, 复杂索引,
测试图像,
是前向广告
)
+ jacobian_str
)
def _快速梯度检查(
函数,
func_out,
输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
非确定性容差,
*,
使用前向广告=False,
复杂索引=
无,
测试图像=False,
遮蔽=False,
):
# 详细信息请见 https://github.com/pytorch/pytorch/issues/53876
输入张量索引,
输入张量 =
_获取输入张量(
输入)
# 反向模式计算 v^T * J (VJP)
# 由于我们通过有限差分法计算了 J * u (JVP),因此执行等式检查
# 介于 VJP * u, v * JVP 之间
# ----
# 前向模式计算 J * u (JVP)
# 由于我们已经通过有限差分法计算了 JVP,
# 因此在此处不需要 v 进行正确性检查,如下所述
all_v, 所有_u,
全部_u_密集 =
_制作向量(
输入张量,
输出,
使用前向广告=
使用前向传播
)
输入数值,
所有_u 都是数字,
所有_v 都是数字 = (
(输入,
所有_u, all_v)
如果
隐藏
否则
稠密化((
输入,
所有_u, all_v))
)
numerical_vJu = 获取数值_vJu(
函数,
输入都是数字,
输入张量索引,
func_out,
所有 u 数值,
所有 v 数值,
eps,
是前向广告=
使用前向广告,
)
# TODO: 将 https://github.com/pytorch/pytorch/pull/77743 的内容也复制用于快速梯度检查
如果
使用前向广告:
断言
所有_v
是
无
分析_vJu =
获取前向分析雅可比(
函数,
输入,
_as_tuple(func_out),
所有_u=
所有_u,
检查梯度数据类型=
检查梯度数据类型,
)
否则:
如果
不
输出:
检查无导数输出快速(
函数, func_out,
输入,
输入张量索引,
所有_u, eps,
非确定性容差
)
分析_vJu =
_获取分析_vJu 反向模式(
输入,
输出,
非确定性容差,
检查梯度数据类型, all_v,
全部密集
)
检查分析数值是否相等(
分析_vJu,
numerical_vJu,
复杂索引,
输入,
输出,
函数,
all_v,
所有_u,
相对误差,
atol,
eps,
测试图像,
是前向广告=
使用前向广告,
)
返回
真实
# 注意 [张量变长参数]
# ~~~~~~~~~~~~~~~~~~~~~~~~
# 'func' 接受一个张量变长参数,目前类型系统中无法表达。
# 如果接受 https://mypy.readthedocs.io/en/latest/additional_features.html?highlight=callable#extended-callable-types,
可调用对象的第一个参数可以是 VarArg(Tensor)。
目前,我们允许任何输入。
[文档]def gradcheck(
函数:
可调用[...,
联合[
_Tensor 或 Tensors]],
# 参见注释[张量的可变参数]
输入:
_Tensor 或 Tensors,
*,
eps: 浮点数 =
0.000001,
atol: 浮点数 =
0.00001,
相对误差:
浮点数 =
0.001,
抛出异常:
布尔值 = True,
非确定性容差:
浮点数 = 0.0,
检查未定义的梯度:
布尔值 = True,
检查梯度数据类型:
布尔值 = False,
检查批梯度:
布尔值 = False,
检查批前向梯度:
布尔值 = False,
check_forward_ad: 布尔值 = False,
check_backward_ad: 布尔值 = True,
快速模式:
布尔值 = False,
遮蔽:
可选[bool] =
无,
) -> bool: # 无需注意:D400,D205
r检查通过小有限差分计算出的梯度与解析梯度的一致性
输入与`inputs`相关的张量梯度,这些张量是浮点型或复数类型
并且使用 `requires_grad=True`。
数值与解析梯度的检查使用::func:`~torch.allclose`。
对于大多数我们用于优化的复杂函数,没有概念
雅可比存在。相反,gradcheck 验证数值和解析值
Wirtinger 和共轭 Wirtinger 导数是一致的。因为梯度
计算是在假设整体函数具有实数值的情况下进行的
输出,我们对具有复杂输出的函数采取特殊处理方式。
gradcheck 应用于对应取实部的两个实值函数
复杂输出的第一部分组件,以及取虚部
第二个的复杂输出。更多详情,请查看
ref:`复杂自动微分文档`
.. 注意::
默认值是为双精度 :attr:`输入` 设计的。
如果 :attr:`输入` 的精度较低,此检查可能会失败,例如
``FloatTensor``
.. 注意::
在非可微点进行评估时,Gradcheck 可能会失败
因为通过有限差分法计算出的数值梯度可能与通过解析计算出的梯度不同
(不一定是因为任一计算是错误的)。
更多上下文,请参阅::ref:`非可微函数的梯度`.
..警告::
如果:attr:`input`中任何检查的张量有重叠的内存,即
不同索引指向相同的内存地址(例如,来自
func:`torch.Tensor.expand`),则此检查可能会失败,因为数值
在这样的索引处通过点扰动计算的梯度将改变
将改变所有其他共享相同内存地址的索引的值。
参数:
func(函数):一个 Python 函数,它接受张量输入并返回
张量或张量元组
输入(张量元组或张量):函数的输入
eps(float,可选):有限差分的扰动
atol(float,可选):绝对容差
rtol(float,可选):相对容差
raise_exception(bool,可选):指示是否在发生错误时引发异常
检查失败。异常提供了更多信息。
故障的确切性质。这在调试 gradchecks 时很有帮助。
非确定性容忍度(float,可选):非确定性的容忍度。当运行
通过区分相同输入,结果必须匹配
精确地(默认,0.0)或在此公差范围内。
检查未定义的梯度(布尔值,可选):如果 ``True``,检查未定义的输出梯度
支持并作为零处理,对于“Tensor”输出。
check_batched_grad (布尔值,可选): 如果为 ``True``,检查我们是否可以计算
使用原型 vmap 支持进行批处理梯度。默认为 False。
check_batched_forward_grad (bool, 可选): 如果为 ``True``,则检查我们是否可以计算
使用前向 ad 和原型 vmap 支持进行批处理前向梯度。默认为 ``False``。
check_forward_ad (bool, 可选): 如果为 ``True``,则检查使用前向
模式 AD 匹配数值模式。默认为 ``False``。
check_backward_ad (布尔值,可选):如果为 ``False``,则不执行任何依赖反向模式的检查
反向模式 AD 将要实现。默认为 ``True``。
fast_mode (布尔值,可选):目前 gradcheck 和 gradgradcheck 的快速模式仅
针对 R 到 R 函数实现。如果输入和输出都不是复杂数据
gradcheck 的更快实现,不再计算整个雅可比矩阵
被执行;否则,我们将回退到慢速实现。
掩码(bool,可选):如果为 True,则计算未指定元素的梯度
稀疏张量将被忽略。默认为 ``False``。
返回值:
如果所有差异都满足 allclose 条件则为 ``True``。
"""
断言 (
检查前向自动微分
或者
检查反向自动微分
), "至少期望 check_forward_ad 或 check_backward_ad 中有一个为 True"
断言
不 (
检查批梯度
和
不
检查反向求导
), "设置 check_batched_grad=True 需要 check_backward_ad 为 True"
断言
不 (
检查批处理前向梯度
和
不
检查前向导数
), "设置检查批处理前向梯度为 True 需要检查前向导数为 True"
args = locals().复制()
参数.
流行(
抛出异常)
如果
不
抛出异常:
尝试:
返回 _gradcheck_helper(**
参数)
除了
毕业审核错误:
返回
假
否则:
返回 _gradcheck_helper(**
参数)
def _gradcheck_helper(
函数,
输入,
eps,
atol,
相对误差,
非确定性容差,
检查未定义的梯度,
检查梯度数据类型,
检查批梯度,
检查批前向梯度,
check_forward_ad,
check_backward_ad,
快速模式,
遮蔽,
):
元组输入 = _as_tuple(
输入)
_检查输入(
元组输入)
函数输出 =
函数(*
元组输入)
输出 =
可微输出(func_out)
检查输出(
输出)
梯度检查函数 = functools.
偏函数(
_快速梯度检查
如果
快速模式
否则
_慢速梯度检查_,
遮蔽=
隐藏
)
_gradcheck_real_imag(
gradcheck_fn,
函数,
func_out,
元组输入,
输出,
eps,
相对误差,
atol,
检查梯度数据类型,
check_forward_ad=check_forward_ad,
check_backward_ad=check_backward_ad,
非确定性容差=
非确定性容差,
检查未定义的梯度=
检查未定义的梯度,
)
如果
检查批处理前向梯度:
_测试批量梯度前向(
函数,
元组输入)
# 短路,因为剩余的测试依赖于尚未实现的反向自动微分
如果
不 check_backward_ad:
返回
真实
for i, o 在
列举(
输出):
如果
检查批处理梯度:
_测试批量梯度(
元组输入, o, i)
_测试反向乘以 grad_output(
输出,
元组输入,
遮蔽)
如果
检查未定义的梯度
和 check_backward_ad:
_测试_未定义反向模式(
函数,
输出,
元组输入)
返回
真实
[文档]def gradgradcheck(
函数:
可调用[...,
_Tensor 或 Tensors
]
#参见笔记[张量 VarArg]
输入:
_Tensor 或 Tensors,
grad_outputs: 可选[
_Tensor 或 Tensors] =
无,
*,
eps: 浮点数 =
0.000001,
atol: 浮点数 =
0.00001,
相对误差:
浮点数 =
0.001,
生成非连续梯度输出:
布尔值 = False,
抛出异常:
布尔值 = True,
非确定性容差:
浮点数 = 0.0,
检查未定义的梯度:
布尔值 = True,
检查梯度数据类型:
布尔值 = False,
检查批处理梯度:
布尔值 = False,
检查正向对反向:
布尔值 = False,
检查反向对反向:
布尔值 = True,
快速模式:
布尔值 = False,
遮蔽:
布尔值 = False,
) -> bool: # noqa: D400,D205
r检查通过小有限差分计算出的梯度梯度的梯度
与相对于 :attr:`inputs` 和 :attr:`grad_outputs` 的解析梯度进行比较
这些梯度或梯度输出为浮点型或复数类型
``requires_grad=True``.
此函数检查通过计算得到的梯度反向传播到给定的 :attr:`grad_outputs` 是否正确。
该检查使用 :func:`~torch.allclose` 在数值梯度和解析梯度之间进行。
使用 :func:`~torch.allclose` 检查数值梯度和解析梯度是否一致。
.. 注意::
默认值是为:attr:`input`设计的
attr:`grad_outputs`为双精度。如果它们的精度更低,例如``FloatTensor``,则此检查可能会失败。
例如。如果:attr:`input`和:attr:`grad_outputs`中的任何检查张量
..警告::
在:attr:`input`和:attr:`grad_outputs`中,如果任何检查的张量
重叠内存,即不同的索引指向同一内存
地址(例如,来自:func:`torch.Tensor.expand`),此检查可能会失败
因为在该点扰动下计算的数值梯度
索引将在所有共享相同索引的其他索引处更改值
内存地址。
参数:
func(函数):一个 Python 函数,它接受张量输入并返回
张量或张量的元组。
输入(张量或张量元组):函数的输入。
grad_outputs(可选,张量或张量元组):梯度。
对函数输出的尊重。
eps(浮点数,可选):有限差分的扰动。
atol(浮点数,可选):绝对容差。
rtol(浮点数,可选):相对容差。
gen_non_contig_grad_outputs (bool, 可选): 如果 :attr:`grad_outputs` 是 ``None`` 且 :attr:`gen_non_contig_grad_outputs` 为 ``True``,则随机生成的梯度输出将被设置为非连续的
``None`` 和 :attr:`gen_non_contig_grad_outputs` 为 ``True`` 时,
随机生成的梯度输出将被设置为非连续的
raise_exception (bool, 可选): 指示是否在发生异常时抛出异常
检查失败。异常提供了更多信息。
故障的确切性质。这在调试 gradchecks 时很有帮助。
非确定性容忍度(float,可选):非确定性的容忍度。当运行
通过区分相同输入,结果必须匹配
精确(默认,0.0)或在此公差范围内。注意,梯度中存在的小量非确定性会导致二阶导数的不准确度更大。
的非确定性会导致二阶导数的不准确度更大。
的。
check_undefined_grad(布尔值,可选):如果为 True,检查未定义的输出梯度
支持并被视为零
check_batched_grad (布尔值,可选):如果为 True,检查是否可以使用原型 vmap 支持。默认为 False。
batched gradients 使用原型 vmap 支持进行计算。默认为 False。
fast_mode (布尔值,可选):如果为 True,运行 gradgradcheck 的更快实现。
不再计算整个雅可比矩阵。
遮蔽(bool,可选):如果为 True,则忽略稀疏张量的未指定元素(默认为 False)。
稀疏张量默认忽略(False)。
返回值:
如果所有差异都满足 allclose 条件则为 True。
"""
断言 (
检查正向是否超过反向
或者
检查反向是否超过反向
), 预期至少有一个 check_fwd_over_rev 或 check_rev_over_rev 为 True
断言
不 (
检查未定义的梯度
和
不
检查 rev_over_rev
), 设置 check_undefined_grad=True 需要 check_rev_over_rev 为 True
断言
不 (
检查批梯度
和
不
检查 rev_over_rev
), "设置 check_batched_grad=True 需要 check_rev_over_rev 为 True"
# TODO: 我们也要测试这个吗?
# assert not (check_batched_forward_grad and not check_fwd_over_rev), (
# "设置 check_batched_forward_grad=True 需要 check_fwd_over_rev 为 True")
元组输入 = _as_tuple(
输入)
如果 grad_outputs
是
无:
如果未指定 grad_outputs,则创建与输出相同形状、类型和设备的随机张量
输出 =
可微输出(
函数(*
元组输入))
元组 grad_outputs =
元组(
PyTorch.
测试.
创建张量(
x.形状,
数据类型=x.dtype
如果 x.is_floating_point()
或者 x.
是复杂的()
否则
PyTorch.double,
设备=x.
设备,
低=-1,
高=1,
需要梯度=True,
不连续的=
非连续梯度输出,
)
for x 在
输出
)
否则:
梯度输出元组 = _as_tuple(grad_outputs)
输出数量 =
长度(
元组梯度输出)
# 注意:我们需要在这里保存输入的 requires_grad 信息,因为 gradcheck 在运行前向模式 AD 之前会断开输入
# 在运行前向模式 AD 之前
差分输入参数索引 = {
i for i, x 在
列举(
元组输入)
如果
是否与张量类似(x)
和 x.
需要梯度
}
差分梯度输出索引 = {
i for i, x 在
列举(
元组形式的梯度输出)
如果 x.
需要梯度
}
def 新函数(*
参数):
恢复 requires_grad 信息
输入参数 =
元组(
x.需要梯度_()
如果 i
在 diff_input_args_indices
否则 x
for i, x 在
列举(
参数
[-
输出数量])
)
输出 =
可微输出(
函数(*
输入参数))
grad_outputs = 元组(
x.需要梯度_()
如果 i
在
差分梯度输出索引
否则 x
for i, x 在
列举(
参数[-
输出数量
])
)
差异输入参数 =
元组(
x for i, x 在
列举(
输入参数)
如果 i
在
差异输入参数索引
)
梯度输入 =
PyTorch.
自动微分.
研究生(
输出,
差异输入参数, grad_outputs,
创建图=True,
允许未使用=
真实
)
梯度输入 =
元组(g for g
在
梯度输入
如果 g
是
不
无)
返回
梯度输入
返回 gradcheck(
新功能,
元组输入 +
元组梯度输出,
eps=eps,
atol=atol,
相对误差=
相对误差,
抛出异常=
抛出异常,
非确定性容差=
非确定性容差,
检查未定义的梯度=
检查未定义的梯度,
检查梯度数据类型=
检查梯度数据类型,
检查批梯度=
检查批梯度,
快速模式=
快速模式,
check_forward_ad=检查前向对反向,
check_backward_ad=检查反向对反向,
遮蔽=
遮蔽,
)