我注意到在训练过程中经常出现NAN
。
通常情况下,这似乎是由内积/全连接或卷积层的权重爆炸引起的。
这是由于梯度计算爆炸造成的吗?还是因为权重初始化(如果是这样,为什么会产生这种影响)?还是可能由于输入数据的性质引起的?
总体问题很简单:NAN在训练期间出现的最常见原因是什么?其次,有哪些方法可以解决这个问题(以及它们为什么有效)?
我注意到在训练过程中经常出现NAN
。
通常情况下,这似乎是由内积/全连接或卷积层的权重爆炸引起的。
这是由于梯度计算爆炸造成的吗?还是因为权重初始化(如果是这样,为什么会产生这种影响)?还是可能由于输入数据的性质引起的?
总体问题很简单:NAN在训练期间出现的最常见原因是什么?其次,有哪些方法可以解决这个问题(以及它们为什么有效)?
我已经多次遇到这种现象,以下是我的观察:
原因:大的梯度会使学习过程偏离轨道。
你应该期望什么:查看运行时日志,你应该关注每次迭代的损失值。你会注意到损失值从一次迭代到另一次迭代开始显著增长,最终损失将变得太大而无法用浮点变量表示,并且它将变成nan
。
你可以做什么:将base_lr
(在solver.prototxt中)降低一个数量级(至少)。如果你有几个损失层,你应该检查日志以查看哪个层负责梯度爆炸,并为该特定层减少train_val.prototxt
中的loss_weight
,而不是通用的base_lr
。
原因:caffe无法计算有效的学习率,而是得到'inf'
或'nan'
,这个无效的学习率会使所有更新失效,从而使所有参数失效。
你应该期望什么:查看运行时日志,你应该看到学习率本身变成了'nan'
,例如:
你能做什么: 在你的
... sgd_solver.cpp:106] Iteration 0, lr = -nan
'solver.prototxt'
文件中修复影响学习率的所有参数。例如,如果你使用lr_policy: "poly"
并忘记定义max_iter
参数,你最终会得到lr = nan
...有关caffe中学习率的更多信息,请参见此线程。损失函数出现问题
原因: 有时,损失层中的损失计算会导致nan
出现。例如,使用未标准化值来提供InfogainLoss
层,使用带有错误的自定义损失层等等。 你应该期望: 查看运行时日志,你可能不会注意到任何异常情况:损失逐渐减小,突然出现了一个nan
。 你能做什么: 看看是否可以重现错误,在损失层添加打印输出并调试错误。
例如:我曾经使用一种损失,通过批次中标签出现的频率对惩罚进行规范化。恰巧,如果一个训练标签在批次中根本没有出现,计算出的损失会产生nan
。在这种情况下,使用足够大的批次(相对于集合中标签的数量)就足以避免此错误。输入出现问题
原因:你的输入中有nan
! 你应该期望: 一旦学习过程“碰到”这个有问题的输入,输出就会变成nan
。查看运行时日志,你可能不会注意到任何异常情况:损失逐渐减小,突然出现了一个nan
。 你能做什么: 重新构建你的输入数据集(lmdb/leveldn/hdf5...),确保你的训练/验证集中没有坏的图像文件。为了调试,可以建立一个简单的网络,读取输入层,在其顶部具有虚拟损失,并通过所有输入数据:如果其中一个是有问题的,则这个虚拟网络也应该产生nan
。某些情况下,选择池化的
"Pooling"
层的步长大于内核大小stride > kernel_size
可能导致出现nan
。例如:
layer { name: "faulty_pooling" type: "Pooling" bottom: "x" top: "y" pooling_param { pool: AVE stride: 5 kernel: 3 } }
y
中出现nan
值的结果。
"BatchNorm"
中的不稳定性有报告称,在某些设置下,
"BatchNorm"
层可能由于数值不稳定性而输出nan
。
这个问题在 bvlc/caffe 中被提出,PR#5136 正试图解决它。
最近,我了解到
debug_info
标志:在'solver.prototxt'
中设置debug_info: true
将使 caffe 在训练期间记录更多调试信息(包括梯度大小和激活值)打印到日志中:这些信息可以帮助发现训练过程中的梯度爆炸和其他问题。
bias_filler {
type: "constant"
value: 0
}
nan
的原因,而是提出了一种帮助调试的方法。
你可以使用以下Python代码层:class checkFiniteLayer(caffe.Layer):
def setup(self, bottom, top):
self.prefix = self.param_str
def reshape(self, bottom, top):
pass
def forward(self, bottom, top):
for i in xrange(len(bottom)):
isbad = np.sum(1-np.isfinite(bottom[i].data[...]))
if isbad>0:
raise Exception("checkFiniteLayer: %s forward pass bottom %d has %.2f%% non-finite elements" %
(self.prefix,i,100*float(isbad)/bottom[i].count))
def backward(self, top, propagate_down, bottom):
for i in xrange(len(top)):
if not propagate_down[i]:
continue
isf = np.sum(1-np.isfinite(top[i].diff[...]))
if isf>0:
raise Exception("checkFiniteLayer: %s backward pass top %d has %.2f%% non-finite elements" %
(self.prefix,i,100*float(isf)/top[i].count))
在你怀疑可能会引起麻烦的某些点将此层添加到train_val.prototxt
中:
layer {
type: "Python"
name: "check_loss"
bottom: "fc2"
top: "fc2" # "in-place" layer
python_param {
module: "/path/to/python/file/check_finite_layer.py" # must be in $PYTHONPATH
layer: "checkFiniteLayer"
param_str: "prefix-check_loss" # string for printouts
}
}
如果你遭遇与我一样的问题,这里有一个解决方案——
我在建立一个使用float16数据类型的网络时遇到了nan或inf的损失。在所有其他方法都失败后,我想到了切换回float32,结果nan的损失问题得到了解决!
因此,底线是:如果你将dtype切换为float16,请将其改回float32。
学习率过高,应该降低。 在RNN代码中,准确性为nan,选择较低的学习率可以解决这个问题。
我尝试构建一个稀疏自编码器,并在其中引入了多层以诱导稀疏性。在运行网络时,我遇到了NaN的问题。在删除一些层(在我的情况下,实际上必须删除1个)之后,我发现NaN消失了。因此,我想太多的稀疏性也可能会导致NaN的出现(一些0/0计算可能已被调用!?)