• pytorch API之自动微分机制(grad, backward)


    pytorch API之求导函数

    利用GRAD求导


    我们在用神经网络求解PDE时, 经常要用到输出值对输入变量(不是Weights和Biases)求导; 在训练WGAN-GP 时, 也会用到网络对输入变量的求导。以上两种需求, 均可以用pytorch 中的autograd.grad() 函数实现。

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

    outputs: 求导的因变量(需要求导的函数)

    inputs: 求导的自变量

    grad_outputs: 如果 outputs为标量,则grad_outputs=None,也就是说,可以不用写; 如果outputs 是向量,则此参数必须写,不写将会报如下错误:

    那么此参数究竟代表着什么呢?

    先假设 x , y x, y x,y为一维向量, 即可设自变量因变量分别为:

    grad_outputs 是一个shape 与 outputs 一致的向量, 即

    在给定grad_outputs 之后,真正返回的梯度为

    为方便下文叙述我们引入记号 g r a d = J ⊗ g r a d e o u t p u t s grad = J \otimes grade_outputs grad=Jgradeoutputs
    其次假设 KaTeX parse error: Undefined control sequence: \time at position 50: ….y_2) \in R^{s \̲t̲i̲m̲e̲ ̲t} ,第i个列向量对应的Jacobi矩阵为KaTeX parse error: Undefined control sequence: \leqt at position 15: J_i, 1 \leq i \̲l̲e̲q̲t̲.
    此时的grad_outputs 为(维度与outputs一致)
    KaTeX parse error: Undefined control sequence: \time at position 40: …,go_t)\in R^{s \̲t̲i̲m̲e̲ ̲t}
    由第一种情况, 我们有
    g r a d = ∑ t i = 1 j i ⊗ g o i grad = \sum_{t}^{i=1}{j_i} \otimes go_i grad=ti=1jigoi
    也就是说对输出变量的列向量求导,再经过权重累加。
    x = ( x 1 , . . . , x p ) ∈ R n × p , y ∈ R m x= (x_1, ...,x_p)\in R^{n \times p}, y \in R^m x=(x1,...,xp)Rn×p,yRm沿用第一种情况记号
    g r a d = ( g r a d 1 , . . . g r a d p ) grad=(grad_1,...grad_p) grad=(grad1,...gradp), 其中每一个 g r a d i grad_i gradi均由第一种方法得出,
    即对输入变量列向量求导,之后按照原先顺序排列即可。

    retain_graph: True 则保留计算图, False则释放计算图
    create_graph: 若要计算高阶导数,则必须选为True
    allow_unused: 允许输入变量不进入计算

    代码示例

    import torch
    from torch import autograd
     
    x = torch.rand(3, 4)
    x.requires_grad_()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    观察 x 为

    不妨设 y 是 x 所有元素的和, 因为 y是标量,故计算导数不需要设置grad_outputs

    y = torch.sum(x)
    grads = autograd.grad(outputs=y, inputs=x)[0]
    print(grads)
    
    • 1
    • 2
    • 3

    结果为

    若y是向量

    y = x[:,0] +x[:,1]
    # 设置输出权重为1
    grad = autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(y))[0]
    print(grad)
    # 设置输出权重为0
    grad = autograd.grad(outputs=y, inputs=x, grad_outputs=torch.zeros_like(y))[0]
    print(grad)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果为
    在这里插入图片描述
    最后, 我们通过设置 create_graph=True 来计算二阶导数

    y = x ** 2
    grad = autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(y), create_graph=True)[0]
    grad2 = autograd.grad(outputs=grad, inputs=x, grad_outputs=torch.ones_like(grad))[0]
    print(grad2)
    
    • 1
    • 2
    • 3
    • 4

    结果为

    利用autograd.grad方法求导数

    import numpy as np 
    import torch 
    # f(x) = a*x**2 + b*x + c的导数
    x = torch.tensor(0.0,requires_grad = True) # x需要被求导
    a = torch.tensor(1.0)
    b = torch.tensor(-2.0)
    c = torch.tensor(1.0)
    y = a*torch.pow(x,2) + b*x + c
    
    # create_graph 设置为 True 将允许创建更高阶的导数 
    dy_dx = torch.autograd.grad(y,x,create_graph=True)[0]
    print(dy_dx.data)
    # 求二阶导数
    dy2_dx2 = torch.autograd.grad(dy_dx,x)[0] 
    print(dy2_dx2.data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:

    tensor(-2.)
    tensor(2.)

    import numpy as np 
    import torch 
    x1 = torch.tensor(1.0,requires_grad = True) # x需要被求导
    x2 = torch.tensor(2.0,requires_grad = True)
    y1 = x1*x2
    y2 = x1+x2
    
    # 允许同时对多个自变量求导数
    (dy1_dx1,dy1_dx2) = torch.autograd.grad(outputs=y1,
                    inputs = [x1,x2],retain_graph = True)
    print(dy1_dx1,dy1_dx2)
    # 如果有多个因变量,相当于把多个因变量的梯度结果求和
    (dy12_dx1,dy12_dx2) = torch.autograd.grad(outputs=[y1,y2],
                inputs = [x1,x2])
    print(dy12_dx1,dy12_dx2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:

    tensor(2.) tensor(1.)
    tensor(3.) tensor(2.)

    利用backward()求导

    backward方法通常在一个标量张量上调用,该方法求得的梯度将存在对应自变量张量的grad属性下。如果调用的张量非标量,则要传入一个和它同形状的gradient参数张量。相当于用该gradient参数张量与调用张量作向量点乘,得到的标量结果再反向传播。

    标量的反向传播

    import numpy as np 
    import torch 
    # f(x) = a*x**2 + b*x + c的导数
    x = torch.tensor(0.0,requires_grad = True) # x需要被求导
    a = torch.tensor(1.0)
    b = torch.tensor(-2.0)
    c = torch.tensor(1.0)
    y = a*torch.pow(x,2) + b*x + c 
    y.backward()
    dy_dx = x.grad
    print(dy_dx)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出:

    tensor(-2.)

    非标量的反向传播

    import numpy as np 
    import torch 
    # f(x) = a*x**2 + b*x + c
    x = torch.tensor([[0.0,0.0],[1.0,2.0]],requires_grad = True) # x需要被求导
    a = torch.tensor(1.0)
    b = torch.tensor(-2.0)
    c = torch.tensor(1.0)
    y = a*torch.pow(x,2) + b*x + c 
    gradient = torch.tensor([[1.0,1.0],[1.0,1.0]])
    print("x:",x)
    print("y:",y)
    y.backward(gradient = gradient)
    x_grad = x.grad
    print("x_grad:",x_grad)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出:

    x: tensor([[0., 0.], [1., 2.]], requires_grad=True)
    y: tensor([[1., 1.], [0., 1.]], grad_fn=)
    x_grad: tensor([[-2., -2.], [ 0., 2.]])

    非标量的反向传播可以用标量的反向传播实现

    import numpy as np 
    import torch 
    # f(x) = a*x**2 + b*x + c
    x = torch.tensor([[0.0,0.0],[1.0,2.0]],requires_grad = True) # x需要被求导
    a = torch.tensor(1.0)
    b = torch.tensor(-2.0)
    c = torch.tensor(1.0)
    y = a*torch.pow(x,2) + b*x + c 
    gradient = torch.tensor([[1.0,1.0],[1.0,1.0]])
    z = torch.sum(y*gradient)
    print("x:",x)
    print("y:",y)
    z.backward()
    x_grad = x.grad
    print("x_grad: ",x_grad)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:

    x: tensor([[0., 0.], [1., 2.]], requires_grad=True)
    y: tensor([[1., 1.], [0., 1.]], grad_fn=)
    x_grad: tensor([[-2., -2.], [ 0., 2.]])

    利用自动微分和优化器求最小值

    import numpy as np 
    import torch 
    # f(x) = a*x**2 + b*x + c的最小值
    x = torch.tensor(0.0,requires_grad = True) # x需要被求导
    a = torch.tensor(1.0)
    b = torch.tensor(-2.0)
    c = torch.tensor(1.0)
    optimizer = torch.optim.SGD(params=[x],lr = 0.01)
    
    def f(x):
        result = a*torch.pow(x,2) + b*x + c 
        return(result)
    for i in range(500):
        optimizer.zero_grad()
        y = f(x)
        y.backward()
        optimizer.step()   
        
    print("y=",f(x).data,";","x=",x.data)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出:

    y= tensor(0.) ; x= tensor(1.0000)

    参考

    https://www.w3cschool.cn/article/4182917.html

  • 相关阅读:
    lwIP - A Lightweight TCP/IP stack
    Nginx
    C语言——内存管理
    详述分布式事务Seata TCC空回滚/幂等/悬挂问题、解决方案(seata1.5.1如何解决?)
    Boom 3D全新2022版音频增强应用程序App
    使用Docker搭建MongoDB 5.0版本副本集集群
    wifi无线使用adb
    【第三趴】uni-app页面搭建与配置(了解工程目录结构、学会搭建页面、配置页面并成功运行)
    面试又挂了:大厂面试到底更看重学历还是技术?来看看大佬的说法
    QT:布局管理器&消息盒子&对话框
  • 原文地址:https://blog.csdn.net/weixin_42486623/article/details/126538032