文章目录
- 一、模型训练中出现loss为NaN原因
-
- [1. 学习率过高](#1. 学习率过高)
- [2. 梯度消失或爆炸](#2. 梯度消失或爆炸)
- [3. 数据不平衡或异常](#3. 数据不平衡或异常)
- [4. 模型不稳定](#4. 模型不稳定)
- [5. 过拟合](#5. 过拟合)
- [二、 针对梯度消失或爆炸的解决方案](#二、 针对梯度消失或爆炸的解决方案)
-
- [1. 使用`torch.autograd.detect_anomaly()`](#1. 使用
torch.autograd.detect_anomaly()
) - [2. 使用 torchviz 可视化计算图](#2. 使用 torchviz 可视化计算图)
- [3. 检查梯度的数值范围](#3. 检查梯度的数值范围)
- [4. 调整梯度剪裁](#4. 调整梯度剪裁)
- [1. 使用`torch.autograd.detect_anomaly()`](#1. 使用
- 三、更具体的办法
-
- [3.1 可能导致梯度爆炸的部分](#3.1 可能导致梯度爆炸的部分)
- [3.2 解决方案](#3.2 解决方案)
一、模型训练中出现loss为NaN原因
1. 学习率过高
在训练的某个阶段,学习率可能设置得过高,导致模型参数更新幅度过大,甚至可能出现数值不稳定的情况。你可以尝试降低学习率,并观察训练过程中的变化。
2. 梯度消失或爆炸
如果模型的某些层出现梯度消失或爆炸的问题,可能会导致loss变得异常低。你可以检查梯度的大小,确保它们在合理范围内。
3. 数据不平衡或异常
训练数据中可能存在异常值或分布不平衡的情况,导致模型在某些批次的训练过程中出现异常。你可以检查数据集,确保数据质量。
4. 模型不稳定
模型架构或训练过程中的某些设置可能导致不稳定,比如过深的网络、过复杂的模型等。你可以尝试简化模型架构或添加正则化项。
5. 过拟合
模型可能在某些阶段已经过拟合到训练数据上,导致训练loss异常低而验证loss较高。你可以通过早停法(early stopping)、正则化、数据增强等方法来缓解过拟合问题。
解决方法
- 调节学习率:适当降低学习率,观察训练过程中的变化。
- 检查梯度:通过torch.autograd检查梯度的大小,确保没有出现梯度消失或爆炸。
- 数据检查:确保数据集没有异常值或分布不平衡的情况。
- 模型架构:简化模型架构,增加正则化项,如L2正则化、dropout等。
- 验证集监控:通过监控验证集的loss和指标,防止过拟合。\
二、 针对梯度消失或爆炸的解决方案
使用 torch.autograd.detect_anomaly() 和相关工具确实可以帮助你检测和排除训练过程中出现的梯度问题。以下是如何在你的代码中使用这些工具来检测异常和可视化梯度的示例。
1. 使用torch.autograd.detect_anomaly()
这个函数可以帮助检测反向传播过程中出现的异常,并输出具体的错误信息和位置。
py
import torch
# 定义模型
model = MyModel()
# 定义损失函数和优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 输入数据
inputs = torch.randn(56, 1024, 28, 28)
targets = torch.randint(0, 10, (56,))
# 在训练过程中使用 detect_anomaly
with torch.autograd.detect_anomaly():
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
2. 使用 torchviz 可视化计算图
torchviz 是一个可以帮助你可视化计算图的工具,这对于调试复杂的模型非常有用。
首先,安装 torchviz:
pip install torchviz
然后,可以使用以下代码来生成和保存计算图:
py
from torchviz import make_dot
# 定义模型
model = MyModel()
# 输入数据
inputs = torch.randn(56, 1024, 28, 28)
# 获取模型输出
outputs = model(inputs)
# 创建计算图
dot = make_dot(outputs, params=dict(model.named_parameters()))
# 保存计算图
dot.format = 'png'
dot.render('model_graph')
3. 检查梯度的数值范围
你可以在每个训练步骤之后检查模型中各个参数的梯度,以确保梯度的数值范围正常。
py
# 定义模型
model = MyModel()
# 定义损失函数和优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 输入数据
inputs = torch.randn(56, 1024, 28, 28)
targets = torch.randint(0, 10, (56,))
# 在训练过程中使用 detect_anomaly
with torch.autograd.detect_anomaly():
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
# 检查梯度数值范围
for name, param in model.named_parameters():
if param.grad is not None:
grad_min = param.grad.min().item()
grad_max = param.grad.max().item()
print(f'{name} - grad_min: {grad_min}, grad_max: {grad_max}')
optimizer.step()
4. 调整梯度剪裁
在训练过程中,可以使用梯度剪裁来防止梯度爆炸。以下是如何在 PyTorch 中实现梯度剪裁的示例:
py
# 定义模型
model = MyModel()
# 定义损失函数和优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 输入数据
inputs = torch.randn(56, 1024, 28, 28)
targets = torch.randint(0, 10, (56,))
# 在训练过程中使用 detect_anomaly
with torch.autograd.detect_anomaly():
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
# 梯度剪裁
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
通过以上方法,可以更好地检测和调试训练过程中出现的梯度问题,提高模型的训练稳定性和效率。如果在使用过程中发现任何异常或需要进一步调试,请随时提供更多细节。
三、更具体的办法
3.1 可能导致梯度爆炸的部分
-
ReLU 激活函数的使用:激活函数可参考激活函数汇总
ReLU 是一种常见的激活函数,但如果输入有较大的正值,经过 ReLU 之后,这些值会直接传递下去,可能导致后续层的梯度爆炸。考虑使用其他激活函数,如 Leaky ReLU 或 SELU,它们在某些情况下对梯度更友好。
pyembedding_spatial_786 = torch.nn.functional.relu(embedding_spatial_786, inplace=True)
-
特征插值:
插值操作可能会生成较大的值,尤其是在上采样过程中。如果插值后的值过大,可能会导致梯度爆炸。
upsample_feat = F.interpolate(feat_high, scale_factor=2., mode='nearest')
-
特征拼接:
多个特征拼接后,如果这些特征值过大,会导致拼接后的张量值过大,进而影响后续层的梯度。
pyinner_out = self.fpn_blocks[len(proj_feats) - 1 - idx](torch.concat([upsample_feat, feat_low], dim=1))
-
全连接层:
全连接层的权重初始化方式可能会导致梯度爆炸。确保使用了合适的初始化方法,如 Xavier 初始化或 He 初始化。
-
权重共享:
如果多个部分共享权重,需要确保这些共享权重不会导致梯度的累积效应。
3.2 解决方案
-
梯度剪裁:
在反向传播过程中使用梯度剪裁,可以防止梯度爆炸。你可以在 optimizer.step() 之前加上梯度剪裁。
pytorch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
-
使用更稳定的激活函数:
尝试使用 Leaky ReLU 或 SELU,它们在某些情况下对梯度更友好。
-
检查权重初始化:
确保所有层的权重初始化方式合理,避免初始值过大。
pyfor m in model.modules(): if isinstance(m, (nn.Conv2d, nn.Linear)): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
-
监控梯度值:
在每次反向传播后,监控梯度的值,确保梯度不会爆炸。
pyfor name, param in model.named_parameters(): if param.grad is not None: grad_min = param.grad.min().item() grad_max = param.grad.max().item() print(f'{name} - grad_min: {grad_min}, grad_max: {grad_max}')
Enjoy~
∼ O n e p e r s o n g o f a s t e r , a g r o u p o f p e o p l e c a n g o f u r t h e r ∼ \sim_{One\ person\ go\ faster,\ a\ group\ of\ people\ can\ go\ further}\sim ∼One person go faster, a group of people can go further∼