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 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.]])