详解torch.nn.utils.clip_grad_norm_ 的使用与原理

这篇具有很好参考价值的文章主要介绍了详解torch.nn.utils.clip_grad_norm_ 的使用与原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

clip_grad_norm_的原理

本文是对梯度剪裁: torch.nn.utils.clip_grad_norm_()文章的补充。所以可以先参考这篇文章

从上面文章可以看到,clip_grad_norm最后就是对所有的梯度乘以一个clip_coef,而且乘的前提是clip_coef一定是小于1的,所以,按照这个情况:clip_grad_norm只解决梯度爆炸问题,不解决梯度消失问题

clip_grad_norm_参数的选择(调参)

从上面文章可以看到,clip_coef的公式为:

c l i p _ c o e f = m a x _ n o r m t o t a l _ n o r m clip\_coef = \frac{max\_norm}{total\_norm} clip_coef=total_normmax_norm

max_norm的取值

假定忽略clip_coef > 1的情况,则可以根据公式推断出:

  1. clip_coef越小,则对梯度的裁剪越厉害,即,使梯度的值缩小的越多
  2. max_norm越小,clip_coef越小,所以,max_norm越大,对于梯度爆炸的解决越柔和,max_norm越小,对梯度爆炸的解决越狠

max_norm可以取小数

接下来看下total_norm和norm_type的取值

从上面文章可以看到,total_norm受梯度大小和norm_type的影响,通过公式很难直观的感受到,这里我通过实验得出了以下结论(不严谨,欢迎勘误):

  1. 梯度越大,total_norm值越大,进而导致clip_coef的值越小,最终也会导致对梯度的裁剪越厉害,很合理
  2. norm_type不管取多少,对于total_norm的影响不是太大(1和2的差距稍微大一点),所以可以直接取默认值2
  3. norm_type越大,total_norm越小(实验观察到的结论,数学不好,不会证明,所以本条不一定对)

实验过程如下:

首先我对源码进行了一些修改,将.grad去掉,增加了一些输出,方便进行实验:

import numpy as np
import torch
from torch import nn

def clip_grad_norm_(parameters, max_norm, norm_type=2):
    if isinstance(parameters, torch.Tensor):
        parameters = [parameters]
    parameters = list(filter(lambda p: p is not None, parameters))
    max_norm = float(max_norm)
    norm_type = float(norm_type)
    if norm_type == np.inf:
        total_norm = max(p.data.abs().max() for p in parameters)
    else:
        total_norm = 0
        for p in parameters:
            param_norm = p.data.norm(norm_type)
            total_norm += param_norm.item() ** norm_type
        total_norm = total_norm ** (1. / norm_type)
    clip_coef = max_norm / (total_norm + 1e-6)
    if clip_coef < 1:
        for p in parameters:
            p.data.mul_(clip_coef)
            
    print("max_norm=%s, norm_type=%s, total_norm=%s, clip_coef=%s" % (max_norm, norm_type, total_norm, clip_coef))

只改变norm_type的情况下,各变量值的变化:

for i in range(1, 5):
    clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=1, norm_type=i)
    
clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=1, norm_type=np.inf)
max_norm=1.0, norm_type=1.0, total_norm=265.0, clip_coef=0.0037735848914204344
max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263
max_norm=1.0, norm_type=3.0, total_norm=135.16899108886716, clip_coef=0.007398146457602848
max_norm=1.0, norm_type=4.0, total_norm=129.34915161132812, clip_coef=0.007731013151704421
max_norm=1.0, norm_type=inf, total_norm=tensor(125.), clip_coef=tensor(0.0080)

只改变梯度,各变量值的变化:

for i in range(1, 5):
    clip_grad_norm_(torch.Tensor([125*i,75,45,15,5]), max_norm=1, norm_type=2)
max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263
max_norm=1.0, norm_type=2.0, total_norm=265.3299865722656, clip_coef=0.003768891745519864
max_norm=1.0, norm_type=2.0, total_norm=385.389404296875, clip_coef=0.0025947781289671814
max_norm=1.0, norm_type=2.0, total_norm=507.83856201171875, clip_coef=0.001969129705451148

只改变max_norm,各变量值的变化:

for i in range(1, 5):
    clip_grad_norm_(torch.Tensor([125,75,45,15,5]), max_norm=i, norm_type=2)
max_norm=1.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.006519813631054263
max_norm=2.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.013039627262108526
max_norm=3.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.01955944089316279
max_norm=4.0, norm_type=2.0, total_norm=153.3786163330078, clip_coef=0.02607925452421705

clip_grad_norm_使用演示

在上面文章还提到一个重要的事情:clip_grad_norm_要放在backwardstep之间。接下来我会实际演示梯度在训练过程中的变化,并解释要这么做的原因:

首先定义一个测试模型:

class TestModel(nn.Module):
    
    def __init__(self):
        super().__init__()
        
        self.model = nn.Sequential(
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
            nn.Linear(1,1, bias=False),
            nn.Sigmoid(),
        )
    
    def forward(self, x):
        return self.model(x)
model = TestModel()

定义好模型后,固定一下模型参数:

for param in model.parameters():
    param.data = torch.Tensor([[0.5]])
    print("param=%s" % (param.data.item()))
param=0.5
param=0.5
param=0.5
param=0.5

可以看目前四个线性层的权重参数都为0.5。之后对模型进行一轮训练,并进行反向传播:

criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1)
predict_y = model(torch.Tensor([0.1]))
loss = criterion(predict_y, torch.Tensor([1]))
model.zero_grad()
loss.backward()

反向传播过后,再次打印模型参数,可以看到反向传播后计算好的各个参数的梯度:

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))
param=0.5, grad=-3.959321111324243e-05
param=0.5, grad=-0.0016243279678747058
param=0.5, grad=-0.014529166743159294
param=0.5, grad=-0.11987950652837753

重点来了,各个参数的梯度如上图所示(越靠近输入的位置,梯度越小,虽然没有出现梯度爆炸,反而出现了梯度消失,但不影响本次实验),现在对其进行梯度裁剪:

nn.utils.clip_grad_norm_(model.parameters(), max_norm=0.01208, norm_type=2)
tensor(0.1208)

在上面,我传入的max_norm=0.01208,而total_norm=0.1208,所以可得clip_coef=0.1,即所有的梯度都会缩小一倍,此时我们再打印一下梯度:

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))
param=0.5, grad=-3.960347839893075e-06
param=0.5, grad=-0.00016247491294052452
param=0.5, grad=-0.001453293371014297
param=0.5, grad=-0.01199105940759182

看到没,所有的梯度都减小了10倍。之后我们执行step()操作,其就会将进行param=param-lr*grad操作来进行参数更新。再次打印网络参数:

optimizer.step()

for param in model.parameters():
    print("param=%s, grad=%s" % (param.data.item(), param.grad.item()))
param=0.5000039339065552, grad=-3.960347839893075e-06
param=0.5001624822616577, grad=-0.00016247491294052452
param=0.5014532804489136, grad=-0.001453293371014297
param=0.5119910836219788, grad=-0.01199105940759182

可以看到,在执行step后,执行了param=param-grad操作(我设置的lr为1)。同时,grad并没有清0,所以这也是为什么要显式的调用zero_grad的原因。





参考资料

梯度剪裁: torch.nn.utils.clip_grad_norm_():https://blog.csdn.net/Mikeyboi/article/details/119522689

什么是范数(Norm),其具有哪些性质: https://blog.csdn.net/zhaohongfei_358/article/details/122818616文章来源地址https://www.toymoban.com/news/detail-404963.html

到了这里,关于详解torch.nn.utils.clip_grad_norm_ 的使用与原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • with torch.no_grad() 详解

    在使用pytorch时,并不是所有的操作都需要进行计算图的生成,只是想要网络结果的话就不需要后向传播 ,如果你想通过网络输出的结果去进一步优化网络的话 就需要后向传播了。 不使用with torch.no_grad() :此时有grad_fn=属性,表示,计算的结果在一计算图当中, 可以进行梯度

    2024年02月16日
    浏览(30)
  • 【Python】torch.no_grad()函数详解和示例

    torch.no_grad() 是 PyTorch 中的一个上下文管理器,用于在进入该上下文时禁用梯度计算。这在你只关心评估模型,而不是训练模型时非常有用,因为它可以显著减少内存使用并加速计算。 当你在 torch.no_grad() 上下文管理器中执行张量操作时,PyTorch 不会为这些操作计算梯度。这意

    2024年03月12日
    浏览(42)
  • torch.nn.Linear详解

    在学习transformer时,遇到过非常频繁的nn.Linear()函数,这里对nn.Linear进行一个详解。 参考:https://pytorch.org/docs/stable/_modules/torch/nn/modules/linear.html 从名称就可以看出来,nn.Linear表示的是线性变换,原型就是初级数学里学到的线性函数: y=kx+b 不过在深度学习中,变量都是多维张

    2023年04月09日
    浏览(26)
  • python:torch.no_grad()的作用 + requires_grad,grad_fn,grad的含义及使用

    requires_grad: grad_fn: grad: 说法1: 说法2: 代码: 保证param原地数值改变操作下requires_grad=True不变。 参考资料: requires_grad,grad_fn,grad的含义及使用 测试torch.no_grad()的作用 pytorch中torch.no_grad有什么用? PyTorch 中的“with torch no_grad”有什么作用?

    2024年02月17日
    浏览(34)
  • PyTorch中的torch.nn.Parameter() 详解

    今天来聊一下PyTorch中的torch.nn.Parameter()这个函数,笔者第一次见的时候也是大概能理解函数的用途,但是具体实现原理细节也是云里雾里,在参考了几篇博文,做过几个实验之后算是清晰了,本文在记录的同时希望给后来人一个参考,欢迎留言讨论。 先看其名,parameter,中文

    2023年04月08日
    浏览(78)
  • Pytorch:torch.nn.Module.apply用法详解

    torch.nn.Module.apply 是 PyTorch 中用于递归地应用函数到模型的所有子模块的方法。它允许对模型中的每个子模块进行操作,比如初始化权重、改变参数类型等。 以下是关于 torch.nn.Module.apply 的示例: 1. 语法 Module:PyTorch 中的神经网络模块,例如 torch.nn.Module 的子类。 fn:要应用到

    2024年01月15日
    浏览(37)
  • 【深度学习】多卡训练__单机多GPU方法详解(torch.nn.DataParallel、torch.distributed)

    多GPU训练能够加快模型的训练速度,而且在单卡上不能训练的模型可以使用多个小卡达到训练的目的。 多GPU训练可以分为单机多卡和多机多卡这两种,后面一种也就是分布式训练——训练方式比较麻烦,而且要关注的性能问题也有很多,据网上的资料有人建议能单机训练最好

    2024年02月02日
    浏览(26)
  • pytorch 固定部分网络参数需要使用 with torch.no_grad()吗

    在 PyTorch 中,torch.no_grad() 是一个上下文管理器,用于设置一段代码的计算图不需要梯度。具体来说,当我们在 torch.no_grad() 的上下文中执行某些操作时,PyTorch 不会为这些操作自动计算梯度,以节省计算资源。 使用 torch.no_grad() 可以有如下几种情况: 测试模型:在测试模型或

    2024年02月11日
    浏览(26)
  • 详解Pytorch中的torch.nn.MSELoss函,包括对每个参数的分析!

    一、函数介绍 Pytorch中MSELoss函数的接口声明如下,具体网址可以点这里。 torch.nn.MSELoss(size_average=None, reduce=None, reduction=‘mean’) 该函数 默认用于计算两个输入对应元素差值平方和的均值 。具体地,在深度学习中,可以使用该函数用来计算两个特征图的相似性。 二、使用方式

    2023年04月19日
    浏览(34)
  • Pytorch的torch.utils.data中Dataset以及DataLoader等详解

    在我们进行深度学习的过程中,不免要用到数据集,那么数据集是如何加载到我们的模型中进行训练的呢?以往我们大多数初学者肯定都是拿网上的代码直接用,但是它底层的原理到底是什么还是不太清楚。所以今天就从内置的Dataset函数和自定义的Dataset函数做一个详细的解

    2024年02月11日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包