自动求梯度
1. 函数对自变量x求梯度–ax^2+b
torch提供了简便的求梯度方法:带入变量值x,求解目标函数f(x),将目标函数的值反向传播f.backward(),就能计算对应变量的导数。
- 以二次函数为例: f ( x ) = a x 2 + b f(x)=ax^2+b f(x)=ax2+b
- 其中a,b是已知的参数,对x 求导: f ′ ( x ) = 2 a x f'(x)=2ax f′(x)=2ax
- 当 a = 1 , x = 1 时 a=1,x=1时 a=1,x=1时: f ′ ( x ) = 2 f'(x)=2 f′(x)=2
import torch
from torch.autograd import Variable
def f(x):
return x**2
x1 = Variable(torch.Tensor([1]),requires_grad=True) # 使用variable封装待求导的参数
print("初始化", x1.grad)
y=f(x1)
y.backward()
print("一次传播", x1.grad)
x1.grad.data.zero_()
print("梯度置零", x1.grad)
y=f(x1)
y.backward()
y=f(x1) # #如果x1.grad 每次计算前梯度不归零,那么当前的梯度会与原来的梯度累加
y.backward() # #每次loss 反向传播前需要将梯度设置为0
print("二次传播", x1.grad)
输出
初始化 None
一次传播 tensor([2.])
梯度置零 tensor([0.])
二次传播 tensor([4.])
2. 网络对参数w求梯度- loss(w,x)
神经网络训练:通过调整神经网络参数使网络输出尽可能接近于想要的对象。具体通过损失函数的梯度下降实现的,即损失函数对网络参数求梯度,利用该梯度调整网络参数。
模型的损失函数实际是输入x 和模型参数w 的函数:loss(w,x)。依据part1demo流程,随机初始化w后,输入观测数据x(图像 或者 其他需要分类或者回归的数据)。loss(w, x).backward()就可以求出对应的网络参数w的梯度。再利用各种优化算法(SGD,admn)更新各个网络参数w即可。
pytorch 网络训练的流程
- 搭建好网络:net()
- 数据x输入网络:y=net(x,w)
- 网络输出y与标签label计算标量损失函数:loss(y,label,w)
- 损失函数loss()反向传播:loss.backward()->通过自动求导Adagrad实现
- 网络参数更新:net.parameters().step()->通过优化器实现
注意事项:在每次损失函数反向传播计算梯度时,原本的参数梯度必须设置为0。否则,各个参数的梯度会累加之前的梯度。网络参数梯度置0:
optimizer=torch.optim.SGD(net.parameters(),lr=0.001,momentum=0.1)
optimizer.zero_grad()
loss.backward()
3. 自动求梯度的底层支持–torch.autograd
torch.autograd 是一套自动求导引擎,能够根据输入和前向传播过程自动构建计算图,并执行反向传播. 计算图是深度学习框架的核心。
[本节为陈云<<深度学习框架-pytorch 入门与实践>>一书第3.2章的内容autograd 读书笔记.]
pytorch 中 autograd模块 实现了 计算图 相关功能. autograd 的核心数据结构Variable。
pytorch 中 autograd模块 底层采用 计算图 实现梯度计算。
3.1 Variable
Variable中封装了以下三个部分内容
- data : 保存数据tensor
- grad : 保存data对应的梯度,形状与data一致,也是一个Variable
- grad_fn : 指向一个Function,记录Variable操作历史,如果一个变量是由用户创建的,则他为叶子节点,对应的grad_fn为None.
3.1.1 Variable构造函数
Variable的构造函数–需要传入tensor,还有两个可选参数:
- requires_grad : bool 是否需要对该variable进行求导
- volatile : bool 为True , 那么基于该variable 构建的图都不会求导,转为推理阶段设计.
volatile优先级别比requires_grad高。在计算路径上同时存在requires_grad=True 和 volatile=True 的变量,那么其后的通过计算得出的变量的requires_grad 为False.
3.1.2 Variable链式求导–backward()
Variable支持大部分的tensor操作,但不支持部分inplace函数。如果想要计算各个Variable的梯度,只需要调用根节点variable的backward方法,autograd 会自动沿计算图反向传播,计算每一个叶子节点的梯度.
Variable.backward(grad_variables=None,retain_graph=None,create_graph=None)
-
grad_variables : 相当与链式法则 ∂ 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中的 ∂ z ∂ y \frac{\partial z}{\partial y} ∂y∂z 。y.backward(grad_y) 等价于 z.backward()
-
retain_graph : 反向传播中会缓存一些中间结果,反向传播结束后缓存被清空。可以通过指定这个参数不清空缓存,用于多次反向传播。
-
create_graph : 反向传播过程中再次构建计算图,通过backward of backward实现高阶求导.
3.1.3 Variable反向传播函数–grad_fn
variable变量的grad_fn 属性可以用来查看反向传播函数, next_functions保存grad_fn的输入,是一个tuple。每个变量在图中的位置可以通过grad_fn 属性在图中的位置推测得到。每一个前向传播函数都有一个与之对应的反向传播函数,用来计算各个输入变量的梯度;这些函数以Backward结尾。
x=Variable(torch.ones(1))
b=Variable(torch.rand(1,requires_grad=True))
w=Variable(torch.rand(1),requires_grad=True)
y=w*x
z=y+b
z.grad_fn # 查看z的反向传播函数
Out[16]: <AddBackward0 at 0x7f84b0a0e3c8>
z.grad_fn.next_functions # z 反向传播函数的输入,一个为y.grad_fn(为MulBackward0),另一个是b.grad_fn(为none)
Out[17]: ((<MulBackward0 at 0x7f84903a9588>, 0), (None, 0))
y.grad_fn
Out[27]: <MulBackward0 at 0x7f84903a9588>
y.grad_fn.next_functions
Out[19]: ((<AccumulateGrad at 0x7f84b0a0ee10>, 0), (None, 0))
z.backward(retain_graph=True) # 保留中间结果
w.grad
Out[23]: tensor([1.])
z.backward(retain_graph=True) # 保留中间结果, 才能二次传播
w.grad
Out[26]:tensor([2.]) # 这个为2 不是保留中间结果的原因,而是两次梯度传播
3.2 计算图
pytorch 中 autograd模块 底层采用 计算图 实现梯度计算。计算图是一种特殊的有向无环图 , 用于记录算子 与 变量 之间的关系。
在pytorch 中autograd模块会随着用户操作,记录生成当前 variable 的所有操作。以此建立一个有向无环图(每个变量都有一个有向无环图?还以一个计算过程有一个?应该是一个计算过程有一个图)。用户每进行一次操作,计算图就会变化。对应的变化会通3.1.3 的grad_fn 记录
3.2.1 动态创建计算图
pytorch 的计算图在每次前向传播的时候都是从头开始构建的 , 所以能够利用python的控制语句(for,if) 根据需求 创建动态图.
少一个例子
3.2.2 非叶子节点的梯度–autograd.grad 与 hook
在反向传播的过程中,非叶子节点的导数在计算完成之后即被清空,若想查看这些变量的梯度,有两种方法:autograd.grad 与 hook。
torch.autograd.grad()
In [28]: x=Variable(torch.ones(3),requires_grad=True)
In [30]: w=Variable(torch.ones(3),requires_grad=True)
In [31]: y=x*w
In [32]: z=y.sum()
In [33]: z.backward()
In [34]: (x.grad,w.grad,y.grad)
Out[34]: (tensor([1., 1., 1.]), tensor([1., 1., 1.]), None) # y.grad的梯度计算完被释放掉了
# 使用torch.autograd.grad()计算中间节点的梯度
In [35]: torch.autograd.grad(z,y) # 求Z对Y的梯度
Out[35]: (tensor([1., 1., 1.]),)
hook 呢?🤔️
3.3 复杂自建函数,自动求导支持
自己写一个复杂函数,不支持自动求导。这种情况下,定义此复杂函数为Function(), 继承自,Torch.autograd.Function。实现前向与反向传播 的代码。使用Function.apply(variable)调用实现Function。
详见:p95