16 数值稳定性和模型初始化
16.1 数值稳定性
数值稳定性是深度学习中比较重要的点,特别是当神经网络变得很深的时候,数值通常很容易变得不稳定。
例 16.1 (神经网络的梯度)
考虑如下有 \(d\) 层的神经网络 \[\mathbf {h}^t=f_t\left(\mathbf {h}^{t-1}\right)\quad\text{and}\quad y = \ell\circ f_d\circ \cdots\circ f_1(\mathbf {x})\]
计算损失 \(\ell\) 关于参数 \(\mathbf {W}_t\) 的梯度 (Gradient)
\[\frac{\partial \ell}{\partial \mathbf{W}^t} = \frac{\partial \ell}{\partial \mathbf{h}^{d}} \underbrace{\frac{\mathbf{h}^{d}}{\mathbf{h}^{d-1}}\cdots\frac{\mathbf{h}^{t+1}}{\mathbf{h}^{t}}}_{d-t~\text{次矩阵乘法}} \frac{\mathbf{h}^{t}}{\mathbf{W}^{t}}\]
16.1.1 数值稳定性的常见两个问题
梯度爆炸
- 假设梯度都是一些比1大的数比如1.5,做100次乘积之后得到 \(4\times 10^{17}\),这个数字很容易带来一些浮点数上限的问题(需了解更多请参考计算机系统-计算机中浮点数的存储方式)。
- 问题
- 值超出值域(infinity)
- 对于16位浮点数尤为严重(数值区间 [6e-5 , 6e4]),GPU用16位浮点数更快
- 对学习率敏感
- 如果学习率太大→大参数值→更大的梯度,如此循环几次,容易导致梯度爆炸
- 如果学习率太小→训练无进展
- 我们可能需要在训练过程中不断调整学习率
- 值超出值域(infinity)
梯度消失
- 现象描述: 假设梯度都是一些比1小的数比如0.8,做100次乘积之后得到 \(2\times 10^{-10}\),也可能会带来浮点数下溢的问题。
- 问题
- 梯度值变为0
- 对16位浮点数尤为严重
- 训练没有进展
- 不管如何选择学习率,由于梯度已经为0了,学习率x梯度=0
- 对于底部层尤为严重
- 仅仅顶部层训练得较好。第t层导数包含d-t个矩阵乘积,越往底层走,t越小,乘得越多,梯度消失越严重,所以底部层效果更差。
- 无法让神经网络更深。只能把顶部层训练得比较好,底部层跑不动,这和给一个浅的神经网络没有什么区别。
- 梯度值变为0
16.1.2 代码实践
16.2 模型初始化与激活函数
16.2.1 让训练更加稳定
我们的一个核心目标是如何让训练更稳定,梯度值不要太大也不要太小
- 目标:让梯度值在合理的范围内
- 例如 [1e-6, 1e3]
- 常用方法:
- 将乘法变加法:
- ResNet(跳跃连接,如果很多层,加入加法进去)
- LSTM(引入记忆细胞,更新门,遗忘门,通过门权重求和,控制下一步是否更新)
- 归一化:
- 梯度归一化(归一化均值,方差)
- 梯度裁剪 (Clipping) :比如大于/小于一个固定的阈值,就让梯度等于这个阈值,将梯度限制在一个范围中。(可以缓解梯度爆炸)
- 合理的权重初始和激活函数:本节课讲述重点
- 将乘法变加法:
16.2.2 基本假设:让每层的均值/方差是一个常数
-
将每层的输出和梯度都看做随机变量
比如第i层有100维,就将输出和梯度分别看成100个随机变量
-
让它们的均值和方差都保持一致
我们的目标,这样不管神经网络多深,最后一层总与第一层差不多,从而不会梯度爆炸和消失需要控制 被激活函数映射之后的隐藏层值
ReLU 和 tanh 对此都表现良好, 然而, 对于 Sigmoid 做泰勒展开发现, \[\operatorname{Sigmoid}(x)=\frac{1}{2} + \frac{x}{4} - \frac{x^3}{48} + o(x)\] 为了防止梯度爆炸, 可以Sigmoid 做 Scaling 调整后为 \[4\times \operatorname{Sigmoid}(x) -2\]
16.2.3 总结
当数值过大或者过小时,会导致数值问题。
常发生在深度模型中,因为其会对n个数累乘。
合理的权重初始值(如Xavier) 和 激活函数的选取(如relu, tanh, 调整后的sigmoid)可以提升数值稳定性。