Tensorflow NaN bug?

Estoy usando TensorFlow y modifiqué el ejemplo tutorial para tomar mis imágenes RGB.

El algoritmo funciona perfectamente de fábrica en el nuevo conjunto de imágenes, hasta que repentinamente (aún convergente, con una precisión del 92% por lo general), falla con el error de que ReluGrad recibió valores no finitos. La depuración muestra que no ocurre nada inusual con los números hasta que, de repente, por razones desconocidas, se produce el error. Añadiendo

print "max W vales: %g %g %g %g"%(tf.reduce_max(tf.abs(W_conv1)).eval(),tf.reduce_max(tf.abs(W_conv2)).eval(),tf.reduce_max(tf.abs(W_fc1)).eval(),tf.reduce_max(tf.abs(W_fc2)).eval()) print "max b vales: %g %g %g %g"%(tf.reduce_max(tf.abs(b_conv1)).eval(),tf.reduce_max(tf.abs(b_conv2)).eval(),tf.reduce_max(tf.abs(b_fc1)).eval(),tf.reduce_max(tf.abs(b_fc2)).eval()) 

como código de depuración para cada ciclo, produce el siguiente resultado:

 Step 8600 max W vales: 0.759422 0.295087 0.344725 0.583884 max b vales: 0.110509 0.111748 0.115327 0.124324 Step 8601 max W vales: 0.75947 0.295084 0.344723 0.583893 max b vales: 0.110516 0.111753 0.115322 0.124332 Step 8602 max W vales: 0.759521 0.295101 0.34472 0.5839 max b vales: 0.110521 0.111747 0.115312 0.124365 Step 8603 max W vales: -3.40282e+38 -3.40282e+38 -3.40282e+38 -3.40282e+38 max b vales: -3.40282e+38 -3.40282e+38 -3.40282e+38 -3.40282e+38 

Como ninguno de mis valores es muy alto, la única forma en que puede pasar un NaN es con un 0/0 mal manejado, pero como este código no hace divisiones u operaciones similares, no veo otra explicación que esta provenga de el código TF interno.

No tengo ni idea de qué hacer con esto. ¿Alguna sugerencia? El algoritmo está convergiendo muy bien, su precisión en mi conjunto de validación estaba aumentando constantemente y acaba de alcanzar el 92.5% en la iteración 8600.

En realidad, resultó ser algo estúpido. Estoy publicando esto en caso de que alguien más se encuentre con un error similar.

 cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) 

es en realidad una forma horrible de calcular la entropía cruzada. En algunas muestras, ciertas clases podrían excluirse con certeza después de un tiempo, lo que da como resultado y_conv = 0 para esa muestra. Normalmente no es un problema ya que no estás interesado en eso, pero en la forma en que cross_entropy está escrito allí, arroja 0 * log (0) para esa muestra / clase en particular. De ahí el NaN.

Reemplazándolo con

 cross_entropy = -tf.reduce_sum(y_*tf.log(tf.clip_by_value(y_conv,1e-10,1.0))) 

resuelto todos mis problemas

En realidad, el recorte no es una buena idea, ya que evitará que el gradiente se propague hacia atrás cuando se scope el umbral. En cambio, podemos agregar un poco de constante a la salida de softmax.

 cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv + 1e-10)) 

Si y_conv es el resultado de un softmax, digamos, y_conv = tf.nn.softmax(x) , entonces una solución aún mejor es reemplazarlo con log_softmax :

 y = tf.nn.log_softmax(x) cross_entropy = -tf.reduce_sum(y_*y) 

Una alternativa libre de sesgos.

Muchas de las otras soluciones usan recorte para evitar un gradiente indefinido. Dependiendo de su problema, el recorte introduce un sesgo y puede no ser aceptable en todos los casos. Como lo demuestra el siguiente código, solo necesitamos manejar el punto de discontinuidad, no la región cercana.

Respuesta específica

 def cross_entropy(x, y, axis=-1): safe_y = tf.where(tf.equal(x, 0.), tf.ones_like(y), y) return -tf.reduce_sum(x * tf.log(safe_y), axis) def entropy(x, axis=-1): return cross_entropy(x, x, axis) 

Pero funcionó?

 x = tf.constant([0.1, 0.2, 0., 0.7]) e = entropy(x) # ==> 0.80181855 g = tf.gradients(e, x)[0] # ==> array([1.30258512, 0.60943794, 0., -0.64332503], dtype=float32) Yay! No NaN. 

(Nota: dup-cross post eliminado)

Receta general

Use un tf.where interno para asegurarse de que la función no tenga asíntota. Es decir, alterar la entrada a la función de generación de inf de modo que no se pueda crear inf. Luego use un segundo tf.where para seleccionar siempre la ruta de código válida. Es decir, implementar la condición matemática como lo haría “normalmente”, es decir, la implementación “ingenua”.

En el código de Python, la receta es:

En lugar de esto:

 tf.where(x_ok, f(x), safe_f(x)) 

Hacer esto:

 safe_x = tf.where(x_ok, x, safe_x) tf.where(x_ok, f(safe_x), safe_f(x)) 

Ejemplo

Supongamos que desea calcular:

 f(x) = { 1/x, x!=0 { 0, x=0 

Una implementación ingenua resulta en NaN en el gradiente, es decir,

 def f(x): x_ok = tf.not_equal(x, 0.) f = lambda x: 1. / x safe_f = tf.zeros_like return tf.where(x_ok, f(x), safe_f(x)) 

¿Funciona?

 x = tf.constant([-1., 0, 1]) tf.gradients(f(x), x)[0].eval() # ==> array([ -1., nan, -1.], dtype=float32) # ...bah! We have a NaN at the asymptote despite not having # an asymptote in the non-differentiated result. 

El patrón básico para evitar gradientes de NaN cuando se usa tf.where es llamar a tf.where dos veces. La parte más tf.where asegura que el resultado f(x) siempre es finito. El extremo tf.where asegura que se tf.where el resultado correcto. Para el ejemplo de ejecución, el truco se desarrolla así:

 def safe_f(x): x_ok = tf.not_equal(x, 0.) f = lambda x: 1. / x safe_f = tf.zeros_like safe_x = tf.where(x_ok, x, tf.ones_like(x)) return tf.where(x_ok, f(safe_x), safe_f(x)) 

Pero funcionó?

 x = tf.constant([-1., 0, 1]) tf.gradients(safe_f(x), x)[0].eval() # ==> array([-1., 0., -1.], dtype=float32) # ...yay! double-where trick worked. Notice that the gradient # is now a constant at the asymptote (as opposed to being NaN). 

Está intentando calcular la entropía cruzada usando la fórmula estándar. No solo el valor no está definido cuando x=0 , también es numéricamente inestable.

Es mejor usar tf.nn.softmax_cross_entropy_with_logits o si realmente desea usar fórmulas hechas a mano, para tf.clip_by_value ceros a un número muy pequeño en el registro.

Aquí está la implementación de las pérdidas de entropía cruzada binarias (sigmoideas) y categóricas (softmax) en TensorFlow 1.1:

Como se puede ver en el caso binario, consideran algunos casos especiales para lograr la estabilidad numérica:

 # The logistic loss formula from above is # x - x * z + log(1 + exp(-x)) # For x < 0, a more numerically stable formula is # -x * z + log(1 + exp(x)) # Note that these two expressions can be combined into the following: # max(x, 0) - x * z + log(1 + exp(-abs(x))) # To allow computing gradients at zero, we define custom versions of max and # abs functions. zeros = array_ops.zeros_like(logits, dtype=logits.dtype) cond = (logits >= zeros) relu_logits = array_ops.where(cond, logits, zeros) neg_abs_logits = array_ops.where(cond, -logits, logits) return math_ops.add(relu_logits - logits * labels, math_ops.log1p(math_ops.exp(neg_abs_logits)), name=name) 

Usé LSTM para secuencias largas y obtuve nan gradientes. Ninguna de estas respuestas me ayudó. Pero se me ocurrieron tres soluciones propias. Espero que sean útiles para otras personas que vinieron aquí desde la búsqueda de Google.

  1. El recorte de degradado no me ayudó porque los gradientes convirtieron nan en una actualización por lotes. En este caso, puede reemplazar nans por ceros con tales líneas:

     opt = tf.train.AdamOptimizer(args.lr) grads = opt.compute_gradients(loss) grads2 = [(tf.where(tf.is_nan(grad), tf.zeros(grad.shape), grad), var) for grad, var in grads] opt_op = opt.apply_gradients(grads2) 

    Si desea rastrear si apareció nans, puede usar este código:

     was_nan = tf.reduce_any(tf.convert_to_tensor([tf.reduce_any(tf.is_nan(g)) for g in grads])) 
  2. Reemplace LSTMCell con LayerNormBasicLSTMCell, una célula LSTM con norma de capa, algo similar a la norma de lotes entre los pasos del tiempo.

  3. Si usa el abandono recurrente del estado recurrente, puede reemplazarlo por “Omisión recurrente sin pérdida de memoria”. Código:

     LayerNormBasicLSTMCell(neurons, dropout_keep_prob=0.8) 

    Tenga en cuenta que también puede activar la característica de abandono solo sin normalización de capa:

     LayerNormBasicLSTMCell(neurons, layer_norm=False, dropout_keep_prob=0.8) 

A veces recibía nans y otras veces mientras trabajaba en una red estándar de feed-forward. Anteriormente utilicé un código similar de TensorFlow y funcionó bien.

Resulta que importé los nombres de las variables por accidente. Entonces, tan pronto como se seleccionó la primera fila (los nombres de las variables) en un lote, comenzaron las pérdidas nan. ¿Tal vez estar atento a eso?

Además de todas las excelentes respuestas anteriores, agregaré la mía. Es un escenario menos común de encontrar, pero causa NaN: dividir por cero .

En mi red para una tarea NLP, hay una capa que hace un pool promedio . A saber, cada dato es una secuencia de tokens. Mi capa realiza una incrustación de token y luego calcula el promedio del vector incrustado.

El cálculo promedio está codificado como

 tf.reduce_sum(embedded)/tf.reduce_sum(tf.not_equal(input, pad)) 

Aquí el pad es un token ficticio que uso en el procesamiento por lotes.

Ahora bien, si algunos datos contienen una lista de token vacía (por cualquier razón), su longitud (el denominador en el fragmento de código anterior) sería 0. Entonces provoca un problema de división por cero y el NaN permanecerá en las siguientes capas / pasos de optimización .

En caso de que alguien se encuentre con este problema, usé tf.where para suavizar esa longitud:

 sum_embedding = tf.reduce_sum(embedded, 1) embedding_length = tf.reduce_sum(tf.cast(tf.not_equal(input, pad), dtype=tf.float32), axis=1, keep_dims=True) embedding_length_smoothed = tf.where(tf.greater(embedding_length, 0.0), embedding_length, tf.ones(tf.shape(embedding_length))) avg_embedding = sum_embedding / embedding_length_smoothed 

Básicamente, esto trata todos los datos con una lista de tokens de longitud 0 de longitud 1 y evita el problema de NaN.