• 从头学pytorch(二) 自动求梯度


    PyTorch提供的autograd包能够根据输⼊和前向传播过程⾃动构建计算图,并执⾏反向传播。

    Tensor

    Tensor的几个重要属性或方法

    • .requires_grad 设为true的话,tensor将开始追踪在其上的所有操作
    • .backward()完成梯度计算
    • .grad属性 计算的梯度累积到.grad属性
    • .detach()解除对一个tensor上操作的追踪,或者用with torch.no_grad()将不想被追踪的操作代码块包裹起来.
    • .grad_fn属性 该属性即创建Tensor的Function类的类型,即该Tensor是由什么运算得来的

    几个例子具体地解释一下:

    import torch
    x = torch.ones(2, 2, requires_grad=True)
    print(x)
    print(x.grad_fn)
    
    y = x+2
    print(y)
    print(y.grad_fn)
    
    z = y*y*3
    out=z.mean()
    print(z,out)
    

    输出

    tensor([[1., 1.],
            [1., 1.]], requires_grad=True)
    None
    tensor([[3., 3.],
            [3., 3.]], grad_fn=<AddBackward>)
    <AddBackward object at 0x0000018752434B70>
    
    
    tensor([[27., 27.],
            [27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)
    

    y由加法得到,所以y.grad_fn=,x直接创建,其x.grad_fn=None. x这种直接创建的又称为叶子节点.

    print(x.is_leaf, y.is_leaf) # True False
    

    可以用.requires_grad_()来用in-place的方式改变requires_grad属性.

    a = torch.randn(2, 2) # 缺失情况下默认 requires_grad = False
    a = ((a * 3) / (a - 1))
    print(a.requires_grad) # False
    a.requires_grad_(True)
    print(a.requires_grad) # True
    b = (a * a).sum()
    print(b.grad_fn)
    

    输出

    False
    True
    <SumBackward0 object at 0x0000018752434D30>
    

    梯度

    所计算的梯度都是结果变量关于创建变量的梯度。
    比如对:

    x = torch.ones(2, 2, requires_grad=True)
    print(x)
    print(x.grad_fn)
    
    y = x+2
    print(y)
    print(y.grad_fn)
    
    z = y*3
    z.backward(torch.ones_like(z))
    print(y.grad) #None  
    print(x.grad)
    

    输出

    None
    tensor([[3., 3.],
            [3., 3.]])
    

    上述代码相当于创建了一个动态图,其中x是我们创建的变量,y和z都是因为x的改变会改变的结果变量. 所以在这个动态图里能够求的梯度只有(frac{partial{z}}{partial{x}}),(frac{partial{y}}{partial{x}})

    为什么l.backward(gradient)需要传入一个和l同样形状的gradient?
    对于l.backward()而言,当l是标量时,可以不传参,相当于l.backward(torch.tensor(1.))
    当l不是标量时,需要传入一个和l同shape的gradient。

    假设 x 经过一番计算得到 y,那么 y.backward(w) 求的不是 y 对 x 的导数,而是 l = torch.sum(y*w) 对 x 的导数。w 可以视为 y 的各分量的权重,也可以视为遥远的损失函数 l 对 y 的偏导数(这正是函数说明文档的含义)。特别地,若 y 为标量,w 取默认值 1.0,才是按照我们通常理解的那样,求 y 对 x 的导数

    简单地说就是,张量对张量没法求导,所以我们需要人为地定义一个w,把一个非标量的Tensor通过torch.sum(y*w)的形式转换成标量。我们自己定义的这个w的不同,当然最后得到的梯度就不同.通常定义为全1.也就是认为Tensor y中的每一个变量的重要性是等同的.

    另一个角度的理解就是,y是一个tensor,是一个向量,有N个标量,这每一个标量都与x有关。对这N个标量我们需要赋以不同的权重,以显示y中每一个标量受到x影响的程度.

    比如对

    import torch
    x = torch.ones(2, 2, requires_grad=True)
    print(x)
    print(x.grad_fn)
    
    y = x+2
    print(y)
    print(y.grad_fn)
    
    z = y*3
    print(z.shape)
    w1=torch.Tensor([[1,2],[1,2]])
    z.backward([w1])
    print(x.grad)
    
    x.grad.data.zero_()
    w2=torch.Tensor([[1,1],[1,1]])
    z.backward([w2])
    print(x.grad)
    

    输出

    tensor([[1., 1.],
            [1., 1.]], requires_grad=True)
    None
    tensor([[3., 3.],
            [3., 3.]], grad_fn=<AddBackward>)
    <AddBackward object at 0x00000187524A6828>
    torch.Size([2, 2])
    tensor([[3., 6.],
            [3., 6.]])
    tensor([[3., 3.],
            [3., 3.]])
    

    对w1和w2而言,z.backward()以后x.grad是不同的。
    注意:梯度是累加的,所以第二次计算之前我们做了清零的操作:x.grad.data.zero_()

    可以参考:
    https://zhuanlan.zhihu.com/p/29923090
    https://www.cnblogs.com/zhouyang209117/p/11023160.html

    再来看看中断梯度追踪的例子:

    x = torch.tensor(1.0, requires_grad=True)
    y1 = x ** 2 
    with torch.no_grad():
        y2 = x ** 3
    y3 = y1 + y2
        
    print(x.requires_grad)
    print(y1, y1.requires_grad) # True
    print(y2, y2.requires_grad) # False
    print(y3, y3.requires_grad) # True
    

    输出:

    True
    tensor(1., grad_fn=<PowBackward0>) True
    tensor(1.) False
    tensor(2., grad_fn=<ThAddBackward>) True
    

    反向传播,求梯度

    y3.backward()
    print(x.grad)
    

    输出:

    tensor(2.)
    

    为什么是2呢?$ y_3 = y_1 + y_2 = x^2 + x^3$,当 (x=1)(frac {dy_3} {dx}) 不应该是5吗?事实上,由于 (y_2) 的定义是被torch.no_grad():包裹的,所以与 (y_2) 有关的梯度是不会回传的,只有与 (y_1) 有关的梯度才会回传,即 (x^2)(x) 的梯度。

    上面提到,y2.requires_grad=False,所以不能调用 y2.backward(),会报错:

    RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
    

    此外,如果我们想要修改tensor的数值,但是又不希望被autograd记录(即不会影响反向传播),那么我么可以对tensor.data进行操作。

    x = torch.ones(1,requires_grad=True)
    
    print(x.data) # 还是一个tensor
    print(x.data.requires_grad) # 但是已经是独立于计算图之外
    
    y = 2 * x
    x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播
    
    y.backward()
    print(x) # 更改data的值也会影响tensor的值
    print(x.grad)
    

    输出:

    tensor([1.])
    False
    tensor([100.], requires_grad=True)
    tensor([2.])
    
  • 相关阅读:
    hdu 1002 A + B Problem II
    hdu 1001 Sum Problem
    hdu 1000 A + B Problem
    mysql允许其他电脑访问权限开通
    zend studio安装svn插件
    phpcms采集地址中为相对路径解决方法
    如何解决phpcms后台验证码不显示的问题
    phpcms常用方法简介
    网站标签栏ico设置代码
    phpcms get标签用法
  • 原文地址:https://www.cnblogs.com/sdu20112013/p/12046594.html
Copyright © 2020-2023  润新知