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 的推出,两个框架之间的差距正在缩小,为开发者提供了更多的选择。

浙公网安备 33010602011771号