• pytorch基础语法问题


    pytorch基础语法问题

    shape

    import torch
    # 创建一个形状为(2, 3)的张量
    x = torch.Tensor([[1, 2, 3], [4, 5, 6],[1,1,1],[2,2,2]])
    print(len(x.shape))
    print(x.shape[0])
    # 遍历张量中的元素
    for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            print(x[i, j])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    len(x.shape),维数,一般为二维
    x.shape[0]:行数
    x.shape[1]: 列数

    2
    4
    tensor(1.)
    tensor(2.)
    tensor(3.)
    tensor(4.)
    tensor(5.)
    tensor(6.)
    tensor(1.)
    tensor(1.)
    tensor(1.)
    tensor(2.)
    tensor(2.)
    tensor(2.)
    
    进程已结束,退出代码0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    torch.ones_like函数和torch.zeros_like函数

    返回一个形状与input相同且值全为1的张量。torch.ones_like(input)相当于torch.ones(input.size, dtype=input.dtype,layout=input.layout,device=input.device)

    input = torch.rand(4, 6)
    print(input)
    # 生成与input形状相同、元素全为1的张量
    a = torch.ones_like(input)
    print(a)
    # 生成与input形状相同、元素全为0的张量
    b = torch.zeros_like(input)
    print(b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    z.backward(torch.ones_like(z))
    
    • 1

    z.backward(torch.ones_like(z))中的torch.ones_like(z)相当于在对z进
    行求导时,对z中的元素进行了求和操作,从而将其转为一个标量。

    y.backward(torch.ones_like(x), retain_graph=True)

    y.backward(torch.ones_like(x), retain_graph=True)
    d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))
    
    • 1
    • 2

    torch.autograd.backward

    x = torch.tensor(1.0, requires_grad=True)
    y = torch.tensor(2.0, requires_grad=True)
    z = x**2+y
    z.backward()
    print(z, x.grad, y.grad)
    
    >>> tensor(3., grad_fn=<AddBackward0>) tensor(2.) tensor(1.)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以z是一个标量,当调用它的backward方法后会根据链式法则自动计算出叶子节点的梯度值。

    但是 如果遇到z是一个向量或者是一个矩阵的情况,这个时候又该怎么计算梯度呢? 这种情况我们需要定义grad_tensor来计算矩阵的梯度。在介绍为什么使用之前我们先看一下源代码中backward的接口是如何定义的:

    torch.autograd.backward(
    		tensors, 
    		grad_tensors=None, 
    		retain_graph=None, 
    		create_graph=False, 
    		grad_variables=None)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • tensor: 用于计算梯度的tensor。也就是说这两种方式是等价的:- torch.autograd.backward(z) == z.backward()
    • grad_tensors: 在计算矩阵的梯度时会用到。他其实也是一个tensor,shape一般需要和前面的tensor保持一致。
    • retain_graph: 通常在调用一次backward后,pytorch会自动把计算图销毁,所以要想对某个变量重复调用backward,则需要将该参数设置为True
    • create_graph: 当设置为True的时候可以用来计算更高阶的梯度
    • grad_variables: 这个官方说法是grad_variables’ is deprecated. Use - ‘grad_tensors’ instead.也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了。

    好了,参数大致作用都介绍了,下面我们看看pytorch为什么设计了grad_tensors这么一个参数,以及它有什么用呢?

    参数grad_tensors: z.backward(torch.ones_like(x))

    原则上,Pytorch不支持对张量的求导,即如果z是张量的话,需要先将其转为标量。

    浏览了很多博客,给出的解决方案都是说在求导时,加一个torch.ones_like(z)的参数。

    torch.ones_like(z)的作用。简而言之,torch.ones_like(z)相当于在对z进行求导时,对z中的元素进行求和操作,从而将其转为一个标量,便于后续的求导。

    x = torch.ones(2,requires_grad=True)
    z = x + 2
    z.backward()
    
    >>> ...
    RuntimeError: grad can be implicitly created only for scalar outputs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    本质要得到 z对x求导, 但是已知的是X,Z ;一个矩阵对另一个矩阵求导,才能得到 每个z_partial 对x_partial的导数
    其实,可以让sum(z_partial) 对于X求导,对xi 求偏导,就可以得到对应的z_partial
    对x_partial的导数,,因为sum(z_partial) 对xi 求偏导,只有包含xi 的那一项在求导,其余与xi 无关的项
    对xi求导为0

    我们再仔细想想,对z求和不就是等价于z点乘一个一样维度的全为1的矩阵吗?即 [公式]
    ,而这个I也就是我们需要传入的grad_tensors参数。(点乘只是相对于一维向量而言的,对于矩阵或更高为的张量,可以看做是对每一个维度做点乘)

    import torch
    
    x = torch.ones(2,3,requires_grad=True)
    z = 2*x + 2
    print(z)
    print(z.sum())
    # print(z.*torch.ones_like(x))
    z.sum().backward()#或者z.backward(torch.ones_like(x)) 效果一样!
    print(x.grad)
    
    '''
    tensor([[4., 4., 4.],
            [4., 4., 4.]], grad_fn=)
    tensor(24., grad_fn=)
    tensor([[2., 2., 2.],
            [2., 2., 2.]])
    '''
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    来个复杂例子z.backward(torch.Tensor([[1., 0]])
    x = torch.tensor([2., 1.], requires_grad=True).view(1, 2)
    y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
    
    z = torch.mm(x, y)
    print(f"z:{z}")
    z.backward(torch.Tensor([[1., 0]]), retain_graph=True)
    print(f"x.grad: {x.grad}")
    print(f"y.grad: {y.grad}")
    
    >>> z:tensor([[5., 8.]], grad_fn=<MmBackward>)
    x.grad: tensor([[1., 3.]])
    y.grad: tensor([[2., 0.],
            [1., 0.]])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    说了这么多,grad_tensors的作用其实可以简单地理解成在求梯度时的权重,因为可能不同值的梯度对结果影响程度不同,所以pytorch弄了个这种接口,而没有固定为全是1。引用自知乎上的一个评论:如果从最后一个节点(总loss)来backward,这种实现(torch.sum(y*w))的意义就具体化为 multiple loss term with difference weights 这种需求了吧
    内容来源

    看到这里我不由得想,会不会有更复杂的例子呢,万一 输入参数太多多维,导致得到的z不只是一个一维向量,是多维的矩阵,那么就是sum起来或者是点乘一个和z尺寸相同的全1矩阵咯,反正,z是一定是要被处理成一个标量才能进行求导
    原则上,Pytorch不支持对张量的求导,即如果z是张量的话,需要先将其转为标量。

    更复杂例子
    import torch
    
    x = torch.tensor(3.,requires_grad=True)
    p = torch.ones(2,2,requires_grad=True)
    
    y = x*x
    z = 2*y+2*p*p
    # [
    # [1,1],
    # [1,1]
    # ]
    z.backward(torch.ones_like(z))
    # # z = z.sum() # 与下面的torch.sum(z)作用相同,即z中所有元素的和。
    # z = torch.sum(z)
    # z.backward()
    print(x.grad)
    print(p.grad)
    # print(y.grad) # backward()无法对非叶子节点求导
    
    # 知识点汇总:
    # 原则上,Pytorch不支持对张量的求导,即如果z是张量的话,需要先将其转为标量。
    # 就这个例子来说,z.backward(torch.ones_like(z))中的torch.ones_like(z)相当于在对z进行求导时,对z中的元素进行了求和操作,从而将其转为一个标量。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    pp,张量相乘,对应位置的元素相乘,torch.mul()和 ,广播机制
    z = 2y+2p*p,张量相加,广播机制,y这个标量被生生广播 扩维了,
    当 z.sum().backward(),求和再对x求导,这个导数就大了不少(广播机制之后再求和,计算过程中标量y维数扩大了四倍,导致z对y的导数也扩大了四倍,夸大了,不合适

    这么大,对x求导不太公正啊

    实际上,也可以通过 求均值 的形式将其转为标量
    z = z.mean() # z中所有元素的均值
    z.backward()
    
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    该部分来自于此处

    retain_graph=True参数

    当我们计算梯度时,PyTorch会自动根据计算图反向传播梯度来更新模型参数。但是,当我们的计算图比较复杂,或者需要多次反向传播时,我们可能需要使用retain_graph参数来保存计算图。

    retain_graph表示在进行反向传播计算梯度的时候,是否保留计算图。如果设置为True,则计算图将被保留,可以在之后的操作中进行多次反向传播计算。如果为False,则计算图将被清空。这是为了释放内存并防止不必要的计算。

    pytorch进行一次backward之后,各个节点的值会清除,这样进行第二次backward会报错,因为虽然计算节点数值保存了,但是计算图结构被释放了,如果加上retain_graph==True后,可以再来一次backward。

    import torch
    
    # 定义张量
    x = torch.ones(2, 2, requires_grad=True)
    y = x + 2
    z = y * y * 3
    out = z.mean()
    print(x.grad)
    # print(y) 全3矩阵
    # print(z) #全27矩阵
    # 计算梯度
    out.backward(retain_graph=True)
    print(x.grad)
    # 再次计算梯度
    z.backward(torch.ones_like(z))
    print(x.grad)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    None
    tensor([[4.5000, 4.5000],
            [4.5000, 4.5000]])
    tensor([[22.5000, 22.5000],
            [22.5000, 22.5000]])
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    在这里插入图片描述

    z对x求导,在x为全1矩阵之处应该是18,但你会发现代码运行结果是22.5,很没有厘头,其实是因为 梯度累加
    如何解决呢

    x.grad.data.zero_()
    
    • 1
    在每次反向传播求导时,计算的梯度不会自动清零。如果进行多次迭代计算梯度而没有清零,那么梯度会在前一次的基础上叠加。需要使用 Tensor.grad.zero_()将梯度清零。x.grad.data.zero_()
    import torch
    
    # 定义张量
    x = torch.ones(2, 2, requires_grad=True)
    y = x + 2
    z = y * y * 3
    out = z.mean()
    print(x.grad)
    # print(y) 全3矩阵
    # print(z) #全27矩阵
    # 计算梯度
    out.backward(retain_graph=True)
    print(x.grad)
    x.grad.data.zero_()
    # 再次计算梯度
    z.backward(torch.ones_like(z))
    print(x.grad)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    None
    tensor([[4.5000, 4.5000],
            [4.5000, 4.5000]])
    tensor([[18., 18.],
            [18., 18.]])
    
    • 1
    • 2
    • 3
    • 4
    • 5

    再来个例子

    import torch
    
    w = torch.tensor([1.], requires_grad=True)
    x = torch.tensor([2.], requires_grad=True)
    # y=(x+w)*(w+1)
    a = torch.add(w, x)
    b = torch.add(w, 1)
    y = torch.mul(a, b)
    
    # 反向传播求导数
    # torch.autograd.backward(y)
    y.backward(retain_graph=True)
    print("w's grad: {}\\nx's grad: {}".format(w.grad, x.grad))
    # print("a's grad: {}".format(a.grad))
    
    # 清零梯度
    # w.grad.zero_()
    # x.grad.zero_()
    
    # 第二次求导
    y.backward()
    print("w's grad: {}\\nx's grad: {}".format(w.grad, x.grad))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    输出结果:

    w's grad1: tensor([5.])
    x's grad1: tensor([2.])
    w's grad2: tensor([10.])
    x's grad2: tensor([4.])
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以,如果注释掉 grad.zero() 相关的代码,那么第二次计算得到的导数就叠加到了第一次结果之上。

    非叶子节点(见上一篇文章)的梯度会默认被释放掉,除非用 retain_grad()函数明确指明保留其梯度。
    print("a's grad: {}".format(a.grad))
    
    • 1

    输出结果:

    a’s grad: None
    如这里如果我们输出非叶子节点 的梯度,显示为 None。

    此段来自:backward()函数注意事项

    一些援引

    待看
    pytorch中retain_graph==True的作用说明(详细例子+踩坑说明)

    在这里插入图片描述
    本来有个问题,啥叫释放,怎么释放,只要最后一次一次backward不设置retain_graph==True,就算释放
    在这里插入图片描述
    以上是这篇的意思
    梯度会叠加,我看到有代码 在循环里面使用backward,也没用 retain_graph==True,计算树没被释放?还有,想必需要用到梯度叠加?

    矩阵相乘

    pytorch中的矩阵乘法操作(总结的好!简明精要
    pytorch中的矩阵乘法操作:
    torch.mm()
    - 只适合于二维张量的矩阵乘法
    - m x n, n x p -> m x p
    torch.bmm()
    - 只适合于三维张量的矩阵乘法,与torch.mm类似,但多了一个batch_size维度。
    - b x m x n, b x n x p -> b x m x p

    torch.mul()和*

    • ⭐ torch.mul()和*等价。
    • 张量对应位置元素相乘
    • 将输入张量input的每个元素与另一个向量or标量other相乘,返回一个新的张量out,两者维度需满足广播规则

    torch.dot()
    向量点积:两向量对应位置相乘然后全部相加。只能支持两个一维向量。
    torch.mv(), @, torch.matmul()

  • 相关阅读:
    EasyXnote5关于批量绘图
    每日汇评:黄金的回调可能会在周五上涨3%之后延续
    【毕业设计】13-基于单片机的锂电池管理系统(原理图+源码+仿真工程+论文)
    机器学习-集成学习LightGBM
    MySQL 开发规范
    2022软件工程师薪资报告出炉!
    极致性能优化:前端SSR渲染利器Qwik.js
    四级常见英语短语1000条
    牛客网_HJ2_计算某字符出现次数
    InputMethodManagerService启动-Android12
  • 原文地址:https://blog.csdn.net/qq_51070956/article/details/134295044