pytorch 查看中间变量的梯度

本文介绍如何在PyTorch中使用register_hook接口保存中间变量的梯度,以便于开发者验证实现的正确性。通过示例代码展示了如何为特定中间变量注册梯度保存接口,并在反向传播后获取其梯度值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pytorch 为了节省显存,在反向传播的过程中只针对计算图中的叶子结点(leaf variable)保留了梯度值(gradient)。但对于开发者来说,有时我们希望探测某些中间变量(intermediate variable) 的梯度来验证我们的实现是否有误,这个过程就需要用到 tensor的register_hook接口。一段简单的示例代码如下,代码主要来自pytorch开发者的回答,笔者稍作修改使其更符合最新版的pytorch 语法(v1.2.0)。

grads = {}

def save_grad(name):
    def hook(grad):
        grads[name] = grad
    return hook

x = torch.randn(1, requires_grad=True)
y = 3*x
z = y * y

# 为中间变量注册梯度保存接口,存储梯度时名字为 y。
y.register_hook(save_grad('y'))

# 反向传播 
z.backward()

# 查看 y 的梯度值
print(grads['y'])

一个示例输出是:

tensor([-1.5435])

转载于:https://www.cnblogs.com/SivilTaram/p/pytorch_intermediate_variable_gradient.html

<think>在PyTorch中,变量(通常指`torch.Tensor`)参与梯度计算的条件是:1.该张量被设置为需要梯度(requires_grad=True)。2.该张量参与了一个计算图(即参与了一个可导运算,并且该运算的结果被用于计算最终的损失函数)。3.在计算最终标量(通常是损失)后,调用`.backward()`方法,此时会从该标量回溯计算图,计算图中所有具有`requires_grad=True`的张量的梯度。具体来说:-当我们创建一个张量时,可以通过设置`requires_grad=True`来要求PyTorch跟踪该张量上的所有操作,以便后续进行梯度计算。-由具有`requires_grad=True`的张量经过一系列运算得到的张量,也会自动具有`requires_grad=True`(除非中间有操作显式地关闭了梯度跟踪)。-当调用`.backward()`方法时,只有那些在计算图中参与了到达损失函数的路径的张量,并且其`requires_grad`为True,才会计算梯度。注意:即使一个张量设置了`requires_grad=True`,但如果它没有参与任何导致损失函数的计算,那么它的梯度将保持为None(即不会被计算)。下面我们通过一个简单的代码示例来演示:```pythonimporttorch#创建两个张量,并设置requires_grad=True以跟踪梯度x=torch.tensor([1.0,2.0],requires_grad=True)y=torch.tensor([3.0,4.0],requires_grad=True)#进行运算z=x*y#逐元素相乘#此时z也是一个张量,并且z.requires_grad为True#因为z是由x和y计算而来的,而x和y都需要梯度#再进一步,计算一个标量(损失)loss=z.sum()#将z的所有元素求和,得到一个标量#调用.backward()来计算梯度loss.backward()#现在,x和y的梯度已经被计算出来了print(x.grad)#输出:tensor([3.,4.])print(y.grad)#输出:tensor([1.,2.])```解释:-我们创建了两个张量x和y,并将它们的`requires_grad`设置为True,这样PyTorch会跟踪所有涉及它们的操作。-然后我们计算z=x*y(逐元素相乘),所以z=[1*3,2*4]=[3,8]。-接着,我们计算了损失函数loss,它是z的所有元素之和:3+8=11。-当我们调用`loss.backward()`时,PyTorch会自动计算loss相对于x和y的梯度(即d(loss)/dx和d(loss)/dy)。-根据链式法则,d(loss)/dx=d(loss)/dz*dz/dx。由于loss是z的和,所以d(loss)/dz=[1,1](因为对每个元素求导都是1)。而dz/dx(即z对x的导数)就是y的(因为z=x*y,所以对x的导数是y)。因此,d(loss)/dx=[1*3,1*4]=[3,4]。-同理,d(loss)/dy=[1,1]*x=[1*1,1*2]=[1,2]。注意事项:1.只有标量(scalar)可以直接调用`.backward()`。如果是一个向量(非标量),则需要传入一个与向量同形的张量作为梯度参数(即`gradient`参数)。但在深度学习中,我们通常先计算一个标量损失,然后调用`backward()`。2.在不需要梯度的代码块中(例如模型评估),可以使用`withtorch.no_grad():`来临时禁止梯度计算,以提高效率并减少内存消耗。3.梯度是累加的。在训练循环中,我们通常在每次反向传播前将梯度清零,以避免梯度累积。下面再给出一个在训练循环中使用的例子:```pythonimporttorchimporttorch.nnasnn#创建一个简单的线性模型model=nn.Linear(2,1)optimizer=torch.optim.SGD(model.parameters(),lr=0.1)#模拟数据inputs=torch.tensor([[1.0,2.0],[3.0,4.0]])targets=torch.tensor([[3.0],[7.0]])#训练循环forepochinrange(10):#前向传播outputs=model(inputs)#计算损失loss=nn.MSELoss()(outputs,targets)#清零梯度optimizer.zero_grad()#反向传播:计算梯度loss.backward()#更新参数optimizer.step()print(f'Epoch{epoch},Loss:{loss.item()}')```在这个例子中:-模型的参数(即线性层的权重和偏置)默认是`requires_grad=True`的。-前向传播计算输出,然后计算损失(标量)。-在反向传播之前,使用`optimizer.zero_grad()`将模型参数的梯度清零(因为梯度是累加的,不清零的话每次迭代的梯度会加在一起)。-然后调用`loss.backward()`,计算图中所有参与计算并且`requires_grad=True`的张量(即模型参数)的梯度。-最后,优化器使用这些梯度来更新参数(`optimizer.step()`)。总结:在PyTorch中,变量参与梯度计算的条件是:该变量设置了`requires_grad=True`,并且它参与了计算图的构建(即从该变量到最终损失函数有一条路径),且在最终损失函数上调用了`.backward()`方法。§§相关问题§§:1.如何停止一个张量的梯度跟踪?2.在PyTorch中,非标量(向量或矩阵)如何调用backward()?3.在训练过程中,为什么需要在反向传播前将梯度清零?4.在PyTorch中,如何查看一个张量是否正在被梯度跟踪?</think>在 PyTorch 中,变量(张量)参与梯度计算需要同时满足以下条件: ### 参与梯度计算的必要条件 1. **张量设置了 `requires_grad=True`** - 显式声明需要梯度计算 - 示例:`x = torch.tensor([1.0], requires_grad=True)` 2. **张量参与可导运算** - 涉及 PyTorch 的自动微分操作(如加减乘除、矩阵运算、激活函数等) - 示例:`y = x ** 2 + 3*x` 3. **执行反向传播计算** - 在最终输出上调用 `.backward()` 方法 - 示例:`y.backward()` ### 关键代码示例 ```python import torch # 满足条件的张量会参与梯度计算 x = torch.tensor([2.0], requires_grad=True) # 条件1: requires_grad=True y = x ** 2 + 3 * x # 条件2: 参与可导运算 # 计算梯度 y.backward() # 条件3: 执行反向传播 print("x的梯度:", x.grad) # 输出: tensor([7.]) (因为 dy/dx = 2x + 3 = 7) ``` ### 不参与梯度计算的常见情况 1. **未设置 `requires_grad=True`** ```python a = torch.tensor([1.0]) # 默认 requires_grad=False b = a * 2 b.backward() # 报错: 叶子张量没有梯度 ``` 2. **在 `torch.no_grad()` 上下文中** ```python with torch.no_grad(): c = x * 3 # c 不参与梯度计算 ``` 3. **对非标量调用 `.backward()` 未提供梯度参数** ```python v = torch.tensor([1.0, 2.0], requires_grad=True) out = v * 2 out.backward() # 报错: grad can be implicitly created only for scalar outputs ``` ### 正确计算非标量梯度的方式 ```python # 为向量输出提供梯度参数 v = torch.tensor([1.0, 2.0], requires_grad=True) out = v * 2 # 创建与输出同形的"伪梯度" gradient = torch.tensor([0.1, 0.01]) out.backward(gradient) # 链式法则: d(loss)/dv = gradient * 2 print(v.grad) # 输出: tensor([0.2000, 0.0200]) ``` ### 梯度计算机制详解 1. **计算图构建**: - 当进行可导运算时,PyTorch 动态构建计算图 - 示例:`z = x * y` 会创建乘法运算节点 2. **反向传播过程**: - `.backward()` 从输出节点开始反向遍历计算图 - 对每个操作应用链式法则:$\frac{\partial \text{output}}{\partial \text{input}} = \frac{\partial \text{output}}{\partial \text{intermediate}} \times \frac{\partial \text{intermediate}}{\partial \text{input}}$ 3. **梯度累加特性**: - 多次反向传播会导致梯度累加 - 训练时需用 `optimizer.zero_grad()` 清零梯度 ### 实际训练示例 ```python import torch.nn as nn model = nn.Linear(3, 1) # 所有参数自动设置 requires_grad=True optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # 训练步骤 for data, target in dataloader: optimizer.zero_grad() # 清零历史梯度 output = model(data) # 前向传播 (构建计算图) loss = nn.MSELoss()(output, target) loss.backward() # 反向传播 (计算梯度) optimizer.step() # 更新参数 ``` ### 最佳实践建议 1. **推理时禁用梯度**: ```python @torch.no_grad() def predict(model, data): return model(data) ``` 2. **冻结部分参数**: ```python for param in model.layer1.parameters(): param.requires_grad = False # 冻结该层梯度 ``` 3. **高效内存管理**: ```python with torch.inference_mode(): # 比 no_grad() 更高效 test_output = model(test_data) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值