如何使用梯度裁剪(Gradient Clipping)避免梯度爆炸

在这里插入图片描述
给定误差函数,学习率,甚至目标变量的大小,训练神经网络可能变得不稳定。训练期间权重的较大更新会导致数值上溢或下溢,通常称为梯度爆炸(gradients exploding)。

梯度爆炸在递归神经网络中更为常见,例如LSTM,因为梯度的累积在数百个输入时间步长上展开。

梯度爆炸的一种常见且相对容易的解决方案是:在通过网络向后传播误差并使用其更新权重之前,更改误差的导数。两种方法包括:给定选定的向量范数( vector norm)来重新缩放梯度;以及裁剪超出预设范围的梯度值。这些方法一起被称为梯度裁剪(gradient clipping)。

1. 梯度爆炸和裁剪
使用随机梯度下降优化算法训练神经网络。这首先需要在一个或多个训练样本上估算损失,然后计算损失的导数,该导数通过网络反向传播,以更新权重。使用学习率控制的反向传播误差的一小部分来更新权重。

权重的更新可能会很大,以至于权重的数值精度超出或低于该数值精度。权重在上溢或下溢时可以取“NaN”或“Inf”值,但网络将毫无用处,因为信号流过无效权重时永远预测NaN值。权重的上溢或下溢是指网络训练过程的不稳定性,并且由于不稳定的训练过程导致网络无法进行训练,从而导致模型实质上是无用的,因此被称为梯度爆炸。

在给定的神经网络(例如卷积神经网络或多层感知器)中,可能由于配置选择不当而发生梯度爆炸:
学习率选择不当会导致较大的权重更新。
准备的数据有很多噪声,导致目标变量差异很大。
损失函数选择不当,导致计算出较大的误差值。

在递归神经网络(例如长短期记忆网络)中容易出现梯度爆炸。通常,可以通过精心配置网络模型来避免爆炸梯度,例如,选择较小的学习速率,按比例缩放目标变量和标准损失函数。尽管如此,对于具有大量输入时间步长的递归网络,梯度爆炸仍然是一个需要着重考虑的问题。

梯度爆炸的一种常见解决方法是先更改误差导数,然后通过网络反向传播误差导数,然后使用它来更新权重。通过重新缩放误差导数,权重的更新也将被重新缩放,从而大大降低了上溢或下溢的可能性。更新误差导数的主要方法有两种:
梯度缩放(Gradient Scaling)
梯度裁剪(Gradient Clipping)

梯度缩放涉及对误差梯度向量进行归一化,以使向量范数大小等于定义的值,例如1.0。只要它们超过阈值,就重新缩放它们。如果渐变超出了预期范围,则渐变裁剪会强制将渐变值(逐个元素)强制为特定的最小值或最大值。这些方法通常简称为梯度裁剪。

当传统的梯度下降算法建议进行一个非常大的步长时,梯度裁剪将步长减小到足够小,以至于它不太可能走到梯度最陡峭的下降方向的区域之外。

它是一种仅解决训练深度神经网络模型的数值稳定性,而不能改进网络性能的方法。

梯度向量范数或预设范围的值可以通过反复试验来配置,可以使用文献中使用的常用值,也可以先通过实验观察通用向量范数或范围,然后选择一个合理的值。

对于网络中的所有层,通常都使用相同的梯度裁剪配置。不过,在某些示例中,与隐藏层相比,输出层中允许更大范围的误差梯度。

2. TensorFlow.Keras 实现
2.1 梯度范数缩放(Gradient Norm Scaling)
梯度范数缩放:在梯度向量的L2向量范数(平方和)超过阈值时,将损失函数的导数更改为具有给定的向量范数。

例如,可以将范数指定为1.0,这意味着,如果梯度的向量范数超过1.0,则向量中的值将重新缩放,以使向量范数等于1.0。在Keras中通过在优化器上指定 clipnorm 参数实现:

....
opt = SGD(lr=0.01, momentum=0.9, clipnorm=1.0)

2.2 梯度值裁剪(Gradient Value Clipping)
如果梯度值小于负阈值或大于正阈值,则梯度值剪切将损失函数的导数剪切为给定值。例如,可以将范数指定为0.5,这意味着如果梯度值小于-0.5,则将其设置为-0.5,如果梯度值大于0.5,则将其设置为0.5。通过在优化器上指定 clipvalue 参数实现:

...
opt = SGD(lr=0.01, momentum=0.9, clipvalue=0.5)

3. 实例
通过一个简单的MLP回归问题来说明梯度裁剪的作用。

3.1 梯度爆炸 MLP

from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 150

# 构造回归问题数据集
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)

# 划分训练集和验证集
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

# 定义模型
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))

# 编译模型
model.compile(loss='mean_squared_error', optimizer=SGD(lr=0.01, momentum=0.9))

# 训练模型
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)

# 评估模型
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))

# 绘制损失曲线
plt.title('Mean Squared Error')
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()

在这种情况下,该模型无法学习,从而导致对NaN值的预测。给定非常大的误差,然后在训练中针对权重更新计算出的误差梯度,模型权重会爆炸。传统的解决方案是使用标准化或归一化来重新调整目标变量。不过,本文使用替代方法–梯度修剪。
在这里插入图片描述
3.2 梯度范数缩放 MLP

from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 150

# 构造回归问题数据集
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)

# 划分训练集和验证集
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

# 定义模型
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))

# 编译模型
opt = SGD(lr=0.01, momentum=0.9, clipnorm=1.0)
model.compile(loss='mean_squared_error', optimizer=opt)

# 训练模型
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)

# 评估模型
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))

# 绘制损失曲线
plt.title('Mean Squared Error')
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()

在这里插入图片描述
该图显示了损失在20个epoch内从20000以上的大数值迅速下降到100以下的小数值。
3.3 梯度值裁剪 MLP

from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
import matplotlib.pyplot as plt
plt.rcParams['figure.dpi'] = 150

# 构造回归问题数据集
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)

# 划分训练集和验证集
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]

# 定义模型
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))

# 编译模型
opt = SGD(lr=0.01, momentum=0.9, clipvalue=5.0)
model.compile(loss='mean_squared_error', optimizer=opt)

# 训练模型
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)

# 评估模型
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))

# 绘制损失曲线
plt.title('Mean Squared Error')
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()

在这里插入图片描述

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习中,梯度裁剪gradient clipping)是一种常用的技术,用于控制梯度的范围,以避免梯度爆炸的问题。PyTorch提供了多种方式来实现梯度裁剪。 一种常见的梯度裁剪方法是通过调整梯度的范数(norm)来限制梯度的大小。可以使用PyTorch中的`torch.nn.utils.clip_grad_norm_`函数来实现梯度裁剪。以下是一个示例: ```python import torch import torch.nn as nn import torch.nn.utils as utils # 创建一个简单的模型 class MyModel(nn.Module): def __init__(self): super(MyModel, self).__init__() self.linear = nn.Linear(10, 1) # 假设有一个线性层 def forward(self, x): return self.linear(x) # 创建模型实例 model = MyModel() # 创建输入和目标张量 input = torch.randn(1, 10) target = torch.randn(1) # 前向传播 output = model(input) # 计算损失函数 loss = nn.MSELoss()(output, target) # 反向传播并计算梯度 loss.backward() # 裁剪梯度 max_norm = 1.0 # 设置梯度的最大范数 utils.clip_grad_norm_(model.parameters(), max_norm) ``` 在上述示例中,我们首先定义了一个简单的模型`MyModel`,其中包含一个线性层。然后,我们创建了一个模型实例`model`,并定义了输入和目标张量。接下来,我们进行前向传播,计算输出并计算损失函数。然后,通过调用`backward()`方法进行反向传播,计算模型参数相对于损失函数的梯度。最后,我们使用`torch.nn.utils.clip_grad_norm_`函数来裁剪模型参数的梯度,其中`max_norm`参数指定了梯度的最大范数。 除了`torch.nn.utils.clip_grad_norm_`函数外,PyTorch还提供了`torch.nn.utils.clip_grad_value_`函数,用于通过限制梯度的绝对值来进行裁剪。 希望这个示例能够帮助您理解如何在PyTorch中实现梯度裁剪。如果您有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值