【調參19】如何使用梯度裁剪(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()

在這裏插入圖片描述
該圖表明,該模型可以快速學習問題,僅在幾個訓練週期內就損失了不到100的MSE。


參考:
https://machinelearningmastery.com/how-to-avoid-exploding-gradients-in-neural-networks-with-gradient-clipping/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章