在較深的網絡,如多層CNN或者非常長的RNN,由於求導的鏈式法則,有可能會出現梯度消失(Gradient Vanishing)或梯度爆炸(Gradient Exploding )的問題。
原理
問題:為什么梯度爆炸會造成訓練時不穩定而且不收斂?
梯度爆炸,其實就是偏導數很大的意思。回想我們使用梯度下降方法更新參數:
損失函數的值沿着梯度的方向呈下降趨勢,然而,如果梯度(偏導數)很大話,就會出現函數值跳來跳去,收斂不到最值的情況,如圖:
當然出現這種情況,其中一種解決方法是,將學習率αα設小一點,如0.0001。
這里介紹梯度裁剪(Gradient Clipping)的方法,對梯度進行裁剪,論文提出對梯度的L2范數進行裁剪,也就是所有參數偏導數的平方和再開方。
TensorFlow代碼
方法一:
optimizer = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5) grads = optimizer.compute_gradients(loss) for i, (g, v) in enumerate(grads): if g is not None: grads[i] = (tf.clip_by_norm(g, 5), v) # 閾值這里設為5 train_op = optimizer.apply_gradients(grads)
其中
optimizer.compute_gradients()
返回的是正常計算的梯度,是一個包含(gradient, variable)的列表。
tf.clip_by_norm(t, clip_norm)
返回裁剪過的梯度,維度跟t一樣。
不過這里需要注意的是,這里范數的計算不是根據全局的梯度,而是一部分的。
方法二:
optimizer = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5) grads, variables = zip(*optimizer.compute_gradients(loss)) grads, global_norm = tf.clip_by_global_norm(grads, 5) train_op = optimizer.apply_gradients(zip(grads, variables))
這里是計算全局范數,這才是標准的。不過缺點就是會慢一點,因為需要全部梯度計算完之后才能進行裁剪。
總結
當你訓練模型出現Loss值出現跳動,一直不收斂時,除了設小學習率之外,梯度裁剪也是一個好方法。
然而這也說明,如果你的模型穩定而且會收斂,但是效果不佳時,那這就跟學習率和梯度爆炸沒啥關系了。因此,學習率的設定和梯度裁剪的閾值並不能提高模型的准確率。