关于Pytorch中autograd的若干(踩坑)总结

目录

 

关于Variable和Tensor

叶子节点leaf

autograd操作

Tensor.data和Tensor.detach()

autograd.grad和hook


关于Variable和Tensor

旧版本的Pytorch中,Variable是对Tensor的一个封装;在Pytorch大于v0.4的版本后,Varible和Tensor合并了,意味着Tensor可以像旧版本的Variable那样运行,当然新版本中Variable封装仍旧可以用,但是对Varieble操作返回的将是一个Tensor。

import torch as t
from torch.autograd import Variable

a = t.ones(3,requires_grad=True)
print(type(a))
#输出:<class 'torch.Tensor'>

a=Variable(a)
print(type(a))
#输出仍旧是:<class 'torch.Tensor'>

print(a.volatile)
#输出:__main__:1: UserWarning: volatile was removed (Variable.volatile is always False)
a.volatile=True
print(a.volatile)
#输出:__main__:1: UserWarning: volatile was removed (Variable.volatile is always False)
#现版本pytorch中移除了volatile这个属性,即volatile总是false

叶子节点leaf

对于那些不是任何函数(Function)的输出,由用户创建的节点称为叶子节点,叶子节点的grad_fn为None。

import torch as t
a = t.ones(3,requires_grad=True)
b = t.rand(3,requires_grad=True)
a,a.is_leaf
#输出:(tensor([1., 1., 1.], requires_grad=True), True)
b
#输出:(tensor([0.4254, 0.8763, 0.5901], requires_grad=True), True)

c = a*b
c.is_leaf
#输出:False.说明c不是叶子节点
a.grad_fn
#输出:None.叶子节点的grad_fn为None.
c.grad_fn
#输出:<MulBackward0 object at 0x7fa45c406278> 

autograd操作

首先Tensor是默认不需要求导的,即requires_grad默认为False。

import torch as t
a = t.ones(3)
a.requires_grad
#输出:False.Tensor默认不需要求导

如果某一个节点requires_grad被设置为True,那么所有依赖它的节点requires_grad都为True。

import torch as t

a = t.ones(3)
b = t.ones(3,requires_grad=True)
b.requires_grad
#输出:True
c = a + b
c.requires_grad
#输出:True.虽然c没有指定需要求导,然是c依赖于b,而b需要求导,所以c.requires_grad=True

只有scalar才能进行反向backward()操作,并且backward对于叶节点的grad的是累加的。当只进行计算操作不做backward,叶节点的grad不发生变化。

更正一下,并不是只有scaler才能进行backward操作,矩阵和向量也可以,只不过backward()中要添加对应维度的参数。

可以参考Pytorch中的自动求导函数backward()所需参数含义

import torch as t

a = t.ones(3,requires_grad=True)
b = t.rand(3,requires_grad=True)
a,b
#输出:(tensor([1., 1., 1.], requires_grad=True), 
#tensor([0.9373, 0.0556, 0.6426], requires_grad=True))
c = a*b
c
#输出:tensor([0.9373, 0.0556, 0.6426], grad_fn=<MulBackward0>)
c.backward(retain_graph=True)
#输出:RuntimeError: grad can be implicitly created only for scalar outputs
#只有数值scalar才能进行backward操作
d = c.sum()
d.backward(retain_graph=True)
#retain_graph=True是为了保存中间缓存,否则再次backward的时候会报错
a.grad
#输出:tensor([0.9373, 0.0556, 0.6426])
b.grad
#输出:tensor([1., 1., 1.])
#backward后a和b的grad产生了数值
e = c.sum()
e.backward(retain_graph=True)
b.grad
#输出:tensor([2., 2., 2.]).b的grad进行了两次backward后进行了累加.
f = c.sum()
b.grad
#输出:tensor([2., 2., 2.])
#只进行计算不backward,梯度不更新




Tensor.data和Tensor.detach()

如过tensor的数值需要参与计算又不想参与到计算图的更新中,计算的时候可以用tensor.data,这样既能利用tensor的数值,又不会更新梯度。

import torch as t

a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)

a.data.requires_grad
#输出:False. a.data独立于计算图之外

c = a.data * b.data
d = c.sum()
d.backward()
#输出:RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
#因为独立于计算图之外,requires_grad = False所以不能backward()

当tensor.data被修改的时候,tensor也会同步的被修改,此时用该tensor进行计算并backward的时候梯度的值就不再准确了,因为tensor已经被修改了!

import torch as t

a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()
a.data.sigmoid_()
#输出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311]])
#虽然对a.data进行sigmoid操作,但是a的值已经被修改了.
d.backward()
b.grad
#输出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311]])
#b的grad不准了,本来应该都是1!

为了避免因为对tensor.data修改导致grad变化的情况,可以利用tensor.detach,同样可以保证tensor不参与到计算图当中,但是当tensor的值被改变的时候,再进行backward就会报错而不会有先前的因为tensor的值被改变而导致不准的情况了。

import torch as t

a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a * b
d = c.sum()
a_ = a.detach()
a_.sigmoid_()
a
#输出:tensor([[0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311],
#        [0.7311, 0.7311, 0.7311, 0.7311]], requires_grad=True)
#a的值已经发生了改变
d.backward()
#报错:RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
#因为a的值被修改了,所以不能再进行backward

推荐用tensor.detach的方式而不是tensor.data的方式,因为这样更保险!

autograd.grad和hook

在计算的时候有时候我们可能会用到非叶节点的grad,但是非叶节点的grad在backward之后就会被自动清空:

import torch as t

a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()
d.backward()
a.grad
#输出:tensor([[0.3114, 0.3017, 0.8461, 0.6899],
#        [0.3878, 0.8712, 0.2406, 0.7396],
#        [0.6369, 0.0907, 0.4984, 0.5058]])
c.grad
#输出:None
#c为非叶子节点,计算后被清空

可以用autograd.grad和hook来处理这种情况:

#利用autograd.grad获取中间节点梯度
t.autograd.grad(d,c)
#输出:(tensor([[1., 1., 1., 1.],
#        [1., 1., 1., 1.],
#        [1., 1., 1., 1.]]),)
#利用hook获取中间节点梯度
import torch as t

a = t.ones(3,4,requires_grad=True)
b = t.rand(3,4,requires_grad=True)
c = a*b
d = c.sum()

def print_grad(grad):
    print(grad)

#给c注册hook
c_hook = c.register_hook(print_grad)

d.backward()
#输出:tensor([[1., 1., 1., 1.],
#        [1., 1., 1., 1.],
#        [1., 1., 1., 1.]])

#移除钩子
c_hook.remove()

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值