¿Cómo aplicar el recorte de degradado en TensorFlow?

96

Considerando el código de ejemplo .

Me gustaría saber cómo aplicar el recorte de gradiente en esta red en el RNN donde existe la posibilidad de explosiones de gradientes.

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

Este es un ejemplo que podría usarse, pero ¿dónde lo introduzco? En la def de 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)

Pero esto no tiene sentido ya que el tensor _X es la entrada y no el grad. ¿Qué se va a recortar?

¿Tengo que definir mi propio Optimizador para esto o hay una opción más simple?

Fanático del Arsenal
fuente

Respuestas:

143

El recorte de degradado debe ocurrir después de calcular los degradados, pero antes de aplicarlos para actualizar los parámetros del modelo. En su ejemplo, ambas cosas son manejadas por el AdamOptimizer.minimize()método.

Para recortar sus degradados, deberá calcularlos, recortarlos y aplicarlos explícitamente como se describe en esta sección en la documentación de la API de TensorFlow . Específicamente, deberá sustituir la llamada al minimize()método con algo como lo siguiente:

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
fuente
4
Styrke, gracias por la publicación. ¿Sabes cuáles son los siguientes pasos para ejecutar una iteración del optimizador? Por lo general, se crea una instancia de un optimizador como optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) y luego se realiza una iteración del optimizador como, optimizer.run()pero el uso optimizer.run()no parece funcionar en este caso.
applecider
6
Ok, lo tengo optimizer.apply_gradients(capped_gvs)que debe asignarse a algo, x = optimizer.apply_gradients(capped_gvs)luego, dentro de su sesión, puede entrenar comox.run(...)
applecider
3
Saludos a @ remi-cuingnet por la sugerencia de edición agradable . (Que desafortunadamente fue rechazado por críticos apresurados)
Styrke
Esto me da UserWarning: Converting sparse IndexedSlices to a dense Tensor with 148331760 elements. This may consume a large amount of memory.De alguna manera mis escasos gradientes se convierten en densos. ¿Alguna idea de cómo superar este problema?
Pekka
8
En realidad, la forma correcta de recortar gradientes (de acuerdo con los documentos de tensorflow, los científicos informáticos y la lógica) es con tf.clip_by_global_norm, como lo sugiere @danijar
gdelab
116

A pesar de lo que parece ser popular, probablemente desee recortar todo el degradado según su norma global:

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

Recortar cada matriz de degradado cambia individualmente su escala relativa, pero también es posible:

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

En TensorFlow 2, una cinta calcula los gradientes, los optimizadores provienen de Keras y no necesitamos almacenar la operación de actualización porque se ejecuta automáticamente sin pasarla a una sesión:

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))
danijar
fuente
10
Buen ejemplo con clip_by_global_norm()! Esto también se describe the correct way to perform gradient clippingen los documentos de tensorflow
MZHm
9
@Escachator Es empírico y dependerá de su modelo y posiblemente de la tarea. Lo que hago es visualizar la norma de gradiente tf.global_norm(gradients)para ver su rango habitual y luego recortar un poco más para evitar que los valores atípicos estropeen el entrenamiento.
danijar
1
¿Seguiría llamando opt.minimize()después o llamaría algo diferente opt.run()como se sugiere en algunos de los comentarios sobre otras respuestas?
reese0106
3
@ reese0106 No, optimizer.minimize(loss)es solo una forma abreviada de calcular y aplicar los degradados. Puede ejecutar el ejemplo en mi respuesta con sess.run(optimize).
danijar
1
Entonces, si estuviera usando tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)una función de experimento, optimize¿reemplazaría la mía train_opcorrecta? Ahora mismo train_op = optimizer.minimize(loss, global_step=global_step)), estoy tratando de asegurarme de ajustarme en consecuencia ...
reese0106
10

En realidad, esto se explica correctamente en la documentación. :

Llamar a minimizar () se encarga tanto de calcular los gradientes como de aplicarlos a las variables. Si desea procesar los degradados antes de aplicarlos, puede utilizar el optimizador en tres pasos:

  • Calcule los gradientes con compute_gradients ().
  • Procese los gradientes como desee.
  • Aplique los degradados procesados ​​con apply_gradients ().

Y en el ejemplo que proporcionan, utilizan estos 3 pasos:

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

Aquí MyCapperhay cualquier función que limite su degradado. La lista de funciones útiles (distintas de tf.clip_by_value()) está aquí .

Salvador Dalí
fuente
¿Seguiría llamando opt.minimize()después o llamaría algo diferente opt.run()como se sugiere en algunos de los comentarios sobre otras respuestas?
reese0106
@ reese0106 No, necesita asignar el opt.apply_gradients(...)a una variable como, train_steppor ejemplo (como lo haría para opt.minimize(). Y en su bucle principal lo llama como de costumbre para entrenarsess.run([train_step, ...], feed_dict)
dsalaj
Tenga en cuenta que el gradiente se define como el vector de derivadas de la pérdida wrt para todos los parámetros del modelo. TensorFlow lo representa como una lista de Python que contiene una tupla para cada variable y su gradiente. Esto significa recortar la norma de degradado, no puede recortar cada tensor individualmente, debe considerar la lista de una vez (por ejemplo, usar tf.clip_by_global_norm(list_of_tensors)).
danijar
8

Para aquellos que quieran entender la idea del recorte de gradiente (por norma):

Siempre que la norma de gradiente es mayor que un umbral particular, recortamos la norma de gradiente para que permanezca dentro del umbral. Este umbral a veces se establece en 5.

Deje que el gradiente sea gy el max_norm_threshold sea j .

Ahora, si || g || > j , hacemos:

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

Esta es la implementación realizada en tf.clip_by_norm

kmario23
fuente
si necesito seleccionar el umbral a mano, ¿existe algún método común para hacerlo?
ningyuwhut
Esta es una especie de magia negra sugerida en algunos artículos. De lo contrario, tendrá que hacer muchos experimentos y descubrir cuál funciona mejor.
kmario23
4

En mi opinión, la mejor solución es envolver su optimizador con el decorador de estimadores de 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)

De esta manera, solo tiene que definir esto una vez y no ejecutarlo después de cada cálculo de gradientes.

Documentación: https://www.tensorflow.org/api_docs/python/tf/contrib/estimator/clip_gradients_by_norm

Ido Cohn
fuente
2

El recorte de gradiente básicamente ayuda en caso de explosión o desaparición de gradientes. Diga que su pérdida es demasiado alta, lo que resultará en gradientes exponenciales para fluir a través de la red, lo que puede dar como resultado valores de Nan. Para superar esto, recortamos los degradados dentro de un rango específico (-1 a 1 o cualquier rango según la condición).

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

donde grads _and_vars son los pares de gradientes (que calcula a través de tf.compute_gradients) y sus variables a las que se aplicarán.

Después del recorte, simplemente aplicamos su valor usando un optimizador. optimizer.apply_gradients(clipped_value)

Raj
fuente