
本文详解如何在 PyTorch 中正确计算形如 ( f(x) = P(x)h ) 的向量值函数关于输入向量 ( x ) 的梯度(即 Jacobian-Vector Product),重点解决 backward() 对非标量输出报错的问题,并提供可复现的代码示例与关键注意事项。
本文详解如何在 pytorch 中正确计算形如 \( f(x) = p(x)h \) 的向量值函数关于输入向量 \( x \) 的梯度(即 jacobian-vector product),重点解决 `backward()` 对非标量输出报错的问题,并提供可复现的代码示例与关键注意事项。
在 PyTorch 中,autograd 系统默认仅支持标量输出的反向传播(即 loss.backward())。当目标函数输出为向量(如本例中 Pxh = P(x) @ h 是一个二维张量)时,直接调用 Pxh.backward() 会触发 RuntimeError: grad can be implicitly created only for scalar outputs 错误——因为 PyTorch 不知道该用何种“权重”将各分量的梯度加权求和。
根本解决方案是:显式传入与输出同形状的梯度权重张量(通常为 torch.ones_like(output)),从而将向量值反向传播解释为对每个输出分量施加单位扰动,等价于计算 Jacobian 矩阵与向量 h 的乘积(JVP)的梯度。
以下是一个完整、可运行的修正示例:
import torch
def P(x, A):
x_col = x.unsqueeze(1) # [n] → [n, 1]
Ax = torch.matmul(A, x_col) # [n, n] @ [n, 1] → [n, 1]
denom = torch.dot(Ax.squeeze(), Ax.squeeze()) # scalar: x' A^T A x (A symmetric ⇒ A^T A = A²)
P_matrix = torch.matmul(Ax, Ax.t()) / denom # [n, 1] @ [1, n] → [n, n]
return P_matrix # 保持为 2D 张量,语义更清晰
# 设置参数
A = torch.tensor([[1.0, 0.5],
[0.5, 1.3]], dtype=torch.float32)
x = torch.tensor([1.0, 2.0], dtype=torch.float32, requires_grad=True)
h = torch.tensor([2.0, -1.0], dtype=torch.float32)
# 前向:计算 P(x)h ∈ ℝ²
Pxh = torch.matmul(P(x, A), h) # shape: [2]
# 反向:显式指定 grad_output = ones → 等价于 sum(Pxh) 的梯度,即 J^T @ 1
Pxh.backward(torch.ones_like(Pxh))
print("∇_x (P(x)h) =", x.grad)
# 输出示例:tensor([ 0.4853, -0.2427])
✅ 关键原理说明:
- Pxh.backward(torch.ones_like(Pxh)) 等价于先执行 loss = Pxh.sum() 再 loss.backward(),其结果正是 Jacobian 矩阵 ( J = \partial(Pxh)/\partial x \in \mathbb{R}^{2\times2} ) 的行和,即 ( J^\top \mathbf{1} )。
- 若需获取完整 Jacobian(如验证数学推导),可对 Pxh 的每个分量分别调用 backward(配合 retain_graph=True),或使用 torch.autograd.functional.jacobian(更直观但开销略高):
from torch.autograd.functional import jacobian
J_full = jacobian(lambda x_: torch.matmul(P(x_, A), h), x)
print("Full Jacobian:\n", J_full)
# shape: [2, 2] —— 第 i 行对应 ∂(Pxh)_i / ∂x
⚠️ 注意事项:
- requires_grad=True 必须设置在原始输入张量(如 x)上,中间变量(如 Ax, P_matrix)会自动追踪;
- 避免在 backward() 前修改 x 或其依赖项(如调用 .detach() 或 no_grad 上下文),否则计算图断裂;
- 若 A 也需优化(如作为模型参数),应同样设 requires_grad=True 并确保其参与计算图;
- 在 JAX 中对应解法为 jax.jvp(正向模式)或 jax.grad(lambda x: jnp.dot(P(x, A), h).sum())(反向模式),逻辑一致。
掌握这一技巧,即可稳健地对任意矩阵/张量值中间函数进行数值导数验证,是深度学习中实现自定义算子、调试复杂损失函数或验证数学推导的必备能力。











