• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    PyTorch 如何自动计算梯度

    在PyTorch中,torch.Tensor类是存储和变换数据的重要工具,相比于Numpy,Tensor提供GPU计算和自动求梯度等更多功能,在深度学习中,我们经常需要对函数求梯度(gradient)。

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

    本篇将介绍和总结如何使用autograd包来进行自动求梯度的有关操作。

    1. 概念

    Tensor是这个pytorch的自动求导部分的核心类,如果将其属性.requires_grad=True,它将开始追踪(track) 在该tensor上的所有操作,从而实现利用链式法则进行的梯度传播。完成计算后,可以调用.backward()来完成所有梯度计算。此Tensor的梯度将累积到.grad属性中。

    如果不想要被继续对tensor进行追踪,可以调用.detach()将其从追踪记录中分离出来,接下来的梯度就传不过去了。此外,还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为此时并不需要继续对梯度进行计算。

    Function是另外一个很重要的类。Tensor和Function互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor都有一个.grad_fn属性,该属性即创建该Tensor的Function, 就是说该Tensor是不是通过某些运算得到的,若是,则grad_fn返回一个与这些运算相关的对象,否则是None。

    2. 具体实现

    2.1. 创建可自动求导的tensor

    首先我们创建一个tensor,同时设置requires_grad=True:

    x = torch.ones(2, 2, requires_grad=True)
    print(x)
    print(x.grad_fn)
    '''

    输出:

    tensor([[1., 1.],

    [1., 1.]], requires_grad=True)

    None

    '''

    像x这种直接创建的tensor 称为叶子节点,叶子节点对应的grad_fn是None。如果进行一次运算操作:

    y = x + 1
    print(y)
    print(y.grad_fn)
    '''
    tensor([[2., 2.],
            [2., 2.]], grad_fn=AddBackward>)
    AddBackward object at 0x1100477b8>
    
    '''
    

    而y是通过一个加法操作创建的,所以它有一个为操作的grad_fn。

    尝试进行更复杂的操作:

    z = y ** 2
    out = z.mean()
    print(z, out)
    '''
    tensor([[4., 4.],
            [4., 4.]], grad_fn=PowBackward0>) tensor(4., grad_fn=MeanBackward0>)
    '''

    上面的out是一个标量4,通常对于标量直接使用out.backward()进行求导,不需要指定求导变量,后面进行详细说明。

    也可以通过.requires_grad_()改变requires_grad属性:

    a = torch.randn(3, 2) # 缺失情况下默认 requires_grad = False
    a = (a ** 2)
    print(a.requires_grad) # False
    
    a.requires_grad_(True) #使用in-place操作,改变属性
    print(a.requires_grad) # True
    b = (a * a).sum()
    print(b.grad_fn)
    '''
    False
    True
    SumBackward0 object at 0x7fd8c16edd30>
    '''
    

    2.2. 梯度计算

    torch.autograd实现梯度求导的链式法则,用来计算一些雅克比矩阵的乘积,即函数的一阶导数的乘积。

    注意:grad在反向传播过程中是累加的(accumulated),每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零x.grad.data.zero_()。

    x = torch.ones(2, 2, requires_grad=True)
    y = x + 1
    z = y ** 2
    out = z.mean()
    print(z, out)
    out.backward()
    print(x.grad)
    
    # 注意grad是累加的
    out2 = x.sum()
    out2.backward()
    print(out2)
    print(x.grad)
    
    out3 = x.sum()
    x.grad.data.zero_()
    out3.backward()
    print(out3)
    print(x.grad)
    '''
    tensor([[4., 4.],
            [4., 4.]], grad_fn=PowBackward0>) tensor(4., grad_fn=MeanBackward0>)
    tensor([[1., 1.],
            [1., 1.]])
    tensor(4., grad_fn=SumBackward0>)
    tensor([[2., 2.],
            [2., 2.]])
    tensor(4., grad_fn=SumBackward0>)
    tensor([[1., 1.],
            [1., 1.]])
    '''
    

    Tensor的自动求导对于标量比如上面的out.backward()十分方便,但是当反向传播的对象不是标量时,需要在y.backward()种加入一个与out同形的Tensor,不允许张量对张量求导,只允许标量对张量求导,求导结果是和自变量同形的张量。

    这是为了避免向量(甚至更高维张量)对张量求导,而转换成标量对张量求导。

    x = torch.tensor([1.0, 2.0, 3.0, 4.0], requires_grad=True)
    y = 2 * x
    z = y.view(2, 2)
    print(z)
    '''
    tensor([[2., 4.],
            [6., 8.]], grad_fn=ViewBackward>)
    '''

    显然上面的tensor z不是一个标量,所以在调用 z.backward()时需要传入一个和z同形的权重向量进行加权求和得到一个标量。

    c = torch.tensor([[1.0, 0.1], [0.01, 0.001]], dtype=torch.float)
    z.backward(c)
    print(x.grad)
    
    '''
    tensor([[2., 4.],
            [6., 8.]], grad_fn=ViewBackward>)
    tensor([2.0000, 0.2000, 0.0200, 0.0020])
    '''
    

    2.3 停止梯度追踪

    我们可以使用detach()或者torch.no_grad()语句停止梯度追踪:

    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)
    # y2.backward() #这句会报错,因为此时 y2.requires_grad=False,,无法调用反向传播
    '''
    tensor(2.)
    '''

    这里结果为2,是因为我们没有获得y2的梯度,仅仅是对y1做了一次反向传播,作为最后的梯度输出。

    2.4. 修改tensor的值

    如果我们想要修改tensor的数值,但是不希望保存在autograd的记录中,require s_grad = False, 即不影响到正在进行的反向传播,那么可以用tensor.data进行操作。但是这种操作需要注意可能会产生一些问题,比如标量为0

    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)
    

    pytorch0.4以后保留了.data() 但是官方文档建议使用.detach(),因为使用x.detach时,任何in-place变化都会使backward报错,因此.detach()是从梯度计算中排除子图的更安全方法。

    如下面的例子:

    torch.tensor([1,2,3.], requires_grad = True)
    out = a.sigmoid()
    c = out.detach()
    c.zero_()  # in-place为0 ,tensor([ 0.,  0.,  0.])
    print(out) # modified by c.zero_() !! tensor([ 0.,  0.,  0.])
    out.sum().backward()  # Requires the original value of out, but that was overwritten by c.zero_()
    '''
    RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
    '''
    a = torch.tensor([1,2,3.], requires_grad = True)
    out = a.sigmoid()
    c = out.data
    c.zero_() # tensor([ 0.,  0.,  0.])
    print(out)  # out  was modified by c.zero_() tensor([ 0.,  0.,  0.])
    out.sum().backward()
    a.grad  # 这么做不会报错,但是a已经被改变,最后计算的梯度实际是错误的
    '''
    tensor([ 0.,  0.,  0.])
    '''

    补充:pytorch如何计算导数_Pytorch 自动求梯度(autograd)

    深度学习其实就是一个最优化问题,找到最小的loss值,因为自变量过多,想要找到最小值非常困难。所以就出现了很多最优化方法,梯度下降就是一个非常典型的例子。本文针对python的pytorch库中的自动求梯度进行了详细的解释

    Tensor

    pytorch里面的tensor可以用来存储向量或者标量。

    torch.tensor(1) # 标量
    torch.tensor([1]) # 1*1 的向量

    tensor还可以指定数据类型,以及数据存储的位置(可以存在显存里,硬件加速)

    torch.tensor([1,2], dtype=torch.float64)

    梯度

    在数学里,梯度的定义如下:

    可以看出,自变量相对于因变量的每一个偏导乘以相应的单位向量,最后相加,即为最后的梯度向量。

    在pytorch里面,我们无法直接定义函数,也无法直接求得梯度向量的表达式。更多的时候,我们其实只是求得了函数的在某一个点处相对于自变量的偏导。

    我们先假设一个一元函数:y = x^2 + 3x +1,在pytorch里面,我们假设x = 2, 那么

    >>> x = torch.tensor(2, dtype=torch.float64, requires_grad=True)
    >>> y = x * x + 3 * x + 1
    >>> y.backward()
    >>> x.grad
    tensor(7., dtype=torch.float64)

    可以看出,最后y相对于x的导数在x=2的地方为7。在数学里进行验证,那么就是

    y' = 2*x + 3, 当x=2时,y' = 2 * 2 + 3 = 7, 完全符合torch自动求得的梯度值。

    接下来计算二元函数时的情况:

    >>> x1 = torch.tensor(1.0)
    >>> x2 = torch.tensor(2.0, requires_grad=True)
    >>> y = 3*x1*x1 + 9 * x2
    >>> y.backward()
    tensor(6.)
    >>> x2.grad
    tensor(9.)

    可以看出,我们可以求得y相对于x2的偏导数。

    以上讨论的都是标量的情况,接下来讨论自变量为向量的情况。

    mat1 = torch.tensor([[1,2,3]], dtype=torch.float64, requires_grad=True)
    >>> mat2
    tensor([[1.],
            [2.],
            [3.]], dtype=torch.float64, requires_grad=True)

    mat1是一个1x3的矩阵,mat2是一个3x1的矩阵,他们俩的叉乘为一个1x1的矩阵。在pytorch里面,可以直接对其进行backward,从而求得相对于mat1或者是mat2的梯度值。

    >>> y = torch.mm(mat1, mat2)
    >>> y.backward()
    >>> mat1.grad
    tensor([[1., 2., 3.]], dtype=torch.float64)
    >>> mat2.grad
    tensor([[1.],
            [2.],
            [3.]], dtype=torch.float64)

    其实可以把mat1中的每一个元素当成一个自变量,那么相对于mat1的梯度向量,就是分别对3个x进行求偏导。

    相当于是y = mat1[0] * mat2[0] + mat1[1] * mat2[1] + mat1[2] * mat2[2]

    然后分别求y对于mat1,mat2每个元素的偏导。

    另外,如果我们最后输出的是一个N x M 的一个向量,我们要计算这个向量相对于自变量向量的偏导,那么我们就需要在backward函数的参数里传入参数。

    如上图所述,其实pytorch的autograd核心就是计算一个 vector-jacobian 乘积, jacobian就是因变量向量相对于自变量向量的偏导组成的矩阵,vector相当于是因变量向量到一个标量的函数的偏导。最后就是标量相对于一个向量的梯度向量。

    总结

    最后,其实神经网络就是寻求一个拟合函数,但是因为参数过多,所以不得不借助每一点的梯度来一点一点的接近最佳的LOSS值,pytorch拥有动态的计算图,存储记忆对向量的每一个函数操作,最后通过反向传播来计算梯度,这可以说是pytorch的核心。

    所以深入了解如果利用pytorch进行自动梯度计算非常重要。

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    您可能感兴趣的文章:
    • PyTorch梯度裁剪避免训练loss nan的操作
    • Pytorch训练过程出现nan的解决方式
    • pytorch 权重weight 与 梯度grad 可视化操作
    • PyTorch 如何检查模型梯度是否可导
    • 浅谈pytorch中为什么要用 zero_grad() 将梯度清零
    • pytorch 梯度NAN异常值的解决方案
    上一篇:解决numpy和torch数据类型转化的问题
    下一篇:聊聊pytorch测试的时候为何要加上model.eval()
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    PyTorch 如何自动计算梯度 PyTorch,如何,自动,计算,梯度,