10、PyTorch autograd使用教程
文章目录
- 1. 相关思考
- 2. 矩阵求导
- 3. 两种方法求jacobian
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} ∂w∂y=x,∂b∂y=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} ∂y∂z=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} ∂w∂z=∂y∂z⋅∂w∂y=m⋅x - 我们定义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} ∂w∂z= 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} ∂b∂z=∂y∂z⋅∂b∂y=m= 111 - 小结:如果要计算向量y对向量w进行求导,但pytorch的反向传播用的是标量,一般做法是引入全1向量求和,这样就将向量y对向量w的求导转换成z对w的求导,最后运用链式法则即可求出。
- python 代码
import torch
from torch import nnif __name__ == "__main__":run_code = 0in_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_bz = 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.]])