警惕!损失Loss为Nan或者超级大的原因

2023-10-19 10:46:20 浏览数 (1)

前言

训练或者预测过程中经常会遇到训练损失值或者验证损失值不正常、无穷大、或者直接nan的情况:

遇到这样的现象,通常有以下几个原因导致:

梯度爆炸造成Loss爆炸

原因很简单,学习率较高的情况下,直接影响到每次更新值的程度比较大,走的步伐因此也会大起来。如下图,过大的学习率会导致无法顺利地到达最低点,稍有不慎就会跳出可控制区域,此时我们将要面对的就是损失成倍增大(跨量级)。

另外,这种情况很容易在网络层数比较深的时候出现,借用gluon的一段话:

这就是典型的梯度爆炸,解决方法也很简单,降低初始的学习率,并设置学习率衰减。

检查输入数据和输出数据

通常我们都会保证输入的数据是否正确(这个要是不能保证那么后续也就没必要继续了..)。一般输入不正确的话可以立马观察出来。

有两种情况可能并不是那么容易检测到:

  • 数据比较多,99%的数据是对的,但有1%的数据不正常,或者损坏,在训练过程中这些数据往往会造成nan或者inf,这时候需要仔细挑选自己的数据,关于如何挑选数据(https://cloud.tencent.com/developer/article/2346565)。
  • 训练过程中跳出了错误的数据,这是需要在IDE或者通过其他途径对运行中的程序进行分析。

这时我们要注意的是在训练过程中的输入和输出是否正确:

(利用debug寻找错误的输入)

神经网络中,很有可能在前几层的输入是正确的,但是到了某一层的时候输出就会变成nan或者inf(其中-inf代表负无穷,而nan代表不存在的数),这个时候就需要通过debug去一一检查。

当然我们可以在自己代码中添加检测函数。

例如在Pytorch框架中我们可以使用torch.autograd.tect_anomaly类来监测训练或者预测过程中遇到的隐晦的问题:

代码语言:javascript复制
>>> import torch
>>> from torch import autograd
>>> class MyFunc(autograd.Function):
...     @staticmethod
...     def forward(ctx, inp):
...         return inp.clone()
...     @staticmethod
...     def backward(ctx, gO):
...         # Error during the backward pass
...         raise RuntimeError("Some error in backward")
...         return gO.clone()
>>> def run_fn(a):
...     out = MyFunc.apply(a)
...     return out.sum()
>>> inp = torch.rand(10, 10, requires_grad=True)
>>> out = run_fn(inp)
>>> out.backward()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/your/pytorch/install/torch/tensor.py", line 93, in backward
        torch.autograd.backward(self, gradient, retain_graph, create_graph)
      File "/your/pytorch/install/torch/autograd/__init__.py", line 90, in backward
        allow_unreachable=True)  # allow_unreachable flag
      File "/your/pytorch/install/torch/autograd/function.py", line 76, in apply
        return self._forward_cls.backward(self, *args)
      File "<stdin>", line 8, in backward
    RuntimeError: Some error in backward
>>> with autograd.detect_anomaly():
...     inp = torch.rand(10, 10, requires_grad=True)
...     out = run_fn(inp)
...     out.backward()
    Traceback of forward call that caused the error:
      File "tmp.py", line 53, in <module>
        out = run_fn(inp)
      File "tmp.py", line 44, in run_fn
        out = MyFunc.apply(a)
    Traceback (most recent call last):
      File "<stdin>", line 4, in <module>
      File "/your/pytorch/install/torch/tensor.py", line 93, in backward
        torch.autograd.backward(self, gradient, retain_graph, create_graph)
      File "/your/pytorch/install/torch/autograd/__init__.py", line 90, in backward
        allow_unreachable=True)  # allow_unreachable flag
      File "/your/pytorch/install/torch/autograd/function.py", line 76, in apply
        return self._forward_cls.backward(self, *args)
      File "<stdin>", line 8, in backward
    RuntimeError: Some error in backward

损失函数可能不正确

损失函数也是有可能导致输出nan,尤其是在我们自己设计损失函数的时候。

损失函数应该考虑到是否可以正常地backward

其次对输入的Tensor是否进行了类型转化,保证计算中保持同一类型。

最后考虑在除数中加入微小的常数保证计算稳定性。

batchNorm可能捣鬼

如果你的网络中batchNorm层很多,而且充当比较重要的角色,那么可以适当地检查一下Tensor在输入Batchnorm层后有没有可能变为nan,如果恰好发生这种情况,batchNorm层中的移动均值(running_mean)和移动方差(running_var)也很有可能都是nan,而且这种情况很有可能发生在预测阶段。

这种情况通过发生在训练集和验证集是两个截然不同的分布的时候,这是在训练集中学习到的均值和方法在验证集中是没有作用反而会捣乱。或者在一个神经网络中存在两种结构不同的阵营:典型的是Unet,当在自定义Unet的时候,编码网络和解码网络如果是两个结构存在较大差异的网络,那么在编码阶段学习到的分布在解码阶段就会出现问题。

举个真实的例子:Unet resnet34 表现正常,但是使用Unet resnext50 则造成损失爆炸(将解码阶段的batchnorm层失效后表现正常)。

当然上述现象出现的原因大部分在当我们使用**model.eval()**(Pytorch)之后发生。如果你在预测阶段也将模型model设置为model.train(True),那么问题可能就不会出现:

解决方式:

或者设置Batchnorm中的参数track_running_stats=False使移动均值和移动方差不起作用:

相关的问题:

https://github.com/BVLC/caffe/pull/5136

采用stride大于kernel size的池化层

在卷积层的卷积步伐大于卷积核大小的时候,有可能产生nan

比如:

代码语言:javascript复制
layer {
  name: "faulty_pooling"
  type: "Pooling"
  bottom: "x"
  top: "y"
  pooling_param {
    pool: AVE
    stride: 5
    kernel: 3
  }
}

你的Shuffle设置有没有乱动

Suffle即洗牌的意思,如果我们在数据加载阶段将Shuffle参数设置在True,那么在神经网络的读取数据的时候,将会打乱顺序去读取,也就是不按照数据的排列顺序去读取。

一般我们是在训练阶段开启shuffle而在预测阶段关闭shuffle,训练阶段在进行每个epoch的时候开启shuffle可以使数据充分地被随机过一遍(类似于我们烤鱼,我们选择是频繁将鱼上下翻面(shuffle)或者只翻一次面,每次烤很长时间),这样训练的鲁棒性比不shuffle稍高一些。

但是假如我们使用了batch_norm层,并且数据的分布极不规律(使用shuflle和不使用shuffle读取的数据顺序的信息分布完全不同),那么在训练阶段训练好的模型(使用shuffle),在预测阶段使用的时候(不使用shuffle),由于数据分布的不同,也是有可能导致batch_norm层出现nan,从而导致不正常的损失函数出现。

在GPU上和CPU上表现并不同

还有种可能的情况,也是没办法的情况,该深度学习框架存在Bug –> 在GPU上运行损失爆炸,但移动到CPU上可能就没有问题,这时候该怎么办么?查找关键原因,赶紧去提个Issue吧~

参考资料:

https://discuss.pytorch.org/t/model-breaks-in-evaluation-mode/2190

https://discuss.pytorch.org/t/model-eval-gives-incorrect-loss-for-model-with-batchnorm-layers/7561/19

https://stackoverflow.com/questions/33962226/common-causes-of-NaNs-during-training

0 人点赞