如何在 TensorFlow 中应用梯度裁剪?

How to apply gradient clipping in TensorFlow?

考虑 example code

我想知道如何在可能出现梯度爆炸的 RNN 网络上应用梯度裁剪。

tf.clip_by_value(t, clip_value_min, clip_value_max, name=None)

这是一个可以使用的示例,但是我在哪里介绍呢? 在 RNN 的定义中

    lstm_cell = rnn_cell.BasicLSTMCell(n_hidden, forget_bias=1.0)
    # Split data because rnn cell needs a list of inputs for the RNN inner loop
    _X = tf.split(0, n_steps, _X) # n_steps
tf.clip_by_value(_X, -1, 1, name=None)

但这没有意义,因为张量 _X 是输入,而不是要剪裁的梯度?

我必须为此定义自己的优化器还是有更简单的选项?

梯度裁剪需要在计算梯度之后,但在应用它们更新模型参数之前发生。在您的示例中,这两件事都由 AdamOptimizer.minimize() 方法处理。

为了裁剪渐变,您需要按照 this section in TensorFlow's API documentation 中所述明确计算、裁剪和应用它们。具体来说,您需要将对 minimize() 方法的调用替换为如下内容:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
gvs = optimizer.compute_gradients(cost)
capped_gvs = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gvs]
train_op = optimizer.apply_gradients(capped_gvs)

尽管看起来很流行,但您可能希望根据其全局规范裁剪整个渐变:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimize = optimizer.apply_gradients(zip(gradients, variables))

单独裁剪每个梯度矩阵会改变它们的相对比例,但也是可能的:

optimizer = tf.train.AdamOptimizer(1e-3)
gradients, variables = zip(*optimizer.compute_gradients(loss))
gradients = [
    None if gradient is None else tf.clip_by_norm(gradient, 5.0)
    for gradient in gradients]
optimize = optimizer.apply_gradients(zip(gradients, variables))

在 TensorFlow 2 中,磁带计算梯度,优化器来自 Keras,我们不需要存储更新操作,因为它会自动运行而无需将其传递给会话:

optimizer = tf.keras.optimizers.Adam(1e-3)
# ...
with tf.GradientTape() as tape:
  loss = ...
variables = ...
gradients = tape.gradient(loss, variables)
gradients, _ = tf.clip_by_global_norm(gradients, 5.0)
optimizer.apply_gradients(zip(gradients, variables))

这其实是正确的explained in the documentation.:

Calling minimize() takes care of both computing the gradients and applying them to the variables. If you want to process the gradients before applying them you can instead use the optimizer in three steps:

  • Compute the gradients with compute_gradients().
  • Process the gradients as you wish.
  • Apply the processed gradients with apply_gradients().

在他们提供的示例中,他们使用了这 3 个步骤:

# Create an optimizer.
opt = GradientDescentOptimizer(learning_rate=0.1)

# Compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)

# grads_and_vars is a list of tuples (gradient, variable).  Do whatever you
# need to the 'gradient' part, for example cap them, etc.
capped_grads_and_vars = [(MyCapper(gv[0]), gv[1]) for gv in grads_and_vars]

# Ask the optimizer to apply the capped gradients.
opt.apply_gradients(capped_grads_and_vars)

这里 MyCapper 是任何可以限制渐变的函数。有用的函数列表(除了 tf.clip_by_value())是 here.

对于那些想了解梯度裁剪(按规范)概念的人:

只要梯度范数大于特定阈值,我们就会裁剪梯度范数,使其保持在阈值内。此阈值有时设置为 5.

设梯度为g,max_norm_threshold为j

现在,如果||g|| > j ,我们做:

g = ( j * g ) / || g||

这是在tf.clip_by_norm

中完成的实现

梯度裁剪基本上有助于防止爆炸或消失gradients.Say你的损失太高,这将导致指数梯度流过网络,这可能会导致 Nan 值。为了克服这个问题,我们将渐变剪裁在特定范围内(-1 到 1 或根据条件的任何范围)。

clipped_value=tf.clip_by_value(grad, -range, +range), var) for grad, var in grads_and_vars

其中 grads _and_vars 是梯度对(您通过 tf.compute_gradients 计算)及其将应用于的变量。

剪辑后,我们只需使用优化器应用它的值。 optimizer.apply_gradients(clipped_value)

IMO 最好的解决方案是用 TF 的估计器装饰器包装你的优化器 tf.contrib.estimator.clip_gradients_by_norm:

original_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
optimizer = tf.contrib.estimator.clip_gradients_by_norm(original_optimizer, clip_norm=5.0)
train_op = optimizer.minimize(loss)

这样你只需要定义一次,而不是运行在每次梯度计算之后。

文档: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm

tf.keras很容易!

optimizer = tf.keras.optimizers.Adam(clipvalue=1.0)

此优化器会将所有梯度裁剪到 [-1.0, 1.0].

之间的值

参见docs