在深度学习模型训练过程中,NaN问题指的是损失值、梯度或模型参数在计算中变成非数字状态,导致训练无法继续。这种现象通常出现在反向传播阶段,当梯度数值超出浮点数表示范围,或者前向计算里出现了对数自变量为负、除以零等情况时,结果就会变成NaN并迅速传播到整个网络。

要理解NaN为什么会产生,需要先看神经网络的数值链路。每一层线性变换后接激活函数,再进入下一层,反向时梯度沿原路返回。如果权重初始化过大、网络层数很深,或者学习率设置偏高,梯度可能在层层相乘中指数级放大,这就是梯度爆炸。一旦梯度绝对值超过浮点上限,就会记录为inf,后续加减乘除直接得到NaN。
另一类数值不稳定来自运算本身。比如使用log函数时输入了小于或等于零的数值,或者做归一化时分母batch内方差为零,都会产生非法数学结果。框架通常不会报错而是静默输出NaN,让使用者难以第一时间定位。因此解决NaN不能只盯一处,而要同时约束梯度规模和保障计算合法。
梯度裁剪的作用与实现方式
梯度裁剪是专门应对梯度爆炸的手段。它的核心逻辑是在优化器用梯度更新参数之前,先检查所有梯度的整体范数。如果范数大于预设阈值,就按比例把梯度缩小,使总范数等于阈值。这样无论反向传播算出多大的梯度,都不会让某一步参数更新过分剧烈,从而切断NaN产生的链条。
常见做法有两种。一种是按范数裁剪,把全部可训练变量的梯度拼成向量,算L2范数,超界则乘缩放系数。另一种是按值裁剪,给每个梯度元素设上下限,超出直接截断。下面用表格对比二者特点:
| 裁剪方式 | 操作逻辑 | 适用场景 |
|---|---|---|
| 按范数裁剪 | 计算全局梯度L2范数,超阈值整体缩放 | 循环神经网络、深层Transformer |
| 按值裁剪 | 每个梯度限定在正负区间,越界截断 | 稀疏梯度或局部异常明显时 |
实践中按范数裁剪更常用,因为它保留了梯度方向,只改大小,对收敛影响小。阈值一般取一到十之间,需结合模型规模调整。要注意裁剪不能替代合理学习率,只是兜底保护,若阈值设得过小,模型会欠拟合,表现为损失下降极慢。
数值稳定的关键技巧
数值稳定关注前向与反向全程不出现非法运算。首先是初始化,使用Xavier或He初始化可让每层输出方差维持在相近水平,避免信号在深层中被放大或缩小到极端。其次是归一化,批归一化或层归一化把激活值拉回合理区间,既加速收敛也减少零方差风险。
对于含对数、除法、开方的地方,应做微小常数保护。例如算交叉熵时给概率加一亿分之一再取对数,做除法时分母加epsilon防止零除。这些写法在主流框架已有内置,但自定义运算时容易遗漏。另外采用混合精度训练要开启loss scaling,否则半精度下小梯度会变成零,反传失真诱发不稳定。
数值稳定还包括激活函数选择。 ReLU在负区导数是零,不会引起爆炸却可能造成死神经元;而Tanh、Sigmoid两端饱和,梯度趋零,和大学习率组合反而安全。根据任务选用Swish、GELU等平滑函数,配合归一化,能从底层降低NaN概率。
二者如何配合解决NaN
单独使用梯度裁剪,可以挡住爆炸类NaN,但挡不住前向零除。单独做数值稳定,能消除运算非法,却难防极深网络里累积放大。将两者结合:前向用归一化与常数保护保证每步合法,反向用裁剪保证更新温和,形成双保险。训练循环里先前向算损失,若含自定义数学式就嵌入稳定项,再反向求导,最后裁剪再步进。
以文本生成模型为例,未处理时每几千步出现一次NaN,加入层归一化并将对数概率加保护常数后,频率降到万步一次,再设范数阈值五,连续训练几十万步也无NaN。这说明工程上不必追求单一妙方,组合策略更务实。排查NaN时建议先打印前几层梯度范数,确认是否爆炸,再检查损失项里有无非法输入,逐步定位。
梯度裁剪与数值稳定不是互斥方案,而是训练健壮模型的左右手。前者管住更新幅度,后者管住计算合法,协同起来才能让深度学习训练告别NaN中断。
总结来看,面对NaN问题,开发者应在模型搭建期就引入归一化与合理初始化,在训练脚本中默认开启梯度裁剪,并对所有自定义数学运算做边界保护。养成打印梯度直方图的习惯,便能在崩塌前发现苗头。只有这样,训练过程才真正可控、可复现。