PyTorch笔记--关于backward()

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

 为了能够记录张量的梯度,在创建张量的时候可以设置参数requires_grad = True,或者创建张量后调用requires_grad_()方法。

>>> x = torch.randn(2, 2, requires_grad = True)

 

>>> x = torch.randn(3, 3)
>>> x.requires_grad_()

同时由它计算得到的中间张量也会自动被设置成requires_grad = True,下面的程序中y = x2,y.requires_grad = True

>>> x = torch.randn(3, 3)
>>> x.requires_grad_()
tensor([[ 0.6734, -2.4904,  2.0093],
        [-0.2601, -0.3734, -1.5601],
        [ 0.9121,  0.3902,  1.0404]], requires_grad=True)
>>> y = x.pow(2)  # y = x*x
>>> y.requires_grad
True

这里可以使用反向传播来计算x的梯度值。需要注意的是y是一个张量,不是标量,并不能直接使用backward方法(pytorch不允许张量对张量求导)。

可以采取求和的方式将y变成一个标量。代码如下

>>> y = x.pow(2).sum()
>>> y.backward()
>>> x.grad
tensor([[ 1.3468, -4.9807,  4.0187],
        [-0.5203, -0.7469, -3.1202],
        [ 1.8243,  0.7804,  2.0808]])

我的理解如下(可能是错的)

假设(这里使用1维的张量举例,没有完全和代码中的对应)

PyTorch笔记--关于backward()

 

 

 其中

PyTorch笔记--关于backward()

 

 

 就是直接执行下面一段程序的结果

>>> y = x.pow(2) # y = x*x
>>> y
tensor([[0.4534, 6.2019, 4.0375],
        [0.0677, 0.1394, 2.4339],
        [0.8320, 0.1523, 1.0824]], grad_fn=<PowBackward0>)

是对x的每一项单独求平方,最后得到的y是与x同样shape的张量。

如果对其求和,表达式就变成了

PyTorch笔记--关于backward()

 对每一项的平方求和后,显然y就是一个标量了。

>>> y = x.pow(2).sum()
>>> y  # 输出y的值
tensor(15.4006, grad_fn=<SumBackward0>)

重点在于上面提到的这个表达式

PyTorch笔记--关于backward()

 

 

 

对xj求偏导,将求和公式展开可以知道,其他的项对求导的结果是不影响的(因为只有yj是xj的表达式),

PyTorch笔记--关于backward()

 

 

 所以使用标量y对x的每一项求导,结果依然是正确的。同时求和后y成为了一个标量,也符合了pytorch在语法规则上的要求。

所以是相当于我们在不影响求导结果的前提下,对原来的表达式做了适当的变换,使其计算结果成为了一个标量,再使用这个

变换了的表达式对张量里的每一项进行求导。

 

同时,pytorch也提供了另一种方法辅助我们能够完成张量对张量的求导。就是使用grad_tensors参数。

torch.autograd.backward(
        tensors,  # 要计算导数的张量 torch.autograd.backward(tensor)和tensor.backward()作用是等价的
        grad_tensors=None,  # 在用非标量进行求导时需要使用该参数
        retain_graph=None, # 保留计算图
        create_graph=False, 
        grad_variables=None)

使用grad_tensors参数:

  • 它是非标量(y)进行求导时才使用
  • 它的大小需要与张量x(y=f(x))的大小相同
  • 它在每一个元素是全1是就是正常的求导
  • 可以调整它的值来针对每一项在求导时占据的权重。

下面是使用该参数进行求导的示例。在文章最后给的第一个链接中对其有详细的解释,这里做一个整理。

>>> x.grad.zero_()  # 梯度清零
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
>>> grad_tensors = torch.ones_like(x)  # 生成和x相同shape的全为1的张量
>>> y = x.pow(2) # y = x*x
>>> y.backward(grad_tensors) # 将grad_tensors作为backward方法的输入
>>> x.grad  # 张量x的梯度
tensor([[ 1.3468, -4.9807,  4.0187],
        [-0.5203, -0.7469, -3.1202],
        [ 1.8243,  0.7804,  2.0808]])

其中

>>> x.grad.zero_() # 梯度清零

是将原来计算的梯度清零,因为张量绑定的梯度张量在不清空的情况下会逐渐累积。

上面给出的代码可以看到,当我们建立了一个与x相同的shape且元素值都是1的grad_tensors张量后,并将其作为backward()方法的输入,

最终的求导结果也是正确的,它同样实现了张量对张量的求导。

>>> grad_tensors
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])

 

grad_tensors是如何起作用的呢?

它是将对y的求导转换为对y·grad_tensors的求导。点乘后的结果是一个标量,再用这个新的表达式进行求导。

所以当grad_tensors的值都是1时,本质上还是对每一项进行求和。

grad_tensors张量的值相当于对对应位置的项求导前加了个系数。示例如下

>>> grad_tensors=torch.tensor([[1.0, 1.0, 1.0], [0.1, 0.1, 0.1], [0.01, 0.01, 0.01]])
>>> y = x.pow(2)
>>> x.grad.zero_()
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
>>> y.backward(grad_tensors)
>>> x.grad
tensor([[ 1.3468, -4.9807,  4.0187],
        [-0.0520, -0.0747, -0.3120],
        [ 0.0182,  0.0078,  0.0208]])

 

下面是grad_tensors的取值和最后的结果的对应:

>>> grad_tensors
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
>>> x.grad
tensor([[ 1.3468, -4.9807,  4.0187],
        [-0.5203, -0.7469, -3.1202],
        [ 1.8243,  0.7804,  2.0808]])

 

>>> grad_tensors
tensor([[1.0000, 1.0000, 1.0000],
        [0.1000, 0.1000, 0.1000],
        [0.0100, 0.0100, 0.0100]])
>>> x.grad  # 可以简单地理解为每一项乘了一个grad_tensors相同位置的值
tensor([[ 1.3468, -4.9807,  4.0187],
        [-0.0520, -0.0747, -0.3120],
        [ 0.0182,  0.0078,  0.0208]])

 

参考:

https://blog.csdn.net/qq_27825451/article/details/89393332

https://book.51cto.com/art/202103/650997.htm

https://www.cnblogs.com/marsggbo/p/11549631.html

《深入浅出PyTorch》张校捷

 

上一篇:1 预备知识


下一篇:新版本浏览器跨站cookie 获取问题