快捷键

torch.autograd.functional 的源代码

# mypy: 允许未类型化定义

导入 火炬
来自 torch._vmap_internals 导入 _vmap

来自 . 导入 前进广告  前 AD


全部 = [vjp, jvp, "雅可比安", 赫斯矩阵, hvp, "vhp"]

# 实用函数


def _as_tuple_nocheck(x):
    if isinstance(x, 元组):
        返回 x
    如果...否则 isinstance(x, 列表):
        返回 元组(x)
    否则:
        返回 (x,)


def _as_tuple(输入, 参数名称=, 函数名=):
    确保 inp 是一个元组类型的张量
    返回原始 inp 是否为元组以及输入的元组版本
    if 变量名    函数名  :
        返回 _as_tuple_nocheck(输入)

    是输入元组 = 真实
    if 不是 isinstance(输入, 元组):
        输入 = (输入,)
        是输入元组 = 

    for i, el  列举(输入):
        if 不是 isinstance(el, 火炬.张量):
            if 是输入元组:
                抛出 类型错误(
                    f"The {参数名称}被赋予{函数名}必须是 Tensor 或者 Tensor 的元组,但必须是"
                    f"在索引处的值{i}类型{类型(el)}
                )
            否则:
                抛出 类型错误(
                    f"The {参数名称}被赋予{函数名}必须是 Tensor 或者 Tensor 的元组,但必须是"
                    f给定{参数名称}类型{类型(el)}
                )

    返回 是输入元组, 输入


def _元组后处理(资源, 解包):
    # 解包一个可能嵌套的 Tensor 元组
    # to_unpack 应该是一个布尔值或两个布尔值的元组。
    用于:
    - 当 res 应匹配给定的_invert_as_tuple 的 inp 时反转_as_tuple
    - 可选地移除由多次调用_as_tuple 创建的两个元组的嵌套
    if isinstance(解包, 元组):
        断言 长度(解包) == 2
        if 不是 解包[1]
            res = 元组(el[0] for el  资源)
        if 不是 解包[0]
            res = 资源[0]
    否则:
        if 不是 解包:
            res = 资源[0]
    返回 res


def _grad_preprocess(输入, 创建图, 需要图形):
    # 预处理输入以确保它们需要梯度
    # 输入是一个需要预处理的张量元组
    # create_graph 指定用户是否希望梯度反向传播到输入的 Tensors
    # need_graph 指定我们是否内部希望梯度反向传播到 res 中的 Tensors
    # 注意,我们始终创建一个新的 Tensor 对象,以便能够区分
    # 作为参数给出的输入 Tensors 和用户函数自动捕获的相同 Tensors 之间的差异。
    # 请查看此问题以获取更多关于如何发生的详细信息:https://github.com/pytorch/pytorch/issues/32576
    res = 输入文本为空,请提供需要翻译的文本
    for 输入  输入:
        if 创建图  输入.需要梯度:
            # 至少以可微分的方式创建一个新的 Tensor 对象
            if 不是 输入.is_sparse:
                # 使用 .view_as() 获取浅拷贝
                资源.添加(输入.以查看方式(输入))
            否则:
                对于稀疏张量,我们不能使用查看,因此需要克隆
                资源.添加(输入.克隆())
        否则:
            资源.添加(输入.detach().需要梯度_(需要图形))
    返回 元组(资源)


def _grad 后处理(输入, 创建图):
    避免返回用户未请求的带有历史的张量时,对生成的张量进行后处理。
    
    if isinstance(输入[0] 火炬.张量):
        if 不是 创建图:
            返回 元组(输入.detach() for 输入  输入)
        否则:
            返回 输入
    否则:
        返回 元组(_grad 后处理(输入, 创建图) for 输入  输入)


def _验证_v(v, 其他, 是其他元组):
    # 这假设其他是正确的形状,并且 v 应该匹配
    # 两个都是假设为张量元组的
    if 长度(其他) != 长度(v):
        if 是其他元组:
            抛出 运行时错误(
                f"v 是一个无效长度的元组:应该是"{长度(其他)}但是得到了{长度(v)}
            )
        否则:
            抛出 运行时错误("给定的 v 应包含一个张量。")

    for 索引, (el_v, el_other)  列举(zip(v, 其他)):
        如果 el_v.尺寸() != el_other.尺寸():
            预先添加 = 请提供需要翻译的文本
            如果 是其他元组:
                预先添加 = f"条目"{索引}在 "
            抛出 运行时错误(
                f"{预先添加}v 的尺寸无效:应为{el_other.尺寸()}但是得到了{el_v.尺寸()}
            )


def _check_requires_grad(输入, 输入类型, 严格的):
    用于在严格模式下执行所有必要的检查以引发友好的错误。
    如果  严格的:
        返回

    如果 输入类型   [输出, "grad_inputs", "雅可比安", 赫斯矩阵]
        抛出 运行时错误("Invalid input_type to _check_requires_grad")
    for i, 输入  列举(输入):
        如果 输入  :
            # 这仅适用于 grad_inputs。
            抛出 运行时错误(
                f用户提供的函数的输出与输入无关{i}
                "在严格模式下不允许这样做。"
            )
        如果  输入.需要梯度:
            如果 输入类型 == 赫斯矩阵:
                抛出 运行时错误(
                    f用户提供的函数关于输入的 Hessian{i}"
                    该语句独立于输入。在严格模式下不允许这样做。
                    您应该确保您的函数是三阶可微的,并且
                    "海森矩阵依赖于输入。"
                )
            如果...否则 输入类型 == "雅可比安":
                抛出 运行时错误(
                    在计算 Hessian 矩阵时,发现用户提供的雅可比矩阵的
                    f"的导数"{i}与输入无关。这并不
                    "在严格模式下允许。你应该确保你的函数是可微分的,并且雅可比矩阵依赖于输入(例如,这会被线性函数违反)。"
                    "可微分的,并且雅可比矩阵依赖于输入(这会由线性函数违反)。"
                    "违反,例如线性函数。)"
                )
            如果...否则 输入类型 == "grad_inputs":
                抛出 运行时错误(
                    f"关于输入的梯度"{i}与用户提供的函数的输入无关。在严格模式下不允许这样做。
                    "This is not allowed in strict mode."
                )
            否则:
                抛出 运行时错误(
                    f"输出"{i}用户提供的函数的输出不需要梯度。
                    输出必须在严格模式下以可微分的方式从输入计算得出。
                    当以严格模式运行时。
                )


def _自动微分梯度(
    输出,
    输入,
    梯度输出=,
    创建图=错误,
    retain_graph=,
    是否批处理梯度=错误,
):
    # autograd.grad 的版本,接受 `None` 作为输出,并且不对这些输出计算梯度。
    # 这个版本有额外的约束,即输入必须是一个元组。
    断言 isinstance(输出, 元组)
    如果 梯度输出  :
        梯度输出 = (,) * 长度(输出)
    断言 isinstance(梯度输出, 元组)
    断言 长度(输出) == 长度(梯度输出)

    新输出: 元组[火炬.张量, ...] = ()
    新梯度输出: 元组[火炬.张量, ...] = ()
    for 输出, 梯度输出_缩写  zip(输出, 梯度输出):
        if 外部  不是   输出.需要梯度:
            新输出 += (输出,)
            新的梯度输出 += (毕业输出,)

    if 长度(新输出) == 0:
        # 不可微分输出,我们不需要调用自动微分引擎
        返回 (,) * 长度(输入)
    否则:
        返回 火炬.自动微分.研究生(
            新输出,
            输入,
            新梯度输出,
            允许未使用=,
            创建图=创建图,
            retain_graph=retain_graph,
            是否批处理梯度=是否批处理梯度,
        )


def 填充零(梯度, 引用, 严格, 创建图, 阶段):
    # 用于检测 grads 中的 None,并根据标志,用适当大小的 0 填充 Tensors 或引发错误。
    # 严格和创建图可以帮助我们检测何时应该引发错误。
    # strict 和 create graph 允许我们检测何时应该引发错误。
    # 阶段信息告诉我们考虑哪个回溯调用以给出良好的错误信息
    if 阶段 不是  [返回, "后空翻", "双后向", "双倍后退技巧"]
        抛出 运行时错误(f"无效的阶段参数"{阶段}填充零)

    资源: 元组[火炬.张量, ...] = ()
    for i, grads_i  列举(梯度):
        if grads_i  :
            if 严格:
                if 阶段 == 返回:
                    抛出 运行时错误(
                        "用户提供的函数的输出独立于"
                        f"输入无关"{i}. 在严格模式下不允许这样做。
                    )
                如果...否则 阶段 == "后空翻":
                    抛出 运行时错误(
                        f"关于输入的梯度在使用双反向技巧计算 grad_outputs 时与输入无关"{i}"
                        "在应用双反向技巧计算时,梯度与 grad_outputs 中的输入无关"
                        "前向模式梯度。在严格模式下不允许。"
                    )
                如果...否则 阶段 == "双后向":
                    抛出 运行时错误(
                        "用户提供的函数的雅可比矩阵与 "
                        f"输入无关"{i}. 在严格模式下不允许这样做。
                    )
                否则:
                    抛出 运行时错误(
                        "用户提供的函数的海森矩阵与 "
                        f条目{i}在 grad_jacobian 中。在严格的" "
                        "模式下,这会阻止使用双反向技巧来"
                        替换前向模式的 AD。"
                    )

            grads_i = 火炬.等于零的(引用[i])
        否则:
            if 严格的  创建图  不是 grads_i.需要梯度:
                if 双精度 不是  阶段:
                    抛出 运行时错误(
                        "用户提供的函数的雅可比矩阵与 "
                        f"输入无关"{i}。当 create_graph=True 时,在严格模式下不允许这样做。
                    )
                否则:
                    抛出 运行时错误(
                        "用户提供的函数的海森矩阵与 "
                        f"输入无关"{i}。当 create_graph=True 时,在严格模式下不允许这样做。
                    )

        res += (grads_i,)

    返回 res


公共 API


[文档]def vjp(函数, 输入, v=, 创建图=错误, 严格=错误): r计算向量 ``v`` 与给定函数在输入点处的雅可比矩阵的点积。 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一组张量或一个张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 v(Tensors 或 Tensor 的元组):计算雅可比积的向量。必须与`func`的输出大小相同。此参数在`func`的输出为可选时。 雅可比积是计算的对象。必须与`func`的输出大小相同。 此参数在`func`的输出为可选时。 ``func``。当 func 的输入包含单个元素时,此参数是可选的(如果未提供,则将设置为 作为包含单个“1”的张量。 create_graph(布尔值,可选):如果为 True,输出和结果将以可微分的方式进行计算。注意,当 strict 为 False 时,结果可能不需要梯度或为 将以可微分的方式进行计算。注意,当 `strict` 为 `False` 时, 结果不需要梯度或可以, 与输入断开连接。默认为 ``False``。 strict(布尔值,可选):如果为 ``True``,当我们 检测到存在一个输入,其所有输出都与它无关时,将引发错误。 如果为 ``False``,我们将返回一个全零的 Tensor。 对应输入的 vjp,即预期的数学值。 默认为 ``False``。 返回: 输出(元组):包含元组的: func_output (Tensors 或 Tensor 的元组): `func(inputs)` 的输出 vjp (张量元组或张量):与输入具有相同形状的点积结果 与输入具有相同的形状。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> 定义函数 exp_reducer(x): ... 返回 x.exp().sum(dim=1) >>> inputs = torch.rand(4, 4) >>> v = torch.ones(4) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> vjp(exp_reducer, inputs, v) (tensor([5.7817, 7.2458, 5.7830, 6.7782]),) tensor([[1.4458, 1.3962, 1.3042, 1.6354], [2.1288, 1.0652, 1.5483, 2.5035] [2.2046, 1.1292, 1.1432, 1.3059] [1.3225, 1.6652, 1.7753, 2.0152]])) >>> vjp(exp_reducer, inputs, v, create_graph=True) (tensor([5.7817, 7.2458, 5.7830, 6.7782], grad_fn=<SumBackward1>), tensor([[1.4458, 1.3962, 1.3042, 1.6354], [2.1288, 1.0652, 1.5483, 2.5035] [2.2046, 1.1292, 1.1432, 1.3059] [1.3225, 1.6652, 1.7753, 2.0152]], grad_fn=<MulBackward0>)) >>> def adder(x, y): ...返回 2 * x + 3 * y >>> inputs = (torch.rand(2), torch.rand(2)) >>> v = torch.ones(2) >>> vjp(adder, inputs, v) (tensor([2.4225, 2.3340]),) (tensor([2., 2.]), tensor([3., 3.]))) "沉浸式翻译" 火炬.启用梯度(): is_inputs_tuple, 输入 = _as_tuple(输入, 输入, vjp) 输入 = _grad_preprocess(输入, 创建图=创建图, 需要图形=) 输出 = 函数(*输入) 输出是否为元组, 输出 = _as_tuple( 输出, 用户提供的函数的输出, 计算图(vjp) ) _check_requires_grad(输出, 输出, 严格=严格) if v 不是 : _, v = _as_tuple(v, "v", vjp) v = _grad_preprocess(v, 创建图=创建图, 需要图形=错误) _验证_v(v, 输出, 输出是否为元组) 否则: if 长度(输出) != 1 或者 输出[0].元素个数() != 1: 抛出 运行时错误( "向量 v 只能在用户提供的函数返回 None 时" "用户提供的函数返回 " "一个只有一个元素的单一张量。" ) enable_grad = 真实 if 创建图 否则 火炬.梯度是否启用() 火炬.设置梯度启用(启用梯度): 梯度结果 = _自动微分梯度(输出, 输入, v, 创建图=创建图) 反向传播 = 填充零(梯度结果, 输入, 严格, 创建图, 返回) 清理对象并返回给用户 输出 = _grad 后处理(输出, 创建图) 反向传播 = _grad 后处理(vjp, 创建图) 返回 _元组后处理(输出, 输出是否为元组), _元组后处理( vjp, 输入是否为元组 )
[文档]def JVP(雅可比向量积)(函数, 输入, v=, 创建图=False, 严格的=False): r计算给定函数在输入点处的雅可比矩阵与向量 ``v`` 的点积。 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一组张量或一个张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 v(张量或张量元组):计算雅可比乘积的向量。必须与 func 的输入大小相同。 vector product is computed. Must be the same size as the input of ``func``. This argument is optional when the input to ``func`` contains a single element and (if it is not provided) will be set ``func``。当 func 的输入包含单个元素时,此参数是可选的(如果未提供,则将设置为 ``func``。当 func 的输入包含单个元素时,此参数是可选的(如果未提供,则将设置为 作为包含单个“1”的张量。 create_graph(布尔值,可选):如果为 True,输出和结果将以可微分的方式进行计算。注意,当 strict 为 False 时,结果可能不需要梯度或为 将以可微分的方式进行计算。注意,当 `strict` 为 `False` 时, 结果不需要梯度或可以, 与输入断开连接。默认为 ``False``。 strict(布尔值,可选):如果为 ``True``,当我们 检测到存在一个输入,其所有输出都与它无关时,将引发错误。 如果为 ``False``,我们将返回一个全零的 Tensor。 jvp 对于给定的输入,这是预期的数学值。 默认为 ``False``。 返回: 输出(元组):包含元组的: func_output (Tensors 或 Tensor 的元组): `func(inputs)` 的输出 jvp (Tensors 或 Tensor 的元组): 与输出形状相同的点积结果 的形状相同。 注意: ``autograd.functional.jvp`` 通过使用反向操作来计算 jvp。 反向操作(有时称为双重反向技巧)。这并不是 计算 JVP 的最高效方式。请考虑使用 `torch.func.jvp` 或是 请使用低级前向模式 AD API 。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> 定义函数 exp_reducer(x): ... 返回 x.exp().sum(dim=1) >>> inputs = torch.rand(4, 4) >>> v = torch.ones(4, 4) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> jvp(exp_reducer, inputs, v) (tensor([6.3090, 4.6742, 7.9114, 8.2106]), tensor([6.3090, 4.6742, 7.9114, 8.2106])) >>> jvp(exp_reducer, inputs, v, create_graph=True) (tensor([6.3090, 4.6742, 7.9114, 8.2106], grad_fn=<SumBackward1>), tensor([6.3090, 4.6742, 7.9114, 8.2106], grad_fn=<SqueezeBackward1>)) >>> def adder(x, y): ...返回 2 * x + 3 * y >>> inputs = (torch.rand(2), torch.rand(2)) >>> v = (torch.ones(2), torch.ones(2)) >>> jvp(adder, 输入, v) (张量([2.2399, 2.5005]), 张量([5., 5.])) """ 替换为 火炬.启用梯度(): 是否输入为元组, 输入 = _as_tuple(输入, 输入, jvp) 输入 = _grad_preprocess(输入, 创建图=创建图, 需要图形=True) 如果 v 不是 : _, v = _as_tuple(v, "v", jvp) v = _grad_preprocess(v, 创建图=创建图, 需要图形=错误) _验证_v(v, 输入, 是否输入为元组) 否则: 如果 长度(输入) != 1 或者 输入[0].元素个数() != 1: 抛出 运行时错误( "向量 v 只有在输入为空时才能是 None" "用户提供的函数是一个单独的张量" "且只有一个元素。" ) 输出 = 函数(*输入) 输出是否为元组, 输出 = _as_tuple( 输出, 用户提供的函数的输出, "jvp" ) _check_requires_grad(输出, 输出, 严格的=严格的) # 向后传播是线性的,所以 grad_outputs 的值并不重要, # 它不会出现在反向传播图中。我们只需要确保 # 它不包含无穷大或非数字。 梯度输出 = 元组( PyTorch.等于零的(输出, 需要梯度=True) for 外部 输出 ) 梯度输入 = _自动微分梯度(输出, 输入, 梯度输出, 创建图=True) _check_requires_grad(梯度输入, "grad_inputs", 严格的=严格的) 如果 创建图: PyTorch.启用梯度(): 梯度结果 = _自动微分梯度( 梯度输入, grad_outputs, v, 创建图=创建图 ) jvp = 填充零(梯度结果, 输出, 严格的, 创建图, "后空翻") 否则: 梯度结果 = _自动微分梯度( 梯度输入, grad_outputs, v, 创建图=创建图 ) jvp = 填充零(梯度结果, 输出, 严格, 创建图, "后空翻") 清理对象并返回给用户 输出 = _grad 后处理(输出, 创建图) jvp = _grad 后处理(JVP(雅可比向量积), 创建图) 返回 _元组后处理(输出, 输出是否为元组), _元组后处理( JVP(雅可比向量积), 输出是否为元组 )
def _construct_standard_basis_for( 张量: 元组[火炬.张量, ...] 张量元素数: 元组[int, ...] ) -> 元组[火炬.张量, ...] # 这个函数: # - 构建一个 N=sum(tensor_numels)的标准基,即一个 NxN 的单位矩阵。 # - 将单位矩阵分割成块,每个块的大小由`tensor_numels`决定。 # - 每个块对应一个张量。块具有相同的 dtype 和 设备作为张量 # 例如,当 tensor_numels = [1, 2, 1] 时,此函数返回: (张量([[1], 张量([[0, 0], 张量([[0], [0], [1, 0], [0], # [0], [0, 1], [0], # [0]]) , [0, 0]]) , [1]]) ) # # 预设条件:tensor_numels 等于 tensors 中每个张量的 numel() 的元组 # 预设条件:tensors 必须至少有一个元素。 # # 注意:[使用 vmap 和 grad 计算多个张量的雅可比矩阵] # 关于此函数的上下文。所有预条件都得到了保护 # 在 torch.autograd.functional.jacobian 中 断言 长度(张量) == 长度(张量元素数) 断言 长度(张量) > 0 总元素数 = 总和(张量元素数) 数据块 = 元组( 张量.新零(总元素数, tensor_numel) for 张量, tensor_numel zip(张量, 张量元素数) ) 对角线起始索引 = 0 for 数据块, numel zip(数据块们, 张量元素数): 数据块.对角线(对角线起始索引).填充_(1) 对角线起始索引 -= numel 返回 数据块 def _jacfwd(函数, 输入, 严格=, 向量化=): if 严格: 抛出 运行时错误( torch.autograd.functional.jacobian: `strict=True` 不支持与 `vectorized=True` 同时使用。 '并且 `strategy="forward-mode"` 还不支持同时使用(目前还不支持)。' "请设置 `strict=False` 或者 " '`strategy="reverse-mode"`。' ) 是否输入为元组, 输入 = _as_tuple(输入, 输入, "雅可比安") 输出信息 = 输入文本为空,请提供需要翻译的文本 if 向量化: # 计算使用 vmap 和 grad 的雅可比矩阵(针对多个输出) 输入元素数量 = 元组(输入.元素数量() for 输入 输入) # 第 1 步:准备切线 切线 = _construct_standard_basis_for(输入, 输入_numels) # 第 2 步:计算与双张量相关的 vmap def JVP(雅可比向量积)(切线): fwAD.双级(): 双输入 = 元组( fwAD.使双重(输入, 切线.以查看方式(输入)) for 输入, 切线 zip(输入, 切线) ) _is_outputs_tuple, 双输出 = _as_tuple( 函数(*双输入), 输出 ) 输出信息.添加(_is_outputs_tuple) jv = 输入文本为空,请提供需要翻译的文本 primal_outs = 输入文本为空,请提供需要翻译的文本 for dual_out 双输出: 原始, 切线 = fwAD.解包双倍(双输出) 原始输出.添加(原始) if 切线 不是 : jv.添加(切线) 否则: jv.添加(火炬.等于零的(原始)) 输出信息.添加(原始输出) 返回 元组(jv) 输出前分割 = _vmap(JVP(雅可比向量积))(切线) 输出是否为元组, 输出 = 输出信息 # 第 3 步:对于每个输出切线,沿维度 0 分割 雅可比输入输出 = 输入文本为空,请提供需要翻译的文本 for jac_output_i 输出, 输出_i zip(分割前的输出, 输出): jacobian_output_i_output = 输入文本为空,请提供需要翻译的文本 for jacobian, 输入_j zip(jac_output_i 输出.分割(输入_numels, 维度=0), 输入): 我们需要转置雅可比矩阵,因为在正向自动微分中, 批维度表示输入的维度 雅可比输入_i 输出_j = jacobian.排列(*范围(1, jacobian.维数), 0).重塑( (*输出_i.形状, *输入_j.形状) ) # noqa: C409 jacobian_output_i_output.append(jacobian_input_i_output_j) 雅可比输入输出.添加(jacobian 输出_i 输出) # Omit [Step 4] because everything is already transposed with forward AD 返回 _tuple_postprocess( 雅可比输入输出, (输出是否为元组, 是否输入为元组) ) 否则: 抛出 不支持的操作异常( "使用前向-AD 或前向-反向 Hessian 计算雅可比矩阵仅对`vectorize=True`实现。" "仅对`vectorize=True`实现。" )
[文档]def 贾卡比安( 函数, 输入, 创建图=错误, 严格=错误, 向量化=错误, 策略=反向模式, ): r计算给定函数的雅可比矩阵。 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一组张量或一个张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 创建图(布尔值,可选):如果 ``True``,则雅可比矩阵将被 以可微的方式计算。注意,当 "strict" 是 ``False``,结果不能需要梯度或与输入断开连接 从输入中。默认为 ``False``。 strict(布尔值,可选):如果为 ``True``,当我们 检测到存在一个输入,其所有输出都与它无关时,将引发错误。 如果为 ``False``,我们将返回一个全零的 Tensor。 对于这些输入的雅可比矩阵,这是预期的数学值。 默认为 ``False``。 向量化(bool,可选):此功能为实验性。 请考虑使用 :func:`torch.func.jacrev` 或。 如果您正在寻找更少实验性且性能更优的方案,请使用 :func:`torch.func.jacfwd` 更少实验性且性能更优。 在计算雅可比矩阵时,我们通常对雅可比矩阵的每一行调用一次 ``autograd.grad``。 如果此标志被设置,则 ... “是”,我们只执行一次 `autograd.grad` 调用 使用 `vmap` 原型功能的 `batched_grad=True` 尽管这应该在许多情况下提高性能, 因为这个功能仍然是实验性的,可能会有性能 悬崖。请参阅:func:`torch.autograd.grad`的`batched_grad`参数以获取更多信息。 更多信息。 策略(str,可选):设置为`"forward-mode"`或`"reverse-mode"`以确定雅可比矩阵是使用正向还是反向计算。 来确定雅可比矩阵是使用正向还是反向计算。 模式 AD。当前,“forward-mode”需要“vectorized=True”。 默认为“reverse-mode”。如果“func”的输出多于输入, 则“forward-mode”通常性能更优。否则,建议使用“reverse-mode”。 返回值: 雅可比矩阵(张量或嵌套张量的元组):如果有单个 输入和输出,这将是一个包含单个张量的 Tensor 雅可比矩阵用于线性化输入和输出。如果其中两个之一是 一个元组,那么雅可比矩阵将是一个张量的元组。如果两者都是 它们是元组,那么雅可比矩阵将是一个元组,其元素为元组 张量,其中 `Jacobian[i][j]` 将包含第 `i` 个输出的雅可比矩阵和第 `j` 个输入的雅可比矩阵,其大小为相应输出和输入大小的连接 ``i``\th 输出和 ``j``\th 输入的雅可比矩阵,其大小为相应输出和输入大小的连接 连接相应输出和输入的大小 对应的输入并将具有相同的 dtype 和设备 对应的输入。如果策略是“前向模式”,dtype 将是输出类型;否则,输入类型。 的类型;否则,输入的类型。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> 定义函数 exp_reducer(x): ... 返回 x.exp().sum(dim=1) >>> inputs = torch.rand(2, 2) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> jacobian(exp_reducer, inputs) tensor([[[1.4917, 2.4352], [0.0000, 0.0000]], [[0.0000, 0.0000], [2.4369, 2.3799]]) >>> jacobian(exp_reducer, inputs, create_graph=True) tensor([[[1.4917, 2.4352], [0.0000, 0.0000]], [[0.0000, 0.0000], [[2.4369, 2.3799]], grad_fn=) >>> 定义一个名为 exp_adder 的函数,参数为 x 和 y: ... 返回 2 * x.exp() + 3 * y >>> inputs = (torch.rand(2), torch.rand(2)) >>> jacobian(exp_adder, inputs) (tensor([[2.8052, 0.0000], [0.0000, 3.3963]]), tensor([[3., 0.], [0., 3.]])) """ 断言 策略 ("正向模式", 反向模式), ( 预期策略应为“前进模式”或“反向模式”。提示:如果你的 '函数的输出比输入多时,"正向模式"通常性能更优。' '否则,建议使用"反向模式"。' ) if 策略 == "正向模式": if 创建图: 抛出 不支持的操作异常( "torch.autograd.functional.jacobian: `create_graph=True`" '并且 `strategy="forward-mode"` 还不支持同时使用(目前还不支持)。' "请设置 `create_graph=False` 或者 " '`strategy="reverse-mode"`。' ) 返回 _jacfwd(函数, 输入, 严格, 向量化) 火炬.启用梯度(): 是否输入为元组, 输入 = _as_tuple(输入, 输入, "雅可比安") 输入 = _grad_preprocess(输入, 创建图=创建图, 需要图形=) 输出 = 函数(*输入) 输出是否为元组, 输出 = _as_tuple( 输出, 用户提供的函数的输出, "雅可比矩阵" ) _check_requires_grad(输出, 输出, 严格=严格) if 向量化: if 严格: 抛出 运行时错误( torch.autograd.functional.jacobian: `strict=True` 不支持与 `vectorized=True` 同时使用。 和 `vectorized=True` 不支持同时使用。 "请设置 `strict=False` 或者 " " `vectorize=False` 。" ) # NOTE: [使用 vmap 和 grad 计算多个输出的雅可比矩阵] # 考虑函数 f(x) = (x**2, x.sum()),其中 x = torch.randn(3)。 结果表明,我们可以通过使用 vmap 对正确的 grad_outputs 进行调用,通过一次 autograd.grad 调用来计算这个函数的雅可比矩阵。 首先,计算雅可比矩阵的一种方法是堆叠 x**2 和 x.sum()。 # 首先,计算雅可比矩阵的一种方法是堆叠 x**2 和 x.sum()。 将其转换为 4D 向量。例如,使用 g(x) = torch.stack([x**2, x.sum()]) # 要获取雅可比矩阵的第一行,我们调用 >>> autograd.grad(g(x), x, grad_outputs=torch.tensor([1, 0, 0, 0])) 要获取雅可比矩阵的 2nd 行,我们调用 # >>> autograd.grad(g(x), x, grad_outputs=torch.tensor([0, 1, 0, 0])) # and so on. # # 使用 vmap,我们可以将这些 4 个计算向量化为一个,通过传递 R^4 的标准基作为 grad_output。 # # passing the standard basis for R^4 as the grad_output. # vmap(partial(autograd.grad, g(x), x))(torch.eye(4)). # # 现在我们要如何计算雅可比矩阵而不堆叠输出呢? # 我们只需将标准基分解到输出中。因此,要计算 f(x) 的雅可比矩阵,我们会使用 # # compute the jacobian of f(x), we'd use >>> autograd.grad(f(x), x, grad_outputs=_construct_standard_basis_for(...)) # The grad_outputs looks like the following: # ( torch.tensor([[1, 0, 0], # [0, 1, 0], # [0, 0, 1], # [0, 0, 0]]), # torch.tensor([[0], # [0], # [0], # [1]]) ) # # 但我们还没有完成! # >>> vmap(partial(autograd.grad(f(x), x, grad_outputs=...))) # 返回形状为 [4, 3] 的 Tensor。我们必须记住将 # 形状为 [4, 3] 的 jacobian 分成两部分: # - 第一部分形状为 [3, 3],用于第一个输出 # - 第二部分形状为 [ 3],用于第二个输出 # 步骤 1:通过分割标准基来构造 grad_outputs 输出元素数量 = 元组(输出.元素数量() for 输出 输出) 梯度输出 = _construct_standard_basis_for(输出, output_numels) 平坦输出 = 元组(输出.重塑(-1) for 输出 输出) # 步骤 2:调用 vmap 和 autograd.grad def vjp(grad_output): vj = 列表( _自动微分梯度( flat_outputs, 输入, grad_output, 创建图=创建图, 是否批处理梯度=, ) ) for el_idx, vj_el 列举(vj): if vj_el 不是 : continue vj[el_idx] = 火炬.等于零的(输入[el_idx]).展开( (总和(output_numels),) + 输入[el_idx].形状 ) 返回 元组(vj) 平滑输出雅可比矩阵 = vjp(梯度输出) # 第 3 步:返回的雅可比是一个大张量,每个输入一个。 我们将每个张量按输出进行分割。 雅可比输入输出 = 输入文本为空,请提供需要翻译的文本 for jac_input_i, 输入_i zip(平坦输出的雅可比, 输入): 输入_i_输出雅可比 = 输入文本为空,请提供需要翻译的文本 for 雅克, 输出_j zip( jac_input_i.分割(output_numels, 暗淡=0), 输出 ): 雅可比输入_i 输出_j = jacobian.视图(输出_j.形状 + 输入_i.形状) jacobian 输入_i 输出.添加(雅可比输入_i 输出_j) 雅可比输入输出.添加(jacobian 输入_i 输出) # 第 4 步:目前,`jacobian`是一个 List[List[Tensor]]。 # 最外层的 List 对应输入的数量, # the inner List corresponds to the number of outputs. # 我们需要交换这些的顺序并转换为元组 # 在返回之前。 雅可比输出输入 = 元组(zip(*雅可比输入输出)) 雅可比输出输入 = _grad 后处理( 雅可比输出输入, 创建图 ) 返回 _tuple 后处理( 雅可比输出输入, (输出是否为元组, 是否输入为元组) ) 贾卡比安: 元组[PyTorch.张量, ...] = () for i, out 列举(输出): 由于空列表,mypy 抱怨表达式和变量类型不同 ja_c_i: 元组[列表[PyTorch.张量]] = 元组空列表 ( for _ 范围(长度(输入))) # 类型:忽略[赋值] for j 范围(输出.元素个数()): vj = _自动微分梯度( (输出.重塑(-1)]j],), 输入, retain_graph=True, 创建图=创建图, ) for el_idx, (ja_c_i_é_l, vj_el, in_p_é_l) 列举( zip(ja_c_i, vj, 输入) ): 如果 vj_el 不是 : 如果 严格的 创建图 不是 vj_el.需要梯度: msg = ( "用户提供的函数的雅可比矩阵是 " f与输入无关{i}。这在 " 中是不允许的 "创建图时启用严格模式。" ) 抛出 运行时错误(信息) ja_c_i_é_l.添加(vj_el) 否则: if 严格: msg = ( f"输出"{i}用户提供的函数是 " f与输入无关{el_idx}。这在 " 中是不允许的 "严格模式。" ) 抛出 运行时错误(信息) ja_c_i_é_l.添加(火炬.等于零的(in_p_é_l)) ja_c_bian += ( 元组( 火炬.(ja_c_i_é_l, 维度=0).视图( 输出.尺寸() + 输入[el_idx].尺寸() # type: ignore[operator] ) for (el_idx, ja_c_i_é_l) 列举(ja_c_i) ), ) ja_c_bian = _grad 后处理(贾卡比安, 创建图) 返回 _元组后处理(贾卡比安, (输出是否为元组, 是否输入为元组))
[文档]def 赫斯矩阵( 函数, 输入, 创建图=, 严格=, 向量化=, 外雅可比策略=反向模式, ): r"计算给定标量函数的 Hessian 矩阵。" 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一个只有一个元素的张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 create_graph(布尔值,可选):如果为`True`,则 Hessian 将以可微分的方式计算。 注意,当`strict`为`False`时,结果可能不需要梯度或与输入断开连接。 不需要梯度或与输入断开连接。 默认为 ``False``。 strict (bool, 可选): 如果为 ``True``,当我们检测到存在一个输入 使得所有输出都与它无关时,将引发错误。如果为 ``False``,我们返回一个零张量作为 对于这些输入的 Hessian,这是预期的数学值。 默认为 ``False``。 向量化(bool,可选):此功能为实验性。 请考虑使用 :func:`torch.func.hessian` 如果您正在寻找更少实验性且性能更优的方案,请使用它。 在计算 Hessian 矩阵时,通常我们会对 Hessian 矩阵的每一行调用一次 `autograd.grad`。 如果此标志被设置,则 ... ``True``,我们使用 vmap 原型功能作为后端 将 `autograd.grad` 的调用向量化,以便我们只调用一次 而不是每行一次。这应该会提高性能 在很多用例中有所改进,然而,由于这个功能 不完整,可能会有性能悬崖。请 使用 `torch._C._debug_only_display_vmap_fallback_warnings(True)` 显示任何性能警告并向我们提交问题 存在针对您用例的警告。默认为 ``False``。 outer_jacobian_strategy (str, 可选): 通过计算雅可比矩阵的雅可比矩阵来计算 Hessian。内部雅可比矩阵始终在反向模式 AD 中计算。将策略设置为“forward-mode”或“reverse-mode”将确定外部雅可比矩阵是否 通过计算雅可比矩阵的雅可比矩阵来计算 Hessian。内部雅可比矩阵始终在反向模式 AD 中计算。 将策略设置为“forward-mode”或“reverse-mode”将确定外部雅可比矩阵是否 通过计算雅可比矩阵的雅可比矩阵来计算 Hessian。内部雅可比矩阵始终在反向模式 AD 中计算。 使用正向或反向模式 AD 计算。当前,计算外 雅可比矩阵在“forward-mode”模式下需要`vectorized=True`。默认 将 ``"reverse-mode"`` 反转模式。 返回: 希氏矩阵(张量或张量元组的元组):如果只有一个输入, 这是一个包含输入 Hessian 的单个 Tensor。 如果它是一个元组,那么 Hessian 将是一个元组,其中每个元素也是一个元组, ``Hessian[i][j]`` 将包含第 ``i`` 个输入和第 ``j`` 个输入的 Hessian, 大小为第 ``i`` 个输入的大小加上第 ``i`` 个输入的大小之和。 输入的 ``j``\th 输入的大小。 ``Hessian[i][j]`` 将具有相同的 数据类型和设备作为相应的第 i 个输入。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> def pow_reducer(x): ... return x.pow(3).sum() >>> inputs = torch.rand(2, 2) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> hessian(pow_reducer, inputs) tensor([[[[5.2265, 0.0000], [0.0000, 0.0000]], [[0.0000, 4.8221], [0.0000, 0.0000]]] [[[0.0000, 0.0000], [1.9456, 0.0000]], [[0.0000, 0.0000], [[0.0000, 3.2550]] >>> hessian(pow_reducer, inputs, create_graph=True) tensor([[[[5.2265, 0.0000], [0.0000, 0.0000]], [[0.0000, 4.8221], [0.0000, 0.0000]]] [[[0.0000, 0.0000], [1.9456, 0.0000]], [[0.0000, 0.0000], [0.0000, 3.2550]]]], grad_fn=<ViewBackward>) >>> def pow_adder_reducer(x, y): ... return (2 * x.pow(2) + 3 * y.pow(2)).sum() >>> inputs = (torch.rand(2), torch.rand(2)) >>> hessian(pow_adder_reducer, inputs) ((张量([[4., 0.], [0., 4.]]), 张量([[0., 0.], [0., 0.]]) (tensor([[0., 0.], [0., 0.]]), tensor([[6., 0.], [0., 6.]]))) "沉浸式翻译" is_inputs_tuple, 输入 = _as_tuple(输入, 输入, 赫斯矩阵) 断言 外部雅可比策略 ( "正向模式", 反向模式, ), 预期策略应为 "前向模式" 或 "反向模式"。 def 确保单输出函数(*输入): 外部 = 函数(*输入) 是否输出元组, t_out = _as_tuple( 输出, 用户提供的函数的输出, 赫斯矩阵 ) _check_requires_grad(t_out, 输出, 严格=严格) 如果 是_out_tuple 或者 不是 isinstance(输出, 火炬.张量): 抛出 运行时错误( 给定的 Hessian 函数应返回单个张量 ) 如果 输出.元素个数() != 1: 抛出 运行时错误( "由给定给 hessian 的函数返回的张量应包含单个元素" ) 返回 输出.挤压() def 贾克函数(*输入): 如果 外雅可比策略 == "正向模式": # _grad_preprocess 需要设置 create_graph=True 并且输入需要 require_grad # 否则输入将被断开 输入 = 元组(t.需要梯度_() for t 输入) jaç = 贾卡比安(确保单输出函数, 输入, 创建图=True) _check_requires_grad(雅克, "雅可比安", 严格=严格的) 返回 jaç res = 贾卡比安( 贾克函数, 输入, 创建图=创建图, 严格=严格, 向量化=向量化, 策略=外雅可比策略, ) 返回 _元组后处理(资源, (是否输入为元组, 是否输入为元组))
[文档]def vhp(函数, 输入, v=, 创建图=错误, 严格=错误): r计算向量 ``v`` 与给定标量函数在指定点的 Hessian 的点积。 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一个只有一个元素的张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 v(张量或张量元组):计算乘积的向量。必须与 func 的输入大小相同。 当 func 的输入包含单个元素时,此参数是可选的;如果没有提供,将默认设置为。 ``func``。如果 func 的输入包含单个元素,则此参数是可选的;如果没有提供,将默认设置为。 ``func``的输入包含单个元素时,此参数是可选的;如果没有提供,将默认设置为。 包含单个“1”的张量。 create_graph(布尔值,可选):如果为 True,输出和结果将以可微分的方式进行计算。注意,当 strict 为 False 时,结果可能不需要梯度或为 将以可微分的方式进行计算。注意,当 `strict` 为 `False` 时, 结果不需要梯度或可以, 断开与输入的连接。 默认为 ``False``。 strict(布尔值,可选):如果为 ``True``,当我们 检测到存在一个输入,其所有输出都与它无关时,将引发错误。 如果为 ``False``,我们将返回一个全零的 Tensor。 vhp 对于这些输入,即预期的数学值。 默认为 ``False``。 返回: 输出(元组):包含元组的: func_output (Tensors 或 Tensor 的元组): `func(inputs)` 的输出 vhp(Tensors 或 Tensor 的元组):与...的点积结果 输入相同的形状。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> def pow_reducer(x): ... return x.pow(3).sum() >>> inputs = torch.rand(2, 2) >>> v = torch.ones(2, 2) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> vhp(pow_reducer, inputs, v) (tensor(0.5591), tensor([[1.0689, 1.2431], [3.0989, 4.4456]]) >>> vhp(pow_reducer, inputs, v, create_graph=True) (tensor(0.5591, grad_fn=<SumBackward0>), tensor([[1.0689, 1.2431], [3.0989, 4.4456]], grad_fn=<MulBackward0>)) >>> def pow_adder_reducer(x, y): ... return (2 * x.pow(2) + 3 * y.pow(2)).sum() >>> inputs = (torch.rand(2), torch.rand(2)) >>> v = (torch.zeros(2), torch.ones(2)) >>> vhp(pow_adder_reducer, inputs, v) (tensor(4.8053), (tensor([0., 0.])) tensor([6., 6.]))) "沉浸式翻译" 火炬.启用梯度(): is_inputs_tuple, 输入 = _as_tuple(输入, 输入, "vhp") 输入 = _grad_preprocess(输入, 创建图=创建图, 需要图形=) if v 不是 : _, v = _as_tuple(v, "v", "vhp") v = _grad_preprocess(v, 创建图=创建图, 需要图形=错误) _验证_v(v, 输入, is_inputs_tuple) 否则: if 长度(输入) != 1 或者 输入[0].元素个数() != 1: 抛出 运行时错误( "向量 v 只有在用户提供的函数输入为空时才能为 None。" "必须是一个只有一个元素的单一张量。" ) 输出 = 函数(*输入) 输出是否为元组, 输出 = _as_tuple( 输出, 用户提供的函数的输出, vhp ) _check_requires_grad(输出, 输出, 严格=严格) if 输出是否为元组 或者 不是 isinstance(输出[0] 火炬.张量): 抛出 运行时错误( 给定的 vhp 函数应返回单个 Tensor ) if 输出[0].元素个数() != 1: 抛出 运行时错误( 给定的 vhp 函数返回的 Tensor 应包含单个元素 ) jaç = _自动微分梯度(输出, 输入, 创建图=) _check_requires_grad(雅克, "雅可比安", 严格=严格) enable_grad = 真实 if 创建图 否则 火炬.梯度是否启用() 火炬.设置梯度启用(启用梯度): 梯度结果 = _自动微分梯度(雅克, 输入, v, 创建图=创建图) 高频脉冲 = 填充零(梯度结果, 输入, 严格, 创建图, "双后向") 输出 = _grad 后处理(输出, 创建图) 高频脉冲 = _grad 后处理(vhp, 创建图) 返回 _tuple 后处理(输出, 输出是否为元组), _tuple 后处理( vhp, 输入是否为元组 )
[文档]def hvp(函数, 输入, v=, 创建图=错误, 严格=错误): r计算标量函数的 Hessian 矩阵与向量 v 在指定点的点积。 参数: func(函数):一个 Python 函数,它接受张量输入并返回 一个只有一个元素的张量。 输入(一组张量或张量):传递给函数 ``func`` 的输入。 v(张量或张量元组的元组):Hessian 向量的向量 当 func 的输入包含单个元素时,此参数是可选的;如果没有提供,将默认设置为。 ``func``。如果 func 的输入包含单个元素,则此参数是可选的;如果没有提供,将默认设置为。 ``func``的输入包含单个元素时,此参数是可选的;如果没有提供,将默认设置为。 包含单个“1”的张量。 create_graph(布尔值,可选):如果为 True,输出和结果将以可微分的方式进行计算。注意,当 strict 为 False 时,结果可能不需要梯度或断开连接。 注意,当`strict`为`False`时,结果可能不需要梯度或断开连接。 ``False``,结果不能需要梯度或与输入断开连接 从输入中。默认为 ``False``。 strict(布尔值,可选):如果为 ``True``,当我们 检测到存在一个输入,其所有输出都与它无关时,将引发错误。 如果为 ``False``,我们将返回一个全零的 Tensor。 hvp 对于这些输入,是预期的数学值。 默认为 ``False``。 返回: 输出(元组):包含元组的: func_output (Tensors 或 Tensor 的元组): `func(inputs)` 的输出 hvp(Tensors 或 Tensor 的元组):与...的点积结果 与输入具有相同的形状。 示例: >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_AUTOGRAD) >>> def pow_reducer(x): ... return x.pow(3).sum() >>> inputs = torch.rand(2, 2) >>> v = torch.ones(2, 2) >>> # xdoctest: +IGNORE_WANT("非确定性") >>> hvp(pow_reducer, inputs, v) (tensor(0.1448), tensor([[2.0239, 1.6456], [2.4988, 1.4310]]) >>> hvp(pow_reducer, inputs, v, create_graph=True) (tensor(0.1448, grad_fn=<SumBackward0>), tensor([[2.0239, 1.6456], [2.4988, 1.4310]], grad_fn=<MulBackward0>)) >>> def pow_adder_reducer(x, y): ... return (2 * x.pow(2) + 3 * y.pow(2)).sum() >>> inputs = (torch.rand(2), torch.rand(2)) >>> v = (torch.zeros(2), torch.ones(2)) >>> hvp(pow_adder_reducer, inputs, v) (tensor(2.3030), (tensor([0., 0.])) tensor([6., 6.]))) 注意: 该函数由于反向模式自动微分约束,比 `vhp` 函数慢得多。 如果你的函数是连续两次可微分的,那么 hvp = vhp.t()。所以如果你知道你的函数满足这个条件,你应该使用 vhp,它比当前实现快得多。 知道你的函数满足这个条件,你应该使用 vhp,它比当前实现快得多。 使用 vhp,它比当前实现快得多。 "沉浸式翻译" 火炬.启用梯度(): is_inputs_tuple, 输入 = _as_tuple(输入, 输入, hvp) 输入 = _grad_preprocess(输入, 创建图=创建图, 需要图形=) 如果 v 不是 : _, v = _as_tuple(v, "v", hvp) v = _grad_preprocess(v, 创建图=创建图, 需要图形=错误) _验证_v(v, 输入, is_inputs_tuple) 否则: 如果 长度(输入) != 1 或者 输入[0].元素个数() != 1: 抛出 运行时错误( "向量 v 只有在用户提供的函数输入为空时才能为 None。" "必须是一个只有一个元素的单一张量。" ) 输出 = 函数(*输入) 输出是否为元组, 输出 = _as_tuple( 输出, 用户提供的函数的输出, "hvp" ) _check_requires_grad(输出, 输出, 严格的=严格的) 如果 输出是否为元组 或者 不是 isinstance(输出[0] 火炬.张量): 抛出 运行时错误( 给定给 hvp 的函数应返回单个张量 ) 如果 输出[0].元素个数() != 1: 抛出 运行时错误( 函数给定的 hvp 返回的张量应包含单个元素 ) jaç = _自动微分梯度(输出, 输入, 创建图=) _check_requires_grad(雅克, "雅可比安", 严格的=严格的) grad_jac = 元组(火炬.等于零的(输入, 需要梯度=) for 输入 输入) 双重回退 = _自动微分梯度(雅克, 输入, grad_jac, 创建图=) _check_requires_grad(雅克, 赫斯矩阵, 严格的=严格的) enable_grad = 真实 如果 创建图 否则 火炬.梯度是否启用() 火炬.设置梯度启用(启用梯度): 梯度结果 = _自动微分梯度(double_back, grad_jac, v, 创建图=创建图) hvp = 填充零( 梯度结果, 输入, 严格的, 创建图, 双重回退技巧 ) 输出 = _grad 后处理(输出, 创建图) hvp = _grad 后处理(hvp, 创建图) 返回 _tuple 后处理(输出, 输出是否为元组), _tuple 后处理( hvp, 输入是否为元组 )

© 版权所有 PyTorch 贡献者。

使用 Sphinx 构建,并使用 Read the Docs 提供的主题。

文档

查看 PyTorch 的全面开发者文档

查看文档

教程

深入了解初学者和高级开发者的教程

查看教程

资源

查找开发资源,获取您的疑问解答

查看资源