欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深度学习(三):梯度计算

程序员文章站 2022-07-12 22:55:59
...

文章目录

1 概念

  Tensor是autograd包的核心类,若将其属性.requires_grad设置为True,它将开始追踪在其上的所有操作。完成计算后,可以调用 .backward()来完成所有梯度计算。此Tensor的梯度将累计到.grad属性中。若要停止追踪,则方法如下:

  • 调用.detach()
  • with torch.no_grad(): 包裹的代码块将不会被追踪

2 示例

示例1

import torch

def my_grad():
    x = torch.ones(2, 2, requires_grad=True)
    print(x)
    print(x.grad_fn)    #该tensor若为计算所得,则输出一个与这些计算相关的对象,否则为None
    y = x + 2
    print(y)
    print(y.grad_fn)
    
if __name__ == "__main__":
    my_grad()

  运行结果:

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
None
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x00000178B6D15C18>

  像x这种直接创建的的称为 叶子节点

示例2:计算指定函数关于x的梯度

import torch

def my_grad():
    x = torch.ones(2, 2, requires_grad=True)
    y = x + 2
    z = y * y * 3
    z_mean = z.mean()
    z_mean.backward()    #等价z_mean.backward(torch.tensor(1.))
    print(x.grad)

if __name__ == "__main__":
    my_grad()

  运行结果:

tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

  需要注意的是.backward(),如果当前tensor是标量,则不需要为backward()传入参数;否则,需要传入一个与当前tensor同行的tensor。这样做的好处在于避免tensor对tensor求导,只允许标量对tensor求导,则求导结果是和自变量同形的tensor。
  当然,grad在反向传播的过程中是累加的,这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前把梯度清零,如以下:

示例3:梯度清零

import torch

def my_grad():
    x = torch.ones(2, 2, requires_grad=True)
    y = x + 2
    z = y * y * 3
    z_mean = z.mean()
    z_mean.backward()

    z_sum1 = x.sum()
    z_sum1.backward()
    print(x.grad)
    
    z_sum2 = x.sum()
    x.grad.data.zero_()    #梯度清零
    z_sum2.backward()
    print(x.grad)

if __name__ == "__main__":
    my_grad()

  运行结果:

tensor([[5.5000, 5.5000],
        [5.5000, 5.5000]])
tensor([[1., 1.],
        [1., 1.]])

示例4:当前tensor非标量

import torch

def my_grad():
    x = torch.tensor([[1.0, 2.0], [3.0, 4.0]], requires_grad=True)
    y = x * 2
    print(y)
    z = torch.tensor([[1.0, 1.0], [1.0, 1.0]])    #传入与y同形的权重向量
    y.backward(z)
    print(x.grad)

if __name__ == "__main__":
    my_grad()

  运行结果:

tensor([[2., 4.],
        [6., 8.]], grad_fn=<MulBackward0>)
tensor([[2., 2.],
        [2., 2.]])

  需要注意的是:
  (1)z = torch.tensor([[1.0, 1.0], [1.0, 1.0]]) 为权重向量,代表求梯度时的偏置;
  (2)x.grad与x同形。

示例5:中断梯度追踪

import torch

def my_grad():
    x = torch.tensor(1.0, requires_grad=True)
    y = x ** 2
    with torch.no_grad():
        y2 = x ** 2
    y3 = y + y2
    
    print(x.requires_grad)
    print(y, y.requires_grad)
    print(y2, y2.requires_grad)
    print(y3, y3.requires_grad)
    y3.backward()
    print(x.grad)

if __name__ == "__main__":
    my_grad()

  运行结果:

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

  可以看到y2没有grad_fn,y2.requires_grad=False,且y3对x求梯度为2.,原因在于y2被with torch.no_grad():包裹,与y2相关的梯度没有回传。

示例6:操作tensor.data
  对tensor.data操作的好处在于,既可以修改tensor的值,又不会被autograd记录,即不会反向传播。

import torch

def my_grad():
    x = torch.tensor(1.0, requires_grad=True)
    
    print(x.data)    #依旧是一个tensor
    print(x.data.requires_grad)     #但不会影响反向传播
    
    y = x * 2
    x.data *= 100    #只是改变了值
    
    y.backward()
    print(x)    #更改data的值也会影响tensor的值
    print(x.grad)

if __name__ == "__main__":
    my_grad()

  运行结果:

tensor(1.)
False
tensor(100., requires_grad=True)
tensor(2.)