赞
踩
给定误差函数,学习率,甚至目标变量的大小,训练神经网络可能变得不稳定。训练期间权重的较大更新会导致数值上溢或下溢,通常称为梯度爆炸(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()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。