PyTorch基础教程(四)PyTorch 的计算图和自动求导机制

目录

一、自动求导机制的简介

二、自动求导机制实例

1.反向传播示意图

2.自动求导机制示例代码

3.反向传播实例代码

三、梯度函数的使用

四、计算图构建的启用和禁用


一、自动求导机制的简介

        PyTorch 会根据计算过程来自动生成动态图,然后可以根据动态图的创建过程进行反向传播,计算到每个节点的梯度值。

        为记录张量梯度,首先需要在创建张量的时候设置一个参数 requires_grad = True ,意味着这个张量将会加入到计算图中,作为计算图叶子节点参与计算,通过一系列的计算,最后输出结果张量,也就是根节点

说明:

        几乎所有的张量创建方式都可以指定 requires_grad = True 这个参数,一旦指定了这个参数,在后续的计算中得到的中间结果的张量都会被设置成 requires_grad = True 。对于 PyTorch 来说,每个张量都有一个 grad _fn 方法,这个方法包含着创建该张量的运算的导数信息。在反向传播过程中,通过传入后一层的神经网络的梯度,该函数会计算出参与运算的所有张量的梯度。grad _fn 本身也携带着计算图的信息,该方法本身有一个 next _functions 属性,包含连接该张量的其他张量的 grad _fn 。通过不断反向传播回溯中间张量的计算节点,可以得到所有张量的梯度。一个张量的梯度张量的信息保存在该张量的 grad 属性中。
        除 PyTorch 张量本身外, PyTorch 提供了一个专门用来做自动求导的包,即 torch . autograd 。它包含有两个重要的函数,即 torch . autograd . backward 函数和 torch . autograd . grad 函数。torch . autograd . backward 函数通过传入根节点张量,以及初始梯度张量(形状和当前张量的相同),可以计算产生该根节点所有对应的叶子节点的梯度。当张量为标量张量时( Scalar ,即只有一个元素的张量),可以不传入初始梯度张量,默认会设置初始梯度张量为1。当计算梯度张量的时候,原先建立起来的计算图会被自动释放,如果需要再次做自动求导,因为计算图已经不存在,就会报错。如果要在反向传播的时候保留计算图,可以设置 retaingraph = True 。另外,在自动求导的时候默认不会建立反向传播的计算图(因为反向传播也是一个计算过程,可以动态创建计算图),如果需要在反向传播计算的同时建立和梯度张量相关的计算图(在某些情况下,如需要计算高阶导数的情况下,不过这种情况比较少),可以设置 create _graph = True 。对于一个可求导的张量,也可以直接调用该张量内部的 backward 方法来进行自动求导。


二、自动求导机制实例

1.反向传播示意图

PyTorch基础教程(四)PyTorch 的计算图和自动求导机制_第1张图片

2.自动求导机制示例代码

 

提在前面:张量绑定的梯度张量在不清零的情况下会逐渐积累,需要注意张量梯度清零问题

import torch

gradA = torch.randn(3,3,requires_grad = True)    #将参数 requires_grad 设置为 True

gradA
Out[1]: 
tensor([[-0.6710, -0.9374, -1.8171],
        [-1.1296,  0.3943,  1.1825],
        [-0.7658,  1.5006, -0.6165]], requires_grad=True)

gradB = gradA.pow(2).sum()   #计算张量的所有分量平方和

gradB
Out[2]: tensor(10.6791, grad_fn=)

gradB.backward() #进行反向传播

gradA.grad    #梯度是原张量分量的两倍
Out[3]: 
tensor([[-1.3419, -1.8747, -3.6342],
        [-2.2592,  0.7886,  2.3651],
        [-1.5316,  3.0013, -1.2331]])

gradB = gradA.pow(2).sum() #再次计算所有张量分量的平方和

gradB
Out[4]: tensor(10.6791, grad_fn=)

gradB.backward() #再次进行反向传播

gradA.grad #梯度累积
Out[5]: 
tensor([[-2.6838, -3.7494, -7.2685],
        [-4.5184,  1.5772,  4.7302],
        [-3.0632,  6.0026, -2.4662]])

gradA.grad.zero_()
Out[6]: 
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

3.反向传播实例代码

import torch

# 1.准备数据
# y=3x+0.8
learning_rate = 0.01    #定义学习率
x = torch.rand([500,1])  #准备500个随机数据
y_true = x*3 + 0.8  #定义真实模型

#2.定义w和b
w = torch.rand([1,1],requires_grad = True)
b = torch.tensor(0,requires_grad = True,dtype = torch.float32)
#说明:在tensor中使用requires_grad = True时,必须定义类型为浮点型;

#3.通过循环,反向传播,更新参数
for i in range(5000):
    #4.通过模型计算y_predict
    y_predict = torch.matmul(x,w) + b

    #5.计算损失函数
    loss = (y_true-y_predict).pow(2).mean()
    
    if w.grad is not None:  #梯度清零
        w.grad.data.zero_()
    if b.grad is not None:  #梯度清零
        b.grad.data.zero_()
        
    loss.backward() #反向传播
    w.data = w.data - learning_rate*w.grad
    b.data = b.data - learning_rate*b.grad
    if i%1000 == 0:
        print("w,b,loss",w.item(),b.item(),loss.item())
#运行结果

w,b,loss 0.9877061247825623 0.036264512687921524 3.6476798057556152
w,b,loss 2.684662103652954 0.968678891658783 0.008809476159512997
w,b,loss 2.920255661010742 0.8426564931869507 0.0005633729160763323
w,b,loss 2.9798336029052734 0.8107872605323792 3.602857395890169e-05
w,b,loss 2.9948995113372803 0.8027284145355225 2.3045577108860016e-06

三、梯度函数的使用

说明:

        在某些情况下,不需要求出当前张量对所有产生该张量的叶子节点的梯度,这时可以使用 torch . autograd . grad 函数。这个函数的参数是两个张量,第一个张量是计算图的数据结果张量(或张量列表),第二个张量是需要对计算图求导的张量(或张量列表)。最后输出的结果是第一个张量对第二个张量求导的结果(注意最后输出的梯度会累积,和前面介绍的 torch . autograd . bacward 函数的行为一样)。这里需要注意的是,这个函数不会改变叶子节点的 grad 属性,而不像 torch . autograd . bacward 函数一样会设置叶子节点的 grad 属性为最后求出梯度张量。同样, torch . autograd . grad 函数会在反向传播求导的时候释放计算图,如果需要保留计算图,同样可以设置 retain _ graph = True 。如果需要反向传播的计算图,可以设置 create _ graph = True 
        另外,有时候会碰到一种情况是求导的两个张量之间在计算图上没有关联,在这种情况下函数会报错,如果不需要函数的报错行为,可以设置 allow _ unused = True 这个参数,结果会返回分量全为0的梯度张量(因为两个张量没有关联,所以求导的梯度为0)。

#示例代码

import torch

autoA = torch.randn(3,3,requires_grad = True)  #初始化A张量

autoA
Out[1]: 
tensor([[-0.0750, -0.2216, -0.8375],
        [-0.7613, -0.2277,  1.8035],
        [ 0.6972,  0.9937,  0.8507]], requires_grad=True)

autoB = autoA.pow(2).sum()

autoB
Out[2]: tensor(6.8372, grad_fn=)

torch.autograd.grad(autoB,autoA)  #B张量对A张量求导
Out[3]: 
(tensor([[-0.1500, -0.4431, -1.6750],
         [-1.5226, -0.4554,  3.6069],
         [ 1.3944,  1.9874,  1.7014]]),)

四、计算图构建的启用和禁用

        由于计算图的构建需要消耗内存和计算资源,在一些情况下,计算图并不是必要的,比如神经网络的推导。在这种情况下,可以使用 torch . no _ grad 上下文管理器,在这个上下文管理器的作用域里进行的神经网络计算不会构建任何计算图。
        另外,还有一种情况是对于一个张量,我们在反向传播的时候可能不需要让梯度通过这个张量的节点,也就是新建的计算图要和原来的计算图分离。在这种情况下,可以使用张量的 detach 方法,通过调用这个方法,可以返回一个新的张量,该张量会成为一个新的计算图的叶子节点,新的计算图和老的计算图相互分离,互不影响

#示例代码

import torch

A = torch.randn(3,3,requires_grad = True)
print("A: \n",A)

B = A.sum()  #B 的计算构建了计算图,输出结果带有 grad_fn
print("B: \n",B)

with torch.no_grad():
    C = A.sum()

print("C: \n",C)  #C 的计算没有构建计算图,输出结果没有  grad_fn

print("A.sum(): \n",A.sum())  #保持原始计算图

print("A.sum().detach(): \n",A.sum().detach())  #和原始计算图分离
#运行结果

A: 
 tensor([[-1.3179,  1.5933,  0.7447],
        [-1.3308,  0.3353,  0.6832],
        [ 1.6125,  1.9289, -1.2839]], requires_grad=True)
B: 
 tensor(2.9653, grad_fn=)
C: 
 tensor(2.9653)
A.sum(): 
 tensor(2.9653, grad_fn=)
A.sum().detach(): 
 tensor(2.9653)

有关 with 的用法请参考:Python with 关键字 | 菜鸟教程 (runoob.com)

最新动态,请关注微信公众号回不去的明天 

你可能感兴趣的:(PyTorch,基础教程,pytorch,人工智能,python)