深度神经网络的训练-梯度消失和梯度爆炸问题

梯度消失与梯度爆炸问题

反向传播算法的工作原理是从输出层到输入层次,并在此过程中传播误差梯度。一旦算法计算出损失函数相对于每个参数的梯度,可以使用这些梯度以梯度下降步骤来更新每个参数。

随着算法向下传播到较低层,梯度通常会越来越小。结果梯度下降更新使较低层的连接权重保持不变,训练不能收敛到一个良好的解。这就是梯度消失问题。

在某种情况下,可能会出现相反的情况:梯度可能会越来越大,各层需要更新很大的权重直到算法发散。这就是梯度爆炸问题。

深度神经网络很受梯度不稳定的影响,不同的层可能以不同的速度学习

Glorot 和 He 初始化

Glorot和Bengio在他们的论文中提出一种能显著缓解不稳定梯问题的方法。他们指出,我们需要信号在两个方向上正确流动:进行预测时,信号为正向;在反向传播梯度时,信号为反向。我们既不希望信号小时,也不希望它爆炸饱和。为了信号正确流动,作者认为,我们需要每层输出的方差等于其输入的方差,并且我们需要在反方向时流过某层之前和之后的梯度具有相同的方差。除非该层具有相等数量的输入和神经元,否则实际上不能同时保证两者,但是Glorot和Bengio提出了一个很好的折中方案,在实践中证明很好地发挥作用,按照下面的公式随机初始化每层的连接权重,其中\(fan_{avg}=(fan_{in}+fan_{out})/2\)。这种初始化策略称为Xavier初始化或者Glorot初始化。

\[\mbox{正态分布,其均值为0,方差为}\,\sigma^2=\frac{1}{fan_{avg}} \]

\[\mbox{或-r和+r之间的均匀分布,其中}\, r=\frac{3}{fan_{avg}} \]

非饱和激活函数

ReLU激活函数并不完美。它有一个被称为“濒死的ReLU”的问题:在训练过程,某些神经元实际“死亡”了,这意味着它们停止输出除0以外的任何值。在某些情况下,你可能发现网络中一半的神经元都死了,特别是如果你使用较大的学习率。当神经元的权重进行调整时,其输入的加权和对于训练集中所有实例均为负数,神经元会死亡。发生这种情况,它只会继续输出零,梯度下降不会再影响它,因为ReLU函数的输入为负时其梯度为零。

批量归一化

尽管将He初始化与ELU(或ReLU的任何变体)一起使用可以显著减少训练开始时的梯度消失/梯度爆炸问题的危险,但这并不能保证它们在训练期间不会再出现。

2015年的一篇论文中,Sergey Ioffe和Christian Szegedy提出了一种称为批量归一化(BN)的技术来解决这些问题。该技术包括在模型中的每个隐藏层的激活函数之前或之后添加一个操作。该操作对每个输入零中心并归一化,然后每层使用两个新的参数向量缩放和偏移其结果:一个用于缩放,另一个用于偏移。该操作可以使模型学习各层输入的最佳缩放和均值。在许多情况下,如果将BN层添加为神经网络的第一层,则无需归一化训练集(例如,使用StandardScaler);BN层会完成此操作。

为了使输入零中心并归一化,该算法需要估计每个输入的均值和标准差。通过评估当前小批次上的输入的均值和标准差(因此成为“批量归一化“)来做到这以下

下列公式总结了整个操作

\[1.\ \mu_B=\frac{1}{m_B}\sum_{i=1}^{m_B}x^{(i)} \]

\[2.\ \sigma_B^2=\frac{1}{m_B}\sum_{i=1}^{m_B}({x^{(i)}-\mu_B})^2 \]

\[3.\ \hat{x}^{(i)}=\frac{x^{(i)}-\mu_B}{\sqrt{\sigma_B^2+\varepsilon}} \]

\[4.\ z^{(i)}=\gamma\otimes\hat{x}^{(i)}+\beta \]

  • \(\mu_B\)是输入均值的向量,在整个小批量B上评估(每个输入包含一个均值)

  • \(\sigma_B\)是输入标准差的向量,也在整个小批量中进行评估(每个输入包括一个标准差)

  • \(m_B\)是小批量中的实例数量

  • \(x^{(i)}\)是实例i的零中心和归一化输入的向量

  • \(\gamma\)是该层的输出缩放参数变量(每个输入包含一个缩放参数)

  • \(\otimes\)表示逐元素乘法(每个输入乘以其相应的输出缩放参数)

  • \(\beta\)是层的输出移动(偏移)参数向量(每个输入包含一个偏移参数)。每个输入都通过其相应的移动参数进行偏移

  • \(\varepsilon\)是一个很小的数字以避免被零(通常为\(10^{-5}\))。这称为平滑项

  • \(z^{(i)}\)是BN操作的输出。它是输入的缩放和偏移版本

批量归一化应用于最先进的图像分类模型,以少14倍的训练步骤即可达到相同的精度。

批量归一化的作用就像正则化一样,减少了对其他正则化技术的需求

用Keras实现批量化

实现批量归一化的方法只需在每个隐藏层的激活函数之前或之后添加一个BatchNormalization层,然后可选地在模型的第一层后添加一个BN层

import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf
from tensorflow import keras

model=keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28,28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300,activation='elu',kernel_initializer='he_normal'),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100,activation='elu',kernel_initializer='he_normal'),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(10,activation='softmax')
])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten (Flatten)            (None, 784)               0         
_________________________________________________________________
batch_normalization (BatchNo (None, 784)               3136      
_________________________________________________________________
dense (Dense)                (None, 300)               235500    
_________________________________________________________________
batch_normalization_1 (Batch (None, 300)               1200      
_________________________________________________________________
dense_1 (Dense)              (None, 100)               30100     
_________________________________________________________________
batch_normalization_2 (Batch (None, 100)               400       
_________________________________________________________________
dense_2 (Dense)              (None, 10)                1010      
=================================================================
Total params: 271,346
Trainable params: 268,978
Non-trainable params: 2,368
_________________________________________________________________

第一个BN层的参数,两个是可训练的(通过反向传播),两个不是

[(var.name,var.trainable)for var in model.layers[1].variables]
[('batch_normalization/gamma:0', True),
 ('batch_normalization/beta:0', True),
 ('batch_normalization/moving_mean:0', False),
 ('batch_normalization/moving_variance:0', False)]

在Keras创建BN层时,还会创建两个操作,在训练期间的迭代中,Keras都会调用这两个参数。这些操作会更新移动平均值。由于使用的是TensorFlow后端,所以这些操作都是TensorFlow操作

model.layers[1]
<tensorflow.python.keras.layers.normalization_v2.BatchNormalization at 0x7efbfda216d0>

BN论文的作者主张在激活函数之前(而不是之后)添加BN层。哪个更好取决于任务。

要在激活函数之前添加BN层,必须从隐藏层中删除激活函数,并将其作为单独的层添加到BN层之后

由于批量归一化层的每一个输入都包含一个偏移参数,所以可以从上一层中删除该偏置项(创建时只需要传递use_bias=False即可)

model=keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28,28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300,kernel_initializer='he_normal',use_bias=False),
    keras.layers.BatchNormalization(),
    keras.layers.Activation('elu'),
    keras.layers.Dense(100,kernel_initializer='he_normal',use_bias=False),
    keras.layers.BatchNormalization(),
    keras.layers.Activation('elu'),
    keras.layers.Dense(10,activation='softmax')
    
])

梯度裁减

缓解梯度爆炸问题的另一种流行技术是在反向传播期间裁减梯度,使它们永远不会超过某个阈值。这称为梯度裁减。最常用于循环神经网络,因为在RNN中难以使用批量归一化

在Keras中,实现梯度裁减仅仅是在一个创建优化器时设置clipvalue或clipnorm参数的问题

optimizer=keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss='mse',optimizer=optimizer)
posted @ 2021-09-26 21:15  里列昂遗失的记事本  阅读(410)  评论(0编辑  收藏  举报