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

117

考虑这个示例代码

我想知道如何在可能出现梯度爆炸的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是输入而不是要剪辑的梯度?

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

8个回答

155

梯度裁剪需要在计算梯度后但更新模型参数之前进行。在您的示例中,这两个步骤都由AdamOptimizer.minimize()方法处理。

为了裁剪您的梯度,您需要按照TensorFlow API文档中的此部分描述进行明确计算、裁剪和应用梯度。具体而言,您需要用以下内容替换对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)

4
Styrke,谢谢你的帖子。你知道实际运行优化器的下一步是什么吗?通常情况下,优化器被实例化为optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost),然后通过optimizer.run()来运行一次迭代,但是在这种情况下使用optimizer.run()似乎无法正常工作? 答:Styrke,感谢您的发布。您知道下一步实际运行优化器的步骤吗?通常情况下,您可以通过以下方式实例化优化器:optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) ,然后运行一次迭代的方法是 sess.run(optimizer),但是在这种情况下使用optimizer.run()似乎不起作用? - applecider
6
好的,optimizer.apply_gradients(capped_gvs)需要被赋值给某个变量,比如 x = optimizer.apply_gradients(capped_gvs),然后在会话中你可以这样进行训练:x.run(...) - applecider
5
向 @remi-cuingnet 大声喊话,感谢他/她提供的不错的编辑建议。(不幸地被匆忙的评审人员拒绝了) - Styrke
如果您在使用“None”梯度时遇到问题,请参考以下链接: https://dev59.com/v5rga4cB1Zd3GeqPmm5e - patapouf_ai
8
实际上,根据TensorFlow文档、计算机科学家和逻辑的建议,正确剪裁梯度的方法是使用tf.clip_by_global_norm,正如@danijar所建议的那样。 - gdelab
显示剩余5条评论

130

尽管目前似乎很流行,但你可能想根据其全局范数剪切整个梯度:

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 中,一个 tape 计算梯度,优化器来自 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))

11
很好的clip_by_global_norm()示例!这也被描述为在TensorFlow文档中执行梯度裁剪的正确方法:https://www.tensorflow.org/versions/r1.2/api_docs/python/tf/clip_by_global_norm - MZHm
9
这是经验性的,并且会取决于您的模型和可能的任务。我的做法是将梯度范数 tf.global_norm(gradients) 可视化,以查看它的常规范围,然后剪裁略高于该范围的值,以防止异常值影响训练。 - danijar
1
你会仍然调用 opt.minimize() 吗?还是像其他答案的评论中建议的那样调用不同的函数,比如 opt.run() - reese0106
3
不,optimizer.minimize(loss)只是计算并应用梯度的一种简便方式。你可以使用 sess.run(optimize) 运行我答案中的示例。 - danijar
1
如果我在实验函数中使用 tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op),那么你的 optimize 会替换我的 train_op,对吗?目前我的 train_op = optimizer.minimize(loss, global_step=global_step)),所以我想确保我做出相应的调整... - reese0106
显示剩余9条评论

19

tf.keras 很容易上手!

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

此优化器将所有梯度裁剪到[-1.0, 1.0]的范围内。

请参阅文档


3
此外,如果我们使用自定义训练并使用 optimizer.apply_gradients,则在调用此方法之前需要剪辑梯度。在这种情况下,我们需要 gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients],然后跟随 .apply_graidents - Innat
2
它还支持 clipnorm 和显然的 global_clipnormoptimizer = tf.keras.optimizers.Adam(global_clipnorm=5.0) - James Hirschorn

10

实际上,文档中已经很好地解释了这一点:

调用minimize()会同时计算梯度并将其应用于变量。如果您想在应用梯度之前处理它们,可以使用优化器的三个步骤:

  • 使用compute_gradients()计算梯度。
  • 按照您的意愿处理梯度。
  • 使用apply_gradients()应用处理后的梯度。

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

# 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()之外,其他有用的函数列表在这里


你会在调用其他答案中的评论所建议的那样,仍然调用 opt.minimize(),还是会调用类似于 opt.run() 的不同函数? - reese0106
@reese0106 不,你需要将 opt.apply_gradients(...) 分配给一个变量,比如 train_step(就像你为 opt.minimize() 做的那样)。然后在你的主循环中,你可以像平常一样调用它来训练:sess.run([train_step, ...], feed_dict) - dsalaj
请注意,梯度被定义为模型中所有参数相对于损失的导数向量。TensorFlow将其表示为一个Python列表,其中包含每个变量及其梯度的元组。这意味着要剪辑梯度范数,您不能单独剪辑每个张量,而需要一次考虑整个列表(例如使用tf.clip_by_global_norm(list_of_tensors))。 - danijar
链接出现404错误。 - John Glen

9

对于那些想要了解梯度截断(按范数)思想的人:

每当梯度范数大于特定阈值时,我们就会截断梯度范数,使其保持在该阈值内。此阈值有时设置为 5

让梯度为g,最大范数阈值为j

现在,如果||g|| > j,我们执行以下操作:

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

这是在tf.clip_by_norm中实现的。


如果我需要手动选择阈值,是否有常见的方法来做到这一点? - ningyuwhut
这有点像某些论文中建议的黑魔法。否则,你必须进行大量实验并找出哪种方法更有效。 - kmario23

5

我认为最好的解决方案是使用TF的estimator装饰器包装您的优化器 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

不支持混合精度。 - Tae Soo Kim
仅适用于tensorflow 1.x。 - Jan Kaifer

3
梯度裁剪基本上有助于处理爆炸或消失梯度的情况。例如,如果您的损失值过高,将导致指数梯度流经网络,这可能会导致NaN值。为克服这一点,我们在特定范围内裁剪梯度(-1到1或根据条件的任何范围)。
其中grads_and_vars是通过tf.compute_gradients计算的梯度对和它们将被应用的变量对。
裁剪后,我们只需使用优化器应用其值即可。 optimizer.apply_gradients(clipped_value)

1

方法1

如果您正在使用自定义训练循环训练模型,则一个更新步骤将如下所示:

'''
 for loop over full dataset
 X -> training samples
 y -> labels
'''
optimizer = tf.keras.optimizers.Adam()
for x, y in train_Data:
    with tf.GradientTape() as tape:
            prob = model(x, training=True)
            # calculate loss
            train_loss_value = loss_fn(y, prob)
        
        # get gradients
        gradients = tape.gradient(train_loss_value, model.trainable_weights)
        # clip gradients if you want to clip by norm
        gradients = [(tf.clip_by_norm(grad, clip_norm=1.0)) for grad in gradients]
        # clip gradients via values
        gradients = [(tf.clip_by_value(grad, clip_value_min=-1.0, clip_value_max=1.0)) for grad in gradients]
        # apply gradients
        optimizer.apply_gradients(zip(gradients, model.trainable_weights))

方法二

或者你也可以直接将上面代码中的第一行替换为以下内容

# for clipping by norm
optimizer = tf.keras.optimizers.Adam(clipnorm=1.0)
# for clipping by value
optimizer = tf.keras.optimizers.Adam(clipvalue=0.5)

第二种方法也适用于使用 model.compile -> model.fit 管道的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接