深度学习梯度弥散与爆炸

问题描述

  • 梯度弥散:在深层神经网络中,梯度弥散是指在反向传播过程中梯度逐渐变小,以至于在网络的较浅层梯度接近于零。这导致网络的这些层的权重几乎不更新,使得模型难以学习到输入数据的复杂特征。
  • 梯度爆炸:相对地,梯度爆炸是指梯度在反向传播过程中变得异常大,导致权重更新过度,从而造成模型参数极端不稳定,甚至数值溢出。这会导致模型无法收敛,训练过程中的权重更新剧烈,使得模型性能大幅下降。

我们用 Python 脚本来可视化梯度消失和梯度爆炸现象,我们可以创建一个深层神经网络并通过它传递一些随机数据,然后观察不同层的梯度大小。为了简化,我们可以使用一个全连接的神经网络,并使用不同的激活函数来模拟梯度消失和爆炸。

在下面的例子中,我将构建一个简单的深层神经网络,使用 PyTorch 进行演示。我们将观察在正向传播后,通过反向传播计算的梯度在不同层的大小。这将帮助我们理解梯度消失和爆炸是如何发生的。

图片[1]-深度学习梯度弥散与爆炸-点头深度学习网站
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 定义一个简单的多层全连接神经网络
class SimpleNet(nn.Module):
    def __init__(self, layer_sizes, init_method=None):
        super(SimpleNet, self).__init__()
        layers = []
        for i in range(len(layer_sizes) - 1):
            layers.append(nn.Linear(layer_sizes[i], layer_sizes[i + 1]))
            layers.append(nn.ReLU())
        self.net = nn.Sequential(*layers[:-1])  # 移除最后一个ReLU

        # 应用初始化方法
        if init_method:
            for layer in self.net:
                if type(layer) == nn.Linear:
                    init_method(layer.weight)

    def forward(self, x):
        return self.net(x)

def visualize_gradients(layer_sizes, init_method=None):
    # 初始化网络和数据
    net = SimpleNet(layer_sizes, init_method)
    inputs = torch.randn(1, layer_sizes[0])
    outputs = torch.randn(1, layer_sizes[-1])

    # 前向传播
    preds = net(inputs)

    # 计算损失并进行后向传播
    criterion = nn.MSELoss()
    loss = criterion(preds, outputs)
    loss.backward()

    # 可视化每层的梯度
    gradients = [layer.weight.grad.norm().item() for layer in net.net if type(layer) == nn.Linear]
    plt.plot(gradients, '-o')
    plt.xlabel('Layer')
    plt.ylabel('Gradient Norm')
    plt.title('Gradient Norms at Each Layer')
    plt.show()

# 网络层次和大小
layer_sizes = [10, 20, 30, 40, 50, 40, 30, 20, 10]

# 可视化具有普通初始化的梯度
visualize_gradients(layer_sizes)

# 使用稍大但仍较小的权重初始化来更准确模拟梯度弥散
visualize_gradients(layer_sizes, init_method=lambda w: nn.init.normal_(w, mean=0, std=0.1))

# 使用稍小但仍较大的权重初始化来更准确模拟梯度爆炸
visualize_gradients(layer_sizes, init_method=lambda w: nn.init.normal_(w, mean=0, std=5))

问题原因

  • 权重和激活函数:这两个问题都与权重的初始化以及激活函数的选择有关。在梯度弥散中,使用如Sigmoid或Tanh这样的激活函数,其导数在输入值非常高或非常低时接近零,导致梯度逐层减小。而在梯度爆炸中,权重初始化过大或使用了没有饱和上限的激活函数(如ReLU),可能导致梯度随着层数增加而指数级增长。
  • 深度网络的连锁反应:无论是梯度弥散还是梯度爆炸,深层网络中的连锁法则都起着关键作用。在反向传播过程中,梯度必须通过多层网络传播,每层的传播过程中都被执行了相乘的操作,这种连乘的操作会导致梯度逐渐减小(弥散)或指数增长(爆炸)。

下面进一步解释权重和激活函数带来的影响:

在神经网络中,激活函数的作用是在网络的每个节点(神经元)上引入非线性,这是使得神经网络能够学习并表示复杂函数的关键因素之一。Sigmoid和Tanh是两种经典的激活函数,它们在早期的神经网络中被广泛使用。

1. Sigmoid激活函数:
– Sigmoid函数的形式是: \(\sigma(x)=\frac{1}{1+e^{-x}}\)
– 它的输出范围在 0 到 1 之间。
– 当输入值 \(x\) 非常大或非常小的时候,Sigmoid函数会饱和,即趋近于1或 0 。在这些区域,函数的斜率 (导数) 接近于零。
2. Tanh激活函数:
– Tanh函数的形式是: \(\tanh (x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\)
– 它的输出范围在-1到1之间。
– 类似地,当输入值 \(x\) 非常大或非常小的时候,Tanh函数同样会饱和,即趋近于 1 或-1。在这些极端输入值下,函数的导数也接近于零。


在神经网络的反向传播过程中,每个神经元的权重更新依赖于梯度,而梯度是由激活函数的导数决定的。当使用Sigmoid或Tanh作为激活函数时,若输入值过大或过小导致函数饱和,其导数会接近于零。这意味着:

当梯度通过饱和区域的神经元反向传播时,梯度会因为乘以一个接近零的数而变得非常小。
在深层神经网络中,这个问题会随着层数的增加而加剧。每一层都有可能进一步减少梯度的大小,导致在网络较浅层的梯度非常小,甚至接近于零。
结果就是网络中靠近输入层的神经元接收到的梯度非常小,导致这些层的权重几乎不更新,从而影响整个网络的学习能力。
这就是为什么在深层神经网络中,Sigmoid和Tanh等激活函数可能导致梯度弥散问题的原因。为了避免这一问题,现代的神经网络通常采用ReLU及其变种作为激活函数,因为ReLU在正区间的导数是恒定的,不会导致梯度弥散。

ReLU (Rectified Linear Unit) 激活函数的公式是 \(f(x)=\max (0, x)\) 。其特点是对于所有正输入,其输出就是输入本身,而对于负输入,输出是 0 。 ReLU的导数在正区间是恒定的(等于1),而在负区间是 0。这意味着对于正的输入值,梯度不会在传递过程中减少。

当使用ReLU作为激活函数时,如果网络中的激活值大多为正数(特别是当权重初始化过大时),梯度在反向传播过程中不会减少,甚至可能因为连续乘以较大的权重而增大。在深层网络中,这种效应会随着层数的增加而累积,导致梯度在网络中传播时可能会指数级增长。这最终导致梯度爆炸,即梯度变得过于巨大,使得权重更新过度,从而破坏模型的训练过程。

 

解决方案

1. 优化激活函数

Leaky ReLU和Parametric ReLU

  • 详细说明
    • Leaky ReLU:此变种通过为负输入值提供一个小的、非零的斜率(例如0.01),公式可以表示为 f(x)=max(0.01x,x)。这避免了在负区间梯度完全消失的问题。
    • Parametric ReLU (PReLU):与Leaky ReLU相似,但它将负区间的斜率作为可学习的参数,进一步提高了模型的灵活性和适应能力。
  • 优势:这些变种通过在负区间提供非零斜率,减少了梯度在负输入值时的消失,有助于保持梯度在整个网络中的健康流动,特别是在深层网络中。
  • 参考博文【link】

2. 使用残差网络(ResNet)结构

  1. 跳跃连接
    • 详细说明:残差网络通过在网络中添加直接连接前一层(或几层前)的输出到后一层的结构,这种结构被称为“跳跃连接”或“残差连接”。这些连接允许部分梯度直接跨过一个或多个层,从而减少了梯度通过每个层的乘法运算的需要。
    • 优势:跳跃连接使得梯度能够更容易地在网络中流动,减少了梯度弥散的问题,特别是在非常深的网络中。
  2. 易于梯度流动的结构
    • 详细说明:由于跳跃连接的存在,梯度可以绕过一些复杂的层级转换,直接传播到更深的层。这样的结构使得网络即使在深度增加时也能够维持有效的梯度流动,从而避免了梯度弥散。
  3. 参考博文【link】

3. 批量归一化(Batch Normalization)

  1. 规范化层输入
    • 详细说明:批量归一化通过在每个层之后(或之前)规范化激活值,确保它们具有统一的均值和方差。这个过程通常包括减去均值并除以标准差,然后通过可学习的参数进行缩放和平移。
    • 优势:这种规范化有助于减少内部协变量偏移,即训练过程中数据分布的变化,从而加速训练过程并提高模型的稳定性。
  2. 缓解梯度弥散
    • 详细说明:通过在网络的每一层保持输入的规范化,批量归一化有助于维持梯度在网络中的健康流动。这不仅有助于缓解梯度弥散,还可以在某种程度上减少梯度爆炸的风险。
  3. 参考博文【link】

4. 适当的权重初始化

  1. He和Xavier初始化
    • 详细说明
      • He初始化:特别适用于ReLU激活函数的网络,它根据前一层的神经元数量来调整初始权重的方差。
      • Xavier初始化:适用于Sigmoid和Tanh激活函数,也根据前一层神经元的数量来调整权重。
    • 优势:这些初始化方法有助于在训练开始时保持梯度在合理的范围内,从而减少了梯度弥散或爆炸的风险。
  2. 其他初始化方法也有类似的效果,详见博文:【link】

5. 使用LSTM或GRU网络结构

  1. 梯度裁剪
    • 详细说明:梯度裁剪是处理梯度爆炸问题的直接方法,它通过设定一个阈值来限制梯度的最大值。如果梯度的大小超过了这个阈值,它会被缩放到合适的大小。
    • 优势:这种方法可以有效地防止梯度在训练过程中变得过大,从而防止梯度爆炸,特别是在循环神经网络中。
  2. 循环神经网络的变种
    • 详细说明:LSTM(长短期记忆网络)和GRU(门控循环单元)是特别设计来处理序列数据的神经网络结构。它们通过引入门控机制,可以有效地控制信息在网络中的流动,从而在保留长期依赖性的同时减少梯度弥散。
    • 优势:这些结构对于处理具有长期依赖性的数据特别有效,可以在循环神经网络中减轻梯度弥散问题。
    • 详解博文:【link】
© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容