autograd 是 pytorch 构建神经网络的核心
- 当把tensor的属性 .requires_grad置为True时,pytorch则会追踪在这个tensor上的接下来的所有操作。然后当使用.backward()时,将会自动求导。所有分支上的梯度值将会被累计在 .grad中
- 可以使用with torch.no_grad(): 来暂停梯度记录(可以用在evaluation中)
- 用.backward()求导
tensor创建
例 1
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)
y = x + 2
print(y)
结果:这里x的requires_grad 设置成为 True
,因此pytorch将会记录x的所有相关的计算流程,方便求微分
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
y = x + 2
print(y)
由于运算过程中x 已经设为requires_grad, 在进行 y的赋值运算时 x执行了加法运算。而在y中的grad_fn不为空,说明已经包含了当执行back propagation 时,y所对应的backward函数,这个函数存在y.grad_fn中。
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y由x与2相加生成,查看一下y.grad_fn,发现x与2对应的backward 函数被打包成名为next_function的tuple
类,其中第一项对应的即为x的backward相关函数,第二项对应的即为2对应的相关函数,由于对实数求导得0,因此不含backward函数。

z = y * y * 3
out = z.mean()
print(z, out)
例 2
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
结果:可知在不指定a的时候,默认a的requires_grad 为 False
,当声明a后,可以通过in-place操作.requires_grad_( ... )
来改变状态
False
True
<SumBackward0 object at 0x7f341c47cd68>
自动求梯度
例 1 结果为标量时
x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
#由于最终的结果只是单个标量
#out.backward()等于 out.backward(torch.tensor(1.))
out.backward()
print(x.grad)
结果:对out进行backprop,out关于x的导数存在了x的.grad中,所以直接print(x.grad)
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
整个公式:
z
i
=
3
(
x
i
+
2
)
2
o
=
1
4
∑
i
z
i
z_i = 3(x_i+2)^2 \\ o = \frac{1}{4}\sum_i z_i
zi=3(xi+2)2o=41i∑zi
则 z i ∣ x i = 1 = 27 z_i\bigr\rvert_{x_i=1} = 27 zi∣∣xi=1=27,对 o o o求 x x x的偏导 ∂ o ∂ x i = 3 2 ( x i + 2 ) \frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2) ∂xi∂o=23(xi+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,其实本质上这里的求导是一种对应的关系,标量的结果对应参数x中每个值的相应的梯度.
数学上,若有向量值函数 y ⃗ = f ( x ⃗ ) \vec{y}=f(\vec{x}) y=f(x),那么 y ⃗ \vec{y} y 相对于 x ⃗ \vec{x} x 的梯度是一个雅可比矩阵:
J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y m ∂ x 1 ⋮ ⋱ ⋮ ∂ y 1 ∂ x n ⋯ ∂ y m ∂ x n ) J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\ \vdots & \ddots & \vdots\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right) J=(∂x1∂y1⋯∂x1∂ym ⋮⋱⋮ ∂xn∂y1⋯∂xn∂ym)
通常来说,torch.autograd 是计算雅可比向量积的一个“引擎”。也就是说,给定任意向量 v = ( v 1 v 2 ⋯ v m ) T v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T} v=(v1v2⋯vm)T,计算乘积 v T ⋅ J v^{T}\cdot J vT⋅J如果 v v v 恰好是一个标量函数 l = g ( y ⃗ ) l=g\left(\vec{y}\right) l=g(y) 的导数,即 v = ( ∂ l ∂ y 1 ⋯ ∂ l ∂ y m ) T v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T} v=(∂y1∂l⋯∂ym∂l)T,那么根据链式法则,雅可比向量积应该是 l l l 对 x ⃗ \vec{x} x 的导数:
J T ⋅ v = ( ∂ y 1 ∂ x 1 ⋯ ∂ y m ∂ x 1 ⋮ ⋱ ⋮ ∂ y 1 ∂ x n ⋯ ∂ y m ∂ x n ) ( ∂ l ∂ y 1 ⋮ ∂ l ∂ y m ) = ( ∂ l ∂ x 1 ⋮ ∂ l ∂ x n ) J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\ \vdots & \ddots & \vdots\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\ \vdots\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\ \vdots\ \frac{\partial l}{\partial x_{n}} \end{array}\right) JT⋅v=(∂x1∂y1⋯∂x1∂ym ⋮⋱⋮ ∂xn∂y1⋯∂xn∂ym)(∂y1∂l ⋮ ∂ym∂l)=(∂x1∂l ⋮ ∂xn∂l)
注意:行向量的 v T ⋅ J v^{T}\cdot J vT⋅J也可以被视作列向量的 J T ⋅ v J^{T}\cdot v JT⋅v
摘自:pytorch中文github
雅可比向量积的这一特性使得将外部梯度输入到具有非标量输出的模型中变得非常方便。
例 2 结果不为标量时
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
tensor([1444.3817, -340.2753, -480.7858], grad_fn=<MulBackward0>)
此时torch.autograd不能直接计算完整的Jacobian矩阵(就是pytorch能力不足).但此时如果想计算vector-Jacobian乘积,只需要把向量加到backward中作为参数即可.
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
tensor([4.0960e+02, 4.0960e+03, 4.0960e-01])
此处我觉得官方的例子不好,本身大家就不太懂,还非得生成随机的tensor,还加入不确定次数的2的累积.让人完全不知道算出来的x.grad代表着什么
重新举个例子
x = torch.tensor([1.,2.,3.], requires_grad=True)
y = x * 2
print(y)
v = torch.tensor([2,1,1],dtype=torch.float)
y.backward(v)
print(x.grad)
tensor([2., 4., 6.], grad_fn=<MulBackward0>)
tensor([4., 2., 2.])
结果:可见这里y本身可以看成三个x与2相乘的结果的集合而成的向量. 在这里的v相当于给了结果向量一个权重,生成对应的关于x的导数.如果想要正常结果,直接把输入的向量v改成[1,1,1]就好了