理解梯度裁剪

2025 年 6 月 20 日 | 阅读 9 分钟

梯度裁剪解决了在神经网络反向传播中计算梯度时遇到的一个主要挑战。在向后传播的过程中,我们计算所有权重和偏差的梯度,以优化我们的成本函数。人工神经网络在多个领域的成功依赖于梯度的计算。然而,任何有利的情况都常常伴随着一个缺点。

在海量的文本或多维数据中,梯度经常封装从源头派生的信息,包括长程连接。因此,在计算复杂数据时,事情可能会发生灾难性的错误,导致您丢失下一个价值百万美元的模型。幸运的是,可以提前采用梯度裁剪来减轻这个问题。

反向传播的常见挑战

所有现代机器学习应用都依赖于反向传播方法,这种方法比您想象的更为普遍。反向传播用于计算成本函数相对于网络权重和偏差的梯度。它概述了您为了最小化成本函数必须对权重进行的所有修改(最陡峭的下降是正确的 -1*∇,最陡峭的上升是 +∇)。

梯度消失

随着模型复杂性随着隐藏单元的增加而增加,成本函数 (C) 的变化对初始层中权重的影响的转换,或者梯度的范数,变得如此之小,以至于最终趋近于零。我们将这种现象称为梯度消失。

这使得模型学习更加困难。网络在前向传播过程中被扰乱,并且模型最终停止,因为权重不再能够为成本函数 (C) 的下降做出贡献,并保持不变。

梯度爆炸

相反,训练过程中梯度范数的显著增长被称为“梯度爆炸问题”。

长期分量的增长,其增长速度可能比短期分量呈指数级增长,是导致此类事件的原因。因此,由于网络变得不稳定,并且最多无法从训练数据中学习,因此无法进行梯度下降阶段。

通过使用保留先前输入信息的隐藏状态,循环神经网络 (RNN) 在处理序列数据方面表现出色。时间 t 的隐藏状态的值由时间 t 的输入及其在时间 t-1 的值决定。尽管有其优势,但这种设计在训练过程中会导致两个问题:梯度消失和梯度膨胀。

通过梯度裁剪来修复梯度爆炸。

研究梯度问题是几种不同方法关注的重点。

一种突出的策略是 L2 正则化,它向网络的成本函数添加“权重衰减”。随着正则化值的增加和权重的减小,权重变得不那么有用,模型也变得更加线性。

梯度爆炸和消失的原因

训练过程中梯度过大导致模型不稳定的问题被称为“梯度爆炸”。类似地,梯度消失描述了训练过程中变得过小的梯度。这会阻止网络权值的数值波动。这些问题阻止模型从训练数据中学习。接下来的随意的对话并不非常详尽,但它为我们提供了对消失和爆发梯度来源的足够见解。

RNN 通过反向传播在时间上进行训练,使用多层前馈神经网络,其中层数等于时间步长。首先通过为每个时间步创建一个副本来展开网络。然后,我们在展开的网络上进行反向传播,同时考虑权重共享。

其中 W. 表示循环权重矩阵,可以证明损失函数的梯度由 Wᵀ 的 n 个副本的乘积组成,其中 n 是回溯到过去的时间层数。消失和爆发梯形的原因就是这个矩阵乘积。

当标量 a ≠ 1 时,可能发生指数增长或收缩。例如,取 n = 30。例如,0.9ⁿ = 0.042,而 1.1ⁿ ≈ 17.45。我们之所以选择 30,是因为在时间序列分析中检查 30 天的数据,以及在自然语言处理任务中一个短语由 30 个单词组成是很常见的。矩阵乘积 (Wᵀ)ⁿ 的情况类似。假设 Wᵀ 是可对角化的,这是最简单的观察方法。对于某个对角矩阵 D = diag(λ₁, …, λₓ),则 Wᵀ = QDQ⁻¹,且 (Wᵀ)ⁿ = QDⁿQ⁻¹,其中 Dⁿ = diag(λ₁ⁿ, …, λₓⁿ)。

梯度裁剪

处理梯度膨胀的一种方法是梯度修剪。梯度裁剪的概念很简单:如果梯度增长过大,我们会重新缩放梯度以保持其较小的值。换句话说,如果 ||g|| ≥ c,则

其中 g 是梯度,g‖g‖ 是 g 的范数,c 是一个超参数。通过重新缩放,新的 g 的范数将为 c,因为 g/g‖ 是一个单位向量。请记住,如果 g < c,我们无需执行任何操作。

通过使用梯度裁剪,梯度向量 g 的范数保证不超过 c。这使得梯度下降即使在模型的损失函数地形崎岖不平的情况下也能正常工作。下图展示了损失地形中一个非常陡峭悬崖的例子。没有裁剪,参数在进行显著的向下步长后会离开“良好”区域。裁剪将参数保留在“良好”范围内并限制了向下步长的幅度。

Understanding Gradient Clipping

在具有两个参数 w 和 b 的循环网络中,梯度裁剪的影响。在极端悬崖区域,使用梯度裁剪的梯度下降可以更合理地工作。(左)在越过这个小山谷底部后,没有梯度裁剪的梯度下降遇到了来自岩壁的非常大的梯度。(右)当梯度下降与梯度裁剪结合使用时,对悬崖的响应更为温和。步长受到限制,防止其被推离接近解的陡峭区域,即使它确实爬上了岩壁。

使用两种梯度裁剪的变体

  • 基于值的裁剪
  • 按范数裁剪

按值裁剪梯度

按值裁剪的概念很简单。我们指定一个裁剪值的最小值和最大值。

如果梯度超过预定值,我们就将其裁剪到阈值。如果梯度小于下限,我们也将其裁剪到阈值的下限。

以下是算法

其中梯度可以取值的范围位于边界值 max_threshold 和 min_threshold 之间。梯度用 g 表示,而 g 的范数用 g 表示。

按范数裁剪梯度

按值裁剪和按范数裁剪共享相似的概念。区别在于梯度通过乘以梯度的单位向量并与阈值相乘来裁剪。

以下是算法

其中 g 是梯度,‖g‖ 是 g 的范数,阈值是一个超参数。由于 g/g‖ 是一个单位向量,通过重新缩放,新的 g 的范数将等于阈值。请记住,如果 g < c,我们无需执行任何操作。

通过使用梯度裁剪,梯度向量的范数保证最多等于阈值。这使得梯度下降即使在模型的损失函数地形很可能是一个悬崖的情况下也能正常工作。

深度学习网络的梯度裁剪

现在我们已经清楚了梯度爆炸的原因以及梯度裁剪如何解决它。

我们还展示了两种将裁剪应用于深度神经网络的不同方法。因此,让我们看看这两种梯度裁剪技术如何在两个流行的机器学习框架 PyTorch 和 Keras 中实现。

使用 Keras

用于图像分类的开源数字分类数据是 MNIST 数据集,我们将使用它。

在开始编写代码之前,需要在虚拟环境中安装以下库

代码

首先,让我们导入所需的库和模块

接下来,在工作目录中创建一个 .env 文件,并添加以下内容

如下所示,这将允许您通过将这些变量加载到笔记本或脚本中来初始化 Neptune 运行对象

借助 run 对象,我们可以将模型元数据和训练详细信息记录到您的 Neptune 项目仪表板。

现在让我们导入并准备 MNIST 数据集

现在我们构建一个简单的顺序模型

关键步骤现在是定义如何进行裁剪。幸运的是,我们只需一行代码即可完成此操作,方法是定义要使用的优化器。

上面的摘录显示了代码的按值裁剪(注释掉)和按范数裁剪版本。其余代码用于训练模型。

此外,使用 Neptune 记录来自 history 对象 的训练信息

上面的代码行会将训练和验证损失及准确性日志记录到较高的训练目录。

Understanding Gradient Clipping

使用 PyTorch

在此 PyTorch 实现阶段,我们将再次使用 PyTorch DataLoader 类加载数据。然后,利用我们学到的两种方法,我们将使用 Pythonic 语法计算梯度并进行裁剪。

首先,设置您的环境并导入所需的库

使用以下代码创建一个 .env 文件

加载并初始化 Neptune

现在,让我们声明 PyTorch 中的 DataLoader 类以及一些超参数。

定义 LSTM 模型

定义损失函数和优化器

实现梯度裁剪并训练模型

测试模型

停止 Neptune

因此,您现在知道裁剪的作用以及如何使用它。为了帮助您理解其重要性,我们将在本节中展示它的实际应用,呈现一种对比情况。

Understanding Gradient Clipping