pytorch学习记录
一、自动求导机制
参考:Pytorch的自动求导机制与使用方法(一) – 知乎 (zhihu.com)
自动求导机制是pytorch核心功能之一。
1.1 求导法则
限制:pytorch中默认只能是标量 对标量/向量/矩阵 求导。
即只能对 f ( x ) = z , f : R n → R f(x)=z, f:R^n\rightarrow R f ( x ) = z , f : R n → R 求导,其中 x x x 可以通过多次复合函数得到,对 f f f 求导利用链式法则:
∂
z
∂
x
=
∂
z
∂
y
∂
y
∂
x
\frac{\partial z}{\partial x}=\frac{\partial z}{\partial y}\frac{\partial y}{\partial x}
∂ x ∂ z = ∂ y ∂ z ∂ x ∂ y
其中 x x x 为叶节点, z z z 为根节点, y y y 是过程操作,并不会被收集,只是作一个传播作用。
pytorch中的求导法则,实则是反向传播。
1.2 Tensor张量操作规则
PyTorch中数据以张量(n维数组)的形式流动torch.Tensor可以用来创建张量。
当Tensor的属性中requires_grad=True 时,则系统就可以开始跟踪对此Tensor的所有操作。其中记录的每个操作求得的梯度不会保存,只有在最后的一个操作才会保存梯度。
如上述的两次链式法则,只保存 ∂ z ∂ x \frac{\partial z}{\partial x} ∂ x ∂ z ,而 ∂ z ∂ y \frac{\partial z}{\partial y} ∂ y ∂ z 只是被当作中间值,不会保存 。
例:Tensor.backward()方法默认 计算对计算图叶子节点的导数,中间过程的导数是不计算的
x = torch.tensor(3.0 , requires_grad=True )
y = 2 *x
z = y**2
f = z+2
f.backward()
print (x.grad)
print (y.grad)
print (z.grad)
输出结果:
tensor(24. )
None
None
pytorch在求导时候会自动构建计算图。
从上图可以看出,一个Tensor中:
data中保存着所存有的数据
grad中保存梯度
requires_grad表示是否开始追踪所有的操作历史
想要计算梯度的时候,需要调用Tensor.backward()。在调用backward()时,只有当requires_grad和is_leaf同时为真时,才会计算节点的梯度值。
例:pytorch只能标量对其他进行求导。
input :
import torch
x = torch.ones(2 ,2 ,requires_grad=True )
y = x+2
print (x.is_leaf, y.is_leaf)
print (y.requires_grad)
y.backward()
ouput:
<<<True False
<<<True
<<<RuntimeError: grad can be implicitly created only for scalar outputs
z = y * y * 3
out = z.mean()
print (out)
print (out.requires_grad)
out.backward()
print (x.grad)
output:
<<<tensor(27. , grad_fn=<MeanBackward0>)
<<<True
<<<tensor([[4.5000 , 4.5000 ],
[4.5000 , 4.5000 ]])
求导过程如下:
令 o u t = o out=o o u t = o ,由于
o
=
1
4
∑
z
i
=
1
4
∑
3
(
x
i
)
+
2
o=\frac{1}{4}\sum z_i=\frac{1}{4}\sum 3(x_i)+2
o = 4 1 ∑ z i = 4 1 ∑ 3 ( x i ) + 2
所以
∂
o
∂
x
i
∣
x
i
=
1
=
9
2
=
4.5
\frac{\partial o}{\partial x_i}|_{x_i=1}=\frac{9}{2}=4.5
∂ x i ∂ o ∣ x i = 1 = 2 9 = 4.5
例:对 y = x 3 y=x^3 y = x 3 ,对 x = 2 x=2 x = 2 进行求导
import torch
import torch.autograd
x = torch.tensor([2 ,0 ],requires_grad=True )
print ("x= " ,x)
print ("x.requires_grad= " , x.requires_grad)
y = x ** 3
print ("y= " ,y)
print ("y.requires_grad = " , y.grad_fn)
y.backward()
print ("x.grad = " , x.grad)
输出结果为:
x = tensor([2. ], requires_grad=True )
x.requires_grad = True
y = tensor([8. ], grad_fn=<PowBackward0>)
y.requires_grad = <PowBackward0 object at 0x7f3a1dac6320 >
x.grad = tensor([12. ])
1.3 autograd类原理
autograd类的原理其实是利用雅可比矩阵进行计算。
设函数 f : R n → R m f:R^n\rightarrow R^m f : R n → R m ,其中 f = ( f 1 , f 2 , … , f m ) f=(f_1,f_2,\dots,f_m) f = ( f 1 , f 2 , … , f m ) ,则雅克比矩阵为:
J
=
[
∂
f
∂
x
1
⋯
∂
f
∂
x
n
]
=
[
∂
f
1
∂
x
1
⋯
∂
f
1
∂
x
n
⋮
⋱
⋮
∂
f
m
∂
x
1
⋯
∂
f
m
∂
x
n
]
J=\left[ \frac{\partial f}{\partial x_1}\ \cdots \ \frac{\partial f}{\partial x_n} \right] =\left[ \begin{matrix} \frac{\partial f_1}{\partial x_1}& \cdots& \frac{\partial f_1}{\partial x_n}\\ \vdots& \ddots& \vdots\\ \frac{\partial f_m}{\partial x_1}& \cdots& \frac{\partial f_m}{\partial x_n}\\ \end{matrix} \right]
J = [ ∂ x 1 ∂ f ⋯ ∂ x n ∂ f ] = ⎣
⎡ ∂ x 1 ∂ f 1 ⋮ ∂ x 1 ∂ f m ⋯ ⋱ ⋯ ∂ x n ∂ f 1 ⋮ ∂ x n ∂ f m ⎦
⎤
上面矩阵是 f f f 关于 x = ( x 1 , … , x m ) x=(x_1,\dots,x_m) x = ( x 1 , … , x m ) 求导。
令 l l l 是一个标量函数,对 f f f 进行求导有:
v
=
[
∂
l
∂
f
1
,
∂
l
∂
f
2
,
⋯
,
∂
l
∂
f
m
]
v=[\frac{\partial l}{\partial f_1},\frac{\partial l}{\partial f_2},\cdots,\frac{\partial l}{\partial f_m} ]
v = [ ∂ f 1 ∂ l , ∂ f 2 ∂ l , ⋯ , ∂ f m ∂ l ]
则 l l l 对 x x x 进行求导有:
d
l
d
x
=
J
∗
v
T
\frac{dl}{dx}=J*v^T
d x d l = J ∗ v T
其中对 x i x_i x i 求偏导有:
∂
l
∂
x
i
=
v
∗
J
i
\frac{\partial l}{\partial x_i}=v*J_i
∂ x i ∂ l = v ∗ J i
其中 J i J_i J i 表示 J J J 矩阵的第 i i i 列。
可以看到, l : R m → R l:R^m\rightarrow R l : R m → R ,从神经网络的例子来理解, f f f 是隐藏层为 m m m 个神经元的个数, l l l 为输出层。
从损失函数理解:标量 l l l 类似于MSE函数将minibatch平均为一个平均loss上.
1.4 具体例子
标量对向量求导:
令 x = [ x 1 , x 2 , x 3 ] T x=[x_1,x_2,x_3]^T x = [ x 1 , x 2 , x 3 ] T , w = [ w 1 , w 2 , w 3 ] T w=[w_1,w_2,w_3]^T w = [ w 1 , w 2 , w 3 ] T , y = w ∗ x + b y=w*x+b y = w ∗ x + b
则偏导数为:
∂
y
∂
x
=
[
∂
y
∂
x
1
,
∂
y
∂
x
2
,
∂
y
∂
x
3
]
=
[
w
1
,
w
2
,
w
3
]
\frac{\partial y}{\partial x}=[\frac{\partial y}{\partial x_1},\frac{\partial y}{\partial x_2},\frac{\partial y}{\partial x_3}]=[w_1,w_2,w_3]
∂ x ∂ y = [ ∂ x 1 ∂ y , ∂ x 2 ∂ y , ∂ x 3 ∂ y ] = [ w 1 , w 2 , w 3 ]
x = torch.tensor([1.0 ,2.0 ,3.0 ], requires_grad=True )
w = torch.tensor([4.0 ,5.0 ,6.0 ], requires_grad=True )
b = 10
y = torch.dot(x,w)+b
y.backward()
print (x.grad)
print (w.grad)
output:
<<<tensor([4. , 5. , 6. ])
<<<tensor([1. , 2. , 3. ])
标量对矩阵求导:
令 X = [ x 11 x 12 x 13 x 21 x 22 x 23 ] X=\left[ \begin{matrix}{} x_{11}& x_{12}& x_{13}\\ x_{21}& x_{22}& x_{23}\\ \end{matrix} \right] X = [ x 11 x 21 x 12 x 22 x 13 x 23 ]
第一次操作:
Y
=
X
+
1
=
[
x
11
+
1
x
12
+
1
x
13
+
1
x
21
+
1
x
22
+
1
x
23
+
1
]
Y=X+1=\left[ \begin{matrix}{} x_{11}+1& x_{12}+1& x_{13}+1\\ x_{21}+1& x_{22}+1& x_{23}+1\\ \end{matrix} \right]
Y = X + 1 = [ x 11 + 1 x 21 + 1 x 12 + 1 x 22 + 1 x 13 + 1 x 23 + 1 ]
第二次操作:
Z
=
[
z
11
z
12
z
13
z
21
z
22
z
23
]
=
[
(
y
11
)
2
(
y
12
)
2
(
y
13
)
2
(
y
21
)
2
(
y
22
)
2
(
y
23
)
2
]
Z=\left[ \begin{matrix}{} z_{11}& z_{12}& z_{13}\\ z_{21}& z_{22}& z_{23}\\ \end{matrix} \right] =\left[ \begin{matrix}{} \left( y_{11} \right) ^2& \left( y_{12} \right) ^2& \left( y_{13} \right) ^2\\ \left( y_{21} \right) ^2& \left( y_{22} \right) ^2& \left( y_{23} \right) ^2\\ \end{matrix} \right]
Z = [ z 11 z 21 z 12 z 22 z 13 z 23 ] = [ ( y 11 ) 2 ( y 21 ) 2 ( y 12 ) 2 ( y 22 ) 2 ( y 13 ) 2 ( y 23 ) 2 ]
第三次操作:
f
=
1
6
s
u
m
(
Z
)
f=\frac{1}{6}sum(Z)
f = 6 1 s u m ( Z )
偏导数为:
∂
f
∂
x
i
j
=
1
6
∗
2
(
∂
(
x
i
j
+
1
)
2
∂
x
i
j
)
=
1
3
(
x
i
j
+
1
)
\frac{\partial f}{\partial x_{ij}}=\frac{1}{6}*2(\frac{\partial(x_{ij}+1)^2}{\partial x_{ij}})=\frac{1}{3}(x_{ij}+1)
∂ x ij ∂ f = 6 1 ∗ 2 ( ∂ x ij ∂ ( x ij + 1 ) 2 ) = 3 1 ( x ij + 1 )
import torch
import torch.autograd
x = torch.tensor([[1.0 ,2.0 ,3.0 ],[4.0 ,5.0 ,6.0 ]], requires_grad=True )
y = x+1
z = y**2
f = torch.mean(z)
f.backward()
print (x.grad)
ouput:
<<<tensor([[0.6667 , 1.0000 , 1.3333 ],
[1.6667 , 2.0000 , 2.3333 ]])
向量/矩阵对向量/矩阵求导:
在pytorch中一般标量对向量或矩阵用的比较多,因为深度学习中最后的loss是输出一个值为标量,因此向量对矩阵求导需要传入一个梯度,即传入一个 l l l 对 f f f 的求导得到的梯度。
令 x = [ x 1 , x 2 , x 3 ] x=[x_1,x_2,x_3] x = [ x 1 , x 2 , x 3 ] , y = x ∗ 2 = [ 2 ∗ x 1 , 2 ∗ x 2 , 2 ∗ x 3 ] y=x*2=[2*x_1,2*x_2,2*x_3] y = x ∗ 2 = [ 2 ∗ x 1 , 2 ∗ x 2 , 2 ∗ x 3 ] ,此时对应上面1.3节有, y = ( f 1 , f 2 , f 3 ) y=(f_1,f_2,f_3) y = ( f 1 , f 2 , f 3 ) ,其中 f i = 2 ∗ x i f_i=2*x_i f i = 2 ∗ x i ,所以有:
d
y
d
x
=
[
∂
f
1
∂
x
1
∂
f
1
∂
x
2
∂
f
1
∂
x
3
∂
f
2
∂
x
1
∂
f
2
∂
x
2
∂
f
2
∂
x
3
∂
f
3
∂
x
1
∂
f
3
∂
x
2
∂
f
3
∂
x
3
]
=
[
2
0
0
0
2
0
0
0
2
]
\frac{dy}{dx}=\left[ \begin{matrix} \frac{\partial f_1}{\partial x_1}&\frac{\partial f_1}{\partial x_2} & \frac{\partial f_1}{\partial x_3}\\ \frac{\partial f_2}{\partial x_1} & \frac{\partial f_2}{\partial x_2} &\frac{\partial f_2}{\partial x_3} \\ \frac{\partial f_3}{\partial x_1}& \frac{\partial f_3}{\partial x_2} & \frac{\partial f_3}{\partial x_3} \\ \end{matrix} \right] =\left[ \begin{matrix} 2&0 & 0\\ 0 & 2 &0 \\ 0& 0 &2\\ \end{matrix} \right]
d x d y = ⎣
⎡ ∂ x 1 ∂ f 1 ∂ x 1 ∂ f 2 ∂ x 1 ∂ f 3 ∂ x 2 ∂ f 1 ∂ x 2 ∂ f 2 ∂ x 2 ∂ f 3 ∂ x 3 ∂ f 1 ∂ x 3 ∂ f 2 ∂ x 3 ∂ f 3 ⎦
⎤ = ⎣
⎡ 2 0 0 0 2 0 0 0 2 ⎦
⎤
传入: v v v 为 l l l 对 f f f 的梯度
v
=
[
0.1
,
1.0
,
0.0001
]
v=[0.1,1.0,0.0001]
v = [ 0.1 , 1.0 , 0.0001 ]
例:
x = torch.randn(3 , requires_grad=True )
y = x * 2
print (y)
v = torch.tensor([0.1 , 1.0 , 0.0001 ], dtype=torch.float )
y.backward(v)
print (x.grad)
output:
<<<tensor([-0.1656 , 1.2321 , -3.1254 ], grad_fn=<MulBackward0>)
<<<tensor([2.0000e-01 , 2.0000e+00 , 2.0000e-04 ])
注意此处获得的梯度是 l l l 对 x x x 的梯度,而 l l l 是没有显式的,因为只是传入了一个 l l l 对 f f f 的梯度。