实验记录 训练过程中网络参数的更新梯度爆炸,出现nan值

一、遇到的问题

训练的过程中,网络的输出突然为nan值, 导致训练终止。

二、进行排查

                    loss.backward()
                    # ==========
                    # for name, param in self.actor.net.named_parameters():
                    #     if param.grad is not None:
                    #         if 'state' in name:
                    #             print("prompt_embeddings_state 梯度统计:")
                    #             grad = param.grad
                    #             print(f"Mean: {grad.mean().item():.6f}")
                    #             print(f"Max: {grad.max().item():.6f}")
                    #             print(f"Min: {grad.min().item():.6f}")
                    #             print(f"Std: {grad.std().item():.6f}")
                    #         grad_has_nan = torch.isnan(param.grad).any()  # 检查梯度是否有 NaN
                    #         if grad_has_nan:
                    #             has_nan_grad = True
                    #             print(f"⚠️ 梯度 NaN 检测: 模块 '{name}' 的梯度包含 NaN!")
                    #             print(f"   梯度形状: {param.grad.shape}")
                    #             print(f"   NaN 数量: {torch.isnan(param.grad).sum().item()} 个")
                    #             print(f"   梯度示例:\n{param.grad}\n")  # 打印梯度详情
                    #         # print(f"Gradient of {name}:")
                    #         # print(param.grad)  # 打印梯度张量
                    #         # print(f"Gradient shape: {param.grad.shape}")
                    #     #     else:
                    #     #         print(f"✅ 模块 '{name}' 的梯度正常(无 NaN)")
                    #     # else:
                    #     #     print(f"ℹ️ 模块 '{name}' 无梯度(可能是冻结参数或未参与计算)")

加在loss.backward()后,去排查那块参数出现了问题。

打印的信息

⚠️ 梯度 NaN 检测: 模块 'backbone.prompt_embeddings_state' 的梯度包含 NaN!
   梯度形状: torch.Size([1, 40, 768])
   NaN 数量: 30720 个
   梯度示例:
tensor([[[nan, nan, nan,  ..., nan, nan, nan],
         [nan, nan, nan,  ..., nan, nan, nan],
         [nan, nan, nan,  ..., nan, nan, nan],
         ...,
         [nan, nan, nan,  ..., nan, nan, nan],
         [nan, nan, nan,  ..., nan, nan, nan],
         [nan, nan, nan,  ..., nan, nan, nan]]], device='cuda:0')

然后检查与其相关的前向计算。

在我的训练过程中,因为我要计算kl散度,当时的实现
 

# state_kl_loss = F.kl_div(
        #     input=F.log_softmax(s1, dim=-1),  # 输入分布(log_softmax)
        #     target=F.softmax(s2, dim=-1),  # 目标分布(softmax)
        #     reduction='batchmean'  # 对批次取平均
        # )

各种打印信息
 

 # 7. 计算 s1 和 s2 的 KL 散度(KL(s1 || s2))
        # 注意:KL 散度是非对称的,这里计算 KL(s1_avg || s2_avg)

        # print("s1 统计信息 (before log_softmax):")
        # print(f"Mean: {s1.mean().item():.6f}, Std: {s1.std().item():.6f}")
        # print(f"Max: {s1.max().item():.6f}, Min: {s1.min().item():.6f}")
        #
        # print("s2 统计信息 (before softmax):")
        # print(f"Mean: {s2.mean().item():.6f}, Std: {s2.std().item():.6f}")
        # print(f"Max: {s2.max().item():.6f}, Min: {s2.min().item():.6f}")

        # log_softmax_s1 = F.log_softmax(s1, dim=-1)
        # softmax_s2 = F.softmax(s2, dim=-1)
        #
        # print("log_softmax(s1) 统计信息:")
        # print(f"Mean: {log_softmax_s1.mean().item():.6f}, Std: {log_softmax_s1.std().item():.6f}")
        # print(f"Max: {log_softmax_s1.max().item():.6f}, Min: {log_softmax_s1.min().item():.6f}")
        # print("是否有 -inf:", (log_softmax_s1 == -float('inf')).any())
        #
        # print("softmax(s2) 统计信息:")
        # print(f"Mean: {softmax_s2.mean().item():.6f}, Std: {softmax_s2.std().item():.6f}")
        # print(f"Max: {softmax_s2.max().item():.6f}, Min: {softmax_s2.min().item():.6f}")
        # print("是否有 0:", (softmax_s2 == 0.0).any())
        # print("是否有 inf:", torch.isinf(softmax_s2).any())

发现
 


s1 统计信息 (before log_softmax):
Mean: -0.162447, Std: 3.158298
Max: 17.990225, Min: -48.743835
s2 统计信息 (before softmax):
Mean: -0.157571, Std: 5.495389
Max: 40.220631, Min: -63.233921
prompt_embeddings_state 梯度统计:

Mean: -0.000205
Max: 8.615377
Min: -6.830388
Std: 0.324599

下一轮之后就变成了
Mean: nan
Max: nan
Min: nan
Std: nan

这是因为要把tensor变成概率分布,进行了取softmax操作,这之后,最小值就有可能非常接近0了,因为数据的维度可是3万多,这么多的数据一起进行softmax,加和为1,按平均来算每个还不过0.00003

具体的打印了一下,发现
 

印信息
log_softmax(s1) 统计信息:
Mean: -20.639292, Std: 3.170919
Max: -2.767323, Min: -69.401436
是否有 -inf: tensor(False, device='cuda:0')
softmax(s2) 统计信息:
Mean: 0.000033, Std: 0.001325
Max: 0.117576, Min: 0.000000
是否有 0: tensor(True, device='cuda:0')
是否有 inf: tensor(False, device='cuda:0')
prompt_embeddings_state 梯度统计:
Mean: nan
Max: nan
Min: nan
Std: nan

确实,softmax之后的最小值为0


三、关于kl散度的问题

用上述的方法实现kl散度确实存在这个问题,那我们可以换个角度实现。

state_kl_loss = -torch.sum(F.log_softmax(s1, dim=1) * F.softmax(s2, dim=1), dim=1).mean()

该函数实现

与kl散度的关系

默认 

自身的信息熵为常数 

四、yolov9训练时遇到的梯度爆炸问题
 

在对yolov9进行改进时,添加了设计的额外模块,导致刚开始训练,梯度就出现了nan值。在研究之后,发现是其使用的混合精度训练导致的该问题,因此,需要取消这种混合精度训练。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

匿名的魔术师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值