Pytorch:autograd 自动求导
神经网络离不开大量的梯度求导,若是交给人为计算,这将是十分耗时的一件事情。在pytorch中,autograd自动求导系统能有效解决这一问题。
torch.autograd
torch.autograd.backward()
(标量或向量的backward()方法实则也是调用了autograd.backward()方法)
功能:自动求取梯度
tensors:用于求导的张量,如loss
retain_graph:保存计算图(pytorch采用的是动态图机制,所以在一次反向传播结束时会释放掉计算图的内存,如果需要继续使用计算图,也即需要再次运行反向传播,需使retain_graph为true,否则再次运行时会报错)
create_graph:创建导数计算图,用于高阶求导
grad_tensors:多梯度权重(当使用多个损失函数作为loss时,即loss不是标量,而是向量,需要给每个函数设置一定的权重比例)
举例grad_tensors:(注意,这里为张量的backward(),其 gradient 对应传入 torch.autograd.backward()中的grad_tensors)
w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)
a = torch.add(w, x) # retain_grad()
b = torch.add(w, 1)
y0 = torch.mul(a, b) # y0 = (x+w) * (w+1)
y1 = torch.add(a, b) # y1 = (x+w) + (w+1) dy1/dw = 2
loss = torch.cat([y0, y1], dim=0) # [y0, y1]
grad_tensors = torch.tensor([1., 2.])
loss.backward(gradient=grad_tensors) # gradient 传入 torch.autograd.backward()中的grad_tensors
print(w.grad) #运行结果为 tensor([9.]) 大家可以自己演算验证一下
torch.autograd.grad()
功能: 求取梯度
outputs:用于求导的张量,如loss,即导数分式的分子
inputs:需要梯度的张量,如w,即导数分式的分母
retain_graph:保存计算图
create_graph:创建导数计算图,用于高阶求导
grad_tensors:多梯度权重
下为一个二阶求导的例子:(因为需要再次求导,所以create_graph需为True)
x = torch.tensor([3.], requires_grad=True)
y = torch.pow(x, 2) # y = x**2
grad_1 = torch.autograd.grad(y, x, create_graph=True) # grad_1 = dy/dx = 2x = 2 * 3 = 6
print(grad_1)
grad_2 = torch.autograd.grad(grad_1[0], x) # grad_2 = d(dy/dx)/dx = d(2x)/dx = 2
print(grad_2)
关于autograd的一些注意的地方:
1.梯度不会自动清零,换句话说,梯度会默认进行累加
简单见解:
由于PyTorch的动态图和autograd机制使得其非常灵活,这也意味着你可以得到对一个张量的梯度,然后再次用该梯度进行计算,然后又可重新计算对新操作的梯度,对于何时停止前向操作并没有一个确定的点。所以自动设置梯度为0比较棘手,因为你不知道什么时候一个计算会结束以及什么时候又会有一个新的开始。
默认累加的好处是当在多任务中对前面共享部分的tensor进行了多次计算操作后,调用不同任务loss的backward,那些tensor的梯度会自动累加,缺点是当你不想先前的梯度影响到当前梯度的计算时需要手动清零。
具体解释可以参考另一篇文章:https://blog.csdn.net/DragonGirI/article/details/107381375
所以需要手动清零,使用w.grad.zero_()即可清零
2.依赖与叶子结点的结点,require_grad默认为true
3.叶子结点不可执行in-place操作
in-place表示在原内存上修改数据的操作,可以通过下面的例子简单了解一下
a = torch.ones((1, )) # 结果 2079242876320 tensor([1.]) print(id(a), a) # a = a + torch.ones((1, )) # 结果 2079241269080 tensor([2.]) # print(id(a), a) a += torch.ones((1, )) # 结果 2079242876320 tensor([2.]) print(id(a), a)
可见a=a+1的赋值方式不是in-place,而a+=1为in-place方式
叶子结点为什么不能in-place就是因为叶子结点的值在反向传播时需要被用于相关联张量的梯度求导,改变值会在反向传播时引起歧义冲突。改变可以用w.data.add_()