浅谈Pytorch中autograd的若干(踩坑)总结

关于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()中要添加对应维度的参数。

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()

补充:关于Pytorch中autograd和backward的一些笔记

1 Tensor

Pytorch中所有的计算其实都可以回归到Tensor上,所以有必要重新认识一下Tensor。

如果我们需要计算某个Tensor的导数,那么我们需要设置其.requires_grad属性为True。为方便说明,在本文中对于这种我们自己定义的变量,我们称之为叶子节点(leaf nodes),而基于叶子节点得到的中间或最终变量则可称之为结果节点。

另外一个Tensor中通常会记录如下图中所示的属性:

data: 即存储的数据信息

requires_grad: 设置为True则表示该 Tensor 需要求导

grad: 该 Tensor 的梯度值,每次在计算 backward 时都需要将前一时刻的梯度归零,否则梯度值会一直累加,这个会在后面讲到。

grad_fn: 叶子节点通常为 None,只有结果节点的 grad_fn 才有效,用于指示梯度函数是哪种类型。

is_leaf: 用来指示该 Tensor 是否是叶子节点。

举例:

x = torch.rand(3, requires_grad=True)
y = x ** 2
z = x + x
print(
    'x requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
        .format(x.requires_grad, x.is_leaf, x.grad, x.grad_fn)
)
print(
    'y requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
        .format(y.requires_grad, y.is_leaf, y.grad, y.grad_fn)
)
print(
    'z requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
        .format(z.requires_grad, z.is_leaf, z.grad, z.grad_fn)
)

运行结果:

x requires grad: True, is leaf: True, grad: None, grad_fn: None.

y requires grad: True, is leaf: False, grad: None, grad_fn: <PowBackward0 object at 0x0000021A3002CD88>.

z requires grad: True, is leaf: False, grad: None, grad_fn: <AddBackward0 object at 0x0000021A3002CD88>.

2 torch.autograd.backward

如下代码:

x = torch.tensor(1.0, requires_grad=True)
y = torch.tensor(2.0, requires_grad=True)
z = x**2+y
z.backward()
print(z, x.grad, y.grad)
>>> tensor(3., grad_fn=<AddBackward0>) tensor(2.) tensor(1.)

当 z 是一个标量,当调用它的 backward 方法后会根据链式法则自动计算出叶子节点的梯度值。

但是如果遇到 z 是一个向量或者是一个矩阵的情况,这个时候又该怎么计算梯度呢?这种情况我们需要定义grad_tensor来计算矩阵的梯度。

在介绍为什么使用之前我们先看一下源代码中backward的接口是如何定义的:

torch.autograd.backward(
        tensors,
        grad_tensors=None,
        retain_graph=None,
        create_graph=False,
        grad_variables=None)

tensor: 用于计算梯度的 tensor。也就是说这两种方式是等价的:torch.autograd.backward(z) == z.backward()

grad_tensors: 在计算非标量的梯度时会用到。他其实也是一个tensor,它的shape一般需要和前面的tensor保持一致。

retain_graph: 通常在调用一次 backward 后,pytorch 会自动把计算图销毁,所以要想对某个变量重复调用 backward,则需要将该参数设置为True

create_graph: 当设置为True的时候可以用来计算更高阶的梯度

grad_variables: 这个官方说法是 grad_variables' is deprecated. Use 'grad_tensors' instead. 也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了。

pytorch设计了grad_tensors这么一个参数。它的作用相当于“权重”。

先看一个例子:

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward()
>>> ...
RuntimeError: grad can be implicitly created only for scalar outputs

上面的报错信息意思是只有对标量输出它才会计算梯度,而求一个矩阵对另一矩阵的导数束手无策。

x = torch.ones(2,requires_grad=True)
z = x + 2
z.sum().backward()
print(x.grad)
>>> tensor([1., 1.])

而grad_tensors这个参数就扮演了帮助求和的作用。

换句话说,就是对 Z 和一个权重张量grad_tensors进行 hadamard product 后求和。这也是 grad_tensors 需要与传入的 tensor 大小一致的原因。

x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward(torch.ones_like(z)) # grad_tensors需要与输入tensor大小一致
print(x.grad)
>>> tensor([1., 1.])

3 torch.autograd.grad

torch.autograd.grad(
        outputs,
        inputs,
        grad_outputs=None,
        retain_graph=None,
        create_graph=False,
        only_inputs=True,
        allow_unused=False)

看了前面的内容后再看这个函数就很好理解了,各参数作用如下:

outputs: 结果节点,即被求导数

inputs: 叶子节点

grad_outputs: 类似于backward方法中的grad_tensors

retain_graph: 同上

create_graph: 同上

only_inputs: 默认为True,如果为True,则只会返回指定input的梯度值。 若为False,则会计算所有叶子节点的梯度,并且将计算得到的梯度累加到各自的.grad属性上去。

allow_unused: 默认为False, 即必须要指定input,如果没有指定的话则报错。

注意该函数返回的是 tuple 类型。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • 浅谈pytorch grad_fn以及权重梯度不更新的问题

    前提:我训练的是二分类网络,使用语言为pytorch Varibale包含三个属性: data:存储了Tensor,是本体的数据 grad:保存了data的梯度,本事是个Variable而非Tensor,与data形状一致 grad_fn:指向Function对象,用于反向传播的梯度计算之用 在构建网络时,刚开始的错误为:没有可以grad_fn属性的变量. 百度后得知要对需要进行迭代更新的变量设置requires_grad=True ,操作如下: train_pred = Variable(tr

  • 解决torch.autograd.backward中的参数问题

    torch.autograd.backward(variables, grad_variables=None, retain_graph=None, create_graph=False) 给定图的叶子节点variables, 计算图中变量的梯度和. 计算图可以通过链式法则求导.如果variables中的任何一个variable是 非标量(non-scalar)的,且requires_grad=True.那么此函数需要指定grad_variables,它的长度应该和variables的长度匹配,

  • 浅谈对pytroch中torch.autograd.backward的思考

    反向传递法则是深度学习中最为重要的一部分,torch中的backward可以对计算图中的梯度进行计算和累积 这里通过一段程序来演示基本的backward操作以及需要注意的地方 >>> import torch >>> from torch.autograd import Variable >>> x = Variable(torch.ones(2,2), requires_grad=True) >>> y = x + 2 >&g

  • 浅谈Pytorch中autograd的若干(踩坑)总结

    关于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=

  • 浅谈Pytorch中的torch.gather函数的含义

    pytorch中的gather函数 pytorch比tensorflow更加编程友好,所以准备用pytorch试着做最近要做的一些实验. 立个flag开始学习pytorch,新开一个分类整理学习pytorch中的一些踩到的泥坑. 今天刚开始接触,读了一下documentation,写一个一开始每太搞懂的函数gather b = torch.Tensor([[1,2,3],[4,5,6]]) print b index_1 = torch.LongTensor([[0,1],[2,0]]) ind

  • 浅谈Pytorch中的自动求导函数backward()所需参数的含义

    正常来说backward( )函数是要传入参数的,一直没弄明白backward需要传入的参数具体含义,但是没关系,生命在与折腾,咱们来折腾一下,嘿嘿. 对标量自动求导 首先,如果out.backward()中的out是一个标量的话(相当于一个神经网络有一个样本,这个样本有两个属性,神经网络有一个输出)那么此时我的backward函数是不需要输入任何参数的. import torch from torch.autograd import Variable a = Variable(torch.Te

  • 浅谈pytorch中的BN层的注意事项

    最近修改一个代码的时候,当使用网络进行推理的时候,发现每次更改测试集的batch size大小竟然会导致推理结果不同,甚至产生错误结果,后来发现在网络中定义了BN层,BN层在训练过程中,会将一个Batch的中的数据转变成正太分布,在推理过程中使用训练过程中的参数对数据进行处理,然而网络并不知道你是在训练还是测试阶段,因此,需要手动的加上,需要在测试和训练阶段使用如下函数. model.train() or model.eval() BN类的定义见pytorch中文参考文档 补充知识:关于pyto

  • 浅谈PyTorch中in-place operation的含义

    in-place operation在pytorch中是指改变一个tensor的值的时候,不经过复制操作,而是直接在原来的内存上改变它的值.可以把它成为原地操作符. 在pytorch中经常加后缀"_"来代表原地in-place operation,比如说.add_() 或者.scatter().python里面的+=,*=也是in-place operation. 下面是正常的加操作,执行结束加操作之后x的值没有发生变化: import torch x=torch.rand(2) #t

  • 浅谈pytorch中torch.max和F.softmax函数的维度解释

    在利用torch.max函数和F.Ssoftmax函数时,对应该设置什么维度,总是有点懵,遂总结一下: 首先看看二维tensor的函数的例子: import torch import torch.nn.functional as F input = torch.randn(3,4) print(input) tensor([[-0.5526, -0.0194, 2.1469, -0.2567], [-0.3337, -0.9229, 0.0376, -0.0801], [ 1.4721, 0.1

  • 浅谈vue中使用编辑器vue-quill-editor踩过的坑

    结合vue+element-ui+vue-quill+editor二次封装成组件 1.图片上传 分析原因 项目中使用vue-quill-editor富文本编辑器,在编辑内容的时候,我们往往会编辑图片,而vue-quill-editor默认的处理方式是直接将图片转成base64格式,导致上传的内容十分庞大,且服务器接受post的数据的大小是有限制的,很有可能就提交失败,造成用户体验差. 引入element-ui 编辑editor.vue文件 <template> <div> <

  • 浅谈pytorch中为什么要用 zero_grad() 将梯度清零

    pytorch中为什么要用 zero_grad() 将梯度清零 调用backward()函数之前都要将梯度清零,因为如果梯度不清零,pytorch中会将上次计算的梯度和本次计算的梯度累加. 这样逻辑的好处是,当我们的硬件限制不能使用更大的bachsize时,使用多次计算较小的bachsize的梯度平均值来代替,更方便,坏处当然是每次都要清零梯度. optimizer.zero_grad() output = net(input) loss = loss_f(output, target) los

  • 浅谈pytorch中的nn.Sequential(*net[3: 5])是啥意思

    看到代码里面有这个 1 class ResNeXt101(nn.Module): 2 def __init__(self): 3 super(ResNeXt101, self).__init__() 4 net = resnext101() # print(os.getcwd(), net) 5 net = list(net.children()) # net.children()得到resneXt 的表层网络 # for i, value in enumerate(net): # print(

  • 浅谈pytorch中stack和cat的及to_tensor的坑

    初入计算机视觉遇到的一些坑 1.pytorch中转tensor x=np.random.randint(10,100,(10,10,10)) x=TF.to_tensor(x) print(x) 这个函数会对输入数据进行自动归一化,比如有时候我们需要将0-255的图片转为numpy类型的数据,则会自动转为0-1之间 2.stack和cat之间的差别 stack x=torch.randn((1,2,3)) y=torch.randn((1,2,3)) z=torch.stack((x,y))#默

随机推荐