深度学习中梯度消失和爆炸问题

2025年7月10日 | 阅读 14 分钟

在深度学习中,优化在训练神经网络中起着重要作用。梯度下降是最流行的优化方法之一。然而,它可能会出现两个重要的问题:梯度消失和梯度爆炸。本文将解释这些问题以及更多内容:它们是什么,为什么会发生,以及如何处理它们。我们还将编译并训练一个模型来理解如何在训练过程中识别和处理这些挑战。

什么是梯度消失问题?

梯度消失问题是在反向传播计算中出现的一种情况,当激活函数的梯度(导数)在通过深度神经网络的层向后传播时,往往会变得非常小。这个问题在非常深的网络中尤其普遍,因为梯度倾向于在层之间变得越来越小。因此,权重更新非常小,几乎为零,导致训练过程缓慢且效率低下。在极端情况下,模型甚至可能停止学习。

梯度消失问题为什么会发生?

在反向传播中会遇到梯度消失问题,梯度通过神经网络的层向后传播,并且它们往往会消失。这些梯度在传播过程中会减小,因此对初始层的权重更新的影响微乎其微。这使得学习速度大大减慢,在最坏的情况下,甚至可能导致网络根本无法训练。

这尤其与 sigmoid 和 tanh 等激活函数有关。这些函数将输入值压缩到很小的范围内(sigmoid 为 [0, 1],tanh 为 [-1, 1]),它们的导数范围也很小(sigmoid 为 0 到 0.25,tanh 为 0 到 1)。当这些函数的参数非常大或非常小时,激活会饱和,此时导数又会趋近于零。

这在浅层网络中可能不是大问题。然而,在深度神经网络中,这种小的梯度在反向传播中会一层一层地相乘。这会导致当梯度到达第一层时,梯度实际上为零,从而导致第一层学习缓慢或停滞。因此,网络在学习数据中有用的模式时会遇到困难,这会影响性能,在某些情况下,甚至可能导致网络根本无法收敛。

检测梯度消失问题的有哪些方法?

1. 模型性能停滞

梯度消失问题的第一个迹象是模型性能指标(例如损失或准确率)的停滞。经过多个训练周期后,损失仍然很高,这意味着网络没有在学习。

2. 权重没有变化

当权重(尤其是在网络较低层的权重)在训练过程中变化不大时,这表明梯度太小,无法对权重进行有意义的更新。这种不动的移动意味着这些层没有发生学习。

3. 梯度值非常小

通过大量层反向传播的梯度可能会变得非常小。检测梯度消失问题的最简单方法是查看梯度值;如果您发现梯度值在较深的层中趋近于零,那么您肯定遇到了梯度消失问题。

4. 可视化技术

有一些工具可以帮助可视化网络中的梯度行为,例如梯度直方图或梯度范数图。此类可视化可以告诉您梯度在反向传播过程中是否减小。

5. 学习曲线平坦或不稳定

随时间推移,平坦或随机不稳定的训练和验证曲线可能是模型因梯度消失而无法正常学习的迹象。

6. 收敛缓慢或不发生。

如果您的模型需要过长的时间来训练或无法收敛到最优解,这可能是由于梯度太小,无法指导模型达到收敛解。

我们可以做些什么来解决梯度消失问题?

1. 批量归一化

批量归一化对任何给定层的输入进行标准化,并最小化内部协变量偏移。这使得训练更稳定、更快速,因为它保持了梯度通过层的流的一致性;换句话说,学习效率更高,梯度消失的可能性更小。

2. 使用 ReLU 激活函数

整流线性单元 (ReLU) 是一种激活函数,它将负输入映射到零,并保持正输入不变。与 sigmoid 或 tanh 不同,ReLU 不会将值压缩到很小的范围内,这使得梯度可以在反向传播中持续存在,从而降低了它们消失的可能性。

3. 跳跃连接和残差网络 (ResNets)

跳跃连接允许梯度在反向传播中避开一层或多层,这是一种确保改进梯度流的技术。它在 ResNets 等非常深的网络中特别有用,因为这些快捷连接可以防止流经网络的梯度收缩得太大。

4. 带有 LSTM 和 GRU 的循环神经网络

长序列可能会导致循环神经网络中的梯度消失问题。长短期记忆 (LSTM) 和门控循环单元 (GRU) 的架构通过引入门来调节信息和梯度的传递,从而有助于解决此问题,确保在整个时间段内保留重要信号。

5. 梯度裁剪

梯度裁剪在训练过程中将梯度值限制在一个最大值。虽然它通常用于解决梯度爆炸问题,但它也适用于解决梯度消失问题,方法是确保梯度不会变得太小或太大,从而稳定训练过程。

构建和训练模型来说明梯度消失问题。

现在,是时候看看梯度消失问题在深度神经网络中是如何发生的,以及如何有效地解决它了。

步骤 1:导入必要的库

首先导入所有必要的库,以便创建、训练和可视化深度学习模型。

步骤 2:加载数据集

通过将 Credit_card.csv 和 Credit_card_label.csv 这两个 CSV 文件导入 Pandas DataFrame(分别表示为 df 和 labels),来加载数据。

步骤 3:数据预处理

将 labels DataFrame 中名为 label 的列的值转换为整数列类型,然后将其添加为列,并重命名为 Approved,添加到主 DataFrame 中。

步骤 4:特征工程

使用当前数据创建新特征,例如 Age、EmployedDaysOnly 和 UnemployedDaysOnly,以改进数据集。

使用 pd.categorical 将 cats 中找到的分类变量编码为数字。通过填充这些列中的缺失值来使用它们各自的模式,以确保数据的一致性。

步骤 5:不平衡数据处理和拆分

为了解决类别不平衡问题,对少数类进行过采样,使数据集更加平衡。

在平衡数据后,将其划分为训练、验证和测试数据,以便用于建模和模型测试。

步骤 6:分类特征编码

此代码行使用 cat.codes 选项,将 cats 列表中的所有列转换为各自类别数字编码的版本。转换基于为每个唯一类别分配一个特定代码,其中单词类被更改为可用于构建模型的数值。

步骤 7:模型构建

创建神经网络最常见的方法是使用 Keras 模型,称为 Sequential,它允许逐层堆叠以构建网络架构。

步骤 8:模型分层

我们向模型添加 10 个密集(全连接)层。每个隐藏层的神经元数量为 10,激活函数为 sigmoid。第一层还指定 input_dim=18,表示输入特征有 18 个。这是输入层。

最后,输出层由 1 个神经元组成,使用 sigmoid 激活函数,因此适用于二元分类。

步骤 9:模型编译

在此步骤中,通过定义模型期望的学习方式以及评估其性能来编译模型。我们定义了一个损失度量来衡量估计值和实际值之间的差异,一个优化器来以减少损失的方式更改权重,以及一个用于监控模型准确性的度量。

处理二元分类问题时,通常使用二元交叉熵损失,Adam 等优化器是典型的,准确率通常是衡量模型学习进展情况的典型度量。

步骤 10:模型训练

在此步骤中,模型在训练数据(X_train 和 y_train)上训练 100 个周期。在训练过程中记录的损失和准确率度量存储在 history 对象中,以便可以非常有用地分析模型的性能。

输出

 
Epoch 1/100
65/65 [==============================] - 3s 3ms/stop - loss: 0.7027 - Accuracy: 0.5119
Epoch 2/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6936 - Accuracy: 0.5119
Epoch 3/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6933 - Accuracy: 0.5119
.
.
Epoch 97/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6930 - Accuracy: 0.5119
Epoch 98/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6930 - Accuracy: 0.5119
Epoch 99/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6932 - Accuracy: 0.5119
Epoch 100/100
65/65 [==============================] - 0s 3ms/stop - loss: 0.6929 - Accuracy: 0.5119

步骤 11:绘制训练损失

输出

Vanishing and Exploding Gradient Problems in Deep Learning

梯度消失问题的解决方案

步骤 1:特征缩放

为了解决梯度消失问题,建议对输入特征进行归一化。对数据进行归一化或标准化可确保输入的值处于可扩展范围内,从而有助于在反向传播时梯度传播。此过程减少了梯度在网络层之间过度收缩的可能性。

步骤 2:更改模型

更深的网络结构

通过增加隐藏层和增加神经元数量来扩展模型。这允许网络理解更复杂的模式和更深层次的信息表示。

提前停止

在训练期间,通过跟踪验证损失来包含提前停止。当损失在特定数量的周期(耐心)后未能得到令人满意的降低时,将终止训练,以防止过拟合和不必要的计算。

增加 Dropout

在每个密集层之后添加 dropout 层,通过在训练时随机关闭一部分神经元来抑制过拟合。

调整学习率

为了获得稳定的收敛,使用 0.001 的学习率。也可以尝试其他学习率以最大化性能。

输出

 
Epoch 1/100
100/100 [==============================] - 2s 10ms/stop - loss: 0.6901 - Accuracy: 0.5362 - val_loss: 0.6842 - val_accuracy: 0.5700
Epoch 2/100
100/100 [==============================] - 0s 5ms/stop - loss: 0.6789 - Accuracy: 0.5854 - val_loss: 0.6755 - val_accuracy: 0.5940
Epoch 3/100
100/100 [==============================] - 0s 5ms/stop - loss: 0.6703 - Accuracy: 0.6096 - val_loss: 0.6672 - val_accuracy: 0.6120
...
Epoch 18/100
100/100 [==============================] - 0s 5ms/stop - loss: 0.5256 - Accuracy: 0.7513 - val_loss: 0.5902 - val_accuracy: 0.7000
Epoch 19/100
100/100 [==============================] - 0s 5ms/stop - loss: 0.5167 - Accuracy: 0.7600 - val_loss: 0.5950 - val_accuracy: 0.6980
Restoring model weights from the end of the best epoch.
Epoch 00019: early stopping

评估指标

输出

 
Classification Report:
              precision    recall  f1-score   support

         0.0       0.78      0.83      0.80       100
         1.0       0.76      0.70      0.73        80

    accuracy                           0.77       180
   macro avg       0.77      0.77      0.77       180
weighted avg       0.77      0.77      0.77       180

什么是梯度爆炸?

梯度爆炸问题是在深度神经网络训练过程中发生的问题。当损失相对于模型权重的导数异常高时,就会发生这种情况。它可能导致权重不可控,并导致训练不稳定和模型崩溃。

梯度爆炸为什么会发生?

相反,有时会发现损失函数的导数(梯度)在训练过程中会爆炸,这是一种称为梯度爆炸的现象,发生在反向传播期间,当损失的导数在层之间相乘时。这基本上是梯度消失问题的反面。

这主要取决于网络中的权重值,而不是激活函数。当权重值过大时,训练期间会产生较大的导数。结果是权重的突然且不稳定的变化,并且模型永远无法达到稳定的解决方案。

简而言之,梯度爆炸发生在梯度太大而无法处理,最终导致权重过大,从而使训练不稳定,模型无法充分学习。

识别梯度爆炸有哪些方法?

训练深度神经网络是一项密集的工作,因为有必要监控梯度爆炸的行为。以下是一些方法:

1. 不稳定的损失

损失是痉挛的,它会突然跳升或不逐渐下降。这表明大梯度导致了不稳定的权重更新。

2. NaN 值

强烈表明梯度溢出的指标是损失或任何其他计算中出现 NaN(非数字)。

3. 权重的剧烈增长

如果模型的权重在训练过程中开始呈指数级持续增长,那么这就导致了梯度爆炸。

4. 梯度可视化

一旦这些梯度的幅度变得非常大,就可能不清楚问题出在哪里。使用 TensorBoard 等工具可以同时跟踪和可视化逐层的梯度,从而可以轻松识别异常大的值。

梯度爆炸问题如何解决?

1. 梯度裁剪

这可以防止在进行反向传播时梯度过大,因为它设置了一个最大限制。任何大于此数字的梯度都会被裁剪,以确保它不会超出范围。这还可以避免梯度取大值并最终导致训练不稳定的情况。

2. 批量归一化

此方法通过标准化每个小批量中的激活来保持每层输入的范围稳定。它还可以保持层之间梯度尺度的连续性,从而最大程度地减少梯度消失和爆炸的可能性,并使训练总体上更稳定。

训练和构建梯度爆炸问题的示例

我们将使用与梯度消失示例相同的预处理数据,只是这次我们将指定一个能体现梯度爆炸问题的神经网络。

步骤 1:创建模型并添加层

为了实现这种效果,我们从具有高权重初始化和大量层数的神经网络开始,以在训练过程中遇到梯度爆炸的高可能性。

步骤 2:编译模型

在此步骤中,我们通过指定损失函数、优化器和评估指标来定义要训练的模型。这种安排预设了模型使用训练数据进行训练。

步骤 3:模型训练

然而,在这一点上,我们开始训练过程,即我们将训练数据拟合到模型。在多个周期中,模型会训练自己以适应权重以最小化损失标准。

步骤 4:查看训练损失

在这里,我们将绘制每个周期的训练损失,因为我们想看看模型的性能将如何变化。这有助于检测不稳定的训练或梯度爆炸。

输出

Vanishing and Exploding Gradient Problems in Deep Learning

梯度爆炸问题的解决方案

为了缓解梯度爆炸问题,可以通过以下方式更新模型:

1. 权重初始化

激活 glorot_uniform 初始化器函数的实现,该函数支持训练期间的梯度流,并广泛用于深度网络。

2. 梯度裁剪

使用梯度裁剪,通过将 Adam 优化器的 clipnorm 参数设置为 1.0 来实现。这会将梯度归一化值限制在一个固定点,而不会升级。

3. 核约束

通过对层中的权重设置 max_norm 约束(设置为 2.0)来对梯度流施加额外的控制,从而限制了权重向量的范数范围。

代码

输出

 
Epoch 1/100
65/65 [==============================] - 2s 10ms/stop - loss: 0.6882 - Accuracy: 0.5431 - val_loss: 0.6760 - val_accuracy: 0.5789
Epoch 2/100
65/65 [==============================] - 0s 5ms/stop - loss: 0.6739 - Accuracy: 0.5752 - val_loss: 0.6618 - val_accuracy: 0.6114
Epoch 3/100
65/65 [==============================] - 0s 5ms/stop - loss: 0.6601 - Accuracy: 0.6064 - val_loss: 0.6485 - val_accuracy: 0.6201
...
Epoch 35/100
65/65 [==============================] - 0s 6ms/stop - loss: 0.1798 - Accuracy: 0.9313 - val_loss: 0.2062 - val_accuracy: 0.9199
Epoch 36/100
65/65 [==============================] - 0s 6ms/stop - loss: 0.1735 - Accuracy: 0.9331 - val_loss: 0.2005 - val_accuracy: 0.9273
...
Epoch 42/100
65/65 [==============================] - 0s 6ms/stop - loss: 0.1650 - Accuracy: 0.9376 - val_loss: 0.1998 - val_accuracy: 0.9288

最终评估报告(训练后)

输出

 
precision    recall  f1-score   support

           0       0.95      0.91      0.93       352
           1       0.91      0.95      0.93       335

    accuracy                           0.93       687
   macro avg       0.93      0.93      0.93       687
weighted avg       0.93      0.93      0.93       687

结论

为了训练深度神经网络,有必要解决诸如梯度消失和爆炸等问题。ReLU 激活、适当的权重初始化(如 He 或 Glorot)、梯度裁剪、批量归一化以及架构更改等技术有助于在反向传播期间将梯度保持在可控范围内。这些是限制梯度过小或过大,并使行为稳定且有效的学习方法。

通过使用这些技术,神经网络将有更大的机会有效收敛,在训练过程中不失败,并获得更好的性能。适当的设计决策可以极大地提高模型学习复杂模式并有效泛化到以前未见过数据的能力。