ChatGPT解决这个技术问题 Extra ChatGPT

如何在 TensorFlow 中应用渐变裁剪?

考虑 example code

我想知道如何在 RNN 上的这个网络上应用梯度裁剪,那里有可能发生梯度爆炸。

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

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

    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 是输入而不是 grad 什么是要剪裁的?

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


S
Styrke

梯度裁剪需要在计算梯度之后,但在应用它们更新模型参数之前进行。在您的示例中,这两件事都由 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)

Styrke,谢谢你的帖子。您知道实际运行优化器迭代的下一步是什么吗?通常,优化器被实例化为 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) ,然后优化器的迭代以 optimizer.run() 完成,但在这种情况下使用 optimizer.run() 似乎不起作用?
好的 optimizer.apply_gradients(capped_gvs) 需要分配给某些东西 x = optimizer.apply_gradients(capped_gvs) 然后在您的会话中您可以训练为 x.run(...)
nice edit suggestion 向@remi-cuingnet 大喊。 (不幸的是被仓促的审稿人拒绝了)
这给了我UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.所以我的稀疏渐变以某种方式转换为密集。知道如何克服这个问题吗?
实际上,剪裁渐变的正确方法(根据 tensorflow 文档、计算机科学家和逻辑)是使用 tf.clip_by_global_norm,正如@danijar 所建议的那样
d
danijar

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

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))

clip_by_global_norm() 的好例子!这在 tensorflow 文档中也被描述为 the correct way to perform gradient clippingtensorflow.org/versions/r1.2/api_docs/python/tf/…
@Escachator 这是经验性的,将取决于您的模型和可能的任务。我所做的是将梯度范数 tf.global_norm(gradients) 可视化以查看它的通常范围,然后将其剪裁一点以防止异常值扰乱训练。
您是否仍会在之后调用 opt.minimize(),还是会像其他答案的某些评论中所建议的那样调用诸如 opt.run() 之类的不同名称?
@reese0106 不,optimizer.minimize(loss) 只是计算和应用梯度的简写。您可以使用 sess.run(optimize) 运行我的答案中的示例。
因此,如果我在实验函数中使用 tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op),那么您的 optimize 将替换我的 train_op 对吗?现在我的train_op = optimizer.minimize(loss, global_step=global_step)),所以我正在努力确保我做出相应的调整......
N
Nicolas Gervais

tf.keras 很容易!

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

此优化器会将所有渐变裁剪为 [-1.0, 1.0] 之间的值。

请参阅 docs


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

这实际上是正确的 explained in the documentation.

调用 minimize() 负责计算梯度并将它们应用于变量。如果您想在应用梯度之前对其进行处理,您可以分三步使用优化器: 使用 compute_gradients() 计算梯度。根据需要处理渐变。使用 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


您是否仍会在之后调用 opt.minimize(),还是会像其他答案的某些评论中所建议的那样调用诸如 opt.run() 之类的不同名称?
@reese0106 不,例如,您需要将 opt.apply_gradients(...) 分配给像 train_step 这样的变量(就像您对 opt.minimize() 所做的那样。在您的主循环中,您像往常一样调用它来训练 sess.run([train_step, ...], feed_dict)
请记住,梯度被定义为模型中所有参数的损失导数的向量。 TensorFlow 将其表示为 Python 列表,其中包含每个变量及其梯度的元组。这意味着剪裁梯度范数,您不能单独剪裁每个张量,您需要立即考虑列表(例如使用 tf.clip_by_global_norm(list_of_tensors))。
链接上的404
k
kmario23

对于那些想了解渐变裁剪(按规范)的想法的人:

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

设梯度为 g,max_norm_threshold 为 j。

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

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

这是在 tf.clip_by_norm 中完成的实现


如果我需要手动选择阈值,有什么常用的方法吗?
这是一些论文中提出的一种黑魔法。否则,您必须进行大量实验并找出哪个效果更好。
L
LouYu

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


不支持混合精度
仅适用于 tensorflow 1.x
R
Raj

梯度裁剪基本上有助于在梯度爆炸或消失的情况下。假设你的损失太高,这将导致指数梯度流过网络,这可能会导致 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)