【26】pytorch中的grad求导说明以及利用backward获取梯度信息

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 【26】pytorch中的grad求导说明以及利用backward获取梯度信息

1. pytorch关于grad的简单测试


1.1 标量对向量求导

image.png

# test grad 1
x = torch.tensor([2], dtype=torch.float, requires_grad=True)
y = 3*torch.pow(x, 2) + 2*x
y.backward()
x.grad   # 3*(2*x)+2=14


输出:

tensor([14.])


1.2 矩阵对矩阵求导

如果x的值是一个列表的话,也就是需要对多个输入进行求导,这时候就不能简单的进行y.backward()来求得x的梯度信息了,需要使用backward中的gradient参数,或者是autograd.backward中的grad_tensors参数,这两者是等价的,因为输入是一个列表参数,此时y也应该是一个列表。假若输入:x = [ 2 , 3 , 4 ] ,那么输出的梯度信息应该为x . g r a d = [ 14 , 20 , 26 ] 。


测试代码如下:

# test grad 2
x = torch.tensor([2, 3, 4], dtype=torch.float, requires_grad=True)
y = 3*torch.pow(x, 2) + 2*x
print("y:",y)
# ps: 对于一个向量矩阵进行反向传播需要gradient这个参数
torch.autograd.backward(y, retain_graph=True, 
                        grad_tensors=torch.tensor([1,1,1], dtype=torch.float32))
# y.backward(retain_graph=True,
#             gradient=torch.tensor([1,1,1], dtype=torch.float32))
print("grad_tensors=torch.tensor([1,1,1]:\n",x.grad)   # tensor([14., 20., 26.])
torch.autograd.backward(y, retain_graph=True, 
                        grad_tensors=torch.tensor([3,2,1], dtype=torch.float32))
# y.backward(retain_graph=True,
#             gradient=torch.tensor([3,2,1], dtype=torch.float32))
print("grad_tensors=torch.tensor([3,2,1]:\n",x.grad)   # tensor([42., 40., 26.])


输出:

y: tensor([16., 33., 56.], grad_fn=<AddBackward0>)
grad_tensors=torch.tensor([1,1,1]:
 tensor([14., 20., 26.])
grad_tensors=torch.tensor([3,2,1]:
 tensor([56., 60., 52.])


或者可以注意到我注释的内容,其两者是等价的:

# test grad 3
x = torch.tensor([2, 3, 4], dtype=torch.float, requires_grad=True)
y = 3*torch.pow(x, 2) + 2*x
print("y:",y)
# ps: 对于一个向量矩阵进行反向传播需要gradient这个参数
# torch.autograd.backward(y, retain_graph=True, 
#                         grad_tensors=torch.tensor([1,1,1], dtype=torch.float32))
y.backward(retain_graph=True,
            gradient=torch.tensor([1,1,1], dtype=torch.float32))
print("grad_tensors=torch.tensor([1,1,1]:\n",x.grad)   # tensor([14., 20., 26.])
# torch.autograd.backward(y, retain_graph=True, 
#                         grad_tensors=torch.tensor([3,2,1], dtype=torch.float32))
y.backward(retain_graph=True,
            gradient=torch.tensor([3,2,1], dtype=torch.float32))
print("grad_tensors=torch.tensor([3,2,1]:\n",x.grad)   # tensor([42., 40., 26.])


输出的内容和上面的代码输出是一样的:

y: tensor([16., 33., 56.], grad_fn=<AddBackward0>)
grad_tensors=torch.tensor([1,1,1]:
 tensor([14., 20., 26.])
grad_tensors=torch.tensor([3,2,1]:
 tensor([56., 60., 52.])


原因:pytorch在求导的过程中,分为下面两种情况:

  • 如果是标量对向量求导(scalar对tensor求导):那么就可以保证上面的计算图的根节点只有一个,此时不用引入grad_tensors参数,直接调用backward函数即可,如第一种情况。
  • 如果是(向量)矩阵对(向量)矩阵求导(tensor对tensor求导):实际上是先求出Jacobian矩阵中每一个元素的梯度值(每一个元素的梯度值的求解过程对应上面的计算图的求解方法),然后将这个Jacobian矩阵与grad_tensors参数对应的矩阵进行对应的点乘,得到最终的结果。如第二种情况。


此外,还需要注意,tensor.backward()中的gradient参数与torch.autograd.backward()中的参数grad_tensors的用法是一样的,但名称不一样;也就是以上两行的代码结果一样.所以可以看见输出的结果也是一样的。


2. pytorch获取网络输入的梯度信息


这里通过简单的搭建一个卷积层和一个全连接层来进行理论计算关于矩阵输入的梯度信息,实验的参数与大致计算流程如下图所示,这里的图表内容来自于博主:太阳花的小绿豆,详细见参考资料[2]:

image.png

实验代码如下:

import torch
import torch.nn as nn
x = torch.tensor([1, 2, 3, 1, 1, 2, 2, 1, 2], 
                 dtype=torch.float32, requires_grad=True).reshape(1,1,3,3)
# x = torch.autograd.Variable(x, requires_grad=True)
x.retain_grad()
print("input:",x)
conv = nn.Conv2d(1,1,kernel_size=(2,2),bias=False)
conv_weight = torch.tensor([1,0,1,2],dtype=torch.float32).reshape(1,1,2,2)
conv.load_state_dict({"weight": conv_weight})
# handle1 = conv.register_full_backward_hook(save_gradient)
conv_out = conv(x)
print("conv output:", conv_out, "\nconv output shape:", conv_out.shape)
fc = nn.Linear(4, 2, bias=False)
fc_weight = torch.tensor([[0,1,0,1],
                         [1,0,1,1]], dtype=torch.float32)
fc.load_state_dict({"weight":fc_weight})
fc_out = fc(conv_out.reshape(1,-1))
print("fc_out output:", fc_out, "\nfc_out output shape:", fc_out.shape)
# 文档中retain_graph和create_graph两个参数作用相同,因为前者是保持计算图不释放,而后者是创建计算图
# fc_out[0][0].backward()
torch.autograd.backward(fc_out[0][0], retain_graph=True, create_graph=False)
print("fc_out[0][0].backward:\n",x.grad)
# 清楚梯度,否则会累加
x.grad.zero_()
torch.autograd.backward(fc_out[0][1], retain_graph=False, create_graph=True)
print("fc_out[0][1].backward:\n",x.grad)


输出:

input: tensor([[[[1., 2., 3.],
          [1., 1., 2.],
          [2., 1., 2.]]]], grad_fn=<ViewBackward>)
conv output: tensor([[[[4., 7.],
          [5., 6.]]]], grad_fn=<ThnnConv2DBackward>) 
conv output shape: torch.Size([1, 1, 2, 2])
fc_out output: tensor([[13., 15.]], grad_fn=<MmBackward>) 
fc_out output shape: torch.Size([1, 2])
fc_out[0][0].backward:
 tensor([[[[0., 1., 0.],
          [0., 2., 2.],
          [0., 1., 2.]]]])
fc_out[0][1].backward:
 tensor([[[[1., 0., 0.],
          [2., 3., 0.],
          [1., 3., 2.]]]], grad_fn=<AddBackward0>)

image.png


image.png


image.png


所以,可以看见,手动计算的结果和pytorch的计算结果是一样的。


3. pytorch获取中间过程的梯度信息


对于之前的实验都是根据输入获取反向传播重新得到了输入的梯度,那么如何能获得中间过程中的梯度呢?


当通过神经网络进行训练时,我们所要提取的就是卷积神经网络最后一层的特征层的反向梯度信息。为此,得到了这个方向传播的梯度信息就可以做一个全局平均当成是一个当前channel的一个权重,从而可以作一个加权和的操作得到最后的grad-cam操作。所以,这一小节中,pytorch获取中间过程的梯度信息是为grad-cam可视化作准备的。


假设,一下内容以resnet50为例,如何提取之后layer4的最后一个卷积输出的反向梯度信息,以下代码可以提取最后一个卷积输出的反向梯度信息。


import torch
import torch.nn as nn
from torchvision.models import resnet50
input_grad = []
output_grad = []
def save_gradient(module, grad_input, grad_output):
    input_grad.append(grad_input)
    print(f"{module.__class__.__name__} input grad:\n{grad_input}\n")
    output_grad.append(grad_output)
    print(f"{module.__class__.__name__} output grad:\n{grad_output}\n")
model = resnet50(pretrained=True)
last_layer = model.layer4[-1]
last_layer.conv3.register_full_backward_hook(save_gradient)
input = torch.rand([8, 3, 224, 224], dtype=torch.float, requires_grad=True)
output = model(input)
print("output.shape:", output.shape)
output[0][0].backward()
gard_info = input.grad
print("gard_info.shape: ", gard_info.shape)
# print("input_grad:", input_grad)
# print("output_grad:", output_grad)


输出:


output.shape: torch.Size([8, 1000])
Conv2d input grad:
(tensor([[[[-1.6128e-02, -1.0101e-02, -1.4746e-02,  ..., -1.6404e-02,
           -8.5241e-03, -2.0387e-02],
          [-1.6439e-03, -2.9773e-03, -8.4831e-03,  ..., -2.3907e-02,
           -1.5434e-02, -1.8303e-02],
          [-8.1337e-03, -1.1036e-02, -1.2853e-02,  ..., -1.0207e-02,
           -2.2195e-02, -1.2312e-02],
          ...,
Conv2d output grad:
(tensor([[[[ 3.1618e-04, -4.9662e-03, -5.0071e-03,  ...,  2.5234e-04,
            2.4588e-04,  3.0680e-04],
          [ 2.5217e-04,  2.4869e-04,  2.8324e-04,  ...,  3.4220e-04,
           -4.9799e-03, -4.9050e-03],
          [ 3.1466e-04, -5.0282e-03, -5.0318e-03,  ...,  2.9681e-04,
           -5.0313e-03, -4.8814e-03],
          ...,
gard_info.shape:  torch.Size([8, 3, 224, 224])


这里的输出过长,就不全部展示了。


以上对于简单的backward,pytorch中只会自动的获取最后的输入梯度信息,但是对于网络中间特征层的梯度信息一般是不会保留的,所以需要利用一个列表将这些动态数据梯度信息保留下来。由此可以解决pytorch获取中间过程的梯度信息问体。


但是resnet模型结构是提前设定的,如何选择某一层的feature map输出,是第二个问题。关于这个问题,由于这里是保留了模型原有的预训练参数的,也就是只是一个推理过程,不需要训练,所以我使用了以下方法实现:


import torch
from torchvision.models import resnet50
def get_feature_map(model, input_tensor):
    x = model.conv1(input_tensor)
    x = model.bn1(x)
    x = model.relu(x)
    x = model.maxpool(x)
    x = model.layer1(x)
    x = model.layer2(x)
    x = model.layer3(x)
    x = model.layer4(x)
    return x
# get output and feature
model = resnet50(pretrained=True)
feature = get_feature_map(model, input)


在下一篇内容中会大概构建一下grad-cam的逻辑过程以及效果。


参考资料:

pytorch中backward()函数详解

Grad-CAM简介


相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
7月前
|
机器学习/深度学习 算法 PyTorch
Pytorch自动求导机制详解
在深度学习中,我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率,可以告诉我们如何调整模型的参数以使损失函数最小化。自动求导是一种计算梯度的技术,它允许我们在定义模型时不需要手动推导梯度计算公式。PyTorch 提供了自动求导的功能,使得梯度的计算变得非常简单和高效。
161 0
|
7月前
|
机器学习/深度学习 人工智能 算法
【PyTorch深度强化学习】TD3算法(双延迟-确定策略梯度算法)的讲解及实战(超详细 附源码)
【PyTorch深度强化学习】TD3算法(双延迟-确定策略梯度算法)的讲解及实战(超详细 附源码)
1257 1
|
2月前
|
算法 PyTorch 算法框架/工具
Pytorch学习笔记(九):Pytorch模型的FLOPs、模型参数量等信息输出(torchstat、thop、ptflops、torchsummary)
本文介绍了如何使用torchstat、thop、ptflops和torchsummary等工具来计算Pytorch模型的FLOPs、模型参数量等信息。
392 2
|
7月前
|
机器学习/深度学习 缓存 自然语言处理
PyTorch使用Tricks:梯度裁剪-防止梯度爆炸或梯度消失 !!
PyTorch使用Tricks:梯度裁剪-防止梯度爆炸或梯度消失 !!
617 0
|
7月前
|
机器学习/深度学习 PyTorch 算法框架/工具
pytorch中非标量调用backward()的时候为什么要先进行sum操作
在《动手学深度学习》第二版教程中,当y为非标量的时候,调用backward()函数时,为什么先要求和呢(即y.sum().backward()),估计很多读者有点懵,今天小编给大家说说我的理解。
433 3
|
7月前
|
机器学习/深度学习 算法 PyTorch
【PyTorch深度强化学习】带基线的蒙特卡洛策略梯度法(REINFOECE)在短走廊和CartPole环境下的实战(超详细 附源码)
【PyTorch深度强化学习】带基线的蒙特卡洛策略梯度法(REINFOECE)在短走廊和CartPole环境下的实战(超详细 附源码)
135 0
|
7月前
|
机器学习/深度学习 算法 关系型数据库
PyTorch深度强化学习中蒙特卡洛策略梯度法在短走廊环境(CartPole-v0)中的实战(超详细 附源码)
PyTorch深度强化学习中蒙特卡洛策略梯度法在短走廊环境(CartPole-v0)中的实战(超详细 附源码)
86 0
|
7月前
|
机器学习/深度学习 并行计算 PyTorch
PyTorch使用快速梯度符号攻击(FGSM)实现对抗性样本生成(附源码和数据集MNIST手写数字)
PyTorch使用快速梯度符号攻击(FGSM)实现对抗性样本生成(附源码和数据集MNIST手写数字)
163 0
|
PyTorch Serverless 算法框架/工具
Pytorch与autograd自动求导
Pytorch与autograd自动求导
96 0
|
人工智能 自然语言处理 PyTorch
NLP信息抽取全解析:从命名实体到事件抽取的PyTorch实战指南
NLP信息抽取全解析:从命名实体到事件抽取的PyTorch实战指南
821 0

热门文章

最新文章