torch 自动求导

符号对应

向量和矩阵

torch.randn(n, 1) <=> \(\begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n\end{bmatrix}\)

torch.randn(1, n) <=> \(\begin{bmatrix}x_1 & x_2 & \cdots & x_n\end{bmatrix}\)

torch.randn(m, n) <=>\(\begin{bmatrix}a_{11} & a_{12} & \cdots & a_{1n} \\a_{21} & a_{22} & \cdots & a_{2n} \\\vdots & \vdots & \ddots & \vdots \\a_{m1} & a_{m2} & \cdots & a_{mn}\end{bmatrix}\)


偏导数

y = torch.randn(\(m_y\), \(n_y\))
x = torch.randn(\(m_x\), \(n_x\))

=>

\(\frac{\partial y}{\partial x}\) <=> torch.randn(\(m_y, n_y, n_x, m_x\))

例如

\(m_y = 1, n_y = 1, m_x = k, n_x = 1\)
=>
\(\frac{\partial y}{\partial x}\) <=> torch.randn(\(1, 1, 1, k\)) => torch.randn(\(1, k\))


\(m_y = k, n_y = 1, m_x = 1, n_x = 1\)
=>
\(\frac{\partial y}{\partial x}\) <=> torch.randn(\(k, 1, 1, 1\)) => torch.randn(\(k, 1\))


\(m_y = k_1, n_y = 1, m_x = k_2, n_x = 1\)
=>
\(\frac{\partial y}{\partial x}\) <=> torch.randn(\(k_1, 1, k_2, 1\)) => torch.randn(\(k_1, k_2\))


链式法则

\(\frac{\partial y}{\partial x} = \frac{\partial y}{\partial k}\frac{\partial k}{\partial x}\)
=>
\((m, n) = (m, k).(k,n)\)






pytorch自动微分的核心组件

PyTorch 的自动微分机制由以下几个核心组件构成:

(1) 张量(Tensor)

  • 张量是 PyTorch 的基本数据结构,类似于 NumPy 数组。
  • 当设置 requires_grad=True 时,PyTorch 会跟踪对该张量的所有操作。

(2) Autograd 引擎

  • Autograd 是 PyTorch 的自动微分引擎,负责构建计算图并执行反向传播。
  • 它会根据前向传播中的操作记录生成计算图,并在反向传播中应用链式法则。

(3) Function 类

  • 每个操作(如加法、乘法)都对应一个 Function 对象。
  • Function 对象包含两个方法:
    • forward:执行前向传播。
    • backward:执行反向传播,计算梯度。





pytorch自动求导

(method) def backward(
    gradient: Any | None = None,
    retain_graph: Any | None = None,
    create_graph: bool = False,
    inputs: Any | None = None
) -> (Any | None)

1. backward() 的作用

backward() 是 PyTorch 自动微分(autograd)系统的核心方法之一,用于计算当前张量相对于计算图中叶子张量(leaf tensors)的梯度。它通过链式法则(chain rule)实现梯度的反向传播。

关键点:

  • 如果当前张量是标量(scalar),可以直接调用 .backward()
  • 如果当前张量是非标量(non-scalar),需要提供额外的 gradient 参数。
  • 梯度会被累积到叶子张量的 .grad 属性中。

2. 参数详解

(1) gradient: Tensor | None = None

  • 作用
    • 当前张量是非标量时,需要指定 gradient 参数。
    • 它表示目标函数相对于当前张量的梯度。
    • 非张量求导解释
  • 类型
    • 如果是一个张量,其形状必须与当前张量匹配。
    • 如果是 None,表示默认情况下目标函数是标量。
  • 自动转换
    • 默认情况下,gradient 会被自动转换为不需要梯度的张量(即 requires_grad=False)。
    • 如果设置了 create_graph=True,则 gradient 也会参与计算图的构建。
  • 示例
    x = torch.tensor([1., 2., 3.], requires_grad=True)
    y = x ** 2
    
    # 非标量张量需要指定 gradient
    gradient = torch.tensor([1., 1., 1.])  # 假设每个元素的权重相同
    y.backward(gradient=gradient)
    
    print(x.grad)  # 输出: tensor([2., 4., 6.])
    

(2) retain_graph: bool | None = None

  • 作用
    • 控制是否在反向传播后保留计算图。
    • 如果设置为 True,计算图不会被释放,允许多次调用 .backward()
  • 默认值
    • 如果未显式设置,则默认值为 create_graph 的值。
  • 注意事项
    • 在大多数情况下,设置 retain_graph=True 并不必要,可以通过更高效的方式避免重复计算。
  • 示例
    x = torch.tensor([1., 2., 3.], requires_grad=True)
    y = x ** 2
    
    # 第一次反向传播,保留计算图
    y.sum().backward(retain_graph=True)
    
    # 第二次反向传播
    y.sum().backward()
    
    print(x.grad)  # 输出: tensor([4., 8., 12.]),因为梯度会累加
    

(3) create_graph: bool = False

  • 作用
    • 如果设置为 True,会创建一个新的计算图来支持高阶导数计算。
    • 这对于计算二阶导数(如 Hessian 矩阵)非常有用。
  • 默认值
    • 默认值为 False
  • 示例
    x = torch.tensor([1., 2., 3.], requires_grad=True)
    y = x ** 2
    
    # 创建梯度图
    z = y.sum()
    z.backward(create_graph=True)
    
    # 对梯度再求导
    grad_x = x.grad
    grad_x.backward(torch.ones_like(grad_x))
    
    print(x.grad)  # 输出更高阶导数
    

(4) inputs: sequence of Tensor | None = None

  • 作用
    • 指定哪些叶子张量需要累积梯度。
    • 如果未提供,则默认对所有参与计算的叶子张量累积梯度。
  • 类型
    • 可以是一个张量序列。
  • 示例
    x = torch.tensor([1., 2., 3.], requires_grad=True)
    y = torch.tensor([4., 5., 6.], requires_grad=True)
    z = x ** 2 + y ** 2
    
    # 只对 x 计算梯度
    z.sum().backward(inputs=[x])
    
    print(x.grad)  # 输出: tensor([2., 4., 6.])
    print(y.grad)  # 输出: None
    

3. 注意事项

(1) 梯度累加

  • backward() 会将梯度累积到叶子张量的 .grad 属性中。
  • 如果需要重新计算梯度,请先清零或重置 .grad 属性:
    x.grad.zero_()
    

(2) 标量 vs 非标量

  • 如果目标张量是标量(如损失函数),可以直接调用 .backward()
  • 如果目标张量是非标量,则需要提供 gradient 参数。

(3) 内存管理

  • 默认情况下,.backward() 会在完成后释放计算图以节省内存。
  • 如果需要多次调用 .backward(),请设置 retain_graph=True

4. 示例代码总结

以下是一个完整的示例,结合了所有参数的使用:

import torch

# 创建叶子张量
x = torch.tensor([1., 2., 3.], requires_grad=True)
y = torch.tensor([4., 5., 6.], requires_grad=True)

# 非标量张量
z = x ** 2 + y ** 2

# 清零梯度
if x.grad is not None:
    x.grad.zero_()
if y.grad is not None:
    y.grad.zero_()

# 计算梯度并保留计算图
gradient = torch.tensor([1., 1., 1.])  # 假设每个元素的权重相同
z.backward(gradient=gradient, retain_graph=True, create_graph=True)

print("Gradient w.r.t. x:", x.grad)  # 输出: tensor([2., 4., 6.])
print("Gradient w.r.t. y:", y.grad)  # 输出: tensor([8., 10., 12.])

# 对梯度再求导
x_grad = x.grad
x_grad.backward(torch.ones_like(x))

print("Second order gradient w.r.t. x:", x.grad)  # 输出更高阶导数

5. 总结

  • 核心功能

    • backward() 用于计算当前张量相对于叶子张量的梯度。
    • 支持标量和非标量张量,支持高阶导数计算。
  • 参数解释

    • gradient:指定目标函数相对于当前张量的梯度(仅适用于非标量张量)。
    • retain_graph:控制是否保留计算图。
    • create_graph:支持高阶导数计算。
    • inputs:指定需要累积梯度的叶子张量。
  • 注意事项

    • 梯度会累积到 .grad 属性中。
    • 默认情况下,计算图会在 .backward() 后被释放。




计算图

PyTorch 和 TensorFlow 都是深度学习领域非常流行的框架,它们各自提供了一套独特的机制来构建和管理计算图。尽管二者的目标相似——即支持高效的数值计算、自动微分等,但它们在实现这些目标的方法上有所不同。下面我们分别介绍 PyTorch 和 TensorFlow 的计算图,并对比它们的主要差异。

PyTorch 计算图

特点

  • 动态计算图:PyTorch 使用的是“定义即运行”(define-by-run)的范式,这意味着计算图是在每次前向传播时即时构建的。因此,PyTorch 的计算图是动态的,允许用户灵活地修改模型结构。
  • 易于调试:由于计算图是动态生成的,所以更容易进行调试。你可以使用标准的 Python 调试工具来检查任意时刻的变量值。
  • 自动微分:通过 torch.autograd 模块,PyTorch 支持自动计算梯度。这使得它非常适合用于研究和快速原型开发。

如何工作

  • 在 PyTorch 中,当你执行一个操作时,它会立即被执行并可能创建一个新的节点加入到当前的计算图中。当调用 .backward() 方法时,PyTorch 会根据这个计算图反向传播误差以计算梯度。
  • 用户可以通过设置 requires_grad=True 来指定哪些张量需要计算梯度,这样可以节省内存和计算资源。

TensorFlow 计算图

特点

  • 静态计算图:与 PyTorch 不同,TensorFlow 最初采用的是静态计算图模型,意味着你需要先定义好整个计算图,然后在这个固定的图上执行运算。不过,从 TensorFlow 2.0 开始,默认启用了 Eager Execution,这使得 TensorFlow 也可以支持类似 PyTorch 的动态计算图。
  • 性能优化:静态计算图允许 TensorFlow 对整个计算过程进行全局优化,例如常量折叠、内存共享等,这对于大规模分布式训练特别有利。
  • 图形化工具:TensorFlow 提供了 TensorBoard 等可视化工具,可以帮助用户更好地理解和调试他们的模型。

如何工作

  • 在 TensorFlow 1.x 中,你需要首先定义计算图,然后通过会话(session)来执行图中的操作。而在 TensorFlow 2.x 中,Eager Execution 允许你直接执行操作而无需先构建计算图,这极大地简化了模型的编写和调试流程。

主要差异

特性 PyTorch TensorFlow
计算图类型 动态计算图 静态计算图(TensorFlow 1.x),支持动态计算图(TensorFlow 2.x Eager Execution)
编程模式 定义即运行 定义即运行(TF 2.x),先定义后运行(TF 1.x)
调试难度 较易调试 TF 1.x 较难调试,TF 2.x 易于调试
灵活性 更加灵活,适合实验性研究 高性能优化,更适合生产环境
社区支持 强大的学术界支持 广泛的企业应用支持

结论

选择哪个框架取决于你的具体需求:

  • 如果你在做研究或快速原型开发,可能更倾向于使用 PyTorch,因为它提供了更加直观和灵活的方式来进行模型设计和调试。
  • 如果你需要处理大规模的数据集或模型部署到生产环境中,TensorFlow 可能是一个更好的选择,特别是考虑到其对分布式训练的支持以及与 Google 生态系统的集成。

无论选择哪一个框架,了解它们各自的计算图机制对于有效地利用这些工具至关重要。随着 TensorFlow 2.x 的推出,两个框架之间的差距正在缩小,为开发者提供了更多的选择。

posted @ 2025-04-15 15:20  玉米面手雷王  阅读(46)  评论(0)    收藏  举报