backward理解

backward:自动求梯度。计算小批量随机梯度。

当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解 叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数 深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:1.先选取一组模型参数的初始值,如随机选取;2.接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。

在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B,然后求小批量中数据样本的平均损失有关模型参数的导 数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

 在上式中,|B| 代表每个小批量中的样本个数(批量大小,batch size),η 称作学习率(learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学 出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。在少数情况下,超参数也可以通过模型训练学出。

梯度累积

所谓梯度累积,其实很简单,我们梯度下降所用的梯度,实际上是多个样本算出来的梯度的平均值,以 batch_size=128 为例,你可以一次性算出 128 个样本的梯度然后平均,我也可以每次算 16 个样本的平均梯度,然后缓存累加起来,算够了 8 次之后,然后把总梯度除以 8,然后才执行参数更新。当然,必须累积到了 8 次之后,用 8 次的平均梯度才去更新参数,不能每算 16 个就去更新一次,不然就是 batch_size=16 了。

定义优化函数

以下的 sgd 函数实现了上一节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化 损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。

def sgd(params, lr, batch_size):
    '''
    小批量随机梯度下降
    
    params: 权重
    lr: 学习率
    batch_size: 批大小
    '''
    for param in params:
        param.data -= lr * param.grad / batch_size

在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的小批量数据样本(特征 X 和标签 y ),通过调用反向函数 backward 计算小批量随机梯度,并调用优化算法 sgd 迭代模型参数。由于我们之前设批量大小 batch_size 为10,每个小批量的损失 l 的形状为(10, 1)。回忆一下自动 求梯度一节。由于变量 l 并不是一个标量,所以我们可以调用 .sum() 将其求和得到一个标量,再运行 l.backward() 得到该变量有关模型参数的梯度。注意在每次更新完参数后不要忘了将参数的梯度清零。(如果不清零,PyTorch默认会对梯度进行累加)

对于这种我们自己定义的变量,我们称之为叶子节点(leaf nodes),而基于叶子节点得到的中间或最终变量则可称之为结果节点

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.)

z对x求导为:2,z对y求导为:1

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

求一个矩阵对另一矩阵的导数束手无策。

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

【点乘:对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作】

 如:

x = torch.tensor([2., 1.], requires_grad=True)
y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)

z = torch.mm(x.view(1, 2), 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.]])

结果解释如下:

查看梯度以及参数更新的问题 

import torch 
import torch.nn as nn
import numpy as np
import torch.optim as optim
from torchsummary import summary
import os
from torch.utils.data.dataset import Dataset
from torch.utils.data import DataLoader
from tqdm import tqdm


# 设置一下数据集   数据集的构成是随机两个整数,形成一个加法的效果 input1 + input2 = label
class TrainDataset(Dataset):
    def __init__(self):
        super(TrainDataset, self).__init__()
        self.data = []
        for i in range(1,1000):
            for j in range(1,1000):
                self.data.append([i,j])
    def __getitem__(self, index):
        input_data = self.data[index]
        label = input_data[0] + input_data[1]
        return torch.Tensor(input_data),torch.Tensor([label])
    def __len__(self):
        return len(self.data)

class TestNet(nn.Module):
    def __init__(self):
        super(TestNet, self).__init__()
        self.net1 = nn.Linear(2,1)

    def forward(self, x):
        x = self.net1(x)
        return x

def train():
    traindataset = TrainDataset()
    traindataloader = DataLoader(dataset = traindataset,batch_size=1,shuffle=False)
    testnet = TestNet().cuda()
    myloss = nn.MSELoss().cuda()
    optimizer = optim.SGD(testnet.parameters(), lr=0.001 )
    for epoch in range(100):
        for data,label in traindataloader :
            print("\n=====迭代开始=====")
            data = data.cuda()
            label = label.cuda()
            output = testnet(data)
            print("输入数据:",data)
            print("输出数据:",output)
            print("标签:",label)
            loss = myloss(output,label)
            optimizer.zero_grad()
            for name, parms in testnet.named_parameters():	
                print('-->name:', name)
                print('-->para:', parms)
                print('-->grad_requirs:',parms.requires_grad)
                print('-->grad_value:',parms.grad)
                print("===")
            loss.backward()
            optimizer.step()
            print("=============更新之后===========")
            for name, parms in testnet.named_parameters():	
                print('-->name:', name)
                print('-->para:', parms)
                print('-->grad_requirs:',parms.requires_grad)
                print('-->grad_value:',parms.grad)
                print("===")
            print(optimizer)
            input("=====迭代结束=====")



if __name__ == '__main__':

    os.environ["CUDA_VISIBLE_DEVICES"] = "{}".format(3)
    train()


 

 image-20210817213402502

 

 

参考自:动手学深度学习(Pytorch)第2章深度学习基础-上 - 知乎

Pytorch autograd,backward详解 - 知乎

Pytorch 模型 查看网络参数的梯度以及参数更新是否正确,优化器学习率设置固定的学习率,分层设置学习率_呆呆象呆呆的博客-CSDN博客

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 如果您想在Python中反向迭代一个序列,可以使用reversed()函数。例如,如果您有一个列表,您可以使用下面的代码反向迭代它: ``` my_list = [1, 2, 3, 4, 5] for i in reversed(my_list): print(i) ``` 这将输出: ``` 5 4 3 2 1 ``` 如果您想在字符串中反向迭代字符,可以使用切片操作符`[::-1]`。例如: ``` my_str = "Hello world!" for char in my_str[::-1]: print(char) ``` 这将输出: ``` ! d l r o w o l l e H ``` ### 回答2: "backward python"是指在Python中用于反向迭代的概念和技术。 在Python中,我们通常通过循环结构来迭代访问集合中的元素,比如使用for循环来遍历列表、字符串、字典等。这种迭代的方式是从集合的第一个元素开始逐个访问,直到最后一个元素。 然而,有时候我们需要以相反的顺序进行迭代,即从集合的最后一个元素开始逐个访问,直到第一个元素。这就是所谓的反向迭代。 Python提供了多种实现反向迭代的方法。其中一种常见的方法是使用内置函数reversed()。reversed()函数用于返回一个迭代器对象,该对象按照与给定序列相反的顺序生成元素。我们可以使用for循环结合reversed()函数来进行反向迭代。 另一种方法是使用切片。我们可以使用切片操作符[:]来获取集合的反向副本,然后对该副本进行正向迭代。例如,可以使用[::-1]来获取列表的反向副本。 此外,我们还可以使用range()函数结合倒序索引来进行反向迭代。通过指定合适的起始值、结束值和步长,我们可以创建一个逆序的整数序列,并通过for循环进行迭代。 总的来说,"backward python"是指在Python中进行反向迭代的概念和技术,通过使用reversed()函数、切片、倒序索引等方法,我们可以方便地实现对集合的反向访问。 ### 回答3: backward python 是指在编写代码的过程中,将程序的执行顺序从后往前进行的一种编程方法。与传统的顺序编程相比,backward python 更注重程序的结果,而不是过程。 backward python 的好处之一是可以更快地得到程序的输出结果。因为在编写代码时,我们先确定了程序的目标和预期结果,然后再根据目标逆向推导出程序的具体实现。这样做可以避免重复的试错过程,提高开发效率,特别是对于大型项目或者复杂的算法,效果更加明显。 此外,backward python 还能够使程序更加易于维护和修改。由于代码是从后往前编写的,所以修改程序时只需要关注与目标有关的部分,而无需深入理解整个程序的细节。这样,在增加新功能、修复 bug 或者进行代码重构时,能够更加高效和稳定地进行。 然而,backward python 也存在一些局限性。首先,逆向推导代码的过程需要较强的抽象和逻辑思维能力。尤其是对于一些复杂的问题,需要具备更高的专业知识和解决问题的能力。其次,由于程序的执行顺序与正常的代码风格不同,可能导致其他人阅读和理解代码时出现困惑,降低代码的可读性和可维护性。 综上所述,backward python 是一种以结果为导向的编程方法,在一些特定的场景下,可以提高开发效率和代码的可维护性,但要注意在实际应用中权衡利弊,并根据项目的需求和团队的实际情况进行取舍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值