关于backward()的一些理解

1. requires_grad 的含义及标志位说明

requires_gard 是Tensor变量的一个属性,一般默认为False。另外,0.4.0版本的 Pytorch 将 Variable 和 Tensor 合并,统称为 Tensor,在过去的版本中,requires_grad属性是Variable封装的属性

  • 如果对于某Variable 变量 x ,其 x.requires_grad == True, 则表示它可以参与求导,也可以从它向后求导。默认情况下,一个新的Variables 的 requires_grad 和 volatile 都等于 False

  • requires_grad == True 具有传递性:
    如果:x.requires_grad == True,y.requires_grad == False , z=f(x,y)则, z.requires_grad == True

  • 凡是参与运算的变量(包括 输入量,中间输出量,输出量,网络权重参数等),都可以设置 requires_grad

  • volatileTrue 就等价于 requires_gradFalse 。volatileTrue 同样具有传递性。一般只用在inference过程中。若是某个过程,从 x 开始 都只需做预测,不需反传梯度的话,那么只需设置.volatile=True ,那么 x 以后的运算过程的输出均为 volatileTrue ,即 requires_grad==False 。
    虽然inference 过程不必backward(),所以requires_grad 的值为False 或 True,对结果是没有影响的,但是对程序的运算效率有直接影响;所以使用volatile=True ,就不必把运算过程中所有参数都手动设一遍requires_grad=False 了,方便快捷.

  • detach() ,如果 x 为中间输出,x’ = x.detach 表示创建一个与 x 相同,但requires_grad==False 的variable, (实际上是把x’ 以前的计算图 grad_fn 都消除了),x’ 也就成了叶节点。原先反向传播时,回传到x时还会继续,而现在回到x’处后,就结束了,不继续回传求到了。另外值得注意, x (variable类型) 和 x’ (variable类型)都指向同一个Tensor ,即 x.data,而detach_() 表示不创建新变量,而是直接修改 x 本身

  • retain_graph ,每次 backward() 时,默认会把整个计算图free掉。一般情况下是每次迭代,只需一次 forward() 和一次 backward() ,前向运算forward() 和反向传播backward()是成对存在的,一般一次backward()也是够用的。但是不排除,由于自定义loss等的复杂性,需要一次forward(),多个不同loss的backward()来累积同一个网络的grad,来更新参数。于是,若在当前backward()后,不执行forward() 而可以执行另一个backward(),需要在当前backward()时,指定保留计算图,即backward(retain_graph)

2. 反向求导和权重更新

  • 求导和优化(权重更新)是两个独立的过程,只不过优化时一定需要对应的已求取的梯度值。所以求得梯度值很关键,而且,经常会累积多种loss对某网络参数造成的梯度,一并更新网络

  • 反向传播过程中,肯定需要整个过程都链式求导。虽然中间参数参与求导,但是却可以不用于更新该处的网络参数。参数更新可以只更新想要更新的网络的参数

  • 如果obj是函数运算结果,且是标量,则 obj.backward() (注意,backward()函数中没有填入任何tensor值, 就相当于 backward(torch.tensor([1])) )

  • 对于继承自 nn.Module 的某一网络 net 或网络层,定义好后,发现 默认情况下,net.paramters 的 requires_grad 就是 True 的(虽然只是实验证明的,还未从源码处找到证据),这跟普通的Variable张量不同。因此,当x.requires_grad == False , y = net(x) 后, 有 y.requires_grad == True ;但值得注意,虽然nn.xxloss和激活层函数,是继承nn.Module的,但是这两种并没有网络参数,就更谈不上 paramters.requires_grad 的值了。所以类似这两种函数的输出,其requires_grad只跟输入有关,不一定是 True

3. 计算图相关

  • 计算图就是模型 前向forward() 和后向求梯度backward() 的流程参照

  • 能获取回传梯度(grad)的只有计算图的叶节点。注意是获取,而不是求取。中间节点的梯度在计算求取并回传之后就会被释放掉,没办法获取。想要获取中间节点梯度,可以使用 register_hook (钩子)函数工具。当然, register_hook 不仅仅只有这个作用

  • 只有标量才能直接使用 backward(),即loss.backward() , pytorch 框架中的各种nn.xxLoss(),得出的都是minibatch 中各结果 平均/求和 后的值。如果使用自定义的函数,得到的不是标量,则backward()时需要传入 grad_variable 参数

  • 经常会有这样的情况:
    x1 —> |net1| —> y1 —> |net2| —> z1 , net1和net2是两个不同的网络。x1 依次通过 两个网络运算,生成 z1 。比较担心一次性运算后,再backward(),是不是只更新net1 而不是net1、net2都更新呢?
    类比 x2 —> |f1| —> y2 —> |f2| —> z2 , f1 、f2 是两个普通的函数,z2=f2(y2) , y2=f1(x2)

按照以下格式实验:

w1 = torch.Tensor([2]) #认为w1 与 w2 是函数f1 与 f2的参数
w1 = Variable(w1,requires_grad=True)
w2 = torch.Tensor([2])
w2 = Variable(w2,requires_grad=True)
x2 = torch.rand(1)
x2 = Variable(x2,requires_grad=True)
y2 = x2**w1 # f1 运算
z2 = w2*y2+1           # f2 运算
z2.backward()
print(x2.grad)
print(y2.grad)
print(w1.grad)
print(w2.grad)

发现 x2.grad,w1.grad,w2.grad 是个值 ,但是 y2.grad 却是 None, 说明x2,w1,w2的梯度保留了,y2 的梯度获取不到。实际上,仔细想一想会发现,x2,w1,w2均为叶节点。在这棵计算树中 ,x2 与w1 是同一深度(底层)的叶节点,y2与w2 是同一深度,w2 是单独的叶节点,而y2 是x2 与 w1 的父节点,所以只有y2没有保留梯度值, 印证了之前的说法。同样这也说明,计算图本质就是一个类似二叉树的结构。

那么对于 两个网络,会是怎么样呢?我使用pytorch 的cifar10 例程,稍作改动做了实验。把例程中使用的一个 Alexnet 拆成了两个net ------ net1 和 net2 。

optimizer = torch.optim.SGD(itertools.chain(net1.parameters(), net2.parameters()),lr=0.001, momentum=0.9) # 这里 net1 和net2 优化的先后没有区别 !!
#
optimizer.zero_grad() #将参数的grad值初始化为0
#
# forward + backward + optimize
outputs1 = net1(inputs) #input 未置requires_grad为True,但不影响
outputs2 = net2(outputs1)
loss = criterion(outputs2, labels) #计算损失
loss.backward() #反向传播
#
print("inputs.requires_grad:")
print(inputs.requires_grad) # False
print("the grad of inputs:")
print(inputs.grad) # None
    
print("outputs1.requires_grad:")
print(outputs1.requires_grad) # True
print("the grad of outputs1:")
print(outputs1.grad) # None
#
print("the grad of net1:")
print(net1.conv1.bias.grad) # no-None
print("the grad of net2:")
print(net2.fc3.bias.grad) # no-None
#
optimizer.step() #用SGD更新参数

后缀注释就是打印的结果。可以看出,只有网络参数的grad是直接可获取的。而且是两个网络都可以获取grad 值,获取grad后,当然就可以更新网络的参数了,两个网络都是可以更新的。

类比上边例子的解释,两个网络其实就是处在叶节点的位置,只不过深度不同。同理,网络内部的运算,每一层网络权重参数其实也是处在叶节点上,只不过在树中的深度不同罢了,前向运算时按照二叉树的结构,不断生成父节点。

事实上,原先是以为 网络 与 普通函数不同,因为它具有register_xx_hook()这个类函数工具,所以认为它可以默认保存权重参数的grad来用于更新,后来才明白,本质上与普通函数的参数一样,都是处在叶节点,就可以保存参数的grad,至于register_xx_hook(),看来是另做它用,或者说用register_xx_hook()可以记录甚至更改中间节点的grad值。

4. 一些特殊情况

如果想把网络某一部分参数固定,不让其被训练,可以使用requires_grad.

for p in sub_module.parameters():
  p.requires_grad = False

可以这样理解,因为是叶节点(而不是中间节点),所以不求grad(grad为’None’),也不会影响网络的正常反向传播

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答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 是一种以结果为导向的编程方法,在一些特定的场景下,可以提高开发效率和代码的可维护性,但要注意在实际应用中权衡利弊,并根据项目的需求和团队的实际情况进行取舍。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值