记录一次 NaN in Loss 的解决过程

本文详细记录了在PyTorch模型训练过程中遇到Loss中出现NaN的排查过程。通过检查梯度和参数,定位到问题出现在反向传播的LayerNorm层,原因是计算标准差时除以了0。最终解决方案是在计算标准差时添加一个小的正数以防止除0错误。
部署运行你感兴趣的模型镜像

前言

训练的模型经常数十个epoch后Loss中出现NaN,困扰了很久终于解决了,记录一下。

检测

我通常会在计算loss.backward()optimizer.step()前,添加条件判断是否出现NaN:

if loss != loss:
    raise Exception('NaN in loss, crack!')

定位

输出参数和梯度

同样放在loss.backward()optimizer.step()前。
用于打印网络所有层参数和梯度的最大和最小值,仅在Debug时使用:

# print grad check
v_n = []
v_v = []
v_g = []
for name, parameter in net.named_parameters():
    v_n.append(name)
    v_v.append(parameter.detach().cpu().numpy() if parameter is not None else [0])
    v_g.append(parameter.grad.detach().cpu().numpy() if parameter.grad is not None else [0])
for i in range(len(v_n)):
    if np.max(v_v[i]).item() - np.min(v_v[i]).item() < 1e-6:
        color = bcolors.FAIL + '*'
    else:
        color = bcolors.OKGREEN + ' '
    print('%svalue %s: %.3e ~ %.3e' % (color, v_n[i], np.min(v_v[i]).item(), np.max(v_v[i]).item()))
    print('%sgrad  %s: %.3e ~ %.3e' % (color, v_n[i], np.min(v_g[i]).item(), np.max(v_g[i]).item()))

部分输出如下:

 value encoder_blocks.0.KPConv.weights: -1.890e-01 ~ 9.013e-02
 grad  encoder_blocks.0.KPConv.weights: nan ~ nan
 value encoder_blocks.0.KPConv.kernel_points: -3.859e-01 ~ 4.054e-01
 grad  encoder_blocks.0.KPConv.kernel_points: 0.000e+00 ~ 0.000e+00
 value encoder_blocks.0.batch_norm.batch_norm.weight: -6.673e-03 ~ 1.823e-01
 grad  encoder_blocks.0.batch_norm.batch_norm.weight: nan ~ nan
 value encoder_blocks.0.batch_norm.batch_norm.bias: -1.067e-01 ~ 9.120e-02
 grad  encoder_blocks.0.batch_norm.batch_norm.bias: nan ~ nan

分析

首先查看是Loss中先出现NaN还是梯度中先出现NaN。
若Loss中先出现,则有可能是正向传播过程出现问题或计算Loss过程出现问题。
但我发现我的模型中梯度中首先出现NaN,说明是反向传播过程出现问题,从后往前查看梯度,找到倒数第一个NaN的地方

 value encoder.ffn0.l1.weight: -1.010e-06 ~ 1.011e-06
 grad  encoder.ffn0.l1.weight: nan ~ nan
 value encoder.ffn0.l1.bias: -9.871e-07 ~ 9.912e-07
 grad  encoder.ffn0.l1.bias: nan ~ nan
 value encoder.ffn0.l2.weight: -9.877e-07 ~ 1.031e-06
 grad  encoder.ffn0.l2.weight: nan ~ nan
 value encoder.ffn0.l2.bias: -9.813e-07 ~ 4.404e-06
 grad  encoder.ffn0.l2.bias: nan ~ nan
*value encoder.norm.weight: 3.158e-05 ~ 3.163e-05
*grad  encoder.norm.weight: -5.224e-15 ~ 9.597e-15
*value encoder.norm.bias: -2.440e-09 ~ 2.480e-09
*grad  encoder.norm.bias: -9.202e-15 ~ 1.174e-14
 value encoder.ffn1.net.0.weight: -1.017e-06 ~ 1.047e-06
 grad  encoder.ffn1.net.0.weight: -4.821e-14 ~ 6.914e-14
 value encoder.ffn1.net.0.bias: -1.174e-06 ~ 1.422e-06
 grad  encoder.ffn1.net.0.bias: -4.267e-10 ~ 6.084e-10

因此我们定位到问题是出现在 encoder.ffn0.l2 之后 encoder.norm.weight 之前,查看代码查找问题。

精确定位

知道了问题存在的位置我还是找不到问题,那就是定位得不够精确,其实可以直接使用pytorch自带的检查功能(严重影响性能,仅在Debug中使用):

with autograd.detect_anomaly():
	# 正向传播
	# 计算Loss
	# 反向传播

当模型中出现NaN时,会抛出异常,并在打印常规的Debug信息前,提供更多的Debug信息。额外提供的信息如下:

  File "~/work/trainer.py", line 252, in train                            
    outputs = net(batch, config)                                                                      
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                           
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/parallel/distributed.py", line 705, in forward                                                                            
    output = self.module(*inputs[0], **kwargs[0])                                                     
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                             
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                           
  File "~/work/models/crossvitmodule.py", line 672, in forward                  
    x = list(x)                                                                                       
  File "~/work/models/crossvitmodule.py", line 663, in <lambda>                 
    x = map(lambda u, v: u + self.pos_embedding(u, v), x, point)                                      
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                           
  File "~/work/models/crossvitmodule.py", line 423, in forward                  
    point = self.proj2(point)                                                                         
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                           
  File "~/work/models/crossvitmodule.py", line 404, in forward                  
    return self.leaky_relu(self.norm(self.linear(x)))                                                 
  File "~/anaconda3/envs/pytorch1.8/lib/python3.7/site-packages/torch/nn/modules/module.py", line 889, in _call_impl                                                                               
    result = self.forward(*input, **kwargs)                                                           
  File "~/work/models/crossvitmodule.py", line 29, in forward                   
    std = torch.var(x, dim=1, unbiased=False, keepdim=True).sqrt()                        
 (function _print_stack)   

直接定位到std = torch.var(x, dim=1, unbiased=False, keepdim=True).sqrt()这一行。
原代码为手动实现的LayerNorm:

class LayerNorm(nn.Module):
    def __init__(self, dim, eps = 1e-5):
        super().__init__()
        self.eps = eps
        self.g = nn.Parameter(torch.ones(1, dim, 1, 1))
        self.b = nn.Parameter(torch.zeros(1, dim, 1, 1))

    def forward(self, x):
        std = torch.var(x, dim = 1, unbiased = False, keepdim = True).sqrt()
        mean = torch.mean(x, dim = 1, keepdim = True)
        return (x - mean) / (std + self.eps) * self.g + self.b

当x为0时在计算标准差时正向传播需要计算sqrt(x)
而反向传播时需要计算sqrt(x)的微分1/(2*sqrt(x))此时需要确保x != 0

解决方案

防止除0即可:

std = torch.sqrt(torch.var(x, dim = 1, unbiased = False, keepdim = True) + self.eps)

您可能感兴趣的与本文相关的镜像

GPT-oss:20b

GPT-oss:20b

图文对话
Gpt-oss

GPT OSS 是OpenAI 推出的重量级开放模型,面向强推理、智能体任务以及多样化开发场景

<think>我们正在处理YoloV12模型训练时loss出现NaN的问题。根据参考引用中提到的类似问题(主要是YOLOv8),我们可以推测可能的原因和解决方案。虽然YOLOv12的具体实现可能与YOLOv8不同,但很多训练问题的原因是相通的,比如学习率、数据、梯度等问题。根据引用[1]和[2],我们可以总结以下可能导致训练lossNaN的原因及解决方案:原因1:学习率设置过高解决方案:降低学习率原因2:梯度爆炸或消失解决方案:使用梯度裁剪(gradientclipping)原因3:数据问题(如存在无效标注、损坏的图像文件等)解决方案:检查数据集,确保所有标注正确,图像文件无损原因4:数值计算问题(如除以零、对负数取对数等)解决方案:检查代码中是否有不安全的运算,加入安全措施(如添加epsilon)原因5:硬件或驱动问题(如使用GTX16xx系列显卡可能与CUDA的兼容性有关)解决方案:更新显卡驱动和CUDA版本原因6:混合精度训练导致数值不稳定解决方案:关闭混合精度训练(使用全精度float32)或调整混合精度训练的配置原因7:损失函数计算问题解决方案:检查损失函数的实现,看是否有可能导致NaN的情况(例如,在计算交叉熵时,输入概率未经过softmax或sigmoid处理,导致数值不稳定)原因8:模型结构问题(如层数过深或某些层的输出不稳定)解决方案:简化模型或调整模型结构由于是YOLOv12,我们还需要考虑一些特定的情况。通常,训练初期出现NaN,可能是初始化的权重不合适或学习率太高。训练中途出现NaN,可能是数据批次中有异常数据。具体的调参技巧和解决方案可以按照以下步骤尝试:步骤1:降低学习率初始学习率可以降低一个数量级,例如从0.01降到0.001,观察是否还会出现NaN。步骤2:使用梯度裁剪在训练代码中添加梯度裁剪,限制梯度的大小,避免梯度爆炸。例如,在PyTorch中,可以在优化器更新之前使用:torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm=1.0)步骤3:检查数据使用数据增强时,注意检查是否有数据增强操作导致数值异常(如归一化后的值超出范围)。同时,检查数据集中是否有标注框的坐标超出了图像边界,或者有标注框的宽高为0或负数。在训练前对数据进行清洗。步骤4:调整损失函数检查损失函数的计算过程,特别是涉及对数运算的地方,可以加上一个很小的epsilon(如1e-8)来避免对0或负数取对数。步骤5:关闭混合精度训练如果使用了混合精度训练(AMP),尝试关闭它,使用全精度(float32)进行训练。步骤6:调试模型在训练开始时,用一个小样本(例如1个batch)进行训练,看是否出现NaN。如果出现,则检查模型前向传播的输出和反向传播的梯度,定位出现NaN的具体模块。步骤7:初始化策略检查模型参数的初始化,不合适的初始化可能导致输出过大。可以尝试使用不同的初始化方法。步骤8:监控训练过程在训练过程中,不仅监控损失值,还可以监控权重的范数、梯度的范数等,帮助诊断问题。步骤9:更新软件环境确保使用的框架(如PyTorch、TensorFlow)和CUDA版本是最新的或已知稳定的版本,特别是已知与显卡型号兼容的版本。由于YOLOv12可能是一个较新的版本,也可能存在一些特有的问题。如果以上通用方法无法解决,建议查阅YOLOv12的官方文档或社区讨论,看是否有针对该版本的具体解决方案。注意:以上解决方案部分来源于引用[1][2]中针对YOLOv8的类似问题,但同样适用于其他深度学习模型训练中出现的NaN问题。下面我们按照这些思路提供一个详细的解决方案步骤。</think> 针对YOLOv12训练中loss出现NaN的问题,以下是结合深度学习和模型训练原理的解决方案: ### 原因分析与解决方案 1. **学习率过高导致梯度爆炸** - **解决方法**: - 初始学习率降为默认值的$1/10$(例如 $10^{-4}$$ 到 $10^{-5}$) - 使用自适应学习率优化器: ```python optimizer = torch.optim.Adam(model.parameters(), lr=1e-5, weight_decay=1e-4) ``` 2. **数值不稳定性** - **解决方法**: - 添加梯度裁剪防止梯度爆炸: ```python torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) ``` - 检查损失函数中对数运算(如 $log(x)$)添加epsilon值: ```python loss = -torch.log(pred + 1e-8) ``` 3. **数据问题** - **解决方法**: - 验证数据标注:检查标注坐标是否超出图像边界,确保坐标满足 $0 \leq x \leq width, 0 \leq y \leq height$ - 过滤无效样本:移除宽高为0的无效边界框 - 数据标准化:检查预处理是否产生异常值(如除以255的归一化) 4. **硬件兼容性问题(尤其GTX16xx系列)** - **解决方法**: - 更新CUDA至≥11.7版本 - 关闭混合精度训练(AMP): ```python # 训练命令移除amp相关参数 python train.py --amp False ``` 5. **损失函数异常** - **解决方法**: - 验证损失计算层输出: ```python # 在损失函数前添加检查 assert not torch.isnan(prediction).any(), "NaN in prediction" ``` - 尝试替换损失函数组件(如CIoU替换为SIoU) ### 调试技巧 1. **分阶段定位法**: - 仅训练1个batch验证前向传播:$ loss = f(X_{[:1]}) $ - 逐步开启数据增强模块 2. **监控指标**: - 实时记录梯度范数:$\| \nabla W \|_2$ - 使用TensorBoard监控每层激活值分布 > 经验表明,80%的NaN问题由学习率/数据异常引起[^1],GTX16系显卡需特别注意驱动兼容[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值