训练深度学习模型也被称为“炼丹”,这其中涉及到多个方面的参数调整,本文介绍几个主流的优化策略。
损失值
训练过程中loss突然变成NaN的可能原因与解决方案
思考神经网络的训练过程:输入数据,前向传播,计算损失,反向传播,更新参数后继续前向传播,计算损失,反向传播…每一个阶段都可能导致loss变成NaN
。
输入部分出现坏样本
坏样本一般是指不符合任务要求,需要被清洗掉的样本,比如图像识别任务中全黑图片等。坏样本不符合模型训练的要求,因此输入到模型中根本无法得到正常的结果,所以loss会变得巨大以至于成为NaN。
解决方案:找出并清理坏样本
如果发现loss在逐步减小但是处理某一批数据时突然变为NaN,一般可以考虑是样本的原因。在此基础上,可以暂时停掉模型的反向传播,保持初始参数,也就是无论loss是多少都不进行参数调整。如果仍然是跑到某一批数据就出现NaN,那么就可以排除是模型优化过程中梯度爆炸的问题。(因为模型参数根本没变,可以排除是训练导致的loss爆炸)
我们可以设置训练的batch size为1,逐样本送入网络,同时设置shuffle为False,也就是不打乱顺序。出现loss为NaN,表示遇到了坏样本,此时迭代的批次数,就是该坏样本在数据集的位置,从而定位了坏样本出现的位置。排除后,重新训练,继续观察loss的情况,直到能正常训练一个epoch,表示所有的坏样本均被去掉,可以开始正常训练了。不合适的损失函数
采用一些涉及到除法、对数函数等损失函数时,需要注意除零、对数输入是负数的情况,这同样会导致loss变成NaN。这种情况一般在开始训练时就出现,不存在loss逐渐增大到爆炸的过程。
解决方案:一般通过对网络输出值域以及损失函数定义的数学分析进行判断,并进行相应的调整。梯度爆炸
在训练过程中如果学习率等超参数设置的不合理,会导致优化过程中不仅没有减小loss,反而因为震荡导致loss逐渐增大,最终超过了float表示范围,出现NaN。这一过程通常伴随渐变和自激的特性,即在训练到某一轮时对参数调整步幅过大从而导致模型变差,输出的loss随之增大,此时反向传播的梯度也会随之增大,最终模型的参数越调越差,直至崩溃。
如果输出每一轮的loss,就可以发现loss逐渐增大的过程,从而能够基本锁定问题出现在梯度爆炸这一层面。
解决方案:一般是减小学习率,如果涉及到多个损失函数,可以找出哪一项导致了梯度爆炸,通过减小其权重解决问题
出现loss为NaN
的情况将导致训练无法正常进行,需要排查可能的原因并解决。梯度爆炸是最常见的情况,损失函数问题和坏样本问题比较难想到。只有全面排查,彻底解决loss为NaN
的问题,才能使模型按照我们希望的方向进行优化。
学习率
学习率如果设置的过大,有可能会导致梯度爆炸,同时也有可能导致难以收敛到最优点;反之,如果学习率过小,会导致网络训练得太慢。
warmup策略
在训练初期,由于网络参数是随机初始化的,损失值很大,导致梯度值也很大,此时我们希望学习率小一点,防止梯度爆炸;而在训练中期,我们希望学习率大一点,加速网络训练;在训练末期,我们希望网络收敛到最优点,此时学习率也应该小一点。
如果使用较大的batch size训练神经网络时,可以使用warmup策略。
Warmup策略顾名思义就是让学习率先预热一下,在训练初期不直接使用最大的学习率,而是用一个逐渐增大的学习率去训练网络,当学习率增大到最高点时,再使用学习率下降策略衰减学习率的值。
实验表明,在batch size比较大时,warmup可以稳定提升模型的精度。
学习率下降策略
在整个训练过程中,我们不能使用同样的学习率来更新权重,否则无法到达最优点,所以需要在训练过程中调整学习率的大小。
在训练初始阶段,由于权重处于随机初始化的状态,损失函数相对容易进行梯度下降,所以可以设置一个较大的学习率;在训练后期,由于权重参数已经接近最优值,较大的学习率无法进一步寻找最优值,所以需要设置一个较小的学习率。
有多种学习率下降方式:
- 阶梯式下降
- 多项式下降
- 指数下降
- 余弦下降
其中cosine_decay
无需调整超参数,鲁棒性也比较高,成为现在提高模型精度首选的学习率下降方式。
Dropout
Dropout在训练时让某神经元的激活值以一定的概率$p$(满足伯努利分布)停止工作,由于它不会太依赖某些局部特征,这样可以使得模型泛化性更强,防止过拟合。
在测试时,为了保证每次推理的一致性,不能再随机让神经元激活值失活。假设每个神经元原始输出的绝对值均值为$x$,那么该层训练时dropout之后的输出绝对值均值为$(1-p)*x$,如果测试时简单地让所有神经元都按照正常输出值原样输出,那么输出的绝对值均值为$x$,这会导致训练和测试的不一致。
因此,带dropout的层,测试时会让所有神经元都输出,但是输出值要从原始的$x$变成$(1-p)*x$。
当然,也可以在训练的时候就除以$1-p$,这样训练时dropout后每个神经元输出的绝对值均值就是$x$,测试的时候直接做恒等映射输出就行,速度更快,这也是PyTorch的官方实现。
【参考文献】