Pytorch所有的神经网络的核心是autograd库. 我们先简单了解一下, 然后训练第一个神经网络.
autograd
包提供了Tensor所有操作的自动求导(差分). 这是一个运行时定义(define-by-run)的架构, 意味着你的后向传播是根据你的代码怎样运行来定义的. 每一次的迭代都可能不同.
让我们看几个例子.
torch.Tensor
是这个包的核心类. 如果将其属性attribute.requires_grad
设为True
, 它就会记录所有的操作. 计算完成后, 调用.backward()
, 会自动计算所有的梯度(gradients). 这个tensor的梯度会累加到.grad
属性中.
为了阻止tensor记录历史, 可以调用.detach()
将它和计算历史脱钩, 并阻止其下一步计算继续记录操作历史.
为了防止追踪历史(和使用存储), 你可以将代码块放在torch.no_grad():
中. 这在评估一个模型时特别有用, 因为模型中有可训练的参数, 他们的requires_grad
是True
. 而在评估过程中不需要计算梯度.
还有一个类在autograd实现中特别重要, Function
.
Tensor
和Function
内部联系并且建立一张非环形的图(acyclic graph). 这张图将运算的全部历史全部编码. 每一个tensor都有一个.grad_fn
指向创建此tensor的Function
. 除非Tensor是用户创建的, 其grad_fn
是None
.
如果你想计算导数, 你可以在一个Tensor上调用.backward()
. 如果Tensor是个标量(也就是只有一个元素), 你不需要指定任何参数. 但是如果它有不止一个元素, 你需要指定一个匹配大小的tensor作为梯度参数.
import torch
创建一个requires_grad=True
的tensor用来追踪计算历史.
x = torch.ones(2, 2, requires_grad=True)
print(x)
Out:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
做一个tensor运算:
y = x + 2
print(y)
Out:
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y
作为一个操作的结果而被创建, 所以它有grad_fn
.
print(y.grad_fn)
Out:
<AddBackward0 object at 0x7f6a7f893e10>
在y上做更多运算.
z = y * y * 3
out = z.mean()
print(z, out)
Out:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... )
原址改变现有Tensor的requires_grad标志位. 这个输入标志位默认是False
.
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)
Out:
False
True
<SumBackward0 object at 0x7f6a7f8a2ba8>
现在开始反向传播. 因为out
只有一个标量, out.backward()
等效于out.backward(torch.tensor(1.))
.
out.backward()
打印梯度 d(out)/dx
print(x.grad)
Out:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
你应该得到一个4.5的矩阵. 令tensorout
为" o o o". 我们得到 o = 1 2 ∑ i z i o=\frac{1}{2}\sum_iz_i o=21∑izi, z i = 3 ( x i + 2 ) 2 z_i=3(x_i+2)^2 zi=3(xi+2)2, z i ∣ x i = 1 = 27 z_i|_{x_i=1}=27 zi∣xi=1=27. 所以, ∂ 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}|_{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的梯度就是一个Jocobian 矩阵:
J = ( ∂ y 1 ∂ x 1 . . . ∂ y 1 ∂ x n ⋮ ⋯ ⋮ ∂ y m ∂ x 1 . . . ∂ y m ∂ x n ) J=\begin{pmatrix} \frac{\partial y_1}{\partial x_1} & ...&\frac{\partial y_1}{\partial x_n} \\ \vdots &\cdots&\vdots\\ \frac{\partial y_m}{\partial x_1} & ...&\frac{\partial y_m}{\partial x_n} \end{pmatrix} J=⎝⎜⎛∂x1∂y1⋮∂x1∂ym...⋯...∂xn∂y1⋮∂xn∂ym⎠⎟⎞
一般地, torch.autograd
是一个计算向量和Jacobian矩阵乘积的引擎. 也就是, 给定一个向量 v = ( v 1 , v 2 , ⋯ , v m ) T v=(v_1, v_2, \cdots, v_m)^T v=(v1,v2,⋯,vm)T, 计算乘积 v T ⋅ J v^T\cdot J vT⋅J. 如果 v v v是一个标量函数 l = g ( y ⃗ ) l=g(\vec y) l=g(y)的梯度, 也就是, v = ( ∂ l ∂ y 1 , ⋯ , ∂ l ∂ y 1 ) v=(\frac{\partial l}{\partial y_1}, \cdots, \frac{\partial l}{\partial y_1}) v=(∂y1∂l,⋯,∂y1∂l), 那么根据链式法则(chain rule), 向量和Jacobian矩阵乘积的结果就是 l l l对 x ⃗ \vec{x} x的梯度:
J T ⋅ v = ( ∂ y 1 ∂ x 1 . . . ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 . . . ∂ y m ∂ x n ) ( ∂ l ∂ y 1 ⋮ ∂ l ∂ y m ) = ( ∂ l ∂ x 1 ⋮ ∂ l ∂ x n ) J^T\cdot v=\begin{pmatrix} \frac{\partial y_1}{\partial x_1} & ...&\frac{\partial y_1}{\partial x_n} \\ \vdots &\ddots&\vdots\\ \frac{\partial y_m}{\partial x_1} & ...&\frac{\partial y_m}{\partial x_n} \end{pmatrix} \begin{pmatrix} \frac{\partial l}{\partial y_1} \\ \vdots \\ \frac{\partial l}{\partial y_m} \end{pmatrix}= \begin{pmatrix} \frac{\partial l}{\partial x_1} \\ \vdots \\ \frac{\partial l}{\partial x_n} \end{pmatrix} 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\cdot v所输出的列向量)
向量-Jacobian矩阵乘积的性质可以很方便的给非标量输出的模型传递外部梯度.
现在看一个向量-Jacobian矩阵乘积的例子:
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
Out:
tensor([ 367.6527, -1195.3390, -546.3927], grad_fn=<MulBackward0>)
Now in this case y is no longer a scalar. torch.autograd could not compute the full Jacobian directly, but if we just want the vector-Jacobian product, simply pass the vector to backward as argument:
这里y
不是标量. torch.autograd
不能直接计算全部的Jacobian, 但是如果需要向量-Jacobian矩阵乘积, 简单地传递一个向量参数给backward
即可:
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
Out:
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
设置.requires_grad=True
, 阻止autograd
追踪Tensor的历史. 将代码块放在with torch.no_grad():
里:
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
Out:
True
True
False
或者使用detach()
获取一个内容相同但不需要求梯度的新tensor:
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
Out:
True
False
tensor(True)
稍后阅读:
autograd.Function
的文档在:https://pytorch.org/docs/stable/autograd.html#function
Total running time of the script: ( 0 minutes 3.509 seconds)
公式编辑语法: https://katex.org/docs/supported.html
简单来说, 需要继承Function基类并override forward()和backward()函数. 这两个函数都是static的.
官方文档:
https://pytorch.org/docs/stable/notes/extending.html
补充解释:
https://blog.csdn.net/Hungryof/article/details/78346304
pytorch0.4之前版本有Variable和Tensor的区别. 现在两者统一, 使用Tensor即可.
torch.Tensor
有几个属性/方法和autograd相关:
官方文档:
https://pytorch.org/docs/stable/autograd.html
autograd机制能够记录作用于Tensor上的所有操作,生成一个动态计算图。图的叶子节点是输入的数据,根节点是输出的结果。当在根节点调用.backward()的时候就会从根到叶应用链式法则计算梯度。默认情况下,只有.requires_grad和is_leaf两个属性都为True的节点才会被计算导数,并存储到grad中。
我们已经知道PyTorch使用动态计算图(DAG)记录计算的全过程,那么DAG是怎样建立的呢?一些博客认为DAG的节点是Tensor(或说Variable),这其实是不准确的。DAG的节点是Function对象,边表示数据依赖,从输出指向输入。因此Function类在PyTorch自动微分中位居核心地位,但是用户通常不会直接去使用,导致人们对Function类了解并不多。
每当对Tensor施加一个运算的时候,就会产生一个Function对象,它产生运算的结果,记录运算的发生,并且记录运算的输入。Tensor使用.grad_fn属性记录这个计算图的入口。反向传播过程中,autograd引擎会按照逆序,通过Function的backward依次计算梯度。
https://www.cnblogs.com/cocode/p/10746347.html
backward函数本身没有返回值,它计算出来的梯度存放在叶子节点的grad属性中。PyTorch文档中提到,如果grad属性不为空,新计算出来的梯度值会直接加到旧值上面。
为什么不直接覆盖旧的结果呢?这是因为有些Tensor可能有多个输出,那么就需要调用多个backward。叠加的处理方式使得backward不需要考虑之前有没有被计算过导数,只需要加上去就行了,这使得设计变得更简单。因此我们用户在反向传播之前,常常需要用zero_grad函数对导数手动清零,确保计算出来的是正确的结果。