Bootstrap

10、PyTorch autograd使用教程

1. 相关思考

在这里插入图片描述

2. 矩阵求导

假设我们有如下向量:
y 1 × 3 = x 1 × 5 [ w T ] 5 × 3 + b 1 × 3 \begin{equation} y_{1\times3}=x_{1\times5}[w^T]_{5\times3}+b_{1\times3} \end{equation} y1×3=x1×5[wT]5×3+b1×3
根据公式,我们知道偏导如下:
∂ y ∂ w = x , ∂ y ∂ b = 1 \begin{equation} \frac{\partial{y}}{\partial w}=x,\frac{\partial{y}}{\partial b}=1 \end{equation} wy=x,by=1
但通过公式我们知道,y为向量,在pytorch中一般都是标量后再进行反向传播,故我们需要引入求和公式
z = y T m , m = [ 1 1 1 ] \begin{equation} z=y^Tm,m=\begin{bmatrix}1\\\\1\\\\1\end{bmatrix} \end{equation} z=yTm,m= 111

  • 那么z就是可以当作标量,可以用z做反向传播
    ∂ z ∂ y = m \begin{equation} \frac{\partial{z}}{\partial y}=m \end{equation} yz=m
  • 由链式法则可得如下:
    ∂ z ∂ w = ∂ z ∂ y ⋅ ∂ y ∂ w = m ⋅ x \begin{equation} \frac{\partial{z}}{\partial w}=\frac{\partial{z}}{\partial y}\cdot\frac{\partial{y}}{\partial w}=m\cdot x \end{equation} wz=yzwy=mx
  • 我们定义x=[0,1,2,3,4],那么梯度可得:
    ∂ z ∂ w = [ 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 ] \begin{equation} \frac{\partial{z}}{\partial w}= \begin{bmatrix} 0&1&2&3&4\\\\ 0&1&2&3&4\\\\ 0&1&2&3&4 \end{bmatrix} \end{equation} wz= 000111222333444
  • 同理:可得关于b的导数如下:
    ∂ z ∂ b = ∂ z ∂ y ⋅ ∂ y ∂ b = m = [ 1 1 1 ] \begin{equation} \frac{\partial{z}}{\partial b}=\frac{\partial{z}}{\partial y}\cdot\frac{\partial{y}}{\partial b}=m=\begin{bmatrix} 1\\\\ 1\\\\ 1 \end{bmatrix} \end{equation} bz=yzby=m= 111
  • 小结:如果要计算向量y对向量w进行求导,但pytorch的反向传播用的是标量,一般做法是引入全1向量求和,这样就将向量y对向量w的求导转换成z对w的求导,最后运用链式法则即可求出。
  • python 代码
import torch
from torch import nn

if __name__ == "__main__":
    run_code = 0
    in_x = torch.arange(5,dtype=torch.float)
    in_w = torch.randn((3, 5), requires_grad=True)
    in_b = torch.arange(3, dtype=torch.float, requires_grad=True)
    y = in_x @ in_w.T + in_b
    z = torch.sum(y)
    print(f"*"*50)
    print(f"before:")
    print(f"x={in_x}")
    print(f"w=\n{in_w}")
    print(f"w_grad=\n{in_w.grad}")
    print(f"b=\n{in_b}")
    print(f"b_grad=\n{in_b.grad}")
    print(f"y={y}")
    print(f"before:")
    print(f"*"*50)
    z.backward()
    print(f"*"*50)
    print(f"after:")
    print(f"x={in_x}")
    print(f"w=\n{in_w}")
    print(f"w_grad=\n{in_w.grad}")
    print(f"b=\n{in_b}")
    print(f"b_grad=\n{in_b.grad}")
    print(f"b_grad=\n{in_b.grad.shape}")
    print(f"y={y}")
    print(f"after:")
    print(f"*"*50)
  • 结果:
**************************************************
before:
x=tensor([0., 1., 2., 3., 4.])
w=
tensor([[-1.5762, -0.0040,  1.7958,  0.2164,  0.7108],
        [ 0.6488, -0.8668,  0.0572, -1.1207,  0.0568],
        [-0.5594,  0.1091,  0.6546,  0.0851,  1.1287]], requires_grad=True)
w_grad=
None
b=
tensor([0., 1., 2.], requires_grad=True)
b_grad=
None
y=tensor([ 7.0800, -2.8872,  8.1886], grad_fn=<AddBackward0>)
before:
**************************************************
**************************************************
after:
x=tensor([0., 1., 2., 3., 4.])
w=
tensor([[-1.5762, -0.0040,  1.7958,  0.2164,  0.7108],
        [ 0.6488, -0.8668,  0.0572, -1.1207,  0.0568],
        [-0.5594,  0.1091,  0.6546,  0.0851,  1.1287]], requires_grad=True)
w_grad=
tensor([[0., 1., 2., 3., 4.],
        [0., 1., 2., 3., 4.],
        [0., 1., 2., 3., 4.]])
b=
tensor([0., 1., 2.], requires_grad=True)
b_grad=
tensor([1., 1., 1.])
b_grad=
torch.Size([3])
y=tensor([ 7.0800, -2.8872,  8.1886], grad_fn=<AddBackward0>)
after:
**************************************************

3. 两种方法求jacobian

  • 方法一:直接使用 from torch.autograd.functional import jacobian
  • 方法二:将f(x)的向量按照每个元素是标量的方式,对每个标量进行反向传播,最后将结果叠加起来。
import torch
from torch.autograd.functional import jacobian


# 定义函数 f(x)
def funx(x):
    return torch.stack([
        x[0] ** 2 + 2 * x[1],
        3 * x[0] + 4 * x[1] ** 2
    ])


if __name__ == "__main__":
    # 初始化输入张量并启用梯度
    in_x = torch.tensor([1.0, 2.0], dtype=torch.float, requires_grad=True)

    # 计算函数输出
    y = funx(in_x)

    # 初始化一个零矩阵,用于保存手动计算的雅可比矩阵
    manual_jacobian = torch.zeros(len(y), len(in_x))

    # 手动计算雅可比矩阵
    for i in range(len(y)):
        # 每次 backward 计算一个分量的梯度
        in_x.grad = None  # 清除之前的梯度
        y[i].backward(retain_graph=True)
        manual_jacobian[i] = in_x.grad

    # 使用 autograd.functional.jacobian 验证雅可比
    auto_jacobian = jacobian(funx, in_x)

    print(f"Manual Jacobian:\n{manual_jacobian}")
    print(f"Auto Jacobian:\n{auto_jacobian}")
  • 结果:
Manual Jacobian:
tensor([[ 2.,  2.],
        [ 3., 16.]])
Auto Jacobian:
tensor([[ 2.,  2.],
        [ 3., 16.]])
;