web-dev-qa-db-fra.com

Tensorflow NaN bug?

J'utilise TensorFlow et j'ai modifié l'exemple tutorial pour prendre mes images RVB.

L'algorithme fonctionne parfaitement avec le nouvel ensemble d'images. Jusqu'à ce qu'il apparaisse soudainement (toujours convergent, la précision est d'environ 92%), il se bloque avec l'erreur que ReluGrad a reçu des valeurs non finies. Le débogage montre qu'il ne se passe rien d'inhabituel avec les nombres jusqu'à ce que l'erreur soit générée, pour une raison inconnue. Ajouter

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

en tant que code de débogage pour chaque boucle, donne la sortie suivante:

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

Puisqu'aucune de mes valeurs n'est très élevée, la seule façon dont NaN peut se produire est par un 0/0 mal géré, mais comme ce code de tutoriel ne fait pas de divisions ou d'opérations similaires, je ne vois pas d'autre explication que celle-ci. le code interne TF. 

Je ne sais pas quoi faire avec ça. Aucune suggestion? L'algorithme converge bien, sa précision sur mon jeu de validation a été en constante augmentation et a atteint 92,5% à l'itération 8600.

52
user1111929

En fait, cela s’est avéré être quelque chose de stupide. Je publie ceci au cas où quelqu'un d'autre rencontrerait une erreur similaire.

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

est en fait une façon horrible de calculer l’entropie croisée. Dans certains échantillons, certaines classes pourraient être exclues avec certitude après un certain temps, entraînant y_conv = 0 pour cet échantillon. Ce n’est normalement pas un problème puisque cela ne vous intéresse pas, mais dans la façon dont cross_entropy est écrit ici, cela donne 0 * log (0) pour cet échantillon/cette classe en particulier. D'où le NaN.

Le remplacer par 

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

résolu tous mes problèmes.

124
user1111929

En fait, le découpage n'est pas une bonne idée car cela empêchera le gradient de se propager en arrière lorsque le seuil est atteint. Au lieu de cela, nous pouvons ajouter un peu de constante à la sortie de softmax.

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

Si y_conv est le résultat d'un softmax, disons, y_conv = tf.nn.softmax(x), une solution encore meilleure consiste à le remplacer par log_softmax:

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

Une alternative sans préjugés.

Beaucoup d'autres solutions utilisent le découpage pour éviter un dégradé indéfini. Selon votre problème, le découpage introduit un biais et peut ne pas être acceptable dans tous les cas. Comme le code suivant le démontre, il suffit de gérer le point de discontinuité - pas la région proche de celui-ci.

Réponse spécifique

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)

Mais ça a marché?

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.

(Remarque: supprimé dup cross-post .) 

Recette générale

Utilisez un tf.where interne pour vous assurer que la fonction n'a pas d'asymptote . Autrement dit, modifiez l'entrée dans la fonction générant inf de sorte qu'aucun inf ne puisse être créé . Ensuite, utilisez un second tf.where pour toujours sélectionner le chemin de code valide. . Autrement dit, implémentez la condition mathématique comme vous le feriez "normalement", c'est-à-dire l'implémentation "naïve".

En code Python, la recette est la suivante:

Au lieu de cela:

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

Faire ceci:

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

Exemple

Supposons que vous souhaitiez calculer:

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

Une implémentation naïve se traduit par des NaN dans le gradient, c'est-à-dire

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

Est-ce que ça marche?

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.

Le modèle de base pour éviter les dégradés de NaN lors de l’utilisation de tf.where consiste à appeler tf.where à deux reprises. Le tf.where le plus interne garantit que le résultat f(x) est toujours fini. Le tf.where le plus externe garantit que le résultat correct est choisi. Pour l'exemple en cours, l'astuce se déroule comme suit:

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

Mais ça a marché?

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).
13
jvdillon

Voici l'implémentation des pertes d'entropie croisée binaires (sigmoïde) et catégoriques (softmax) dans TensorFlow 1.1:

Comme on peut le voir dans le cas binaire, ils considèrent certains cas particuliers pour atteindre la stabilité numérique:

# 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)
1
Lenar Hoyt

Vous essayez de calculer entropie croisée en utilisant la formule standard. Non seulement la valeur est indéfinie lorsque x=0, mais elle est également instable numériquement.

Il est préférable d'utiliser tf.nn.softmax_cross_entropy_with_logits ou si vous voulez vraiment utiliser une formule conçue à la main, vers tf.clip_by_value zéros à un très petit nombre dans le journal. 

1
Salvador Dali

Outre toutes les bonnes réponses ci-dessus, je vais ajouter les miennes. C'est un scénario moins courant, mais provoque NaN: divise par zéro .

Dans mon réseau pour une tâche NLP, il y a une couche qui fait pooling moyen. À savoir, chaque donnée est une séquence de jetons. Ma couche incorpore un jeton, puis calcule la moyenne du vecteur incorporé. 

Le calcul de la moyenne est codé comme 

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

Ici pad est un jeton factice que j'utilise dans le traitement par lots. 

Maintenant, si certaines données contiennent une liste de jetons vide (quelle que soit la raison), sa longueur (le dénominateur dans l'extrait de code ci-dessus) serait 0. Ensuite, un division par zéro est généré et le NaN restera dans tous les éléments suivants couches/étapes d'optimisation.

Au cas où quelqu'un rencontrerait ce problème, j'ai utilisé tf.where pour lisser ces longueurs: 

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

Cela traite essentiellement toutes les données avec une liste de jetons de longueur 0 comme étant de longueur 1, et évite le problème NaN.

0
Camuslu

J'ai utilisé LSTM pour de longues séquences et j'ai obtenu des gradients nan. Aucune de ces réponses ne m'a aidé. Mais je suis venu avec trois solutions propres. J'espère qu'ils seront utiles à d'autres personnes qui sont venues ici à partir de la recherche Google.

  1. La coupure de gradient ne m'a pas aidée, car les gradients ont tourné à la vitesse nan en une mise à jour par lot. Dans ce cas, vous pouvez remplacer nans par des zéros avec ces lignes:

    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 vous voulez suivre si des nans sont apparus, vous pouvez utiliser ce code:

    was_nan = tf.reduce_any(tf.convert_to_tensor([tf.reduce_any(tf.is_nan(g)) for g in grads]))
    
  2. Remplacez LSTMCell par LayerNormBasicLSTMCell - une cellule LSTM avec norme de couche - quelque chose de similaire à la norme de traitement par lots entre les pas de temps.

  3. Si vous utilisez des abandons d'état récurrents réguliers, vous pouvez le remplacer par "Abandon récurrent sans perte de mémoire". Code:

    LayerNormBasicLSTMCell(neurons, dropout_keep_prob=0.8)
    

    Notez que vous pouvez également activer la fonctionnalité de suppression uniquement sans normalisation de couche:

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

J'ajouterai ici l'un de mes problèmes précédents avec NaNs. J'utilisais la fonction sigmoïde comme activation de la dernière couche de mon réseau. Cependant, la fonction d'activation sigmoïde utilise la fonction exponentielle à calculer et j'ai obtenu de très gros nombres entrant dans le sigmoïde.

Il en a résulté des gradients infinis et certains NaN ont commencé à apparaître.

0
Joseph Budin

Je recevais parfois des nans et pas d’autres fois lorsque je travaillais sur un réseau à feed-forward standard. J'ai déjà utilisé du code TensorFlow similaire et cela a bien fonctionné.

Il s'avère que j'ai importé les noms de variables par accident. Ainsi, dès que la première ligne (les noms de variables) a été sélectionnée dans un lot, les nan pertes ont commencé. Peut-être garder un œil sur ça?

0
tf.nn.michael

Parfois, vous utilisez la fonction tf.sqrt() sans y ajouter une petite constante 1e-10, ce qui provoque ce problème nan.

0
jmir

J'utilise Tensorflow Estimator, qui, je crois, explique ces divisions par zéro et d'autres problèmes de stabilité numérique, et obtient parfois cette erreur (ERROR:tensorflow:Model diverged with loss = NaN during training). La plupart du temps, lorsque j'obtiens ceci, c'est parce que mes entrées incluent nans. Donc: assurez-vous que vos images (ou ce que vous utilisez) n’ont pas de valeurs NaN cachées quelque part.

0
rodrigo-silveira