《动手学深度学习》自动求梯度
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《动手学深度学习》自动求梯度相关的知识,希望对你有一定的参考价值。
在深度学习中,我们经常需要对函数求梯度(gradient)。PyTorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。本节将介绍如何使用autograd包来进行自动求梯度的有关操作。
概念
上一节介绍的Tensor
是这个包的核心类,如果将其属性.requires_grad
设置为True
,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用.backward()
来完成所有梯度计算。此Tensor
的梯度将累积到.grad
属性中。
注意在
y.backward()
时,如果y
是标量,则不需要为backward()
传入任何参数;否则,需要传入一个与y
同形的Tensor
。解释见 2.3.2 节。
如果不想要被继续追踪,可以调用.detach()
将其从追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外,还可以用with torch.no_grad()
将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数(requires_grad=True
)的梯度。
Function
是另外一个很重要的类。Tensor
和Function
互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor
都有一个.grad_fn
属性,该属性即创建该Tensor
的Function
, 就是说该Tensor
是不是通过某些运算得到的,若是,则grad_fn
返回一个与这些运算相关的对象,否则是None。
下面通过一些例子来理解这些概念。
Tensor
创建一个Tensor
并设置requires_grad=True
:
x = torch.ones(2, 2, requires_grad=True)
print(x)
print(x.grad_fn)
输出:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
None
再做一下运算操作:
y = x + 2
print(y)
print(y.grad_fn)
输出:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward>)
<AddBackward object at 0x1100477b8>
注意x是直接创建的,所以它没有grad_fn
, 而y是通过一个加法操作创建的,所以它有一个为<AddBackward>
的grad_fn
。
像x这种直接创建的称为叶子节点,叶子节点对应的grad_fn
是None
。
print(x.is_leaf, y.is_leaf) # True False
再来点复杂度运算操作:
z = y * y * 3
out = z.mean()
print(z, out)
输出:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)
通过.requires_grad_()
来用in-place的方式改变requires_grad
属性:
a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = False
a = ((a * 3) / (a - 1))
print(a.requires_grad) # False
a.requires_grad_(True)
print(a.requires_grad) # True
b = (a * a).sum()
print(b.grad_fn)
输出:
False
True
<SumBackward0 object at 0x118f50cc0>
梯度
因为out
是一个标量,所以调用backward()
时不需要指定求导变量:
out.backward() # 等价于 out.backward(torch.tensor(1.))
我们来看看out
关于x
的梯度
d
(
o
u
t
)
d
x
\\frac{d(out)}{dx}
dxd(out):
print(x.grad)
输出:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
我们令out
为
o
o
o , 因为
o
=
1
4
∑
i
=
1
4
z
i
=
1
4
∑
i
=
1
4
3
(
x
i
+
2
)
2
o=\\frac14\\sum_{i=1}^4z_i=\\frac14\\sum_{i=1}^43(x_i+2)^2
o=41i=1∑4zi=41i=1∑43(xi+2)2
所以
∂
o
∂
x
i
∣
x
i
=
1
=
9
2
=
4.5
\\frac{\\partial{o}}{\\partial{x_i}}\\bigr\\rvert_{x_i=1}=\\frac{9}{2}=4.5
∂xi∂o∣∣xi=1=29=4.5
所以上面的输出是正确的。
数学上,如果有一个函数值和自变量都为向量的函数
y
⃗
=
f
(
x
⃗
)
\\vec{y}=f(\\vec{x})
y=f(x), 那么
y
⃗
\\vec{y}
y 关于
x
⃗
\\vec{x}
x 的梯度就是一个雅可比矩阵(Jacobian matrix): 以上是关于《动手学深度学习》自动求梯度的主要内容,如果未能解决你的问题,请参考以下文章 数值稳定性 梯度爆炸 梯度消失 + 模型初始化和激活函数 动手学深度学习v2 pytorch 线性回归 + 基础优化算法 动手学深度学习v2 pytorch
J
=
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1
⋯
∂
y
m
∂
x
n
)
J=\\left(\\begin{array}{ccc} \\frac{\\partial y_{1}}{\\partial x_{1}} & \\cdots & \\frac{\\partial y_{1}}{\\partial x_{n}}\\\\ \\vdots & \\ddots & \\vdots\\\\ \\frac{\\partial y_{m}}{\\partial x_{1}} & \\cdots & \\frac{\\partial y_{m}}{\\partial x_{n}} \\end{array}\\right)
J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym⎠⎟⎞
而torch.autograd
这个包就是用来计算一些雅克比矩阵的乘积的。例如,如果
v
v
v 是一个标量函数的
l
=
g
(
y
⃗
)
l=g\\left(\\vec{y}\\right)
l=g(y) 的梯度:
v
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
v=\\left(\\begin{array}{ccc}\\frac{\\partial l}{\\partial y_{1}} & \\cdots & \\frac{\\partial l}{\\partial y_{m}}\\end{array}\\right)
v=(∂y1∂l⋯∂ym∂l)
那么根据链式法则我们有
l
l
l 关于
x
⃗
\\vec{x}
x 的雅克比矩阵就为:
v
J
=
(
∂
l
∂
y
1
⋯
∂
l
∂
y
m
)
(
∂
y
1
∂
x
1
⋯
∂
y
1
∂
x
n
⋮
⋱
⋮
∂
y
m
∂
x
1