赞
踩
在深度学习中,自动求导是一项核心技术,它使得我们能够方便地计算梯度并优化模型参数。
PyTorch
提供了一个强大的自动求导模块(Autograd
),它可以自动计算张量的导数得出梯度信息,同时也支持高阶导数计算。
在学习PyTorch
的过程中,经常会看到这些词汇: 自动求导、梯度计算、前向传播、反向传播、动态计算图等,下面是一些简单介绍:
PyTorch
的 Autograd
模块负责自动计算张量的梯度。当我们在 PyTorch
中定义了一个张量,并设置了 requires_grad=True
时,PyTorch
会自动跟踪对该张量的所有操作,并构建一个动态计算图。PyTorch
的 Autograd
模块通过构建计算图并使用反向传播算法,自动计算张量的梯度。PyTorch
中,反向传播算法通过计算动态计算图的梯度来实现。PyTorch
中的一个重要特性,它与静态计算图不同,可以根据代码的执行情况动态构建计算图。动态计算图使得 PyTorch
更加灵活,可以处理各种动态的模型结构和数据流动。他们之间的依赖关系:
PyTorch
能够跟踪对张量的操作;在学习自动求导模块(Autograd
)之前,我们先简单回忆下高数中是如何定义导数的:
从导数的定义上来看,不但理解起来比较费劲,也很难看出导数在深度学习中有什么作用,针对大部分场景的求导,本质上都是求某个函数在某一点的切线。如下图是一个经典的切线模型,求的是 x 0 x_0 x0处的导数:
看到这里,可能还是没有想明白,导数在深度学习中到底有什么作用?在学习AI
时,经常会听到道士下山
的故事,故事里最后抛出的问题是: 怎么样让道士快速下山? 最快的办法就是顺着坡度最陡峭的地方走下去。那么怎么样找到最陡峭的地方呢? 答案就是: 求导; 上面说了求导的本质就是某点的切线,切线则有斜率,斜率越大的地方也就是越陡峭的点,然后沿着相反的方向进行,这也是梯度下降算法的原理。
@注: 求导后得到的结果,在深度学习中,被称为梯度。
只有体会到复杂操作后的过程,才能真实感受到工具的便捷性,下面分别使用两种方式对函数 f ( x ) = 3 x 2 + 2 x + 1 f(x) = 3x^2+2x+1 f(x)=3x2+2x+1进行求导;下图是列举一些常见函数对应的的求导函数公式,方便后续手动计算时,进行参考
更多常见函数的求导函数示例:https://baike.baidu.com/item/导数/579188#3
import torch # 定义函数 def myfunction(x): return 3 * x ** 2 + 2 * x + 1 if __name__ == '__main__': # 定义变量,并为其指定需要计算梯度 t = torch.tensor(2.0, requires_grad=True) # 计算函数的值 result = myfunction(t) # 反向传播,进行梯度计算 result.backward() # 打印梯度 print('打印梯度:', t.grad) # 打印梯度:tensor(14.)
调用 backward()
方法时,PyTorch
会从张量的节点开始,沿着计算图反向传播,计算所有叶子节点相对于该张量的梯度。需要特别注意的是: 在每次调用 backward()
方法之后,PyTorch 会自动清空计算图中的梯度信息。因此,多次调用 backward()
方法会尝试在没有梯度信息的情况下进行反向传播,从而导致运行时错误。
@注: 从上面示例可以看出
Autograd
便捷性,如果没有自动求导包Autograd
的存在,想想当函数变的复杂时,该怎么去计算某点的导数…
在 PyTorch
中,反向传播函数 backward()
只能在一个张量(或者一系列张量)对应的图中被调用一次,因为它会计算当前图中所有叶子节点的梯度。如果多次调用backward()
,会发生梯度累积,导致数据不准确;
修改【3.2】代码示例:
def doBackward(var: torch.tensor): # 计算函数的值 result = myfunction(var) # 反向传播,进行梯度计算 result.backward() print('打印梯度:', var.grad) if __name__ == '__main__': # 定义变量,并为其指定需要计算梯度 t = torch.tensor(2.0, requires_grad=True) # 请求多次 for i in range(3): doBackward(t) """ 打印梯度: tensor(14.) 打印梯度: tensor(28.) 打印梯度: tensor(42.) """
通过上面运行输出,发现自动求导的结果(梯度)进行了累积,为了避免这种问题的出现,通常需要我们在模型训练过程中,手动清除之前计算的梯度。
通常情况下,在每次进行反向传播之前,需要调用 optimizer.zero_grad()
来清空之前计算的梯度。这样可以避免梯度累积,确保每次反向传播都是基于当前的梯度计算。修改上面示例中的部分代码:
def doBackward(var: torch.tensor):
# 计算函数的值
result = myfunction(var)
# ------- 假设有个优化器:optimizer -------
# 在每次迭代之前清零梯度
optimizer.zero_grad()
# 反向传播,进行梯度计算
result.backward()
print('计算结果:', var.grad)
为什么梯度不能累积呢?根据资料查询可以发现,梯度累积可能会导致几个问题,尤其是在训练深度神经网络时:
因此,在训练深度神经网络时,通常建议避免梯度累积,确保每次迭代都使用当前的梯度进行更新,以保证训练的稳定性和收敛速度。
torch.no_grad()
上下文管理器或者在张量上调用 .detach()
方法来实现局部禁用梯度计算。下面列举一些情况下,可能需要使用局部禁用梯度计算的具体示例:
在迁移学习或者模型微调中,通常会冻结预训练模型的一部分参数,只更新其中的部分参数。为了实现这一目的,可以使用 torch.no_grad()
上下文管理器来禁用梯度计算。
# 示例:冻结预训练模型的一部分参数
with torch.no_grad():
for param in model.parameters():
param.requires_grad = False
# 只对新添加的层的参数进行训练
optimizer = torch.optim.SGD(model.fc.parameters(), lr=0.001)
在模型推断时,不需要计算梯度,因此可以使用 torch.no_grad()
上下文管理器来禁用梯度计算,以提高推断速度和减少内存占用。
# 示例:在前向推断时禁用梯度计算
with torch.no_grad():
output = model(input)
在计算模型的性能指标(如准确率、损失值等)时,不需要计算梯度,因此可以使用 torch.no_grad()
上下文管理器来禁用梯度计算,以提高计算效率。
# 示例:在计算指标时禁用梯度计算
with torch.no_grad():
loss = criterion(output, target)
通过局部禁用梯度计算,可以灵活地控制梯度计算的范围,提高训练和推断的效率,并且可以避免不必要的梯度计算和内存消耗。
本文由mdnice多平台发布
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。